@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/bin/doflow.js +2 -2
  2. package/core/.antigravity-plugin/plugin.json +0 -1
  3. package/core/.claude-plugin/plugin.json +1 -1
  4. package/core/.codex-plugin/plugin.json +1 -1
  5. package/core/.plugin/plugin.json +1 -1
  6. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  7. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  8. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  9. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  10. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  11. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  12. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  13. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  14. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  15. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  16. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  17. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  18. package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
  19. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  20. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
  21. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
  22. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
  23. package/core/harnesses/gemini/hooks/session-start.sh +5 -3
  24. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
  25. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
  26. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
  27. package/core/harnesses/kiro/hooks/session-start.sh +11 -5
  28. package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
  29. package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
  30. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
  31. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  32. package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
  33. package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
  34. package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
  35. package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
  36. package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
  37. package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
  38. package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
  39. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
  40. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
  41. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
  42. package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
  43. package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
  44. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
  45. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
  46. package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
  47. package/core/registry/assets.json +19 -0
  48. package/core/registry/contracts.json +2 -2
  49. package/core/registry/harnesses.json +196 -10
  50. package/core/registry/lifecycle.json +2626 -7
  51. package/core/registry/storage.json +110 -83
  52. package/core/registry/verification.json +1 -1
  53. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
  54. package/core/shared/scripts/doflow/bin/doflow-run +10 -6
  55. package/core/shared/skills/do-design/SKILL.md +6 -6
  56. package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
  57. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
  58. package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
  59. package/core/shared/skills/do-implement/SKILL.md +3 -2
  60. package/core/shared/skills/do-plan/SKILL.md +7 -8
  61. package/package.json +4 -2
  62. package/src/adapters/antigravity/{index.ts → index.js} +141 -105
  63. package/src/adapters/claude/{index.ts → index.js} +63 -49
  64. package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
  65. package/src/adapters/codex/{agents.ts → agents.js} +24 -24
  66. package/src/adapters/codex/{config.ts → config.js} +49 -49
  67. package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
  68. package/src/adapters/codex/{index.ts → index.js} +148 -105
  69. package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
  70. package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
  71. package/src/adapters/copilot/{index.ts → index.js} +42 -42
  72. package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
  73. package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
  74. package/src/adapters/gemini/{index.ts → index.js} +96 -57
  75. package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
  76. package/src/adapters/{index.ts → index.js} +13 -13
  77. package/src/adapters/kiro/{index.ts → index.js} +49 -35
  78. package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
  79. package/src/adapters/opencode/{index.ts → index.js} +52 -52
  80. package/src/adapters/pi/hooks.js +119 -0
  81. package/src/adapters/pi/{index.ts → index.js} +64 -39
  82. package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
  83. package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
  84. package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
  85. package/src/cli/commands/{install.ts → install.js} +25 -25
  86. package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
  87. package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
  88. package/src/cli/commands/{remove.ts → remove.js} +10 -10
  89. package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
  90. package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
  91. package/src/cli/commands/{status.ts → status.js} +24 -24
  92. package/src/cli/commands/{tools.ts → tools.js} +16 -16
  93. package/src/cli/commands/{update.ts → update.js} +20 -20
  94. package/src/cli/{index.ts → index.js} +43 -31
  95. package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
  96. package/src/cli/{shared.ts → shared.js} +40 -40
  97. package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
  98. package/src/helper/{git.ts → git.js} +2 -2
  99. package/src/helper/{guardrails.ts → guardrails.js} +10 -10
  100. package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
  101. package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
  102. package/src/helper/{prompt.ts → prompt.js} +5 -5
  103. package/src/helper/{repo-root.ts → repo-root.js} +2 -2
  104. package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
  105. package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
  106. package/src/helper/{toml.ts → toml.js} +48 -48
  107. package/src/install/{backup-set.ts → backup-set.js} +16 -16
  108. package/src/install/{backup.ts → backup.js} +65 -65
  109. package/src/install/{context.ts → context.js} +15 -15
  110. package/src/install/{manifest.ts → manifest.js} +27 -27
  111. package/src/install/{mcp.ts → mcp.js} +30 -30
  112. package/src/install/{paths.ts → paths.js} +15 -15
  113. package/src/install/{targets.ts → targets.js} +10 -10
  114. package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
  115. package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
  116. package/src/lifecycle/coverage.js +57 -0
  117. package/src/lifecycle/hook-step.js +61 -0
  118. package/src/lifecycle/{index.ts → index.js} +160 -146
  119. package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
  120. package/src/lifecycle/{policies.ts → policies.js} +11 -11
  121. package/src/lifecycle/{view.ts → view.js} +28 -28
  122. package/src/registry/{index.ts → index.js} +195 -66
  123. package/src/release/build-package.js +91 -0
  124. package/src/release/changelog-section.js +70 -0
  125. package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
  126. package/src/runtime/advice/{cli.ts → cli.js} +31 -31
  127. package/src/runtime/advice/{client.ts → client.js} +44 -44
  128. package/src/runtime/advice/{config.ts → config.js} +16 -16
  129. package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
  130. package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
  131. package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
  132. package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
  133. package/src/runtime/advice/{record.ts → record.js} +19 -19
  134. package/src/runtime/advice/{registry.ts → registry.js} +16 -16
  135. package/src/runtime/advice/{strength.ts → strength.js} +7 -7
  136. package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
  137. package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
  138. package/src/runtime/candidate.js +505 -0
  139. package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
  140. package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
  141. package/src/runtime/{claims.ts → claims.js} +44 -35
  142. package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
  143. package/src/runtime/{cli.ts → cli.js} +59 -292
  144. package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
  145. package/src/runtime/context/budget.js +203 -0
  146. package/src/runtime/context/compose.js +1367 -0
  147. package/src/runtime/context/delivery-store.js +102 -0
  148. package/src/runtime/context/mandatory.js +594 -0
  149. package/src/runtime/context/pointer.js +142 -0
  150. package/src/runtime/context-pack.js +1277 -0
  151. package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
  152. package/src/runtime/evidence-fields.js +39 -0
  153. package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
  154. package/src/runtime/failure/{capture.ts → capture.js} +29 -29
  155. package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
  156. package/src/runtime/failure/{cli.ts → cli.js} +30 -30
  157. package/src/runtime/failure/{home.ts → home.js} +6 -6
  158. package/src/runtime/failure/{store.ts → store.js} +32 -32
  159. package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
  160. package/src/runtime/{freshness.ts → freshness.js} +59 -22
  161. package/src/runtime/{health.ts → health.js} +72 -72
  162. package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
  163. package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
  164. package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
  165. package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
  166. package/src/runtime/inventory/{index.ts → index.js} +36 -36
  167. package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
  168. package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
  169. package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
  170. package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
  171. package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
  172. package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
  173. package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
  174. package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
  175. package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
  176. package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
  177. package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
  178. package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
  179. package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
  180. package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
  181. package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
  182. package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
  183. package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
  184. package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
  185. package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
  186. package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
  187. package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
  188. package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
  189. package/src/runtime/{mask.ts → mask.js} +22 -22
  190. package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
  191. package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
  192. package/src/runtime/{model-router.ts → model-router.js} +20 -20
  193. package/src/runtime/{outcome.ts → outcome.js} +27 -27
  194. package/src/runtime/{reach.ts → reach.js} +21 -21
  195. package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
  196. package/src/runtime/{readiness.ts → readiness.js} +113 -43
  197. package/src/runtime/{recovery.ts → recovery.js} +15 -15
  198. package/src/runtime/{research-request.ts → research-request.js} +34 -34
  199. package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
  200. package/src/runtime/retrieval-record.js +223 -0
  201. package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
  202. package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
  203. package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
  204. package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
  205. package/src/runtime/source-version.js +289 -0
  206. package/src/runtime/storage/{cli.ts → cli.js} +9 -9
  207. package/src/runtime/storage/{engine.ts → engine.js} +37 -37
  208. package/src/runtime/storage/{registry.ts → registry.js} +19 -19
  209. package/src/runtime/storage/{roots.ts → roots.js} +8 -8
  210. package/src/runtime/storage/{rules.ts → rules.js} +26 -26
  211. package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
  212. package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
  213. package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
  214. package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
  215. package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
  216. package/src/runtime/{task-state.ts → task-state.js} +86 -21
  217. package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
  218. package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
  219. package/src/runtime/trace/{render.ts → render.js} +7 -7
  220. package/src/runtime/trace/{views.ts → views.js} +40 -40
  221. package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
  222. package/src/runtime/verification/{engine.ts → engine.js} +143 -143
  223. package/src/runtime/verification/{registry.ts → registry.js} +6 -6
  224. package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
  225. package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
  226. package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
  227. package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
  228. package/src/runtime/{worktree.ts → worktree.js} +31 -31
  229. package/src/state/{index.ts → index.js} +26 -26
  230. package/src/state/{lockfile.ts → lockfile.js} +20 -20
  231. package/src/state/{run-lock.ts → run-lock.js} +44 -44
  232. package/core/.antigravity-plugin/hooks.json +0 -39
  233. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  234. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  235. package/core/harnesses/shared/hooks/hooks.json +0 -47
  236. package/src/runtime/context-pack.ts +0 -492
