@fitlab-ai/agent-infra 0.9.3 → 0.9.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 (203) hide show
  1. package/README.md +3 -1
  2. package/README.zh-CN.md +3 -1
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +144 -123
  6. package/dist/lib/agent-clients/adapter.js +94 -4
  7. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  8. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  9. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  10. package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +382 -0
  12. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +163 -0
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -0
  14. package/dist/lib/agent-clients/adapters/codex.js +2 -2
  15. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  16. package/dist/lib/agent-clients/adapters/opencode.js +15 -3
  17. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  18. package/dist/lib/agent-clients/registry.js +9 -1
  19. package/dist/lib/git/worktree-identity.js +64 -0
  20. package/dist/lib/internal/codex-lifecycle.js +202 -0
  21. package/dist/lib/internal/sandbox-control.js +24 -0
  22. package/dist/lib/internal/task-activity.js +45 -13
  23. package/dist/lib/internal/task-create.js +48 -0
  24. package/dist/lib/internal/task-ledger.js +12 -3
  25. package/dist/lib/internal/task-lifecycle.js +19 -1
  26. package/dist/lib/internal/task-orchestration.js +170 -27
  27. package/dist/lib/platform/github-client.js +19 -2
  28. package/dist/lib/platform/pr-checks.js +1 -1
  29. package/dist/lib/platform/pull-requests.js +47 -1
  30. package/dist/lib/platform/releases.js +13 -5
  31. package/dist/lib/process-data/index.js +239 -0
  32. package/dist/lib/process-data/normalize.js +198 -0
  33. package/dist/lib/process-data/privacy.js +33 -0
  34. package/dist/lib/process-data/reconcile.js +95 -0
  35. package/dist/lib/process-data/repair.js +58 -0
  36. package/dist/lib/process-data/sources.js +345 -0
  37. package/dist/lib/process-data/store.js +282 -0
  38. package/dist/lib/process-data/types.js +2 -0
  39. package/dist/lib/run/index.js +4 -3
  40. package/dist/lib/run/tui.js +7 -8
  41. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  42. package/dist/lib/sandbox/capture.js +7 -2
  43. package/dist/lib/sandbox/commands/create.js +110 -228
  44. package/dist/lib/sandbox/commands/enter.js +15 -47
  45. package/dist/lib/sandbox/commands/list-running.js +16 -6
  46. package/dist/lib/sandbox/commands/ls.js +10 -6
  47. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  48. package/dist/lib/sandbox/commands/refresh.js +4 -84
  49. package/dist/lib/sandbox/commands/rm.js +24 -3
  50. package/dist/lib/sandbox/commands/show.js +12 -3
  51. package/dist/lib/sandbox/commands/start.js +7 -4
  52. package/dist/lib/sandbox/config.js +2 -0
  53. package/dist/lib/sandbox/constants.js +6 -0
  54. package/dist/lib/sandbox/control/client.js +58 -0
  55. package/dist/lib/sandbox/control/protocol.js +54 -0
  56. package/dist/lib/sandbox/control/server.js +144 -0
  57. package/dist/lib/sandbox/dockerfile.js +36 -13
  58. package/dist/lib/sandbox/mounts.js +17 -4
  59. package/dist/lib/sandbox/recovery.js +128 -5
  60. package/dist/lib/sandbox/redaction.js +12 -0
  61. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  62. package/dist/lib/sandbox/workspace-identity.js +95 -0
  63. package/dist/lib/sandbox/workspace-view.js +109 -0
  64. package/dist/lib/task/activity-intent.js +257 -51
  65. package/dist/lib/task/activity-log.js +32 -5
  66. package/dist/lib/task/commit-finalization.js +278 -0
  67. package/dist/lib/task/commit-intent.js +165 -0
  68. package/dist/lib/task/create-service.js +137 -0
  69. package/dist/lib/task/create.js +333 -0
  70. package/dist/lib/task/decision-intents.js +31 -17
  71. package/dist/lib/task/delegation-receipts.js +2 -2
  72. package/dist/lib/task/implementation-inputs.js +54 -7
  73. package/dist/lib/task/ledger-intents.js +35 -1
  74. package/dist/lib/task/orchestration.js +505 -34
  75. package/dist/lib/task/resolve-ref.js +41 -1
  76. package/dist/lib/task/review-fingerprint.js +4 -0
  77. package/dist/lib/task/workspace-snapshot.js +78 -3
  78. package/lib/agent-clients/adapter.ts +130 -6
  79. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  80. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  81. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  82. package/lib/agent-clients/adapters/claude-code.ts +21 -3
  83. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +442 -0
  84. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +307 -0
  85. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +250 -0
  86. package/lib/agent-clients/adapters/codex.ts +2 -2
  87. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  88. package/lib/agent-clients/adapters/opencode.ts +18 -3
  89. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  90. package/lib/agent-clients/registry.ts +9 -1
  91. package/lib/git/worktree-identity.ts +86 -0
  92. package/lib/internal/codex-lifecycle.ts +204 -0
  93. package/lib/internal/sandbox-control.ts +24 -0
  94. package/lib/internal/task-activity.ts +50 -14
  95. package/lib/internal/task-create.ts +49 -0
  96. package/lib/internal/task-ledger.ts +12 -3
  97. package/lib/internal/task-lifecycle.ts +22 -1
  98. package/lib/internal/task-orchestration.ts +117 -21
  99. package/lib/platform/github-client.ts +20 -2
  100. package/lib/platform/pr-checks.ts +1 -1
  101. package/lib/platform/pull-requests.ts +49 -1
  102. package/lib/platform/releases.ts +16 -4
  103. package/lib/process-data/index.ts +224 -0
  104. package/lib/process-data/normalize.ts +202 -0
  105. package/lib/process-data/privacy.ts +41 -0
  106. package/lib/process-data/reconcile.ts +110 -0
  107. package/lib/process-data/repair.ts +64 -0
  108. package/lib/process-data/sources.ts +354 -0
  109. package/lib/process-data/store.ts +315 -0
  110. package/lib/process-data/types.ts +135 -0
  111. package/lib/run/index.ts +5 -3
  112. package/lib/run/tui.ts +11 -8
  113. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  114. package/lib/sandbox/capture.ts +9 -2
  115. package/lib/sandbox/commands/create.ts +127 -260
  116. package/lib/sandbox/commands/enter.ts +13 -59
  117. package/lib/sandbox/commands/list-running.ts +23 -6
  118. package/lib/sandbox/commands/ls.ts +22 -6
  119. package/lib/sandbox/commands/rebuild.ts +4 -3
  120. package/lib/sandbox/commands/refresh.ts +4 -126
  121. package/lib/sandbox/commands/rm.ts +24 -3
  122. package/lib/sandbox/commands/show.ts +29 -3
  123. package/lib/sandbox/commands/start.ts +6 -4
  124. package/lib/sandbox/config.ts +4 -0
  125. package/lib/sandbox/constants.ts +8 -0
  126. package/lib/sandbox/control/client.ts +79 -0
  127. package/lib/sandbox/control/protocol.ts +110 -0
  128. package/lib/sandbox/control/server.ts +155 -0
  129. package/lib/sandbox/dockerfile.ts +66 -13
  130. package/lib/sandbox/mounts.ts +24 -4
  131. package/lib/sandbox/recovery.ts +163 -6
  132. package/lib/sandbox/redaction.ts +14 -0
  133. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  134. package/lib/sandbox/tool-types.ts +16 -0
  135. package/lib/sandbox/workspace-identity.ts +123 -0
  136. package/lib/sandbox/workspace-view.ts +150 -0
  137. package/lib/task/activity-intent.ts +350 -61
  138. package/lib/task/activity-log.ts +46 -7
  139. package/lib/task/commit-finalization.ts +376 -0
  140. package/lib/task/commit-intent.ts +230 -0
  141. package/lib/task/create-service.ts +171 -0
  142. package/lib/task/create.ts +372 -0
  143. package/lib/task/decision-intents.ts +32 -12
  144. package/lib/task/delegation-receipts.ts +2 -2
  145. package/lib/task/implementation-inputs.ts +62 -8
  146. package/lib/task/ledger-intents.ts +43 -3
  147. package/lib/task/orchestration.ts +673 -41
  148. package/lib/task/resolve-ref.ts +46 -1
  149. package/lib/task/review-fingerprint.ts +9 -0
  150. package/lib/task/workspace-snapshot.ts +98 -4
  151. package/package.json +7 -7
  152. package/templates/.agents/hooks/lifecycle-delegation.js +47 -3
  153. package/templates/.agents/rules/README.en.md +1 -1
  154. package/templates/.agents/rules/README.zh-CN.md +1 -1
  155. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  157. package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
  158. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  159. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  160. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
  161. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  162. package/templates/.agents/rules/task-management.en.md +3 -0
  163. package/templates/.agents/rules/task-management.zh-CN.md +3 -0
  164. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  165. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  166. package/templates/.agents/skills/commit/SKILL.en.md +21 -17
  167. package/templates/.agents/skills/commit/SKILL.zh-CN.md +28 -16
  168. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +50 -0
  169. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +56 -0
  170. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  171. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  172. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  173. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  174. package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
  175. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
  176. package/templates/.agents/skills/create-task/SKILL.en.md +26 -43
  177. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
  178. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  179. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  180. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  181. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  182. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  183. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  184. package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
  185. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
  186. package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
  187. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
  188. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
  189. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  190. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  191. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
  192. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
  193. package/templates/.agents/templates/task.en.md +1 -1
  194. package/templates/.agents/templates/task.zh-CN.md +1 -1
  195. package/templates/.claude/commands/watch-pr.en.md +1 -1
  196. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  197. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  198. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  199. package/templates/.codex/hooks.json +50 -0
  200. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  201. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  202. package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
  203. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +0 -10
