@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
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
// Shared copy-tree engine — the generic "own a tree of files" implementation every asset
|
|
6
6
|
// declaring `renderer: "copy-tree"` in core/registry/assets.json needs. Codex's adapter already
|
|
7
7
|
// proved this per-file fingerprint/conflict model for agents and hooks (ownedRemovalPlan,
|
|
8
|
-
// nativeManagedResources); this module generalizes it so
|
|
8
|
+
// nativeManagedResources); this module generalizes it so every adapter's
|
|
9
9
|
// tree-shaped assets (skills, rules, agent-specs, templates, scripts, modes, references) share
|
|
10
|
-
// one implementation instead of
|
|
10
|
+
// one implementation instead of adapter-specific copies.
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const path = require('node:path');
|
|
13
13
|
const crypto = require('node:crypto');
|
|
14
|
+
const { guidanceManifest } = require('./guidance-manifest.js');
|
|
15
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
14
16
|
|
|
15
17
|
function sha256(buf ) {
|
|
16
18
|
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
@@ -39,7 +41,7 @@ const LAYOUTS = {
|
|
|
39
41
|
const ext = path.extname(sourceRel);
|
|
40
42
|
return path.join(sourceRel.slice(0, sourceRel.length - ext.length), `agent${ext || '.md'}`);
|
|
41
43
|
},
|
|
42
|
-
/** `
|
|
44
|
+
/** `RULES.md` -> `RULES.instructions.md` — GitHub's path-specific
|
|
43
45
|
* instructions are discovered by their `.instructions.md` suffix. */
|
|
44
46
|
'instructions-md': (sourceRel ) => {
|
|
45
47
|
const ext = path.extname(sourceRel);
|
|
@@ -59,6 +61,8 @@ const LAYOUTS = {
|
|
|
59
61
|
const ext = path.extname(sourceRel);
|
|
60
62
|
return `${sourceRel.slice(0, sourceRel.length - ext.length)}.json`;
|
|
61
63
|
},
|
|
64
|
+
/** `rules/RULES.md` -> `RULES.md` for flat rule directories. */
|
|
65
|
+
flat: (sourceRel ) => path.basename(sourceRel),
|
|
62
66
|
};
|
|
63
67
|
|
|
64
68
|
/** Resolve a declared layout name to its mapper. Unknown names fail loudly rather than silently
|
|
@@ -70,6 +74,25 @@ function resolveLayout(name ) {
|
|
|
70
74
|
return layout;
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
/** Source-file selectors for projections that intentionally materialize only part of a tree. */
|
|
78
|
+
const SELECTORS = {
|
|
79
|
+
// A dangling import that is not generated would install a core set missing a file the entry file
|
|
80
|
+
// names, silently; the selector fails instead (MCP_INDEX.md is generated and exempt) (107-B C-4).
|
|
81
|
+
'guidance-manifest': (sourceDir , fsImpl ) => {
|
|
82
|
+
const { files, dangling } = guidanceManifest(sourceDir, fsImpl);
|
|
83
|
+
if (dangling.length) throw new Error(`guidance-manifest: DOFLOW_CORE.md imports files that do not exist under ${sourceDir}: ${dangling.join(', ')}`);
|
|
84
|
+
return new Set(files);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Resolve a declared selector name, or return null when a projection mirrors the whole tree. */
|
|
89
|
+
function resolveSelect(name ) {
|
|
90
|
+
if (name == null) return null;
|
|
91
|
+
const selector = SELECTORS[name];
|
|
92
|
+
if (!selector) throw new Error(`Unknown copy-tree select '${name}' (known: ${Object.keys(SELECTORS).join(', ')})`);
|
|
93
|
+
return selector;
|
|
94
|
+
}
|
|
95
|
+
|
|
73
96
|
/** Destination content transforms. Where a harness reads a supported but different file FORMAT
|
|
74
97
|
* than the shared source authors (Copilot's path-specific instructions require an `applyTo`
|
|
75
98
|
* frontmatter header; OpenCode's markdown agents want their own frontmatter vocabulary), the
|
|
@@ -87,10 +110,32 @@ const TRANSFORMS = {
|
|
|
87
110
|
void sourceRel;
|
|
88
111
|
return Buffer.from(`---\napplyTo: '**'\n---\n\n${stripFrontmatter(content.toString('utf8'))}`);
|
|
89
112
|
},
|
|
90
|
-
/**
|
|
91
|
-
|
|
113
|
+
/** `<rule>.md` -> the same body under Antigravity's `trigger: always_on` front-matter. Antigravity
|
|
114
|
+
* silently discards a `.agents/rules/*.md` file without valid front-matter, so the shared core
|
|
115
|
+
* files (which carry none) are loaded only through this header (107-B review C-2). */
|
|
116
|
+
'antigravity-always-on-rules': (sourceRel , content ) => {
|
|
117
|
+
void sourceRel;
|
|
118
|
+
return Buffer.from(`---\ntrigger: always_on\n---\n\n${stripFrontmatter(content.toString('utf8'))}`);
|
|
119
|
+
},
|
|
120
|
+
/** Shared agent spec -> the same file without `access`, plus Claude's read-only limit keys (118 IC-005) */
|
|
121
|
+
'claude-agents': (sourceRel , content ) => {
|
|
92
122
|
void sourceRel;
|
|
93
|
-
return Buffer.from(
|
|
123
|
+
return Buffer.from(applyAgentLimits(content.toString('utf8'), 'claude'));
|
|
124
|
+
},
|
|
125
|
+
/** Shared agent spec -> the same file without `access`; Codex's limit is `unenforced` (the TOML carries it), so no keys are added */
|
|
126
|
+
'codex-agents': (sourceRel , content ) => {
|
|
127
|
+
void sourceRel;
|
|
128
|
+
return Buffer.from(applyAgentLimits(content.toString('utf8'), 'codex'));
|
|
129
|
+
},
|
|
130
|
+
/** Shared agent spec -> the same file without `access`, plus Copilot's read-only limit keys (118 IC-005) */
|
|
131
|
+
'copilot-agents': (sourceRel , content ) => {
|
|
132
|
+
void sourceRel;
|
|
133
|
+
return Buffer.from(applyAgentLimits(content.toString('utf8'), 'copilot'));
|
|
134
|
+
},
|
|
135
|
+
/** Shared agent spec -> the same file without `access`; Antigravity's limit is `unenforced`, so no keys are added */
|
|
136
|
+
'antigravity-agents': (sourceRel , content ) => {
|
|
137
|
+
void sourceRel;
|
|
138
|
+
return Buffer.from(applyAgentLimits(content.toString('utf8'), 'antigravity'));
|
|
94
139
|
},
|
|
95
140
|
/** Shared agent spec frontmatter -> OpenCode's markdown-agent vocabulary */
|
|
96
141
|
'opencode-agents': (sourceRel , content ) => {
|
|
@@ -102,8 +147,80 @@ const TRANSFORMS = {
|
|
|
102
147
|
void sourceRel;
|
|
103
148
|
return Buffer.from(renderKiroAgent(content.toString('utf8')));
|
|
104
149
|
},
|
|
150
|
+
/** Hook tiers (maint-hooks): the same Kiro agent with no `hooks`, for Kiro outside `hooks.harnesses`. */
|
|
151
|
+
'kiro-agents-content': (sourceRel , content ) => {
|
|
152
|
+
void sourceRel;
|
|
153
|
+
return Buffer.from(renderKiroAgent(content.toString('utf8'), { hooks: false }));
|
|
154
|
+
},
|
|
155
|
+
/** Hook tiers: an authored Kiro agent JSON (core/harnesses/kiro/agents) without its `hooks` key. */
|
|
156
|
+
'kiro-agent-content': (sourceRel , content ) => {
|
|
157
|
+
if (!sourceRel.endsWith('.json')) return content;
|
|
158
|
+
const agent = JSON.parse(content.toString('utf8'));
|
|
159
|
+
delete agent.hooks;
|
|
160
|
+
return Buffer.from(`${JSON.stringify(agent, null, 2)}\n`);
|
|
161
|
+
},
|
|
105
162
|
};
|
|
106
163
|
|
|
164
|
+
/** Row 131: core/registry/contracts.json, read once. Its `agentSkillsSpec` holds the Agent Skills
|
|
165
|
+
* spec keys; each contract's `skillFields` the keys that harness documents. */
|
|
166
|
+
const SKILL_CONTRACTS = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', 'contracts.json'), 'utf8'));
|
|
167
|
+
|
|
168
|
+
/** One `skill-frontmatter-<harness>` transform per contract that lists skillFields: a SKILL.md keeps
|
|
169
|
+
* the spec keys and that harness's skillFields at the top level, and every other top-level key moves
|
|
170
|
+
* under `metadata` as a quoted string. Files other than SKILL.md pass through unchanged. */
|
|
171
|
+
for (const contract of SKILL_CONTRACTS.contracts.filter((entry ) => Array.isArray(entry.skillFields))) {
|
|
172
|
+
const allowed = new Set ([...SKILL_CONTRACTS.agentSkillsSpec.skillFields, ...contract.skillFields]);
|
|
173
|
+
TRANSFORMS[`skill-frontmatter-${contract.harness}`] = (sourceRel , content ) => (
|
|
174
|
+
path.basename(sourceRel) === 'SKILL.md' ? Buffer.from(portableSkillFrontmatter(content.toString('utf8'), allowed, sourceRel)) : content);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** A one-line YAML scalar's value. Single-quoted: `''` is one quote. Double-quoted: JSON's escapes
|
|
178
|
+
* (`\"`, `\\`, `\n`, `\t`, `\uXXXX`, ...), which YAML reads the same way; any other escape throws
|
|
179
|
+
* rather than be guessed. Anything else is plain and taken as written. */
|
|
180
|
+
function yamlScalar(value , key , file ) {
|
|
181
|
+
const single = value.match(/^'((?:[^']|'')*)'$/);
|
|
182
|
+
if (single) return single[1].replace(/''/g, "'");
|
|
183
|
+
if (!/^"(?:[^"\\]|\\.)*"$/.test(value)) return value;
|
|
184
|
+
try { return JSON.parse(value); } catch {
|
|
185
|
+
throw new Error(`${file}: '${key}' has a double-quoted value with an escape this projector does not decode`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Move every top-level frontmatter key outside `allowed` under `metadata:`. A moved value is
|
|
190
|
+
* written as a JSON string (valid YAML), a plain scalar's continuation lines joined with a space; a
|
|
191
|
+
* nested map or a block scalar (`|`, `>`) is not handled, so it throws rather than guess, and so does a
|
|
192
|
+
* flow-style `metadata` (`{...}` on the key's line), which block entries cannot be appended to without a
|
|
193
|
+
* YAML parser. `file` names the source in each error. */
|
|
194
|
+
function portableSkillFrontmatter(text , allowed , file = 'SKILL.md') {
|
|
195
|
+
const match = text.match(/^---\n([\s\S]*?\n)---(?=\n|$)/);
|
|
196
|
+
if (!match) return text;
|
|
197
|
+
const entries = [];
|
|
198
|
+
for (const line of match[1].replace(/\n$/, '').split('\n')) {
|
|
199
|
+
// A top-level key, bare or quoted (`"key":`, `'key':`); `prefix` is the length up to its colon.
|
|
200
|
+
const found = line.match(/^(?:"((?:[^"\\]|\\.)+)"|'((?:[^']|'')+)'|([A-Za-z][A-Za-z0-9_-]*))[ \t]*:/);
|
|
201
|
+
const key = found ? (found[1]?.replace(/\\(.)/g, '$1') ?? found[2]?.replace(/''/g, "'") ?? found[3]) : null;
|
|
202
|
+
if (key || !entries.length) entries.push({ key, prefix: found ? found[0].length : 0, lines: [line] });
|
|
203
|
+
else entries[entries.length - 1].lines.push(line);
|
|
204
|
+
}
|
|
205
|
+
const metadataEntry = entries.find((entry) => entry.key === 'metadata');
|
|
206
|
+
if (metadataEntry && /^[ \t]*[^\s#]/.test(metadataEntry.lines[0].slice(metadataEntry.prefix))) {
|
|
207
|
+
throw new Error(`${file}: flow-style metadata cannot take moved keys; write metadata as a block map`);
|
|
208
|
+
}
|
|
209
|
+
const moved = entries.filter((entry) => entry.key && entry.key !== 'metadata' && !allowed.has(entry.key));
|
|
210
|
+
if (!moved.length) return text;
|
|
211
|
+
const metadata = moved.map(({ key, prefix, lines }) => {
|
|
212
|
+
const head = lines[0].slice(prefix).trim();
|
|
213
|
+
if (!head || /^[|>][-+0-9]*$/.test(head)) throw new Error(`${file}: '${key}' has no scalar value to move under metadata`);
|
|
214
|
+
const value = [head, ...lines.slice(1).map((part) => part.trim())].join(' ').trim();
|
|
215
|
+
return ` ${/^[A-Za-z][A-Za-z0-9_-]*$/.test(key ) ? key : JSON.stringify(key)}: ${JSON.stringify(yamlScalar(value, key , file))}`;
|
|
216
|
+
});
|
|
217
|
+
const kept = entries.filter((entry) => !moved.includes(entry));
|
|
218
|
+
const existing = kept.find((entry) => entry.key === 'metadata');
|
|
219
|
+
if (existing) existing.lines.push(...metadata);
|
|
220
|
+
else kept.push({ key: 'metadata', prefix: 9, lines: ['metadata:', ...metadata] });
|
|
221
|
+
return `---\n${kept.flatMap((entry) => entry.lines).join('\n')}\n${text.slice(match[0].length - 3)}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
107
224
|
function resolveTransform(name ) {
|
|
108
225
|
if (!name) return null;
|
|
109
226
|
const transform = TRANSFORMS[name];
|
|
@@ -130,23 +247,113 @@ function renderClaudeOutputStyle(sourceRel , content ) {
|
|
|
130
247
|
return ['---', `name: DoFlow: ${base}`, `description: ${JSON.stringify(description)}`, 'keep-coding-instructions: true', '---', '', text.replace(/\n*$/, ''), ''].join('\n');
|
|
131
248
|
}
|
|
132
249
|
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
|
|
250
|
+
/** One harness's `agentLimits` block from core/registry/harnesses.json, read once. */
|
|
251
|
+
let harnessAgentLimits = null;
|
|
252
|
+
function agentLimitsOf(harnessId ) {
|
|
253
|
+
if (!harnessAgentLimits) {
|
|
254
|
+
harnessAgentLimits = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', 'harnesses.json'), 'utf8')).harnesses;
|
|
255
|
+
}
|
|
256
|
+
const harness = (harnessAgentLimits ).find((item ) => item.id === harnessId);
|
|
257
|
+
if (!harness) throw new Error(`agentLimits: unknown harness '${harnessId}'`);
|
|
258
|
+
return harness.agentLimits || {};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The `apply` keys a harness writes into the file of a `read-only` agent spec (118 IC-003), read from
|
|
262
|
+
* `agentLimits.readOnly`. `null` when the harness's limit is `unenforced`: nothing is written, and the
|
|
263
|
+
* registry says why. */
|
|
264
|
+
function readOnlyApply(harnessId ) {
|
|
265
|
+
const entry = agentLimitsOf(harnessId).readOnly;
|
|
266
|
+
return entry?.status === 'native' ? entry.apply : null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** The MCP servers DoFlow's registry names, read once and sorted: every `core/registry/mcp.json`
|
|
270
|
+
* server (the ones an install writes) and every capability provider's `mcp.server` in
|
|
271
|
+
* capabilities.json (semble, graphify). All are retrieval servers, so a read-only agent keeps them. */
|
|
272
|
+
let registryMcpServers = null;
|
|
273
|
+
function approvedMcpServers() {
|
|
274
|
+
if (!registryMcpServers) {
|
|
275
|
+
const read = (file ) => JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', file), 'utf8'));
|
|
276
|
+
const providers = Object.values(read('capabilities.json').capabilities || {}).flatMap((capability ) => capability.providers || []);
|
|
277
|
+
registryMcpServers = [...new Set ([
|
|
278
|
+
...read('mcp.json').servers.map((server ) => server.id),
|
|
279
|
+
...providers.map((provider ) => provider.mcp?.server).filter(Boolean),
|
|
280
|
+
])].sort();
|
|
281
|
+
}
|
|
282
|
+
return registryMcpServers ;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** One category's tools on a harness: the `map` entry, or for `mcp` the selector of each approved server. */
|
|
286
|
+
function categoryTools(entry , category ) {
|
|
287
|
+
return category === 'mcp' ? approvedMcpServers().map((server) => entry.mcpSelector.replace('{server}', server)) : entry.map[category];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** The categories of a spec's `tool-scope` value (`read, shell, web`), or null when it declares none. */
|
|
291
|
+
function scopeCategories(value ) {
|
|
292
|
+
if (value === undefined) return null;
|
|
293
|
+
return value.split(',').map((item ) => item.trim()).filter(Boolean);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The keys a harness writes for a declared tool scope (row 135), from `agentLimits.toolScope`: the
|
|
297
|
+
* category tools as a comma string or a list, or a closed permission object (`"*": deny`, then an
|
|
298
|
+
* allow per declared tool). `null` when the spec declares no scope or the harness cannot express one
|
|
299
|
+
* (`unenforced`): then nothing is written, never a broader scope. */
|
|
300
|
+
function toolScopeApply(harnessId , scope ) {
|
|
301
|
+
if (!scope) return null;
|
|
302
|
+
const entry = agentLimitsOf(harnessId).toolScope;
|
|
303
|
+
if (entry?.status !== 'native') return null;
|
|
304
|
+
for (const category of scope) {
|
|
305
|
+
if (category !== 'mcp' && !Object.hasOwn(entry.map, category)) throw new Error(`tool-scope: unknown category '${category}' for harness '${harnessId}'`);
|
|
306
|
+
}
|
|
307
|
+
const tools = scope.flatMap((category) => categoryTools(entry, category));
|
|
308
|
+
if (entry.form === 'closed') return { [entry.field]: { '*': 'deny', ...Object.fromEntries(tools.map((tool ) => [tool, 'allow'])) } };
|
|
309
|
+
return { [entry.field]: entry.form === 'comma' ? tools.join(', ') : tools };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** The keys an agent file gains: the scope keys, then the read-only keys. Where both write one key the
|
|
313
|
+
* narrower wins: two lists keep only the tools both name, plus the approved MCP selectors (retrieval
|
|
314
|
+
* servers a read-only agent keeps); two permission objects keep every `deny`. */
|
|
315
|
+
function agentLimitKeys(harnessId , { readOnly, scope } ) {
|
|
316
|
+
const scoped = toolScopeApply(harnessId, scope);
|
|
317
|
+
const limits = readOnly ? readOnlyApply(harnessId) : null;
|
|
318
|
+
if (!scoped || !limits) return scoped || limits;
|
|
319
|
+
const merged = { ...scoped };
|
|
320
|
+
for (const [key, value] of Object.entries(limits)) {
|
|
321
|
+
const have = merged[key];
|
|
322
|
+
if (have === undefined) merged[key] = value;
|
|
323
|
+
else if (Array.isArray(have) && Array.isArray(value)) {
|
|
324
|
+
const mcp = scope?.includes('mcp') ? categoryTools(agentLimitsOf(harnessId).toolScope, 'mcp') : [];
|
|
325
|
+
merged[key] = have.filter((tool ) => value.includes(tool) || mcp.includes(tool));
|
|
326
|
+
}
|
|
327
|
+
else if (have && value && typeof have === 'object' && typeof value === 'object') merged[key] = { ...have, ...value };
|
|
328
|
+
else throw new Error(`agentLimits: harness '${harnessId}' writes '${key}' from both the tool scope and the read-only limit`);
|
|
329
|
+
}
|
|
330
|
+
return merged;
|
|
331
|
+
}
|
|
136
332
|
|
|
137
|
-
/**
|
|
138
|
-
*
|
|
139
|
-
|
|
140
|
-
|
|
333
|
+
/** One frontmatter key as YAML lines: a scalar inline, an array as a flow sequence, an object as an
|
|
334
|
+
* indented block. A key that is not a plain name (`*`, `context7_*`) is double-quoted. */
|
|
335
|
+
function yamlLines(key , value , indent = '') {
|
|
336
|
+
const name = /^[A-Za-z0-9_-]+$/.test(key) ? key : JSON.stringify(key);
|
|
337
|
+
if (Array.isArray(value)) return [`${indent}${name}: ${JSON.stringify(value)}`];
|
|
338
|
+
if (value && typeof value === 'object') {
|
|
339
|
+
return [`${indent}${name}:`, ...Object.entries(value).flatMap(([k, v]) => yamlLines(k, v, `${indent} `))];
|
|
340
|
+
}
|
|
341
|
+
return [`${indent}${name}: ${value}`];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Rewrite a shared agent spec's frontmatter for one harness: `access` and `tool-scope` are never projected under
|
|
345
|
+
* those names; the spec gains the harness's keys for its tool scope and, when `read-only`, its `apply` keys. Body and every other
|
|
346
|
+
* key pass through byte for byte; a file without frontmatter is returned unchanged. */
|
|
347
|
+
function applyAgentLimits(text , harnessId ) {
|
|
141
348
|
const match = text.match(/^---\r?\n([\s\S]*?\r?\n)---(?=\r?\n|$)/);
|
|
142
349
|
if (!match) return text;
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return `${text.slice(0, text.indexOf('\n') + 1)}${kept.join('')}${text.slice(match[0].length - 3)}`;
|
|
350
|
+
const lines = match[1].split(/(?<=\n)/);
|
|
351
|
+
const readOnly = lines.some((line ) => /^access:\s*read-only\s*$/.test(line));
|
|
352
|
+
const scopeLine = lines.find((line ) => /^tool-scope:/.test(line));
|
|
353
|
+
const apply = agentLimitKeys(harnessId, { readOnly, scope: scopeCategories(scopeLine?.replace(/^tool-scope:/, '')) });
|
|
354
|
+
const added = apply ? Object.entries(apply).flatMap(([key, value]) => yamlLines(key, value)).map((line ) => `${line}\n`) : [];
|
|
355
|
+
const kept = lines.filter((line ) => !/^(access|tool-scope):/.test(line));
|
|
356
|
+
return `${text.slice(0, text.indexOf('\n') + 1)}${kept.join('')}${added.join('')}${text.slice(match[0].length - 3)}`;
|
|
150
357
|
}
|
|
151
358
|
|
|
152
359
|
/** The command a Kiro hook runs for one front-door script: the project copy when the git root
|
|
@@ -164,21 +371,21 @@ function kiroHook({ name, trigger, matcher, script, timeout }
|
|
|
164
371
|
};
|
|
165
372
|
}
|
|
166
373
|
|
|
167
|
-
/** The
|
|
374
|
+
/** The two preToolUse hooks every DoFlow-rendered Kiro agent carries. The MCP guard uses the
|
|
168
375
|
* "*" matcher: Kiro documents "@<server>" (every tool of one server) and "*" but no all-MCP
|
|
169
376
|
* matcher, and mcp-tool-guard.sh exits 0 immediately for any non-MCP tool name, so "*" costs one
|
|
170
377
|
* fast no-op for built-in tools and still covers every MCP server. */
|
|
171
378
|
function kiroPreToolUseHooks() {
|
|
172
379
|
return [
|
|
173
380
|
kiroHook({ name: 'doflow-pre-implementation-gate', trigger: 'preToolUse', matcher: 'write', script: 'pre-implement-gate.sh', timeout: 10 }),
|
|
174
|
-
kiroHook({ name: 'doflow-pre-bash-guard', trigger: 'preToolUse', matcher: 'shell', script: 'pre-bash-guard.sh', timeout: 10 }),
|
|
175
381
|
kiroHook({ name: 'doflow-mcp-tool-guard', trigger: 'preToolUse', matcher: '*', script: 'mcp-tool-guard.sh', timeout: 10 }),
|
|
176
382
|
];
|
|
177
383
|
}
|
|
178
384
|
|
|
179
385
|
/** Map one core/shared agent-spec file onto a kiro-cli custom agent. Spec keys other than
|
|
180
|
-
* name/description (model: inherit, effort) are dropped: Kiro inherits the session model.
|
|
181
|
-
|
|
386
|
+
* name/description (model: inherit, effort) are dropped: Kiro inherits the session model. `access` is
|
|
387
|
+
* never projected; the spec's tool scope (narrowed by `apply.tools` when `read-only`) replaces `tools: ["*"]`. */
|
|
388
|
+
function renderKiroAgent(text , { hooks = true } = {}) {
|
|
182
389
|
const fm = {};
|
|
183
390
|
let body = text;
|
|
184
391
|
if (text.startsWith('---')) {
|
|
@@ -191,17 +398,16 @@ function renderKiroAgent(text ) {
|
|
|
191
398
|
body = text.slice(end + 4).replace(/^\n+/, '');
|
|
192
399
|
}
|
|
193
400
|
}
|
|
401
|
+
const limits = agentLimitKeys('kiro', { readOnly: fm.access === 'read-only', scope: scopeCategories(fm['tool-scope']) });
|
|
194
402
|
const agent = {
|
|
195
403
|
name: fm.name || '', description: fm.description || '',
|
|
196
404
|
prompt: `${body.replace(/\s+$/, '')}\n`,
|
|
197
|
-
tools: ['*'],
|
|
198
|
-
hooks: kiroPreToolUseHooks(),
|
|
405
|
+
tools: limits?.tools ?? ['*'],
|
|
406
|
+
...(hooks ? { hooks: kiroPreToolUseHooks() } : {}),
|
|
199
407
|
};
|
|
200
408
|
return `${JSON.stringify(agent, null, 2)}\n`;
|
|
201
409
|
}
|
|
202
410
|
|
|
203
|
-
const OPENCODE_READONLY_AGENTS = new Set(['spec-analyst', 'system-architect', 'quality-guardian', 'research-writer']);
|
|
204
|
-
|
|
205
411
|
/** Map one core/shared agent-spec file onto OpenCode's documented markdown-agent frontmatter
|
|
206
412
|
* (description / mode / permission; model omitted because OpenCode has no 'inherit'). Unknown
|
|
207
413
|
* spec keys (tools, effort) are dropped rather than passed through, so OpenCode never sees
|
|
@@ -222,9 +428,8 @@ function renderOpencodeAgent(text ) {
|
|
|
222
428
|
const lines = ['---'];
|
|
223
429
|
if (fm.description) lines.push(`description: ${JSON.stringify(fm.description)}`);
|
|
224
430
|
lines.push('mode: subagent');
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
431
|
+
const limits = agentLimitKeys('opencode', { readOnly: fm.access === 'read-only', scope: scopeCategories(fm['tool-scope']) });
|
|
432
|
+
if (limits) lines.push(...Object.entries(limits).flatMap(([key, value]) => yamlLines(key, value)));
|
|
228
433
|
lines.push('---', '', body.replace(/\n*$/, ''), '');
|
|
229
434
|
return lines.join('\n');
|
|
230
435
|
}
|
|
@@ -233,11 +438,15 @@ function renderOpencodeAgent(text ) {
|
|
|
233
438
|
* the destination tree beyond checking existence. `relPath` is destination-relative and doubles as
|
|
234
439
|
* the ledger identity, so a layout change relocates the recorded resource too — planTree's
|
|
235
440
|
* relocation handling then removes the old path rather than orphaning it. */
|
|
236
|
-
function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform } ) {
|
|
441
|
+
function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform, select } ) {
|
|
237
442
|
if (!fsImpl.existsSync(sourceDir)) throw new Error(`copy-tree source is missing: ${sourceDir}`);
|
|
238
443
|
const mapRel = typeof layout === 'function' ? layout : resolveLayout(layout);
|
|
239
444
|
const mapContent = typeof transform === 'function' ? transform : resolveTransform(transform);
|
|
240
|
-
const
|
|
445
|
+
const selected = resolveSelect(select)?.(sourceDir, fsImpl);
|
|
446
|
+
const files = walkRelFiles(sourceDir, fsImpl).filter((sourceRel) => {
|
|
447
|
+
if (!selected) return true;
|
|
448
|
+
return selected.has(sourceRel.split(path.sep).join('/'));
|
|
449
|
+
}).map((sourceRel) => {
|
|
241
450
|
const relPath = mapRel(sourceRel);
|
|
242
451
|
const sourceAbs = path.join(sourceDir, sourceRel);
|
|
243
452
|
const destAbs = path.join(destDir, relPath);
|
|
@@ -247,6 +456,27 @@ function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform }
|
|
|
247
456
|
return { files };
|
|
248
457
|
}
|
|
249
458
|
|
|
459
|
+
/** Selected files that map to one destination cannot both be installed safely. */
|
|
460
|
+
function selectedDestinationCollisions(files , sourceDir , destDir ) {
|
|
461
|
+
const sourcesByDestination = new Map ();
|
|
462
|
+
for (const file of files) {
|
|
463
|
+
const key = path.resolve(file.destAbs);
|
|
464
|
+
const sources = sourcesByDestination.get(key) || [];
|
|
465
|
+
sources.push(file);
|
|
466
|
+
sourcesByDestination.set(key, sources);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const destinations = new Set ();
|
|
470
|
+
const conflicts = [];
|
|
471
|
+
for (const [destination, sources] of sourcesByDestination) {
|
|
472
|
+
if (sources.length < 2) continue;
|
|
473
|
+
destinations.add(destination);
|
|
474
|
+
const names = sources.map((file) => path.relative(sourceDir, file.sourceAbs).split(path.sep).join('/'));
|
|
475
|
+
conflicts.push(`selected files ${names.map((name) => `'${name}'`).join(' and ')} map to the same destination '${path.relative(destDir, destination).split(path.sep).join('/')}'`);
|
|
476
|
+
}
|
|
477
|
+
return { destinations, conflicts };
|
|
478
|
+
}
|
|
479
|
+
|
|
250
480
|
/** A file's identity on disk (device + inode), or null when the path does not resolve to one. */
|
|
251
481
|
function fileId(file , fsImpl ) {
|
|
252
482
|
try {
|
|
@@ -292,7 +522,7 @@ function sameFileIndex(paths , fsImpl = fs)
|
|
|
292
522
|
* `siblingReplaced` lists the harnesses whose recorded bytes this plan replaces: the file matches
|
|
293
523
|
* neither the source nor this harness's own record, only a sibling's (forced or not).
|
|
294
524
|
*/
|
|
295
|
-
function planTree({ sourceDir, destDir, previousResources = [], operation = 'apply', fsImpl = fs, layout, transform, force = false, keepModified = false, siblingFingerprints = new Map() } ) {
|
|
525
|
+
function planTree({ sourceDir, destDir, previousResources = [], operation = 'apply', fsImpl = fs, layout, transform, select, force = false, keepModified = false, siblingFingerprints = new Map() } ) {
|
|
296
526
|
const prevByPath = new Map (previousResources.map((resource ) => [resource.relPath, resource]));
|
|
297
527
|
const changes = [];
|
|
298
528
|
const conflicts = [];
|
|
@@ -307,7 +537,7 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
|
|
|
307
537
|
const sourceFingerprint = (destAbs ) => {
|
|
308
538
|
if (sourceByDest === undefined) {
|
|
309
539
|
sourceByDest = sourceDir && fsImpl.existsSync(sourceDir)
|
|
310
|
-
? new Map (discoverTree({ sourceDir, destDir, fsImpl, layout, transform }).files.map((file) => [file.destAbs, file.fingerprint]))
|
|
540
|
+
? new Map (discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select }).files.map((file) => [file.destAbs, file.fingerprint]))
|
|
311
541
|
: new Map();
|
|
312
542
|
}
|
|
313
543
|
return sourceByDest.get(destAbs);
|
|
@@ -335,7 +565,7 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
|
|
|
335
565
|
// Untampered on removal means the same two signals the apply path below already accepts: the
|
|
336
566
|
// bytes this harness last recorded, or the bytes the current source would write. The second
|
|
337
567
|
// one matters because a destination tree can be claimed by several harnesses (scripts.doflow
|
|
338
|
-
// is one `<project>/.doflow/scripts` for claude, codex and
|
|
568
|
+
// is one `<project>/.doflow/scripts` for claude, codex and others), so a sibling's update
|
|
339
569
|
// legitimately rewrites files this harness's rows still describe — the same "a sibling
|
|
340
570
|
// changed bytes my row still describes" case the apply path had to be taught, arriving here
|
|
341
571
|
// as an un-releasable claim instead of a refused install. A hand edit matches neither and is
|
|
@@ -366,10 +596,18 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
|
|
|
366
596
|
return { changes, conflicts, kept, siblingReplaced: [] };
|
|
367
597
|
}
|
|
368
598
|
|
|
369
|
-
const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform });
|
|
599
|
+
const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select });
|
|
600
|
+
const collisions = select == null
|
|
601
|
+
? { destinations: new Set (), conflicts: [] }
|
|
602
|
+
: selectedDestinationCollisions(files, sourceDir, destDir);
|
|
603
|
+
conflicts.push(...collisions.conflicts);
|
|
370
604
|
producedSameFile = sameFileIndex(files.map((file) => file.destAbs), fsImpl);
|
|
371
605
|
const satisfiedAtSameLocation = new Set();
|
|
372
606
|
for (const file of files) {
|
|
607
|
+
if (collisions.destinations.has(path.resolve(file.destAbs))) {
|
|
608
|
+
satisfiedAtSameLocation.add(file.relPath);
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
373
611
|
const prev = prevByPath.get(file.relPath);
|
|
374
612
|
// prev.target === undefined means "location unknown" (pre-this-fix ledger data, or a
|
|
375
613
|
// caller-constructed previousResources entry) — treat as same-location, matching pre-fix
|
|
@@ -442,16 +680,18 @@ function applyTree({ changes = [], fsImpl = fs, transform } )
|
|
|
442
680
|
/** Why a removal the runtime prune planned may no longer run, or null when it still may: the
|
|
443
681
|
* prune's plan-time proof (src/lifecycle/index.js pruneRetiredRuntimeFiles) re-run right before the
|
|
444
682
|
* unlink, so a directory swapped for a symlink, or a file linked to a shipped one, after the plan was
|
|
445
|
-
* made is not deleted through. `guard` is `{ root, source }`: the tree
|
|
683
|
+
* made is not deleted through. `guard` is `{ root, source, scope }`: the tree, the source it projects
|
|
684
|
+
* and the scope root the tree must resolve inside (a tree linked out of its scope is refused).
|
|
446
685
|
* One walk of the source and one stat per shipped path per tree per call, cached in `cache`. */
|
|
447
686
|
function pruneGuardRefusal(change , cache , fsImpl ) {
|
|
448
|
-
const { root, source } = change.pruneGuard;
|
|
449
|
-
const key = `${root}\0${source}`;
|
|
687
|
+
const { root, source, scope } = change.pruneGuard;
|
|
688
|
+
const key = `${root}\0${source}\0${scope ?? ''}`;
|
|
450
689
|
if (!cache.has(key)) {
|
|
451
690
|
const produced = fsImpl.existsSync(source) ? discoverTree({ sourceDir: source, destDir: root, fsImpl }).files.map((file ) => file.destAbs) : [];
|
|
452
|
-
cache.set(key, { realRoot: fsImpl.realpathSync(root), sameFile: sameFileIndex(produced, fsImpl) });
|
|
691
|
+
cache.set(key, { realRoot: fsImpl.realpathSync(root), realScope: scope ? fsImpl.realpathSync(scope) : null, sameFile: sameFileIndex(produced, fsImpl) });
|
|
453
692
|
}
|
|
454
|
-
const { realRoot, sameFile } = cache.get(key);
|
|
693
|
+
const { realRoot, realScope, sameFile } = cache.get(key);
|
|
694
|
+
if (realScope && !realRoot.startsWith(`${realScope}${path.sep}`)) return `${root} resolves outside the scope root ${scope}`;
|
|
455
695
|
if (!fsImpl.realpathSync(change.target).startsWith(`${realRoot}${path.sep}`)) return `it resolves outside ${root}`;
|
|
456
696
|
const shipped = sameFile(change.target);
|
|
457
697
|
return shipped ? `it is the same file as ${shipped}, which this version ships` : null;
|
|
@@ -500,12 +740,17 @@ function pruneEmptyAncestors(startDir , { fsImpl = fs }
|
|
|
500
740
|
* `plannedWrites` lists the destinations the run being verified wrote. An absent source file is
|
|
501
741
|
* not reported in general, because a user may delete one, but an absent file this run wrote means
|
|
502
742
|
* the run lost it (a case-only rename once deleted the file it had just written) and is a conflict. */
|
|
503
|
-
function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, plannedWrites = [] } ) {
|
|
504
|
-
const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform });
|
|
743
|
+
function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, select, plannedWrites = [] } ) {
|
|
744
|
+
const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select });
|
|
505
745
|
const written = new Set(plannedWrites);
|
|
506
746
|
const resources = [];
|
|
507
747
|
const conflicts = [];
|
|
748
|
+
const collisions = select == null
|
|
749
|
+
? { destinations: new Set (), conflicts: [] }
|
|
750
|
+
: selectedDestinationCollisions(files, sourceDir, destDir);
|
|
751
|
+
conflicts.push(...collisions.conflicts);
|
|
508
752
|
for (const file of files) {
|
|
753
|
+
if (collisions.destinations.has(path.resolve(file.destAbs))) continue;
|
|
509
754
|
if (!file.exists) {
|
|
510
755
|
if (written.has(file.destAbs)) conflicts.push(`${file.relPath} was written by this run but is missing`);
|
|
511
756
|
continue;
|
|
@@ -517,7 +762,7 @@ function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, planne
|
|
|
517
762
|
return { ok: conflicts.length === 0, resources, conflicts };
|
|
518
763
|
}
|
|
519
764
|
|
|
520
|
-
// ---- per-adapter copy-tree glue, shared so
|
|
765
|
+
// ---- per-adapter copy-tree glue, shared so each adapter doesn't carry its own copy ----
|
|
521
766
|
|
|
522
767
|
/** Filter a harness's asset list down to the ones this engine handles. */
|
|
523
768
|
function copyTreeAssets(assets ) {
|
|
@@ -617,4 +862,4 @@ function ledgerFileResources(resources , harness , assetId )
|
|
|
617
862
|
.map((resource ) => ({ relPath: resource.identity, fingerprint: resource.fingerprint, target: resource.target }));
|
|
618
863
|
}
|
|
619
864
|
|
|
620
|
-
module.exports = { kiroHook, kiroHookCommand, kiroPreToolUseHooks, walkRelFiles, discoverTree, sameFileIndex, planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, sharedTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveLayout, LAYOUTS, resolveTransform, TRANSFORMS, fingerprint, pruneEmptyAncestors, readJson, sourceDirFor };
|
|
865
|
+
module.exports = { readOnlyApply, toolScopeApply, approvedMcpServers, agentLimitKeys, kiroHook, kiroHookCommand, kiroPreToolUseHooks, walkRelFiles, discoverTree, sameFileIndex, planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, sharedTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveLayout, LAYOUTS, resolveSelect, SELECTORS, resolveTransform, TRANSFORMS, portableSkillFrontmatter, fingerprint, pruneEmptyAncestors, readJson, sourceDirFor };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The shared, harness-neutral core set: the breadth-first @-import walk rooted at DOFLOW_CORE.md.
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
// Generated per install, excluded by name rather than forgotten.
|
|
8
|
+
//
|
|
9
|
+
// MCP_INDEX.md and everything it imports under guidance/mcp/ are written at install time from the
|
|
10
|
+
// MCP servers the user actually selected. Their size is a property of the machine, not of
|
|
11
|
+
// DoFlow-authored content, and it differs between two correct installs — so the import walk stops
|
|
12
|
+
// at MCP_INDEX.md and does not descend into its @mcp/* imports.
|
|
13
|
+
const GENERATED_EXCLUSIONS = new Map ([
|
|
14
|
+
['MCP_INDEX.md', 'generated per install from the servers actually selected; size varies per machine'],
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function importsIn(text ) {
|
|
18
|
+
return [...text.matchAll(/^@(\S+)$/gm)].map(([, target]) => target);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The first path segment of `rel` (relative to `root`) that is a symlink, or null. */
|
|
22
|
+
function symlinkedSegment(root , rel , fsImpl ) {
|
|
23
|
+
if (typeof fsImpl.lstatSync !== 'function') return null;
|
|
24
|
+
let current = root;
|
|
25
|
+
for (const segment of rel.split('/')) {
|
|
26
|
+
current = path.join(current, segment);
|
|
27
|
+
if (fsImpl.lstatSync(current).isSymbolicLink()) return path.relative(root, current).split(path.sep).join('/');
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function guidanceManifest(root , fsImpl = fs) {
|
|
33
|
+
const rootFile = 'DOFLOW_CORE.md';
|
|
34
|
+
if (!fsImpl.existsSync(path.join(root, rootFile))) {
|
|
35
|
+
throw new Error(`guidanceManifest root is missing ${rootFile}: ${root}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const files = [];
|
|
39
|
+
const generated = [];
|
|
40
|
+
const dangling = [];
|
|
41
|
+
const seen = new Set ();
|
|
42
|
+
const queue = [rootFile];
|
|
43
|
+
|
|
44
|
+
while (queue.length) {
|
|
45
|
+
const rel = path.posix.normalize(queue.shift() .replace(/\\/g, '/'));
|
|
46
|
+
if (seen.has(rel)) continue;
|
|
47
|
+
seen.add(rel);
|
|
48
|
+
if (GENERATED_EXCLUSIONS.has(rel)) { generated.push(rel); continue; }
|
|
49
|
+
|
|
50
|
+
// The core set is a closed set of files under the guidance root. An import that leaves it,
|
|
51
|
+
// or that is (or sits under) a symlink, is not something the copy-tree engine installs, so it
|
|
52
|
+
// is refused here rather than reported as part of a set that is never written (107-B review C-4).
|
|
53
|
+
if (rel === '..' || rel.startsWith('../') || path.posix.isAbsolute(rel)) {
|
|
54
|
+
throw new Error(`guidanceManifest: ${rel} escapes the guidance root: ${root}`);
|
|
55
|
+
}
|
|
56
|
+
const full = path.join(root, ...rel.split('/'));
|
|
57
|
+
if (!fsImpl.existsSync(full)) { dangling.push(rel); continue; }
|
|
58
|
+
const linked = symlinkedSegment(root, rel, fsImpl);
|
|
59
|
+
if (linked) throw new Error(`guidanceManifest: ${rel} is or sits under a symlink (${linked}); the copy-tree engine does not install symlinks: ${root}`);
|
|
60
|
+
const text = fsImpl.readFileSync(full, 'utf8');
|
|
61
|
+
files.push(rel);
|
|
62
|
+
for (const target of importsIn(text)) {
|
|
63
|
+
if (path.posix.isAbsolute(target)) throw new Error(`guidanceManifest: ${rel} imports the absolute path ${target}: ${root}`);
|
|
64
|
+
queue.push(path.posix.normalize(path.posix.join(path.posix.dirname(rel), target)));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { files, generated, dangling };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { GENERATED_EXCLUSIONS, guidanceManifest };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const GUIDANCE_TOKEN = '{{GUIDANCE}}';
|
|
6
|
+
|
|
7
|
+
function renderGuidancePointer(content , { scope, scopeRoot } ) {
|
|
8
|
+
if (scope !== 'global' && scope !== 'project') throw new Error(`Unknown guidance pointer scope '${scope}'`);
|
|
9
|
+
if (typeof content !== 'string') throw new TypeError('Guidance pointer content must be a string');
|
|
10
|
+
|
|
11
|
+
const guidancePath = scope === 'global'
|
|
12
|
+
? path.resolve(scopeRoot, '.doflow', 'guidance').replace(/\\/g, '/')
|
|
13
|
+
: '.doflow/guidance';
|
|
14
|
+
const rendered = content.split(GUIDANCE_TOKEN).join(guidancePath);
|
|
15
|
+
if (rendered.includes(GUIDANCE_TOKEN)) throw new Error('Guidance pointer contains an unresolved token');
|
|
16
|
+
return rendered;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { renderGuidancePointer };
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// adapter). verifyHookCommands and the two private helpers it calls, commandScriptNames and
|
|
5
|
+
// Hook-command verification, kept apart from codex/hooks.ts because it does not depend on the
|
|
6
|
+
// harness (unlike SUPPORTED_EVENTS, validateHooksConfig, and classifyClaudeGuardrails, which
|
|
7
|
+
// encode genuine per-harness differences and stay in each adapter). verifyHookCommands and the two private helpers it calls, commandScriptNames and
|
|
9
8
|
// hookHandlers, check that a hook config's command handlers resolve to a real, executable script
|
|
10
9
|
// in the harness's own hooks directory — that check does not vary by harness, so it lives once.
|
|
11
10
|
const fs = require('node:fs');
|
|
@@ -23,12 +22,9 @@ function hookHandlers(config ) {
|
|
|
23
22
|
(Array.isArray(group?.hooks) ? group.hooks : []).map((handler ) => ({ event, handler }))));
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
// On `trusted`:
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// a running CLI session. It reports trust as a prerequisite whose shape it observed, not one it
|
|
30
|
-
// bypassed or assumed satisfied. That reading is identical for both harnesses, which is part of
|
|
31
|
-
// why this function is shared.
|
|
25
|
+
// On `trusted`: Codex gates hook execution behind its own trust prompt, through its config.
|
|
26
|
+
// DoFlow does not verify that gate live; doing so would need a running CLI session. It reports
|
|
27
|
+
// trust as a prerequisite whose shape it observed, not one it bypassed or assumed satisfied.
|
|
32
28
|
// Windows carries no executable permission bits — statSync().mode always reports 0666/0444 and
|
|
33
29
|
// chmodSync can only toggle the read-only flag — so the POSIX "not executable" failure mode
|
|
34
30
|
// cannot exist there. Requiring the bit would refuse every hooks install on win32 (the scripts
|