@khoavu882/doflow 2.0.0-beta.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -3
- 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/lib.sh +43 -24
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- 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/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- 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 +18 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
- package/core/harnesses/kiro/hooks/session-start.sh +12 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
- package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
- package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +72 -80
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +60 -33
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1542 -277
- package/core/registry/lifecycle.json +2837 -37
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +701 -30
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +154 -35
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +24 -23
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +7 -9
- package/core/shared/skills/do-plan/SKILL.md +29 -19
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +226 -113
- package/src/adapters/claude/index.js +39 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +100 -22
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +294 -48
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +38 -13
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +120 -0
- package/src/adapters/pi/index.js +48 -16
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +3 -2
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +3 -2
- package/src/cli/index.js +338 -33
- package/src/cli/runtime-commands.js +243 -52
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +74 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +434 -86
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +686 -32
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +514 -0
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +27 -12
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +221 -277
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +100 -0
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +1106 -245
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +41 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +77 -10
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +3 -3
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +127 -16
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +402 -73
- package/src/runtime/retrieval-record.js +293 -0
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +63 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +290 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +202 -56
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +440 -65
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -5
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- 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/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -210
- package/src/adapters/gemini/index.js +0 -390
|
@@ -183,6 +183,24 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
183
183
|
function settingsContent(fileName , raw , scope ) {
|
|
184
184
|
return fileName === 'settings.json' && scope !== 'global' ? raw.split(GLOBAL_HOOK_PREFIX).join(PROJECT_HOOK_PREFIX) : raw;
|
|
185
185
|
}
|
|
186
|
+
/** Hook tiers (maint-hooks): with Claude outside `hooks.harnesses` the managed settings carry no
|
|
187
|
+
* `hooks` block. Returns the managed text to write and the hooks it left out, which a merge strips
|
|
188
|
+
* from the live file so an earlier install's entries do not stay behind. */
|
|
189
|
+
function tieredSettings(fileName , raw , context ) {
|
|
190
|
+
if (fileName !== 'settings.json' || context?.hooksOff !== true) return { raw, droppedHooks: null };
|
|
191
|
+
let parsed;
|
|
192
|
+
try { parsed = JSON.parse(raw); } catch { return { raw, droppedHooks: null }; }
|
|
193
|
+
if (!parsed || parsed.hooks === undefined) return { raw, droppedHooks: null };
|
|
194
|
+
const { hooks, ...rest } = parsed;
|
|
195
|
+
return { raw: serializeSettings(rest), droppedHooks: hooks };
|
|
196
|
+
}
|
|
197
|
+
/** The live settings without DoFlow's own entries of `hooks`; every other key is left alone. */
|
|
198
|
+
function withoutManagedHooks(current , hooks ) {
|
|
199
|
+
if (!hooks || !current || typeof current !== 'object' || current.hooks === undefined) return current;
|
|
200
|
+
const { hooks: liveHooks, ...rest } = current;
|
|
201
|
+
const stripped = stripManagedSettings({ hooks: liveHooks, _: true }, { hooks });
|
|
202
|
+
return stripped?.hooks ? { ...rest, hooks: stripped.hooks } : rest;
|
|
203
|
+
}
|
|
186
204
|
function settingsLedgerResources(ledger , assetId ) {
|
|
187
205
|
const byFile = new Map();
|
|
188
206
|
for (const resource of ledger?.resources || []) {
|
|
@@ -242,7 +260,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
242
260
|
for (const fileName of SETTINGS_FILES) {
|
|
243
261
|
const sourceAbs = path.join(sourceDir, fileName);
|
|
244
262
|
if (!fs.existsSync(sourceAbs)) continue;
|
|
245
|
-
|
|
263
|
+
const tiered = tieredSettings(fileName, settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), context);
|
|
264
|
+
let managedRaw = tiered.raw;
|
|
246
265
|
if (fileName === 'settings.json' && context.statusline && !removing) {
|
|
247
266
|
// --statusline: point Claude Code's statusLine key at the projected script. It rides the
|
|
248
267
|
// same managed settings write as everything else this asset owns — one serialization, one
|
|
@@ -267,7 +286,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
267
286
|
const currentRaw = fs.readFileSync(target, 'utf8');
|
|
268
287
|
let merged;
|
|
269
288
|
try {
|
|
270
|
-
merged = mergeSettings(JSON.parse(currentRaw), JSON.parse(managedRaw));
|
|
289
|
+
merged = mergeSettings(withoutManagedHooks(JSON.parse(currentRaw), tiered.droppedHooks), JSON.parse(managedRaw));
|
|
271
290
|
} catch {
|
|
272
291
|
conflicts.push(`${fileName} is not valid JSON and cannot be merged`);
|
|
273
292
|
continue;
|
|
@@ -358,7 +377,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
358
377
|
for (const fileName of SETTINGS_FILES) {
|
|
359
378
|
const sourceAbs = path.join(sourceDir, fileName);
|
|
360
379
|
if (!fs.existsSync(sourceAbs)) continue;
|
|
361
|
-
const
|
|
380
|
+
const sourceText = tieredSettings(fileName, settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), context).raw;
|
|
381
|
+
const fingerprint = sha256(sourceText);
|
|
362
382
|
const target = settingsTarget(fileName, paths);
|
|
363
383
|
// 'absent' (not 'missing') matches the same convention the instructions asset already uses
|
|
364
384
|
// for "no file here" — 'missing' is reserved by the lifecycle layer's removal reconciliation
|
|
@@ -373,7 +393,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
373
393
|
let managed;
|
|
374
394
|
try {
|
|
375
395
|
current = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
376
|
-
managed = JSON.parse(
|
|
396
|
+
managed = JSON.parse(sourceText);
|
|
377
397
|
} catch {
|
|
378
398
|
conflicts.push(`${fileName} is not valid JSON`);
|
|
379
399
|
statuses.push({ assetId: asset.id, capability: 'settings', status: 'conflict', target });
|
|
@@ -480,6 +500,19 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
480
500
|
return { changes, conflicts, prerequisites: [], notices: [...copyTree.notices, ...mcpPlan.notices] };
|
|
481
501
|
}
|
|
482
502
|
|
|
503
|
+
/** The hook front doors (IC-037): the one asset that holds the scripts the settings hooks run. */
|
|
504
|
+
function hookDoorAssets(assets ) { return (assets || []).filter((asset ) => asset.id === 'claude.hooks-scripts'); }
|
|
505
|
+
|
|
506
|
+
function planHookDoors({ assets, scope, scopeRoot, context = {}, ledger } ) {
|
|
507
|
+
const doors = planCopyTreeAssets({ assets: hookDoorAssets(assets), scope, scopeRoot, context, ledger, removing: false });
|
|
508
|
+
return { changes: doors.changes, conflicts: doors.conflicts, prerequisites: [], notices: doors.notices };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function verifyHookDoors({ assets, scope, scopeRoot, context = {} } ) {
|
|
512
|
+
const doors = verifyCopyTreeAssets({ assets: hookDoorAssets(assets), scope, scopeRoot, context });
|
|
513
|
+
return { ok: doors.conflicts.length === 0, statuses: doors.statuses, resources: doors.resources, conflicts: doors.conflicts };
|
|
514
|
+
}
|
|
515
|
+
|
|
483
516
|
function apply({ changes, scope, scopeRoot, context = {} } ) {
|
|
484
517
|
for (const change of changes) {
|
|
485
518
|
if (change.projection?.renderer !== INSTRUCTION_RENDERER) continue;
|
|
@@ -536,7 +569,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
536
569
|
return { ok: conflicts.length === 0, statuses, resources, conflicts };
|
|
537
570
|
}
|
|
538
571
|
|
|
539
|
-
return { nativePaths, discover, render, plan, apply, remove, verify };
|
|
572
|
+
return { nativePaths, discover, render, plan, apply, remove, verify, planHookDoors, verifyHookDoors };
|
|
540
573
|
}
|
|
541
574
|
|
|
542
575
|
const singleton = createClaudeAdapter();
|
|
@@ -544,5 +577,6 @@ const singleton = createClaudeAdapter();
|
|
|
544
577
|
module.exports = {
|
|
545
578
|
nativePaths: singleton.nativePaths, discover: singleton.discover, render: singleton.render,
|
|
546
579
|
plan: singleton.plan, apply: singleton.apply, remove: singleton.remove, verify: singleton.verify,
|
|
580
|
+
planHookDoors: singleton.planHookDoors, verifyHookDoors: singleton.verifyHookDoors,
|
|
547
581
|
createClaudeAdapter,
|
|
548
582
|
};
|
|
@@ -9,18 +9,12 @@ const crypto = require('node:crypto');
|
|
|
9
9
|
const fs = require('node:fs');
|
|
10
10
|
const path = require('node:path');
|
|
11
11
|
const { atomicWrite } = require('./config.js');
|
|
12
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
12
13
|
|
|
13
14
|
const AGENT_DIRECTORY = 'agents';
|
|
14
15
|
const AGENT_KIND = 'custom-agent';
|
|
15
16
|
const REQUIRED_FIELDS = ['name', 'description', 'developer_instructions'];
|
|
16
|
-
const
|
|
17
|
-
'backend-architect', 'deep-research-agent', 'devops-architect', 'frontend-architect',
|
|
18
|
-
'quality-engineer', 'requirements-analyst', 'root-cause-analyst', 'security-engineer',
|
|
19
|
-
'system-architect', 'technical-writer',
|
|
20
|
-
]);
|
|
21
|
-
const IMPLEMENTATION_ROLES = new Set([
|
|
22
|
-
'performance-engineer', 'pm-agent', 'python-expert', 'refactoring-expert',
|
|
23
|
-
]);
|
|
17
|
+
const SPEC_DIRECTORY = path.join(REPO_ROOT, 'core', 'shared', 'agent-specs');
|
|
24
18
|
const ALLOWED_FIELDS = new Set([...REQUIRED_FIELDS, 'sandbox_mode']);
|
|
25
19
|
|
|
26
20
|
function fingerprint(content ) {
|
|
@@ -85,7 +79,21 @@ function parseAgentToml(source , { fileName } = {})
|
|
|
85
79
|
return values;
|
|
86
80
|
}
|
|
87
81
|
|
|
88
|
-
|
|
82
|
+
/** `access` of every shared agent spec by name (118 IC-002). A TOML whose name has no spec is the
|
|
83
|
+
* user's own agent and is not tied to any access. */
|
|
84
|
+
function specAccess(specDir = SPEC_DIRECTORY, fsImpl = fs) {
|
|
85
|
+
const access = new Map ();
|
|
86
|
+
if (!fsImpl.existsSync(specDir)) return access;
|
|
87
|
+
for (const entry of fsImpl.readdirSync(specDir)) {
|
|
88
|
+
if (!entry.endsWith('.md')) continue;
|
|
89
|
+
const frontmatter = fsImpl.readFileSync(path.join(specDir, entry), 'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
90
|
+
const value = frontmatter?.[1].match(/^access:\s*(\S+)\s*$/m)?.[1];
|
|
91
|
+
if (value) access.set(entry.slice(0, -'.md'.length), value);
|
|
92
|
+
}
|
|
93
|
+
return access;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function validateAgentContract(source , { fileName, access = specAccess() } = {}) {
|
|
89
97
|
const agent = parseAgentToml(source, { fileName });
|
|
90
98
|
for (const field of REQUIRED_FIELDS) {
|
|
91
99
|
if (typeof agent[field] !== 'string' || !agent[field].trim()) throw new Error(`Agent is missing required '${field}'`);
|
|
@@ -97,11 +105,12 @@ function validateAgentContract(source , { fileName }
|
|
|
97
105
|
if (agent.sandbox_mode !== undefined && !['read-only', 'workspace-write', 'danger-full-access'].includes(agent.sandbox_mode)) {
|
|
98
106
|
throw new Error(`Agent '${agent.name}' has an unsupported sandbox_mode`);
|
|
99
107
|
}
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
const specAccessValue = access.get(agent.name);
|
|
109
|
+
if (specAccessValue === 'read-only' && agent.sandbox_mode !== 'read-only') {
|
|
110
|
+
throw new Error(`Agent '${agent.name}' has access: read-only and must use sandbox_mode = "read-only"`);
|
|
102
111
|
}
|
|
103
|
-
if (
|
|
104
|
-
throw new Error(`
|
|
112
|
+
if (specAccessValue === 'write' && agent.sandbox_mode !== undefined) {
|
|
113
|
+
throw new Error(`Agent '${agent.name}' has access: write and must inherit its parent sandbox`);
|
|
105
114
|
}
|
|
106
115
|
return Object.freeze({ ...agent, fileName: filenameFor(agent.name) });
|
|
107
116
|
}
|
|
@@ -134,12 +143,13 @@ function discoverCodexAgents(sourceDir , fsImpl = fs) {
|
|
|
134
143
|
const directory = path.resolve(sourceDir);
|
|
135
144
|
if (!fsImpl.existsSync(directory)) throw new Error(`Codex agent source directory does not exist: ${sourceDir}`);
|
|
136
145
|
const agents = [];
|
|
146
|
+
const access = specAccess();
|
|
137
147
|
for (const entry of fsImpl.readdirSync(directory, { withFileTypes: true }).sort((a , b ) => a.name.localeCompare(b.name))) {
|
|
138
148
|
if (!entry.name.endsWith('.toml')) continue;
|
|
139
149
|
if (!entry.isFile()) throw new Error(`Codex agent source '${entry.name}' must be a regular file`);
|
|
140
150
|
const name = expectedName(entry.name);
|
|
141
151
|
const source = fsImpl.readFileSync(path.join(directory, entry.name), 'utf8');
|
|
142
|
-
agents.push({ ...validateAgentContract(source, { fileName: entry.name }), name, source, fingerprint: fingerprint(source) });
|
|
152
|
+
agents.push({ ...validateAgentContract(source, { fileName: entry.name, access }), name, source, fingerprint: fingerprint(source) });
|
|
143
153
|
}
|
|
144
154
|
if (!agents.length) throw new Error(`No Codex agent TOML contracts found in ${sourceDir}`);
|
|
145
155
|
return agents;
|
|
@@ -202,7 +212,7 @@ function reconcileCodexAgents(options ) {
|
|
|
202
212
|
}
|
|
203
213
|
|
|
204
214
|
module.exports = {
|
|
205
|
-
AGENT_DIRECTORY, AGENT_KIND,
|
|
215
|
+
AGENT_DIRECTORY, AGENT_KIND, fingerprint, specAccess,
|
|
206
216
|
parseAgentToml, validateAgentContract, agentDirectory, destinationFor, discoverCodexAgents,
|
|
207
217
|
planCodexAgents, applyCodexAgents, reconcileCodexAgents,
|
|
208
218
|
};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// that are not executable. Trust is reported as a prerequisite rather than being bypassed.
|
|
8
8
|
const fs = require('node:fs');
|
|
9
9
|
const path = require('node:path');
|
|
10
|
+
const crypto = require('node:crypto');
|
|
10
11
|
const { verifyHookCommands } = require('../hook-commands.js');
|
|
11
12
|
const { mergeHooks } = require('../../helper/settings-merge.js');
|
|
12
13
|
const { atomicWrite } = require('./config.js');
|
|
@@ -102,7 +103,32 @@ function destinationFor(context = {}) {
|
|
|
102
103
|
throw new Error('A hook destination or Codex scope is required');
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
/** DoFlow's record of a deployed hook script, in the ledger's fingerprint form: sha256 of its bytes. */
|
|
107
|
+
function scriptFingerprint(bytes ) { return `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`; }
|
|
108
|
+
|
|
109
|
+
/** The scripts beside the config that differ from the shipped source (or are absent), by name, and
|
|
110
|
+
* those among them a ledger row says were edited since DoFlow wrote them. A script with no row
|
|
111
|
+
* (an install from before scripts were recorded) is DoFlow's to refresh: the config deploy always
|
|
112
|
+
* overwrote it. `rows` are this harness's script rows: { target, fingerprint }. */
|
|
113
|
+
function planHookScripts({ commands, sourceHooksDir, destinationHooksDir, rows = [], force = false, fsImpl = fs } ) {
|
|
114
|
+
const sources = new Map (commands.checks.map((check ) => [check.script, check.file] ));
|
|
115
|
+
for (const name of shippedHookScripts(sourceHooksDir, fsImpl)) if (!sources.has(name)) sources.set(name, path.join(sourceHooksDir, name));
|
|
116
|
+
const stale = [];
|
|
117
|
+
const edited = [];
|
|
118
|
+
for (const [name, source] of sources) {
|
|
119
|
+
const target = path.join(destinationHooksDir, name);
|
|
120
|
+
if (fsImpl.existsSync(target)) {
|
|
121
|
+
const current = fsImpl.readFileSync(target);
|
|
122
|
+
if (current.equals(fsImpl.readFileSync(source))) continue;
|
|
123
|
+
const row = rows.find((item ) => path.resolve(item.target) === path.resolve(target));
|
|
124
|
+
if (row && row.fingerprint !== scriptFingerprint(current) && !force) edited.push(name);
|
|
125
|
+
}
|
|
126
|
+
stale.push(name);
|
|
127
|
+
}
|
|
128
|
+
return { names: [...sources.keys()], stale, edited };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext = {}, trusted = false, scriptRows = [], force = false, fsImpl = fs } = {}) {
|
|
106
132
|
let desired = config;
|
|
107
133
|
try {
|
|
108
134
|
if (!desired && sourceFile) desired = JSON.parse(fsImpl.readFileSync(sourceFile, 'utf8'));
|
|
@@ -130,16 +156,25 @@ function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext
|
|
|
130
156
|
}
|
|
131
157
|
const content = schema.ok && mergedDesired ? `${JSON.stringify(mergedDesired, null, 2)}\n` : null;
|
|
132
158
|
if (errors.length) return { ok: false, status: 'invalid-request', destination, original, changes: [], errors, commands };
|
|
133
|
-
const
|
|
159
|
+
const configChanged = original !== content;
|
|
134
160
|
const destinationHooksDir = destinationContext.paths?.hooksDirectory
|
|
135
161
|
|| destinationContext.hooksDir
|
|
136
162
|
|| path.join(path.dirname(destination), 'hooks');
|
|
163
|
+
// The scripts are deployed with the config but are not part of it: a script whose bytes differ
|
|
164
|
+
// from the source is redeployed even when the config is already right.
|
|
165
|
+
const scripts = planHookScripts({ commands, sourceHooksDir, destinationHooksDir, rows: scriptRows, force, fsImpl });
|
|
166
|
+
if (scripts.edited.length) {
|
|
167
|
+
return { ok: false, status: 'conflict', destination, original, changes: [], commands,
|
|
168
|
+
errors: scripts.edited.map((name ) => `Codex hook script '${name}' was modified outside DoFlow`) };
|
|
169
|
+
}
|
|
170
|
+
const scriptsOnly = !configChanged && scripts.stale.length > 0;
|
|
171
|
+
const changed = configChanged || scriptsOnly;
|
|
137
172
|
// The scripts deployCodexHooks copies beside hooks.json, so a backup of this change holds them too.
|
|
138
173
|
const companionTargets = [...new Set([
|
|
139
174
|
...commands.checks.map((check ) => path.join(destinationHooksDir, check.script)),
|
|
140
175
|
...shippedHookScripts(sourceHooksDir, fsImpl).map((name ) => path.join(destinationHooksDir, name)),
|
|
141
176
|
])].sort();
|
|
142
|
-
return { ok: true, status: changed ? 'change' : 'unchanged', destination, original, content,
|
|
177
|
+
return { ok: true, status: changed ? 'change' : 'unchanged', destination, original, content, scriptsOnly, scriptNames: scripts.names,
|
|
143
178
|
changes: changed ? [{ type: fsImpl.existsSync(destination) ? 'update' : 'create', file: destination, companionTargets }] : [],
|
|
144
179
|
errors: [], commands, trust: commands.trust, scriptsDir: sourceHooksDir, destinationHooksDir };
|
|
145
180
|
}
|
|
@@ -147,7 +182,7 @@ function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext
|
|
|
147
182
|
function deployCodexHooks(plan , { dryRun = false, fsImpl = fs } = {}) {
|
|
148
183
|
if (!plan.ok) return { ...plan, applied: false };
|
|
149
184
|
if (dryRun || plan.status === 'unchanged') return { ...plan, applied: false };
|
|
150
|
-
atomicWrite(plan.destination, plan.content, fsImpl);
|
|
185
|
+
if (!plan.scriptsOnly) atomicWrite(plan.destination, plan.content, fsImpl);
|
|
151
186
|
const deployed = new Set ();
|
|
152
187
|
for (const check of plan.commands.checks) {
|
|
153
188
|
const target = path.join(plan.destinationHooksDir, check.script);
|
|
@@ -200,4 +235,4 @@ function removeCodexHookScripts({ scriptsDir, destinationHooksDir }
|
|
|
200
235
|
return { removed };
|
|
201
236
|
}
|
|
202
237
|
|
|
203
|
-
module.exports = { SUPPORTED_EVENTS, validateHooksConfig, classifyClaudeGuardrails, verifyHookCommands, planCodexHooks, deployCodexHooks, shippedHookScripts, removeCodexHookScripts };
|
|
238
|
+
module.exports = { SUPPORTED_EVENTS, validateHooksConfig, classifyClaudeGuardrails, verifyHookCommands, planCodexHooks, deployCodexHooks, scriptFingerprint, shippedHookScripts, removeCodexHookScripts };
|
|
@@ -12,9 +12,10 @@ const crypto = require('node:crypto');
|
|
|
12
12
|
const { configPath, fingerprint: configFingerprint, parseToml, planCodexConfig, applyCodexConfig } = require('./config.js');
|
|
13
13
|
const { renderServer, planCodexMcp, applyCodexMcp, ownedCodexMcpIds, userDefinedCodexMcpIds, serverTableText } = require('./mcp.js');
|
|
14
14
|
const { agentDirectory, discoverCodexAgents, planCodexAgents, applyCodexAgents } = require('./agents.js');
|
|
15
|
-
const { planCodexHooks, deployCodexHooks, removeCodexHookScripts } = require('./hooks.js');
|
|
16
|
-
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices } = require('../copy-tree.js');
|
|
15
|
+
const { planCodexHooks, deployCodexHooks, removeCodexHookScripts, scriptFingerprint } = require('./hooks.js');
|
|
16
|
+
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveTransform } = require('../copy-tree.js');
|
|
17
17
|
const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
|
|
18
|
+
const { renderGuidancePointer } = require('../guidance-pointer.js');
|
|
18
19
|
const { nativeMcpCatalog } = require('../../registry/index.js');
|
|
19
20
|
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
20
21
|
|
|
@@ -275,6 +276,26 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
275
276
|
}
|
|
276
277
|
function sha256Text(text ) { return crypto.createHash('sha256').update(text).digest('hex'); }
|
|
277
278
|
|
|
279
|
+
function instructionBody(content ) {
|
|
280
|
+
if (typeof content !== 'string') return null;
|
|
281
|
+
const start = content.indexOf(MARKER_START);
|
|
282
|
+
if (start === -1) return null;
|
|
283
|
+
const bodyStart = start + MARKER_START.length;
|
|
284
|
+
const end = content.indexOf(MARKER_END, bodyStart);
|
|
285
|
+
if (end === -1 || content.indexOf(MARKER_START, end + MARKER_END.length) !== -1) return null;
|
|
286
|
+
let from = bodyStart;
|
|
287
|
+
if (content[from] === '\n') from += 1;
|
|
288
|
+
let to = end;
|
|
289
|
+
if (to > from && content[to - 1] === '\n') to -= 1;
|
|
290
|
+
return content.slice(from, to);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function renderedInstructionContent(asset , context , repoRoot ) {
|
|
294
|
+
const source = sourceDirFor(asset, repoRoot);
|
|
295
|
+
const content = fs.readFileSync(source, 'utf8');
|
|
296
|
+
return renderGuidancePointer(content, { scope: context.scope, scopeRoot: context.paths.root }).replace(/\s+$/, '');
|
|
297
|
+
}
|
|
298
|
+
|
|
278
299
|
function planInstructionsAsset({ assets, context, removing, repoRoot, claimedElsewhere = () => false } ) {
|
|
279
300
|
const asset = instructionsAsset(assets);
|
|
280
301
|
const changes = [];
|
|
@@ -298,18 +319,19 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
298
319
|
return { changes, conflicts };
|
|
299
320
|
}
|
|
300
321
|
const source = sourceDirFor(asset, repoRoot);
|
|
301
|
-
|
|
302
|
-
const
|
|
322
|
+
const content = renderedInstructionContent(asset, context, repoRoot);
|
|
323
|
+
const merged = mergeMarkedSection(source, target, { dryRun: true, content });
|
|
324
|
+
if (!merged.changed) return { changes, conflicts };
|
|
303
325
|
changes.push({ assetId: asset.id, target, source, operation: fs.existsSync(target) ? 'update' : 'create',
|
|
304
326
|
ownershipIdentity: 'doflow:codex:instructions:managed-section', kind: 'instructions-section', identity: 'AGENTS.md',
|
|
305
|
-
afterFingerprint: sha256Text(content), sourceVersion: 'registry-v1', projection: { renderer: 'codex-agents' } });
|
|
327
|
+
afterFingerprint: sha256Text(content), renderedContent: content, sourceVersion: 'registry-v1', projection: { renderer: 'codex-agents' } });
|
|
306
328
|
return { changes, conflicts };
|
|
307
329
|
}
|
|
308
330
|
|
|
309
331
|
function applyInstructionsAsset(changes ) {
|
|
310
332
|
for (const change of changes) {
|
|
311
333
|
if (change.projection?.renderer !== 'codex-agents' || change.operation === 'remove') continue;
|
|
312
|
-
mergeMarkedSection(change.source, change.target);
|
|
334
|
+
mergeMarkedSection(change.source, change.target, { content: change.renderedContent });
|
|
313
335
|
}
|
|
314
336
|
}
|
|
315
337
|
|
|
@@ -325,11 +347,14 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
325
347
|
if (!asset) return { statuses, resources };
|
|
326
348
|
const target = instructionsPath(context);
|
|
327
349
|
const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
|
|
328
|
-
const
|
|
329
|
-
|
|
350
|
+
const expected = renderedInstructionContent(asset, context, repoRoot);
|
|
351
|
+
const actual = instructionBody(existing);
|
|
352
|
+
const hasMarkers = existing !== null && existing.includes(MARKER_START) && existing.includes(MARKER_END);
|
|
353
|
+
const managed = actual !== null && actual === expected;
|
|
354
|
+
statuses.push({ assetId: asset.id, capability: asset.capability, status: managed ? 'managed' : (hasMarkers ? 'missing' : 'absent'), target });
|
|
330
355
|
if (managed) {
|
|
331
356
|
resources.push({ assetId: asset.id, target, ownershipIdentity: 'doflow:codex:instructions:managed-section',
|
|
332
|
-
kind: 'instructions-section', identity: 'AGENTS.md', fingerprint: sha256Text(
|
|
357
|
+
kind: 'instructions-section', identity: 'AGENTS.md', fingerprint: sha256Text(expected), sourceVersion: 'registry-v1',
|
|
333
358
|
projection: { renderer: 'codex-agents' } });
|
|
334
359
|
}
|
|
335
360
|
return { statuses, resources };
|
|
@@ -344,7 +369,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
344
369
|
const destDir = copyTreeDestDir(codexConfigDir(context), asset);
|
|
345
370
|
const sourceDir = sourceDirFor(asset, repoRoot);
|
|
346
371
|
const previousResources = ledgerFileResources(neutralResources, HARNESS, asset.id);
|
|
347
|
-
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(neutralResources, HARNESS), operation: removing ? 'remove' : 'apply', layout: asset.layout, keepModified: !removing,
|
|
372
|
+
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(neutralResources, HARNESS), operation: removing ? 'remove' : 'apply', layout: asset.layout, transform: asset.transform, keepModified: !removing,
|
|
348
373
|
// `force` is the CLI's --force reaching the one conflict class a plan can actually
|
|
349
374
|
// downgrade: a ledger-owned destination whose bytes were edited underneath us. With force,
|
|
350
375
|
// that is drift to heal rather than a refusal — exactly what `doflow reconcile` (always
|
|
@@ -362,6 +387,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
362
387
|
ownershipIdentity: `doflow:codex:copy-tree:${asset.id}:${change.relPath}`,
|
|
363
388
|
kind: 'copy-tree-file', identity: change.relPath,
|
|
364
389
|
afterFingerprint: change.fingerprint, fingerprint: change.fingerprint, sourceVersion: sourceVersion ?? 'unknown',
|
|
390
|
+
transformName: asset.transform || null,
|
|
365
391
|
...(change.kept ? { retained: true, retainedFor: [] } : {}),
|
|
366
392
|
projection: { renderer: 'copy-tree' },
|
|
367
393
|
});
|
|
@@ -371,9 +397,18 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
371
397
|
}
|
|
372
398
|
|
|
373
399
|
function applyCopyTreeAssets(changes ) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
400
|
+
let applied = 0;
|
|
401
|
+
const grouped = new Map();
|
|
402
|
+
for (const change of changes) {
|
|
403
|
+
if (change.projection?.renderer !== 'copy-tree' || change.operation === 'remove') continue;
|
|
404
|
+
const key = change.transformName || null;
|
|
405
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
406
|
+
grouped.get(key).push({ relPath: change.identity, target: change.target, source: change.source, operation: change.operation, fingerprint: change.fingerprint });
|
|
407
|
+
}
|
|
408
|
+
for (const [transformName, treeChanges] of grouped) {
|
|
409
|
+
applied += applyTree({ changes: treeChanges, transform: resolveTransform(transformName) }).applied;
|
|
410
|
+
}
|
|
411
|
+
return applied;
|
|
377
412
|
}
|
|
378
413
|
|
|
379
414
|
function removeCopyTreeAssets(changes ) {
|
|
@@ -389,7 +424,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
389
424
|
for (const asset of copyTreeAssets(assets)) {
|
|
390
425
|
const destDir = copyTreeDestDir(codexConfigDir(context), asset);
|
|
391
426
|
const sourceDir = sourceDirFor(asset, repoRoot);
|
|
392
|
-
const result = verifyTree({ sourceDir, destDir, layout: asset.layout, plannedWrites });
|
|
427
|
+
const result = verifyTree({ sourceDir, destDir, layout: asset.layout, transform: asset.transform, plannedWrites });
|
|
393
428
|
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
394
429
|
for (const resource of result.resources) {
|
|
395
430
|
resources.push({
|
|
@@ -445,9 +480,16 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
445
480
|
/** Codex hooks.json is only replaced when the file on disk still matches the ledger's last-known
|
|
446
481
|
* fingerprint — a foreign edit (or a record the ledger never owned) downgrades an otherwise-clean
|
|
447
482
|
* plan to a conflict rather than silently overwriting it. */
|
|
448
|
-
|
|
483
|
+
/** This scope's ledger rows for the hook scripts deployed beside hooks.json. */
|
|
484
|
+
function scriptRows(neutralResources , context ) {
|
|
485
|
+
return (neutralResources || []).filter((resource ) => resource?.harness === HARNESS && resource.kind === 'hooks-script'
|
|
486
|
+
&& (!resource.scope || resource.scope === context.scope));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function planHooksComponent({ native, neutralResources, context, adopt = false, force = false } ) {
|
|
449
490
|
let hooks = planCodexHooks({ config: native.hooksConfig, sourceFile: native.hooksSourceFile,
|
|
450
|
-
sourceHooksDir: native.hooksSourceDir, trusted: native.hooksTrusted, destinationContext: context
|
|
491
|
+
sourceHooksDir: native.hooksSourceDir, trusted: native.hooksTrusted, destinationContext: context,
|
|
492
|
+
scriptRows: scriptRows(neutralResources, context), force });
|
|
451
493
|
const hooksFile = hooks.destination;
|
|
452
494
|
if (hooks.ok && fs.existsSync(hooksFile)) {
|
|
453
495
|
const [record] = nativeManagedResources(neutralResources, context, { kind: 'hooks-file', target: hooksFile });
|
|
@@ -457,7 +499,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
457
499
|
// conflict — that one is `--force`'s business, not adoption's.
|
|
458
500
|
if (!record && !adopt) {
|
|
459
501
|
hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json exists but is not owned by the neutral ledger'] };
|
|
460
|
-
} else if (record && record.fingerprint !== current) {
|
|
502
|
+
} else if (record && record.fingerprint !== current && !force) {
|
|
461
503
|
hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json was modified outside DoFlow'] };
|
|
462
504
|
}
|
|
463
505
|
}
|
|
@@ -466,13 +508,22 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
466
508
|
|
|
467
509
|
/** Agents/hooks share one removing-vs-planning fork: removal always owns both via the neutral
|
|
468
510
|
* ledger's own records, regardless of what native input the caller provided this run. */
|
|
511
|
+
/** A removal drops the rows of the hook scripts too. The scripts themselves go by their bytes
|
|
512
|
+
* (removeCodexHookScripts), so each row is only released here. */
|
|
513
|
+
function withScriptReleases(plan , neutralResources , context , extra ) {
|
|
514
|
+
const releases = scriptRows(neutralResources, context).map((row ) => ({ type: 'remove', assetId: row.assetId, identity: row.identity,
|
|
515
|
+
target: row.target, ownershipIdentity: row.ownershipIdentity, kind: row.kind, release: true }));
|
|
516
|
+
return { ...plan, ...extra, changes: [...plan.changes, ...releases],
|
|
517
|
+
status: plan.status === 'unchanged' && releases.length ? 'change' : plan.status };
|
|
518
|
+
}
|
|
519
|
+
|
|
469
520
|
function planAgentsAndHooksComponents({ native, neutralResources, context, agentsDirectory, removing, options, adopt = false } ) {
|
|
470
521
|
if (removing) {
|
|
471
522
|
return {
|
|
472
523
|
agents: ownedRemovalPlan(neutralResources, context, 'custom-agent', agentsDirectory),
|
|
473
524
|
// The scripts deployed beside hooks.json have no ledger rows; remove() matches them by bytes.
|
|
474
|
-
hooks:
|
|
475
|
-
scriptsDir: native.hooksSourceDir, destinationHooksDir: context.paths.hooksDirectory },
|
|
525
|
+
hooks: withScriptReleases(ownedRemovalPlan(neutralResources, context, 'hooks-file'), neutralResources, context,
|
|
526
|
+
{ scriptsDir: native.hooksSourceDir, destinationHooksDir: context.paths.hooksDirectory }),
|
|
476
527
|
};
|
|
477
528
|
}
|
|
478
529
|
const components = {};
|
|
@@ -483,7 +534,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
483
534
|
components.agents = planCodexAgents({ ...context, managedResources, sourceDir: native.agentsSourceDir, sourceVersion: options.sourceVersion ?? options.context?.sourceVersion, adopt });
|
|
484
535
|
}
|
|
485
536
|
if (native.hooksSourceFile !== undefined || native.hooksConfig !== undefined) {
|
|
486
|
-
components.hooks = planHooksComponent({ native, neutralResources, context, adopt });
|
|
537
|
+
components.hooks = planHooksComponent({ native, neutralResources, context, adopt, force: options.context?.force === true });
|
|
487
538
|
}
|
|
488
539
|
return components;
|
|
489
540
|
}
|
|
@@ -542,6 +593,25 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
542
593
|
notices: [...copyTree.notices, ...(components.mcp?.notices || [])] };
|
|
543
594
|
}
|
|
544
595
|
|
|
596
|
+
/** The hook front doors (IC-037): hooks.json and the scripts deployed beside it, planned and
|
|
597
|
+
* verified alone. A conflict reaches the lifecycle through `components`, as in plan(). */
|
|
598
|
+
function planHookDoors(options ) {
|
|
599
|
+
const native = projectedNativeOptions(options);
|
|
600
|
+
const hooks = native.hooksSourceFile === undefined && native.hooksConfig === undefined ? null
|
|
601
|
+
: planHooksComponent({ native, neutralResources: options.managedResources || options.ledger?.resources || [], context: nativeContext(options),
|
|
602
|
+
adopt: options.context?.adopt === true, force: options.context?.force === true });
|
|
603
|
+
const changes = [];
|
|
604
|
+
if (hooks) addChanges(changes, assetIdFor(options, 'hooks'), hooks, 'hooks');
|
|
605
|
+
return { changes, components: hooks ? { hooks } : {}, prerequisites: [], notices: [] };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function verifyHookDoors(options = {}) {
|
|
609
|
+
const hooks = verifyHooksResource({ native: projectedNativeOptions(options), context: nativeContext(options), options,
|
|
610
|
+
sourceVersion: options.sourceVersion ?? options.context?.sourceVersion, recoveryRef: options.recoveryRef });
|
|
611
|
+
return { ok: hooks.conflicts.length === 0 && hooks.statuses.every((status ) => status.status !== 'missing' && status.status !== 'conflict'),
|
|
612
|
+
resources: hooks.resources, statuses: hooks.statuses, conflicts: hooks.conflicts };
|
|
613
|
+
}
|
|
614
|
+
|
|
545
615
|
/** Apply only native plans emitted by this adapter. MCP is first because it and
|
|
546
616
|
* config.toml share a file; config is then re-planned over the MCP result so it
|
|
547
617
|
* cannot overwrite an adjacent MCP table. */
|
|
@@ -579,7 +649,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
579
649
|
function remove({ changes = [], dryRun = false } = {}) {
|
|
580
650
|
const result = apply({ changes, dryRun });
|
|
581
651
|
const direct = new Map();
|
|
582
|
-
for (const change of changes) if (change?.nativePlan?.directRemove) direct.set(change.target, change);
|
|
652
|
+
for (const change of changes) if (change?.nativePlan?.directRemove && !change.retained) direct.set(change.target, change);
|
|
583
653
|
for (const change of direct.values()) {
|
|
584
654
|
if (!fs.existsSync(change.target)) continue;
|
|
585
655
|
const current = sha256(fs.readFileSync(change.target, 'utf8'));
|
|
@@ -651,6 +721,13 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
651
721
|
if (!hooks.ok) conflicts.push(...hooks.errors);
|
|
652
722
|
if (status === 'managed') resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'hooks'), kind: 'hooks-file', identity: 'hooks.json',
|
|
653
723
|
target: hooks.destination, fingerprint: sha256(fs.readFileSync(hooks.destination, 'utf8')), sourceVersion, recoveryRef, projection: { renderer: 'codex-hooks' } }));
|
|
724
|
+
if (status === 'managed') {
|
|
725
|
+
for (const name of hooks.scriptNames) {
|
|
726
|
+
resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'hooks'), kind: 'hooks-script', identity: name,
|
|
727
|
+
target: path.join(hooks.destinationHooksDir, name), fingerprint: scriptFingerprint(fs.readFileSync(path.join(hooks.destinationHooksDir, name))),
|
|
728
|
+
sourceVersion, recoveryRef, projection: { renderer: 'codex-hooks' } }));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
654
731
|
return { statuses, resources, conflicts };
|
|
655
732
|
}
|
|
656
733
|
|
|
@@ -691,7 +768,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
691
768
|
return { ok: conflicts.length === 0 && statuses.every((status) => status.status !== 'missing' && status.status !== 'conflict'), resources, statuses, conflicts };
|
|
692
769
|
}
|
|
693
770
|
|
|
694
|
-
return { normalizeContext, nativeContext, projectedNativeOptions, nativeManagedResources, ownershipIdentity, discover, render, plan, apply, remove, verify };
|
|
771
|
+
return { normalizeContext, nativeContext, projectedNativeOptions, nativeManagedResources, ownershipIdentity, discover, render, plan, apply, remove, verify, planHookDoors, verifyHookDoors };
|
|
695
772
|
}
|
|
696
773
|
|
|
697
774
|
const singleton = createCodexAdapter();
|
|
@@ -703,5 +780,6 @@ module.exports = {
|
|
|
703
780
|
ownershipIdentity: singleton.ownershipIdentity,
|
|
704
781
|
discover: singleton.discover, render: singleton.render, plan: singleton.plan, apply: singleton.apply,
|
|
705
782
|
remove: singleton.remove, verify: singleton.verify,
|
|
783
|
+
planHookDoors: singleton.planHookDoors, verifyHookDoors: singleton.verifyHookDoors,
|
|
706
784
|
createCodexAdapter,
|
|
707
785
|
};
|
|
@@ -19,11 +19,15 @@ function normalizeNativeProjection(registry , harness , mcp , cont
|
|
|
19
19
|
if (!override || typeof override !== 'object' || Array.isArray(override)) throw new Error(`Projection override for '${harness.id}' must be an object`);
|
|
20
20
|
const trusted = override.hooks?.trusted;
|
|
21
21
|
if (trusted !== undefined && typeof trusted !== 'boolean') throw new Error(`Projection override '${harness.id}.hooks.trusted' must be boolean`);
|
|
22
|
+
// Hook tiers (maint-hooks): Codex outside `hooks.harnesses` gets no hooks.json, no hook scripts and no
|
|
23
|
+
// `features.hooks` entry; the lifecycle removes what an earlier install wrote through the remove path.
|
|
24
|
+
const hooksOff = harness.capabilities?.hooks?.tiered === true;
|
|
25
|
+
const configResources = (declaration.config?.resources || []).filter((resource ) => !(hooksOff && resource.identity === 'features.hooks'));
|
|
22
26
|
return Object.freeze({
|
|
23
|
-
configResources: Object.freeze(
|
|
27
|
+
configResources: Object.freeze(configResources.map((resource ) => Object.freeze({ ...resource }))),
|
|
24
28
|
mcp: Object.freeze({ catalog: Object.freeze(mcp.map((server ) => Object.freeze({ ...server }))), selected: Object.freeze(mcp.map((server ) => server.id)) }),
|
|
25
29
|
agents: Object.freeze({ sourceDir: resolveRegistrySource(registry, declaration.agents?.source, `${harness.id} agents`) }),
|
|
26
|
-
hooks: Object.freeze({
|
|
30
|
+
hooks: hooksOff ? Object.freeze({}) : Object.freeze({
|
|
27
31
|
sourceFile: resolveRegistrySource(registry, declaration.hooks?.configSource, `${harness.id} hooks config`),
|
|
28
32
|
sourceHooksDir: resolveRegistrySource(registry, declaration.hooks?.scriptsSource, `${harness.id} hooks scripts`),
|
|
29
33
|
trusted: trusted ?? declaration.hooks?.trusted,
|