@@ -153,3 +153,5 @@ checklist it is. Do not halt indefinitely on a persistent `NEEDS_EVIDENCE` — r
153
153
  actually established, state which requirements it covers, and say what remains. And do not write
154
154
  `READY` yourself. The gate did not say it, and saying it on the gate's behalf is precisely the
155
155
  failure this page exists to prevent.
156
+
157
+ Record an absence with `--absence --need <intent>`; a positive item never states `--absence`.
@@ -56,7 +56,7 @@ Two rules deserve their reasoning, because both are easy to "improve" into a def
56
56
  | | Part 1 — in-scope | Part 2 — external dependency |
57
57
  |---|---|---|
58
58
  | Input | `requirement.md`, `design.md`, `plan.md` | a `depends-on:` value with no owning task |
59
- | Produced by | `src/runtime/scaffold/generate.ts`, deterministically | this file's algorithm, executed by you |
59
+ | Produced by | `src/runtime/scaffold/generate.js`, deterministically | this file's algorithm, executed by you |
60
60
  | Lands in | `scaffold/src/`, `scaffold/test/`, `scaffold/MANIFEST.md` | `scaffold/contracts/<service>/` |
61
61
  | Answers | what shape does my own plan imply | what shape must I code against at the boundary |
62
62
 
@@ -391,7 +391,7 @@ this Part; `scaffold/contracts/<service>/` is the only place any of it may write
391
391
  ## Constraints (carried from the design — do not relax these)
392
392
 
393
393
  The first applies to both Parts. Every other constraint here is Part 2's, and its bare step numbers
394
- are Part 2's steps — Part 1's equivalents are enforced by `src/runtime/scaffold/generate.ts` and its guard
394
+ are Part 2's steps — Part 1's equivalents are enforced by `src/runtime/scaffold/generate.js` and its guard
395
395
  rather than restated as prose.
396
396
 
397
397
  - Never write outside `agent-docs/doflow/<slug>/scaffold/` — not into the source tree, and not
@@ -111,8 +111,9 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
111
111
  "$DOFLOW" context-pack --task-id "<task id>" --json
112
112
  ```
113
113
  This verb exits 1 on an empty pack by design — nothing recorded is not the same as nothing
114
- needed — and that non-zero exit is never read as success. Because this call only runs after the
115
- evidence check above already found something recorded for this task id, an exit 1 here means
114
+ needed — and that non-zero exit is never read as success; exit 3 means overflow: the pack is
115
+ complete, act on `overflowAction.message` and report the gap. Because this call only runs after
116
+ the evidence check above already found something recorded for this task id, an exit 1 here means
116
117
  the compiled pack still held nothing usable (evidence with no linked claims, most likely):
117
118
  treat that as a mismatch worth naming in step 6's report, not as license to proceed as though
118
119
  there were no prior context to look for. On a normal, non-empty pack, carry its evidence and
@@ -67,14 +67,13 @@ not have.
67
67
  ```bash
