@fitlab-ai/agent-infra 0.9.12 → 0.9.13

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 (239) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/dist/bin/internal-cli.js +24 -19
  4. package/dist/lib/agent-clients/next-steps.js +6 -1
  5. package/dist/lib/decide.js +3 -3
  6. package/dist/lib/internal/platform-checks.js +3 -3
  7. package/dist/lib/internal/platform-comment.js +9 -9
  8. package/dist/lib/internal/platform-context.js +2 -2
  9. package/dist/lib/internal/platform-issue.js +8 -9
  10. package/dist/lib/internal/platform-pr-review.js +10 -10
  11. package/dist/lib/internal/platform-pr.js +63 -24
  12. package/dist/lib/internal/platform-release-notes.js +3 -3
  13. package/dist/lib/internal/pr-review-grade.js +11 -9
  14. package/dist/lib/internal/release-workflow.js +4 -4
  15. package/dist/lib/internal/sandbox-control.js +30 -4
  16. package/dist/lib/internal/task-activity.js +3 -3
  17. package/dist/lib/internal/task-artifact.js +76 -24
  18. package/dist/lib/internal/task-create.js +2 -2
  19. package/dist/lib/internal/task-event.js +6 -3
  20. package/dist/lib/internal/task-finalization.js +2 -2
  21. package/dist/lib/internal/task-invalidation.js +60 -0
  22. package/dist/lib/internal/task-ledger.js +9 -6
  23. package/dist/lib/internal/task-lifecycle.js +2 -2
  24. package/dist/lib/internal/task-override.js +4 -4
  25. package/dist/lib/internal/task-review.js +3 -3
  26. package/dist/lib/internal/task-verify.js +6 -6
  27. package/dist/lib/internal/task-warning.js +3 -3
  28. package/dist/lib/platform/adapters.js +1 -3
  29. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  30. package/dist/lib/platform/completion-backfill.js +6 -5
  31. package/dist/lib/platform/context.js +70 -130
  32. package/dist/lib/platform/github-client.js +1 -1
  33. package/dist/lib/platform/github-provider.js +903 -0
  34. package/dist/lib/platform/in-label-sync.js +143 -0
  35. package/dist/lib/platform/issue-comments.js +94 -41
  36. package/dist/lib/platform/issues.js +335 -189
  37. package/dist/lib/platform/metadata-labels.js +6 -2
  38. package/dist/lib/platform/none-provider.js +28 -0
  39. package/dist/lib/platform/pr-change-report.js +551 -0
  40. package/dist/lib/platform/pr-checks.js +119 -104
  41. package/dist/lib/platform/pr-review.js +88 -72
  42. package/dist/lib/platform/pr-summary.js +520 -50
  43. package/dist/lib/platform/provider-bridge.js +45 -0
  44. package/dist/lib/platform/provider-contract.d.ts +487 -0
  45. package/dist/lib/platform/provider-contract.js +95 -0
  46. package/dist/lib/platform/provider-loader.js +251 -0
  47. package/dist/lib/platform/provider-validation.js +504 -0
  48. package/dist/lib/platform/pull-request-metadata.js +8 -6
  49. package/dist/lib/platform/pull-requests.js +783 -537
  50. package/dist/lib/platform/release-notes.js +78 -55
  51. package/dist/lib/platform/releases.js +79 -50
  52. package/dist/lib/platform/resource-identity.d.ts +26 -0
  53. package/dist/lib/platform/resource-identity.js +92 -0
  54. package/dist/lib/platform/task-identities.js +31 -0
  55. package/dist/lib/platform/verification-required.js +11 -12
  56. package/dist/lib/platform/verification-sync.js +112 -238
  57. package/dist/lib/pr-review/evidence-grading.js +19 -6
  58. package/dist/lib/run/index.js +63 -23
  59. package/dist/lib/sandbox/control/client.js +96 -15
  60. package/dist/lib/sandbox/control/executor.js +3 -3
  61. package/dist/lib/sandbox/control/protocol.js +77 -0
  62. package/dist/lib/sandbox/control/server.js +398 -25
  63. package/dist/lib/sandbox/control/state.js +204 -1
  64. package/dist/lib/server/protocol.js +38 -1
  65. package/dist/lib/task/activity-log.js +8 -1
  66. package/dist/lib/task/artifact-lifecycle.js +33 -5
  67. package/dist/lib/task/capabilities.js +239 -0
  68. package/dist/lib/task/commands/status.js +32 -0
  69. package/dist/lib/task/commit-operation.js +12 -1
  70. package/dist/lib/task/control-authority.js +8 -5
  71. package/dist/lib/task/create-service.js +8 -8
  72. package/dist/lib/task/current-contract.js +5 -1
  73. package/dist/lib/task/decision-details.js +1 -1
  74. package/dist/lib/task/events.js +297 -17
  75. package/dist/lib/task/finalization.js +37 -13
  76. package/dist/lib/task/human-override.js +58 -36
  77. package/dist/lib/task/invalidation-command.js +86 -0
  78. package/dist/lib/task/invalidation.js +260 -0
  79. package/dist/lib/task/ledger-intents.js +37 -0
  80. package/dist/lib/task/local-artifact-finalization.js +374 -0
  81. package/dist/lib/task/orchestration.js +49 -93
  82. package/dist/lib/task/pr-delivery-fact.js +102 -101
  83. package/dist/lib/task/review-fingerprint.js +24 -2
  84. package/dist/lib/task/rework-intent.js +138 -0
  85. package/dist/lib/task/skill-scope.js +23 -0
  86. package/dist/lib/task/task-execution-lock.js +27 -17
  87. package/dist/lib/task/verification-engine.js +43 -16
  88. package/dist/lib/task/verification.js +3 -3
  89. package/dist/lib/task/workflow-warning-intents.js +1 -1
  90. package/dist/lib/task/write.js +11 -0
  91. package/dist/lib/version.js +11 -1
  92. package/dist/lifecycle-build-manifest.json +3 -3
  93. package/lib/agent-clients/next-steps.ts +6 -1
  94. package/lib/decide.ts +3 -3
  95. package/lib/init.ts +1 -1
  96. package/lib/internal/platform-checks.ts +3 -3
  97. package/lib/internal/platform-comment.ts +8 -8
  98. package/lib/internal/platform-context.ts +2 -2
  99. package/lib/internal/platform-issue.ts +7 -8
  100. package/lib/internal/platform-pr-review.ts +9 -9
  101. package/lib/internal/platform-pr.ts +47 -22
  102. package/lib/internal/platform-release-notes.ts +3 -3
  103. package/lib/internal/pr-review-grade.ts +10 -8
  104. package/lib/internal/release-workflow.ts +4 -4
  105. package/lib/internal/sandbox-control.ts +27 -3
  106. package/lib/internal/task-activity.ts +3 -3
  107. package/lib/internal/task-artifact.ts +65 -22
  108. package/lib/internal/task-create.ts +2 -2
  109. package/lib/internal/task-event.ts +6 -3
  110. package/lib/internal/task-finalization.ts +3 -3
  111. package/lib/internal/task-invalidation.ts +39 -0
  112. package/lib/internal/task-ledger.ts +11 -8
  113. package/lib/internal/task-lifecycle.ts +2 -2
  114. package/lib/internal/task-override.ts +6 -6
  115. package/lib/internal/task-review.ts +3 -3
  116. package/lib/internal/task-verify.ts +6 -6
  117. package/lib/internal/task-warning.ts +3 -3
  118. package/lib/platform/adapters.ts +3 -2
  119. package/lib/platform/change-request-git-evidence.ts +36 -9
  120. package/lib/platform/completion-backfill.ts +9 -8
  121. package/lib/platform/context.ts +97 -134
  122. package/lib/platform/github-client.ts +2 -2
  123. package/lib/platform/github-provider.ts +910 -0
  124. package/lib/platform/in-label-sync.ts +208 -0
  125. package/lib/platform/issue-comments.ts +97 -47
  126. package/lib/platform/issues.ts +344 -174
  127. package/lib/platform/metadata-labels.ts +5 -2
  128. package/lib/platform/none-provider.ts +35 -0
  129. package/lib/platform/pr-change-report.ts +645 -0
  130. package/lib/platform/pr-checks.ts +126 -106
  131. package/lib/platform/pr-review.ts +88 -85
  132. package/lib/platform/pr-summary.ts +560 -53
  133. package/lib/platform/provider-bridge.ts +82 -0
  134. package/lib/platform/provider-contract.ts +536 -0
  135. package/lib/platform/provider-loader.ts +307 -0
  136. package/lib/platform/provider-validation.ts +529 -0
  137. package/lib/platform/pull-request-metadata.ts +10 -7
  138. package/lib/platform/pull-requests.ts +826 -543
  139. package/lib/platform/release-notes.ts +76 -59
  140. package/lib/platform/releases.ts +77 -43
  141. package/lib/platform/resource-identity.ts +121 -0
  142. package/lib/platform/task-identities.ts +40 -0
  143. package/lib/platform/types.ts +4 -2
  144. package/lib/platform/verification-required.ts +11 -13
  145. package/lib/platform/verification-sync.ts +112 -292
  146. package/lib/pr-review/evidence-grading.ts +25 -8
  147. package/lib/run/index.ts +57 -22
  148. package/lib/sandbox/control/client.ts +105 -15
  149. package/lib/sandbox/control/executor.ts +4 -4
  150. package/lib/sandbox/control/protocol.ts +113 -0
  151. package/lib/sandbox/control/server.ts +431 -23
  152. package/lib/sandbox/control/state.ts +251 -1
  153. package/lib/server/protocol.ts +36 -1
  154. package/lib/task/activity-log.ts +8 -0
  155. package/lib/task/artifact-lifecycle.ts +32 -6
  156. package/lib/task/capabilities.ts +262 -0
  157. package/lib/task/commands/status.ts +38 -0
  158. package/lib/task/commit-operation.ts +12 -1
  159. package/lib/task/control-authority.ts +11 -8
  160. package/lib/task/create-service.ts +8 -8
  161. package/lib/task/current-contract.ts +6 -1
  162. package/lib/task/decision-details.ts +6 -2
  163. package/lib/task/events.ts +296 -16
  164. package/lib/task/finalization.ts +50 -14
  165. package/lib/task/human-override.ts +70 -46
  166. package/lib/task/invalidation-command.ts +86 -0
  167. package/lib/task/invalidation.ts +332 -0
  168. package/lib/task/ledger-intents.ts +35 -1
  169. package/lib/task/local-artifact-finalization.ts +563 -0
  170. package/lib/task/orchestration.ts +52 -85
  171. package/lib/task/pr-delivery-fact.ts +131 -148
  172. package/lib/task/review-fingerprint.ts +23 -2
  173. package/lib/task/rework-intent.ts +165 -0
  174. package/lib/task/skill-scope.ts +24 -0
  175. package/lib/task/task-execution-lock.ts +23 -14
  176. package/lib/task/verification-engine.ts +52 -17
  177. package/lib/task/verification.ts +4 -4
  178. package/lib/task/workflow-warning-intents.ts +1 -1
  179. package/lib/task/write.ts +19 -0
  180. package/lib/update.ts +1 -1
  181. package/lib/version.ts +15 -1
  182. package/package.json +7 -1
  183. package/templates/.agents/rules/README.en.md +1 -1
  184. package/templates/.agents/rules/README.zh-CN.md +1 -1
  185. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  186. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  187. package/templates/.agents/rules/issue-sync.en.md +2 -0
  188. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  189. package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
  190. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
  191. package/templates/.agents/rules/pr-sync.en.md +10 -4
  192. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  193. package/templates/.agents/rules/task-short-id.en.md +1 -4
  194. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  195. package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
  196. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
  197. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  198. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  199. package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
  200. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
  201. package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
  202. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
  203. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  204. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  205. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  206. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  207. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
  208. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
  209. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  210. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  211. package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
  212. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
  213. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  214. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  215. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  216. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  217. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  218. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  219. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  220. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  221. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  222. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  223. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
  224. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
  225. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  226. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  227. package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
  228. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
  229. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  230. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  231. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
  232. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
  233. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  234. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  235. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  236. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  237. package/templates/.agents/templates/task.en.md +18 -1
  238. package/templates/.agents/templates/task.zh-CN.md +18 -1
  239. package/templates/.github/workflows/pr-label.yml +7 -26
