@fitlab-ai/agent-infra 0.7.4 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/bin/cli.ts +13 -11
  2. package/dist/bin/cli.js +13 -11
  3. package/dist/lib/init.js +1 -1
  4. package/dist/lib/merge.js +1 -1
  5. package/dist/lib/sandbox/index.js +21 -21
  6. package/dist/lib/task/index.js +13 -13
  7. package/dist/lib/update.js +1 -1
  8. package/lib/init.ts +1 -1
  9. package/lib/merge.ts +1 -1
  10. package/lib/sandbox/index.ts +21 -21
  11. package/lib/task/index.ts +13 -13
  12. package/lib/update.ts +1 -1
  13. package/package.json +1 -1
  14. package/templates/.agents/rules/README.en.md +7 -3
  15. package/templates/.agents/rules/README.zh-CN.md +7 -3
  16. package/templates/.agents/rules/cli-help-format.en.md +49 -0
  17. package/templates/.agents/rules/cli-help-format.zh-CN.md +49 -0
  18. package/templates/.agents/rules/no-mid-flow-questions.en.md +14 -2
  19. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +14 -2
  20. package/templates/.agents/rules/pr-sync.github.en.md +8 -6
  21. package/templates/.agents/rules/pr-sync.github.zh-CN.md +8 -6
  22. package/templates/.agents/rules/review-handshake.en.md +83 -0
  23. package/templates/.agents/rules/review-handshake.zh-CN.md +83 -0
  24. package/templates/.agents/scripts/lib/post-review-commit.js +56 -0
  25. package/templates/.agents/scripts/lib/review-artifacts.js +117 -0
  26. package/templates/.agents/scripts/review-diff-fingerprint.js +99 -0
  27. package/templates/.agents/scripts/validate-artifact.js +240 -0
  28. package/templates/.agents/skills/analyze-task/SKILL.en.md +52 -6
  29. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +52 -6
  30. package/templates/.agents/skills/code-task/config/verify.en.json +3 -0
  31. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +3 -0
  32. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -3
  33. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -3
  34. package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -4
  35. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -4
  36. package/templates/.agents/skills/code-task/scripts/detect-mode.js +2 -107
  37. package/templates/.agents/skills/commit/SKILL.en.md +6 -0
  38. package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -0
  39. package/templates/.agents/skills/commit/reference/task-status-update.en.md +8 -0
  40. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +8 -0
  41. package/templates/.agents/skills/complete-task/SKILL.en.md +10 -0
  42. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -0
  43. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -0
  44. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -0
  45. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  46. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  47. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  48. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
  49. package/templates/.agents/skills/plan-task/config/verify.en.json +3 -0
  50. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +3 -0
  51. package/templates/.agents/skills/review-analysis/config/verify.en.json +2 -1
  52. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +2 -1
  53. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -4
  54. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -4
  55. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -0
  56. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -0
  57. package/templates/.agents/skills/review-code/SKILL.en.md +4 -1
  58. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -1
  59. package/templates/.agents/skills/review-code/config/verify.en.json +5 -2
  60. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +5 -2
  61. package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -4
  62. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -4
  63. package/templates/.agents/skills/review-code/reference/report-template.en.md +6 -0
  64. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +6 -0
  65. package/templates/.agents/skills/review-plan/config/verify.en.json +2 -1
  66. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +2 -1
  67. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -4
  68. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -4
  69. package/templates/.agents/skills/review-plan/reference/report-template.en.md +4 -0
  70. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +4 -0
  71. package/templates/.agents/templates/task.en.md +7 -0
  72. package/templates/.agents/templates/task.zh-CN.md +7 -0
  73. package/templates/.github/workflows/metadata-sync.yml +1 -1
  74. package/templates/.github/workflows/pr-label.yml +1 -1
  75. package/templates/.github/workflows/status-label.yml +1 -1
@@ -1,8 +1,15 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
+ import { execFileSync } from "node:child_process";
4
5
  import { fileURLToPath } from "node:url";
5
6
 
