@fitlab-ai/agent-infra 0.9.2 → 0.9.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 (428) hide show
  1. package/README.md +12 -9
  2. package/README.zh-CN.md +12 -9
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +134 -108
  6. package/dist/lib/agent-clients/adapter.js +137 -4
  7. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  8. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  9. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  10. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  11. package/dist/lib/agent-clients/adapters/claude-code.js +27 -4
  12. package/dist/lib/agent-clients/adapters/codex.js +9 -0
  13. package/dist/lib/agent-clients/adapters/index.js +2 -2
  14. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  15. package/dist/lib/agent-clients/adapters/opencode.js +24 -3
  16. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  17. package/dist/lib/agent-clients/config.js +42 -4
  18. package/dist/lib/agent-clients/next-steps.js +11 -2
  19. package/dist/lib/agent-clients/reconcile.js +2 -1
  20. package/dist/lib/agent-clients/registry.js +16 -2
  21. package/dist/lib/agent-clients/schema.js +18 -0
  22. package/dist/lib/agent-clients/tokens.js +49 -0
  23. package/dist/lib/agent-clients/types.js +1 -1
  24. package/dist/lib/defaults.json +1 -1
  25. package/dist/lib/git/workflow.js +54 -1
  26. package/dist/lib/internal/agent-client.js +61 -4
  27. package/dist/lib/internal/git-workflow.js +6 -1
  28. package/dist/lib/internal/platform-checks.js +3 -3
  29. package/dist/lib/internal/platform-comment.js +29 -2
  30. package/dist/lib/internal/platform-issue.js +7 -0
  31. package/dist/lib/internal/platform-pr-review.js +132 -0
  32. package/dist/lib/internal/platform-pr.js +20 -2
  33. package/dist/lib/internal/platform-release-notes.js +52 -16
  34. package/dist/lib/internal/pr-review-grade.js +140 -0
  35. package/dist/lib/internal/release-workflow.js +13 -6
  36. package/dist/lib/internal/sandbox-control.js +24 -0
  37. package/dist/lib/internal/task-activity.js +105 -0
  38. package/dist/lib/internal/task-event.js +17 -1
  39. package/dist/lib/internal/task-ledger.js +12 -3
  40. package/dist/lib/internal/task-lifecycle.js +26 -1
  41. package/dist/lib/internal/task-orchestration.js +167 -18
  42. package/dist/lib/internal/task-review.js +8 -3
  43. package/dist/lib/platform/adapters.js +8 -1
  44. package/dist/lib/platform/completion-backfill.js +122 -0
  45. package/dist/lib/platform/github-client.js +19 -2
  46. package/dist/lib/platform/issue-comments.js +3 -2
  47. package/dist/lib/platform/merged-pr-equivalence.js +115 -5
  48. package/dist/lib/platform/pr-checks.js +65 -42
  49. package/dist/lib/platform/pr-review.js +166 -0
  50. package/dist/lib/platform/pull-requests.js +371 -4
  51. package/dist/lib/platform/release-notes.js +85 -3
  52. package/dist/lib/platform/releases.js +13 -5
  53. package/dist/lib/pr-review/evidence-grading.js +229 -0
  54. package/dist/lib/process-data/index.js +239 -0
  55. package/dist/lib/process-data/normalize.js +198 -0
  56. package/dist/lib/process-data/privacy.js +33 -0
  57. package/dist/lib/process-data/reconcile.js +95 -0
  58. package/dist/lib/process-data/repair.js +58 -0
  59. package/dist/lib/process-data/sources.js +345 -0
  60. package/dist/lib/process-data/store.js +282 -0
  61. package/dist/lib/process-data/types.js +2 -0
  62. package/dist/lib/run/index.js +4 -3
  63. package/dist/lib/run/tui.js +10 -13
  64. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  65. package/dist/lib/sandbox/capture.js +7 -2
  66. package/dist/lib/sandbox/commands/create.js +109 -248
  67. package/dist/lib/sandbox/commands/enter.js +15 -47
  68. package/dist/lib/sandbox/commands/list-running.js +16 -6
  69. package/dist/lib/sandbox/commands/ls.js +10 -6
  70. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  71. package/dist/lib/sandbox/commands/refresh.js +4 -84
  72. package/dist/lib/sandbox/commands/rm.js +24 -3
  73. package/dist/lib/sandbox/commands/show.js +13 -4
  74. package/dist/lib/sandbox/commands/start.js +7 -4
  75. package/dist/lib/sandbox/config.js +2 -0
  76. package/dist/lib/sandbox/constants.js +6 -0
  77. package/dist/lib/sandbox/control/client.js +44 -0
  78. package/dist/lib/sandbox/control/protocol.js +35 -0
  79. package/dist/lib/sandbox/control/server.js +109 -0
  80. package/dist/lib/sandbox/dockerfile.js +36 -13
  81. package/dist/lib/sandbox/mounts.js +17 -4
  82. package/dist/lib/sandbox/recovery.js +128 -5
  83. package/dist/lib/sandbox/redaction.js +12 -0
  84. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  85. package/dist/lib/sandbox/workspace-identity.js +95 -0
  86. package/dist/lib/sandbox/workspace-view.js +103 -0
  87. package/dist/lib/task/activity-intent.js +307 -0
  88. package/dist/lib/task/artifact-lifecycle.js +2 -1
  89. package/dist/lib/task/commands/log.js +15 -14
  90. package/dist/lib/task/commit-finalization.js +267 -0
  91. package/dist/lib/task/commit-intent.js +165 -0
  92. package/dist/lib/task/decision-intents.js +31 -17
  93. package/dist/lib/task/delegation-receipts.js +25 -4
  94. package/dist/lib/task/events.js +67 -28
  95. package/dist/lib/task/finalization-artifacts.js +35 -0
  96. package/dist/lib/task/implementation-inputs.js +54 -7
  97. package/dist/lib/task/ledger-intents.js +35 -1
  98. package/dist/lib/task/lifecycle-execution.js +46 -0
  99. package/dist/lib/task/orchestration.js +753 -54
  100. package/dist/lib/task/resolve-ref.js +41 -1
  101. package/dist/lib/task/review-finalization.js +29 -9
  102. package/dist/lib/task/task-execution-lock.js +231 -0
  103. package/dist/lib/task/verification-checks.js +1 -1
  104. package/dist/lib/task/verification-engine.js +218 -2
  105. package/dist/lib/task/verification.js +4 -3
  106. package/dist/lib/task/workspace-snapshot.js +17 -5
  107. package/lib/agent-clients/adapter.ts +201 -4
  108. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  109. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  110. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  111. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  112. package/lib/agent-clients/adapters/claude-code.ts +31 -4
  113. package/lib/agent-clients/adapters/codex.ts +9 -0
  114. package/lib/agent-clients/adapters/index.ts +2 -2
  115. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  116. package/lib/agent-clients/adapters/opencode.ts +27 -3
  117. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  118. package/lib/agent-clients/config.ts +49 -4
  119. package/lib/agent-clients/next-steps.ts +15 -2
  120. package/lib/agent-clients/reconcile.ts +2 -1
  121. package/lib/agent-clients/registry.ts +19 -1
  122. package/lib/agent-clients/schema.ts +18 -0
  123. package/lib/agent-clients/tokens.ts +53 -0
  124. package/lib/agent-clients/types.ts +15 -1
  125. package/lib/defaults.json +1 -1
  126. package/lib/git/workflow.ts +66 -2
  127. package/lib/internal/agent-client.ts +59 -4
  128. package/lib/internal/git-workflow.ts +5 -1
  129. package/lib/internal/platform-checks.ts +4 -4
  130. package/lib/internal/platform-comment.ts +17 -2
  131. package/lib/internal/platform-issue.ts +4 -0
  132. package/lib/internal/platform-pr-review.ts +104 -0
  133. package/lib/internal/platform-pr.ts +14 -1
  134. package/lib/internal/platform-release-notes.ts +59 -16
  135. package/lib/internal/pr-review-grade.ts +130 -0
  136. package/lib/internal/release-workflow.ts +13 -6
  137. package/lib/internal/sandbox-control.ts +24 -0
  138. package/lib/internal/task-activity.ts +91 -0
  139. package/lib/internal/task-event.ts +9 -1
  140. package/lib/internal/task-ledger.ts +12 -3
  141. package/lib/internal/task-lifecycle.ts +26 -1
  142. package/lib/internal/task-orchestration.ts +123 -14
  143. package/lib/internal/task-review.ts +8 -3
  144. package/lib/platform/adapters.ts +29 -2
  145. package/lib/platform/completion-backfill.ts +134 -0
  146. package/lib/platform/github-client.ts +20 -2
  147. package/lib/platform/issue-comments.ts +3 -2
  148. package/lib/platform/merged-pr-equivalence.ts +127 -5
  149. package/lib/platform/pr-checks.ts +73 -46
  150. package/lib/platform/pr-review.ts +201 -0
  151. package/lib/platform/pull-requests.ts +444 -3
  152. package/lib/platform/release-notes.ts +100 -4
  153. package/lib/platform/releases.ts +16 -4
  154. package/lib/pr-review/evidence-grading.ts +318 -0
  155. package/lib/process-data/index.ts +224 -0
  156. package/lib/process-data/normalize.ts +202 -0
  157. package/lib/process-data/privacy.ts +41 -0
  158. package/lib/process-data/reconcile.ts +110 -0
  159. package/lib/process-data/repair.ts +64 -0
  160. package/lib/process-data/sources.ts +354 -0
  161. package/lib/process-data/store.ts +315 -0
  162. package/lib/process-data/types.ts +135 -0
  163. package/lib/run/index.ts +5 -3
  164. package/lib/run/tui.ts +14 -12
  165. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  166. package/lib/sandbox/capture.ts +9 -2
  167. package/lib/sandbox/commands/create.ts +126 -280
  168. package/lib/sandbox/commands/enter.ts +13 -59
  169. package/lib/sandbox/commands/list-running.ts +23 -6
  170. package/lib/sandbox/commands/ls.ts +22 -6
  171. package/lib/sandbox/commands/rebuild.ts +4 -3
  172. package/lib/sandbox/commands/refresh.ts +4 -126
  173. package/lib/sandbox/commands/rm.ts +24 -3
  174. package/lib/sandbox/commands/show.ts +30 -4
  175. package/lib/sandbox/commands/start.ts +6 -4
  176. package/lib/sandbox/config.ts +4 -0
  177. package/lib/sandbox/constants.ts +8 -0
  178. package/lib/sandbox/control/client.ts +55 -0
  179. package/lib/sandbox/control/protocol.ts +75 -0
  180. package/lib/sandbox/control/server.ts +117 -0
  181. package/lib/sandbox/dockerfile.ts +66 -13
  182. package/lib/sandbox/mounts.ts +24 -4
  183. package/lib/sandbox/recovery.ts +163 -6
  184. package/lib/sandbox/redaction.ts +14 -0
  185. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  186. package/lib/sandbox/tool-types.ts +16 -0
  187. package/lib/sandbox/workspace-identity.ts +123 -0
  188. package/lib/sandbox/workspace-view.ts +143 -0
  189. package/lib/server/server.schema.json +2 -2
  190. package/lib/task/activity-intent.ts +417 -0
  191. package/lib/task/artifact-lifecycle.ts +2 -1
  192. package/lib/task/commands/log.ts +15 -14
  193. package/lib/task/commit-finalization.ts +356 -0
  194. package/lib/task/commit-intent.ts +230 -0
  195. package/lib/task/decision-intents.ts +32 -12
  196. package/lib/task/delegation-receipts.ts +34 -5
  197. package/lib/task/events.ts +75 -30
  198. package/lib/task/finalization-artifacts.ts +58 -0
  199. package/lib/task/implementation-inputs.ts +62 -8
  200. package/lib/task/ledger-intents.ts +43 -3
  201. package/lib/task/lifecycle-execution.ts +106 -0
  202. package/lib/task/orchestration.ts +1019 -72
  203. package/lib/task/resolve-ref.ts +46 -1
  204. package/lib/task/review-finalization.ts +40 -9
  205. package/lib/task/task-execution-lock.ts +296 -0
  206. package/lib/task/verification-checks.ts +1 -1
  207. package/lib/task/verification-engine.ts +249 -2
  208. package/lib/task/verification.ts +5 -3
  209. package/lib/task/workspace-snapshot.ts +26 -5
  210. package/package.json +3 -3
  211. package/templates/.agents/QUICKSTART.en.md +25 -8
  212. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  213. package/templates/.agents/README.en.md +10 -7
  214. package/templates/.agents/README.zh-CN.md +10 -7
  215. package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
  216. package/templates/.agents/rules/README.en.md +1 -1
  217. package/templates/.agents/rules/README.zh-CN.md +1 -1
  218. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  219. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  220. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  221. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  222. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  223. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  224. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  225. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  226. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  227. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  228. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  229. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  230. package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
  231. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
  232. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  233. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  234. package/templates/.agents/rules/next-step-output.en.md +3 -1
  235. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
  236. package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
  237. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
  238. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  239. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  240. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +3 -3
  241. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  242. package/templates/.agents/rules/pr-sync.en.md +3 -1
  243. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  244. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  245. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  246. package/templates/.agents/rules/release-commands.en.md +1 -1
  247. package/templates/.agents/rules/release-commands.github.en.md +12 -2
  248. package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
  249. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  250. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  251. package/templates/.agents/rules/task-management.en.md +13 -0
  252. package/templates/.agents/rules/task-management.zh-CN.md +13 -0
  253. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
  254. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
  255. package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
  256. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
  257. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
  258. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
  259. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
  260. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
  261. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
  262. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
  263. package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
  264. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +12 -9
  265. package/templates/.agents/skills/commit/SKILL.en.md +23 -11
  266. package/templates/.agents/skills/commit/SKILL.zh-CN.md +29 -11
  267. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  268. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  269. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
  270. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
  271. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  272. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  273. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  274. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  275. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  276. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  279. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  281. package/templates/.agents/skills/complete-task/SKILL.en.md +22 -7
  282. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +22 -7
  283. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  284. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  285. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  286. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  287. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -5
  288. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -5
  289. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  290. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  292. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  293. package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
  294. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
  295. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  296. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  297. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
  298. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
  299. package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
  300. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
  301. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  302. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  303. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  304. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  305. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  306. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  307. package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
  308. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
  309. package/templates/.agents/skills/review-code/SKILL.en.md +11 -8
  310. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
  311. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  312. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  313. package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
  314. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
  315. package/templates/.agents/skills/review-pr/SKILL.en.md +186 -0
  316. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +186 -0
  317. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  318. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  319. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  320. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  321. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  322. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  323. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  324. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  325. package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
  326. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
  327. package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
  328. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
  329. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  330. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  331. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +98 -107
  332. package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
  333. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
  334. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  335. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  336. package/templates/.agents/templates/task.en.md +2 -2
  337. package/templates/.agents/templates/task.zh-CN.md +2 -2
  338. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  339. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  340. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  341. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  342. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  343. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  344. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  345. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  346. package/templates/.claude/commands/post-release.en.md +1 -0
  347. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  348. package/templates/.claude/commands/review-pr.en.md +8 -0
  349. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  350. package/templates/.claude/commands/watch-pr.en.md +1 -1
  351. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  352. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  353. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  354. package/templates/.opencode/commands/post-release.en.md +2 -0
  355. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  356. package/templates/.opencode/commands/review-pr.en.md +11 -0
  357. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  358. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  359. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  360. package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
  361. package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
  362. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  363. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  364. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  365. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  366. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  367. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  368. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  369. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  370. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  371. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  372. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  373. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  374. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  375. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  376. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  377. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  378. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  379. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  380. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  381. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  382. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  383. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  384. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  385. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  386. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  387. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  388. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  389. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  390. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  391. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  392. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  393. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  394. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  395. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  396. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  397. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  398. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  399. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  400. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  401. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  402. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  403. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  404. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  405. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  406. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  407. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  408. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  409. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  410. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  411. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  412. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  413. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  414. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  415. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  416. package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
  417. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
  418. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  419. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  420. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  421. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  422. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  423. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  424. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  425. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  426. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  427. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  428. package/templates/.gemini/settings.json +0 -3
