Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ versioning theo [SemVer](https://semver.org). **8sync rule:** mỗi PR cập nh

## [Unreleased]
### Fixed
- **The critic now actually answers for an away owner: rooms ask through AskUserQuestion.** Seen live on 2026-10-18:
- `delegate` was on (60 s), yet `su-code/DECISIONS.md` stayed empty. The R&D room had asked about BiRefNet as plain text at the end of a turn and waited. The delegate hook only sees an `AskUserQuestion` call, so the critic never got the question.
- Every room's `/goal`, loop tick and new `.claude/loop.md` now say: ask the owner through `AskUserQuestion` (the recommended option first), never in prose before stopping.
- Rooms already running pick this up after `8sync ccc auto up --clear-ss`. An existing `.claude/loop.md` is never overwritten; add the two lines from a fresh one by hand if you keep your own.
- **One Ship question per project.** The BA room shares the backlog lane with the lead and also asked the owner to Ship PR 20. Only the lead asks Ship now.
- **HQ explains why a Ship question waits.** It shows "Câu Ship (merge, release, deploy): chỉ anh duyệt", meaning the critic never answers it, however long the owner is away. A room waiting on a question typed as plain text is marked as one the critic cannot see. Ship staying with the owner is unchanged and intended.
### Fixed
- **The site catches up with v0.100.6–v0.100.10.**
- HQ's tabs (Tổng quan, Board, Flow, Preview) and the Máy chip are listed under "Open HQ".
- The full team lists `arch`, with a pill and a short note on what it does (ADR from The System Design Primer before an `arch` card is built, a daily sourced radar, measured scale).
Expand Down
12 changes: 11 additions & 1 deletion apps/hq-ui/components/hq/ask-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ function PermitCard({ r, p, sid, now, token }: { r: AskRoom; p: Permit; sid: str
}

/** What happens if the owner does not click: the hook decided it when it posted the request. */
/** Ship (merge, release, deploy) is the owner's alone: the critic never answers it, however long the owner is away. */
export const SHIP_NOTE = "Câu Ship (merge, release, deploy): chỉ anh duyệt. Critic không trả lời thay câu này dù anh vắng bao lâu.";
/** A question typed as text, not through AskUserQuestion: the delegate hook cannot see it. */
export const PROSE_NOTE = "Phòng hỏi bằng chữ thường nên critic không trả lời thay được.";

export function isShip(a: { kind: string; questions?: { header?: string | null }[] } | null | undefined): boolean {
return a?.kind === "question" && (a.questions ?? []).some((q) => (q.header ?? "").trim().toLowerCase() === "ship");
}

export function silence(p: Pick<Permit, "then" | "why">, left: number): string {
if (p.then === "allow") {
return left > 0 ? `Không bấm thì sau ${left} s phòng tự cho phép và làm tiếp (việc thường trong dự án).` : "Hết giờ: phòng đã tự cho phép và làm tiếp.";
Expand Down Expand Up @@ -195,14 +204,15 @@ function AskCard({ r, now, token, onOpen }: { r: AskRoom; now: number; token: st
</ol>
</div>
))}
{isShip(a) && <p className="text-xs font-medium text-st-needs">{SHIP_NOTE}</p>}
</div>
) : a?.kind === "permission" ? (
<p className="mt-2 max-w-[75ch] text-sm">
<span className="font-mono text-xs text-muted-foreground">{a.tool}</span>
{a.what && <span className="ml-2">{a.what}</span>}
</p>
) : (
<p className="mt-2 text-sm text-muted-foreground">{r.waiting ?? "Claude Code đang chờ một câu trả lời"}: mở terminal để xem câu hỏi.</p>
<p className="mt-2 text-sm text-muted-foreground">{r.waiting ?? "Claude Code đang chờ một câu trả lời"}: mở terminal để xem câu hỏi. {PROSE_NOTE}</p>
)}

<p className="mt-3 max-w-[75ch] text-xs text-muted-foreground text-pretty">
Expand Down
11 changes: 11 additions & 0 deletions apps/hq-ui/tests/permit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,15 @@ describe("PermitCard markup", () => {
// a request from an older 8sync (no `then`) always skipped
expect(silence({ then: null, why: null }, 5)).toContain("bỏ bước này,");
});

it("a Ship question says it waits for the owner only; a prose question says the critic cannot see it", async () => {
const { isShip, SHIP_NOTE, PROSE_NOTE } = await import("@/components/hq/ask-panel");
expect(isShip({ kind: "question", questions: [{ header: "Ship" }] })).toBe(true);
expect(isShip({ kind: "question", questions: [{ header: "Layout" }, { header: " ship " }] })).toBe(true);
expect(isShip({ kind: "question", questions: [{ header: "Layout" }] })).toBe(false);
expect(isShip({ kind: "permission" })).toBe(false);
expect(isShip(null)).toBe(false);
expect(SHIP_NOTE).toContain("chỉ anh duyệt");
expect(PROSE_NOTE).toContain("critic không trả lời thay");
});
});
30 changes: 28 additions & 2 deletions crates/cli/src/verbs/ccc/auto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ Mỗi nhịp:

