@khoavu882/doflow 2.0.0-beta.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -3
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
- package/core/harnesses/kiro/hooks/session-start.sh +12 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
- package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
- package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +72 -80
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +60 -33
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1542 -277
- package/core/registry/lifecycle.json +2837 -37
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +701 -30
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +154 -35
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +24 -23
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +7 -9
- package/core/shared/skills/do-plan/SKILL.md +29 -19
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +226 -113
- package/src/adapters/claude/index.js +39 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +100 -22
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +294 -48
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +38 -13
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +120 -0
- package/src/adapters/pi/index.js +48 -16
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +3 -2
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +3 -2
- package/src/cli/index.js +338 -33
- package/src/cli/runtime-commands.js +243 -52
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +74 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +434 -86
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +686 -32
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +514 -0
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +27 -12
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +221 -277
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +100 -0
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +1106 -245
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +41 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +77 -10
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +3 -3
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +127 -16
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +402 -73
- package/src/runtime/retrieval-record.js +293 -0
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +63 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +290 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +202 -56
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +440 -65
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -5
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -210
- package/src/adapters/gemini/index.js +0 -390
|
@@ -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
|
|
@@ -177,8 +384,9 @@ function kiroPreToolUseHooks() {
|
|
|
177
384
|
}
|
|
178
385
|
|
|
179
386
|
/** 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
|
-
|
|
387
|
+
* name/description (model: inherit, effort) are dropped: Kiro inherits the session model. `access` is
|
|
388
|
+
* never projected; the spec's tool scope (narrowed by `apply.tools` when `read-only`) replaces `tools: ["*"]`. */
|
|
389
|
+
function renderKiroAgent(text , { hooks = true } = {}) {
|
|
182
390
|
const fm = {};
|
|
183
391
|
let body = text;
|
|
184
392
|
if (text.startsWith('---')) {
|
|
@@ -191,17 +399,16 @@ function renderKiroAgent(text ) {
|
|
|
191
399
|
body = text.slice(end + 4).replace(/^\n+/, '');
|
|
192
400
|
}
|
|
193
401
|
}
|
|
402
|
+
const limits = agentLimitKeys('kiro', { readOnly: fm.access === 'read-only', scope: scopeCategories(fm['tool-scope']) });
|
|
194
403
|
const agent = {
|
|
195
404
|
name: fm.name || '', description: fm.description || '',
|
|
196
405
|
prompt: `${body.replace(/\s+$/, '')}\n`,
|
|
197
|
-
tools: ['*'],
|
|
198
|
-
hooks: kiroPreToolUseHooks(),
|
|
406
|
+
tools: limits?.tools ?? ['*'],
|
|
407
|
+
...(hooks ? { hooks: kiroPreToolUseHooks() } : {}),
|
|
199
408
|
};
|
|
200
409
|
return `${JSON.stringify(agent, null, 2)}\n`;
|
|
201
410
|
}
|
|
202
411
|
|
|
203
|
-
const OPENCODE_READONLY_AGENTS = new Set(['spec-analyst', 'system-architect', 'quality-guardian', 'research-writer']);
|
|
204
|
-
|
|
205
412
|
/** Map one core/shared agent-spec file onto OpenCode's documented markdown-agent frontmatter
|
|
206
413
|
* (description / mode / permission; model omitted because OpenCode has no 'inherit'). Unknown
|
|
207
414
|
* spec keys (tools, effort) are dropped rather than passed through, so OpenCode never sees
|
|
@@ -222,9 +429,8 @@ function renderOpencodeAgent(text ) {
|
|
|
222
429
|
const lines = ['---'];
|
|
223
430
|
if (fm.description) lines.push(`description: ${JSON.stringify(fm.description)}`);
|
|
224
431
|
lines.push('mode: subagent');
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
432
|
+
const limits = agentLimitKeys('opencode', { readOnly: fm.access === 'read-only', scope: scopeCategories(fm['tool-scope']) });
|
|
433
|
+
if (limits) lines.push(...Object.entries(limits).flatMap(([key, value]) => yamlLines(key, value)));
|
|
228
434
|
lines.push('---', '', body.replace(/\n*$/, ''), '');
|
|
229
435
|
return lines.join('\n');
|
|
230
436
|
}
|
|
@@ -233,11 +439,15 @@ function renderOpencodeAgent(text ) {
|
|
|
233
439
|
* the destination tree beyond checking existence. `relPath` is destination-relative and doubles as
|
|
234
440
|
* the ledger identity, so a layout change relocates the recorded resource too — planTree's
|
|
235
441
|
* relocation handling then removes the old path rather than orphaning it. */
|
|
236
|
-
function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform } ) {
|
|
442
|
+
function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform, select } ) {
|
|
237
443
|
if (!fsImpl.existsSync(sourceDir)) throw new Error(`copy-tree source is missing: ${sourceDir}`);
|
|
238
444
|
const mapRel = typeof layout === 'function' ? layout : resolveLayout(layout);
|
|
239
445
|
const mapContent = typeof transform === 'function' ? transform : resolveTransform(transform);
|
|
240
|
-
const
|
|
446
|
+
const selected = resolveSelect(select)?.(sourceDir, fsImpl);
|
|
447
|
+
const files = walkRelFiles(sourceDir, fsImpl).filter((sourceRel) => {
|
|
448
|
+
if (!selected) return true;
|
|
449
|
+
return selected.has(sourceRel.split(path.sep).join('/'));
|
|
450
|
+
}).map((sourceRel) => {
|
|
241
451
|
const relPath = mapRel(sourceRel);
|
|
242
452
|
const sourceAbs = path.join(sourceDir, sourceRel);
|
|
243
453
|
const destAbs = path.join(destDir, relPath);
|
|
@@ -247,6 +457,27 @@ function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform }
|
|
|
247
457
|
return { files };
|
|
248
458
|
}
|
|
249
459
|
|
|
460
|
+
/** Selected files that map to one destination cannot both be installed safely. */
|
|
461
|
+
function selectedDestinationCollisions(files , sourceDir , destDir ) {
|
|
462
|
+
const sourcesByDestination = new Map ();
|
|
463
|
+
for (const file of files) {
|
|
464
|
+
const key = path.resolve(file.destAbs);
|
|
465
|
+
const sources = sourcesByDestination.get(key) || [];
|
|
466
|
+
sources.push(file);
|
|
467
|
+
sourcesByDestination.set(key, sources);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const destinations = new Set ();
|
|
471
|
+
const conflicts = [];
|
|
472
|
+
for (const [destination, sources] of sourcesByDestination) {
|
|
473
|
+
if (sources.length < 2) continue;
|
|
474
|
+
destinations.add(destination);
|
|
475
|
+
const names = sources.map((file) => path.relative(sourceDir, file.sourceAbs).split(path.sep).join('/'));
|
|
476
|
+
conflicts.push(`selected files ${names.map((name) => `'${name}'`).join(' and ')} map to the same destination '${path.relative(destDir, destination).split(path.sep).join('/')}'`);
|
|
477
|
+
}
|
|
478
|
+
return { destinations, conflicts };
|
|
479
|
+
}
|
|
480
|
+
|
|
250
481
|
/** A file's identity on disk (device + inode), or null when the path does not resolve to one. */
|
|
251
482
|
function fileId(file , fsImpl ) {
|
|
252
483
|
try {
|
|
@@ -292,7 +523,7 @@ function sameFileIndex(paths , fsImpl = fs)
|
|
|
292
523
|
* `siblingReplaced` lists the harnesses whose recorded bytes this plan replaces: the file matches
|
|
293
524
|
* neither the source nor this harness's own record, only a sibling's (forced or not).
|
|
294
525
|
*/
|
|
295
|
-
function planTree({ sourceDir, destDir, previousResources = [], operation = 'apply', fsImpl = fs, layout, transform, force = false, keepModified = false, siblingFingerprints = new Map() } ) {
|
|
526
|
+
function planTree({ sourceDir, destDir, previousResources = [], operation = 'apply', fsImpl = fs, layout, transform, select, force = false, keepModified = false, siblingFingerprints = new Map() } ) {
|
|
296
527
|
const prevByPath = new Map (previousResources.map((resource ) => [resource.relPath, resource]));
|
|
297
528
|
const changes = [];
|
|
298
529
|
const conflicts = [];
|
|
@@ -307,7 +538,7 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
|
|
|
307
538
|
const sourceFingerprint = (destAbs ) => {
|
|
308
539
|
if (sourceByDest === undefined) {
|
|
309
540
|
sourceByDest = sourceDir && fsImpl.existsSync(sourceDir)
|
|
310
|
-
? new Map (discoverTree({ sourceDir, destDir, fsImpl, layout, transform }).files.map((file) => [file.destAbs, file.fingerprint]))
|
|
541
|
+
? new Map (discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select }).files.map((file) => [file.destAbs, file.fingerprint]))
|
|
311
542
|
: new Map();
|
|
312
543
|
}
|
|
313
544
|
return sourceByDest.get(destAbs);
|
|
@@ -335,7 +566,7 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
|
|
|
335
566
|
// Untampered on removal means the same two signals the apply path below already accepts: the
|
|
336
567
|
// bytes this harness last recorded, or the bytes the current source would write. The second
|
|
337
568
|
// one matters because a destination tree can be claimed by several harnesses (scripts.doflow
|
|
338
|
-
// is one `<project>/.doflow/scripts` for claude, codex and
|
|
569
|
+
// is one `<project>/.doflow/scripts` for claude, codex and others), so a sibling's update
|
|
339
570
|
// legitimately rewrites files this harness's rows still describe — the same "a sibling
|
|
340
571
|
// changed bytes my row still describes" case the apply path had to be taught, arriving here
|
|
341
572
|
// as an un-releasable claim instead of a refused install. A hand edit matches neither and is
|
|
@@ -366,10 +597,18 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
|
|
|
366
597
|
return { changes, conflicts, kept, siblingReplaced: [] };
|
|
367
598
|
}
|
|
368
599
|
|
|
369
|
-
const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform });
|
|
600
|
+
const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select });
|
|
601
|
+
const collisions = select == null
|
|
602
|
+
? { destinations: new Set (), conflicts: [] }
|
|
603
|
+
: selectedDestinationCollisions(files, sourceDir, destDir);
|
|
604
|
+
conflicts.push(...collisions.conflicts);
|
|
370
605
|
producedSameFile = sameFileIndex(files.map((file) => file.destAbs), fsImpl);
|
|
371
606
|
const satisfiedAtSameLocation = new Set();
|
|
372
607
|
for (const file of files) {
|
|
608
|
+
if (collisions.destinations.has(path.resolve(file.destAbs))) {
|
|
609
|
+
satisfiedAtSameLocation.add(file.relPath);
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
373
612
|
const prev = prevByPath.get(file.relPath);
|
|
374
613
|
// prev.target === undefined means "location unknown" (pre-this-fix ledger data, or a
|
|
375
614
|
// caller-constructed previousResources entry) — treat as same-location, matching pre-fix
|
|
@@ -442,16 +681,18 @@ function applyTree({ changes = [], fsImpl = fs, transform } )
|
|
|
442
681
|
/** Why a removal the runtime prune planned may no longer run, or null when it still may: the
|
|
443
682
|
* prune's plan-time proof (src/lifecycle/index.js pruneRetiredRuntimeFiles) re-run right before the
|
|
444
683
|
* 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
|
|
684
|
+
* made is not deleted through. `guard` is `{ root, source, scope }`: the tree, the source it projects
|
|
685
|
+
* and the scope root the tree must resolve inside (a tree linked out of its scope is refused).
|
|
446
686
|
* One walk of the source and one stat per shipped path per tree per call, cached in `cache`. */
|
|
447
687
|
function pruneGuardRefusal(change , cache , fsImpl ) {
|
|
448
|
-
const { root, source } = change.pruneGuard;
|
|
449
|
-
const key = `${root}\0${source}`;
|
|
688
|
+
const { root, source, scope } = change.pruneGuard;
|
|
689
|
+
const key = `${root}\0${source}\0${scope ?? ''}`;
|
|
450
690
|
if (!cache.has(key)) {
|
|
451
691
|
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) });
|
|
692
|
+
cache.set(key, { realRoot: fsImpl.realpathSync(root), realScope: scope ? fsImpl.realpathSync(scope) : null, sameFile: sameFileIndex(produced, fsImpl) });
|
|
453
693
|
}
|
|
454
|
-
const { realRoot, sameFile } = cache.get(key);
|
|
694
|
+
const { realRoot, realScope, sameFile } = cache.get(key);
|
|
695
|
+
if (realScope && !realRoot.startsWith(`${realScope}${path.sep}`)) return `${root} resolves outside the scope root ${scope}`;
|
|
455
696
|
if (!fsImpl.realpathSync(change.target).startsWith(`${realRoot}${path.sep}`)) return `it resolves outside ${root}`;
|
|
456
697
|
const shipped = sameFile(change.target);
|
|
457
698
|
return shipped ? `it is the same file as ${shipped}, which this version ships` : null;
|
|
@@ -500,12 +741,17 @@ function pruneEmptyAncestors(startDir , { fsImpl = fs }
|
|
|
500
741
|
* `plannedWrites` lists the destinations the run being verified wrote. An absent source file is
|
|
501
742
|
* not reported in general, because a user may delete one, but an absent file this run wrote means
|
|
502
743
|
* 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 });
|
|
744
|
+
function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, select, plannedWrites = [] } ) {
|
|
745
|
+
const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select });
|
|
505
746
|
const written = new Set(plannedWrites);
|
|
506
747
|
const resources = [];
|
|
507
748
|
const conflicts = [];
|
|
749
|
+
const collisions = select == null
|
|
750
|
+
? { destinations: new Set (), conflicts: [] }
|
|
751
|
+
: selectedDestinationCollisions(files, sourceDir, destDir);
|
|
752
|
+
conflicts.push(...collisions.conflicts);
|
|
508
753
|
for (const file of files) {
|
|
754
|
+
if (collisions.destinations.has(path.resolve(file.destAbs))) continue;
|
|
509
755
|
if (!file.exists) {
|
|
510
756
|
if (written.has(file.destAbs)) conflicts.push(`${file.relPath} was written by this run but is missing`);
|
|
511
757
|
continue;
|
|
@@ -517,7 +763,7 @@ function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, planne
|
|
|
517
763
|
return { ok: conflicts.length === 0, resources, conflicts };
|
|
518
764
|
}
|
|
519
765
|
|
|
520
|
-
// ---- per-adapter copy-tree glue, shared so
|
|
766
|
+
// ---- per-adapter copy-tree glue, shared so each adapter doesn't carry its own copy ----
|
|
521
767
|
|
|
522
768
|
/** Filter a harness's asset list down to the ones this engine handles. */
|
|
523
769
|
function copyTreeAssets(assets ) {
|
|
@@ -578,17 +824,17 @@ function sharedTreeDestDir(rootDir , nativeDir ) {
|
|
|
578
824
|
return dest;
|
|
579
825
|
}
|
|
580
826
|
|
|
581
|
-
/** The assets that project a tree several harnesses claim at one scope root: the runtime
|
|
582
|
-
*
|
|
827
|
+
/** The assets that project a tree several harnesses claim at one scope root: the runtime, guidance,
|
|
828
|
+
* and shared hooks trees. A harness whose
|
|
583
829
|
* install predates its own rows for a shared tree (Pi gained guidance rows in v1.15.0) can only
|
|
584
830
|
* recognise the bytes a sibling recorded. */
|
|
585
831
|
const SHARED_TREE_ASSETS = new Set([
|
|
586
832
|
'scripts.doflow', 'runtime.cli', 'runtime.lib', 'runtime.registry',
|
|
587
|
-
'guidance.context-layer', 'kiro.guidance-tree',
|
|
833
|
+
'guidance.context-layer', 'kiro.guidance-tree', 'hooks.shared',
|
|
588
834
|
]);
|
|
589
835
|
|
|
590
836
|
/** For each shared-tree target, the fingerprints other harnesses recorded and which harness
|
|
591
|
-
* recorded each, so an update accepts a runtime or
|
|
837
|
+
* recorded each, so an update accepts a runtime, guidance, or shared hooks tree a sibling wrote (several harnesses
|
|
592
838
|
* claim one `.doflow` file) while bytes no such row recorded are still refused as a hand edit. */
|
|
593
839
|
function ledgerSiblingFingerprints(resources , harness ) {
|
|
594
840
|
const byTarget = new Map ();
|
|
@@ -617,4 +863,4 @@ function ledgerFileResources(resources , harness , assetId )
|
|
|
617
863
|
.map((resource ) => ({ relPath: resource.identity, fingerprint: resource.fingerprint, target: resource.target }));
|
|
618
864
|
}
|
|
619
865
|
|
|
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 };
|
|
866
|
+
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 };
|