@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.
- package/bin/doflow.js +2 -2
- package/core/.antigravity-plugin/plugin.json +0 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
- package/core/harnesses/gemini/hooks/session-start.sh +5 -3
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
- package/core/harnesses/kiro/hooks/session-start.sh +11 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
- package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
- package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
- package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
- package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
- package/core/registry/assets.json +19 -0
- package/core/registry/contracts.json +2 -2
- package/core/registry/harnesses.json +196 -10
- package/core/registry/lifecycle.json +2626 -7
- package/core/registry/storage.json +110 -83
- package/core/registry/verification.json +1 -1
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
- package/core/shared/scripts/doflow/bin/doflow-run +10 -6
- package/core/shared/skills/do-design/SKILL.md +6 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
- package/core/shared/skills/do-implement/SKILL.md +3 -2
- package/core/shared/skills/do-plan/SKILL.md +7 -8
- package/package.json +4 -2
- package/src/adapters/antigravity/{index.ts → index.js} +141 -105
- package/src/adapters/claude/{index.ts → index.js} +63 -49
- package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
- package/src/adapters/codex/{agents.ts → agents.js} +24 -24
- package/src/adapters/codex/{config.ts → config.js} +49 -49
- package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
- package/src/adapters/codex/{index.ts → index.js} +148 -105
- package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
- package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
- package/src/adapters/copilot/{index.ts → index.js} +42 -42
- package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
- package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
- package/src/adapters/gemini/{index.ts → index.js} +96 -57
- package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
- package/src/adapters/{index.ts → index.js} +13 -13
- package/src/adapters/kiro/{index.ts → index.js} +49 -35
- package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
- package/src/adapters/opencode/{index.ts → index.js} +52 -52
- package/src/adapters/pi/hooks.js +119 -0
- package/src/adapters/pi/{index.ts → index.js} +64 -39
- package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
- package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
- package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
- package/src/cli/commands/{install.ts → install.js} +25 -25
- package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
- package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
- package/src/cli/commands/{remove.ts → remove.js} +10 -10
- package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
- package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
- package/src/cli/commands/{status.ts → status.js} +24 -24
- package/src/cli/commands/{tools.ts → tools.js} +16 -16
- package/src/cli/commands/{update.ts → update.js} +20 -20
- package/src/cli/{index.ts → index.js} +43 -31
- package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
- package/src/cli/{shared.ts → shared.js} +40 -40
- package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
- package/src/helper/{git.ts → git.js} +2 -2
- package/src/helper/{guardrails.ts → guardrails.js} +10 -10
- package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
- package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
- package/src/helper/{prompt.ts → prompt.js} +5 -5
- package/src/helper/{repo-root.ts → repo-root.js} +2 -2
- package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
- package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
- package/src/helper/{toml.ts → toml.js} +48 -48
- package/src/install/{backup-set.ts → backup-set.js} +16 -16
- package/src/install/{backup.ts → backup.js} +65 -65
- package/src/install/{context.ts → context.js} +15 -15
- package/src/install/{manifest.ts → manifest.js} +27 -27
- package/src/install/{mcp.ts → mcp.js} +30 -30
- package/src/install/{paths.ts → paths.js} +15 -15
- package/src/install/{targets.ts → targets.js} +10 -10
- package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
- package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
- package/src/lifecycle/coverage.js +57 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/{index.ts → index.js} +160 -146
- package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
- package/src/lifecycle/{policies.ts → policies.js} +11 -11
- package/src/lifecycle/{view.ts → view.js} +28 -28
- package/src/registry/{index.ts → index.js} +195 -66
- package/src/release/build-package.js +91 -0
- package/src/release/changelog-section.js +70 -0
- package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
- package/src/runtime/advice/{cli.ts → cli.js} +31 -31
- package/src/runtime/advice/{client.ts → client.js} +44 -44
- package/src/runtime/advice/{config.ts → config.js} +16 -16
- package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
- package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
- package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
- package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
- package/src/runtime/advice/{record.ts → record.js} +19 -19
- package/src/runtime/advice/{registry.ts → registry.js} +16 -16
- package/src/runtime/advice/{strength.ts → strength.js} +7 -7
- package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
- package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
- package/src/runtime/candidate.js +505 -0
- package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
- package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
- package/src/runtime/{claims.ts → claims.js} +44 -35
- package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
- package/src/runtime/{cli.ts → cli.js} +59 -292
- package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +102 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context-pack.js +1277 -0
- package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
- package/src/runtime/failure/{capture.ts → capture.js} +29 -29
- package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
- package/src/runtime/failure/{cli.ts → cli.js} +30 -30
- package/src/runtime/failure/{home.ts → home.js} +6 -6
- package/src/runtime/failure/{store.ts → store.js} +32 -32
- package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
- package/src/runtime/{freshness.ts → freshness.js} +59 -22
- package/src/runtime/{health.ts → health.js} +72 -72
- package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
- package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
- package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
- package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
- package/src/runtime/inventory/{index.ts → index.js} +36 -36
- package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
- package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
- package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
- package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
- package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
- package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
- package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
- package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
- package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
- package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
- package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
- package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
- package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
- package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
- package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
- package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
- package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
- package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
- package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
- package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
- package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
- package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
- package/src/runtime/{mask.ts → mask.js} +22 -22
- package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
- package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
- package/src/runtime/{model-router.ts → model-router.js} +20 -20
- package/src/runtime/{outcome.ts → outcome.js} +27 -27
- package/src/runtime/{reach.ts → reach.js} +21 -21
- package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
- package/src/runtime/{readiness.ts → readiness.js} +113 -43
- package/src/runtime/{recovery.ts → recovery.js} +15 -15
- package/src/runtime/{research-request.ts → research-request.js} +34 -34
- package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
- package/src/runtime/retrieval-record.js +223 -0
- package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
- package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
- package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
- package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
- package/src/runtime/source-version.js +289 -0
- package/src/runtime/storage/{cli.ts → cli.js} +9 -9
- package/src/runtime/storage/{engine.ts → engine.js} +37 -37
- package/src/runtime/storage/{registry.ts → registry.js} +19 -19
- package/src/runtime/storage/{roots.ts → roots.js} +8 -8
- package/src/runtime/storage/{rules.ts → rules.js} +26 -26
- package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
- package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
- package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
- package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
- package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
- package/src/runtime/{task-state.ts → task-state.js} +86 -21
- package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
- package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
- package/src/runtime/trace/{render.ts → render.js} +7 -7
- package/src/runtime/trace/{views.ts → views.js} +40 -40
- package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
- package/src/runtime/verification/{engine.ts → engine.js} +143 -143
- package/src/runtime/verification/{registry.ts → registry.js} +6 -6
- package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
- package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
- package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
- package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
- package/src/runtime/{worktree.ts → worktree.js} +31 -31
- package/src/state/{index.ts → index.js} +26 -26
- package/src/state/{lockfile.ts → lockfile.js} +20 -20
- package/src/state/{run-lock.ts → run-lock.js} +44 -44
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- 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.
|
|
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.
|
|
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
|
|
115
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
existence only — but
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
"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
|
-
"
|
|
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.
|
|
24
|
-
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor } = require('../copy-tree.
|
|
25
|
-
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.
|
|
26
|
-
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.
|
|
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
|
|
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 }
|
|
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
|
|
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 }
|
|
69
|
-
const changes
|
|
70
|
-
const conflicts
|
|
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
|
|
80
|
-
const previousScriptRows = (neutralResources || []).filter((r
|
|
81
|
-
const docRow = previousHookRows.find((r
|
|
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
|
|
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
|
|
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
|
|
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 }
|
|
172
|
-
const nativePathsFor = (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
|
|
175
|
+
discover: (options , impl = {}) => discover(options, { ...impl, nativePaths: nativePathsFor }),
|
|
176
176
|
render,
|
|
177
|
-
plan: (options
|
|
177
|
+
plan: (options , impl = {}) => plan(options, { ...impl, nativePaths: nativePathsFor }),
|
|
178
178
|
apply,
|
|
179
179
|
remove,
|
|
180
|
-
verify: (options
|
|
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
|
|
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
|
|
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
|
|
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 = '' }
|
|
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
|
|
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
|
|
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
|
|
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 }
|
|
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
|
|
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
|
|
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
|
|
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
|
|
314
|
-
.map((c
|
|
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 ()
|
|
328
|
-
const plain = treeChanges.filter((t
|
|
329
|
-
const writes = plain.filter((c
|
|
330
|
-
const removals = plain.filter((c
|
|
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
|
|
337
|
-
const removals = treeChanges.filter((c
|
|
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 }
|
|
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
|
|
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
|
|
393
|
+
function scopeOf(paths ) { return paths.scope; }
|
|
392
394
|
|
|
393
395
|
// ---- MCP component ----
|
|
394
396
|
|
|
395
|
-
function mcpDefinitionFor(server
|
|
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
|
|
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 = [] }
|
|
409
|
-
const rendered = (servers
|
|
410
|
-
const rows = resources.filter((row
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
530
|
+
function remove(options = {}, impl = {}) {
|
|
506
531
|
return apply(options, impl);
|
|
507
532
|
}
|
|
508
533
|
|
|
509
|
-
|
|
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
|
|
568
|
-
|
|
569
|
-
|
|
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
|
|
655
|
+
function registryRepoRoot(options ) {
|
|
621
656
|
return options.registry?.repoRoot ?? process.cwd();
|
|
622
657
|
}
|
|
623
658
|
|
|
624
|
-
function conflictsToStatuses(conflicts
|
|
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
|
};
|