Không bao giờ: merge, push main, tag, release, publish, deploy khi owner chưa trả lời Ship; đọc hay in secret; xoá dữ liệu; nhắn người ngoài.
Câu hỏi Ship, secret, tiền, high-risk chờ owner. Không có việc hợp vai → R&D cho mục Next kế tiếp, không ngồi không.
Hỏi owner luôn bằng tool AskUserQuestion (option khuyến nghị đứng đầu), không hỏi bằng chữ cuối lượt rồi dừng: owner vắng 60 s
thì critic chỉ trả lời thay được câu hỏi qua AskUserQuestion. Câu Ship chỉ phòng lead hỏi.
";

/// Write [`LOOP_MD`] at `<root>/.claude/loop.md` unless something is already there (the owner's
Expand All @@ -350,10 +352,16 @@ pub(super) fn write_loop_md(root: &Path) -> Result<(PathBuf, bool)> {
/// does: `CronCreate`) that follows `.claude/loop.md` for the room's role. The stop check stays in
/// the cron prompt itself, so an edited loop.md cannot drop it: `off` or `pause`
/// delete the loop on its next tick, the `cap` ([`idle::cap`]) rests it. Re-running the command replaces the job (a fresh 7 days).
/// How every room asks the owner. The delegate hook (`8sync ccc hook delegate`) only sees an
/// AskUserQuestion call; a question typed as plain text at the end of a turn just waits, so the critic
/// never gets it (seen live 2026-10-18: rd asked about BiRefNet in prose and sat). One Ship question
/// per project: two rooms asking it doubles the owner's work (ba and lead both asked for PR 20).
pub(super) const ASK_RULE: &str = "Cần owner quyết → gọi tool AskUserQuestion (≤4 câu, option khuyến nghị đứng đầu có \"(Recommended)\"), KHÔNG hỏi bằng chữ ở cuối lượt rồi dừng: owner vắng 60 s thì critic chỉ trả lời thay được câu hỏi qua AskUserQuestion. Câu Ship (merge/release/deploy) chỉ phòng lead hỏi.";

/// What a room does with the card `auto claim` gives it, per lane (super-board's lane playbooks).
pub(super) fn lane_playbook(lane: &str) -> &'static str {
match lane {
"backlog" => "card backlog: viết AC + MỘT lệnh check (exit 0 = đạt) → `8sync ccc auto move <id> ready --check \"<lệnh>\" --note \"AC: …\"` (bug chặn người dùng hoặc owner nói gấp: thêm `--prio P0`, quan trọng `--prio P1`; dev nhận card gấp trước). Card thêm service/DB/queue/cache/scale/API công khai → gắn nhãn arch khi tạo (`auto add … --label arch`); card nhãn arch chỉ sang ready khi đã có note \"ADR: …\" (phòng arch viết; team không có phòng arch trong `auto status` → tự viết ADR ngắn bằng `/sysdesign`).Hết card backlog: `8sync ccc auto lint` → gỡ card blocked (`move <id> ready`), card approved → gom MỌI card approved vào MỘT câu hỏi Ship cho owner; owner duyệt → `gh pr merge <pr> --squash --match-head-commit <sha>` → `8sync ccc auto done <id> --evidence \"merged <sha>\"`",
"backlog" => "card backlog: viết AC + MỘT lệnh check (exit 0 = đạt) → `8sync ccc auto move <id> ready --check \"<lệnh>\" --note \"AC: …\"` (bug chặn người dùng hoặc owner nói gấp: thêm `--prio P0`, quan trọng `--prio P1`; dev nhận card gấp trước). Card thêm service/DB/queue/cache/scale/API công khai → gắn nhãn arch khi tạo (`auto add … --label arch`); card nhãn arch chỉ sang ready khi đã có note \"ADR: …\" (phòng arch viết; team không có phòng arch trong `auto status` → tự viết ADR ngắn bằng `/sysdesign`).Hết card backlog: `8sync ccc auto lint` → gỡ card blocked (`move <id> ready`); chỉ phòng lead (BA bỏ qua bước này): card approved → gom MỌI card approved vào MỘT câu hỏi Ship cho owner; owner duyệt → `gh pr merge <pr> --squash --match-head-commit <sha>` → `8sync ccc auto done <id> --evidence \"merged <sha>\"`",
"ready" => "card building: worktree `.claude/worktrees/card-<id>` nhánh `card-<id>-<slug>`, `/ship` card tới commit (test + gate), push nhánh, `gh pr create` → `8sync ccc auto move <id> qa --pr <số>`; làm dở → `8sync ccc auto note <id> \"…\"` (giữ claim)",
"qa" => "card qa: checkout nhánh PR, chạy `check` + UAT thật, ảnh chụp lưu `qa/evidence/card-<id>-<slug>.png` (HQ Preview hiện) → đạt `8sync ccc auto move <id> review`; trượt → `8sync ccc auto move <id> ready --note \"[qa] <lỗi + cách tái hiện>\"`",
"review" => "card review: review đối kháng diff PR (bug, test thiếu, bảo mật, quy ước) → ổn `8sync ccc auto move <id> approved --sha <head commit đã review>`; lỗi → `8sync ccc auto move <id> ready --note \"[review] <lỗi>\"`",
Expand All @@ -376,7 +384,7 @@ fn tick_prompt(name: &str) -> String {
None => "2) Vai không giữ cột board: bước 3. ".into(),
};
format!(
"{LOOP_TAG} {name} (vai {role}): 1) chạy `8sync ccc auto status --json`; on=false hoặc paused=true → CronDelete job {LOOP_TAG} này và dừng; `cap` khác null → nghỉ nhịp này (giữ loop). {board}3) `target` khác null → nhịp này làm phần hợp vai {role} cho đích đó; đạt (có `check` thì lệnh đó exit 0) → `8sync ccc auto done <id> --evidence \"<lệnh: kết quả>\"`. 4) Không có đích thì làm MỘT nhịp theo .claude/loop.md của dự án cho vai {role} (thiếu file → lấy MỘT việc hợp vai từ su-code/STATE.md mục Next, tự chọn skill: code → /ship, ≤3 file → /do, R&D → /deep-research). Không merge/push main/tag/deploy khi owner chưa trả lời Ship."
"{LOOP_TAG} {name} (vai {role}): 1) chạy `8sync ccc auto status --json`; on=false hoặc paused=true → CronDelete job {LOOP_TAG} này và dừng; `cap` khác null → nghỉ nhịp này (giữ loop). {board}3) `target` khác null → nhịp này làm phần hợp vai {role} cho đích đó; đạt (có `check` thì lệnh đó exit 0) → `8sync ccc auto done <id> --evidence \"<lệnh: kết quả>\"`. 4) Không có đích thì làm MỘT nhịp theo .claude/loop.md của dự án cho vai {role} (thiếu file → lấy MỘT việc hợp vai từ su-code/STATE.md mục Next, tự chọn skill: code → /ship, ≤3 file → /do, R&D → /deep-research). Không merge/push main/tag/deploy khi owner chưa trả lời Ship. {ASK_RULE}"
)
}

Expand Down Expand Up @@ -1680,6 +1688,24 @@ mod tests {
}
}

