@fitlab-ai/agent-infra 0.11.3 → 0.11.4

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 (102) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +1 -1
  4. package/dist/lib/internal/cli-route-inventory.js +1 -1
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-finalization.js +41 -1
  7. package/dist/lib/internal/task-ledger.js +13 -5
  8. package/dist/lib/internal/task-operation-registry.js +1 -0
  9. package/dist/lib/platform/completion-backfill.js +63 -17
  10. package/dist/lib/sandbox/control/server.js +26 -143
  11. package/dist/lib/sandbox/task-cleanup.js +13 -20
  12. package/dist/lib/task/artifact-command.js +11 -4
  13. package/dist/lib/task/artifact-lifecycle.js +217 -85
  14. package/dist/lib/task/artifact-operations.js +1 -26
  15. package/dist/lib/task/artifact-receipts.js +4 -1
  16. package/dist/lib/task/artifact-schema.js +1 -0
  17. package/dist/lib/task/artifact-selection.js +25 -0
  18. package/dist/lib/task/capabilities.js +158 -39
  19. package/dist/lib/task/commands/status.js +9 -2
  20. package/dist/lib/task/control-authority.js +3 -2
  21. package/dist/lib/task/decision-intents.js +12 -0
  22. package/dist/lib/task/delivery.js +1 -43
  23. package/dist/lib/task/events.js +80 -64
  24. package/dist/lib/task/finalization.js +301 -143
  25. package/dist/lib/task/ledger-intents.js +157 -15
  26. package/dist/lib/task/lifecycle-path.js +75 -0
  27. package/dist/lib/task/lifecycle.js +10 -0
  28. package/dist/lib/task/local-artifact-finalization.js +1 -20
  29. package/dist/lib/task/orchestration.js +17 -7
  30. package/dist/lib/task/review-finalization.js +3 -16
  31. package/dist/lib/task/rework-intent.js +70 -4
  32. package/dist/lib/task/verification-engine.js +20 -30
  33. package/dist/lib/task/verification.js +1 -0
  34. package/dist/lib/task/workflow-warning-intents.js +21 -6
  35. package/dist/lib/task/workspace-snapshot.js +7 -2
  36. package/dist/lifecycle-build-manifest.json +3 -3
  37. package/lib/internal/cli-route-inventory.ts +1 -1
  38. package/lib/internal/task-artifact.ts +1 -1
  39. package/lib/internal/task-finalization.ts +40 -1
  40. package/lib/internal/task-ledger.ts +12 -5
  41. package/lib/internal/task-operation-registry.ts +1 -0
  42. package/lib/platform/completion-backfill.ts +78 -16
  43. package/lib/sandbox/control/server.ts +21 -146
  44. package/lib/sandbox/task-cleanup.ts +12 -20
  45. package/lib/task/artifact-command.ts +14 -4
  46. package/lib/task/artifact-lifecycle.ts +236 -85
  47. package/lib/task/artifact-operations.ts +1 -26
  48. package/lib/task/artifact-receipts.ts +4 -1
  49. package/lib/task/artifact-schema.ts +1 -0
  50. package/lib/task/artifact-selection.ts +53 -0
  51. package/lib/task/capabilities.ts +154 -40
  52. package/lib/task/commands/status.ts +11 -2
  53. package/lib/task/control-authority.ts +3 -1
  54. package/lib/task/decision-intents.ts +9 -0
  55. package/lib/task/delivery.ts +1 -40
  56. package/lib/task/events.ts +82 -64
  57. package/lib/task/finalization.ts +269 -111
  58. package/lib/task/ledger-intents.ts +155 -15
  59. package/lib/task/lifecycle-path.ts +98 -0
  60. package/lib/task/lifecycle.ts +13 -0
  61. package/lib/task/local-artifact-finalization.ts +2 -25
  62. package/lib/task/orchestration.ts +26 -8
  63. package/lib/task/review-finalization.ts +3 -16
  64. package/lib/task/rework-intent.ts +75 -5
  65. package/lib/task/verification-engine.ts +19 -30
  66. package/lib/task/verification.ts +2 -1
  67. package/lib/task/workflow-warning-intents.ts +18 -6
  68. package/lib/task/workspace-snapshot.ts +3 -2
  69. package/package.json +1 -1
  70. package/templates/.agents/rules/review-handshake.en.md +4 -4
  71. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  72. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  73. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  74. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  75. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  76. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  77. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  78. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  79. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  80. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  81. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  82. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  83. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  84. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  85. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  86. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  87. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  88. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  89. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  90. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  91. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  92. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  93. package/templates/.agents/templates/task.en.md +2 -2
  94. package/templates/.agents/templates/task.zh-CN.md +2 -2
  95. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  96. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  97. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  98. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  99. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  100. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  101. package/dist/lib/sandbox/control/finalization-response.js +0 -33
  102. package/lib/sandbox/control/finalization-response.ts +0 -35
@@ -1,9 +1,14 @@
1
1
  const REWORK_INTENT_HEADINGS = ['返工意图', 'Rework Intent'] as const;
2
2
  const REWORK_INTENT_COLUMNS = [
3
+ 'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
4
+ 'classification', 'evidence_digest', 'task_fact_digest', 'status', 'declared_at', 'consumed_at'
5
+ ] as const;
6
+ const LEGACY_REWORK_INTENT_COLUMNS = [
3
7
  'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
4
8
  'status', 'declared_at', 'consumed_at'
5
9
  ] as const;