@@ -132,6 +132,43 @@ function enumerateTaskDirs(repoRoot: string): { taskId: string; taskDir: string
132
132
  return out;
133
133
  }
134
134
 
135
+ /**
136
+ * Enumerate every task directory, including the cold YYYY/MM/DD archive.
137
+ * Invalid directory names, symlinks and entries without task.md are ignored;
138
+ * duplicate task ids fail closed because their identity would be ambiguous.
139
+ */
140
+ function enumerateAllTaskDirs(repoRoot: string): { taskId: string; taskDir: string; state: TaskWorkspaceState }[] {
141
+ const out: { taskId: string; taskDir: string; state: TaskWorkspaceState }[] = [];
142
+ const seen = new Set<string>();
143
+ const add = (taskId: string, taskDir: string, state: TaskWorkspaceState) => {
144
+ if (!TASK_ID_RE.test(taskId)) return;
145
+ const stat = fs.lstatSync(taskDir);
146
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !fs.existsSync(path.join(taskDir, 'task.md'))) return;
147
+ if (seen.has(taskId)) throw new Error(`duplicate task identity: ${taskId}`);
148
+ seen.add(taskId);
149
+ out.push({ taskId, taskDir, state });
150
+ };
151
+
152
+ for (const state of FLAT_WORKSPACE_DIRS) {
153
+ const base = path.join(repoRoot, '.agents', 'workspace', state);
154
+ if (!fs.existsSync(base)) continue;
155
+ for (const taskId of fs.readdirSync(base).sort()) add(taskId, path.join(base, taskId), state);
156
+ }
157
+
158
+ const archive = path.join(repoRoot, '.agents', 'workspace', 'archive');
159
+ for (const year of listSortedNumeric(archive, 4).reverse()) {
160
+ const yearDir = path.join(archive, year);
161
+ for (const month of listSortedNumeric(yearDir, 2).reverse()) {
162
+ const monthDir = path.join(yearDir, month);
163
+ for (const day of listSortedNumeric(monthDir, 2).reverse()) {
164
+ const dayDir = path.join(monthDir, day);
165
+ for (const taskId of fs.readdirSync(dayDir).sort()) add(taskId, path.join(dayDir, taskId), 'archive');
166
+ }
167
+ }
168
+ }
169
+ return out;
170
+ }
171
+
135
172
  function locateHotTaskDirs(
136
173
  repoRoot: string,
137
174
  taskId: string
@@ -312,7 +349,15 @@ function resolveTaskContext(
312
349
  return { ok: true, repoRoot, ...match, state: 'active' };
313
350
  }
314
351
 
315
- export { resolveTaskRef, resolveTaskContext, detectRepoRoot, enumerateTaskDirs, locateHotTaskDirs, TASK_ID_RE };
352
+ export {
353
+ resolveTaskRef,
354
+ resolveTaskContext,
355
+ detectRepoRoot,
356
+ enumerateTaskDirs,
357
+ enumerateAllTaskDirs,
358
+ locateHotTaskDirs,
359
+ TASK_ID_RE
360
+ };
316
361
  export type {
317
362
  ResolveRefResult,
318
363
  ResolveTaskRefErrorCode,
@@ -3,6 +3,13 @@ import path from "node:path";
3
3
 
4
4
  import { artifactName, maxRound } from "./review-artifacts.ts";
5
5
 
6
+ type ReviewedGitTree = string & { readonly __reviewedGitTree: unique symbol };
7
+ const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
8
+
9
+ export function parseReviewedGitTree(value: string): ReviewedGitTree | null {
10
+ return REVIEWED_TREE_RE.test(value) ? value as ReviewedGitTree : null;
11
+ }
12
+
6
13
  // Paths excluded from the post-review coverage gate. Fail-closed: the gate
7
14
  // covers ALL tracked changes by default. This default denylist is EMPTY —
8
15
  // projects opt into exclusions explicitly via review.post_review_exclude_globs.
@@ -71,6 +78,8 @@ export function extractReviewedSnapshotTree(content: string): string {
71
78
  return match ? match[1]!.trim().replace(/`/g, "") : "";
72
79
  }
73
80
 
81
+ export type { ReviewedGitTree };
82
+
74
83
  export function parseReviewVerdict(content: string): string {
75
84
  const match = String(content).match(/^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.*?)\s*$/m);
76
85
  return match ? match[1]!.trim() : "";
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import fs from 'node:fs';
2
3
  import os from 'node:os';
3
4
  import path from 'node:path';
@@ -18,6 +19,25 @@ type RepositorySnapshot = Readonly<{
18
19
  worktreeTree: string;
19
20
  }>;
20
21
 
22
+ type WorkspaceSnapshotContext = Readonly<{
23
+ gitRoot: string;
24
+ stateRoot: string;
25
+ taskId: string | null;
26
+ }>;
27
+
28
+ type TaskFileFingerprint = Readonly<{
29
+ path: string;
30
+ mode: number;
31
+ kind: 'file' | 'symlink';
32
+ sha256: string;
33
+ }>;
34
+
35
+ type OrchestrationWorkspaceFingerprint = Readonly<{
36
+ version: 2;
37
+ gitTree: string;
38
+ taskFiles: readonly TaskFileFingerprint[];
39
+ }>;
40
+
21
41
  function captureWorktreeTree(repoRoot: string, forcedPath: string | null): string {
22
42
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-orchestration-index-'));
23
43
  const indexFile = path.join(tempDir, 'index');
@@ -33,13 +53,72 @@ function captureWorktreeTree(repoRoot: string, forcedPath: string | null): strin
33
53
  }
34
54
  }
35
55
 
36
- function captureWorkspaceSnapshot(repoRoot: string, taskId: string | null): string {
56
+ function captureLegacyWorkspaceSnapshot(repoRoot: string, taskId: string | null): string {
37
57
  const workspaceRelative = taskId
38
58
  ? `.agents/workspace/active/${taskId}`
39
59
  : '.agents/workspace/active';
40
60
  return captureWorktreeTree(repoRoot, workspaceRelative);
41
61
  }
42
62
 
63
+ function taskFileFingerprints(stateRoot: string, taskId: string | null): TaskFileFingerprint[] {
64
+ const activeRoot = path.join(stateRoot, '.agents', 'workspace', 'active');
65
+ const roots = taskId ? [path.join(activeRoot, taskId)] : [activeRoot];
66
+ const records: TaskFileFingerprint[] = [];
67
+ const visit = (absolute: string, relative: string): void => {
68
+ const stat = fs.lstatSync(absolute);
69
+ if (stat.isDirectory()) {
70
+ for (const name of fs.readdirSync(absolute).sort()) {
71
+ visit(path.join(absolute, name), path.posix.join(relative, name));
72
+ }
73
+ return;
74
+ }
75
+ if (!stat.isFile() && !stat.isSymbolicLink()) return;
76
+ const content = stat.isSymbolicLink()
77
+ ? Buffer.from(fs.readlinkSync(absolute))
78
+ : fs.readFileSync(absolute);
79
+ records.push({
80
+ path: relative,
81
+ mode: stat.mode & 0o777,
82
+ kind: stat.isSymbolicLink() ? 'symlink' : 'file',
83
+ sha256: createHash('sha256').update(content).digest('hex')
84
+ });
85
+ };
86
+ for (const root of roots) {
87
+ if (!fs.existsSync(root)) continue;
88
+ const relative = path.posix.join('.agents/workspace/active', path.relative(activeRoot, root).split(path.sep).join('/'));
89
+ visit(root, relative.replace(/\/$/, ''));
90
+ }
91
+ return records.sort((left, right) => left.path.localeCompare(right.path));
92
+ }
93
+
94
+ function encodeWorkspaceFingerprint(value: OrchestrationWorkspaceFingerprint): string {
95
+ return `ws2:${Buffer.from(JSON.stringify(value)).toString('base64url')}`;
96
+ }
97
+
98
+ function decodeWorkspaceFingerprint(value: string): OrchestrationWorkspaceFingerprint | null {
99
+ if (!value.startsWith('ws2:')) return null;
100
+ try {
101
+ const parsed = JSON.parse(Buffer.from(value.slice(4), 'base64url').toString('utf8')) as OrchestrationWorkspaceFingerprint;
102
+ if (parsed.version !== 2 || !/^[a-f0-9]{40,64}$/.test(parsed.gitTree) || !Array.isArray(parsed.taskFiles)) {
103
+ throw new Error('invalid workspace fingerprint fields');
104
+ }
105
+ return parsed;
106
+ } catch (error) {
107
+ throw new Error(`ORCHESTRATION_SNAPSHOT_FAILED: ${error instanceof Error ? error.message : String(error)}`);
108
+ }
109
+ }
110
+
111
+ function captureWorkspaceSnapshot(context: WorkspaceSnapshotContext): string;
112
+ function captureWorkspaceSnapshot(repoRoot: string, taskId: string | null): string;
113
+ function captureWorkspaceSnapshot(contextOrRoot: WorkspaceSnapshotContext | string, taskId?: string | null): string {
114
+ if (typeof contextOrRoot === 'string') return captureLegacyWorkspaceSnapshot(contextOrRoot, taskId ?? null);
115
+ return encodeWorkspaceFingerprint({
116
+ version: 2,
117
+ gitTree: captureWorktreeTree(contextOrRoot.gitRoot, null),
118
+ taskFiles: taskFileFingerprints(contextOrRoot.stateRoot, contextOrRoot.taskId)
119
+ });
120
+ }
121
+
43
122
  function captureRepositorySnapshot(repoRoot: string): RepositorySnapshot {
44
123
  return {
45
124
  head: git(repoRoot, null, ['rev-parse', 'HEAD']).trim(),
@@ -49,9 +128,24 @@ function captureRepositorySnapshot(repoRoot: string): RepositorySnapshot {
49
128
  }
50
129
 
51
130
  function diffWorkspaceSnapshots(repoRoot: string, before: string, after: string): string[] {
52
- const output = git(repoRoot, null, ['diff', '--name-only', '-z', before, after]);
53
- return output.split('\0').filter(Boolean);
131
+ const beforeV2 = decodeWorkspaceFingerprint(before);
132
+ const afterV2 = decodeWorkspaceFingerprint(after);
133
+ if ((beforeV2 === null) !== (afterV2 === null)) {
134
+ throw new Error('ORCHESTRATION_SNAPSHOT_FAILED: workspace fingerprint versions cannot be mixed');
135
+ }
136
+ if (!beforeV2 || !afterV2) {
137
+ const output = git(repoRoot, null, ['diff', '--name-only', '-z', before, after]);
138
+ return output.split('\0').filter(Boolean);
139
+ }
140
+ const output = git(repoRoot, null, ['diff', '--name-only', '-z', beforeV2.gitTree, afterV2.gitTree]);
141
+ const changed = new Set(output.split('\0').filter(Boolean));
142
+ const beforeFiles = new Map(beforeV2.taskFiles.map((record) => [record.path, JSON.stringify(record)]));
143
+ const afterFiles = new Map(afterV2.taskFiles.map((record) => [record.path, JSON.stringify(record)]));
144
+ for (const filePath of new Set([...beforeFiles.keys(), ...afterFiles.keys()])) {
145
+ if (beforeFiles.get(filePath) !== afterFiles.get(filePath)) changed.add(filePath);
146
+ }
147
+ return [...changed].sort();
54
148
  }
55
149
 
56
150
  export { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots };
57
- export type { RepositorySnapshot };
151
+ 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.9.3",
3
+ "version": "0.9.5",
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",
@@ -58,13 +58,13 @@
58
58
  "demo:regen": "sh scripts/demo-regen.sh",
59
59
  "prepare": "git config core.hooksPath .git-hooks || true",
60
60
  "typecheck": "tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.jschecks.json --noEmit",
61
- "test:smoke": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/unit/**/*.test.ts\"",
62
- "test:core": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/unit/**/*.test.ts\" \"tests/integration/**/*.test.ts\"",
63
- "test:integration": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/integration/**/*.test.ts\" \"tests/e2e/**/*.test.ts\"",
61
+ "test:smoke": "node scripts/run-tests.js \"tests/unit/**/*.test.ts\"",
62
+ "test:core": "node scripts/run-tests.js \"tests/unit/**/*.test.ts\" \"tests/integration/**/*.test.ts\"",
63
+ "test:integration": "node scripts/run-tests.js \"tests/integration/**/*.test.ts\" \"tests/e2e/**/*.test.ts\"",
64
64
  "check": "npm run typecheck && npm test",
65
- "test": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/**/*.test.ts\"",
66
- "test:coverage": "npm run build && node --experimental-strip-types --no-warnings --test --experimental-test-coverage \"--test-coverage-exclude=tests/**\" --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage.lcov \"tests/**/*.test.ts\"",
67
- "prepublishOnly": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/**/*.test.ts\""
65
+ "test": "node scripts/run-tests.js \"tests/**/*.test.ts\"",
66
+ "test:coverage": "node scripts/run-tests.js --experimental-test-coverage \"--test-coverage-exclude=tests/**\" --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage.lcov \"tests/**/*.test.ts\"",
67
+ "prepublishOnly": "node scripts/run-tests.js \"tests/**/*.test.ts\""
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/cross-spawn": "^6.0.6",
@@ -1,12 +1,15 @@
1
1
  import { execFileSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
2
3
  import { existsSync } from 'node:fs';
4
+ import { readFileSync } from 'node:fs';
3
5
  import { fileURLToPath } from 'node:url';
4
6
 
5
7
  const MAX_INPUT_BYTES = 64 * 1024;
6
8
  const localInternalCli = fileURLToPath(new URL('../../dist/bin/internal-cli.js', import.meta.url));
9
+ const codexHooks = fileURLToPath(new URL('../../.codex/hooks.json', import.meta.url));
7
10
  const clientIndex = process.argv.indexOf('--client');
8
11
  const client = clientIndex >= 0 ? process.argv[clientIndex + 1] : '';
9
- if (client !== 'claude-code') {
12
+ if (client !== 'claude-code' && client !== 'codex') {
10
13
  process.stderr.write('Lifecycle delegation hook requires a supported --client value\n');
11
14
  process.exit(1);
12
15
  }
@@ -27,9 +30,50 @@ process.stdin.on('end', () => {
27
30
  process.stderr.write('Lifecycle delegation hook received invalid JSON\n');
28
31
  process.exit(1);
29
32
  }
30
- const hook = String(event.hook_event_name || event.event || '').toLowerCase();
31
- const nativeAgent = event.agent_name || event.agent_type || event.agent?.name;
33
+ const phaseIndex = process.argv.indexOf('--event');
34
+ const explicitPhase = phaseIndex >= 0 ? process.argv[phaseIndex + 1] : '';
35
+ const hook = String(explicitPhase || event.hook_event_name || event.event || '').toLowerCase();
36
+ const toolInput = event.tool_input && typeof event.tool_input === 'object' ? event.tool_input : {};
37
+ const nativeAgent = event.agent_name || event.agent_type || event.agent?.name
38
+ || toolInput.agent_type || toolInput.agent || toolInput.name;
32
39
  if (!String(nativeAgent || '').startsWith('agent-infra-lifecycle-')) process.exit(0);
40
+ if (client === 'codex') {
41
+ const phases = new Set(['pre-tool', 'subagent-start', 'post-tool', 'subagent-stop']);
42
+ if (!phases.has(hook)) {
43
+ process.stderr.write('Managed Codex lifecycle hook event has an unknown type\n');
44
+ process.exit(1);
45
+ }
46
+ const hookDefinitionHash = createHash('sha256').update(readFileSync(codexHooks)).digest('hex');
47
+ const normalized = {
48
+ sessionId: String(event.session_id || ''),
49
+ turnId: String(event.turn_id || ''),
50
+ toolUseId: String(event.tool_use_id || ''),
51
+ childThreadId: String(event.agent_id || event.child_id || event.thread_id || event.tool_response?.agent_id || ''),
52
+ nativeAgent: String(nativeAgent),
53
+ requestedModel: String(toolInput.model || ''),
54
+ requestedReasoningEffort: String(toolInput.reasoning_effort || toolInput.reasoningEffort || ''),
55
+ hookDefinitionHash
56
+ };
57
+ const args = ['codex-lifecycle', 'hook-event', '--event', hook];
58
+ try {
59
+ const useLocal = existsSync(localInternalCli);
60
+ const command = useLocal ? process.execPath : 'agent-infra-internal';
61
+ const commandArgs = useLocal ? [localInternalCli, ...args] : args;
62
+ const output = execFileSync(command, commandArgs, {
63
+ encoding: 'utf8',
64
+ input: JSON.stringify(normalized),
65
+ shell: !useLocal && process.platform === 'win32',
66
+ stdio: ['pipe', 'pipe', 'pipe'],
67
+ timeout: 5_000
68
+ });
69
+ process.stdout.write(output);
70
+ } catch (error) {
71
+ if (error.stdout) process.stdout.write(String(error.stdout));
72
+ if (error.stderr) process.stderr.write(String(error.stderr));
73
+ process.exit(typeof error.status === 'number' ? error.status : 1);
74
+ }
75
+ return;
76
+ }
33
77
  const start = hook.includes('start');
34
78
  const stop = hook.includes('stop');
35
79
  if (!start && !stop) {
@@ -16,7 +16,7 @@ so you can quickly find "which ones to read" without opening each file.
16
16
  ## Issue / PR
17
17
 
18
18
  - [`issue-pr-commands.md`](issue-pr-commands.md) — PR commands and Issue intent entry points.
19
- - [`pr-checks-commands.md`](pr-checks-commands.md) — Commands to watch PR required checks and pull failure logs (`watch-pr`).
19
+ - [`pr-checks-commands.md`](pr-checks-commands.md) — Commands to watch all PR checks and pull failure logs (`watch-pr`).
20
20
  - [`create-issue.md`](create-issue.md) — Declarative Issue creation after `create-task` writes `task.md`.
21
21
  - [`issue-sync.md`](issue-sync.md) — Issue comment markers and declarative metadata intent contract.
22
22
  - [`issue-fields.md`](issue-fields.md) — Dynamic Issue Type pinned-field mapping boundary.
@@ -15,7 +15,7 @@
15
15
  ## Issue / PR
16
16
 
17
17
  - [`issue-pr-commands.md`](issue-pr-commands.md) — PR 命令集与 Issue intent 入口说明。
18
- - [`pr-checks-commands.md`](pr-checks-commands.md) — 监控 PR required checks、拉取失败日志的命令集(`watch-pr`)。
18
+ - [`pr-checks-commands.md`](pr-checks-commands.md) — 监控 PR 全部 checks、拉取失败日志的命令集(`watch-pr`)。
19
19
  - [`create-issue.md`](create-issue.md) — `create-task` 落盘后的声明式 Issue 创建 intent。
20
20
  - [`issue-sync.md`](issue-sync.md) — Issue 评论 marker 与声明式元数据 intent 契约。
21
21
  - [`issue-fields.md`](issue-fields.md) — 动态 Issue Type pinned 字段映射边界。
@@ -6,7 +6,7 @@ This rule governs every new `[needs-human-decision]` detail block produced by an
6
6
 
7
7
  A maintainer must be able to understand the issue and choose without knowing the internal workflow or asking a model to reconstruct context. Start in plain language: explain why a decision is needed now, what must be chosen, which choice is recommended, and why. Then explain what the choice affects and what could go wrong if the wrong choice is made or no action is taken.
8
8
 
9
- Provide at least two options with short names, and state the benefits and trade-offs of each one. Explain internal terms such as stage, severity, and evidence the first time they appear. IDs and evidence are for lookup and troubleshooting; they do not replace the plain-language explanation.
9
+ Provide at least two options with short names. For each option, explain what would actually be done, what would happen after choosing it, its benefits, and its trade-offs. Each decision must also include at least one everyday example or analogy that stays true to the real effects and helps the maintainer compare the options. Explain internal terms such as stage, severity, and evidence the first time they appear. IDs and evidence are for lookup and troubleshooting; they do not replace the plain-language explanation.
10
10
 
11
11
  ## Canonical Structure
12
12
 
@@ -19,19 +19,28 @@ Provide at least two options with short names, and state the benefits and trade-
19
19
  - **Why this choice**: {reason based on benefits, trade-offs, and current constraints}
20
20
  - **What this affects**: {affected behavior, files, users, or future practice}
21
21
  - **What could go wrong**: {problems caused by the wrong choice or no action}
22
+ - **Everyday example or analogy**: {a familiar comparison that accurately reflects the real effects and helps compare the options}
22
23
 
23
24
  #### Option A: {Name}
24
25
 
26
+ - **What would actually be done**: {how the system or follow-up work would handle this option}
27
+ - **What happens after choosing it**: {specific changes for users, behavior, or follow-up work}
25
28
  - **Benefits**: {what this option provides}
26
29
  - **Trade-offs**: {what this option costs and where it falls short}
27
30
 
28
31
  #### Option B: {Name}
29
32
 
33
+ - **What would actually be done**: {how the system or follow-up work would handle this option}
34
+ - **What happens after choosing it**: {specific changes for users, behavior, or follow-up work}
30
35
  - **Benefits**: {what this option provides}
31
36
  - **Trade-offs**: {what this option costs and where it falls short}
32
37
  ```
33
38
 
34
- Option C and supporting evidence may be added, but none of the information above may be omitted or collapsed into an incomparable one-line option list. The heading ID must match the task.md Review Disagreement Ledger row. Its evidence (the reference used to find the original explanation) must point to this stable heading anchor.
39
+ Option C and supporting evidence may be added, but none of the information above may be omitted or collapsed into an incomparable one-line option list. An example or analogy helps understanding; it does not replace facts, concrete effects, or risks. One example that clearly compares the overall choice is enough, so do not mechanically repeat the same analogy under every option. The heading ID must match the task.md Review Disagreement Ledger row. Its evidence (the reference used to find the original explanation) must point to this stable heading anchor.
40
+
41
+ ## Implementation Intent Declaration
42
+
43
+ For the code stage, the author must add `**Implementation required**: yes/no` after the options, then pass `--needs-implementation true|false` when `finding-review` escalates to `needs-human-decision` or when calling `decision-upsert`. The value is stored as a predeclared implementation input, so maintainers normally omit this option from `ai decide`. Historical tasks without a declaration may still pass it explicitly to `ai decide`; a value that conflicts with an existing declaration is rejected.
35
44
 
36
45
  ## Decision Closure
37
46
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  详情必须让维护者不需要了解内部流程,也不需要再问模型,就能看懂并做选择。先用大白话说明为什么现在需要决定、具体要选什么、建议选哪个以及原因;再说明会影响什么、选错或暂不处理会怎样。
8
8
 
9
- 每项至少提供两个有简短名称的方案,并分别写清好处和代价。第一次出现 stage、severity、evidence 等内部术语时,必须顺手解释它们的意思;ID 和 evidence 只用于后续查找和排查,不能代替问题说明。
9
+ 每项至少提供两个有简短名称的方案,并分别写清实际会怎么做、选了以后会怎样、好处和代价。每项还必须提供至少一个贴近日常、且不歪曲事实的例子或类比,帮助维护者比较方案。第一次出现 stage、severity、evidence 等内部术语时,必须顺手解释它们的意思;ID 和 evidence 只用于后续查找和排查,不能代替问题说明。
10
10
 
11
11
  ## 规范结构
12
12
 
@@ -19,19 +19,28 @@
19
19
  - **为什么这样建议**:{结合好处、代价和现有限制说明原因}
20
20
  - **会影响什么**:{会受影响的功能、文件、用户或后续做法}
21
21
  - **如果选错或暂不处理**:{可能出现的问题}
22
+ - **打个比方**:{用一个贴近日常且符合真实影响的例子或类比,帮助比较方案}
22
23
 
23
24
  #### 方案 A:{名称}
24
25
 
26
+ - **实际会怎么做**:{选择该方案后,系统或后续工作具体怎么处理}
27
+ - **选了以后会怎样**:{用户、功能或后续工作会发生什么具体变化}
25
28
  - **好处**:{选择该方案能得到什么}
26
29
  - **代价**:{选择该方案要付出什么、有哪些局限}
27
30
 
28
31
  #### 方案 B:{名称}
29
32
 
33
+ - **实际会怎么做**:{选择该方案后,系统或后续工作具体怎么处理}
34
+ - **选了以后会怎样**:{用户、功能或后续工作会发生什么具体变化}
30
35
  - **好处**:{选择该方案能得到什么}
31
36
  - **代价**:{选择该方案要付出什么、有哪些局限}
32
37
  ```
33
38
 
34
- 可增加方案 C 或补充证据,但不得省略上述信息或把多个方案压缩为无法分别比较的一行。标题 ID 必须与 task.md `## 审查分歧账本` 行一致;evidence(用于定位原始说明的引用)必须指向该标题的稳定锚点。
39
+ 可增加方案 C 或补充证据,但不得省略上述信息或把多个方案压缩为无法分别比较的一行。例子或类比用于帮助理解,不能替代事实、实际影响和风险说明;一个能清楚比较整体选择的例子即可,不必为每个方案机械重复同一类比。标题 ID 必须与 task.md `## 审查分歧账本` 行一致;evidence(用于定位原始说明的引用)必须指向该标题的稳定锚点。
40
+
41
+ ## 实现意图声明
42
+
43
+ code 阶段的提出者必须在详情块的方案说明后另写 `**是否需要实现**:是/否`,并在 `finding-review` 升级为 `needs-human-decision` 或 `decision-upsert` 时传入 `--needs-implementation true|false`。该值会作为待裁决实现输入保存;维护者执行 `ai decide` 时通常无需再传此参数。历史任务没有预先声明时,仍可在 `ai decide` 中显式传参;显式值与已有声明冲突时命令失败。
35
44
 
36
45
  ## 裁决闭环
37
46
 
@@ -1,6 +1,6 @@
1
1
  # PR Readiness Platform Intents
2
2
 
3
- Typed intents own required-check selection, PR mergeability, polling deadlines, run/job resolution, and logs. Every inspect/poll aggregates facts from one PR head snapshot; facts are never mixed across heads.
3
+ Typed intents own full check-set retrieval, PR mergeability, polling deadlines, run/job resolution, and logs. Every inspect/poll aggregates facts from one PR head snapshot; facts are never mixed across heads.
4
4
 
5
5
  ```bash
6
6
  agent-infra-internal platform-checks inspect {task-id}
@@ -15,4 +15,4 @@ agent-infra-internal platform-checks logs {task-id} \
15
15
  --run {run-id} [--job {job-id}]
16
16
  ```
17
17
 
18
- The adapter normalizes REST `mergeable=false` to `conflicting`, null/missing to `unknown`, and true to `mergeable`; `mergeable=true` plus diagnostic `dirty` is contradictory and becomes `unknown`. Other diagnostics such as `blocked` do not override REST true. Core combines that fact with required checks for one `headSha`: only `ready` exits 0, `conflicting|checks-failed` exit 1, and pending/timeout/cancel/network blocking exit 2. Dependency and deterministic platform errors are preserved without degradation.
18
+ The adapter normalizes REST `mergeable=false` to `conflicting`, null/missing to `unknown`, and true to `mergeable`; `mergeable=true` plus diagnostic `dirty` is contradictory and becomes `unknown`. Other diagnostics such as `blocked` do not override REST true. Core combines that fact with all checks for one `headSha`: only `ready` exits 0, `conflicting|checks-failed` exit 1, and pending/timeout/cancel/network blocking exit 2. Dependency and deterministic platform errors are preserved without degradation.
@@ -1,6 +1,6 @@
1
1
  # GitHub PR readiness 平台意图
2
2
 
3
- required checks 的筛选、轮询 deadline、失败 run/job 定位和日志获取统一由 typed internal intent 执行。SKILL/模型只负责失败分类、根因分析、最小修复和授权后的提交推送。
3
+ PR 全部 checks 的获取、轮询 deadline、失败 run/job 定位和日志获取统一由 typed internal intent 执行。SKILL/模型只负责失败分类、根因分析、最小修复和授权后的提交推送。
4
4
 
5
5
  GitHub adapter 要求 `gh >= 2.16.0`。平台上下文会在任何 GitHub API、PR 或 checks 操作前检查该依赖;未选择 GitHub 平台时不探测也不依赖 `gh`。
6
6
 
@@ -13,7 +13,7 @@ agent-infra-internal platform-checks watch {task-id} \
13
13
  --interval-seconds 30 --deadline-seconds 1800
14
14
  ```
15
15
 
16
- adapter 把 REST `mergeable=false` 规范化为 `conflicting`,null/缺失为 `unknown`,true 为 `mergeable`;true 与诊断 `dirty` 矛盾时降为 `unknown`。`blocked` 等其他诊断不覆盖 REST true。core 在同一 `headSha` 上与 required checks 聚合:仅 `ready` 退出 0,`conflicting|checks-failed` 退出 1,pending/timeout/cancel/网络阻塞退出 2。依赖与确定性平台错误不降级。
16
+ adapter 把 REST `mergeable=false` 规范化为 `conflicting`,null/缺失为 `unknown`,true 为 `mergeable`;true 与诊断 `dirty` 矛盾时降为 `unknown`。`blocked` 等其他诊断不覆盖 REST true。core 在同一 `headSha` 上与全部 checks 聚合:仅 `ready` 退出 0,`conflicting|checks-failed` 退出 1,pending/timeout/cancel/网络阻塞退出 2。依赖与确定性平台错误不降级。
17
17
 
18
18
  ## 定位失败 run/job
19
19
 
@@ -1,6 +1,6 @@
1
1
  # PR readiness 平台意图
2
2
 
3
- required checks、PR mergeability、轮询 deadline、失败 run/job 定位和日志获取统一由 typed internal intent 执行。每次 inspect/poll 都在同一 PR head 快照上聚合,不跨 head 拼接事实。
3
+ PR 全部 checks、PR mergeability、轮询 deadline、失败 run/job 定位和日志获取统一由 typed internal intent 执行。每次 inspect/poll 都在同一 PR head 快照上聚合,不跨 head 拼接事实。
4
4
 
5
5
  ## 快照与监控
6
6
 
@@ -93,7 +93,7 @@
93
93
  - 成功提交后令 `B=last_reviewed_commit=<new_head>`;`B` 只表示已经落到 Git commit 的审查锚点。
94
94
  - `complete-task` 的 `post-review-commit` gate 只使用 B;B 缺失、畸形或对象不存在时报告 `reviewed snapshot was not anchored`,不得回退 R。
95
95
  - 若 B 之后代码 / 规则路径出现新提交,gate 会拦截,要求重新 `review-code`。自动例外仅限两类:(1) 绑定的平台变更请求(PR/MR)已合并,且平台适配器提供权威快照与远端 Git 证据引用;gate 在隔离的临时仓库中抓取受审查 head 与目标分支,证明 B 是原变更请求 head、merge commit 位于权威目标分支历史中、受审查变更与单父 squash commit 的规范化补丁完全一致,并且 merge 后受保护路径没有新提交;(2) 无有效变更请求,B 后恰有一个受保护路径提交,该提交是本地 HEAD 历史中的单父重写,且它与 B 的完整 tree 相同或受保护内容相同,重写后也没有受保护路径提交。适配器不支持该能力,缺少所需平台 / Git 证据,或当前 Git 凭据不能读取这些远端 refs 时 fail closed。
96
- - required checks 由合并前的分支保护 / ruleset 与 `review-code` / `watch-pr` 路由负责。post-review-commit 仍独立执行适用的严格 / PR squash / 本地重写等价判定。
96
+ - 全部 PR checks 由合并前的 `review-code` / `watch-pr` 路由负责,其中 required checks 仍受分支保护 / ruleset 强制。post-review-commit 仍独立执行适用的严格 / PR squash / 本地重写等价判定。
97
97
  - **豁免**:在账本追加一行 `| PRC-1 | post-review-commit | - | - | human-decided | <裁定说明> |`,记录人工明确允许该批提交免复审。
98
98
 
99
99
  ## gate 行为速查
@@ -68,6 +68,7 @@ The six internal commands (`task-event` / `task-lifecycle` / `platform-issue` /
68
68
  `- {time} — **{base} [started]** by {agent} — started`
69
69
  - **done line** (written when the step completes, unchanged from today): the action is the base itself:
70
70
  `- {time} — **{base}** by {agent} — {completion summary}`
71
+ - **Commit-specific attempt**: the commit skill must not hand-write started. `commit-start` writes a structured `Commit [started]` with `attempt`, `baseline`, and `agent` under the task lock; `commit-terminate` writes the matching `Commit [aborted]` when an attempt is abandoned before side effects. Aborted closes activity only and is never successful commit evidence.
71
72
  - `{base}` is that skill's existing done action text, including `(Round {N})` (e.g. `Plan Task (Round 1)`). started and done must share the same `{base}` to pair.
72
73
 
73
74
  **Pairing and rendering** (`ai task log`): a started entry pairs with the next same-`{base}` done entry onto one row (repeated executions of the same base pair FIFO by ascending time). The STARTED column shows the start time, DONE the completion time; started with no done = in progress (DONE shows `(in progress)`); done with no started (legacy logs) = a standalone completed row. All three shapes are valid and never error.
@@ -83,4 +84,6 @@ The six internal commands (`task-event` / `task-lifecycle` / `platform-issue` /
83
84
  - **Deferred form (the skill creates task.md, so there is no file to write to at the start)** — capture `started_at` in memory before running, then when writing the Activity Log at the end, **append both lines at once** (started line uses `started_at`, done line uses the completion time):
84
85
  `create-task`, `import-issue`, `import-codescan`, `import-dependabot`.
85
86
 
87
+ - **After host resolution** — `review-pr` writes started through `task-activity pr-review-start` only after resolving the unique task host, canonical round, and reviewed head. Success closes through `pr-review-complete`; a controlled pre-publication failure or head drift closes through `pr-review-terminate` with an `aborted` / `superseded` terminal. Replaying the same task/round/head/terminal payload is a no-op; one-shot review never writes a task Activity Log.
88
+
86
89
  **Exceptions**: read-only inspection skills that do not represent real progress (e.g. `check-task`) do not write started. A bare operation with no task.md context (e.g. a `commit` not tied to a task) likewise skips it.
@@ -70,6 +70,7 @@
70
70
  `- {time} — **{基名} [started]** by {agent} — started`
71
71
  - **done 行**(步骤完成时写,与现状一致):action 即基名本身:
72
72
  `- {time} — **{基名}** by {agent} — {完成说明}`
73
+ - **Commit 专用 attempt**:commit 技能不得手写 started。`commit-start` 在任务锁内写入带 `attempt`、`baseline`、`agent` 的结构化 `Commit [started]`;无副作用放弃时由 `commit-terminate` 写匹配的 `Commit [aborted]`。aborted 只闭合活动日志,不构成提交成功证据。
73
74
  - `{基名}` 指该 SKILL 既有 done 条目的 action 文本,含 `(Round {N})`(如 `Plan Task (Round 1)`)。
74
75
  started 与 done 共用同一 `{基名}` 才能配对。
75
76
 
@@ -86,4 +87,6 @@
86
87
  - **延迟补写(本技能创建 task.md,开始时无文件可写)**——开始执行前先在内存记录 `started_at`,最后写活动日志时**一次性补两条**(started 行用 `started_at`、done 行用完成时间):
87
88
  `create-task`、`import-issue`、`import-codescan`、`import-dependabot`。
88
89
 
90
+ - **宿主解析后写入**——`review-pr` 只有在唯一任务宿主、canonical round 与被审 head 确定后才通过 `task-activity pr-review-start` 写 started;成功以 `pr-review-complete` 闭合,已知未发布的受控失败或 head 漂移以 `pr-review-terminate` 写 `aborted` / `superseded` 终态。同一 task/round/head/terminal payload 重放为 no-op;一次性检视不写任务日志。
91
+
89
92
  **例外**:`check-task` 等只读巡检类、不代表实质工作推进的技能不写 started。无 task.md 上下文的纯操作(如无关联任务的 `commit`)同样跳过。
@@ -17,7 +17,7 @@ Implement the approved plan and produce `code.md` or `code-r{N}.md`. This skill
17
17
 
18
18
  - Follow the latest plan artifact: `plan.md` or `plan-r{N}.md`
19
19
  - 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
20
- - If implementation encounters a key design decision not covered by the plan, run `agent-infra-internal task-ledger {task-id} decision-next-id`, write the returned `HD-N` detail block per `.agents/rules/human-decision-context.md`, then run `decision-upsert --id {HD-N} --stage code --artifact {code-artifact}`. Do not scan ids, assemble ledger rows, ask mid-flow, or silently expand scope
20
+ - If implementation encounters a key design decision not covered by the plan, run `agent-infra-internal task-ledger {task-id} decision-next-id`, write the returned `HD-N` detail block per `.agents/rules/human-decision-context.md` and determine whether implementation is required, then run `decision-upsert --id {HD-N} --stage code --artifact {code-artifact} --needs-implementation {true|false}`. Do not scan ids, assemble ledger rows, ask mid-flow, or silently expand scope
21
21
  - Never auto-run `git add` or `git commit`
22
22
  - Create a new code artifact for each round and never overwrite an older one
23
23
  - After executing this skill, you **must** immediately update task.md
@@ -17,7 +17,7 @@ description: >
17
17
 
18
18
  - 严格遵循最新方案产物:`plan.md` 或 `plan-r{N}.md`
19
19
  - 修复模式逐条核实最新 `review-code` 的发现:成立则修复,判定为不成立/幻觉则在报告中反驳并记入 unresolved;不擅自扩大到审查未列出的问题;manual-validation 项不在修复范围
20
- - 实现中遇到方案未覆盖的关键设计决策时,先调用 `agent-infra-internal task-ledger {task-id} decision-next-id` 取得 `HD-N`,按 `.agents/rules/human-decision-context.md` 写入实现报告的 `## 人工裁决待办` 详情块,再调用 `decision-upsert --id {HD-N} --stage code --artifact {code-artifact}`;不得扫描编号、手写账本行、中途提问或擅自扩范围
20
+ - 实现中遇到方案未覆盖的关键设计决策时,先调用 `agent-infra-internal task-ledger {task-id} decision-next-id` 取得 `HD-N`,按 `.agents/rules/human-decision-context.md` 写入实现报告的 `## 人工裁决待办` 详情块并判断是否需要实现,再调用 `decision-upsert --id {HD-N} --stage code --artifact {code-artifact} --needs-implementation {true|false}`;不得扫描编号、手写账本行、中途提问或擅自扩范围
21
21
  - 绝不自动执行 `git add` 或 `git commit`
22
22
  - 每轮实现都创建新的实现产物,不覆盖旧文件
23
23
  - 执行本技能后,你**必须**立即更新 task.md