@webpresso/agent-kit 3.1.30 → 3.2.0

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.

Potentially problematic release.


This version of @webpresso/agent-kit might be problematic. Click here for more details.

Files changed (257) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +179 -19
  3. package/catalog/AGENTS.md.tpl +3 -3
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  6. package/catalog/agent/rules/pre-implementation.md +28 -1
  7. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  8. package/catalog/agent/rules/typescript-version.md +50 -0
  9. package/catalog/agent/skills/codex/SKILL.md +25 -1
  10. package/catalog/agent/skills/verify/SKILL.md +4 -2
  11. package/catalog/docs/templates/blueprint.md +1 -1
  12. package/dist/esm/audit/agents.js +1 -0
  13. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  14. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  15. package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
  16. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  17. package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
  18. package/dist/esm/audit/ci-test-perf.js +30 -0
  19. package/dist/esm/audit/command-surface.js +4 -2
  20. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  21. package/dist/esm/audit/host-substitution-risk.js +256 -0
  22. package/dist/esm/audit/registry.d.ts +4 -4
  23. package/dist/esm/audit/registry.js +8 -1
  24. package/dist/esm/audit/toolchain-isolation.js +1 -2
  25. package/dist/esm/audit/typescript-version.d.ts +2 -0
  26. package/dist/esm/audit/typescript-version.js +123 -0
  27. package/dist/esm/blueprint/core/parser.js +43 -1
  28. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  29. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  30. package/dist/esm/blueprint/core/validation/state.js +25 -14
  31. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  32. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  33. package/dist/esm/blueprint/lifecycle/audit.js +65 -10
  34. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  35. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  36. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  37. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  38. package/dist/esm/blueprint/lifecycle/review-provenance.js +36 -11
  39. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  40. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  41. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  42. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  43. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  44. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  45. package/dist/esm/blueprint/trust/gates.js +20 -0
  46. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  47. package/dist/esm/blueprint/trust/promotion.js +62 -112
  48. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  49. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  50. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  51. package/dist/esm/blueprint/trust/validator.js +7 -1
  52. package/dist/esm/blueprint/utils/archive.js +27 -5
  53. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  54. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  55. package/dist/esm/build/cli-mcp-parity.js +10 -0
  56. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  57. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  58. package/dist/esm/cli/cli.d.ts +1 -1
  59. package/dist/esm/cli/cli.js +34 -0
  60. package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
  61. package/dist/esm/cli/commands/agent-launch.js +82 -4
  62. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  63. package/dist/esm/cli/commands/audit-core.js +8 -1
  64. package/dist/esm/cli/commands/audit.js +17 -5
  65. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  66. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  67. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  68. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  69. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  70. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  71. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  72. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  73. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  74. package/dist/esm/cli/commands/ci-preflight.js +6 -3
  75. package/dist/esm/cli/commands/compile.js +5 -31
  76. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  77. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  78. package/dist/esm/cli/commands/doctor.js +5 -0
  79. package/dist/esm/cli/commands/hook.d.ts +5 -0
  80. package/dist/esm/cli/commands/hook.js +15 -9
  81. package/dist/esm/cli/commands/init/config.js +2 -2
  82. package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
  83. package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
  84. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  85. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  86. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  87. package/dist/esm/cli/commands/init/convergence.js +57 -0
  88. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  89. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  90. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  91. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  92. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  93. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  94. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  95. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  96. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  97. package/dist/esm/cli/commands/init/index.d.ts +1 -0
  98. package/dist/esm/cli/commands/init/index.js +326 -173
  99. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  100. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  101. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  102. package/dist/esm/cli/commands/init/merge.js +30 -1
  103. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  104. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  105. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  106. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  107. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
  108. package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
  109. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  110. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  111. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  112. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
  113. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  114. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  115. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
  116. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
  117. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  118. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  119. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  120. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  121. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  122. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  123. package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
  124. package/dist/esm/cli/commands/opencode-rotate.js +135 -0
  125. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  126. package/dist/esm/cli/commands/public-readiness.js +42 -0
  127. package/dist/esm/cli/commands/review.d.ts +35 -3
  128. package/dist/esm/cli/commands/review.js +729 -209
  129. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  130. package/dist/esm/cli/commands/run-wait.js +122 -0
  131. package/dist/esm/cli/commands/setup-help.js +1 -1
  132. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  133. package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
  134. package/dist/esm/cli/direct-provider-launch.js +9 -5
  135. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  136. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  137. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  138. package/dist/esm/daemon/domains/git-actions.js +136 -0
  139. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  140. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  141. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  142. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  143. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  144. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  145. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  146. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  147. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  148. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  149. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  150. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  151. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  152. package/dist/esm/daemon/handlers/product.js +10 -119
  153. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  154. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  155. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  156. package/dist/esm/daemon/handlers/shared.js +16 -0
  157. package/dist/esm/daemon/methods.d.ts +8 -1
  158. package/dist/esm/daemon/methods.js +7 -0
  159. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  160. package/dist/esm/daemon/protocol/contract.js +26 -0
  161. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  162. package/dist/esm/daemon/protocol/index.js +1 -1
  163. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  164. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  165. package/dist/esm/hooks/conformance/batch.js +5 -0
  166. package/dist/esm/hooks/doctor.d.ts +3 -3
  167. package/dist/esm/hooks/doctor.js +30 -15
  168. package/dist/esm/hooks/permission-request/index.js +9 -3
  169. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  170. package/dist/esm/hooks/permission-request/policy.js +13 -0
  171. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  172. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  173. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  174. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  175. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  176. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  177. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  178. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  179. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  180. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  181. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  182. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  183. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  184. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  185. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  186. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  187. package/dist/esm/hooks/shared/types.d.ts +8 -5
  188. package/dist/esm/hooks/shared/types.js +23 -8
  189. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  190. package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
  191. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  192. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
  193. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  194. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  195. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  196. package/dist/esm/mcp/blueprint/registration.js +3 -2
  197. package/dist/esm/mcp/server.d.ts +1 -0
  198. package/dist/esm/mcp/server.js +27 -0
  199. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  200. package/dist/esm/mcp/tools/_names.js +5 -0
  201. package/dist/esm/mcp/tools/_registry.js +10 -0
  202. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  203. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  204. package/dist/esm/mcp/tools/audit.d.ts +4 -1
  205. package/dist/esm/mcp/tools/audit.js +30 -0
  206. package/dist/esm/mcp/tools/audits.d.ts +3 -0
  207. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  208. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  209. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  210. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  211. package/dist/esm/mcp/tools/review-run.js +216 -0
  212. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  213. package/dist/esm/mcp/tools/run-wait.js +593 -0
  214. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  215. package/dist/esm/mcp/tools/session-id.js +45 -0
  216. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  217. package/dist/esm/mcp/tools/session-info.js +49 -0
  218. package/dist/esm/output-transforms/audit.d.ts +23 -0
  219. package/dist/esm/output-transforms/audit.js +47 -0
  220. package/dist/esm/output-transforms/index.js +2 -0
  221. package/dist/esm/review/authority.js +13 -5
  222. package/dist/esm/review/availability.d.ts +22 -0
  223. package/dist/esm/review/availability.js +107 -20
  224. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  225. package/dist/esm/review/delivery-verifier.js +115 -0
  226. package/dist/esm/review/events.d.ts +3 -2
  227. package/dist/esm/review/events.js +23 -3
  228. package/dist/esm/review/execution/adapters.js +26 -9
  229. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  230. package/dist/esm/review/execution/output-policy.js +12 -0
  231. package/dist/esm/review/execution/review-checkout.js +87 -6
  232. package/dist/esm/review/execution/supervisor.js +5 -0
  233. package/dist/esm/review/execution/types.d.ts +8 -0
  234. package/dist/esm/review/opencode-account.d.ts +24 -0
  235. package/dist/esm/review/opencode-account.js +68 -0
  236. package/dist/esm/review/opencode-auth.d.ts +33 -0
  237. package/dist/esm/review/opencode-auth.js +79 -0
  238. package/dist/esm/review/subject.d.ts +12 -0
  239. package/dist/esm/review/subject.js +74 -22
  240. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  241. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  242. package/dist/esm/status/snapshot.js +30 -19
  243. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  244. package/dist/esm/test/duration-sequencer.js +54 -0
  245. package/dist/esm/test/shard-durations.json +922 -0
  246. package/dist/esm/test/worker-budget.d.ts +6 -0
  247. package/dist/esm/test/worker-budget.js +59 -1
  248. package/dist/esm/test-helpers/global-setup.d.ts +1 -1
  249. package/dist/esm/test-helpers/global-setup.js +68 -24
  250. package/dist/esm/ultragoal/service.d.ts +7 -0
  251. package/dist/esm/ultragoal/service.js +42 -2
  252. package/dist/esm/utils/write-json-file.js +3 -1
  253. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  254. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  255. package/dist/esm/worktrees/registry.d.ts +8 -0
  256. package/dist/esm/worktrees/registry.js +18 -3
  257. package/package.json +16 -13