@@ -1,9 +1,10 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import { randomUUID } from 'node:crypto';
4
+ import { createHash, randomUUID } from 'node:crypto';
5
5
  import { buildProcessTreeStopCommand } from '../../server/process-control.js';
6
6
  import { getProcessIdentityState } from '../../server/process-state.js';
7
+ import { parseSandboxControlResultEvidence, parseSandboxControlPayload, parseSandboxControlReservation, SANDBOX_CONTROL_MAX_RESPONSE_BYTES, SANDBOX_CONTROL_MAX_LOGICAL_RECORDS, SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES, SANDBOX_CONTROL_RESERVATION_BYTES } from './protocol.js';
7
8
  export const SANDBOX_CONTROL_AUDIT_MAX_BYTES = 1024 * 1024;
8
9
  export const SANDBOX_CONTROL_EXECUTION_STOP_MS = 2_000;
9
10
  function assertRegularFile(filePath) {
@@ -23,10 +24,212 @@ export function atomicWriteJson(filePath, value, mode = 0o600, createParent = tr
23
24
  fs.rmSync(temporary, { force: true });
24
25
  }
25
26
  }
27
+ export function sandboxControlEncodedJsonBytes(value) {
28
+ return Buffer.byteLength(`${JSON.stringify(value)}\n`, 'utf8');
29
+ }
30
+ export function atomicWriteJsonNoReplace(filePath, value, mode = 0o600, createParent = true) {
31
+ atomicCreateJson(filePath, value, 'SANDBOX_CONTROL_TERMINAL_ALREADY_EXISTS', mode, createParent);
32
+ }
33
+ function atomicCreateJson(filePath, value, duplicateCode, mode = 0o600, createParent = true) {
34
+ if (createParent)
35
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
36
+ const temporary = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
37
+ try {
38
+ fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { mode, flag: 'wx' });
39
+ try {
40
+ fs.linkSync(temporary, filePath);
41
+ }
42
+ catch (error) {
43
+ if (error.code === 'EEXIST') {
44
+ throw new Error(duplicateCode);
45
+ }
46
+ throw error;
47
+ }
48
+ }
49
+ finally {
50
+ fs.rmSync(temporary, { force: true });
51
+ }
52
+ }
26
53
  export function readJsonFile(filePath) {
27
54
  assertRegularFile(filePath);
28
55
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
29
56
  }