6
- type ReworkTarget = 'analysis' | 'plan' | 'code';
10
+ type ReworkTarget = 'analysis' | 'plan' | 'code' | 'pause';
11
+ type ReworkClassification = 'scope-or-requirement' | 'design' | 'implementation' | 'human-decision' | 'insufficient-evidence';
7
12
  type ReworkIntentStatus = 'pending' | 'consumed' | 'superseded';
8
13
  type ReworkIntent = {
9
14
  intentId: string;
@@ -11,6 +16,9 @@ type ReworkIntent = {
11
16
  sourceArtifact: string;
12
17
  sourceSha256: string;
13
18
  target: ReworkTarget;
19
+ classification?: ReworkClassification;
20
+ evidenceDigest?: string;
21
+ taskFactDigest?: string;
14
22
  status: ReworkIntentStatus;
15
23
  declaredAt: string;
16
24
  consumedAt: string;
@@ -66,15 +74,19 @@ function parseReworkIntentDocument(content: string): ReworkIntentParseResult {
66
74
  const row = Object.fromEntries(REWORK_INTENT_COLUMNS.map((column, index) => [column, values[index]!])) as Record<string, string>;
67
75
  if (!/^RI-[1-9]\d*$/.test(row.intent_id!)) throw new Error(`invalid intent id '${row.intent_id}'`);
68
76
  if (!/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id!)) throw new Error(`invalid finding id '${row.finding_id}'`);
69
- if (!['analysis', 'plan', 'code'].includes(row.target!)) throw new Error(`invalid rework target '${row.target}'`);
77
+ if (!['analysis', 'plan', 'code', 'pause'].includes(row.target!)) throw new Error(`invalid rework target '${row.target}'`);
78
+ if (!['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence'].includes(row.classification!)) throw new Error(`invalid rework classification '${row.classification}'`);
70
79
  if (!['pending', 'consumed', 'superseded'].includes(row.status!)) throw new Error(`invalid rework status '${row.status}'`);
71
80
  if (!/^[a-f0-9]{64}$/.test(row.source_sha256!)) throw new Error(`invalid source hash for '${row.intent_id}'`);
81
+ if (!/^[a-f0-9]{64}$/.test(row.evidence_digest!) || !/^[a-f0-9]{64}$/.test(row.task_fact_digest!)) throw new Error(`invalid semantic digest for '${row.intent_id}'`);
72
82
  if (!row.source_artifact || !row.declared_at) throw new Error(`rework intent '${row.intent_id}' has invalid required fields`);
73
83
  const terminal = row.status === 'consumed' || row.status === 'superseded';
74
84
  if (terminal !== Boolean(row.consumed_at)) throw new Error(`rework intent '${row.intent_id}' has invalid consumed_at`);
75
85
  return {
76
86
  intentId: row.intent_id!, findingId: row.finding_id!, sourceArtifact: row.source_artifact!,
77
87
  sourceSha256: row.source_sha256!, target: row.target as ReworkTarget,
88
+ classification: row.classification as ReworkClassification,
89
+ evidenceDigest: row.evidence_digest!, taskFactDigest: row.task_fact_digest!,
78
90
  status: row.status as ReworkIntentStatus, declaredAt: row.declared_at!, consumedAt: row.consumed_at!
79
91
  } satisfies ReworkIntent;
80
92
  });
@@ -96,7 +108,10 @@ function renderReworkIntents(intents: readonly ReworkIntent[]): string {
96
108
  `| ${REWORK_INTENT_COLUMNS.map(() => '---').join(' | ')} |`,
97
109
  ...intents.map((intent) => `| ${[
98
110
  intent.intentId, intent.findingId, intent.sourceArtifact, intent.sourceSha256,
99
- intent.target, intent.status, intent.declaredAt, intent.consumedAt
111
+ intent.target, intent.classification ?? (intent.target === 'analysis' ? 'scope-or-requirement' : intent.target === 'plan' ? 'design' : intent.target === 'code' ? 'implementation' : 'insufficient-evidence'),
112
+ intent.evidenceDigest ?? requireDigest(`${intent.findingId}|${intent.sourceArtifact}|${intent.sourceSha256}`),
113
+ intent.taskFactDigest ?? requireDigest(`current|${intent.findingId}|${intent.target}`),
114
+ intent.status, intent.declaredAt, intent.consumedAt
100
115
  ].map(escape).join(' | ')} |`)
101
116
  ].join('\n');
102
117
  }
@@ -104,11 +119,63 @@ function renderReworkIntents(intents: readonly ReworkIntent[]): string {
104
119
  function upsertReworkIntent(intents: readonly ReworkIntent[], intent: ReworkIntent): { changed: boolean; intents: readonly ReworkIntent[] } {
105
120
  const existing = intents.find((candidate) => candidate.intentId === intent.intentId);
106
121
  if (!existing) return { changed: true, intents: [...intents, intent] };
107
- const identity = (value: ReworkIntent) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target];
122
+ const identity = (value: ReworkIntent) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target, value.classification, value.evidenceDigest, value.taskFactDigest];
108
123
  if (JSON.stringify(identity(existing)) !== JSON.stringify(identity(intent))) throw new Error(`intent '${intent.intentId}' identity conflicts with existing state`);
109
124
  return { changed: false, intents };
110
125
  }
111
126
 