@@ -10,6 +10,7 @@ import { resolveCurrentManagedWorktreeContext } from "#worktrees/current.js";
10
10
  import { canonicalizeWorktreePath, WORKTREE_PROBE_TIMEOUT_MS } from "#worktrees/identity.js";
11
11
  import { resolveBlueprintRoot } from "#utils/blueprint-root";
12
12
  import { getBlueprintDocumentPaths } from "#utils/document-paths.js";
13
+ import { canonicalizeBlueprint } from "#lifecycle/canonicalize.js";
13
14
  import { applyBlueprintLifecycleToFile, BlueprintCreationService, BlueprintService, complexitySchema, relativeBlueprintSlug, parseBlueprint, planStatusSchema, runBlueprintAudit, resolveBlueprintFile, serializeBlueprint, validateAllTasksDone, } from "#local";
14
15
  import { resolvePackageAssetPreferred } from "#utils/package-assets";
15
16
  import { resolveDraftAuthoringRoot } from "#worktrees/draft-authoring-root.js";
@@ -512,9 +513,19 @@ export async function finalizeBlueprintBySlug(slug, options = {}) {
512
513
  const projectRoot = resolveProjectRoot(options.projectRoot);
513
514
  return finalizeBlueprintMutation(projectRoot, slug);
514
515
  }