68
68
  "$DOFLOW" context-pack --task-id "<task id>" --json
69
69
  ```
70
- **Exit 1 means the pack came back empty.** By this point `do-design` should already have batched
71
- its own stage-boundary evidence and claims (its step 8), so an empty pack here carries more
72
- weight than it does at design time: either that batch never ran, or this task id doesn't match
73
- the one design used. Treat it as advisory rather than blocking — plan's hard gate is artifact
74
- existence only — but say so plainly in this stage's report, and in `plan.md`'s own §3 "Research &
75
- Decisions": a decision written straight from `requirement.md`/`design.md` prose, with no compiled
76
- pack behind it, must not be recorded as if it traced to prior evidence it doesn't have. When the
77
- pack is non-empty, read it alongside `requirement.md`, `design.md`, and the constitution.
70
+ **Exit 1 means the pack came back empty; exit 3 means overflow: the pack is complete, act on
71
+ `overflowAction.message` and report the gap.** By this point `do-design` should have batched
72
+ evidence and claims (step 8), so an empty pack carries weight: either the batch never ran or the
73
+ task id mismatched. Treat it as advisory rather than blocking — plan's hard gate is artifact
74
+ existence only — but note it in this stage's report and `plan.md` §3 "Research & Decisions": a
75
+ decision written straight from prose without a compiled pack must not claim prior evidence. When
76
+ non-empty, read it alongside `requirement.md`, `design.md`, and the constitution.
78
77
  **Also read `specs.md` when step 1's `has_specs` is true** — use that flag, never a filesystem
79
78
  check of your own, and read the `specs` path the resolver returned. `/do-design` moves the
80
79
  interface and data-model contracts out of `design.md` into `specs.md` §1 as numbered `IC-###`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khoavu882/doflow",
3
- "version": "2.0.0-beta.3",
3
+ "version": "2.0.0-beta.5",
4
4
  "description": "DoFlow config installer — npx CLI for the registry-driven multi-harness install/update/remove lifecycle",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -31,9 +31,11 @@
31
31
  "scripts": {
32
32
  "test": "node --test \"test/**/*.test.js\"",
33
33
  "typecheck": "npx -y -p typescript@5.7.2 tsc -p tsconfig.json",
34
+ "build:package": "node src/release/build-package.js .pack",
34
35
  "bench": "node bench/runner.js",
35
36
  "drift": "node scripts/check-format-drift.js",
36
- "version": "node src/release/sync-plugin-versions.ts && git add core/.codex-plugin/plugin.json core/.claude-plugin/plugin.json core/.plugin/plugin.json"
37
+ "release-notes": "node src/release/changelog-section.js",
38
+ "version": "node src/release/sync-plugin-versions.js && git add core/.codex-plugin/plugin.json core/.claude-plugin/plugin.json core/.plugin/plugin.json"
37
39
  },
38
40
  "files": [
39
41
  "bin/",
@@ -20,10 +20,10 @@ const fs = require('node:fs');
20
20
  const os = require('node:os');
21
21
  const path = require('node:path');
22
22
 
23
- const { MARKER_START, MARKER_END } = require('../../helper/marker-merge.ts');
24
- const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor } = require('../copy-tree.ts');
25
- const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.ts');
26
- const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.ts');
23
+ const { MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
24
+ const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor } = require('../copy-tree.js');
25
+ const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
26
+ const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
27
27
 
28
28
  const HARNESS = 'antigravity';
29
29
  const MCP_CONTAINER = 'mcpServers';
@@ -31,7 +31,7 @@ const MCP_RENDERER = 'antigravity-mcp';
31
31
 
32
32
  /** copy-tree's readJson distinguishes absent/unparseable via an {exists,value,error} envelope;
33
33
  * every caller here wants the plain object or {}. */