@@ -0,0 +1,202 @@
1
+ import { sha256 } from './store.ts';
2
+
3
+ import type { CapturedObject, JsonValue, NormalizedKind, NormalizedRecord } from './types.ts';
4
+
5
+ const TASK_ID_RE = /TASK-\d{8}-\d{6}/;
6
+ const ACTIVITY_RE = /^- (\d{4}-\d{2}-\d{2} [^ ]+) — \*\*(.+?)\*\* by ([^ ]+) — (.+)$/gm;
7
+ const LEDGER_RE = /^\|\s*((?:AN|PL|CD)-\d+)\s*\|\s*(analysis|plan|code)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|$/gm;
8
+ const RULING_RE = /^###\s+(HDR-\d+)\s*$([\s\S]*?)(?=^###\s+|^##\s+|(?![\s\S]))/gm;
9
+ const CONFLICT_STATUSES = new Set(['open', 'accepted', 'adjusted', 'refuted', 'cannot-judge', 'needs-human-decision']);
10
+
11
+ function recordId(kind: NormalizedKind, identity: string, suffix = ''): string {
12
+ return `${kind}:${sha256(`${identity}\0${suffix}`).slice(0, 24)}`;
13
+ }
14
+
15
+ function canonicalActor(raw: string): string | null {
16
+ const lowered = raw.toLowerCase();
17
+ const aliases: Record<string, string> = {
18
+ 'claude-code': 'claude',
19
+ 'antigravity-cli': 'antigravity',
20
+ 'gemini-cli': 'gemini',
21
+ gemini: 'gemini'
22
+ };
23
+ if (['claude', 'codex', 'antigravity', 'opencode', 'cursor', 'human'].includes(lowered)) return lowered;
24
+ return aliases[lowered] ?? null;
25
+ }
26
+
27
+ function nextOccurrence(occurrences: Map<string, number>, identity: string): string {
28
+ const occurrence = (occurrences.get(identity) ?? 0) + 1;
29
+ occurrences.set(identity, occurrence);
30
+ return occurrence === 1 ? '' : `occurrence=${occurrence}`;
31
+ }
32
+
33
+ function normalizePlatformPage(object: CapturedObject): NormalizedRecord[] {
34
+ if (!object.content) return [];
35
+ try {
36
+ const page = JSON.parse(object.content) as unknown;
37
+ if (!Array.isArray(page)) return [];
38
+ return page.map((entry, index) => {
39
+ const value = entry as Record<string, unknown>;
40
+ const identity = String(value.id ?? value.node_id ?? value.sha ?? value.number ?? `${object.sourceIdentity}:${index}`);
41
+ const platformIdentity = /\/issues\?/.test(object.sourceIdentity) && typeof value.number === 'number'
42
+ ? `issue:${value.number}`
43
+ : /\/pulls\?/.test(object.sourceIdentity) && typeof value.number === 'number'
44
+ ? `pr:${value.number}`
45
+ : /\/issues\/\d+\/comments/.test(object.sourceIdentity)
46
+ ? `issue-comment:${identity}`
47
+ : /\/pulls\/\d+\/reviews/.test(object.sourceIdentity)
48
+ ? `review:${identity}`
49
+ : /\/pulls\/\d+\/commits/.test(object.sourceIdentity)
50
+ ? `commit:${identity}`
51
+ : `platform:${identity}`;
52
+ const binding = typeof value.body === 'string' ? value.body.match(TASK_ID_RE)?.[0] : undefined;
53
+ return {
54
+ recordId: recordId('platform-resource', object.sourceIdentity, identity),
55
+ kind: 'platform-resource',
56
+ sourceIdentity: platformIdentity,
57
+ sourceSha256: object.sha256,
58
+ ...(binding ? { binding } : {}),
59
+ data: entry as JsonValue
60
+ };
61
+ });
62
+ } catch {
63
+ return [];
64
+ }
65
+ }
66
+
67
+ function normalizeObjects(objects: CapturedObject[]): NormalizedRecord[] {
68
+ const records: NormalizedRecord[] = [];
69
+ for (const object of objects) {
70
+ if (object.disposition?.state && object.disposition.state !== 'included') {
71
+ records.push({
72
+ recordId: recordId('missing', object.sourceIdentity),
73
+ kind: 'missing',
74
+ sourceIdentity: object.sourceIdentity,
75
+ sourceSha256: object.sha256,
76
+ data: object.disposition.state === 'excluded-sensitive'
77
+ ? { reason: 'privacy-excluded', ruleId: object.disposition.ruleId }
78
+ : { reason: object.disposition.reason }
79
+ });
80
+ continue;
81
+ }
82
+ if (object.sourceKind === 'github-rest') {
83
+ records.push(...normalizePlatformPage(object));
84
+ continue;
85
+ }
86
+ if (object.sourceKind === 'operational-report') {
87
+ records.push({
88
+ recordId: recordId('operational-report', object.sourceIdentity),
89
+ kind: 'operational-report',
90
+ sourceIdentity: object.sourceIdentity,
91
+ sourceSha256: object.sha256
92
+ });
93
+ continue;
94
+ }
95
+ if (object.sourceKind === 'structured-telemetry') {
96
+ let data: JsonValue = { availability: 'unknown' };
97
+ try {
98
+ data = JSON.parse(object.content ?? '{}') as JsonValue;
99
+ } catch {
100
+ data = { availability: 'unavailable', reason: 'invalid receipt JSON' };
101
+ }
102
+ records.push({
103
+ recordId: recordId('telemetry', object.sourceIdentity),
104
+ kind: 'telemetry',
105
+ sourceIdentity: object.sourceIdentity,
106
+ sourceSha256: object.sha256,
107
+ data
108
+ });
109
+ continue;
110
+ }
111
+
112
+ const taskId = object.sourceIdentity.match(TASK_ID_RE)?.[0];
113
+ if (/\/task\.md$/.test(object.sourceIdentity) && taskId) {
114
+ const activityOccurrences = new Map<string, number>();
115
+ const findingOccurrences = new Map<string, number>();
116
+ const issueNumber = object.content?.match(/^issue_number:\s*(\d+)$/m)?.[1];
117
+ const agentInfraVersion = object.content?.match(/^agent_infra_version:\s*(\S+)$/m)?.[1];
118
+ records.push({
119
+ recordId: recordId('task', taskId),
120
+ kind: 'task',
121
+ sourceIdentity: taskId,
122
+ sourceSha256: object.sha256,
123
+ ...(issueNumber ? { binding: `issue:${issueNumber}` } : {}),
124
+ data: {
125
+ agentInfraVersion: agentInfraVersion
126
+ ? { state: 'known', value: agentInfraVersion }
127
+ : { state: 'unknown', reason: 'legacy-task-missing-agent-infra-version' }
128
+ }
129
+ });
130
+ for (const match of object.content?.matchAll(ACTIVITY_RE) ?? []) {
131
+ const rawActor = match[3]!;
132
+ const identity = `${match[1]}:${match[2]}`;
133
+ const occurrence = nextOccurrence(activityOccurrences, identity);
134
+ records.push({
135
+ recordId: recordId('lifecycle-event', taskId, occurrence ? `${identity}\0${occurrence}` : identity),
136
+ kind: 'lifecycle-event',
137
+ sourceIdentity: `${taskId}#activity=${match[1]}`,
138
+ sourceSha256: object.sha256,
139
+ data: {
140
+ timestamp: match[1]!,
141
+ action: match[2]!,
142
+ actorRaw: rawActor,
143
+ actorCanonical: canonicalActor(rawActor),
144
+ note: match[4]!
145
+ }
146
+ });
147
+ }
148
+ for (const match of object.content?.matchAll(LEDGER_RE) ?? []) {
149
+ const findingId = match[1]!.trim();
150
+ const stage = match[2]!.trim();
151
+ const round = match[3]!.trim();
152
+ const severity = match[4]!.trim();
153
+ const status = match[5]!.trim();
154
+ const evidence = match[6]!.trim();
155
+ const identity = `${taskId}#finding=${findingId}`;
156
+ const occurrence = nextOccurrence(findingOccurrences, identity);
157
+ const data = { findingId, stage, round, severity, status, evidence };
158
+ records.push({
159
+ recordId: recordId('review-finding', identity, occurrence),
160
+ kind: 'review-finding',
161
+ sourceIdentity: identity,
162
+ sourceSha256: object.sha256,
163
+ binding: taskId,
164
+ data
165
+ });
166
+ if (status && CONFLICT_STATUSES.has(status)) {
167
+ records.push({
168
+ recordId: recordId('conflict', identity, occurrence),
169
+ kind: 'conflict',
170
+ sourceIdentity: `${identity}#status=${status}`,
171
+ sourceSha256: object.sha256,
172
+ binding: taskId,
173
+ data
174
+ });
175
+ }
176
+ }
177
+ for (const match of object.content?.matchAll(RULING_RE) ?? []) {
178
+ const rulingId = match[1]!;
179
+ const identity = `${taskId}#ruling=${rulingId}`;
180
+ records.push({
181
+ recordId: recordId('human-ruling', identity),
182
+ kind: 'human-ruling',
183
+ sourceIdentity: identity,
184
+ sourceSha256: object.sha256,
185
+ binding: taskId,
186
+ data: { rulingId, evidence: match[2]!.trim() }
187
+ });
188
+ }
189
+ continue;
190
+ }
191
+ records.push({
192
+ recordId: recordId('artifact', object.sourceIdentity),
193
+ kind: 'artifact',
194
+ sourceIdentity: object.sourceIdentity,
195
+ sourceSha256: object.sha256,
196
+ ...(taskId ? { binding: taskId } : {})
197
+ });
198
+ }
199
+ return records.sort((left, right) => left.recordId.localeCompare(right.recordId));
200
+ }
201
+
202
+ export { normalizeObjects };
@@ -0,0 +1,41 @@
1
+ import { sha256 } from './store.ts';
2
+
3
+ import type { CapturedObject, PrivacyDisposition, SourceKind } from './types.ts';
4
+
5
+ type ScannedDisposition =
6
+ | Extract<PrivacyDisposition, { state: 'included' }>
7
+ | Extract<PrivacyDisposition, { state: 'excluded-sensitive' }>;
8
+
9
+ const RULES = [
10
+ { id: 'private-key', pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
11
+ { id: 'github-token', pattern: /\b(?:gh[opusr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})\b/ },
12
+ { id: 'bearer-token', pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i },
13
+ { id: 'aws-access-key', pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/ },
14
+ { id: 'slack-token', pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/ },
15
+ { id: 'slack-webhook', pattern: /https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]{20,}/i },
16
+ { id: 'credential-field', pattern: /(?:api[_-]?key|password|secret)\s*[:=]\s*["']?[^\s"']{8,}/i }
17
+ ] as const;
18
+
19
+ function inspectPrivacy(content: string): ScannedDisposition {
20
+ for (const rule of RULES) {
21
+ if (rule.pattern.test(content)) return { state: 'excluded-sensitive', ruleId: rule.id };
22
+ }
23
+ return { state: 'included' };
24
+ }
25
+
26
+ function captureText(sourceKind: SourceKind, sourceIdentity: string, content: string): CapturedObject {
27
+ const bytes = Buffer.byteLength(content);
28
+ const digest = sha256(Buffer.from(content, 'utf8'));
29
+ const disposition = inspectPrivacy(content);
30
+ return disposition.state === 'included'
31
+ ? { sourceKind, sourceIdentity, sha256: digest, bytes, content, disposition }
32
+ : { sourceKind, sourceIdentity, sha256: digest, bytes, disposition };
33
+ }
34
+
35
+ function redactExcerpt(content: string, maxBytes = 4096): string | null {
36
+ if (inspectPrivacy(content).state !== 'included') return null;
37
+ const buffer = Buffer.from(content, 'utf8');
38
+ return buffer.subarray(0, maxBytes).toString('utf8').replace(/\b(token|password|secret)=\S+/gi, '$1=[REDACTED]');
39
+ }
40
+
41
+ export { captureText, inspectPrivacy, redactExcerpt };
@@ -0,0 +1,110 @@
1
+ import { sha256 } from './store.ts';
2
+
3
+ import type { NormalizedRecord, QualityFinding, RepairAction, SnapshotManifest } from './types.ts';
4
+
5
+ function reconcileRecords(
6
+ records: NormalizedRecord[],
7
+ scope: SnapshotManifest['scope']
8
+ ): { findings: QualityFinding[]; repairs: RepairAction[] } {
9
+ const findings: QualityFinding[] = [];
10
+ const repairs: RepairAction[] = [];
11
+ const findingKeys = new Set<string>();
12
+ const addFinding = (
13
+ category: QualityFinding['category'],
14
+ severity: QualityFinding['severity'],
15
+ identities: string[],
16
+ repairable: boolean,
17
+ reason: string
18
+ ) => {
19
+ const sortedIdentities = [...identities].sort();
20
+ const key = `${category}:${sortedIdentities.join('\0')}`;
21
+ if (findingKeys.has(key)) return;
22
+ findingKeys.add(key);
23
+ findings.push({
24
+ findingId: `quality:${sha256(key).slice(0, 20)}`,
25
+ category,
26
+ severity,
27
+ identities: sortedIdentities,
28
+ repairable,
29
+ reason
30
+ });
31
+ };
32
+ const byIdentity = new Map<string, NormalizedRecord[]>();
33
+ for (const record of records) {
34
+ const group = byIdentity.get(record.sourceIdentity) ?? [];
35
+ group.push(record);
36
+ byIdentity.set(record.sourceIdentity, group);
37
+ if (
38
+ record.kind === 'missing' &&
39
+ record.data !== null &&
40
+ typeof record.data === 'object' &&
41
+ !Array.isArray(record.data) &&
42
+ record.data.reason === 'privacy-excluded'
43
+ ) {
44
+ addFinding('privacy-excluded', 'warning', [record.sourceIdentity], false, 'Sensitive content was excluded by policy');
45
+ } else if (record.kind === 'missing') {
46
+ addFinding('unrecoverable', 'info', [record.sourceIdentity], false, 'Source body is unavailable under the capture policy');
47
+ }
48
+ }
49
+
50
+ for (const [identity, group] of byIdentity) {
51
+ if (group.length < 2) continue;
52
+ addFinding('duplicate-identity', 'error', group.map((record) => record.recordId), false, 'Multiple records claim the same stable identity');
53
+ if (group.some((record) => record.kind === 'platform-resource') && new Set(group.map((record) => record.sourceSha256)).size > 1) {
54
+ addFinding('mutable-remote', 'warning', group.map((record) => record.recordId), false, `Remote identity changed content: ${identity}`);
55
+ }
56
+ }
57
+
58
+ const tasks = records.filter((record) => record.kind === 'task');
59
+ const resources = records.filter((record) => record.kind === 'platform-resource');
60
+ const taskByIdentity = new Map(tasks.map((task) => [task.sourceIdentity, task]));
61
+
62
+ for (const task of tasks) {
63
+ const data = task.data && typeof task.data === 'object' && !Array.isArray(task.data) ? task.data : null;
64
+ const version = data?.agentInfraVersion;
65
+ if (!version || typeof version !== 'object' || Array.isArray(version) || version.state !== 'known') {
66
+ addFinding('schema-difference', 'info', [task.recordId], false, 'Task uses a legacy schema without a known agent_infra_version');
67
+ }
68
+ if (scope !== 'all' || !task.binding) continue;
69
+ const identityCandidates = resources.filter((record) => record.sourceIdentity === task.binding);
70
+ if (identityCandidates.length === 0) {
71
+ addFinding('missing-remote', 'error', [task.recordId, task.binding], false, 'Task binding has no captured platform resource');
72
+ }
73
+ if (identityCandidates.length > 1) {
74
+ addFinding('binding-conflict', 'error', [task.recordId, ...identityCandidates.map((record) => record.recordId)], false, 'Multiple platform resources bind to one task');
75
+ }
76
+ for (const remote of identityCandidates) {
77
+ if (remote.binding && remote.binding !== task.sourceIdentity) {
78
+ addFinding('content-mismatch', 'warning', [task.recordId, remote.recordId], false, 'Local and remote binding identities disagree');
79
+ }
80
+ }
81
+ const candidates = identityCandidates.filter((record) => record.binding === task.sourceIdentity);
82
+ if (candidates.length === 1 && (byIdentity.get(candidates[0]!.sourceIdentity)?.length ?? 0) === 1) {
83
+ const target = candidates[0]!;
84
+ repairs.push({
85
+ repairId: `repair:${sha256(`${task.recordId}:${target.recordId}`).slice(0, 20)}`,
86
+ operation: 'link',
87
+ sourceRecordId: task.recordId,
88
+ targetRecordId: target.recordId,
89
+ preconditionSha256: sha256(`${task.sourceSha256}:${target.sourceSha256}`)
90
+ });
91
+ }
92
+ }
93
+
94
+ for (const resource of scope === 'all' ? resources : []) {
95
+ if (resource.binding?.startsWith('TASK-') && !taskByIdentity.has(resource.binding)) {
96
+ addFinding('missing-local', 'error', [resource.recordId, resource.binding], false, 'Platform resource binds to a task that is not captured locally');
97
+ }
98
+ const localClaims = tasks.filter((task) => task.binding === resource.sourceIdentity);
99
+ if (localClaims.length > 1 || (resource.binding && localClaims.length > 0 && !localClaims.some((task) => task.sourceIdentity === resource.binding))) {
100
+ addFinding('binding-conflict', 'error', [resource.recordId, ...localClaims.map((task) => task.recordId)], false, 'Local and remote records claim incompatible bindings');
101
+ }
102
+ }
103
+
104
+ return {
105
+ findings: findings.sort((left, right) => left.findingId.localeCompare(right.findingId)),
106
+ repairs: repairs.sort((left, right) => left.repairId.localeCompare(right.repairId))
107
+ };
108
+ }
109
+
110
+ export { reconcileRecords };
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { canonicalJsonBytes, sha256, verifySnapshot } from './store.ts';
5
+
6
+ import type { RepairAction } from './types.ts';
7
+ import type { NormalizedRecord } from './types.ts';
8
+
9
+ type RepairResult = { applied: boolean; repairId: string | null; actionCount: number; path?: string };
10
+
11
+ function readRepairActions(snapshotPath: string): RepairAction[] {
12
+ const filePath = path.join(snapshotPath, 'repair-manifest.jsonl');
13
+ if (!fs.existsSync(filePath)) return [];
14
+ return fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean).map((line) => {
15
+ const { schema: _schema, ...action } = JSON.parse(line) as { schema: string } & RepairAction;
16
+ return action;
17
+ });
18
+ }
19
+
20
+ function repairSnapshot(root: string, snapshotId: string, apply: boolean): RepairResult {
21
+ const verified = verifySnapshot(root, snapshotId);
22
+ if (!verified.ok || !verified.snapshotPath) throw new Error(`snapshot verification failed: ${verified.errors.join('; ')}`);
23
+ const actions = readRepairActions(verified.snapshotPath);
24
+ const records = new Map(
25
+ fs.readFileSync(path.join(verified.snapshotPath, 'normalized.jsonl'), 'utf8').split('\n').filter(Boolean).map((line) => {
26
+ const record = JSON.parse(line) as { schema: string } & NormalizedRecord;
27
+ return [record.recordId, record] as const;
28
+ })
29
+ );
30
+ for (const action of actions) {
31
+ const source = records.get(action.sourceRecordId);
32
+ const target = records.get(action.targetRecordId);
33
+ if (!source || !target) throw new Error(`repair references a missing record: ${action.repairId}`);
34
+ if (sha256(`${source.sourceSha256}:${target.sourceSha256}`) !== action.preconditionSha256) {
35
+ throw new Error(`repair precondition failed: ${action.repairId}`);
36
+ }
37
+ }
38
+ if (!apply || actions.length === 0) return { applied: false, repairId: null, actionCount: actions.length };
39
+
40
+ const digest = sha256(canonicalJsonBytes(actions));
41
+ const repairId = `repair-${digest.slice(0, 16)}`;
42
+ const repairDir = path.join(root, 'repairs', snapshotId, repairId);
43
+ const manifest = `${canonicalJsonBytes({ schema: 'repair-application/v1', snapshotId, repairId, actionCount: actions.length, overlaySha256: digest }).toString('utf8')}\n`;
44
+ const overlay = actions.map((action) => `${canonicalJsonBytes({ schema: 'normalized-overlay/v1', ...action }).toString('utf8')}\n`).join('');
45
+ if (fs.existsSync(repairDir)) {
46
+ if (fs.readFileSync(path.join(repairDir, 'manifest.json'), 'utf8') !== manifest) throw new Error(`repair collision: ${repairId}`);
47
+ return { applied: false, repairId, actionCount: actions.length, path: repairDir };
48
+ }
49
+ fs.mkdirSync(repairDir, { recursive: true, mode: 0o700 });
50
+ fs.writeFileSync(path.join(repairDir, 'manifest.json'), manifest, { mode: 0o600, flag: 'wx' });
51
+ fs.writeFileSync(path.join(repairDir, 'overlay.jsonl'), overlay, { mode: 0o600, flag: 'wx' });
52
+ return { applied: true, repairId, actionCount: actions.length, path: repairDir };
53
+ }
54
+
55
+ function readAppliedOverlays(root: string, snapshotId: string): string {
56
+ const base = path.join(root, 'repairs', snapshotId);
57
+ if (!fs.existsSync(base)) return '';
58
+ return fs.readdirSync(base).sort().map((entry) => {
59
+ const overlay = path.join(base, entry, 'overlay.jsonl');
60
+ return fs.existsSync(overlay) ? fs.readFileSync(overlay, 'utf8') : '';
61
+ }).join('');
62
+ }
63
+
64
+ export { readAppliedOverlays, repairSnapshot };