516
+ export async function canonicalizeBlueprintBySlug(slug, options = {}) {
517
+ const projectRoot = resolveProjectRoot(options.projectRoot);
518
+ const result = await canonicalizeBlueprint(projectRoot, slug);
519
+ return {
520
+ ...result,
521
+ message: result.moved
522
+ ? `Canonicalized blueprint ${slug}: ${result.from} -> ${result.to}`
523
+ : `Blueprint ${slug} is already canonical: ${result.to}`,
524
+ };
525
+ }
515
526
  export function registerBlueprintRouter(cli) {
516
527
  cli
517
- .command("blueprint [subcommand] [...args]", "Manage blueprints. Use: wp blueprint <action> --help for action-specific options (new, list, show, audit, exec, move, finalize, start, task)")
528
+ .command("blueprint [subcommand] [...args]", "Manage blueprints. Use: wp blueprint <action> --help for action-specific options (new, list, show, audit, exec, move, finalize, start, task, trust-dossier)")
518
529
  .option("--json", "Print JSON output")
519
530
  .option("--no-tui", "Use plain terminal output")
520
531
  .option("--complexity <complexity>", "Blueprint complexity (XS|S|M|L|XL)")
@@ -535,6 +546,7 @@ export function registerBlueprintRouter(cli) {
535
546
  await executeBlueprintSubcommand(subcommand, args, options, {
536
547
  advanceBlueprintTask,
537
548
  auditBlueprints,
549
+ canonicalizeBlueprintBySlug,
538
550
  createBlueprint,
539
551
  controlBlueprintExec,
540
552
  executeBlueprint,
@@ -31,9 +31,20 @@ export interface CiPreflightDeps {
31
31
  readonly cwd?: string;
32
32
  readonly resolveRepoRoot?: (cwd: string) => string | null;
33
33
  readonly hasNodeModules?: (repoRoot: string) => boolean;
34
- readonly runCheck?: (check: CiPreflightCheck, repoRoot: string) => number;
34
+ readonly runCheck?: (check: CiPreflightCheck, repoRoot: string) => number | Promise<number>;
35
35
  readonly stdout?: Pick<typeof process.stdout, "write">;
36
36
  readonly stderr?: Pick<typeof process.stderr, "write">;
37
37
  }
38
- export declare function runCiPreflight(deps?: CiPreflightDeps): number;
38
+ /**
39
+ * Re-invoke the same `wp` runtime that is currently executing so each check
40
+ * runs the real verb (faithful CI parity) rather than a reimplemented copy of
41
+ * its affected-resolution logic. Prefers the repo-local `bin/wp` launcher when
42
+ * present so the exact source/compiled selection and NODE_PATH wiring apply;
43
+ * falls back to the running runtime for consumer repos with a global `wp`.
44
+ */
45
+ export declare function resolveWpLauncher(repoRoot: string): {
46
+ readonly command: string;
47
+ readonly prefixArgs: readonly string[];
48
+ };
49
+ export declare function runCiPreflight(deps?: CiPreflightDeps): Promise<number>;
39
50
  export declare function registerCiPreflightCommand(cli: CAC): void;
@@ -57,7 +57,10 @@ function reproduceLine(check) {
57
57
  * present so the exact source/compiled selection and NODE_PATH wiring apply;
58
58
  * falls back to the running runtime for consumer repos with a global `wp`.
59
59
  */
60
- function resolveWpLauncher(repoRoot) {
60
+ // Exported so `src/mcp/tools/ci-preflight.ts` can re-invoke the exact same
61
+ // `wp` launcher when it substitutes an output-capturing `runCheck` for the
62
+ // default `stdio: "inherit"` spawn — do not duplicate this resolution logic.
63
+ export function resolveWpLauncher(repoRoot) {
61
64
  const localLauncher = path.join(repoRoot, "bin", process.platform === "win32" ? "wp.cmd" : "wp");
62
65
  if (existsSync(localLauncher))
63
66
  return { command: localLauncher, prefixArgs: [] };
@@ -82,7 +85,7 @@ function defaultRunCheck(check, repoRoot) {
82
85
  }
83
86
  return typeof result.status === "number" ? result.status : 1;
84
87
  }
85
- export function runCiPreflight(deps = {}) {
88
+ export async function runCiPreflight(deps = {}) {
86
89
  const cwd = deps.cwd ?? process.cwd();
87
90
  const stdout = deps.stdout ?? process.stdout;
88
91
  const stderr = deps.stderr ?? process.stderr;
@@ -100,7 +103,7 @@ export function runCiPreflight(deps = {}) {
100
103
  }
101
104
  for (const check of CI_PREFLIGHT_CHECKS) {
102
105
  stdout.write(`\nwp ci-preflight: running ${check.label} (${reproduceLine(check)})\n`);
103
- const exitCode = runCheck(check, repoRoot);
106
+ const exitCode = await runCheck(check, repoRoot);
104
107
  if (exitCode !== 0) {
105
108
  const normalizedExit = exitCode === 0 ? 1 : exitCode;
106
109
  stderr.write(`\nwp ci-preflight: ${check.label} failed (exit ${normalizedExit}).\n` +
@@ -247,22 +247,10 @@ export async function runCompile(options) {
247
247
  message: `wp compile: rulesync exited with code ${exitCode}`,
248
248
  };
249
249
  }
250
- // Step 4: Emit plugin manifests in parallel
251
- const skillNames = Object.keys(assets.skills);
252
- const commandNames = Object.keys(assets.commands);
253
- const pkgVersion = readPackageVersion(cwd);
254
- const manifestOpts = {
255
- agentDir,
256
- outDir: cwd,
257
- version: pkgVersion,
258
- skills: skillNames,
259
- commands: commandNames,
260
- };
261
- await Promise.all([
262
- import("#compiler/manifests/claude").then((m) => m.emitManifest(manifestOpts)),
263
- import("#compiler/manifests/codex").then((m) => m.emitManifest(manifestOpts)),
264
- ]);
265
- // Step 5: Run mergeAgentsMd to produce AGENTS.md at repo root
250
+ // Step 4: Run mergeAgentsMd to produce AGENTS.md at repo root
251
+ // (Plugin manifests are hand-maintained SSOT under packages/*-plugin/;
252
+ // wp compile no longer emits them — golden tests + version stamping own
253
+ // that surface.)
266
254
  const agentsLayers = collectAgentsLayers(agentDir);
267
255
  if (agentsLayers.length > 0) {
268
256
  const { mergeAgentsMd } = await import("#compiler/memory/merger");
@@ -278,11 +266,9 @@ export async function runCompile(options) {
278
266
  writeFileSync(join(cwd, "AGENTS.md"), mergeResult.content);
279
267
  }
280
268
  }
281
- // Step 6: Write .compile-manifest.json with content-hash sentinels
269
+ // Step 5: Write .compile-manifest.json with content-hash sentinels
282
270
  const outputHashes = {
283
271
  "AGENTS.md": hashFile(join(cwd, "AGENTS.md")),
284
- "packages/claude-plugin/.claude-plugin/plugin.json": hashFile(join(cwd, "packages", "claude-plugin", ".claude-plugin", "plugin.json")),
285
- "packages/codex-plugin/.codex-plugin/plugin.json": hashFile(join(cwd, "packages", "codex-plugin", ".codex-plugin", "plugin.json")),
286
272
  };
287
273
  const compileManifest = {
288
274
  version: COMPILE_MANIFEST_VERSION,
@@ -303,18 +289,6 @@ export async function runCompile(options) {
303
289
  cleanup();
304
290
  }
305
291
  }
306
- function readPackageVersion(cwd) {
307
- const pkgPath = join(cwd, "package.json");
308
- if (!existsSync(pkgPath))
309
- return "0.0.0";
310
- try {
311
- const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
312
- return typeof parsed.version === "string" ? parsed.version : "0.0.0";
313
- }
314
- catch {
315
- return "0.0.0";
316
- }
317
- }
318
292
  function collectAgentsLayers(agentDir) {
319
293
  const agentsDir = join(agentDir, "agents");
320
294
  if (!existsSync(agentsDir))
@@ -56,3 +56,4 @@ export type DeliveryStatusOptions = {
56
56
  * prior values and expose a retryable adapter warning.
57
57
  */
58
58
  export declare function collectDeliveryStatus(options?: DeliveryStatusOptions): Promise<DeliveryStatus>;
59
+ export declare function blueprintDocumentPath(cwd: string, state: string, slug: string): string | null;
@@ -107,14 +107,21 @@ function checkState(value) {
107
107
  return "pending";
108
108
  return states.length > 0 ? "passing" : "unknown";
109
109
  }
110
+ export function blueprintDocumentPath(cwd, state, slug) {
111
+ const folder = join(cwd, "blueprints", state, slug, "_overview.md");
112
+ if (existsSync(folder))
113
+ return folder;
114
+ const flat = join(cwd, "blueprints", state, `${slug}.md`);
115
+ return existsSync(flat) ? flat : null;
116
+ }
110
117
  function readBlueprintDelivery(cwd) {
111
118
  const branch = run("git", ["branch", "--show-current"], cwd);
112
119
  if (!branch?.startsWith("bp/"))
113
120
  return null;
114
121
  const slug = branch.slice(3);
115
122
  for (const state of ["in-progress", "planned", "draft", "completed", "parked", "archived"]) {
116
- const path = join(cwd, "blueprints", state, slug, "_overview.md");
117
- if (!existsSync(path))
123
+ const path = blueprintDocumentPath(cwd, state, slug);
124
+ if (!path)
118
125
  continue;
119
126
  const raw = readFileSync(path, "utf8");
120
127
  return {
@@ -129,8 +136,8 @@ function readVerificationDelivery(cwd) {
129
136
  const blueprint = readBlueprintDelivery(cwd);
130
137
  if (!blueprint)
131
138
  return null;
132
- const path = join(cwd, "blueprints", blueprint.state, blueprint.slug, "_overview.md");
133
- if (!existsSync(path))
139
+ const path = blueprintDocumentPath(cwd, blueprint.state, blueprint.slug);
140
+ if (!path)
134
141
  return null;
135
142
  const raw = readFileSync(path, "utf8");
136
143
  const evidenceCount = (raw.match(/^\*\*Verification:\*\*/gmu) ?? []).length;
@@ -1,4 +1,5 @@
1
1
  import { auditBlueprintLifecycleSql } from "#audit/blueprint-lifecycle-sql";
2
+ import { auditCiGuardrailsWiring } from "#audit/ci-guardrails-wiring";
2
3
  import { readConfig } from "#cli/commands/init/config";
3
4
  import { auditCatalogDrift, auditDocsFrontmatter, formatRepoAuditReport, } from "#audit/repo-guardrails";
4
5
  import { countWorktreeLifecycleScripts } from "#worktrees/lifecycle-scripts.js";
@@ -7,6 +8,9 @@ const REMEDIATIONS = {
7
8
  "Catalog drift — single package (no workspace file)": "none needed",
8
9
  "Docs frontmatter": "wp audit docs-frontmatter --fix",
9
10
  "Blueprint lifecycle": "wp audit blueprint-lifecycle",
11
+ // Name the missing invocation explicitly so an operator sees which CI step to
12
+ // restore, not just a generic "drifted" signal.
13
+ "CI guardrails wiring": "restore the `wp audit guardrails` CI step (run `wp setup`)",
10
14
  };
11
15
  export async function runDoctor(options = {}) {
12
16
  try {
@@ -17,6 +21,7 @@ export async function runDoctor(options = {}) {
17
21
  docsRoot: options.docsRoot,
18
22
  fix: options.fix,
19
23
  }),
24
+ auditCiGuardrailsWiring(root),
20
25
  await auditBlueprintLifecycleSql(root, { includeRuntimePlans: options.runtimePlans }),
21
26
  ];
22
27
  let failed = false;
@@ -1,4 +1,9 @@
1
1
  import type { CAC } from "cac";
2
+ /**
3
+ * Registered GUARD_* codes emitted by the hook-command fail-closed path.
4
+ * Consumed by the guard-outcome registry-completeness test.
5
+ */
6
+ export declare const HOOK_FALLBACK_GUARD_CODES: readonly ["GUARD_PRETOOL_FAILURE"];
2
7
  declare const HOOK_NAMES: readonly ["pretool-guard", "post-tool", "stop-qa", "stop-ultragoal", "guard-switch", "sessionstart-routing", "precompact-snapshot", "permission-request", "test-quality-check"];
3
8
  export type HookName = (typeof HOOK_NAMES)[number];
4
9
  export declare function isHookName(value: string): value is HookName;
@@ -2,8 +2,16 @@ import { performance } from "node:perf_hooks";
2
2
  import { reportHookDegradation, takeHookDegradation, } from "#hooks/errors/index.js";
3
3
  import { recordHookInvocation, summarizeHookText, takeRememberedHookStdin, } from "#hooks/logs/store.js";
4
4
  import { createSyntheticProcessExit, isSyntheticProcessExit, } from "#hooks/shared/synthetic-process-exit.js";
5
- import { writeHookOutput } from "#hooks/shared/hook-bootstrap";
5
+ import { writeGuardStderr, writeHookOutput } from "#hooks/shared/hook-bootstrap";
6
+ import { formatGuardReason } from "#hooks/shared/guard-outcome.js";
6
7
  import { buildDenyEnvelope } from "#hooks/shared/types.js";
8
+ /**
9
+ * Registered GUARD_* codes emitted by the hook-command fail-closed path.
10
+ * Consumed by the guard-outcome registry-completeness test.
11
+ */
12
+ export const HOOK_FALLBACK_GUARD_CODES = [
13
+ "GUARD_PRETOOL_FAILURE",
14
+ ];
7
15
  import { resolveHookProjectDirFromText } from "#hooks/shared/project-dir.js";
8
16
  import { createManagedHookExecutionContext, } from "#hooks/shared/session-memory-deadline.js";
9
17
  const HOOK_NAMES = [
@@ -101,13 +109,9 @@ function exitCodeFrom(value) {
101
109
  }
102
110
  function writePretoolDeny(reason) {
103
111
  writeHookOutput(`${JSON.stringify(buildDenyEnvelope({ reason }))}\n`);
104
- // Best-effort stderr reason for Claude/Codex exit-2 UX (may be suppressed in some runners).
105
- try {
106
- process.stderr.write(`${reason}\n`);
107
- }
108
- catch {
109
- // ignore
110
- }
112
+ // Deliver to the REAL stderr too: Claude reads stderr on an exit-2 PreToolUse
113
+ // deny, and it is suppressed for decorative output.
114
+ writeGuardStderr(`${reason}\n`);
111
115
  }
112
116
  function emitFallback(name, error) {
113
117
  const metadata = HOOK_METADATA[name];
@@ -121,7 +125,9 @@ function emitFallback(name, error) {
121
125
  detail,
122
126
  });
123
127
  if (metadata.fallback === "fail-closed-deny") {
124
- writePretoolDeny(`webpresso pretool guard failed: ${detail}`);
128
+ // Coded fail-closed denial so the host still sees a stable GUARD_ code even
129
+ // when the guard handler itself threw.
130
+ writePretoolDeny(formatGuardReason("GUARD_PRETOOL_FAILURE", `webpresso pretool guard failed: ${detail}`));
125
131
  // Exit 2 so Grok (and exit-code runners) treat fail-closed as an explicit deny.
126
132
  return 2;
127
133
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { isAbsolute, join, normalize } from "node:path";
7
- import { REQUIRED_CORE_CAPABILITIES } from "./host-visibility.js";
7
+ import { AGENT_HOSTS, REQUIRED_CORE_CAPABILITIES } from "./host-visibility.js";
8
8
  export const CONFIG_VERSION = "1";
9
9
  export const CONFIG_FILENAME = ".webpressorc.json";
10
10
  export const LEGACY_CONFIG_FILENAME = ".agent-kitrc.json";
@@ -136,7 +136,7 @@ function parseConfigFile(path) {
136
136
  }
137
137
  : undefined;
138
138
  const selectedHosts = Array.isArray(hosts?.selected)
139
- ? hosts.selected.filter((s) => ["codex", "claude", "opencode"].includes(String(s)))
139
+ ? hosts.selected.filter((s) => AGENT_HOSTS.includes(String(s)))
140
140
  : [];
141
141
  const requiredCapabilities = Array.isArray(hosts?.requiredCapabilities)
142
142
  ? hosts.requiredCapabilities.filter((s) => typeof s === "string")
@@ -0,0 +1,24 @@
1
+ import { type ConvergeUnit } from "./convergence.js";
2
+ export { resolveUntrackedCollision } from "./untracked-collision.js";
3
+ export type AuthorCommitStatus = "committed" | "no-change" | "skipped-ci" | "failed" | "rolled-back";
4
+ export interface AuthorCommitResult {
5
+ readonly status: AuthorCommitStatus;
6
+ readonly ok: boolean;
7
+ readonly commit?: string;
8
+ readonly allowlist: readonly string[];
9
+ readonly unexpectedPaths?: readonly string[];
10
+ readonly missingPaths?: readonly string[];
11
+ readonly error?: string;
12
+ }
13
+ export interface AuthorCommitOptions {
14
+ readonly repoRoot: string;
15
+ readonly appliedUnits: readonly ConvergeUnit[];
16
+ readonly message: string;
17
+ readonly isCi?: boolean;
18
+ }
19
+ export interface AppliedConvergenceCandidate {
20
+ readonly targetPath: string;
21
+ readonly reason: string;
22
+ }
23
+ export declare function collectAppliedConvergeUnits(repoRoot: string, candidates: readonly AppliedConvergenceCandidate[]): ConvergeUnit[];
24
+ export declare function authorConvergenceCommit(options: AuthorCommitOptions): AuthorCommitResult;
@@ -0,0 +1,221 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { convergeUnitFromTarget, deriveCommitAllowlist } from "./convergence.js";
4
+ export { resolveUntrackedCollision } from "./untracked-collision.js";
5
+ const GIT_TIMEOUT_MS = 15_000;
6
+ function git(repoRoot, args) {
7
+ const result = spawnSync("git", [...args], {
8
+ cwd: repoRoot,
9
+ encoding: "utf8",
10
+ timeout: GIT_TIMEOUT_MS,
11
+ maxBuffer: 1024 * 1024,
12
+ stdio: ["ignore", "pipe", "pipe"],
13
+ });
14
+ return {
15
+ ok: result.status === 0 && !result.error,
16
+ stdout: result.stdout.trim(),
17
+ stderr: result.stderr.trim(),
18
+ };
19
+ }
20
+ function lines(value) {
21
+ return value.length === 0 ? [] : value.split("\n").filter(Boolean).sort();
22
+ }
23
+ function readPorcelainStatus(repoRoot, gitPath) {
24
+ const result = spawnSync("git", [
25
+ "status",
26
+ "--porcelain=v1",
27
+ "-z",
28
+ "--untracked-files=all",
29
+ "--ignored=matching",
30
+ "--",
31
+ gitPath,
32
+ ], {
33
+ cwd: repoRoot,
34
+ encoding: "utf8",
35
+ timeout: GIT_TIMEOUT_MS,
36
+ maxBuffer: 1024 * 1024,
37
+ stdio: ["ignore", "pipe", "pipe"],
38
+ });
39
+ if (result.status !== 0 || result.error)
40
+ return null;
41
+ const fields = result.stdout.split("\0");
42
+ const entries = [];
43
+ for (let index = 0; index < fields.length; index += 1) {
44
+ const record = fields[index];
45
+ if (!record || record.length < 4)
46
+ continue;
47
+ const code = record.slice(0, 2);
48
+ entries.push({ code, gitPath: record.slice(3) });
49
+ if (code.includes("R") || code.includes("C"))
50
+ index += 1;
51
+ }
52
+ return entries;
53
+ }
54
+ function isIgnored(repoRoot, gitPath) {
55
+ return git(repoRoot, ["check-ignore", "--no-index", "-q", "--", gitPath]).ok;
56
+ }
57
+ export function collectAppliedConvergeUnits(repoRoot, candidates) {
58
+ const units = [];
59
+ const seen = new Set();
60
+ for (const candidate of candidates) {
61
+ let candidateUnit;
62
+ try {
63
+ candidateUnit = convergeUnitFromTarget(repoRoot, candidate.targetPath, {
64
+ action: "modify",
65
+ scope: "repo-committed",
66
+ reason: candidate.reason,
67
+ });
68
+ }
69
+ catch {
70
+ continue;
71
+ }
72
+ const statuses = readPorcelainStatus(repoRoot, candidateUnit.gitPath);
73
+ if (!statuses)
74
+ continue;
75
+ for (const { code, gitPath } of statuses) {
76
+ if (seen.has(gitPath))
77
+ continue;
78
+ let unit;
79
+ try {
80
+ unit = convergeUnitFromTarget(repoRoot, join(repoRoot, gitPath), {
81
+ action: code === "??" || code === "!!" || code.includes("A")
82
+ ? "create"
83
+ : code.includes("D")
84
+ ? "delete"
85
+ : "modify",
86
+ scope: "repo-committed",
87
+ reason: candidate.reason,
88
+ });
89
+ }
90
+ catch {
91
+ continue;
92
+ }
93
+ units.push({
94
+ ...unit,
95
+ scope: code === "!!" || (code === "??" && isIgnored(repoRoot, gitPath))
96
+ ? "repo-ignored"
97
+ : "repo-committed",
98
+ });
99
+ seen.add(gitPath);
100
+ }
101
+ }
102
+ return units;
103
+ }
104
+ export function authorConvergenceCommit(options) {
105
+ const allowlist = deriveCommitAllowlist(options.appliedUnits);
106
+ if (options.isCi)
107
+ return { status: "skipped-ci", ok: true, allowlist };
108
+ if (allowlist.length === 0)
109
+ return { status: "no-change", ok: true, allowlist };
110
+ const before = git(options.repoRoot, ["rev-parse", "--verify", "HEAD"]);
111
+ if (!before.ok) {
112
+ return { status: "failed", ok: false, allowlist, error: before.stderr || "Missing HEAD." };
113
+ }
114
+ const add = git(options.repoRoot, ["add", "--", ...allowlist]);
115
+ if (!add.ok) {
116
+ return { status: "failed", ok: false, allowlist, error: add.stderr || "git add failed." };
117
+ }
118
+ const staged = git(options.repoRoot, ["diff", "--cached", "--name-only"]);
119
+ if (!staged.ok) {
120
+ return { status: "failed", ok: false, allowlist, error: staged.stderr || "git diff failed." };
121
+ }
122
+ if (lines(staged.stdout).length === 0) {
123
+ return { status: "no-change", ok: true, allowlist };
124
+ }
125
+ const commit = git(options.repoRoot, ["commit", "-m", options.message]);
126
+ if (!commit.ok) {
127
+ return {
128
+ status: "failed",
129
+ ok: false,
130
+ allowlist,
131
+ error: commit.stderr || commit.stdout || "git commit failed; hook changes were preserved.",
132
+ };
133
+ }
134
+ const committedHead = git(options.repoRoot, ["rev-parse", "--verify", "HEAD"]);
135
+ if (!committedHead.ok) {
136
+ return {
137
+ status: "failed",
138
+ ok: false,
139
+ allowlist,
140
+ error: committedHead.stderr || "Unable to validate the authored commit.",
141
+ };
142
+ }
143
+ const committedParent = git(options.repoRoot, [
144
+ "rev-parse",
145
+ "--verify",
146
+ `${committedHead.stdout}^`,
147
+ ]);
148
+ if (!committedParent.ok || committedParent.stdout !== before.stdout) {
149
+ return {
150
+ status: "failed",
151
+ ok: false,
152
+ allowlist,
153
+ commit: committedHead.stdout,
154
+ error: "The observed commit does not directly follow the recorded HEAD; no rollback was attempted. Inspect Git history and recover manually.",
155
+ };
156
+ }
157
+ const committedPathsResult = git(options.repoRoot, [
158
+ "diff-tree",
159
+ "--root",
160
+ "--no-commit-id",
161
+ "--name-only",
162
+ "-r",
163
+ committedHead.stdout,
164
+ ]);
165
+ if (!committedPathsResult.ok) {
166
+ return {
167
+ status: "failed",
168
+ ok: false,
169
+ allowlist,
170
+ commit: committedHead.stdout,
171
+ error: committedPathsResult.stderr || "Unable to inspect the authored commit.",
172
+ };
173
+ }
174
+ const committedPaths = lines(committedPathsResult.stdout);
175
+ const allowed = new Set(allowlist);
176
+ const committed = new Set(committedPaths);
177
+ const unexpectedPaths = committedPaths.filter((path) => !allowed.has(path));
178
+ const missingPaths = allowlist.filter((path) => !committed.has(path));
179
+ if (unexpectedPaths.length === 0 && missingPaths.length === 0) {
180
+ return {
181
+ status: "committed",
182
+ ok: true,
183
+ allowlist,
184
+ commit: committedHead.stdout,
185
+ };
186
+ }
187
+ const currentHead = git(options.repoRoot, ["rev-parse", "--verify", "HEAD"]);
188
+ if (!currentHead.ok || currentHead.stdout !== committedHead.stdout) {
189
+ return {
190
+ status: "failed",
191
+ ok: false,
192
+ allowlist,
193
+ commit: committedHead.stdout,
194
+ unexpectedPaths,
195
+ missingPaths,
196
+ error: "The setup-authored commit changed before validation; no rollback was attempted. Inspect Git history and recover manually.",
197
+ };
198
+ }
199
+ const rollback = git(options.repoRoot, ["reset", "--mixed", before.stdout]);
200
+ if (!rollback.ok) {
201
+ return {
202
+ status: "failed",
203
+ ok: false,
204
+ allowlist,
205
+ commit: committedHead.stdout,
206
+ unexpectedPaths,
207
+ missingPaths,
208
+ error: rollback.stderr || "Failed to undo the path-invalid setup commit.",
209
+ };
210
+ }
211
+ return {
212
+ status: "rolled-back",
213
+ ok: false,
214
+ allowlist,
215
+ commit: committedHead.stdout,
216
+ unexpectedPaths,
217
+ missingPaths,
218
+ error: `A commit hook changed setup commit scope (unexpected: ${unexpectedPaths.join(", ") || "none"}; missing: ${missingPaths.join(", ") || "none"}). ` +
219
+ "The setup commit was undone and all worktree changes were retained. Update the hook so it does not stage unrelated paths, then stage or discard those paths and rerun wp setup.",
220
+ };
221
+ }
@@ -0,0 +1,13 @@
1
+ export declare const CONVERGENCE_STATE_SCHEMA_VERSION: 1;
2
+ export declare const CONVERGENCE_STATE_PATH = ".webpresso/converged.json";
3
+ export interface ConvergenceState {
4
+ readonly schemaVersion: typeof CONVERGENCE_STATE_SCHEMA_VERSION;
5
+ readonly agentKitVersion: string;
6
+ readonly host: string;
7
+ readonly sentinels: Readonly<Record<string, string>>;
8
+ readonly writtenAt: string;
9
+ }
10
+ export declare function parseConvergenceState(value: unknown): ConvergenceState | null;
11
+ export declare function readConvergenceState(repoRoot: string): ConvergenceState | null;
12
+ export declare function isConvergenceStateCurrent(state: ConvergenceState | null, expected: Pick<ConvergenceState, "agentKitVersion" | "host" | "sentinels">): boolean;
13
+ export declare function writeConvergenceState(repoRoot: string, state: ConvergenceState): void;
@@ -0,0 +1,54 @@
1
+ import { mkdirSync, readFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { writeJsonFile } from "#shared-utils/write-json-file.js";
4
+ export const CONVERGENCE_STATE_SCHEMA_VERSION = 1;
5
+ export const CONVERGENCE_STATE_PATH = ".webpresso/converged.json";
6
+ function isStringRecord(value) {
7
+ return (value !== null &&
8
+ typeof value === "object" &&
9
+ !Array.isArray(value) &&
10
+ Object.values(value).every((entry) => typeof entry === "string"));
11
+ }
12
+ export function parseConvergenceState(value) {
13
+ if (value === null || typeof value !== "object" || Array.isArray(value))
14
+ return null;
15
+ const record = value;
16
+ if (record.schemaVersion !== CONVERGENCE_STATE_SCHEMA_VERSION ||
17
+ typeof record.agentKitVersion !== "string" ||
18
+ typeof record.host !== "string" ||
19
+ typeof record.writtenAt !== "string" ||
20
+ !isStringRecord(record.sentinels)) {
21
+ return null;
22
+ }
23
+ return {
24
+ schemaVersion: CONVERGENCE_STATE_SCHEMA_VERSION,
25
+ agentKitVersion: record.agentKitVersion,
26
+ host: record.host,
27
+ sentinels: record.sentinels,
28
+ writtenAt: record.writtenAt,
29
+ };
30
+ }
31
+ export function readConvergenceState(repoRoot) {
32
+ try {
33
+ return parseConvergenceState(JSON.parse(readFileSync(join(resolve(repoRoot), CONVERGENCE_STATE_PATH), "utf8")));
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ export function isConvergenceStateCurrent(state, expected) {
40
+ if (!state ||
41
+ state.agentKitVersion !== expected.agentKitVersion ||
42
+ state.host !== expected.host) {
43
+ return false;
44
+ }
45
+ return Object.entries(expected.sentinels).every(([name, value]) => state.sentinels[name] === value);
46
+ }
47
+ export function writeConvergenceState(repoRoot, state) {
48
+ const statePath = join(resolve(repoRoot), CONVERGENCE_STATE_PATH);
49
+ mkdirSync(dirname(statePath), { recursive: true });
50
+ writeJsonFile(statePath, state, {
51
+ atomic: true,
52
+ indent: 2,
53
+ });
54
+ }
@@ -0,0 +1,23 @@
1
+ import type { WriteAction } from "./write-plan.js";
2
+ export declare const CONVERGE_SCOPES: readonly ["repo-committed", "repo-ignored"];
3
+ export type ConvergeScope = (typeof CONVERGE_SCOPES)[number];
4
+ export interface ConvergeUnit {
5
+ readonly repoRoot: string;
6
+ readonly targetPath: string;
7
+ readonly gitPath: string;
8
+ readonly action: WriteAction;
9
+ readonly scope: ConvergeScope;
10
+ readonly reason: string;
11
+ }
12
+ export interface CreateConvergeUnitInput {
13
+ readonly repoRoot: string;
14
+ readonly targetPath: string;
15
+ readonly gitPath: string;
16
+ readonly action: WriteAction;
17
+ readonly scope: ConvergeScope;
18
+ readonly reason: string;
19
+ }
20
+ export declare function createConvergeUnit(input: CreateConvergeUnitInput): ConvergeUnit;
21
+ export declare function convergeUnitFromTarget(repoRoot: string, targetPath: string, input: Pick<CreateConvergeUnitInput, "action" | "scope" | "reason">): ConvergeUnit;
22
+ /** Applied units are the sole authority for authored commit scope. */
23
+ export declare function deriveCommitAllowlist(appliedUnits: readonly ConvergeUnit[]): string[];