57
+ export function resultEvidencePath(manifest, requestId) {
58
+ if (!/^[a-f0-9-]{16,64}$/u.test(requestId))
59
+ throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
60
+ return path.join(manifest.processingDir, requestId, 'result.json');
61
+ }
62
+ export function payloadPath(manifest, requestId) {
63
+ if (!/^[a-f0-9-]{16,64}$/u.test(requestId))
64
+ throw new Error('SANDBOX_CONTROL_PAYLOAD_INVALID');
65
+ return path.join(manifest.channelDir, 'responses', `${requestId}.payload.json`);
66
+ }
67
+ export function reservationPath(manifest, requestId) {
68
+ if (!/^[a-f0-9-]{16,64}$/u.test(requestId))
69
+ throw new Error('SANDBOX_CONTROL_RESERVATION_INVALID');
70
+ return path.join(manifest.processingDir, requestId, 'reservation.json');
71
+ }
72
+ export function readSandboxControlResultEvidence(filePath) {
73
+ assertRegularFile(filePath);
74
+ const raw = fs.readFileSync(filePath, 'utf8');
75
+ if (Buffer.byteLength(raw, 'utf8') > SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
76
+ throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_TOO_LARGE');
77
+ }
78
+ try {
79
+ return parseSandboxControlResultEvidence(JSON.parse(raw));
80
+ }
81
+ catch (error) {
82
+ if (error instanceof Error && error.message === 'SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID')
83
+ throw error;
84
+ throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
85
+ }
86
+ }
87
+ export function sanitizeSandboxControlOutput(manifest, output) {
88
+ return manifest.token.length === 0 ? output : output.split(manifest.token).join('[REDACTED]');
89
+ }
90
+ export function sanitizeSandboxControlResult(manifest, result) {
91
+ return {
92
+ exitCode: result.exitCode,
93
+ stdout: sanitizeSandboxControlOutput(manifest, result.stdout),
94
+ stderr: sanitizeSandboxControlOutput(manifest, result.stderr)
95
+ };
96
+ }
97
+ export function writeSandboxControlResultEvidence(manifest, requestId, result) {
98
+ const normalized = sanitizeSandboxControlResult(manifest, result);
99
+ const stdout = normalized.stdout;
100
+ const stderr = normalized.stderr;
101
+ if (!/^[a-f0-9-]{16,64}$/u.test(requestId)
102
+ || !Number.isSafeInteger(result.exitCode)
103
+ || Buffer.byteLength(stdout, 'utf8') + Buffer.byteLength(stderr, 'utf8') > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
104
+ throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
105
+ }
106
+ const evidence = {
107
+ version: 1,
108
+ id: requestId,
109
+ generation: manifest.generation,
110
+ exitCode: result.exitCode,
111
+ stdoutBytes: Buffer.byteLength(stdout, 'utf8'),
112
+ stderrBytes: Buffer.byteLength(stderr, 'utf8'),
113
+ stdoutSha256: createHash('sha256').update(stdout, 'utf8').digest('hex'),
114
+ stderrSha256: createHash('sha256').update(stderr, 'utf8').digest('hex'),
115
+ captureState: 'metadata-only'
116
+ };
117
+ if (sandboxControlEncodedJsonBytes(evidence) > SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
118
+ throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_TOO_LARGE');
119
+ }
120
+ atomicCreateJson(resultEvidencePath(manifest, requestId), evidence, 'SANDBOX_CONTROL_RESULT_EVIDENCE_ALREADY_EXISTS');
121
+ return readSandboxControlResultEvidence(resultEvidencePath(manifest, requestId));
122
+ }
123
+ export function writeSandboxControlPayload(manifest, requestId, result) {
124
+ const payload = createSandboxControlPayload(manifest, requestId, result);
125
+ atomicCreateJson(payloadPath(manifest, requestId), payload, 'SANDBOX_CONTROL_PAYLOAD_ALREADY_EXISTS');
126
+ return readSandboxControlPayload(payloadPath(manifest, requestId));
127
+ }
128
+ export function createSandboxControlPayload(manifest, requestId, result) {
129
+ const stdout = sanitizeSandboxControlOutput(manifest, result.stdout);
130
+ const stderr = sanitizeSandboxControlOutput(manifest, result.stderr);
131
+ const payload = {
132
+ version: 1,
133
+ id: requestId,
134
+ generation: manifest.generation,
135
+ stdout,
136
+ stderr,
137
+ stdoutBytes: Buffer.byteLength(stdout, 'utf8'),
138
+ stderrBytes: Buffer.byteLength(stderr, 'utf8'),
139
+ stdoutSha256: createHash('sha256').update(stdout, 'utf8').digest('hex'),
140
+ stderrSha256: createHash('sha256').update(stderr, 'utf8').digest('hex')
141
+ };
142
+ if (payload.stdoutBytes + payload.stderrBytes > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
143
+ throw new Error('SANDBOX_CONTROL_PAYLOAD_TOO_LARGE');
144
+ }
145
+ if (sandboxControlEncodedJsonBytes(payload) > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
146
+ throw new Error('SANDBOX_CONTROL_PAYLOAD_TOO_LARGE');
147
+ }
148
+ return payload;
149
+ }
150
+ export function readSandboxControlPayload(filePath) {
151
+ assertRegularFile(filePath);
152
+ const raw = fs.readFileSync(filePath, 'utf8');
153
+ if (Buffer.byteLength(raw, 'utf8') > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
154
+ throw new Error('SANDBOX_CONTROL_PAYLOAD_TOO_LARGE');
155
+ }
156
+ try {
157
+ return parseSandboxControlPayload(JSON.parse(raw));
158
+ }
159
+ catch {
160
+ throw new Error('SANDBOX_CONTROL_PAYLOAD_INVALID');
161
+ }
162
+ }
163
+ export function writeSandboxControlReservation(manifest, requestId, usage, now = Date.now()) {
164
+ if (usage.logicalRecords >= SANDBOX_CONTROL_MAX_LOGICAL_RECORDS
165
+ || usage.bytes + SANDBOX_CONTROL_RESERVATION_BYTES > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
166
+ throw new Error('SANDBOX_CONTROL_CAPACITY_EXHAUSTED');
167
+ }
168
+ const reservation = {
169
+ version: 1,
170
+ id: requestId,
171
+ generation: manifest.generation,
172
+ logicalRecords: 1,
173
+ bytes: SANDBOX_CONTROL_RESERVATION_BYTES,
174
+ createdAt: now
175
+ };
176
+ atomicCreateJson(reservationPath(manifest, requestId), reservation, 'SANDBOX_CONTROL_RESERVATION_ALREADY_EXISTS');
177
+ return parseSandboxControlReservation(readJsonFile(reservationPath(manifest, requestId)));
178
+ }
179
+ export function readSandboxControlReservation(filePath) {
180
+ return parseSandboxControlReservation(readJsonFile(filePath));
181
+ }
182
+ export function sandboxControlGenerationUsage(manifest) {
183
+ const records = new Map();
184
+ const responsesDir = path.join(manifest.channelDir, 'responses');
185
+ if (fs.existsSync(responsesDir)) {
186
+ for (const entry of fs.readdirSync(responsesDir, { withFileTypes: true })) {
187
+ if (!entry.isFile())
188
+ continue;
189
+ const terminal = entry.name.match(/^([a-f0-9-]{16,64})\.json$/u);
190
+ const accepted = entry.name.match(/^([a-f0-9-]{16,64})\.accepted\.json$/u);
191
+ const payload = entry.name.match(/^([a-f0-9-]{16,64})\.payload\.json$/u);
192
+ if (!terminal && !accepted && !payload)
193
+ continue;
194
+ const id = (terminal ?? accepted ?? payload)[1];
195
+ const previous = records.get(id) ?? { baseBytes: 0, payloadBytes: 0, logical: false };
196
+ const fileBytes = fs.statSync(path.join(responsesDir, entry.name)).size;
197
+ if (payload)
198
+ previous.payloadBytes += fileBytes;
199
+ else
200
+ previous.baseBytes += fileBytes;
201
+ previous.logical ||= Boolean(terminal || accepted);
202
+ records.set(id, previous);
203
+ }
204
+ }
205
+ if (fs.existsSync(manifest.processingDir)) {
206
+ for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
207
+ if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/u.test(entry.name))
208
+ continue;
209
+ const resultPath = resultEvidencePath(manifest, entry.name);
210
+ const filePath = reservationPath(manifest, entry.name);
211
+ const previous = records.get(entry.name) ?? { baseBytes: 0, payloadBytes: 0, logical: false };
212
+ if (fs.existsSync(resultPath)) {
213
+ const result = readSandboxControlResultEvidence(resultPath);
214
+ if (result.id !== entry.name || result.generation !== manifest.generation) {
215
+ throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
216
+ }
217
+ previous.baseBytes += fs.statSync(resultPath).size;
218
+ previous.logical = true;
219
+ }
220
+ if (fs.existsSync(filePath)) {
221
+ const reservation = readSandboxControlReservation(filePath);
222
+ previous.baseBytes = Math.max(previous.baseBytes, reservation.bytes);
223
+ previous.logical = true;
224
+ }
225
+ records.set(entry.name, previous);
226
+ }
227
+ }
228
+ return {
229
+ logicalRecords: [...records.values()].filter((record) => record.logical).length,
230
+ bytes: [...records.values()].reduce((total, record) => total + record.baseBytes + record.payloadBytes, 0)
231
+ };
232
+ }
30
233
  export function statusPath(manifest) {
31
234
  return path.join(manifest.publicStatusDir, 'status.json');
32
235
  }