7
+ import {
8
+ extractReviewBaseline,
9
+ findAuthoritativeReviewCodeArtifact,
10
+ resolvePostReviewGlobs
11
+ } from "./lib/post-review-commit.js";
12
+
6
13
  const EXIT_CODE = {
7
14
  pass: 0,
8
15
  fail: 1,
@@ -33,6 +40,24 @@ const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-
33
40
  const ACTIVITY_LOG_PATTERN = /^- (\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?) — \*\*(.+?)\*\* by (.+?) — (.+)$/;
34
41
  const BRANCH_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
35
42
 
43
+ // Review disagreement ledger (see .agents/rules/review-handshake.md).
44
+ const LEDGER_SECTION_NAMES = ["审查分歧账本", "Review Disagreement Ledger"];
45
+ const LEDGER_STATUSES = new Set([
46
+ "open",
47
+ "accepted",
48
+ "adjusted",
49
+ "refuted",
50
+ "cannot-judge",
51
+ "confirmed",
52
+ "needs-human-decision",
53
+ "closed",
54
+ "human-decided"
55
+ ]);
56
+ const LEDGER_TERMINAL_OK = new Set(["confirmed", "closed", "human-decided"]);
57
+ const DEFAULT_MAX_HANDSHAKE_ROUNDS = 3;
58
+ const POST_REVIEW_COMMIT_STAGE = "post-review-commit";
59
+ const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
60
+
36
61
  const scriptPath = fileURLToPath(import.meta.url);
37
62
  const repoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
38
63
 
@@ -188,6 +213,10 @@ function runCheck(type, context) {
188
213
  return checkActivityLog(context);
189
214
  case "completion-checklist":
190
215
  return checkCompletionChecklist(context);
216
+ case "review-ledger":
217
+ return checkReviewLedger(context);
218
+ case "post-review-commit":
219
+ return checkPostReviewCommit(context);
191
220
  default: {
192
221
  const adapter = PLATFORM_ADAPTERS[type];
193
222
  if (!adapter) {
@@ -332,6 +361,20 @@ function loadProjectName() {
332
361
  }
333
362
  }
334
363
 
364
+ function loadReviewConfig() {
365
+ const configPath = path.join(repoRoot, ".agents", ".airc.json");
366
+ if (!fs.existsSync(configPath)) {
367
+ return {};
368
+ }
369
+
370
+ try {
371
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
372
+ return config.review && typeof config.review === "object" ? config.review : {};
373
+ } catch {
374
+ return {};
375
+ }
376
+ }
377
+
335
378
  function checkArtifact({ taskDir, config, artifactFile }) {
336
379
  const resolvedArtifact = resolveArtifactPath(taskDir, config.file_pattern, artifactFile);
337
380
  if (!resolvedArtifact.ok) {
@@ -481,6 +524,203 @@ function checkCompletionChecklist({ taskDir, config }) {
481
524
  return passResult("completion-checklist", `Completion Checklist valid (${items.length} items checked)`);
482
525
  }
483
526
 
527
+ function parseLedgerRows(section) {
528
+ const rows = [];
529
+ for (const rawLine of String(section || "").split(/\r?\n/)) {
530
+ const line = rawLine.trim();
531
+ if (!line.startsWith("|")) {
532
+ continue;
533
+ }
534
+ if (/^\|[\s:|-]+\|?$/.test(line)) {
535
+ continue; // separator row
536
+ }
537
+ const inner = line.replace(/^\|/, "").replace(/\|$/, "");
538
+ const cells = inner.split("|").map((cell) => cell.trim());
539
+ if ((cells[0] || "").toLowerCase() === "id") {
540
+ continue; // header row
541
+ }
542
+ rows.push(cells);
543
+ }
544
+ return rows;
545
+ }
546
+
547
+ function resolveReviewSetting(config, key, fallback) {
548
+ if (config && config[key] !== undefined && config[key] !== null) {
549
+ return config[key];
550
+ }
551
+ const reviewConfig = loadReviewConfig();
552
+ if (reviewConfig[key] !== undefined && reviewConfig[key] !== null) {
553
+ return reviewConfig[key];
554
+ }
555
+ return fallback;
556
+ }
557
+
558
+ function checkReviewLedger({ taskDir, config }) {
559
+ const task = loadTask(taskDir);
560
+ if (!task.ok) {
561
+ return failResult("review-ledger", task.message);
562
+ }
563
+
564
+ const section = getSectionContent(task.content, LEDGER_SECTION_NAMES);
565
+ if (!section.trim()) {
566
+ return passResult("review-ledger", "No disagreement ledger section; treated as no open disagreements");
567
+ }
568
+
569
+ const rows = parseLedgerRows(section);
570
+ if (rows.length === 0) {
571
+ return passResult("review-ledger", "Disagreement ledger has no entries");
572
+ }
573
+
574
+ const stageScope = Array.isArray(config.stage_scope) ? config.stage_scope : null;
575
+ const maxRounds = Number(resolveReviewSetting(config, "max_handshake_rounds", DEFAULT_MAX_HANDSHAKE_ROUNDS));
576
+ const problems = [];
577
+ let inScopeCount = 0;
578
+
579
+ for (const cells of rows) {
580
+ if (cells.length < 6) {
581
+ problems.push(`malformed row (expected 6 columns): ${cells.join(" | ")}`);
582
+ continue;
583
+ }
584
+
585
+ const [id, stage, roundRaw, , status, evidence] = cells;
586
+ const stageScoped = stageScope ? stageScope.includes(stage) : true;
587
+ // post-review-commit exemption rows are consumed by the post-review-commit
588
+ // check, not enforced here.
589
+ if (stage === POST_REVIEW_COMMIT_STAGE) {
590
+ continue;
591
+ }
592
+ if (!stageScoped) {
593
+ continue;
594
+ }
595
+ inScopeCount += 1;
596
+
597
+ if (!LEDGER_STATUSES.has(status)) {
598
+ problems.push(`${id}: illegal status '${status}'`);
599
+ continue;
600
+ }
601
+ if (status !== "open" && evidence === "") {
602
+ problems.push(`${id}: status '${status}' requires evidence`);
603
+ }
604
+ const round = Number.parseInt(roundRaw, 10);
605
+ if (
606
+ Number.isFinite(round) &&
607
+ round >= maxRounds &&
608
+ !LEDGER_TERMINAL_OK.has(status) &&
609
+ status !== "needs-human-decision"
610
+ ) {
611
+ problems.push(`${id}: round ${round} reached limit ${maxRounds} without convergence; escalate to needs-human-decision`);
612
+ }
613
+ if (!LEDGER_TERMINAL_OK.has(status)) {
614
+ problems.push(`${id}: unresolved (status '${status}')`);
615
+ }
616
+ }
617
+
618
+ if (problems.length > 0) {
619
+ return failResult("review-ledger", `Unclosed/invalid disagreements: ${problems.join("; ")}`);
620
+ }
621
+
622
+ const scopeLabel = stageScope ? ` for stages [${stageScope.join(", ")}]` : "";
623
+ return passResult("review-ledger", `Disagreement ledger clean (${inScopeCount} in-scope entries terminal${scopeLabel})`);
624
+ }
625
+
626
+ function checkPostReviewCommit({ taskDir, config }) {
627
+ const reviewArtifact = findAuthoritativeReviewCodeArtifact(taskDir);
628
+ if (!reviewArtifact.ok) {
629
+ return passResult("post-review-commit", "No review-code artifact; check inactive");
630
+ }
631
+
632
+ let gitRoot;
633
+ try {
634
+ gitRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
635
+ } catch {
636
+ return blockedResult("post-review-commit", "git unavailable or task directory is not inside a git repository");
637
+ }
638
+
639
+ const task = loadTask(taskDir);
640
+ const content = fs.readFileSync(reviewArtifact.path, "utf8");
641
+ const reviewBaseline = extractReviewBaseline(content);
642
+ const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
643
+ const baselineSource = resolvePostReviewBaseline({
644
+ gitRoot,
645
+ lastReviewedCommit,
646
+ reviewBaseline,
647
+ reviewArtifact: reviewArtifact.fileName
648
+ });
649
+ if (!baselineSource.ok) {
650
+ return baselineSource.result;
651
+ }
652
+
653
+ const sha = baselineSource.sha;
654
+ const globs = resolvePostReviewGlobs(config, loadReviewConfig());
655
+ let commits;
656
+ try {
657
+ const out = execFileSync("git", ["-C", gitRoot, "rev-list", `${sha}..HEAD`, "--", ...globs], { encoding: "utf8" });
658
+ commits = out.split(/\r?\n/).filter((line) => line.trim() !== "");
659
+ } catch {
660
+ return blockedResult("post-review-commit", `git rev-list failed for baseline ${sha}; manual inspection required`);
661
+ }
662
+
663
+ if (commits.length === 0) {
664
+ return passResult("post-review-commit", `No post-review commits to code/rule paths since ${sha.slice(0, 8)}`);
665
+ }
666
+
667
+ const ledgerSection = task.ok ? getSectionContent(task.content, LEDGER_SECTION_NAMES) : "";
668
+ const exempt = parseLedgerRows(ledgerSection).some(
669
+ (cells) => cells[1] === POST_REVIEW_COMMIT_STAGE && cells[4] === "human-decided"
670
+ );
671
+ if (exempt) {
672
+ return passResult(
673
+ "post-review-commit",
674
+ `${commits.length} post-review commit(s) covered by a human-decided exemption`
675
+ );
676
+ }
677
+
678
+ return failResult(
679
+ "post-review-commit",
680
+ `${commits.length} commit(s) to code/rule paths after review baseline ${sha.slice(0, 8)}; re-run review-code or record a human-decided exemption`
681
+ );
682
+ }
683
+
684
+ function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewBaseline, reviewArtifact }) {
685
+ if (lastReviewedCommit) {
686
+ if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
687
+ return { ok: true, sha: lastReviewedCommit };
688
+ }
689
+ }
690
+
691
+ if (!reviewBaseline) {
692
+ return {
693
+ ok: false,
694
+ result: passResult(
695
+ "post-review-commit",
696
+ `${reviewArtifact} predates baseline-commit anchoring; skipped (legacy artifact)`,
697
+ [`${reviewArtifact} has no 审查基线提交 / Review Baseline Commit field`]
698
+ )
699
+ };
700
+ }
701
+
702
+ if (!SHA_PATTERN.test(reviewBaseline)) {
703
+ return {
704
+ ok: false,
705
+ result: blockedResult(
706
+ "post-review-commit",
707
+ `${reviewArtifact} has an empty or malformed 审查基线提交 SHA ('${reviewBaseline}'); manual remediation required`
708
+ )
709
+ };
710
+ }
711
+
712
+ return { ok: true, sha: reviewBaseline };
713
+ }
714
+
715
+ function gitCommitExists(gitRoot, sha) {
716
+ try {
717
+ execFileSync("git", ["-C", gitRoot, "cat-file", "-e", `${sha}^{commit}`], { encoding: "utf8" });
718
+ return true;
719
+ } catch {
720
+ return false;
721
+ }
722
+ }
723
+
484
724
  // === File & Config Loaders ===
485
725
 
486
726
  function loadVerifyConfig(skillName) {
@@ -65,9 +65,49 @@ If `task.md` contains these source fields, also read the corresponding source in
65
65
  - `codescan_alert_number` - Code Scanning alert
66
66
  - `security_alert_number` - Dependabot alert
67
67
 
68
- **Round ≥ 2: respond to the prior review (only when a review artifact exists)**: if the task directory contains `review-analysis.md` / `review-analysis-r{N}.md`, read the highest-round review report; add a `## Response to Prior Review` section to this round's analysis artifact, and for each finding verify it via Read/Grep before acting (holds accept and fix; judged hallucinated/unfounded rebut with counter-evidence rather than defaulting to compliance); record any open disagreement under `## Open Questions`. Round 1 has no review, so skip this section.
68
+ **Round ≥ 2: respond to the prior review (only when a review artifact exists)**: if the task directory contains `review-analysis.md` / `review-analysis-r{N}.md`, read the highest-round review report; add a `## Response to Prior Review` section to this round's analysis artifact, and for each finding verify it via Read/Grep, then dispose of it with one of the four states in `.agents/rules/review-handshake.md` (`accepted` / `adjusted` / `refuted` / `cannot-judge`) — every state needs commensurate evidence, never defaulting to compliance; write the disposition back to the matching row in the task.md disagreement ledger (stage=analysis, round +1). Record any open disagreement under `## Open Questions`. Round 1 has no review, so skip this section.
69
69
 
70
- ### 4. Perform Requirements Analysis
70
+ ### 4. Requirement Sufficiency Gate
71
+
72
+ > Questions in this step are authorized by `.agents/rules/no-mid-flow-questions.md` "Exemption 3: Entry-point requirement-sufficiency clarification": only at the analyze-task entry point, only to judge and fill requirement sufficiency, one question at a time, and **never** to solicit implementation / technical-choice preferences.
73
+
74
+ Runs after Step 0 state check and Step 3 (questioning is an external-state action and must come after the state-check hard gate; the judgment and state read/write need task.md first).
75
+
76
+ **4.1 Read cross-round state**: read the `## Brainstorming` section of task.md (treat as first time when absent, `question_count=0`). Section format:
77
+
78
+ ```
79
+ ## Brainstorming
80
+ - status: asking | done
81
+ - question_count: <int>
82
+ - pending_question: <text, may be empty>
83
+ - answered:
84
+ - Q: … / A: …
85
+ ```
86
+
87
+ **4.2 Receive the answer to the previous question**: if `pending_question` exists:
88
+ - the user's current message yields an answer → write the answer back into `## Description` / `## Requirements`, append that `Q/A` to `answered`, and clear `pending_question` (`question_count` unchanged).
89
+ - no answer carried → restate `pending_question` and take Scenario B early-exit below (do not increase `question_count`).
90
+
91
+ **4.3 Sufficiency judgment** (objective checklist; any gap hit means insufficient):
92
+ - description/requirements empty, or a single sentence with no verifiable acceptance criteria;
93
+ - missing goal or impact scope (unclear what to change / who is affected);
94
+ - requirement items contradict each other, or key terms are undefined and block analysis.
95
+
96
+ **4.4 Branch**:
97
+
98
+ - **Scenario A (sufficient / converged)** — any exit condition met: the checklist fully passes / the user explicitly says "just analyze / skip" / `question_count` reaches the cap (≤5). Set `## Brainstorming` `status: done` and continue the normal flow from step 5; write any remaining gaps into the analysis artifact `## Assumptions` / `## Open Questions`.
99
+ - **Scenario B (insufficient, ask and early-exit)** — close the loop within this step and STOP early:
100
+ 1. Decide this round's question (consistent with 4.2):
101
+ - if a `pending_question` already exists (the previous question is still unanswered) → restate that `pending_question`, do **not** modify it and do **not** increment `question_count`;
102
+ - otherwise (no pending question) → pick the single highest-value question (acceptance criteria > scope > ambiguity) and write `## Brainstorming`: `status: asking`, `pending_question: <question>`, `question_count += 1`.
103
+ 2. Update frontmatter: `current_step: requirement-analysis`, `assigned_to`, `updated_at`, `agent_infra_version` (read `.agents/rules/version-stamp.md` first).
104
+ 3. Append to Activity Log: `- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Analyze Task (Brainstorming)** by {agent} — Asked Q{question_count}, awaiting answer`.
105
+ 4. Issue sync (when `issue_number` exists, skip on any failure): read `.agents/rules/issue-sync.md` first for upstream / permission detection; update only the **task comment** per the task.md comment sync rule; keep the `status` label at `pending-design-work`; do **not** publish an analysis artifact comment.
106
+ 5. Verification (replaces the step 8 artifact gate): `node .agents/scripts/validate-artifact.js check task-meta .agents/workspace/active/{task-id} --skill analyze-task --format text` (the early-exit set `current_step: requirement-analysis`, so it should pass); also keep `rg -n 'Analyze Task \(Brainstorming\)' .agents/workspace/active/{task-id}/task.md` and the task-comment sync evidence. Do **not** run the artifact gate, nor `check activity-log` / `check platform-sync` (both bind to the analysis artifact path).
107
+ 6. User output: show only the current **single question** plus how to answer/continue (re-trigger `analyze-task {task-ref}` with the answer), and append the `Completed at` line per `.agents/rules/next-step-output.md`.
108
+ 7. **STOP** and wait for the answer. The next trigger returns to this step.
109
+
110
+ ### 5. Perform Requirements Analysis
71
111
 
72
112
  Before analysis begins: if `start_date` in the frontmatter is empty, write today's date immediately (command: `date +%F`, format `YYYY-MM-DD`); keep any existing value. Before writing, read `.agents/rules/version-stamp.md` and refresh `updated_at` / `agent_infra_version` at the same time.
73
113
 
@@ -80,7 +120,9 @@ Follow the `analysis` step in `.agents/workflows/feature-development.yaml`:
80
120
  - [ ] Identify potential technical risks and dependencies
81
121
  - [ ] Assess effort and complexity
82
122
 
83
- ### 5. Output Analysis Document
123
+ ### 6. Output Analysis Document
124
+
125
+ > Steps 6–9 are the **Scenario A (normal output)** path. **Scenario B (ask and early-exit)** already finished its state update, task-comment sync, and verification inside step 4 and STOPped, so it does not enter these steps.
84
126
 
85
127
  Create `.agents/workspace/active/{task-id}/{analysis-artifact}`.
86
128
 
@@ -138,7 +180,7 @@ Create `.agents/workspace/active/{task-id}/{analysis-artifact}`.
138
180
  - Risk level: {High/Medium/Low}
139
181
  ```
140
182
 
141
- ### 6. Update Task Status
183
+ ### 7. Update Task Status
142
184
 
143
185
  Get the current time:
144
186
 
@@ -170,7 +212,9 @@ If task.md contains a valid `issue_number`, perform these sync actions (skip and
170
212
  - Publish the `{analysis-artifact}` comment
171
213
  - Read `.agents/rules/issue-fields.md` and follow Flow A to sync every non-empty Issue field (`priority`/`effort`/`start_date`/`target_date`) from `task.md` to the Issue (idempotent; skip without blocking when `has_push=false` or the fetch/write fails)
172
214
 
173
- ### 7. Verification Gate
215
+ ### 8. Verification Gate
216
+
217
+ > This artifact gate is for **Scenario A** only; Scenario B's verification is in step 4 (`check task-meta` + explicit evidence), not the artifact gate here.
174
218
 
175
219
  Run the verification gate to confirm the task artifact and sync state are valid:
176
220
 
@@ -185,7 +229,9 @@ Handle the result as follows:
185
229
 
186
230
  Keep the gate output in your reply as fresh evidence. Do not claim completion without output from this run.
187
231
 
188
- ### 8. Inform User
232
+ ### 9. Inform User
233
+
234
+ > This step is the **Scenario A** normal-completion output; Scenario B's single-question output is in step 4.
189
235
 
190
236
  > Execute this step only after the verification gate passes.
191
237
 
@@ -64,9 +64,49 @@ tail .agents/workspace/active/{task-id}/task.md
64
64
  - `codescan_alert_number` - Code Scanning 告警
65
65
  - `security_alert_number` - Dependabot 告警
66
66
 
67
- **Round ≥ 2:响应上一轮审查(仅当存在审查产物时)**:若任务目录存在 `review-analysis.md` / `review-analysis-r{N}.md`,读取最高轮次的审查报告;在本轮分析产物中新增 `## 对上一轮审查的响应` 段,对每条发现先 Read/Grep 核实再处置(成立 接受并修正;判定为幻觉/不成立 附反证反驳,不默认顺从),未决分歧写入 `## 未决问题`。Round 1 无审查,跳过本段。
67
+ **Round ≥ 2:响应上一轮审查(仅当存在审查产物时)**:若任务目录存在 `review-analysis.md` / `review-analysis-r{N}.md`,读取最高轮次的审查报告;在本轮分析产物中新增 `## 对上一轮审查的响应` 段,对每条发现先 Read/Grep 核实,再按 `.agents/rules/review-handshake.md` 的四态(`accepted` / `adjusted` / `refuted` / `cannot-judge`)处置——每态都要附相称证据,不默认顺从;并把处置回写 task.md `## 审查分歧账本` 对应行(stage=analysis,round +1)。未决分歧写入 `## 未决问题`。Round 1 无审查,跳过本段。
68
68
 
69
- ### 4. 执行需求分析
69
+ ### 4. 入口需求充分性闸门
70
+
71
+ > 本步骤的发问受 `.agents/rules/no-mid-flow-questions.md`「例外 3:入口式需求充分性澄清」授权:仅在 analyze-task 入口、仅用于判断并补齐需求充分性,一次只问一个问题,**绝不**借此征求实现 / 技术选型偏好。
72
+
73
+ 排在第 0 步状态核对与步骤 3 之后执行(提问属对外动作,须在状态核对硬闸门之后;判定与状态读写需先读到 task.md)。
74
+
75
+ **4.1 读取跨轮状态**:读取 task.md 的 `## Brainstorming` 段(不存在则视为首次,`question_count=0`)。段格式:
76
+
77
+ ```
78
+ ## Brainstorming
79
+ - status: asking | done
80
+ - question_count: <int>
81
+ - pending_question: <文本,可空>
82
+ - answered:
83
+ - Q: … / A: …
84
+ ```
85
+
86
+ **4.2 接收上一问的答案**:若存在 `pending_question`:
87
+ - 用户当轮消息可解析出答案 → 把答案回写 `## 描述` / `## 需求`,把该 `Q/A` 追加进 `answered`,清空 `pending_question`(`question_count` 不变)。
88
+ - 未携带答案 → 复述 `pending_question`,按下文场景 B 提问早退(不增加 `question_count`)。
89
+
90
+ **4.3 充分性判定**(客观清单,命中任一缺口即判为不足):
91
+ - 描述/需求为空,或仅一句话且无可验证的验收标准;
92
+ - 缺少目标或受影响范围(不知道要改什么 / 影响谁);
93
+ - 需求条目自相矛盾,或关键名词未定义而无法分析。
94
+
95
+ **4.4 分流**:
96
+
97
+ - **场景 A(充分 / 已收敛)**——满足任一退出条件:充分性清单全部通过 / 用户显式「直接分析 / skip」/ `question_count` 达上限(≤5)。置 `## Brainstorming` 的 `status: done`,继续步骤 5 起的正常流程;未补齐的缺口写入分析产物 `## 假设` / `## 未决问题`。
98
+ - **场景 B(不足,提问早退)**——在本步骤内闭环并提前 STOP:
99
+ 1. 确定本轮要问的问题(与 4.2 保持一致):
100
+ - 若已存在 `pending_question`(上一问尚未得到答案)→ 复述该 `pending_question`,**不**修改它、**不**增加 `question_count`;
101
+ - 否则(无待答问题)→ 选最高价值的一个问题(验收标准 > 范围 > 歧义),写入 `## Brainstorming`:`status: asking`、`pending_question: <问题>`、`question_count += 1`。
102
+ 2. 更新 frontmatter:`current_step: requirement-analysis`、`assigned_to`、`updated_at`、`agent_infra_version`(先读 `.agents/rules/version-stamp.md`)。
103
+ 3. 追加 Activity Log:`- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Analyze Task (Brainstorming)** by {agent} — Asked Q{question_count}, awaiting answer`。
104
+ 4. Issue 同步(存在 `issue_number` 时,任一失败跳过):先读 `.agents/rules/issue-sync.md` 完成 upstream / 权限检测;仅按 task.md 评论同步规则更新 **task 评论**;`status` label 维持 `pending-design-work`;**不**发布分析产物评论。
105
+ 5. 校验(替代步骤 8 的 artifact gate):`node .agents/scripts/validate-artifact.js check task-meta .agents/workspace/active/{task-id} --skill analyze-task --format text`(早退已置 `current_step: requirement-analysis`,预期通过);并保留 `rg -n 'Analyze Task \(Brainstorming\)' .agents/workspace/active/{task-id}/task.md` 与 task 评论同步证据。**不**跑 artifact gate,也不跑 `check activity-log` / `check platform-sync`(二者绑定分析产物路径)。
106
+ 6. 用户输出:只展示当前**单个问题** + 如何回答/继续(再次触发 `analyze-task {task-ref}` 并附答案),并按 `.agents/rules/next-step-output.md` 在末行追加 `Completed at`。
107
+ 7. **STOP**,等待回答。下一次触发回到本步骤。
108
+
109
+ ### 5. 执行需求分析
70
110
 
71
111
  开始分析前:若 frontmatter 的 `start_date` 为空,立即写入当日日期(命令 `date +%F`,格式 `YYYY-MM-DD`);已有值则保留。写入前先读取 `.agents/rules/version-stamp.md`,并同步刷新 `updated_at` / `agent_infra_version`。
72
112
 
@@ -79,7 +119,9 @@ tail .agents/workspace/active/{task-id}/task.md
79
119
  - [ ] 识别潜在技术风险和依赖
80
120
  - [ ] 评估工作量和复杂度
81
121
 
82
- ### 5. 输出分析文档
122
+ ### 6. 输出分析文档
123
+
124
+ > 步骤 6–9 属**场景 A(正常产出)**路径。**场景 B(提问早退)**已在步骤 4 内完成状态更新、task 评论同步与校验并 STOP,不进入这些步骤。
83
125
 
84
126
  创建 `.agents/workspace/active/{task-id}/{analysis-artifact}`。
85
127
 
@@ -137,7 +179,7 @@ tail .agents/workspace/active/{task-id}/task.md
137
179
  - 风险等级:{高/中/低}
138
180
  ```
139
181
 
140
- ### 6. 更新任务状态
182
+ ### 7. 更新任务状态
141
183
 
142
184
  获取当前时间:
143
185
 
@@ -169,7 +211,9 @@ date "+%Y-%m-%d %H:%M:%S%:z"
169
211
  - 发布 `{analysis-artifact}` 评论
170
212
  - 读取 `.agents/rules/issue-fields.md`,按流程 A 把 `task.md` 中所有非空的 Issue 字段(`priority`/`effort`/`start_date`/`target_date`)同步到 Issue(幂等;`has_push=false` 或取数/写入失败时跳过,不阻断)
171
213
 
172
- ### 7. 完成校验
214
+ ### 8. 完成校验
215
+
216
+ > 本步骤的 artifact gate 仅用于**场景 A**;场景 B 的校验见步骤 4(`check task-meta` + 显式证据),不在此跑 artifact gate。
173
217
 
174
218
  运行完成校验,确认任务产物和同步状态符合规范:
175
219
 
@@ -184,7 +228,9 @@ node .agents/scripts/validate-artifact.js gate analyze-task .agents/workspace/ac
184
228
 
185
229
  将校验输出保留在回复中作为当次验证输出。没有当次校验输出,不得声明完成。
186
230
 
187
- ### 8. 告知用户
231
+ ### 9. 告知用户
232
+
233
+ > 本步骤为**场景 A** 正常完成输出;场景 B 的单问输出见步骤 4。
188
234
 
189
235
  > 仅在校验通过后执行本步骤。
190
236
 
@@ -35,6 +35,9 @@
35
35
  "expected_action_pattern": "(Code Task|Code) \\(Round \\d+(?:, fix for review-code(?:-r\\d+)?\\.md)?\\)",
36
36
  "freshness_minutes": 30
37
37
  },
38
+ "review-ledger": {
39
+ "stage_scope": ["analysis", "plan"]
40
+ },
38
41
  "platform-sync": {
39
42
  "when": "issue_number_exists",
40
43
  "expected_status_label": "status: in-progress",
@@ -35,6 +35,9 @@
35
35
  "expected_action_pattern": "(Code Task|Code) \\(Round \\d+(?:, fix for review-code(?:-r\\d+)?\\.md)?\\)",
36
36
  "freshness_minutes": 30
37
37
  },
38
+ "review-ledger": {
39
+ "stage_scope": ["analysis", "plan"]
40
+ },
38
41
  "platform-sync": {
39
42
  "when": "issue_number_exists",
40
43
  "expected_status_label": "status: in-progress",
@@ -4,9 +4,11 @@ Read this file before changing code during fix mode.
4
4
 
5
5
  ## Plan the Fixes
6
6
 
7
- **Verify each finding first (mandatory before editing)**: for every finding in `{review-artifact}`, Read/Grep the cited `file:line` and the corresponding `git diff` to confirm the issue is real:
8
- - Holds → include it in the classification and fixes below
9
- - Unfounded / based on a wrong `file:line` / hallucinated do not change code; give a counter-argument in the report's `## Per-Finding Verification` section and record it under unresolved issues
7
+ **Verify each finding first (mandatory before editing)**: for every finding in `{review-artifact}`, Read/Grep the cited `file:line` and the corresponding `git diff` to confirm the issue is real, then dispose of it with one of the four states in `.agents/rules/review-handshake.md`, and write the disposition + commensurate evidence back to the matching row in the task.md disagreement ledger (stage=code, round +1; symmetric evidence — every state needs evidence, "accept" is not a zero-cost default):
8
+ - `accepted` → include it in the classification and fixes below; evidence cites the fix `file:line`
9
+ - `adjusted` use an alternative fix, with rationale; awaits review-code confirmation
10
+ - `refuted` → verification judged it unfounded / a wrong `file:line` / hallucinated → do not change code; give a counter-argument in the report's `## Per-Finding Verification` section; awaits review-code confirmation
11
+ - `cannot-judge` → insufficient evidence to decide; hand to reviewer/human
10
12
  - Do not expand fixes to issues the review did not list
11
13
 
12
14
  Classify and prioritize work:
@@ -4,9 +4,11 @@
4
4
 
5
5
  ## 规划修复
6
6
 
7
- **先逐条核实(动手前必做)**:对 `{review-artifact}` 的每一条发现,先 Read/Grep 其引用的 `file:line` 与对应 `git diff`,确认问题真实存在:
8
- - 成立纳入下方分类与修复
9
- - 不成立 / 基于错误 `file:line` / 幻觉 不改代码,在报告 `## 对审查发现的逐条核实` 给出反证,并记入 unresolved issues
7
+ **先逐条核实(动手前必做)**:对 `{review-artifact}` 的每一条发现,先 Read/Grep 其引用的 `file:line` 与对应 `git diff`,确认问题真实存在,再按 `.agents/rules/review-handshake.md` 的四态处置,并把处置 + 相称证据回写 task.md `## 审查分歧账本` 对应行(stage=code,round +1;对称证据:每态都要附证据,"接受"不是零成本默认):
8
+ - `accepted`纳入下方分类与修复,证据指向修复点 `file:line`
9
+ - `adjusted` → 采用替代修法,附理由,待 review-code 复核确认
10
+ - `refuted` → 核实判定不成立 / 基于错误 `file:line` / 幻觉 → 不改代码,在报告 `## 对审查发现的逐条核实` 给出反证,待 review-code 复核确认
11
+ - `cannot-judge` → 证据不足无法判断,交检视方/人工
10
12
  - 不擅自把修复扩大到审查未列出的问题
11
13
 
12
14
  按以下顺序分类并确定优先级:
@@ -64,11 +64,11 @@ $ {command}
64
64
 
65
65
  ## Per-Finding Verification
66
66
 
67
- > Fix mode only; for an initial implementation write "(initial implementation this round, no review findings)". Read/Grep-verify each finding of the previous `review-code` before acting on it.
67
+ > Fix mode only; for an initial implementation write "(initial implementation this round, no review findings)". Read/Grep-verify each finding of the previous `review-code`, then dispose of it with one of the four states in `.agents/rules/review-handshake.md`; write the disposition and **commensurate evidence** back to the matching row in the task.md disagreement ledger (stage=code, round +1). Symmetric evidence: accepted/adjusted cite the fix `file:line`; refuted/cannot-judge cite counter-evidence `file:line` or raw command output.
68
68
 
69
- | Finding | Reproduced? | Disposition (fix / rebut) |
70
- |------|----------|----------------------|
71
- | {finding} | {yes/no, with file:line or command} | {fix note, or counter-argument + recorded under unresolved} |
69
+ | Finding | Disposition | Commensurate evidence |
70
+ |------|----------|----------|
71
+ | {finding} | {accepted / adjusted / refuted / cannot-judge} | {fix file:line, or counter-evidence file:line / raw command output} |
72
72
 
73
73
  ## Items for Review
74
74
 
@@ -64,11 +64,11 @@ $ {command}
64
64
 
65
65
  ## 对审查发现的逐条核实
66
66
 
67
- > 仅修复模式填写;初次实现写「(本轮为初次实现,无审查发现)」。对上一轮 `review-code` 的每条发现先 Read/Grep 核实再处置。
67
+ > 仅修复模式填写;初次实现写「(本轮为初次实现,无审查发现)」。对上一轮 `review-code` 的每条发现先 Read/Grep 核实,再按 `.agents/rules/review-handshake.md` 的四态处置;并把处置与**相称证据**回写 task.md `## 审查分歧账本` 对应行(stage=code,round +1)。对称证据:accepted/adjusted 附修复点 file:line,refuted/cannot-judge 附反证 file:line 或命令原文。
68
68
 
69
- | 发现 | 是否复现 | 处置(修复 / 反驳) |
70
- |------|----------|----------------------|
71
- | {finding} | {是/否,附 file:line 或命令} | {修复说明,或反证 + 记入 unresolved} |
69
+ | 发现 | 处置状态 | 相称证据 |
70
+ |------|----------|----------|
71
+ | {finding} | {accepted / adjusted / refuted / cannot-judge} | {修复点 file:line,或反证 file:line / 命令原文} |
72
72
 
73
73
  ## 供审查关注的内容
74
74