#[test]
fn rooms_ask_the_owner_through_the_tool_and_only_the_lead_asks_ship() {
// The delegate hook sees AskUserQuestion only; a question in prose never reaches the critic.
for name in ["lead", "rd", "arch", "dev", "qa"] {
assert!(tick_prompt(name).contains(ASK_RULE), "{name}");
}
assert!(ASK_RULE.contains("AskUserQuestion") && ASK_RULE.contains("KHÔNG hỏi bằng chữ") && ASK_RULE.contains("chỉ phòng lead hỏi"));
assert!(LOOP_MD.contains("AskUserQuestion") && LOOP_MD.contains("Câu Ship chỉ phòng lead hỏi"));
// BA shares the backlog lane but leaves the Ship question to the lead.
assert!(lane_playbook("backlog").contains("chỉ phòng lead (BA bỏ qua bước này)"));
for (name, lane) in [("lead", "backlog"), ("ba", "backlog"), ("dev", "ready"), ("qa", "qa"), ("review", "review")] {
let g = super::super::team::goal_prompt(name, lane, 10);
assert!(g.contains(ASK_RULE), "{name}");
// Claude Code caps a /goal condition at 4,000 characters (https://code.claude.com/docs/en/goal).
assert!(g.chars().count() <= 4000, "{name}: {} chars", g.chars().count());
}
}

#[test]
fn the_architect_tick_runs_its_playbook_and_the_lead_waits_for_an_adr() {
let t = tick_prompt("arch");
Expand Down
Loading