@@ -9,6 +9,28 @@ function splitWords(text) {
9
9
  function taskSubcommand(subcommand) {
10
10
  return subcommand === 'list' ? 'ls' : subcommand;
11
11
  }
12
+ function splitRunArguments(args) {
13
+ const hostArgs = [];
14
+ const skillArgs = [];
15
+ for (let i = 0; i < args.length; i += 1) {
16
+ const arg = args[i];
17
+ if (arg === '--tui') {
18
+ hostArgs.push(arg);
19
+ const value = args[i + 1];
20
+ if (value !== undefined) {
21
+ hostArgs.push(value);
22
+ i += 1;
23
+ }
24
+ }
25
+ else if (arg === '--recreate') {
26
+ hostArgs.push(arg);
27
+ }
28
+ else {
29
+ skillArgs.push(arg);
30
+ }
31
+ }
32
+ return { hostArgs, skillArgs };
33
+ }
12
34
  export function commandHelp() {
13
35
  return [
14
36
  `agent-infra ${VERSION}`,
@@ -78,7 +100,22 @@ export function parseCommand(text) {
78
100
  const spec = getSkillRunSpec(subcommand);
79
101
  if (!spec)
80
102
  return { kind: 'error', message: `Unknown skill: ${subcommand}` };
81
- return { kind: 'ai', role: spec.role, argv: ['run', subcommand, ...rest] };
103
+ if (spec.kind === 'create') {
104
+ return { kind: 'ai', role: spec.role, argv: ['run', '--skill', subcommand, ...rest] };
105
+ }
106
+ const [taskRef, ...skillArgs] = rest;
107
+ if (!taskRef)
108
+ return { kind: 'error', message: '/run task skills require a task-ref' };
109
+ const { hostArgs, skillArgs: forwardedSkillArgs } = splitRunArguments(skillArgs);
110
+ return {
111
+ kind: 'ai',
112
+ role: spec.role,
113
+ argv: [
114
+ 'run', '--skill', subcommand, '--task', taskRef,
115
+ ...hostArgs,
116
+ ...(forwardedSkillArgs.length > 0 ? ['--', ...forwardedSkillArgs] : [])
117
+ ]
118
+ };
82
119
  }
83
120
  return { kind: 'error', message: `Unknown command: ${command}` };
84
121
  }
@@ -5,6 +5,7 @@ const STARTED_SUFFIX_RE = /\s*\[started\]\s*$/;
5
5
  const ABORTED_SUFFIX_RE = /\s*\[aborted\]\s*$/;
6
6
  const ATTEMPT_RE = /(?:^|;\s*)attempt=([A-Za-z0-9-]{8,64})(?:;|$)/;
7
7
  const COMMIT_STARTED_RE = /^started; attempt=([A-Za-z0-9-]{8,64}); baseline=([a-f0-9]{40,64}); agent=([a-z-]+)$/;
8
+ const LIFECYCLE_STEP_RE = /^(?:Complete Manual Validation|(?:Analyze Task|Review Analysis|Plan Task|Review Plan|Code Task|Review Code|Run Manual Validation) \(Round [1-9]\d*(?:(?:, fix for review-code(?:-r[2-9]|-r[1-9]\d+)?\.md)|(?:, decision II-[1-9]\d*))?\))$/;
8
9
  function parseActivityLog(content) {
9
10
  const section = locateActivityLog(content);
10
11
  return section ? { sectionFound: true, entries: section.entries } : { sectionFound: false, entries: [] };
@@ -87,5 +88,11 @@ function pairEntries(entries) {
87
88
  function startedBackedRows(rows) {
88
89
  return rows.filter((row) => row.started !== '');
89
90
  }
90
- export { activityAttempt, appendActivityEntry, commitAttemptStartedNote, locateActivityLog, pairEntries, parseActivityLog, parseCommitAttemptStarted, startedBackedRows };
91
+ function hasOpenLifecycleExecution(content) {
92
+ const section = locateActivityLog(content);
93
+ if (!section)
94
+ return false;
95
+ return pairEntries(section.entries).some((row) => row.done === '' && LIFECYCLE_STEP_RE.test(row.step));
96
+ }
97
+ export { activityAttempt, appendActivityEntry, hasOpenLifecycleExecution, commitAttemptStartedNote, locateActivityLog, pairEntries, parseActivityLog, parseCommitAttemptStarted, startedBackedRows };
91
98
  //# sourceMappingURL=activity-log.js.map
@@ -6,6 +6,7 @@ import { parseImplementationInputs, selectPendingImplementationInput } from './i
6
6
  import { parseVerdict } from './review-artifacts.js';
7
7
  import { extractSection, findSectionHeading } from './sections.js';
8
8
  import { receiptForOutput, sha256File } from './artifact-receipts.js';
9
+ import { isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
9
10
  const artifactFamilyCatalog = [
10
11
  { family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
11
12
  { family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
@@ -85,7 +86,24 @@ function inspectArtifactDirectory(taskDir, family, identity = {}) {
85
86
  taskId: identity.taskId ?? null, taskDir, taskState: identity.taskState ?? null
86
87
  });
87
88
  }
89
+ let invalidation = { operations: [], targets: [] };
90
+ try {
91
+ const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
92
+ const parsed = parseInvalidationDocument(taskContent);
93
+ if (!parsed.ok) {
94
+ return failure(requestRef, family, { code: 'ARTIFACT_INVALIDATION_INVALID', message: parsed.message }, {
95
+ taskId: identity.taskId ?? null, taskDir, taskState: identity.taskState ?? null
96
+ });
97
+ }
98
+ invalidation = parsed.document;
99
+ }
100
+ catch (error) {
101
+ return failure(requestRef, family, { code: 'ARTIFACT_INVALIDATION_INVALID', message: String(error) }, {
102
+ taskId: identity.taskId ?? null, taskDir, taskState: identity.taskState ?? null
103
+ });
104
+ }
88
105
  const artifacts = [];
106
+ const knownArtifacts = [];
89
107
  const diagnostics = [];
90
108
  for (const entry of entries) {
91
109
  const parsed = parseArtifactName(entry.name);
@@ -117,19 +135,23 @@ function inspectArtifactDirectory(taskDir, family, identity = {}) {
117
135
  diagnostics.push(diagnostic('FILESYSTEM_RACE', family, entry.name, `artifact is not readable: ${entry.name}`));
118
136
  continue;
119
137
  }
120
- artifacts.push({ ...parsed, path: abs, size: stat.size, mtimeMs: stat.mtimeMs });
138
+ const identity = { ...parsed, path: abs, size: stat.size, mtimeMs: stat.mtimeMs };
139
+ knownArtifacts.push(identity);
140
+ if (!isArtifactInvalidated(invalidation, family, entry.name))
141
+ artifacts.push(identity);
121
142
  }
122
143
  catch (error) {
123
144
  diagnostics.push(diagnostic('FILESYSTEM_RACE', family, entry.name, String(error)));
124
145
  }
125
146
  }
126
147
  artifacts.sort((left, right) => left.round - right.round);
127
- const rounds = new Set(artifacts.map((item) => item.round));
128
- if (artifacts.length > 0 && !rounds.has(1))
148
+ knownArtifacts.sort((left, right) => left.round - right.round);
149
+ const knownRounds = new Set(knownArtifacts.map((item) => item.round));
150
+ if (knownArtifacts.length > 0 && !knownRounds.has(1))
129
151
  diagnostics.push(diagnostic('MISSING_BASE', family, null, `${family}.md is missing`));
130
- const max = artifacts.at(-1)?.round ?? 0;
152
+ const max = knownArtifacts.at(-1)?.round ?? 0;
131
153
  for (let round = 1; round <= max; round += 1) {
132
- if (!rounds.has(round))
154
+ if (!knownRounds.has(round))
133
155
  diagnostics.push(diagnostic('ROUND_GAP', family, null, `${artifactName(family, round)} is missing`));
134
156
  }
135
157
  const latest = artifacts.at(-1) ?? null;
@@ -163,6 +185,12 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
163
185
  if (!stat.isFile() || stat.isSymbolicLink())
164
186
  throw new Error('referenced input is not a regular file');
165
187
  const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
188
+ const invalidation = parseInvalidationDocument(taskContent);
189
+ if (!invalidation.ok)
190
+ throw new Error(invalidation.message);
191
+ if (isArtifactInvalidated(invalidation.document, expectedFamily, parsed.name)) {
192
+ throw new Error(`${parsed.name} is invalidated`);
193
+ }
166
194
  const receipt = receiptForOutput(taskContent, review.name);
167
195
  if (!receipt)
168
196
  throw new Error(`receipt for ${review.name} is missing`);
@@ -0,0 +1,239 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
4
+ import { receiptForOutput } from './artifact-receipts.js';
5
+ import { parseTypedTaskFrontmatter } from './frontmatter.js';
6
+ import { parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
7
+ import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
8
+ import { parseReworkIntentDocument } from './rework-intent.js';
9
+ import { sha256File } from './artifact-receipts.js';
10
+ import { hasOpenLifecycleExecution } from './activity-log.js';
11
+ const REASONS = new Set([
12
+ 'user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'
13
+ ]);
14
+ function deny(reasonCode, ...evidence) {
15
+ return { allowed: false, reasonCode, evidence };
16
+ }
17
+ function allow(...evidence) {
18
+ return { allowed: true, reasonCode: 'CAPABILITY_ALLOWED', evidence };
19
+ }
20
+ function hasArtifact(facts, action) {
21
+ return (facts.artifacts[action]?.length ?? 0) > 0;
22
+ }
23
+ function canStart(action, facts, trigger) {
24
+ if (!trigger || trigger.requestedAction !== action)
25
+ return deny('TRIGGER_ACTION_MISMATCH', `requested=${trigger?.requestedAction ?? 'missing'}`);
26
+ if (!trigger.requestId || !trigger.requestId.trim())
27
+ return deny('TRIGGER_REQUEST_ID_REQUIRED');
28
+ if (!REASONS.has(trigger.reasonCode))
29
+ return deny('TRIGGER_REASON_INVALID');
30
+ if (trigger.sourceArtifact && !trigger.sourceSha256)
31
+ return deny('SOURCE_ARTIFACT_HASH_REQUIRED');
32
+ if (trigger.sourceSha256 && !trigger.sourceArtifact)
33
+ return deny('SOURCE_ARTIFACT_REQUIRED');
34
+ if (trigger.sourceArtifact && trigger.sourceSha256) {
35
+ const actual = facts.artifactHashes[trigger.sourceArtifact];
36
+ if (!actual)
37
+ return deny('SOURCE_ARTIFACT_MISSING', trigger.sourceArtifact);
38
+ if (actual !== trigger.sourceSha256)
39
+ return deny('SOURCE_ARTIFACT_HASH_MISMATCH', trigger.sourceArtifact);
40
+ }
41
+ if (facts.taskState !== 'active')
42
+ return deny('TASK_NOT_ACTIVE', `state=${facts.taskState}`);
43
+ if (invalidationBlocks(facts.invalidation))
44
+ return deny('INVALIDATION_INCOMPLETE');
45
+ if (facts.executionBusy)
46
+ return deny('EXECUTION_BUSY');
47
+ const implicit = trigger.explicitRequest === false;
48
+ if (action === 'analysis') {
49
+ if (!implicit || hasArtifact(facts, 'analysis') || facts.recommendedAction === 'analysis')
50
+ return allow('analysis-capability');
51
+ return deny('ANALYSIS_REQUEST_REQUIRES_EXPLICIT_TRIGGER');
52
+ }
53
+ if (action === 'review-analysis') {
54
+ return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
55
+ }
56
+ if (action === 'plan') {
57
+ if (!hasArtifact(facts, 'review-analysis'))
58
+ return deny('ANALYSIS_REVIEW_REQUIRED');
59
+ if (!reviewMatchesLatest(facts, 'analysis', 'review-analysis'))
60
+ return deny('ANALYSIS_REVIEW_NOT_LATEST');
61
+ if (facts.reviews['review-analysis'] !== 'approved')
62
+ return deny('ANALYSIS_REVIEW_NOT_APPROVED');
63
+ if (facts.unresolvedLedger.analysis > 0)
64
+ return deny('ANALYSIS_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.analysis}`);
65
+ return allow('analysis-review-approved');
66
+ }
67
+ if (action === 'review-plan') {
68
+ return hasArtifact(facts, 'plan') ? allow('plan-artifact') : deny('PLAN_ARTIFACT_REQUIRED');
69
+ }
70
+ if (action === 'code') {
71
+ if (trigger.implementationInput) {
72
+ if (!hasArtifact(facts, 'review-code'))
73
+ return deny('CODE_REVIEW_REQUIRED');
74
+ if (!reviewMatchesLatest(facts, 'code', 'review-code'))
75
+ return deny('CODE_REVIEW_NOT_LATEST');
76
+ if (facts.reviews['review-code'] !== 'approved')
77
+ return deny('CODE_REVIEW_NOT_APPROVED');
78
+ return allow('implementation-input');
79
+ }
80
+ if (!hasArtifact(facts, 'review-plan'))
81
+ return deny('PLAN_REVIEW_REQUIRED');
82
+ if (!reviewMatchesLatest(facts, 'plan', 'review-plan'))
83
+ return deny('PLAN_REVIEW_NOT_LATEST');
84
+ if (facts.reviews['review-plan'] !== 'approved')
85
+ return deny('PLAN_REVIEW_NOT_APPROVED');
86
+ if (facts.unresolvedLedger.plan > 0)
87
+ return deny('PLAN_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.plan}`);
88
+ return allow('plan-review-approved');
89
+ }
90
+ if (action === 'review-code') {
91
+ return hasArtifact(facts, 'code') ? allow('code-artifact') : deny('CODE_ARTIFACT_REQUIRED');
92
+ }
93
+ if (action === 'manual-validation' || action === 'validation-run') {
94
+ if (!hasArtifact(facts, 'review-code'))
95
+ return deny('CODE_REVIEW_REQUIRED');
96
+ if (!reviewMatchesLatest(facts, 'code', 'review-code'))
97
+ return deny('CODE_REVIEW_NOT_LATEST');
98
+ if (facts.reviews['review-code'] !== 'approved')
99
+ return deny('CODE_REVIEW_NOT_APPROVED');
100
+ if (facts.unresolvedLedger.code > 0)
101
+ return deny('CODE_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.code}`);
102
+ return allow('code-review-approved');
103
+ }
104
+ return deny('CAPABILITY_ACTION_UNKNOWN');
105
+ }
106
+ function artifactRound(name) {
107
+ const match = /-r(\d+)\.md$/.exec(name);
108
+ return match ? Number(match[1]) : 1;
109
+ }
110
+ function latestArtifact(names) {
111
+ return [...names].sort((left, right) => artifactRound(right) - artifactRound(left) || left.localeCompare(right))[0] ?? null;
112
+ }
113
+ function reviewedInputName(content, expectedFamily) {
114
+ const header = content.split(/\r?\n/).findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
115
+ if (header < 0)
116
+ return null;
117
+ const referenceBlock = content.split(/\r?\n/).slice(header, header + 12).join('\n');
118
+ const expected = new RegExp('`' + expectedFamily + '(?:-r[2-9]|-r[1-9]\\d+)?\\.md`');
119
+ return expected.exec(referenceBlock)?.[0].slice(1, -1) ?? null;
120
+ }
121
+ function reviewMatchesLatest(facts, source, review) {
122
+ const latestSource = latestArtifact(facts.artifacts[source] ?? []);
123
+ return Boolean(latestSource && facts.reviewedInputs?.[review] === latestSource);
124
+ }
125
+ function recommendNext(facts) {
126
+ if (invalidationBlocks(facts.invalidation))
127
+ return { action: null, reasonCode: 'INVALIDATION_INCOMPLETE', evidence: ['reconcile task invalidation before routing'] };
128
+ const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
129
+ if (pendingIntent)
130
+ return { action: pendingIntent.target, reasonCode: 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
131
+ if (!hasArtifact(facts, 'analysis'))
132
+ return { action: 'analysis', reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
133
+ if (!hasArtifact(facts, 'review-analysis') || !reviewMatchesLatest(facts, 'analysis', 'review-analysis')) {
134
+ return { action: 'review-analysis', reasonCode: 'ANALYSIS_REVIEW_MISSING', evidence: ['analysis review does not bind the latest analysis artifact'] };
135
+ }
136
+ if (facts.reviews['review-analysis'] !== 'approved' || facts.unresolvedLedger.analysis > 0) {
137
+ return { action: 'analysis', reasonCode: 'ANALYSIS_REWORK_REQUIRED', evidence: ['analysis review or ledger is not clear'] };
138
+ }
139
+ if (!hasArtifact(facts, 'plan'))
140
+ return { action: 'plan', reasonCode: 'PLAN_ARTIFACT_MISSING', evidence: ['plan artifact is absent'] };
141
+ if (!hasArtifact(facts, 'review-plan') || !reviewMatchesLatest(facts, 'plan', 'review-plan')) {
142
+ return { action: 'review-plan', reasonCode: 'PLAN_REVIEW_MISSING', evidence: ['plan review does not bind the latest plan artifact'] };
143
+ }
144
+ if (facts.reviews['review-plan'] !== 'approved' || facts.unresolvedLedger.plan > 0) {
145
+ return { action: 'plan', reasonCode: 'PLAN_REWORK_REQUIRED', evidence: ['plan review or ledger is not clear'] };
146
+ }
147
+ if (!hasArtifact(facts, 'code'))
148
+ return { action: 'code', reasonCode: 'CODE_ARTIFACT_MISSING', evidence: ['code artifact is absent'] };
149
+ if (!hasArtifact(facts, 'review-code') || !reviewMatchesLatest(facts, 'code', 'review-code')) {
150
+ return { action: 'review-code', reasonCode: 'CODE_REVIEW_MISSING', evidence: ['code review does not bind the latest code artifact'] };
151
+ }
152
+ if (facts.reviews['review-code'] !== 'approved') {
153
+ return { action: 'code', reasonCode: 'CODE_REWORK_REQUIRED', evidence: ['code review or ledger is not clear'] };
154
+ }
155
+ return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['latest code review is approved'] };
156
+ }
157
+ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBusy = false) {
158
+ try {
159
+ const metadata = parseTypedTaskFrontmatter(content);
160
+ const invalidation = parseInvalidationDocument(content);
161
+ if (!invalidation.ok)
162
+ return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: invalidation.message };
163
+ const rework = parseReworkIntentDocument(content);
164
+ if (!rework.ok)
165
+ return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: rework.message };
166
+ const ledger = parseLedgerDocument(content);
167
+ if (ledger.present) {
168
+ const invalid = validateLedgerRows(ledger.rows);
169
+ if (invalid)
170
+ return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: invalid.message };
171
+ }
172
+ const files = fs.readdirSync(taskDir).filter((name) => {
173
+ if (!name.endsWith('.md'))
174
+ return false;
175
+ const stat = fs.lstatSync(path.join(taskDir, name));
176
+ return stat.isFile() && !stat.isSymbolicLink();
177
+ });
178
+ const artifactFamilies = {
179
+ analysis: /^analysis(?:-r\d+)?\.md$/,
180
+ 'review-analysis': /^review-analysis(?:-r\d+)?\.md$/,
181
+ plan: /^plan(?:-r\d+)?\.md$/,
182
+ 'review-plan': /^review-plan(?:-r\d+)?\.md$/,
183
+ code: /^code(?:-r\d+)?\.md$/,
184
+ 'review-code': /^review-code(?:-r\d+)?\.md$/
185
+ };
186
+ const familyFor = (name) => Object.keys(artifactFamilies)
187
+ .find((family) => artifactFamilies[family].test(name)) ?? null;
188
+ const activeFiles = files.filter((name) => {
189
+ const family = familyFor(name);
190
+ return !family || !isArtifactInvalidated(invalidation.document, family, name);
191
+ });
192
+ const artifactHashes = {};
193
+ for (const name of activeFiles)
194
+ artifactHashes[name] = sha256File(path.join(taskDir, name));
195
+ const artifacts = Object.fromEntries(Object.keys(artifactFamilies).map((family) => [
196
+ family, activeFiles.filter((name) => artifactFamilies[family].test(name))
197
+ ]));
198
+ const staleArtifacts = Object.fromEntries(Object.keys(artifactFamilies).map((family) => [
199
+ family, files.filter((name) => artifactFamilies[family].test(name) && isArtifactInvalidated(invalidation.document, family, name))
200
+ ]));
201
+ const reviews = {};
202
+ const reviewedInputs = {};
203
+ for (const family of ['review-analysis', 'review-plan', 'review-code']) {
204
+ const latest = latestArtifact(artifacts[family] ?? []);
205
+ if (!latest)
206
+ continue;
207
+ const reviewContent = fs.readFileSync(path.join(taskDir, latest), 'utf8');
208
+ const expectedFamily = family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
209
+ const input = reviewedInputName(reviewContent, expectedFamily);
210
+ const receipt = input ? receiptForOutput(content, latest) : null;
211
+ if (input && receipt?.input === input && artifactHashes[input] === receipt.inputSha256)
212
+ reviewedInputs[family] = input;
213
+ const parsed = parseReviewSummary(reviewContent);
214
+ if (!parsed.ok)
215
+ continue;
216
+ const verdict = resolveCanonicalVerdict(parsed.summary);
217
+ if (verdict.ok)
218
+ reviews[family] = verdict.verdict === 'Approved' ? 'approved' : verdict.verdict === 'Changes Requested' ? 'changes-requested' : 'rejected';
219
+ }
220
+ const unresolvedLedger = { analysis: 0, plan: 0, code: 0 };
221
+ if (ledger.present) {
222
+ for (const stage of ['analysis', 'plan', 'code'])
223
+ unresolvedLedger[stage] = summarizeLedgerStage(ledger.rows, stage).unresolved.length;
224
+ }
225
+ const facts = {
226
+ taskState, currentStep: String(metadata.current_step ?? ''), artifacts, reviews,
227
+ staleArtifacts, reviewedInputs, artifactHashes,
228
+ invalidation: invalidation.document, reworkIntents: rework.intents,
229
+ unresolvedLedger, executionBusy: executionBusy || hasOpenLifecycleExecution(content), recommendedAction: null
230
+ };
231
+ facts.recommendedAction = recommendNext(facts).action;
232
+ return { ok: true, facts };
233
+ }
234
+ catch (error) {
235
+ return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: error instanceof Error ? error.message : String(error) };
236
+ }
237
+ }
238
+ export { buildLifecycleFacts, canStart, recommendNext };
239
+ //# sourceMappingURL=capabilities.js.map