@khoavu882/doflow 2.0.0-beta.5 → 2.0.1
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 +16 -16
- package/bin/doflow.js +22 -12
- package/core/.antigravity-plugin/plugin.json +1 -2
- 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 +4 -1
- 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-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +26 -9
- 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/hooks.json +0 -10
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-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 +51 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -11
- package/core/harnesses/kiro/hooks/doflow.json +0 -10
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
- package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
- 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 +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +35 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1445 -366
- package/core/registry/intake.json +53 -0
- package/core/registry/lifecycle.json +599 -996
- 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/intake-category/files/category.json +8 -0
- package/core/registry/package-templates/intake-category/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 +272 -0
- package/core/registry/storage.json +687 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -6
- 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 +3 -2
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
- 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 +49 -5
- package/core/shared/scripts/doflow/bin/doflow-run +152 -34
- package/core/shared/skills/do/SKILL.md +14 -12
- 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 +45 -97
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -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 +48 -148
- 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 +23 -20
- package/core/shared/skills/do-diagnose/SKILL.md +18 -6
- package/core/shared/skills/do-document/SKILL.md +12 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +19 -7
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
- 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 +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
- 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 +10 -11
- package/core/shared/skills/do-flow/references/task_classes.md +5 -5
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +13 -14
- package/core/shared/skills/do-plan/SKILL.md +34 -20
- 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/shared/workflows/do-flow-chain.md +2 -0
- 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 +143 -66
- package/src/adapters/claude/index.js +46 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +39 -3
- package/src/adapters/codex/index.js +61 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +291 -46
- 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 +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- 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 +22 -3
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +337 -32
- package/src/cli/runtime-commands.js +220 -60
- 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 +4 -16
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +472 -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 +20 -11
- 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 +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/pointer.js +67 -12
- 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 +158 -79
- 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-ledger.js +52 -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 +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +624 -18
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +281 -26
- package/src/runtime/intake/engine.js +677 -0
- package/src/runtime/intake/index.js +136 -0
- package/src/runtime/intake/manifest.js +140 -0
- package/src/runtime/intake/taxonomy.js +206 -0
- 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 +88 -10
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +38 -7
- package/src/runtime/lifecycle/overview.js +26 -2
- 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 +2 -2
- 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 +487 -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 +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- 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/attendance.js +137 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +273 -0
- package/src/runtime/roadmap/loop.js +1719 -0
- package/src/runtime/roadmap/merge.js +390 -0
- package/src/runtime/roadmap/rows.js +1122 -0
- package/src/runtime/roadmap/seed.js +1063 -0
- package/src/runtime/roadmap/worktree-cleanup.js +147 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -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 +1 -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 +85 -14
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +373 -64
- 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/verb-view.js +267 -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 -54
- 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 +256 -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 +610 -67
- 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/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
- 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 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- 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 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
- 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 -251
- package/src/adapters/gemini/index.js +0 -429
|
@@ -29,6 +29,12 @@ const SETTINGS_RENDERER = 'claude-settings';
|
|
|
29
29
|
* its install destination is declared (paths.statuslineScript).
|
|
30
30
|
*/
|
|
31
31
|
const SETTINGS_FILES = ['settings.json', 'keybindings.json'];
|
|
32
|
+
/** Hook entries an earlier DoFlow wrote into settings.json and no longer ships. The update prunes their
|
|
33
|
+
* scripts, so a merge strips these entries from the live file (and a remove does too) instead of leaving a
|
|
34
|
+
* hook that points at a missing script. Written in the global form; `retiredHooks` rewrites the prefix for project scope. */
|
|
35
|
+
const RETIRED_SETTINGS_HOOKS = {
|
|
36
|
+
PreToolUse: [{ matcher: 'Bash', hooks: [{ type: 'command', command: `${GLOBAL_HOOK_PREFIX}pre-bash-guard.sh` }] }],
|
|
37
|
+
};
|
|
32
38
|
|
|
33
39
|
function sha256(value ) {
|
|
34
40
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
@@ -183,6 +189,36 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
183
189
|
function settingsContent(fileName , raw , scope ) {
|
|
184
190
|
return fileName === 'settings.json' && scope !== 'global' ? raw.split(GLOBAL_HOOK_PREFIX).join(PROJECT_HOOK_PREFIX) : raw;
|
|
185
191
|
}
|
|
192
|
+
/** Hook tiers (maint-hooks): with Claude outside `hooks.harnesses` the managed settings carry no
|
|
193
|
+
* `hooks` block. Returns the managed text to write and the hooks it left out, which a merge strips
|
|
194
|
+
* from the live file so an earlier install's entries do not stay behind. */
|
|
195
|
+
function tieredSettings(fileName , raw , context ) {
|
|
196
|
+
if (fileName !== 'settings.json' || context?.hooksOff !== true) return { raw, droppedHooks: null };
|
|
197
|
+
let parsed;
|
|
198
|
+
try { parsed = JSON.parse(raw); } catch { return { raw, droppedHooks: null }; }
|
|
199
|
+
if (!parsed || parsed.hooks === undefined) return { raw, droppedHooks: null };
|
|
200
|
+
const { hooks, ...rest } = parsed;
|
|
201
|
+
return { raw: serializeSettings(rest), droppedHooks: hooks };
|
|
202
|
+
}
|
|
203
|
+
function retiredHooks(scope ) {
|
|
204
|
+
const raw = JSON.stringify(RETIRED_SETTINGS_HOOKS);
|
|
205
|
+
return JSON.parse(scope === 'global' ? raw : raw.split(GLOBAL_HOOK_PREFIX).join(PROJECT_HOOK_PREFIX));
|
|
206
|
+
}
|
|
207
|
+
/** The managed settings text with the retired hook entries added, so a remove strips them as well. */
|
|
208
|
+
function withRetiredHooks(raw , scope ) {
|
|
209
|
+
let parsed;
|
|
210
|
+
try { parsed = JSON.parse(raw); } catch { return raw; }
|
|
211
|
+
const hooks = { ...(parsed.hooks || {}) };
|
|
212
|
+
for (const [event, entries] of Object.entries (retiredHooks(scope))) hooks[event] = [...(hooks[event] || []), ...entries];
|
|
213
|
+
return JSON.stringify({ ...parsed, hooks });
|
|
214
|
+
}
|
|
215
|
+
/** The live settings without DoFlow's own entries of `hooks`; every other key is left alone. */
|
|
216
|
+
function withoutManagedHooks(current , hooks ) {
|
|
217
|
+
if (!hooks || !current || typeof current !== 'object' || current.hooks === undefined) return current;
|
|
218
|
+
const { hooks: liveHooks, ...rest } = current;
|
|
219
|
+
const stripped = stripManagedSettings({ hooks: liveHooks, _: true }, { hooks });
|
|
220
|
+
return stripped?.hooks ? { ...rest, hooks: stripped.hooks } : rest;
|
|
221
|
+
}
|
|
186
222
|
function settingsLedgerResources(ledger , assetId ) {
|
|
187
223
|
const byFile = new Map();
|
|
188
224
|
for (const resource of ledger?.resources || []) {
|
|
@@ -228,7 +264,9 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
228
264
|
// entries, so a drifted file is safe to process rather than a reason to refuse.
|
|
229
265
|
changes.push({ assetId: asset.id, target, operation: 'remove', ownershipIdentity: `doflow:claude:settings:${fileName}`,
|
|
230
266
|
...settingsChangeExtras(fileName), fingerprint: prev.fingerprint,
|
|
231
|
-
managed:
|
|
267
|
+
managed: fileName === 'settings.json'
|
|
268
|
+
? withRetiredHooks(settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), scope)
|
|
269
|
+
: settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope),
|
|
232
270
|
projection: { renderer: SETTINGS_RENDERER } });
|
|
233
271
|
}
|
|
234
272
|
if (previous.get(statuslineScriptName)) {
|
|
@@ -242,7 +280,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
242
280
|
for (const fileName of SETTINGS_FILES) {
|
|
243
281
|
const sourceAbs = path.join(sourceDir, fileName);
|
|
244
282
|
if (!fs.existsSync(sourceAbs)) continue;
|
|
245
|
-
|
|
283
|
+
const tiered = tieredSettings(fileName, settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), context);
|
|
284
|
+
let managedRaw = tiered.raw;
|
|
246
285
|
if (fileName === 'settings.json' && context.statusline && !removing) {
|
|
247
286
|
// --statusline: point Claude Code's statusLine key at the projected script. It rides the
|
|
248
287
|
// same managed settings write as everything else this asset owns — one serialization, one
|
|
@@ -267,7 +306,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
267
306
|
const currentRaw = fs.readFileSync(target, 'utf8');
|
|
268
307
|
let merged;
|
|
269
308
|
try {
|
|
270
|
-
|
|
309
|
+
const live = fileName === 'settings.json' ? withoutManagedHooks(JSON.parse(currentRaw), retiredHooks(scope)) : JSON.parse(currentRaw);
|
|
310
|
+
merged = mergeSettings(withoutManagedHooks(live, tiered.droppedHooks), JSON.parse(managedRaw));
|
|
271
311
|
} catch {
|
|
272
312
|
conflicts.push(`${fileName} is not valid JSON and cannot be merged`);
|
|
273
313
|
continue;
|
|
@@ -358,7 +398,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
358
398
|
for (const fileName of SETTINGS_FILES) {
|
|
359
399
|
const sourceAbs = path.join(sourceDir, fileName);
|
|
360
400
|
if (!fs.existsSync(sourceAbs)) continue;
|
|
361
|
-
const
|
|
401
|
+
const sourceText = tieredSettings(fileName, settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), context).raw;
|
|
402
|
+
const fingerprint = sha256(sourceText);
|
|
362
403
|
const target = settingsTarget(fileName, paths);
|
|
363
404
|
// 'absent' (not 'missing') matches the same convention the instructions asset already uses
|
|
364
405
|
// for "no file here" — 'missing' is reserved by the lifecycle layer's removal reconciliation
|
|
@@ -373,7 +414,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
373
414
|
let managed;
|
|
374
415
|
try {
|
|
375
416
|
current = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
376
|
-
managed = JSON.parse(
|
|
417
|
+
managed = JSON.parse(sourceText);
|
|
377
418
|
} catch {
|
|
378
419
|
conflicts.push(`${fileName} is not valid JSON`);
|
|
379
420
|
statuses.push({ assetId: asset.id, capability: 'settings', status: 'conflict', target });
|
|
@@ -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
|
};
|
|
@@ -17,6 +17,41 @@ const SUPPORTED_EVENTS = new Set([
|
|
|
17
17
|
'UserPromptSubmit', 'SubagentStop', 'Stop', 'SessionStart', 'SubagentStart', 'SessionEnd',
|
|
18
18
|
]);
|
|
19
19
|
|
|
20
|
+
// An earlier DoFlow wired a pre-bash-guard front door into hooks.json and deployed its script. Both
|
|
21
|
+
// are gone from the shipped source; a merge drops the entry from the live file so an updated install
|
|
22
|
+
// does not keep a hook that runs a script nothing refreshes, and the script goes with it. Only the
|
|
23
|
+
// exact command DoFlow shipped is dropped: a user's own hook that happens to name a script of the same
|
|
24
|
+
// file name stays.
|
|
25
|
+
const RETIRED_HOOK_SCRIPT = 'pre-bash-guard.sh';
|
|
26
|
+
const RETIRED_HOOK_COMMAND = 'bash "$(root=$(git rev-parse --show-toplevel 2>/dev/null) && [ -f "$root/.codex/hooks/pre-bash-guard.sh" ] && printf \'%s/.codex/hooks/pre-bash-guard.sh\' "$root" || printf \'%s/hooks/pre-bash-guard.sh\' "${CODEX_HOME:-$HOME/.codex}")"';
|
|
27
|
+
|
|
28
|
+
function withoutRetiredHooks(hooks ) {
|
|
29
|
+
const result = {};
|
|
30
|
+
for (const [event, groups] of Object.entries (hooks || {})) {
|
|
31
|
+
if (!Array.isArray(groups)) { result[event] = groups; continue; }
|
|
32
|
+
const kept = groups.map((group ) => (Array.isArray(group?.hooks)
|
|
33
|
+
? { ...group, hooks: group.hooks.filter((handler ) => handler?.command !== RETIRED_HOOK_COMMAND) }
|
|
34
|
+
: group)).filter((group ) => !Array.isArray(group?.hooks) || group.hooks.length > 0);
|
|
35
|
+
if (kept.length) result[event] = kept;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Whether the retired front door is still on disk as DoFlow's own (it names the bridge call it was written with). */
|
|
41
|
+
function ownsRetiredHookScript(destinationHooksDir , fsImpl = fs) {
|
|
42
|
+
const target = path.join(destinationHooksDir, RETIRED_HOOK_SCRIPT);
|
|
43
|
+
try {
|
|
44
|
+
return fsImpl.lstatSync(target).isFile() && fsImpl.readFileSync(target, 'utf8').includes('doflow_bridge codex PreToolUse pre-bash-guard');
|
|
45
|
+
} catch { return false; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Delete the retired front door when it is still DoFlow's own. */
|
|
49
|
+
function removeRetiredHookScript(destinationHooksDir , fsImpl = fs, dryRun = false) {
|
|
50
|
+
if (!ownsRetiredHookScript(destinationHooksDir, fsImpl)) return 0;
|
|
51
|
+
if (!dryRun) { try { fsImpl.unlinkSync(path.join(destinationHooksDir, RETIRED_HOOK_SCRIPT)); } catch { return 0; } }
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
20
55
|
function validationError(errors , message ) { errors.push(message); }
|
|
21
56
|
|
|
22
57
|
function validateHooksConfig(config ) {
|
|
@@ -148,7 +183,7 @@ function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext
|
|
|
148
183
|
try {
|
|
149
184
|
const existing = JSON.parse(original);
|
|
150
185
|
if (existing?.hooks && desired?.hooks) {
|
|
151
|
-
mergedDesired = { ...existing, ...desired, hooks: mergeHooks(existing.hooks, desired.hooks) };
|
|
186
|
+
mergedDesired = { ...existing, ...desired, hooks: mergeHooks(withoutRetiredHooks(existing.hooks), desired.hooks) };
|
|
152
187
|
}
|
|
153
188
|
} catch {
|
|
154
189
|
// malformed existing will be flagged or left for schema validation
|
|
@@ -167,7 +202,7 @@ function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext
|
|
|
167
202
|
return { ok: false, status: 'conflict', destination, original, changes: [], commands,
|
|
168
203
|
errors: scripts.edited.map((name ) => `Codex hook script '${name}' was modified outside DoFlow`) };
|
|
169
204
|
}
|
|
170
|
-
const scriptsOnly = !configChanged && scripts.stale.length > 0;
|
|
205
|
+
const scriptsOnly = !configChanged && (scripts.stale.length > 0 || ownsRetiredHookScript(destinationHooksDir, fsImpl));
|
|
171
206
|
const changed = configChanged || scriptsOnly;
|
|
172
207
|
// The scripts deployCodexHooks copies beside hooks.json, so a backup of this change holds them too.
|
|
173
208
|
const companionTargets = [...new Set([
|
|
@@ -204,6 +239,7 @@ function deployCodexHooks(plan , { dryRun = false, fsImpl = fs }
|
|
|
204
239
|
fsImpl.copyFileSync(source, target);
|
|
205
240
|
fsImpl.chmodSync(target, fsImpl.statSync(source).mode & 0o777);
|
|
206
241
|
}
|
|
242
|
+
removeRetiredHookScript(plan.destinationHooksDir, fsImpl);
|
|
207
243
|
return { ...plan, applied: true };
|
|
208
244
|
}
|
|
209
245
|
|
|
@@ -220,7 +256,7 @@ function shippedHookScripts(scriptsDir , fsImpl = fs) {
|
|
|
220
256
|
* user edited or added stays. The directory goes only once it is empty. */
|
|
221
257
|
function removeCodexHookScripts({ scriptsDir, destinationHooksDir } = {}, { dryRun = false, fsImpl = fs } = {}) {
|
|
222
258
|
if (!destinationHooksDir || !fsImpl.existsSync(destinationHooksDir)) return { removed: 0 };
|
|
223
|
-
let removed =
|
|
259
|
+
let removed = removeRetiredHookScript(destinationHooksDir, fsImpl, dryRun);
|
|
224
260
|
for (const name of shippedHookScripts(scriptsDir, fsImpl)) {
|
|
225
261
|
const target = path.join(destinationHooksDir, name);
|
|
226
262
|
// lstat, not stat: a symlink (even to a file with the shipped bytes) is not DoFlow's copy.
|
|
@@ -13,8 +13,9 @@ const { configPath, fingerprint: configFingerprint, parseToml, planCodexConfig,
|
|
|
13
13
|
const { renderServer, planCodexMcp, applyCodexMcp, ownedCodexMcpIds, userDefinedCodexMcpIds, serverTableText } = require('./mcp.js');
|
|
14
14
|
const { agentDirectory, discoverCodexAgents, planCodexAgents, applyCodexAgents } = require('./agents.js');
|
|
15
15
|
const { planCodexHooks, deployCodexHooks, removeCodexHookScripts, scriptFingerprint } = require('./hooks.js');
|
|
16
|
-
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices } = require('../copy-tree.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({
|
|
@@ -468,9 +503,22 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
468
503
|
hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json was modified outside DoFlow'] };
|
|
469
504
|
}
|
|
470
505
|
}
|
|
506
|
+
if (hooks.ok) hooks = withStaleScriptReleases(hooks, scriptRows(neutralResources, context));
|
|
471
507
|
return hooks;
|
|
472
508
|
}
|
|
473
509
|
|
|
510
|
+
/** A script row for a name the source no longer ships (a retired front door) is released with the
|
|
511
|
+
* plan, so the ledger does not keep a row for a file the update deletes. The script itself goes by
|
|
512
|
+
* its bytes (deployCodexHooks), so this only drops the row. */
|
|
513
|
+
function withStaleScriptReleases(hooks , rows ) {
|
|
514
|
+
const shipped = new Set(hooks.scriptNames);
|
|
515
|
+
const releases = rows.filter((row ) => !shipped.has(row.identity)).map((row ) => ({ type: 'remove', assetId: row.assetId, identity: row.identity,
|
|
516
|
+
target: row.target, ownershipIdentity: row.ownershipIdentity, kind: row.kind, release: true }));
|
|
517
|
+
if (!releases.length) return hooks;
|
|
518
|
+
return { ...hooks, changes: [...hooks.changes, ...releases],
|
|
519
|
+
...(hooks.status === 'unchanged' ? { status: 'change', scriptsOnly: true } : {}) };
|
|
520
|
+
}
|
|
521
|
+
|
|
474
522
|
/** Agents/hooks share one removing-vs-planning fork: removal always owns both via the neutral
|
|
475
523
|
* ledger's own records, regardless of what native input the caller provided this run. */
|
|
476
524
|
/** A removal drops the rows of the hook scripts too. The scripts themselves go by their bytes
|
|
@@ -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,
|
|
@@ -5,28 +5,37 @@
|
|
|
5
5
|
// GitHub Copilot CLI adapter.
|
|
6
6
|
//
|
|
7
7
|
// Copilot has no single native root the way Claude's `.claude` is one — instructions, skills,
|
|
8
|
-
// agents, and MCP each live under a different path, split further by scope. Skills and
|
|
9
|
-
//
|
|
10
|
-
// pointer is a marker-merged section in a shared file, the same shape
|
|
11
|
-
// MCP servers are entries of one JSON map that DoFlow owns one at a
|
|
12
|
-
// never touching an entry it does not own.
|
|
8
|
+
// agents, and MCP each live under a different path, split further by scope. Skills, agents and the
|
|
9
|
+
// core guidance rules are trees materialised via the shared copy-tree engine in ../copy-tree.ts; the
|
|
10
|
+
// single instruction pointer is a marker-merged section in a shared file, the same shape
|
|
11
|
+
// opencode/pi already use; MCP servers are entries of one JSON map that DoFlow owns one at a
|
|
12
|
+
// time through ../mcp-entries.ts, never touching an entry it does not own.
|
|
13
13
|
//
|
|
14
14
|
// Native paths (see design.md §4's harness-native-surfaces table):
|
|
15
|
-
// instructions -> <projectRoot>/.github/copilot-instructions.md
|
|
16
|
-
//
|
|
15
|
+
// instructions -> <projectRoot>/.github/copilot-instructions.md, the pointer to .doflow/guidance
|
|
16
|
+
// (project only — Copilot CLI also reads a user-level ~/.copilot/copilot-instructions.md,
|
|
17
|
+
// but this adapter does not project a pointer there, so global scope writes none)
|
|
18
|
+
// rules -> <projectRoot>/.github/instructions/ (project) / ~/.copilot/instructions/ (global):
|
|
19
|
+
// the core set (DOFLOW_CORE.md and the files it imports) as `*.instructions.md`
|
|
20
|
+
// with an `applyTo: '**'` header, under instructions/rules/ for the rule files;
|
|
21
|
+
// the full guidance tree stays in .doflow/guidance
|
|
17
22
|
// skills -> ~/.agents/skills (global) / <projectRoot>/.agents/skills (project)
|
|
18
23
|
// agents -> ~/.copilot/agents (global) / <projectRoot>/.github/agents (project)
|
|
19
24
|
// mcp -> ~/.copilot/mcp-config.json (global) / <projectRoot>/.mcp.json (project)
|
|
25
|
+
// hooks -> <projectRoot>/.github/hooks (project) / ~/.copilot/hooks (global): doflow.json plus the
|
|
26
|
+
// doflow-*.sh front doors; the shared bridge tree lands in <root>/.doflow/shared/hooks
|
|
20
27
|
//
|
|
21
28
|
// Evidence: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot,
|
|
22
29
|
// https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills,
|
|
23
30
|
// https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-custom-agents-for-cli,
|
|
24
|
-
// https://docs.github.com/en/copilot/reference/custom-agents-configuration
|
|
31
|
+
// https://docs.github.com/en/copilot/reference/custom-agents-configuration,
|
|
32
|
+
// https://docs.github.com/en/copilot/reference/hooks-reference
|
|
25
33
|
const fs = require('node:fs');
|
|
26
34
|
const path = require('node:path');
|
|
27
35
|
|
|
28
36
|
const { MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
|
|
29
37
|
const { planTree, applyTree, removeTree, verifyTree, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, sourceDirFor, resolveTransform } = require('../copy-tree.js');
|
|
38
|
+
const { renderGuidancePointer } = require('../guidance-pointer.js');
|
|
30
39
|
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
|
|
31
40
|
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
32
41
|
|
|
@@ -49,18 +58,31 @@ const LEGACY_MCP_REGISTRATION = `${HARNESS}:mcp:registration`;
|
|
|
49
58
|
* list per https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills.
|
|
50
59
|
* - `agentsConfigDir` is genuinely asymmetric: `~/.copilot` globally, `.github` at project scope,
|
|
51
60
|
* because that is where Copilot's own custom-agents documentation places each.
|
|
52
|
-
* - `instruction` is project-only: Copilot
|
|
53
|
-
*
|
|
61
|
+
* - `instruction` is project-only: Copilot CLI reads a user-level ~/.copilot/copilot-instructions.md,
|
|
62
|
+
* but this adapter does not project a pointer there, so no user-scope rule is declared and it
|
|
63
|
+
* resolves to null.
|
|
54
64
|
* createCopilotAdapter({ declaredPaths }) is the injection point buildAdapterRegistry() uses;
|
|
55
65
|
* module-level exports delegate to a default-configured instance so direct callers keep their
|
|
56
66
|
* historical shape.
|
|
57
67
|
*/
|
|
58
|
-
function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
68
|
+
function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS], env = process.env } = {}) {
|
|
59
69
|
// Parity note: Copilot's historical nativePaths() derived even its user-scope root from scopeRoot
|
|
60
70
|
// (the CLI passes $HOME there for -g installs), never from a separate homeDir argument — so the
|
|
61
71
|
// resolver is fed no homeDir and "root" keeps resolving exactly as before.
|
|
62
72
|
function nativePaths({ scope, scopeRoot } = {}) {
|
|
63
|
-
|
|
73
|
+
const paths = resolveHarnessPaths(declaredPaths, { scope, scopeRoot });
|
|
74
|
+
// Copilot CLI loads user hooks from $COPILOT_HOME/hooks when COPILOT_HOME is set (hooks reference,
|
|
75
|
+
// "Hook locations"), and DoFlow's generated commands read it too. The path schema has no
|
|
76
|
+
// environment base (src/helper/harness-paths.js), so the override stays here, as Pi's agent dir
|
|
77
|
+
// does; skills, agents, instructions and MCP keep their declared locations.
|
|
78
|
+
if ((scope === 'user' || scope === 'global') && copilotHomeOverride()) paths.hooksConfigDir = copilotHomeOverride();
|
|
79
|
+
return paths;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** $COPILOT_HOME when it is an absolute path, else null (a relative value would move with the cwd). */
|
|
83
|
+
function copilotHomeOverride() {
|
|
84
|
+
const home = typeof env.COPILOT_HOME === 'string' ? env.COPILOT_HOME.trim() : '';
|
|
85
|
+
return path.isAbsolute(home) ? path.resolve(home) : null;
|
|
64
86
|
}
|
|
65
87
|
|
|
66
88
|
/** Discovery keeps the MCP files as plan read them (`mcpSnapshot`), which verify compares against,
|
|
@@ -81,7 +103,7 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
/** Replace only the span between DoFlow's markers. A copilot-instructions.md that exists with no
|
|
84
|
-
* DoFlow section is refused rather than appended to — the same policy opencode/pi
|
|
106
|
+
* DoFlow section is refused rather than appended to — the same policy opencode/pi apply to
|
|
85
107
|
* their own shared instruction files, and for the same reason: where DoFlow's content belongs in a
|
|
86
108
|
* file it doesn't fully own is a decision for the file's owner, not a guess for the installer. Add
|
|
87
109
|
* an empty marker pair to opt in. */
|
|
@@ -108,9 +130,9 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
/** Plan the single instructions change (or none). Global scope always yields no change and no
|
|
111
|
-
* conflict —
|
|
112
|
-
*
|
|
113
|
-
function planInstructionsChange({ assets, found, context, removing, fsImpl } ) {
|
|
133
|
+
* conflict — the user-level ~/.copilot/copilot-instructions.md is not projected, so there is no
|
|
134
|
+
* global target, the same way other adapters skip a capability they do not project at a scope. */
|
|
135
|
+
function planInstructionsChange({ assets, found, scope, scopeRoot, context, removing, fsImpl } ) {
|
|
114
136
|
if (!found.paths.instruction) return { changes: [], conflicts: [] };
|
|
115
137
|
// The marker-managed instruction file is one specific asset: exclude tree-projected rule
|
|
116
138
|
// assets (copilot-rule-instructions), whose `source` is a directory, not a file.
|
|
@@ -118,7 +140,7 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
118
140
|
if (!guidance) return { changes: [], conflicts: [] };
|
|
119
141
|
const outcome = removing
|
|
120
142
|
? { ok: true, operation: 'remove', content: strippedInstruction(found.instruction) }
|
|
121
|
-
: managedInstruction(found.instruction, render({ content: fsImpl.readFileSync(path.resolve(context.repoRoot, guidance.source), 'utf8') }));
|
|
143
|
+
: managedInstruction(found.instruction, render({ content: renderGuidancePointer(fsImpl.readFileSync(path.resolve(context.repoRoot, guidance.source), 'utf8'), { scope, scopeRoot }) }));
|
|
122
144
|
if (!outcome.ok) return { changes: [], conflicts: [outcome.conflict] };
|
|
123
145
|
if (outcome.operation === 'none' || outcome.content === null) return { changes: [], conflicts: [] };
|
|
124
146
|
return {
|
|
@@ -138,7 +160,7 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
138
160
|
* (https://docs.github.com/en/copilot/reference/custom-agents-configuration) requires the
|
|
139
161
|
* `.agent.md` extension, while `core/shared/agent-specs/*.md` — already `name`/`description`
|
|
140
162
|
* YAML-frontmatter files, the same shape `.agent.md` expects — carries a plain `.md` extension.
|
|
141
|
-
* This is the same class of shape mismatch
|
|
163
|
+
* This is the same class of shape mismatch Antigravity's `dir-per-file:agent.md` layout in
|
|
142
164
|
* copy-tree.ts already solves for a directory-per-agent requirement; here the mismatch is only the
|
|
143
165
|
* extension, so a flat rename is enough and no directory restructuring is needed. Declared as a
|
|
144
166
|
* function (rather than a name registered in copy-tree.ts's LAYOUTS) because it is specific to this
|
|
@@ -160,7 +182,7 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
160
182
|
const destDir = copyTreeDestDir(destRoot, asset);
|
|
161
183
|
const sourceDir = sourceDirFor(asset, context, fsImpl, 'Copilot');
|
|
162
184
|
const previousResources = ledgerFileResources(ledger?.resources, HARNESS, asset.id);
|
|
163
|
-
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(ledger?.resources, HARNESS), operation: removing ? 'remove' : 'apply', fsImpl, layout: layout || asset.layout, transform: asset.transform, keepModified: !removing,
|
|
185
|
+
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(ledger?.resources, HARNESS), operation: removing ? 'remove' : 'apply', fsImpl, layout: layout || asset.layout, transform: asset.transform, select: asset.select, keepModified: !removing,
|
|
164
186
|
// Forwarded so the CLI's --force reaches planTree's conflict check; omitting it let
|
|
165
187
|
// planTree's own `force = false` default stand in silently. Gated on `!removing` for the
|
|
166
188
|
// reason codex/index.ts states in full: force heals drift on apply, but a hand-edited file
|
|
@@ -214,7 +236,7 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
214
236
|
for (const asset of treeAssetsFor(assets, renderer)) {
|
|
215
237
|
const destDir = copyTreeDestDir(destRoot, asset);
|
|
216
238
|
const sourceDir = sourceDirFor(asset, context, fsImpl, 'Copilot');
|
|
217
|
-
const result = verifyTree({ sourceDir, destDir, fsImpl, layout: layout || asset.layout, transform: asset.transform, plannedWrites: context?.plannedWrites });
|
|
239
|
+
const result = verifyTree({ sourceDir, destDir, fsImpl, layout: layout || asset.layout, transform: asset.transform, select: asset.select, plannedWrites: context?.plannedWrites });
|
|
218
240
|
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
219
241
|
for (const resource of result.resources) {
|
|
220
242
|
resources.push({
|
|
@@ -268,7 +290,7 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
268
290
|
/** Copilot's MCP entry changes have no asset of their own in core/registry/assets.json (they are a
|
|
269
291
|
* registration side-effect of the selected MCP servers, not a projected asset), so they piggyback
|
|
270
292
|
* on a real asset id already routed to this harness — the same "pseudo-component" technique
|
|
271
|
-
* src/adapters/
|
|
293
|
+
* src/adapters/kiro/index.js#pseudoAssetId uses. The
|
|
272
294
|
* lifecycle layer validates every change's assetId against the harness's actual asset list, so an
|
|
273
295
|
* invented id like a literal 'copilot.mcp' string is rejected as unknown. */
|
|
274
296
|
function pseudoAssetId(assets ) {
|
|
@@ -283,7 +305,7 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
283
305
|
const conflicts = [];
|
|
284
306
|
const removing = context.operation === 'remove';
|
|
285
307
|
|
|
286
|
-
const instructions = planInstructionsChange({ assets, found, context, removing, fsImpl });
|
|
308
|
+
const instructions = planInstructionsChange({ assets, found, scope, scopeRoot, context, removing, fsImpl });
|
|
287
309
|
changes.push(...instructions.changes);
|
|
288
310
|
conflicts.push(...instructions.conflicts);
|
|
289
311
|
|
|
@@ -299,13 +321,21 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
299
321
|
changes.push(...rules.changes);
|
|
300
322
|
conflicts.push(...rules.conflicts);
|
|
301
323
|
|
|
324
|
+
// Hooks: DoFlow's own .github/hooks/doflow.json and doflow-*.sh front doors (renderer
|
|
325
|
+
// copilot-hooks, a flat tree beside any hook files the user keeps there), and the shared bridge
|
|
326
|
+
// tree under ../.doflow, which hooks.shared projects through the plain copy-tree renderer above.
|
|
327
|
+
// Outside `hooks.harnesses` the registry marks the capability unavailable and selects neither.
|
|
328
|
+
const hooks = planTreeAssets({ assets, renderer: 'copilot-hooks', scopeRoot, destRoot: found.paths.hooksConfigDir, layout: null, context, ledger, removing, fsImpl });
|
|
329
|
+
changes.push(...hooks.changes);
|
|
330
|
+
conflicts.push(...hooks.conflicts);
|
|
331
|
+
|
|
302
332
|
const mcpPlan = planMcpEntries({
|
|
303
333
|
...mcpEntriesInput({ paths: found.paths, mcp, mcpAdoptable, ledger, assets }), files: found.mcpSnapshot, removing,
|
|
304
334
|
});
|
|
305
335
|
changes.push(...legacyRegistrationReleases(ledger), ...mcpPlan.changes);
|
|
306
336
|
conflicts.push(...mcpPlan.conflicts);
|
|
307
337
|
|
|
308
|
-
return { changes, conflicts, notices: [...skills.notices, ...agents.notices, ...rules.notices, ...mcpPlan.notices], paths: found.paths };
|
|
338
|
+
return { changes, conflicts, notices: [...skills.notices, ...agents.notices, ...rules.notices, ...hooks.notices, ...mcpPlan.notices], paths: found.paths };
|
|
309
339
|
}
|
|
310
340
|
|
|
311
341
|
function writeChange(change , fsImpl ) {
|
|
@@ -380,6 +410,11 @@ function createCopilotAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
380
410
|
statuses.push(...rules.statuses);
|
|
381
411
|
conflicts.push(...rules.conflicts);
|
|
382
412
|
|
|
413
|
+
const hooks = verifyTreeAssets({ assets, renderer: 'copilot-hooks', destRoot: found.paths.hooksConfigDir, layout: null, context, fsImpl });
|
|
414
|
+
resources.push(...hooks.resources);
|
|
415
|
+
statuses.push(...hooks.statuses);
|
|
416
|
+
conflicts.push(...hooks.conflicts);
|
|
417
|
+
|
|
383
418
|
const mcpResult = verifyMcpEntries({
|
|
384
419
|
...mcpEntriesInput({ paths: found.paths, mcp, mcpAdoptable, ledger, assets }), files: found.mcpSnapshot,
|
|
385
420
|
snapshot: discovery?.mcpSnapshot ?? found.mcpSnapshot, removing: (operation ?? context.operation) === 'remove',
|