127
+ function consumeHumanDecisionReworkIntents(
128
+ intents: readonly ReworkIntent[],
129
+ findingId: string,
130
+ consumedAt: string
131
+ ): { changed: boolean; intents: readonly ReworkIntent[] } {
132
+ let changed = false;
133
+ const next = intents.map((intent) => {
134
+ if (intent.status !== 'pending' || intent.target !== 'pause' || intent.classification !== 'human-decision' || intent.findingId !== findingId) return intent;
135
+ changed = true;
136
+ return { ...intent, status: 'consumed' as const, consumedAt };
137
+ });
138
+ return { changed, intents: next };
139
+ }
140
+
141
+ function parseLegacyReworkIntentDocument(content: string): ReworkIntentParseResult {
142
+ // TODO(compat): Remove this explicit decoder once no task contains the eight-column rework-intent header.
143
+ const body = sectionBody(content);
144
+ if (body === null) return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'legacy rework intent section is missing' };
145
+ const lines = body.split(/\r?\n/).filter((line) => line.trim());
146
+ const header = lines[0] ? cells(lines[0]) : null;
147
+ const separator = lines[1] ? cells(lines[1]) : null;
148
+ if (!header || !separator || header.length !== LEGACY_REWORK_INTENT_COLUMNS.length || !header.every((value, index) => value === LEGACY_REWORK_INTENT_COLUMNS[index])) {
149
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'rework intent section is not the known legacy table' };
150
+ }
151
+ try {
152
+ const intents = lines.slice(2).map((line) => {
153
+ const values = cells(line);
154
+ if (!values || values.length !== LEGACY_REWORK_INTENT_COLUMNS.length) throw new Error('legacy rework intent row has the wrong column count');
155
+ const row = Object.fromEntries(LEGACY_REWORK_INTENT_COLUMNS.map((column, index) => [column, values[index]!])) as Record<string, string>;
156
+ if (!/^RI-[1-9]\d*$/.test(row.intent_id!) || !/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id!)) throw new Error('legacy rework intent identity is invalid');
157
+ if (!['analysis', 'plan', 'code'].includes(row.target!) || !['pending', 'consumed', 'superseded'].includes(row.status!)) throw new Error('legacy rework intent state is invalid');
158
+ if (!/^[a-f0-9]{64}$/.test(row.source_sha256!)) throw new Error('legacy rework intent source hash is invalid');
159
+ const legacyDigest = (value: string) => requireDigest(value);
160
+ const classification: ReworkClassification = row.target === 'analysis' ? 'scope-or-requirement' : row.target === 'plan' ? 'design' : 'implementation';
161
+ return {
162
+ intentId: row.intent_id!, findingId: row.finding_id!, sourceArtifact: row.source_artifact!, sourceSha256: row.source_sha256!,
163
+ target: row.target as ReworkTarget, classification,
164
+ evidenceDigest: legacyDigest(`${row.finding_id}|${row.source_artifact}|${row.source_sha256}`),
165
+ taskFactDigest: legacyDigest(`legacy|${row.finding_id}|${row.target}`),
166
+ status: row.status as ReworkIntentStatus, declaredAt: row.declared_at!, consumedAt: row.consumed_at!
167
+ } satisfies ReworkIntent;
168
+ });
169
+ return { ok: true, present: true, intents };
170
+ } catch (error) {
171
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) };
172
+ }
173
+ }
174
+
175
+ function requireDigest(value: string): string {
176
+ return createHash('sha256').update(value).digest('hex');
177
+ }
178
+
112
179
  function consumeReworkIntents(
113
180
  intents: readonly ReworkIntent[],
114
181
  target: ReworkTarget,
@@ -161,5 +228,8 @@ export {
161
228
  consumeReworkIntents,
162
229
  supersedeReworkIntents,
163
230
  upsertReworkIntent
231
+ , consumeHumanDecisionReworkIntents
232
+ , parseLegacyReworkIntentDocument
164
233
  };
165
- export type { ReworkIntent, ReworkIntentParseResult, ReworkIntentStatus, ReworkTarget };
234
+ export type { ReworkClassification, ReworkIntent, ReworkIntentParseResult, ReworkIntentStatus, ReworkTarget };
235
+ import { createHash } from 'node:crypto';
@@ -40,7 +40,6 @@ import type { OrchestrationRun } from "./orchestration.ts";
40
40
  import { resolveDeliveryTarget } from "./delivery-target.ts";
41
41
  import { readPrDeliveryFact } from "./pr-delivery-fact.ts";
42
42
  import { validateLocalArtifact } from "./local-artifact-finalization.ts";
43
- import { validateQualificationAudit } from "./qualification-audit.ts";
44
43
  import { getArtifactSchema } from "./artifact-schema.ts";
45
44
  import { inspectArtifactContract } from "./artifact-operations.ts";
46
45
  import type { VerificationShared } from "./verification-types.ts";
@@ -78,7 +77,6 @@ const BRANCH_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
78
77
 
79
78
  // Review disagreement ledger (see .agents/rules/review-handshake.md).
80
79
  const LEDGER_SECTION_NAMES = ["审查分歧账本", "Review Disagreement Ledger"];
81
- const DEFAULT_MAX_HANDSHAKE_ROUNDS = 3;
82
80
  const POST_REVIEW_COMMIT_STAGE = "post-review-commit";
83
81
  const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
84
82
 
@@ -511,19 +509,6 @@ function checkArtifact({ taskDir, config, artifactFile, skillName }: any): any {
511
509
  }
512
510
 
513
511
  const content = fs.readFileSync(artifactPath, "utf8");
514
- let taskContent = "";
515
- const taskPath = path.join(taskDir, "task.md");
516
- if (fs.existsSync(taskPath)) {
517
- try {
518
- taskContent = fs.readFileSync(taskPath, "utf8");
519
- } catch (error) {
520
- return failResult("artifact", `Cannot read task qualification contract: ${error instanceof Error ? error.message : String(error)}`);
521
- }
522
- }
523
- if (taskContent && /^review-(?:analysis|plan|code)$/.test(skillName)) {
524
- const qualification = validateQualificationAudit(taskContent, content, { artifact: path.basename(artifactPath) });
525
- if (!qualification.ok) return failResult("artifact", `${path.basename(artifactPath)} has qualification audit errors: ${qualification.code}: ${qualification.message}`);
526
- }
527
512
  const schemaFamily = skillName === "analyze-task"
528
513
  ? "analysis"
529
514
  : skillName === "review-analysis"
@@ -547,9 +532,7 @@ function checkArtifact({ taskDir, config, artifactFile, skillName }: any): any {
547
532
  }
548
533
  if (localFamily) {
549
534
  const local = validateLocalArtifact(content, {
550
- family: localFamily,
551
- ...(taskContent ? { taskContent } : {}),
552
- artifact: path.basename(artifactPath)
535
+ family: localFamily
553
536
  });
554
537
  if (!local.ok) {
555
538
  return failResult(
@@ -818,12 +801,11 @@ function checkReviewLedger({ taskDir, config, repositoryRoot }: any): any {
818
801
  }
819
802
 
820
803
  const stageScope = Array.isArray(config.stage_scope) ? config.stage_scope : null;
821
- const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS, repositoryRoot));
822
804
  const problems = [];
823
805
  let inScopeCount = 0;
824
806
 
825
807
  for (const row of rows) {
826
- const { id, stage, round: roundRaw, status, evidence } = row;
808
+ const { id, stage, status, evidence } = row;
827
809
  const stageScoped = stageScope ? stageScope.includes(stage) : true;
828
810
  // post-review-commit exemption rows are consumed by the post-review-commit
829
811
  // check, not enforced here.
@@ -843,15 +825,6 @@ function checkReviewLedger({ taskDir, config, repositoryRoot }: any): any {
843
825
  if (status !== "open" && evidence === "") {
844
826
  problems.push(`${id}: status '${status}' requires evidence`);
845
827
  }
846
- const round = Number.parseInt(roundRaw!, 10);
847
- if (
848
- Number.isFinite(round) &&
849
- round >= maxRounds &&
850
- !LEDGER_TERMINAL.has(status!) &&
851
- status !== "needs-human-decision"
852
- ) {
853
- problems.push(`${id}: round ${round} reached limit ${maxRounds} without convergence; escalate to needs-human-decision`);
854
- }
855
828
  if (!LEDGER_TERMINAL.has(status!)) {
856
829
  problems.push(`${id}: unresolved (status '${status}')`);
857
830
  }
@@ -1194,7 +1167,8 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }: any): any {
1194
1167
  let targetHead;
1195
1168
  let diffBase;
1196
1169
  try {
1197
- gitRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
1170
+ const taskRepositoryRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
1171
+ gitRoot = reviewWorktreeForTask(taskRepositoryRoot, String(task.metadata.branch || "").trim()) ?? taskRepositoryRoot;
1198
1172
  head = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
1199
1173
  const target = resolveDeliveryTarget(gitRoot, { remote: deliveryRemote, baseRef: deliveryBaseRef });
1200
1174
  if (!target.ok) throw new Error(target.message);
@@ -1275,6 +1249,21 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }: any): any {
1275
1249
  );
1276
1250
  }
1277
1251
 
1252
+ function reviewWorktreeForTask(repositoryRoot: string, branch: string): string | null {
1253
+ if (!branch) return null;
1254
+ const expectedRef = `refs/heads/${branch}`;
1255
+ const records = execFileSync("git", ["-C", repositoryRoot, "worktree", "list", "--porcelain"], {
1256
+ encoding: "utf8",
1257
+ stdio: ["pipe", "pipe", "pipe"]
1258
+ }).trim().split(/\r?\n\r?\n/);
1259
+ for (const record of records) {
1260
+ const worktree = /^worktree (.+)$/m.exec(record)?.[1];
1261
+ const branchRef = /^branch (.+)$/m.exec(record)?.[1];
1262
+ if (worktree && branchRef === expectedRef) return worktree;
1263
+ }
1264
+ throw new Error(`Task branch '${branch}' is not checked out in a registered worktree`);
1265
+ }
1266
+
1278
1267
  function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }: any): any {
1279
1268
  if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
1280
1269
  return { ok: true, sha: lastReviewedCommit };
@@ -15,7 +15,7 @@ type VerificationEvent =
15
15
  | 'review-analysis.completed' | 'plan.completed' | 'review-plan.completed'
16
16
  | 'code.completed' | 'review-code.completed' | 'manual-validation.completed' | 'validation-run.completed'
17
17
  | 'block-task.completed' | 'cancel-task.completed' | 'commit.completed'
18
- | 'complete-task.preflight' | 'complete-task.hard-preflight' | 'complete-task.completed'
18
+ | 'complete-task.preflight' | 'complete-task.hard-preflight' | 'complete-task.prepared' | 'complete-task.completed'
19
19
  | 'create-pr.completed' | 'create-task.completed'
20
20
  | 'import-codescan.completed' | 'import-dependabot.completed'
21
21
  | 'import-issue.completed' | 'watch-pr.completed'
@@ -71,6 +71,7 @@ const VERIFICATION_CATALOG: Readonly<Record<VerificationEvent, VerificationSpec>
71
71
  'commit.completed': gate('commit', 'active'),
72
72
  'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
73
73
  'complete-task.hard-preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
74
+ 'complete-task.prepared': gate('complete-task', 'active'),
74
75
  'complete-task.completed': gate('complete-task', 'completed'),
75
76
  'create-pr.completed': gate('create-pr', 'active'),
76
77
  'create-task.completed': gate('create-task', 'active'),
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
 
3
3
  import { resolveTaskRef } from './resolve-ref.ts';
4
+ import { parseTaskFrontmatter } from './frontmatter.ts';
4
5
  import {
5
6
  WORKFLOW_WARNING_COLUMNS,
6
7
  WORKFLOW_WARNING_HEADINGS,
@@ -137,11 +138,18 @@ function applyWorkflowWarningIntent(intent: WorkflowWarningIntent, options: Task
137
138
  function projectFinalizationWarning(
138
139
  taskRef: string,
139
140
  warning: OperationWarning & { status: 'open' | 'resolved'; resolvedAt: string | null },
140
- options: TaskWriteOptions = {}
141
+ options: TaskWriteOptions & { resolution?: 'passed' | 'superseded' } = {}
141
142
  ): FinalizationWarningProjectionResult {
142
143
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
143
144
  if (!resolved.ok) return { status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } };
144
- if (resolved.state !== 'completed') return { status: 'failed', changed: false, error: { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected completed` } };
145
+ let prepared = resolved.state === 'completed';
146
+ if (!prepared && resolved.state === 'active') {
147
+ try { prepared = parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')).status === 'completed'; }
148
+ catch { prepared = false; }
149
+ }
150
+ if (!prepared) {
151
+ return { status: 'failed', changed: false, error: { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected a prepared or completed task` } };
152
+ }
145
153
  let content: string;
146
154
  let rows: WorkflowWarning[];
147
155
  try {
@@ -160,22 +168,26 @@ function projectFinalizationWarning(
160
168
  message: warning.message,
161
169
  action: retryHintForWarning(warning),
162
170
  resolvedAt: warning.resolvedAt || (warning.status === 'resolved' ? metadata.timestamp : ''),
163
- resolution: warning.status === 'resolved' ? 'Resolved by a successful finalization retry' : ''
171
+ resolution: warning.status === 'resolved'
172
+ ? options.resolution === 'superseded' ? 'Superseded by a later verification observation' : 'Resolved by a successful finalization retry'
173
+ : ''
164
174
  }
165
175
  : {
166
176
  id: nextWarningId(rows), time: metadata.timestamp, step: warning.step,
167
177
  severity: warning.severity, code: warning.code, status: warning.status,
168
178
  target: warning.target, message: warning.message, action: retryHintForWarning(warning),
169
- resolvedAt: warning.resolvedAt || '', resolution: warning.status === 'resolved' ? 'Resolved by a successful finalization retry' : ''
179
+ resolvedAt: warning.resolvedAt || '', resolution: warning.status === 'resolved'
180
+ ? options.resolution === 'superseded' ? 'Superseded by a later verification observation' : 'Resolved by a successful finalization retry'
181
+ : ''
170
182
  };
171
183
  if (existing && JSON.stringify(existing) === JSON.stringify(after)) return { status: 'no-op', changed: false, error: null };
172
184
  const written = writeTask({
173
185
  taskRef: resolved.taskId,
174
- expectedState: 'completed',
186
+ expectedState: resolved.state,
175
187
  mutations: [...sectionMutation(content), rowMutation(after)]
176
188
  }, {
177
189
  ...options,
178
- taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: 'completed' },
190
+ taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
179
191
  metadataProvider: () => metadata
180
192
  });
181
193
  if (written.status === 'failed') return { status: 'failed', changed: false, error: { code: written.error.code, message: written.error.message } };
@@ -42,7 +42,8 @@ function captureWorktreeTree(repoRoot: string, forcedPath: string | null): strin
42
42
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-orchestration-index-'));
43
43
  const indexFile = path.join(tempDir, 'index');
44
44
  try {
45
- git(repoRoot, indexFile, ['read-tree', 'HEAD']);
45
+ try { git(repoRoot, indexFile, ['read-tree', 'HEAD']); }
46
+ catch { git(repoRoot, indexFile, ['read-tree', '--empty']); }
46
47
  git(repoRoot, indexFile, ['add', '-A', '--', ':/']);
47
48
  if (forcedPath && fs.existsSync(path.join(repoRoot, forcedPath))) {
48
49
  git(repoRoot, indexFile, ['add', '-f', '--', forcedPath]);
@@ -138,5 +139,5 @@ function diffWorkspaceSnapshots(repoRoot: string, before: string, after: string)
138
139
  return [...changed].sort();
139
140
  }
140
141
 
141
- export { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots };
142
+ export { captureRepositorySnapshot, captureWorkspaceSnapshot, captureWorktreeTree, diffWorkspaceSnapshots };
142
143
  export type { OrchestrationWorkspaceFingerprint, RepositorySnapshot, WorkspaceSnapshotContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,11 +29,11 @@ After the executor gives `adjusted` / `refuted` / `cannot-judge`, the reviewer m
29
29
  - **Hold with new evidence** → set back to `open` (with new evidence, returned to the executor).
30
30
  - **Escalate to human** → set to `needs-human-decision`.
31
31
 
32
- ## Convergence termination (loop guard)
32
+ ## Evidence-driven rework convergence
33
33
 
34
- - The per-finding handshake round limit is `MAX_HANDSHAKE_ROUNDS`, default **3**, overridable via `review.maxHandshakeRounds` in `.agents/.airc.json`.
35
- - When a finding's `round` reaches the limit without entering a terminal state, it must be forced to `needs-human-decision`; the gate rejects rows that hit the limit without escalating.
36
- - `needs-human-decision` keeps blocking completion until a human records a ruling in the task.md `## 人工裁决` section and flips the row to `human-decided`.
34
+ - The first Changes Requested verdict permits an ordinary revision. After one revision-review cycle, another Changes Requested verdict must classify the problem as scope or requirements, design, implementation, human decision, or insufficient evidence before another executor artifact is created.
35
+ - Classification binds the finding, source review SHA, and stable task-fact digest. Artifact names, rounds, and retry counts are not evidence by themselves.
36
+ - Human choice and insufficient evidence pause stably; implementation can continue when new decisive evidence exists. Retry count alone never forces a human escalation.
37
37
 
38
38
  ## Same-model convergence-bias mitigation (documentation-level discipline)
39
39
 
@@ -29,11 +29,11 @@
29
29
  - **补充新证据后坚持** → 账本置回 `open`(带新证据,回到执行方)。
30
30
  - **升级人工裁决** → 账本置 `needs-human-decision`。
31
31
 
32
- ## 收敛终止语义(防死循环)
32
+ ## 证据驱动的返工收敛
33
33
 
34
- - 单条 finding 的握手轮次上限 `MAX_HANDSHAKE_ROUNDS`,默认 **3**,可在 `.agents/.airc.json` 的 `review.maxHandshakeRounds` 覆盖。
35
- - 某条 finding 的 `round` 达到上限仍未进入终态,必须强制置 `needs-human-decision`;gate 会拦截"达限却未升级"的行。
36
- - `needs-human-decision` 持续阻塞完成,直到人工在 task.md `## 人工裁决` 段记录裁定并把该行翻为 `human-decided`。
34
+ - 首次 Changes Requested 允许正常修订。完成一次修订—复审后再次收到 Changes Requested 时,必须在下一执行产物前把问题分类为范围或需求、方案、实现、人工裁决或证据不足。
35
+ - 分类必须绑定 finding、来源审查 SHA 和稳定任务事实摘要。文件名、artifact round 或返工次数本身不构成新证据。
36
+ - 人工选择或证据不足进入稳定暂停;具有新决定性证据的实现缺陷可以继续修订。不得仅因次数达到阈值强制升级人工裁决。
37
37
 
38
38
  ## 同源模型收敛偏差缓解(文档级纪律)
39
39
 
@@ -13,6 +13,10 @@ If the entry operands contain `--orchestrated`, bind `{execution-flag}` to `--or
13
13
 
14
14
  ## Boundary / Critical Rules
15
15
 
16
+ ### Flow decision
17
+
18
+ The analysis artifact must record the selected path, factual basis, unmet higher-path conditions, and factual upgrade triggers in `## Flow Decision`. The path is streamlined, standard, or full; file count, module count, and speculative risk cannot upgrade it by themselves.
19
+
16
20
  ### Persisted Report Evidence
17
21
 
18
22
  Before generating the analysis report, read `.agents/rules/evidence-reporting.md`. Record the command, target scope, status or structured result, actual result, and uncovered parts for state checks and successful checks; include decisive raw excerpts only for failures, blocking conditions, or disputes.
@@ -61,7 +65,7 @@ If `task.md` is missing, tell the user to create or import the task first.
61
65
 
62
66
  ### 2. Resolve the Analysis Context
63
67
 
64
- Run `agent-infra-internal task-artifact {task-id} inspect --family analysis`. Continue only for `ready`; take `{analysis-round}` / `{analysis-artifact}` from `next.round` / `next.name` and revision inputs from `inputs`. Do not scan rounds or construct names in the skill.
68
+ Run `agent-infra-internal task-artifact {task-id} inspect --family analysis`. Continue only for `ready`. When `selection.disposition` is `reuse`, reuse `selection.artifact`, do not run started/init or write a new artifact, and continue directly to completion verification and next-step guidance. Otherwise take `{analysis-round}` / `{analysis-artifact}` from `next.round` / `next.name` and revision inputs from `inputs`. Do not scan rounds or construct names in the skill.
65
69
 
66
70
  ### 3. Read Task Context
67
71
 
@@ -261,7 +265,10 @@ Keep the gate output in your reply as fresh evidence. Do not claim completion wi
261
265
  > Before rendering next steps, read `.agents/rules/next-step-output.md`, invoke the shared helper only for the selected scenario, and insert its stdout at `{next-step-commands}`.
262
266
 
263
267
  Output format:
264
- Populate `{next-step-commands}` for this scenario by running `agent-infra-internal agent-client next-steps --skill review-analysis --task-ref {task-ref}`.
268
+ Select one command from the canonical path to populate `{next-step-commands}`:
269
+ - Streamlined: `agent-infra-internal agent-client next-steps --skill code-task --task-ref {task-ref}`
270
+ - Standard: `agent-infra-internal agent-client next-steps --skill plan-task --task-ref {task-ref}`
271
+ - Full: `agent-infra-internal agent-client next-steps --skill review-analysis --task-ref {task-ref}`
265
272
 
266
273
  ```
267
274
  Analysis complete for task {task-id}.
@@ -274,7 +281,7 @@ Summary:
274
281
  Output file:
275
282
  - Analysis report: .agents/workspace/active/{task-id}/{analysis-artifact}
276
283
 
277
- Next step - review the analysis:
284
+ Next step - continue on the selected path:
278
285
  {next-step-commands}
279
286
  ```
280
287
 
@@ -292,7 +299,7 @@ Next step - review the analysis:
292
299
 
293
300
  ## STOP
294
301
 
295
- After completing the checklist, **stop immediately**. Wait for the user to review the analysis result and manually invoke the `plan-task` skill.
302
+ After completing the checklist, **stop immediately**. Wait for the user to invoke the next stage selected above.
296
303
 
297
304
  ## Notes
298
305
 
@@ -13,6 +13,10 @@ description: >
13
13
 
14
14
  ## 行为边界 / 关键规则
15
15
 
16
+ ### 流程裁定
17
+
18
+ 分析产物必须在 `## 流程裁定` 中记录本任务路径、判定依据、未满足的更高路径条件和事实型升级触发条件。路径仅可为精简、标准或完整;文件数、模块数和推测性风险不能单独升级路径。
19
+
16
20
  ### 持久化报告证据
17
21
 
18
22
  生成分析报告时,先读取 `.agents/rules/evidence-reporting.md`。状态核对和成功检查记录命令、目标范围、状态/结构化结果、实际结果和未覆盖部分;失败、阻塞或争议才附决定性原文摘录。
@@ -64,7 +68,7 @@ agent-infra-internal task-event {task-id} analyze.started --agent {standard-agen
64
68
 
65
69
  ### 2. 解析分析上下文
66
70
 
67
- 运行 `agent-infra-internal task-artifact {task-id} inspect --family analysis`。仅当结果为 `ready` 时继续;从 `next.round` / `next.name` 记录 `{analysis-round}` / `{analysis-artifact}`,从 `inputs` 读取修订上下文。不得自行扫描轮次或拼装文件名。随后执行 started 事件,并以事件返回的 `artifactContext` 复核同一身份。
71
+ 运行 `agent-infra-internal task-artifact {task-id} inspect --family analysis`。仅当结果为 `ready` 时继续。若 `selection.disposition` 为 `reuse`,复用 `selection.artifact`,不得执行 started、init 或写入新产物,并直接进入完成校验与下一步提示。其他状态从 `next.round` / `next.name` 记录 `{analysis-round}` / `{analysis-artifact}`,从 `inputs` 读取修订上下文;不得自行扫描轮次或拼装文件名。随后执行 started 事件,并以事件返回的 `artifactContext` 复核同一身份。
68
72
 
69
73
  ### 3. 阅读任务上下文
70
74
 
@@ -261,7 +265,10 @@ agent-infra-internal task-verify {task-id} analyze.completed --artifact {analysi
261
265
  > 渲染下一步前先读取 `.agents/rules/next-step-output.md`,仅为已选场景调用统一 helper,并将 stdout 填入 `{next-step-commands}`。
262
266
 
263
267
  输出格式:
264
- 使用 `agent-infra-internal agent-client next-steps --skill review-analysis --task-ref {task-ref}` 生成本场景的 `{next-step-commands}`。
268
+ 按分析中的规范路径选择一条命令生成 `{next-step-commands}`:
269
+ - 精简路径:`agent-infra-internal agent-client next-steps --skill code-task --task-ref {task-ref}`
270
+ - 标准路径:`agent-infra-internal agent-client next-steps --skill plan-task --task-ref {task-ref}`
271
+ - 完整路径:`agent-infra-internal agent-client next-steps --skill review-analysis --task-ref {task-ref}`
265
272
 
266
273
  ```
267
274
  任务 {task-id} 分析完成。
@@ -274,7 +281,7 @@ agent-infra-internal task-verify {task-id} analyze.completed --artifact {analysi
274
281
  产出文件:
275
282
  - 分析报告:.agents/workspace/active/{task-id}/{analysis-artifact}
276
283
 
277
- 下一步 - 审查需求分析:
284
+ 下一步 - 按所选路径继续:
278
285
  {next-step-commands}
279
286
  ```
280
287
 
@@ -292,7 +299,7 @@ agent-infra-internal task-verify {task-id} analyze.completed --artifact {analysi
292
299
 
293
300
  ## 停止
294
301
 
295
- 完成检查清单后,**立即停止**。等待用户审查分析结果并手动调用 `plan-task` 技能。
302
+ 完成检查清单后,**立即停止**。等待用户调用上述所选路径的下一阶段。
296
303
 
297
304
  ## 注意事项
298
305
 
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: code-task
3
3
  description: >
4
- Implement code from the technical plan and output a report.
5
- Use when an approved technical plan needs implementing, or code review found issues to fix.
4
+ Implement code from the selected lifecycle input and output a report.
5
+ Use when analysis or a technical plan is ready for the selected path, or code review found issues to fix.
6
6
  Only invoke this skill automatically when the conversation includes a resolvable task reference.
7
7
  ---
8
8
 
@@ -11,16 +11,20 @@ description: >
11
11
 
12
12
  If the entry operands contain `--orchestrated`, bind `{execution-flag}` to `--orchestrated` and forward it unchanged to the completed event; otherwise bind it to an empty value. Never infer it from `orchestration.json`, environment variables, or prior artifacts. Lifecycle events also require explicit trigger data: use `{trigger-initiator}=orchestrator` for orchestration and `model` otherwise; `{request-id}` is a stable single-line identifier for this task and artifact round, and `{reason-code}` is `user-request` for initial work and `review-finding` for fixes or decisions. Reuse the same values for started and completed.
13
13
 
14
- Implement the approved plan and produce `code.md` or `code-r{N}.md`. This skill supports initial implementation, fix mode based on `review-code` feedback, and human-decision-driven implementation.
14
+ Implement from the input selected by the lifecycle path and produce `code.md` or `code-r{N}.md`. This skill supports initial implementation, fix mode based on `review-code` feedback, and human-decision-driven implementation.
15
15
 
16
16
  ## Boundary / Critical Rules
17
17
 
18
+ ### Path and rework
19
+
20
+ This stage consumes only the canonical path fact parsed by core from the latest valid analysis artifact; a stage omitted by that path must not create an artifact. When another Changes Requested verdict follows one completed revision-review cycle in the same stage, classify the rework before the next executor artifact and bind it to the finding, source SHA, and stable task-fact digest. A round or filename change is not new evidence.
21
+
18
22
  ### Persisted Report Evidence
19
23
 
20
24
  Before generating the implementation report, read `.agents/rules/evidence-reporting.md`. Successful tests record the command, target scope, status or structured result, actual result, and uncovered parts; failures, blocking conditions, or disputes retain a reproducible entry point, exact location, and decisive excerpt instead of complete successful stdout.
21
25
 
22
26
  - When evaluating candidate qualification or `HD-N`, read `.agents/rules/decision-qualification.md`, audit normalized task constraints/candidates, and record the five qualification-audit tables in the implementation report; unknown or unconfirmed constraints must not automatically exclude a candidate
23
- - Follow the latest plan artifact: `plan.md` or `plan-r{N}.md`
27
+ - Follow the lifecycle input selected by core: `analysis.md` / `analysis-r{N}.md` for the streamlined path, and `plan.md` / `plan-r{N}.md` for the standard or full path
24
28
  - Before generating task or lifecycle Markdown that will be synchronized to an Issue, read `.agents/rules/sync-content-generation.md` and apply its producer-side constraints; the sync path does not parse or rewrite the body
25
29
  - Read `.agents/rules/compatibility-policy.md` before implementation. Implement only the compatibility budget explicitly approved by the plan; never retain old branches, result contracts, or migration shims merely to be “safe”
26
30
  - Fix mode verifies each finding of the latest `review-code` one by one: fix it if it holds, or rebut it and record it under unresolved if it is unfounded/hallucinated; do not expand to issues the review did not list; manual-validation items are out of scope
@@ -56,7 +60,7 @@ After prerequisites and mode are confirmed and before this round's first artifac
56
60
 
57
61
  ### 1. Verify Prerequisites
58
62
 
59
- Require `task.md` and at least one plan artifact: `plan.md` or `plan-r{N}.md`.
63
+ Require `task.md`. Step 4 validates the lifecycle input selected by the chosen path; the skill must not require a plan artifact unconditionally.
60
64
 
61
65
  ### 2. Ensure the Task Branch
62
66
 
@@ -78,6 +82,8 @@ status=$?
78
82
  echo "$result"
79
83
  ```
80
84
 
85
+ When `selection.disposition` is `reuse`, reuse `selection.artifact`, do not run started/init, modify code, or write a new artifact, and continue directly to completion verification and next-step guidance. Otherwise continue with the table below.
86
+
81
87
  Dispatch by `$status` and `result.mode`:
82
88
 
83
89
  - `0` + `"init"`: initial implementation; record `{code-artifact}` and `{code-round}`
@@ -90,11 +96,11 @@ Dispatch by `$status` and `result.mode`:
90
96
 
91
97
  ### 5. Read Structured Inputs
92
98
 
93
- Use only the structured result from step 4: read the selected plan artifact and, in fix mode, the selected review artifact. Use `next.name` as `{code-artifact}` and `next.round` as `{code-round}`. In decision mode, take the unified identity from `implementation_input`, `decision_id`, and `decision_evidence`; do not rescan or construct identities in the skill.
99
+ Use only the structured result from step 4: read `{lifecycle-input-artifact}` from `inputs` (analysis for the streamlined path, plan otherwise) and, in fix mode, the selected review artifact. Use `next.name` as `{code-artifact}` and `next.round` as `{code-round}`. In decision mode, take the unified identity from `implementation_input`, `decision_id`, and `decision_evidence`; do not rescan or construct identities in the skill.
94
100
 
95
- ### 6. Read the Technical Plan
101
+ ### 6. Read the Lifecycle Input
96
102
 
97
- Extract implementation steps, files, test strategy, constraints, risks, and approved tradeoffs. In decision mode, also read the `{input-id}` row and its `{decision-evidence}` record in task.md, and implement only that ruling's requested behavior change.
103
+ Extract implementation steps, files, test strategy, constraints, risks, and approved tradeoffs from `{lifecycle-input-artifact}`. In decision mode, also read the `{input-id}` row and its `{decision-evidence}` record in task.md, and implement only that ruling's requested behavior change.
98
104
 
99
105
  ### 7. Implement the Code
100
106