34
- function readJsonObject(file: any, { fsImpl = fs }: any = {}) {
34
+ function readJsonObject(file , { fsImpl = fs } = {}) {
35
35
  const result = readJson(file, { fsImpl });
36
36
  if (!result || !result.exists || result.error) return {};
37
37
  return result.value ?? {};
@@ -54,20 +54,20 @@ const GATE_MATCHER = 'write_to_file|replace_file_content|multi_replace_file_cont
54
54
  /** Build DoFlow's hooks.json groups from the projected scripts' on-disk locations. Absolute command
55
55
  * path: the docs' own examples use relative ./scripts paths whose resolution base is not stated,
56
56
  * and an absolute path is unambiguous for a per-workspace install. */
57
- function hookGroups({ gateCommand, stopCommand }: any) {
57
+ function hookGroups({ gateCommand, stopCommand } ) {
58
58
  return {
59
59
  [HOOKS_GROUP]: { PreToolUse: [{ matcher: GATE_MATCHER, hooks: [{ type: 'command', command: gateCommand }] }] },
60
60
  [STOP_HOOKS_GROUP]: { Stop: [{ type: 'command', command: stopCommand }] },
61
61
  };
62
62
  }
63
63
 
64
- function hookScriptTarget(identity: any, paths: any) {
64
+ function hookScriptTarget(identity , paths ) {
65
65
  return identity === 'pre-implementation-gate.sh' ? paths.hookScript : paths.stopHookScript;
66
66
  }
67
67
 
68
- function planHooks({ paths, scope, neutralResources, removing, repoRoot, fsImpl = fs }: any) {
69
- const changes: any[] = [];
70
- const conflicts: any[] = [];
68
+ function planHooks({ paths, scope, neutralResources, removing, repoRoot, fsImpl = fs } ) {
69
+ const changes = [];
70
+ const conflicts = [];
71
71
  if (scope !== 'project') {
72
72
  // The shims resolve the workspace via git/transcriptPath; a user-scope hooks.json would fire
73
73
  // against every project including non-git ones. Project-only until a documented user story exists.
@@ -76,9 +76,9 @@ function planHooks({ paths, scope, neutralResources, removing, repoRoot, fsImpl
76
76
  const target = paths.hooksJson;
77
77
  const scriptSource = sourceDirFor({ source: 'core/harnesses/antigravity/hooks' }, { repoRoot }, fsImpl, HARNESS);
78
78
 
79
- const previousHookRows = (neutralResources || []).filter((r: any) => r.harness === HARNESS && r.assetId === HOOKS_ASSET_ID && r.kind === 'hooks-json');
80
- const previousScriptRows = (neutralResources || []).filter((r: any) => r.harness === HARNESS && r.assetId === HOOKS_ASSET_ID && r.kind === 'copy-tree-file');
81
- const docRow = previousHookRows.find((r: any) => r.target === target);
79
+ const previousHookRows = (neutralResources || []).filter((r ) => r.harness === HARNESS && r.assetId === HOOKS_ASSET_ID && r.kind === 'hooks-json');
80
+ const previousScriptRows = (neutralResources || []).filter((r ) => r.harness === HARNESS && r.assetId === HOOKS_ASSET_ID && r.kind === 'copy-tree-file');
81
+ const docRow = previousHookRows.find((r ) => r.target === target);
82
82
 
83
83
  if (removing) {
84
84
  if (docRow || fsImpl.existsSync(target)) {
@@ -89,7 +89,7 @@ function planHooks({ paths, scope, neutralResources, removing, repoRoot, fsImpl
89
89
  }
90
90
  for (const identity of HOOK_SCRIPTS) {
91
91
  const scriptTarget = hookScriptTarget(identity, paths);
92
- const scriptRow = previousScriptRows.find((r: any) => r.identity === identity);
92
+ const scriptRow = previousScriptRows.find((r ) => r.identity === identity);
93
93
  if (scriptRow && fsImpl.existsSync(scriptTarget)) {
94
94
  const current = sha256File(fsImpl, scriptTarget);
95
95
  if (current === scriptRow.fingerprint) {
@@ -130,7 +130,7 @@ function planHooks({ paths, scope, neutralResources, removing, repoRoot, fsImpl
130
130
  return { changes, conflicts };
131
131
  }
132
132
 
133
- function sha256File(fsImpl: any, file: any) {
133
+ function sha256File(fsImpl , file ) {
134
134
  return require('node:crypto').createHash('sha256').update(fsImpl.readFileSync(file)).digest('hex');
135
135
  }
136
136
 
@@ -138,7 +138,7 @@ function sha256File(fsImpl: any, file: any) {
138
138
  * ("paths"): project customization lives at <root>/.agents, global config at ~/.gemini/config
139
139
  * (the shared-customization root); instructions and skills are project-only surfaces, so no user
140
140
  * rule is declared for them and they resolve to null there. */
141
- function resolveNativePaths(declaredPaths: any, { scope, scopeRoot, homeDir }: any = {}) {
141
+ function resolveNativePaths(declaredPaths , { scope, scopeRoot, homeDir } = {}) {
142
142
  if (scope !== 'project' && scope !== 'global') throw new Error(`Unsupported Antigravity scope '${scope}'`);
143
143
  const resolved = resolveHarnessPaths(declaredPaths, { scope, scopeRoot, homeDir });
144
144
  return {
@@ -168,33 +168,35 @@ function nativePaths(options = {}) {
168
168
  * keeps its planners module-level (they already thread resolved `paths` explicitly), so injection
169
169
  * flows through an optional `impl.nativePaths` seam instead of a full closure rewrite.
170
170
  */
171
- function createAntigravityAdapter({ declaredPaths = DEFAULT_DECLARED_PATHS }: any = {}) {
172
- const nativePathsFor = (options: any) => resolveNativePaths(declaredPaths, options);
171
+ function createAntigravityAdapter({ declaredPaths = DEFAULT_DECLARED_PATHS } = {}) {
172
+ const nativePathsFor = (options ) => resolveNativePaths(declaredPaths, options);
173
173
  return {
174
174
  nativePaths: nativePathsFor,
175
- discover: (options: any, impl = {}) => discover(options, { ...impl, nativePaths: nativePathsFor }),
175
+ discover: (options , impl = {}) => discover(options, { ...impl, nativePaths: nativePathsFor }),
176
176
  render,
177
- plan: (options: any, impl = {}) => plan(options, { ...impl, nativePaths: nativePathsFor }),
177
+ plan: (options , impl = {}) => plan(options, { ...impl, nativePaths: nativePathsFor }),
178
178
  apply,
179
179
  remove,
180
- verify: (options: any, impl = {}) => verify(options, { ...impl, nativePaths: nativePathsFor }),
180
+ verify: (options , impl = {}) => verify(options, { ...impl, nativePaths: nativePathsFor }),
181
+ planHookDoors: (options , impl = {}) => planHookDoors(options, { ...impl, nativePaths: nativePathsFor }),
182
+ verifyHookDoors: (options , impl = {}) => verifyHookDoors(options, { ...impl, nativePaths: nativePathsFor }),
181
183
  };
182
184
  }
183
185
 
184
186
  const singleton = createAntigravityAdapter();
185
187
 
186
188
  /** The scope's paths exactly as plan() and verify() resolve them, so discovery reads the same files. */
187
- function scopePaths(options: any, resolvePaths = singleton.nativePaths) {
189
+ function scopePaths(options , resolvePaths = singleton.nativePaths) {
188
190
  return resolvePaths({ ...options, scope: options.scope ?? 'project', homeDir: options.context?.homeDir });
189
191
  }
190
192
 
191
- function ledgerResources(options: any) {
193
+ function ledgerResources(options ) {
192
194
  return options.ledger?.resources ?? options.managedResources ?? [];
193
195
  }
194
196
 
195
197
  /** Discovery keeps the MCP files as plan read them (`mcpSnapshot`), which verify compares against,
196
198
  * and reports the servers DoFlow owns in mcp_config.json now (`mcpOwned`). */
197
- function discover(options: any, { fsImpl = fs, nativePaths: resolvePaths = singleton.nativePaths }: any = {}) {
199
+ function discover(options , { fsImpl = fs, nativePaths: resolvePaths = singleton.nativePaths } = {}) {
198
200
  const paths = scopePaths(options, resolvePaths);
199
201
  const instruction = paths.instruction && fsImpl.existsSync(paths.instruction)
200
202
  ? fsImpl.readFileSync(paths.instruction, 'utf8') : null;
@@ -206,11 +208,11 @@ function discover(options: any, { fsImpl = fs, nativePaths: resolvePaths = singl
206
208
  return { paths, instruction, mcpSnapshot, mcpOwned: ownedMcpIds({ ...entries, files: mcpSnapshot }), mcpConfigured };
207
209
  }
208
210
 
209
- function render({ content = '' }: any = {}) {
211
+ function render({ content = '' } = {}) {
210
212
  return `${MARKER_START}\n${String(content).trimEnd()}\n${MARKER_END}\n`;
211
213
  }
212
214
 
213
- function managedInstruction(existing: any, rendered: any) {
215
+ function managedInstruction(existing , rendered ) {
214
216
  if (existing === null) return { ok: true, operation: 'create', content: rendered };
215
217
  const start = existing.indexOf(MARKER_START);
216
218
  const end = existing.indexOf(MARKER_END);
@@ -224,7 +226,7 @@ function managedInstruction(existing: any, rendered: any) {
224
226
  return { ok: true, operation: content === existing ? 'none' : 'merge', content };
225
227
  }
226
228
 
227
- function strippedInstruction(existing: any) {
229
+ function strippedInstruction(existing ) {
228
230
  if (typeof existing !== 'string') return null;
229
231
  const start = existing.indexOf(MARKER_START);
230
232
  const end = existing.indexOf(MARKER_END);
@@ -239,7 +241,7 @@ function strippedInstruction(existing: any) {
239
241
  /** Scope policy mirrors the registry notes, resolved from each asset's own flat nativeDir:
240
242
  * ../x or .agents/x or .doflow/x → rooted at the install root (project scope)
241
243
  * plain names (bin, agents) → inside the scope's config dir (.agents | ~/.gemini/config) */
242
- function treeDestFor(asset: any, paths: any, scope: any) {
244
+ function treeDestFor(asset , paths , scope ) {
243
245
  const nativeDir = asset.nativeDir;
244
246
  if (!nativeDir) return null;
245
247
  if (asset.id === 'skills.doflow') {
@@ -263,7 +265,7 @@ function treeDestFor(asset: any, paths: any, scope: any) {
263
265
  return path.join(paths.configDir, nativeDir);
264
266
  }
265
267
 
266
- function planTrees({ assets, paths, scope, neutralResources, removing, repoRoot, force = false, fsImpl = fs }: any) {
268
+ function planTrees({ assets, paths, scope, neutralResources, removing, repoRoot, force = false, fsImpl = fs } ) {
267
269
  const changes = [];
268
270
  const conflicts = [];
269
271
  const treeResults = [];
@@ -285,10 +287,10 @@ function planTrees({ assets, paths, scope, neutralResources, removing, repoRoot,
285
287
  // planTrees as well, since its signature did not carry the context the others already had.
286
288
  force: !removing && force === true });
287
289
  treeResults.push(result);
288
- conflicts.push(...result.conflicts.map((reason: any) => `${asset.id}: ${reason}`));
290
+ conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
289
291
  // A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
290
292
  // ownership row is released.
291
- notices.push(...result.kept.map((item: any) => `kept hand-edited ${path.relative(paths.root, item.target)}; DoFlow no longer manages it`));
293
+ notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(paths.root, item.target)}; DoFlow no longer manages it`));
292
294
  for (const change of result.changes) {
293
295
  changes.push({
294
296
  assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
@@ -303,15 +305,15 @@ function planTrees({ assets, paths, scope, neutralResources, removing, repoRoot,
303
305
  return { changes, conflicts, targets, notices: [...siblingReplacedNotices(treeResults), ...notices] };
304
306
  }
305
307
 
306
- function runTreeChanges(changes: any, mode: any) {
308
+ function runTreeChanges(changes , mode ) {
307
309
  // Route by what each change IS, not by which verb invoked us: remove() delegates here with a
308
310
  // plan full of operation:'remove' changes, and applyTree deliberately skips those — so routing
309
311
  // everything through one engine call silently deleted nothing (verification then correctly
310
312
  // refused to journal the no-op). Writes go to applyTree, removals to removeTree, always.
311
313
  void mode;
312
314
  const treeChanges = changes
313
- .filter((c: any) => c.projection?.renderer === 'copy-tree' || c.kind === 'copy-tree-file')
314
- .map((c: any) => ({ relPath: c.identity ?? c.relPath, target: c.target,
315
+ .filter((c ) => c.projection?.renderer === 'copy-tree' || c.kind === 'copy-tree-file')
316
+ .map((c ) => ({ relPath: c.identity ?? c.relPath, target: c.target,
315
317
  source: c._text ? undefined : c.source, operation: c.operation,
316
318
  fingerprint: c.fingerprint, pruneGuard: c.pruneGuard, ...(c._text ? { _text: c._text } : {}) }));
317
319
  // Content-managed tree files (hook scripts): write rendered text with +x, not a byte copy.
@@ -324,17 +326,17 @@ function runTreeChanges(changes: any, mode: any) {
324
326
  }
325
327
  return {
326
328
  applied: 0, removed: 0,
327
- ...(function (): any {
328
- const plain = treeChanges.filter((t: any) => t._text === undefined);
329
- const writes = plain.filter((c: any) => c.operation !== 'remove');
330
- const removals = plain.filter((c: any) => c.operation === 'remove');
329
+ ...(function () {
330
+ const plain = treeChanges.filter((t ) => t._text === undefined);
331
+ const writes = plain.filter((c ) => c.operation !== 'remove');
332
+ const removals = plain.filter((c ) => c.operation === 'remove');
331
333
  const applied = writes.length ? applyTree({ changes: writes }).applied : 0;
332
334
  const removed = removals.length ? removeTree({ changes: removals }).removed : 0;
333
335
  return { applied, removed };
334
336
  })(),
335
337
  };
336
- const writes = treeChanges.filter((c: any) => c.operation !== 'remove');
337
- const removals = treeChanges.filter((c: any) => c.operation === 'remove');
338
+ const writes = treeChanges.filter((c ) => c.operation !== 'remove');
339
+ const removals = treeChanges.filter((c ) => c.operation === 'remove');
338
340
  const applied = writes.length ? applyTree({ changes: writes }).applied : 0;
339
341
  const removed = removals.length ? removeTree({ changes: removals }).removed : 0;
340
342
  return { applied, removed };
@@ -342,7 +344,7 @@ function runTreeChanges(changes: any, mode: any) {
342
344
 
343
345
  // ---- instructions component ----
344
346
 
345
- function planInstructions({ paths, assets, removing, repoRoot, fsImpl = fs }: any) {
347
+ function planInstructions({ paths, assets, removing, repoRoot, fsImpl = fs } ) {
346
348
  if (scopeOf(paths) !== 'project') return { changes: [], conflicts: [] };
347
349
  void repoRoot;
348
350
  if (removing) {
@@ -380,7 +382,7 @@ function planInstructions({ paths, assets, removing, repoRoot, fsImpl = fs }: an
380
382
  };
381
383
  }
382
384
 
383
- function pointerBody(fsImpl: any) {
385
+ function pointerBody(fsImpl ) {
384
386
  // The shared pointer prose ships once; read it through the same repo-relative anchor the other
385
387
  // AGENTS-style adapters use.
386
388
  try {
@@ -388,11 +390,11 @@ function pointerBody(fsImpl: any) {
388
390
  } catch { return null; }
389
391
  }
390
392
 
391
- function scopeOf(paths: any) { return paths.scope; }
393
+ function scopeOf(paths ) { return paths.scope; }
392
394
 
393
395
  // ---- MCP component ----
394
396
 
395
- function mcpDefinitionFor(server: any) {
397
+ function mcpDefinitionFor(server ) {
396
398
  // Remote transports project url/httpUrl -> serverUrl per the current Antigravity schema.
397
399
  if (server.transport === 'stdio') {
398
400
  return { command: server.command, ...(server.args?.length ? { args: server.args } : {}) };
@@ -400,27 +402,27 @@ function mcpDefinitionFor(server: any) {
400
402
  return { serverUrl: server.url ?? server.httpUrl };
401
403
  }
402
404
 
403
- function ownershipIdentity(id: any) { return `doflow:${HARNESS}:mcp-server:${id}`; }
405
+ function ownershipIdentity(id ) { return `doflow:${HARNESS}:mcp-server:${id}`; }
404
406
 
405
407
  /** Everything the shared entry-ownership rules (../mcp-entries.ts) need from this adapter. Rows
406
408
  * recorded before entries were fingerprinted carry `fingerprint: null`; such a row owns its entry
407
409
  * only while the entry still equals DoFlow's rendering. */
408
- function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], resources = [] }: any) {
409
- const rendered = (servers: any) => servers.map((server: any) => ({ id: server.id, entry: mcpDefinitionFor(server) }));
410
- const rows = resources.filter((row: any) => row.kind === 'mcp-server');
410
+ function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], resources = [] } ) {
411
+ const rendered = (servers ) => servers.map((server ) => ({ id: server.id, entry: mcpDefinitionFor(server) }));
412
+ const rows = resources.filter((row ) => row.kind === 'mcp-server');
411
413
  return {
412
414
  file: paths.mcpFile, selected: rendered(mcp), adoptable: rendered(mcpAdoptable),
413
- ownRows: rows.filter((row: any) => row.harness === HARNESS).map((row: any) => ({
415
+ ownRows: rows.filter((row ) => row.harness === HARNESS).map((row ) => ({
414
416
  identity: row.identity, target: row.target, fingerprint: row.fingerprint ?? null, ownershipIdentity: row.ownershipIdentity, legacy: false,
415
417
  })),
416
- foreignRows: rows.filter((row: any) => row.harness !== HARNESS).map((row: any) => ({ harness: row.harness, identity: row.identity, target: row.target })),
418
+ foreignRows: rows.filter((row ) => row.harness !== HARNESS).map((row ) => ({ harness: row.harness, identity: row.identity, target: row.target })),
417
419
  identityFor: ownershipIdentity, assetId: POINTER_ASSET_ID, renderer: MCP_RENDERER, label: 'Antigravity MCP',
418
420
  };
419
421
  }
420
422
 
421
423
  // ---- required six-function surface ----
422
424
 
423
- function plan(options: any = {}, impl: any = {}) {
425
+ function plan(options = {}, impl = {}) {
424
426
  const fsImpl = impl.fsImpl || fs;
425
427
  const context = options.context ?? {};
426
428
  const scope = options.scope ?? 'project';
@@ -449,7 +451,30 @@ function plan(options: any = {}, impl: any = {}) {
449
451
  };
450
452
  }
451
453
 
452
- function apply(options: any = {}, impl: any = {}) {
454
+ /** The hook front doors (IC-037): the shims and the hooks.json groups that run them, planned and
455
+ * verified alone. Project scope only, and only when the harness selected the hooks asset. */
456
+ function planHookDoors(options = {}, impl = {}) {
457
+ const fsImpl = impl.fsImpl || fs;
458
+ const context = options.context ?? {};
459
+ const scope = options.scope ?? 'project';
460
+ if (!(options.assets ?? []).some((asset ) => asset.id === HOOKS_ASSET_ID)) return { changes: [], conflicts: [], prerequisites: [], notices: [] };
461
+ const paths = (impl.nativePaths ?? singleton.nativePaths)({ ...options, scope, homeDir: context.homeDir });
462
+ const doors = planHooks({ paths, scope, neutralResources: ledgerResources(options), removing: false, repoRoot: context.repoRoot, fsImpl });
463
+ return { changes: doors.changes, conflicts: doors.conflicts, prerequisites: [], notices: [] };
464
+ }
465
+
466
+ function verifyHookDoors(options = {}, impl = {}) {
467
+ const fsImpl = impl.fsImpl || fs;
468
+ const context = options.context ?? {};
469
+ const scope = options.scope ?? 'project';
470
+ if (scope !== 'project' || !(options.assets ?? []).some((asset ) => asset.id === HOOKS_ASSET_ID)) return { ok: true, statuses: [], resources: [], conflicts: [] };
471
+ const paths = (impl.nativePaths ?? singleton.nativePaths)({ ...options, scope, homeDir: context.homeDir });
472
+ const doors = verifyHooks({ options, paths, context, fsImpl });
473
+ const conflicts = doors.statuses.filter((status ) => status.status === 'conflict').map((status ) => status.reason ?? status.identity);
474
+ return { ok: conflicts.length === 0, ...doors, conflicts };
475
+ }
476
+
477
+ function apply(options = {}, impl = {}) {
453
478
  const fsImpl = impl.fsImpl || fs;
454
479
  // apply()/verify() are reached through the lifecycle with the same scope inputs plan() saw, so
455
480
  // they re-derive paths instead of trusting plan-private state. `paths` itself is unused here —
@@ -458,7 +483,7 @@ function apply(options: any = {}, impl: any = {}) {
458
483
  (impl.nativePaths ?? singleton.nativePaths)(options);
459
484
  const changes = options.changes ?? [];
460
485
 
461
- for (const change of changes.filter((c: any) => c.projection?.renderer === 'antigravity-instructions')) {
486
+ for (const change of changes.filter((c ) => c.projection?.renderer === 'antigravity-instructions')) {
462
487
  if (change.operation === 'remove') {
463
488
  // pi precedent: strip only the managed span; the file survives even when nothing remains.
464
489
  if (!fsImpl.existsSync(change.target)) continue;
@@ -469,7 +494,7 @@ function apply(options: any = {}, impl: any = {}) {
469
494
  fsImpl.mkdirSync(path.dirname(change.target), { recursive: true });
470
495
  fsImpl.writeFileSync(change.target, change._content ?? render({ content: '' }), 'utf8');
471
496
  }
472
- for (const change of changes.filter((c: any) => c.projection?.renderer === 'antigravity-hooks')) {
497
+ for (const change of changes.filter((c ) => c.projection?.renderer === 'antigravity-hooks')) {
473
498
  if (change.kind === 'copy-tree-file') continue; // handled by runTreeChanges below
474
499
  if (change.operation === 'remove') {
475
500
  // Unmerge only DoFlow's groups (one per policy); a user's own hook groups survive.
@@ -502,11 +527,65 @@ function apply(options: any = {}, impl: any = {}) {
502
527
  // Removal flows through apply(): plan(context.operation='remove') emits operation:'remove'
503
528
  // changes, and the branches above already know how to execute each kind. The six-function
504
529
  // contract still requires the verb itself.
505
- function remove(options: any = {}, impl: any = {}) {
530
+ function remove(options = {}, impl = {}) {
506
531
  return apply(options, impl);
507
532
  }
508
533
 
509
- function verify(options: any = {}, impl: any = {}) {
534
+ /** The hook shims' bytes and the registered groups (project scope), as statuses and resources. */
535
+ function verifyHooks({ options, paths, context, fsImpl } ) {
536
+ const statuses = [];
537
+ const resources = [];
538
+ const hooksTarget = paths.hooksJson;
539
+ const scriptTargets = {
540
+ 'pre-implementation-gate.sh': paths.hookScript,
541
+ 'stop-check.sh': paths.stopHookScript,
542
+ };
543
+ // Source-side residue: reads the shims from THIS checkout (the authored source of the
544
+ // projection, not an install destination — the destination is declared as the paths above).
545
+ const scriptSourceDir = path.join(path.resolve(registryRepoRoot(options)), 'core', 'harnesses', 'antigravity', 'hooks');
546
+ const removingOp = (context.operation ?? options.operation ?? '') === 'remove';
547
+ const fingerprints = {};
548
+ let scriptsOk = true;
549
+ let anyPresent = false;
550
+ for (const identity of HOOK_SCRIPTS) {
551
+ let sourceFp = null;
552
+ try { sourceFp = sha256File(fsImpl, path.join(scriptSourceDir, identity)); } catch { /* checkout without that shim */ }
553
+ fingerprints[identity] = sourceFp;
554
+ const target = scriptTargets[identity];
555
+ const current = fsImpl.existsSync(target) ? sha256File(fsImpl, target) : null;
556
+ if (current !== null) anyPresent = true;
557
+ if (current === null || current !== sourceFp) scriptsOk = false;
558
+ }
559
+ const doc = readJsonObject(hooksTarget, { fsImpl });
560
+ const gate = doc?.[HOOKS_GROUP];
561
+ const stop = doc?.[STOP_HOOKS_GROUP];
562
+ const gateOk = Boolean(gate?.PreToolUse?.[0]?.hooks?.[0]?.command)
563
+ && gate.PreToolUse[0].hooks[0].command === scriptTargets['pre-implementation-gate.sh']
564
+ && gate.PreToolUse[0].matcher === GATE_MATCHER;
565
+ // Stop registration is matcher-free: handlers sit directly under the event key.
566
+ const stopOk = Array.isArray(stop?.Stop) && stop.Stop.length > 0
567
+ && stop.Stop[0]?.command === scriptTargets['stop-check.sh'];
568
+ const groupsOk = gateOk && stopOk;
569
+ statuses.push({ assetId: HOOKS_ASSET_ID, capability: 'hooks', status:
570
+ removingOp ? (!anyPresent && !groupsOk ? 'absent' : 'retained')
571
+ : (scriptsOk && groupsOk ? 'managed' : (!anyPresent && !groupsOk ? 'absent' : 'missing')),
572
+ ownershipIdentity: `${HARNESS}:hooks:registration`, target: hooksTarget });
573
+ if (!removingOp && scriptsOk && groupsOk) {
574
+ resources.push({ assetId: HOOKS_ASSET_ID, target: hooksTarget,
575
+ ownershipIdentity: `${HARNESS}:hooks:registration`, kind: 'hooks-json',
576
+ fingerprint: fingerprint(JSON.stringify(doc)), sourceVersion: 'registry-v1',
577
+ projection: { renderer: 'antigravity-hooks' } });
578
+ for (const identity of HOOK_SCRIPTS) {
579
+ resources.push({ assetId: HOOKS_ASSET_ID, target: scriptTargets[identity],
580
+ ownershipIdentity: `${HARNESS}:copy-tree:${HOOKS_ASSET_ID}:${identity}`,
581
+ kind: 'copy-tree-file', identity, fingerprint: fingerprints[identity],
582
+ sourceVersion: 'registry-v1', projection: { renderer: 'antigravity-hooks' } });
583
+ }
584
+ }
585
+ return { statuses, resources };
586
+ }
587
+
588
+ function verify(options = {}, impl = {}) {
510
589
  const fsImpl = impl.fsImpl || fs;
511
590
  const context = options.context ?? {};
512
591
  const scope = options.scope ?? 'project';
@@ -564,68 +643,25 @@ function verify(options: any = {}, impl: any = {}) {
564
643
 
565
644
  // Hooks projection (project scope): the shims' bytes and the registered groups.
566
645
  if (scope === 'project') {
567
- const hooksTarget = paths.hooksJson;
568
- const scriptTargets: Record<string, any> = {
569
- 'pre-implementation-gate.sh': paths.hookScript,
570
- 'stop-check.sh': paths.stopHookScript,
571
- };
572
- // Source-side residue: reads the shims from THIS checkout (the authored source of the
573
- // projection, not an install destination — the destination is declared as the paths above).
574
- const scriptSourceDir = path.join(path.resolve(registryRepoRoot(options)), 'core', 'harnesses', 'antigravity', 'hooks');
575
- const removingOp = (context.operation ?? options.operation ?? '') === 'remove';
576
- const fingerprints: Record<string, any> = {};
577
- let scriptsOk = true;
578
- let anyPresent = false;
579
- for (const identity of HOOK_SCRIPTS) {
580
- let sourceFp = null;
581
- try { sourceFp = sha256File(fsImpl, path.join(scriptSourceDir, identity)); } catch { /* checkout without that shim */ }
582
- fingerprints[identity] = sourceFp;
583
- const target = scriptTargets[identity];
584
- const current = fsImpl.existsSync(target) ? sha256File(fsImpl, target) : null;
585
- if (current !== null) anyPresent = true;
586
- if (current === null || current !== sourceFp) scriptsOk = false;
587
- }
588
- const doc = readJsonObject(hooksTarget, { fsImpl });
589
- const gate = doc?.[HOOKS_GROUP];
590
- const stop = doc?.[STOP_HOOKS_GROUP];
591
- const gateOk = Boolean(gate?.PreToolUse?.[0]?.hooks?.[0]?.command)
592
- && gate.PreToolUse[0].hooks[0].command === scriptTargets['pre-implementation-gate.sh']
593
- && gate.PreToolUse[0].matcher === GATE_MATCHER;
594
- // Stop registration is matcher-free: handlers sit directly under the event key.
595
- const stopOk = Array.isArray(stop?.Stop) && stop.Stop.length > 0
596
- && stop.Stop[0]?.command === scriptTargets['stop-check.sh'];
597
- const groupsOk = gateOk && stopOk;
598
- statuses.push({ assetId: HOOKS_ASSET_ID, capability: 'hooks', status:
599
- removingOp ? (!anyPresent && !groupsOk ? 'absent' : 'retained')
600
- : (scriptsOk && groupsOk ? 'managed' : (!anyPresent && !groupsOk ? 'absent' : 'missing')),
601
- ownershipIdentity: `${HARNESS}:hooks:registration`, target: hooksTarget });
602
- if (!removingOp && scriptsOk && groupsOk) {
603
- resources.push({ assetId: HOOKS_ASSET_ID, target: hooksTarget,
604
- ownershipIdentity: `${HARNESS}:hooks:registration`, kind: 'hooks-json',
605
- fingerprint: fingerprint(JSON.stringify(doc)), sourceVersion: 'registry-v1',
606
- projection: { renderer: 'antigravity-hooks' } });
607
- for (const identity of HOOK_SCRIPTS) {
608
- resources.push({ assetId: HOOKS_ASSET_ID, target: scriptTargets[identity],
609
- ownershipIdentity: `${HARNESS}:copy-tree:${HOOKS_ASSET_ID}:${identity}`,
610
- kind: 'copy-tree-file', identity, fingerprint: fingerprints[identity],
611
- sourceVersion: 'registry-v1', projection: { renderer: 'antigravity-hooks' } });
612
- }
613
- }
646
+ const hooks = verifyHooks({ options, paths, context, fsImpl });
647
+ statuses.push(...hooks.statuses);
648
+ resources.push(...hooks.resources);
614
649
  }
615
650
 
616
651
  const conflicts = [...statuses.filter((s) => s.status === 'conflict').map((s) => s.reason ?? s.identity), ...mcp.conflicts];
617
652
  return { ok: conflicts.length === 0, statuses, resources, conflicts };
618
653
  }
619
654
 
620
- function registryRepoRoot(options: any) {
655
+ function registryRepoRoot(options ) {
621
656
  return options.registry?.repoRoot ?? process.cwd();
622
657
  }
623
658
 
624
- function conflictsToStatuses(conflicts: any, assetId: any, statuses: any) {
659
+ function conflictsToStatuses(conflicts , assetId , statuses ) {
625
660
  for (const reason of conflicts) statuses.push({ assetId, capability: 'scripts', status: 'conflict', reason });
626
661
  }
627
662
 
628
663
  module.exports = {
629
664
  HARNESS, nativePaths: singleton.nativePaths, discover, render, plan, apply, remove, verify,
665
+ planHookDoors: singleton.planHookDoors, verifyHookDoors: singleton.verifyHookDoors,
630
666
  createAntigravityAdapter,
631
667
  };