@khoavu882/doflow 2.0.0-beta.5 → 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 -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/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/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- 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/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- 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 +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 +1 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
- 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 +8 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
- 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 +10 -2
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
- 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 +2 -11
- 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-tool-guard.sh +3 -2
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
- 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 +47 -8
- 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 +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1442 -363
- package/core/registry/lifecycle.json +485 -304
- 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 +679 -31
- 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 +149 -34
- 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 +18 -17
- 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 +15 -58
- 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 +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 +4 -7
- package/core/shared/skills/do-plan/SKILL.md +26 -15
- 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 +143 -66
- package/src/adapters/claude/index.js +24 -4
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/index.js +48 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +290 -44
- 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 +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +322 -29
- package/src/cli/runtime-commands.js +209 -57
- 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 +20 -3
- 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 +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 +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/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 +154 -78
- 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 +34 -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 +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 +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 +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 +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/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 +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 +47 -11
- 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 +374 -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/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/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/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
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
// Antigravity CLI adapter (#8). Scope follows the surfaces verified against Antigravity's own
|
|
6
6
|
// docs (antigravity.google/docs/cli/*):
|
|
7
|
-
// instructions — marker-managed section in
|
|
8
|
-
//
|
|
9
|
-
// one
|
|
7
|
+
// instructions — marker-managed section in project AGENTS.md and global ~/.gemini/GEMINI.md (left
|
|
8
|
+
// alone while another live harness declares it); a retired harness's span there is taken over in
|
|
9
|
+
// one write (row 127 IC-007), a person-edited file kept until --force. See instructionTarget.
|
|
10
|
+
// rules — the core set (DOFLOW_CORE.md and the files it imports), flat, in project .agents/rules
|
|
11
|
+
// only; the full guidance tree stays in .doflow/guidance.
|
|
10
12
|
// skills — folder-form copy-tree into project .agents/skills or user ~/.gemini/config/skills.
|
|
11
13
|
// agents — copy-tree into project .agents/agents or user ~/.gemini/config/agents.
|
|
12
14
|
// mcp — entries of the mcpServers object in .agents/mcp_config.json (workspace) or
|
|
@@ -20,9 +22,10 @@ const fs = require('node:fs');
|
|
|
20
22
|
const os = require('node:os');
|
|
21
23
|
const path = require('node:path');
|
|
22
24
|
|
|
23
|
-
const { MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
|
|
24
|
-
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor } = require('../copy-tree.js');
|
|
25
|
+
const { MARKER_START, MARKER_END, removeMarkedSection } = require('../../helper/marker-merge.js');
|
|
26
|
+
const { planTree, applyTree, removeTree, verifyTree, resolveTransform, copyTreeAssets, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor } = require('../copy-tree.js');
|
|
25
27
|
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
28
|
+
const { renderGuidancePointer } = require('../guidance-pointer.js');
|
|
26
29
|
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
|
|
27
30
|
|
|
28
31
|
const HARNESS = 'antigravity';
|
|
@@ -136,8 +139,8 @@ function sha256File(fsImpl , file ) {
|
|
|
136
139
|
|
|
137
140
|
/** Native paths per scope, resolved from the declaration in core/registry/harnesses.json
|
|
138
141
|
* ("paths"): project customization lives at <root>/.agents, global config at ~/.gemini/config
|
|
139
|
-
* (the shared-customization root); instructions
|
|
140
|
-
* rule is declared
|
|
142
|
+
* (the shared-customization root); instructions are AGENTS.md in the project and ~/.gemini/GEMINI.md
|
|
143
|
+
* globally; the hook surfaces are project-only, so no user rule is declared and they resolve to null. */
|
|
141
144
|
function resolveNativePaths(declaredPaths , { scope, scopeRoot, homeDir } = {}) {
|
|
142
145
|
if (scope !== 'project' && scope !== 'global') throw new Error(`Unsupported Antigravity scope '${scope}'`);
|
|
143
146
|
const resolved = resolveHarnessPaths(declaredPaths, { scope, scopeRoot, homeDir });
|
|
@@ -212,16 +215,25 @@ function render({ content = '' } = {}) {
|
|
|
212
215
|
return `${MARKER_START}\n${String(content).trimEnd()}\n${MARKER_END}\n`;
|
|
213
216
|
}
|
|
214
217
|
|
|
215
|
-
|
|
218
|
+
const INSTRUCTIONS_IDENTITY = `doflow:${HARNESS}:instructions:managed-section`;
|
|
219
|
+
/** The short start marker the retired harness wrote into ~/.gemini/GEMINI.md (row 127). It is read
|
|
220
|
+
* only in a file this adapter is taking over, never written: the span it opens is replaced with ours. */
|
|
221
|
+
const PREDECESSOR_MARKER_START = '<!-- doflow:start -->';
|
|
222
|
+
|
|
223
|
+
/** The file's content with `rendered` as its DoFlow span: created, appended to a file with none, or
|
|
224
|
+
* replacing the span in place. `predecessor` also accepts the retired harness's start marker, for a
|
|
225
|
+
* file being taken over. `name` is the file's name in the conflict. */
|
|
226
|
+
function managedInstruction(existing , rendered , { name = 'AGENTS.md', predecessor = false } = {}) {
|
|
216
227
|
if (existing === null) return { ok: true, operation: 'create', content: rendered };
|
|
217
|
-
|
|
228
|
+
let start = existing.indexOf(MARKER_START);
|
|
229
|
+
if (start === -1 && predecessor) start = existing.indexOf(PREDECESSOR_MARKER_START);
|
|
218
230
|
const end = existing.indexOf(MARKER_END);
|
|
219
231
|
if (start === -1 && end === -1) {
|
|
220
232
|
const separator = existing === '' || existing.endsWith('\n') ? '\n' : '\n\n';
|
|
221
233
|
const content = `${existing}${separator}${rendered}`;
|
|
222
234
|
return { ok: true, operation: 'merge', content };
|
|
223
235
|
}
|
|
224
|
-
if (start === -1 || end === -1 || end < start) return { ok: false, conflict:
|
|
236
|
+
if (start === -1 || end === -1 || end < start) return { ok: false, conflict: `${name} has malformed DoFlow managed-section markers` };
|
|
225
237
|
const content = `${existing.slice(0, start)}${rendered}${existing.slice(end + MARKER_END.length).replace(/^\n?/, '')}`;
|
|
226
238
|
return { ok: true, operation: content === existing ? 'none' : 'merge', content };
|
|
227
239
|
}
|
|
@@ -279,7 +291,7 @@ function planTrees({ assets, paths, scope, neutralResources, removing, repoRoot,
|
|
|
279
291
|
for (const { asset, destDir } of targets) {
|
|
280
292
|
const sourceDir = sourceDirFor(asset, { repoRoot }, fsImpl, HARNESS);
|
|
281
293
|
const previousResources = ledgerFileResources(neutralResources, HARNESS, asset.id);
|
|
282
|
-
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(neutralResources, HARNESS), operation: removing ? 'remove' : 'apply', fsImpl, layout: asset.layout, keepModified: !removing,
|
|
294
|
+
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(neutralResources, HARNESS), operation: removing ? 'remove' : 'apply', fsImpl, layout: asset.layout, transform: asset.transform, select: asset.select, keepModified: !removing,
|
|
283
295
|
// Forwarded so the CLI's --force reaches planTree's conflict check; omitting it let
|
|
284
296
|
// planTree's own `force = false` default stand in silently. Gated on `!removing` for the
|
|
285
297
|
// reason codex/index.ts states in full: force heals drift on apply, but a hand-edited file
|
|
@@ -297,6 +309,7 @@ function planTrees({ assets, paths, scope, neutralResources, removing, repoRoot,
|
|
|
297
309
|
ownershipIdentity: `doflow:${HARNESS}:copy-tree:${asset.id}:${change.relPath}`,
|
|
298
310
|
kind: 'copy-tree-file', identity: change.relPath,
|
|
299
311
|
afterFingerprint: change.fingerprint, fingerprint: change.fingerprint, sourceVersion: 'registry-v1',
|
|
312
|
+
transformName: asset.transform || null,
|
|
300
313
|
...(change.kept ? { retained: true, retainedFor: [] } : {}),
|
|
301
314
|
projection: { renderer: 'copy-tree' },
|
|
302
315
|
});
|
|
@@ -315,7 +328,8 @@ function runTreeChanges(changes , mode ) {
|
|
|
315
328
|
.filter((c ) => c.projection?.renderer === 'copy-tree' || c.kind === 'copy-tree-file')
|
|
316
329
|
.map((c ) => ({ relPath: c.identity ?? c.relPath, target: c.target,
|
|
317
330
|
source: c._text ? undefined : c.source, operation: c.operation,
|
|
318
|
-
fingerprint: c.fingerprint, pruneGuard: c.pruneGuard,
|
|
331
|
+
fingerprint: c.fingerprint, pruneGuard: c.pruneGuard, transformName: c.transformName || null,
|
|
332
|
+
...(c._text ? { _text: c._text } : {}) }));
|
|
319
333
|
// Content-managed tree files (hook scripts): write rendered text with +x, not a byte copy.
|
|
320
334
|
for (const t of treeChanges) {
|
|
321
335
|
if (t._text === undefined) continue;
|
|
@@ -330,58 +344,100 @@ function runTreeChanges(changes , mode ) {
|
|
|
330
344
|
const plain = treeChanges.filter((t ) => t._text === undefined);
|
|
331
345
|
const writes = plain.filter((c ) => c.operation !== 'remove');
|
|
332
346
|
const removals = plain.filter((c ) => c.operation === 'remove');
|
|
333
|
-
|
|
347
|
+
// A projection that declares a transform (the rules' always_on header) is re-derived from the
|
|
348
|
+
// source at write time, so each change is applied under its own transform.
|
|
349
|
+
const byTransform = new Map ();
|
|
350
|
+
for (const change of writes) byTransform.set(change.transformName, [...(byTransform.get(change.transformName) || []), change]);
|
|
351
|
+
let applied = 0;
|
|
352
|
+
for (const [transformName, group] of byTransform) {
|
|
353
|
+
applied += applyTree({ changes: group, transform: transformName ? resolveTransform(transformName) : undefined }).applied;
|
|
354
|
+
}
|
|
334
355
|
const removed = removals.length ? removeTree({ changes: removals }).removed : 0;
|
|
335
356
|
return { applied, removed };
|
|
336
357
|
})(),
|
|
337
358
|
};
|
|
338
|
-
const writes = treeChanges.filter((c ) => c.operation !== 'remove');
|
|
339
|
-
const removals = treeChanges.filter((c ) => c.operation === 'remove');
|
|
340
|
-
const applied = writes.length ? applyTree({ changes: writes }).applied : 0;
|
|
341
|
-
const removed = removals.length ? removeTree({ changes: removals }).removed : 0;
|
|
342
|
-
return { applied, removed };
|
|
343
359
|
}
|
|
344
360
|
|
|
345
361
|
// ---- instructions component ----
|
|
346
362
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
363
|
+
/** The instruction file this run manages, or null: project AGENTS.md as ever; the global GEMINI.md
|
|
364
|
+
* only when the pointer asset is selected and no other live harness in `registry` declares that same
|
|
365
|
+
* file (until the harness that owned it retires, its adapter keeps it and this one stays out). */
|
|
366
|
+
function instructionTarget({ paths, assets, registry } ) {
|
|
367
|
+
if (!paths.instruction) return null;
|
|
368
|
+
if (scopeOf(paths) === 'project') return paths.instruction;
|
|
369
|
+
if (!(assets ?? []).some((asset ) => asset.id === POINTER_ASSET_ID)) return null;
|
|
370
|
+
const declaredBy = (harness ) => {
|
|
371
|
+
if (harness?.id === HARNESS || !harness?.paths?.instruction) return false;
|
|
372
|
+
try { return resolveHarnessPaths({ instruction: harness.paths.instruction }, { scope: 'global', scopeRoot: paths.root, homeDir: paths.root }).instruction === paths.instruction; } catch { return false; }
|
|
373
|
+
};
|
|
374
|
+
return (registry?.harnesses ?? []).some(declaredBy) ? null : paths.instruction;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** How this run stands to a section it took over (IC-007): the lifecycle's handover verdict for the
|
|
378
|
+
* file, this harness's own row on it, and whether a person-edited file stays as it is (`retained`:
|
|
379
|
+
* edited before the handover, or kept since, and no --force). `predecessor` lets the retired
|
|
380
|
+
* harness's span count as the section to replace; `keptRow` is a row recorded as kept. A handover
|
|
381
|
+
* file whose DoFlow span already is this harness's render was written by DoFlow itself (a run that
|
|
382
|
+
* threw after that write and before its ledger write), so it does not count as edited. */
|
|
383
|
+
function instructionState({ target, context, resources, paths, fsImpl = fs } ) {
|
|
384
|
+
const handover = (context.handovers ?? []).find((item ) => item.target === target) ?? null;
|
|
385
|
+
const ownRow = (resources ?? []).find((row ) => row.harness === HARNESS && row.target === target && row.ownershipIdentity === INSTRUCTIONS_IDENTITY) ?? null;
|
|
386
|
+
const ownWrite = () => {
|
|
387
|
+
const rendered = renderedInstruction(paths, fsImpl);
|
|
388
|
+
if (rendered === null || !fsImpl.existsSync(target)) return false;
|
|
389
|
+
const outcome = managedInstruction(fsImpl.readFileSync(target, 'utf8'), rendered);
|
|
390
|
+
return outcome.ok && outcome.operation === 'none';
|
|
391
|
+
};
|
|
392
|
+
const edited = (handover?.edited === true && !ownWrite()) || ownRow?.retainedEdit === true;
|
|
393
|
+
return { predecessor: Boolean(handover) || ownRow?.retainedEdit === true, retained: edited && context.force !== true,
|
|
394
|
+
keptRow: ownRow?.retainedEdit === true };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function instructionsChange(target , fields ) {
|
|
398
|
+
return { assetId: POINTER_ASSET_ID, target, ownershipIdentity: INSTRUCTIONS_IDENTITY,
|
|
399
|
+
kind: 'instructions-section', identity: path.basename(target), projection: { renderer: 'antigravity-instructions' }, ...fields };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function planInstructions({ paths, target, removing, state, fsImpl = fs } ) {
|
|
403
|
+
const none = { changes: [], conflicts: [], notices: [] };
|
|
404
|
+
if (!target) return none;
|
|
350
405
|
if (removing) {
|
|
351
406
|
// Removal must strip the section DoFlow owns while leaving foreign bytes untouched — same
|
|
352
407
|
// contract as the other AGENTS-style adapters.
|
|
353
|
-
if (!fsImpl.existsSync(
|
|
354
|
-
const text = fsImpl.readFileSync(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
ownershipIdentity: `doflow:${HARNESS}:instructions:managed-section`,
|
|
360
|
-
kind: 'instructions-section', identity: 'AGENTS.md',
|
|
361
|
-
projection: { renderer: 'antigravity-instructions' },
|
|
362
|
-
}],
|
|
363
|
-
conflicts: [],
|
|
364
|
-
};
|
|
408
|
+
if (!fsImpl.existsSync(target)) return none;
|
|
409
|
+
const text = fsImpl.readFileSync(target, 'utf8');
|
|
410
|
+
// A kept edited file with no span left still carries this harness's row: the remove change
|
|
411
|
+
// releases the row, and apply finds no span to strip, so the person's file is not touched.
|
|
412
|
+
if (!text.includes(MARKER_START) && !state.keptRow) return none;
|
|
413
|
+
return { ...none, changes: [instructionsChange(target, { operation: 'remove' })] };
|
|
365
414
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (
|
|
415
|
+
if (state.retained) {
|
|
416
|
+
const shown = scopeOf(paths) === 'global' ? `~/${path.relative(paths.root, target)}` : path.relative(paths.root, target);
|
|
417
|
+
return { ...none, notices: [`kept ${shown}: changed before handover; --force replaces the DoFlow section`] };
|
|
418
|
+
}
|
|
419
|
+
const rendered = renderedInstruction(paths, fsImpl);
|
|
420
|
+
if (rendered === null) return none;
|
|
421
|
+
const existing = fsImpl.existsSync(target) ? fsImpl.readFileSync(target, 'utf8') : null;
|
|
422
|
+
const outcome = managedInstruction(existing, rendered, { name: path.basename(target), predecessor: state.predecessor });
|
|
423
|
+
if (!outcome.ok) return { ...none, conflicts: [outcome.conflict] };
|
|
424
|
+
if (outcome.operation === 'none') return none;
|
|
372
425
|
return {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
kind: 'instructions-section', identity: 'AGENTS.md',
|
|
426
|
+
...none,
|
|
427
|
+
changes: [instructionsChange(target, {
|
|
428
|
+
operation: existing === null ? 'create' : 'update',
|
|
377
429
|
afterFingerprint: fingerprint(outcome.content), sourceVersion: 'registry-v1',
|
|
378
|
-
projection: { renderer: 'antigravity-instructions' },
|
|
379
430
|
_content: outcome.content,
|
|
380
|
-
}],
|
|
381
|
-
conflicts: [],
|
|
431
|
+
})],
|
|
382
432
|
};
|
|
383
433
|
}
|
|
384
434
|
|
|
435
|
+
/** This scope's DoFlow section: the shared pointer rendered for the scope, inside the markers. */
|
|
436
|
+
function renderedInstruction(paths , fsImpl ) {
|
|
437
|
+
const sourceText = pointerBody(fsImpl);
|
|
438
|
+
return sourceText ? render({ content: renderGuidancePointer(sourceText, { scope: scopeOf(paths), scopeRoot: paths.root }) }) : null;
|
|
439
|
+
}
|
|
440
|
+
|
|
385
441
|
function pointerBody(fsImpl ) {
|
|
386
442
|
// The shared pointer prose ships once; read it through the same repo-relative anchor the other
|
|
387
443
|
// AGENTS-style adapters use.
|
|
@@ -432,13 +488,18 @@ function plan(options = {}, impl = {}) {
|
|
|
432
488
|
const selectedServers = Array.isArray(options.mcp) ? options.mcp : [];
|
|
433
489
|
const discovery = options.discovery ?? discover(options, impl);
|
|
434
490
|
|
|
435
|
-
const
|
|
491
|
+
const instructionFile = instructionTarget({ paths, assets: options.assets, registry: options.registry });
|
|
492
|
+
const instructions = planInstructions({ paths, target: instructionFile, removing, fsImpl,
|
|
493
|
+
state: instructionState({ target: instructionFile, context, resources: neutralResources, paths, fsImpl }) });
|
|
436
494
|
const trees = planTrees({ assets: options.assets, paths, scope, neutralResources, removing, repoRoot: context.repoRoot, force: context.force === true, fsImpl });
|
|
437
495
|
const mcp = planMcpEntries({
|
|
438
496
|
...mcpEntriesInput({ paths, mcp: selectedServers, mcpAdoptable: options.mcpAdoptable, resources: neutralResources }),
|
|
439
497
|
files: discovery.mcpSnapshot, removing,
|
|
440
498
|
});
|
|
441
|
-
|
|
499
|
+
// Hook tiers (maint-hooks): outside `hooks.harnesses` this plan carries no hook change; the lifecycle
|
|
500
|
+
// plans the removal of what an earlier run wrote, from the ledger, against the full registry.
|
|
501
|
+
const hooksPlan = context.hooksOff === true && !removing ? { changes: [], conflicts: [] }
|
|
502
|
+
: planHooks({ paths, scope, neutralResources, removing, repoRoot: context.repoRoot, fsImpl });
|
|
442
503
|
|
|
443
504
|
const changes = [...instructions.changes, ...trees.changes, ...mcp.changes, ...hooksPlan.changes];
|
|
444
505
|
const conflicts = [...instructions.conflicts, ...trees.conflicts, ...mcp.conflicts, ...hooksPlan.conflicts];
|
|
@@ -447,7 +508,7 @@ function plan(options = {}, impl = {}) {
|
|
|
447
508
|
conflicts,
|
|
448
509
|
prerequisites: [],
|
|
449
510
|
requiredNativeResources: changes,
|
|
450
|
-
notices: [...trees.notices, ...mcp.notices],
|
|
511
|
+
notices: [...instructions.notices, ...trees.notices, ...mcp.notices],
|
|
451
512
|
};
|
|
452
513
|
}
|
|
453
514
|
|
|
@@ -485,14 +546,26 @@ function apply(options = {}, impl = {}) {
|
|
|
485
546
|
|
|
486
547
|
for (const change of changes.filter((c ) => c.projection?.renderer === 'antigravity-instructions')) {
|
|
487
548
|
if (change.operation === 'remove') {
|
|
488
|
-
// pi precedent: strip only the managed span; the file survives even when nothing remains.
|
|
489
549
|
if (!fsImpl.existsSync(change.target)) continue;
|
|
550
|
+
// The global file is the person's: the span goes with the one newline put before it, so their
|
|
551
|
+
// bytes come back exactly (and a file that held only the span goes with it). A kept file taken
|
|
552
|
+
// over from the retired harness may still hold that harness's span, under its short marker.
|
|
553
|
+
if ((options.scope ?? 'project') === 'global') { removeMarkedSection(change.target, { startMarkers: [MARKER_START, PREDECESSOR_MARKER_START] }); continue; }
|
|
554
|
+
// pi precedent: strip only the managed span; the file survives even when nothing remains.
|
|
490
555
|
const next = strippedInstruction(fsImpl.readFileSync(change.target, 'utf8'));
|
|
491
556
|
if (next !== null) fsImpl.writeFileSync(change.target, next, 'utf8');
|
|
492
557
|
continue;
|
|
493
558
|
}
|
|
559
|
+
// One rename onto the file: no reader, and no failure, ever sees it without its DoFlow span.
|
|
494
560
|
fsImpl.mkdirSync(path.dirname(change.target), { recursive: true });
|
|
495
|
-
|
|
561
|
+
const tmp = `${change.target}.${process.pid}.tmp`;
|
|
562
|
+
try {
|
|
563
|
+
fsImpl.writeFileSync(tmp, change._content ?? render({ content: '' }), 'utf8');
|
|
564
|
+
fsImpl.renameSync(tmp, change.target);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
fsImpl.rmSync(tmp, { force: true });
|
|
567
|
+
throw error;
|
|
568
|
+
}
|
|
496
569
|
}
|
|
497
570
|
for (const change of changes.filter((c ) => c.projection?.renderer === 'antigravity-hooks')) {
|
|
498
571
|
if (change.kind === 'copy-tree-file') continue; // handled by runTreeChanges below
|
|
@@ -594,24 +667,28 @@ function verify(options = {}, impl = {}) {
|
|
|
594
667
|
const statuses = [];
|
|
595
668
|
const resources = [];
|
|
596
669
|
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
const
|
|
600
|
-
|
|
670
|
+
const instructionFile = instructionTarget({ paths, assets: options.assets, registry: options.registry });
|
|
671
|
+
const state = instructionState({ target: instructionFile, context, resources: ledgerResources(options), paths, fsImpl });
|
|
672
|
+
const instruction = instructionFile && fsImpl.existsSync(instructionFile)
|
|
673
|
+
? fsImpl.readFileSync(instructionFile, 'utf8') : null;
|
|
674
|
+
const managed = instruction !== null && instruction.includes(MARKER_END)
|
|
675
|
+
&& (instruction.includes(MARKER_START) || (state.predecessor && instruction.includes(PREDECESSOR_MARKER_START)));
|
|
676
|
+
// A kept, person-edited file (IC-007) is owned as it is now, markers or not: an edit can drop the
|
|
677
|
+
// span, and without its row the next run would see no owner and append the section unasked.
|
|
678
|
+
const kept = !removing && state.retained && instruction !== null;
|
|
679
|
+
if (instructionFile) {
|
|
601
680
|
// Removal verification asks "did our section go away?"; install/update asks "is it managed?".
|
|
602
681
|
statuses.push({
|
|
603
682
|
assetId: POINTER_ASSET_ID, capability: 'instructions',
|
|
604
|
-
status: removing ? (managed ? 'retained' : 'absent') : (managed ? 'managed' : 'missing'),
|
|
605
|
-
target:
|
|
683
|
+
status: removing ? (managed ? 'retained' : 'absent') : (managed ? 'managed' : (kept ? 'retained' : 'missing')),
|
|
684
|
+
target: instructionFile,
|
|
606
685
|
});
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
ownershipIdentity: `doflow:${HARNESS}:instructions:managed-section`,
|
|
611
|
-
kind: 'instructions-section', identity: 'AGENTS.md',
|
|
686
|
+
if (!removing && (managed || kept)) {
|
|
687
|
+
// Recorded with the file as it is now; a kept one is flagged so later runs keep it until --force.
|
|
688
|
+
resources.push(instructionsChange(instructionFile, {
|
|
612
689
|
fingerprint: fingerprint(instruction), sourceVersion: 'registry-v1',
|
|
613
|
-
|
|
614
|
-
});
|
|
690
|
+
...(state.retained ? { retainedEdit: true } : {}),
|
|
691
|
+
}));
|
|
615
692
|
}
|
|
616
693
|
}
|
|
617
694
|
|
|
@@ -619,7 +696,7 @@ function verify(options = {}, impl = {}) {
|
|
|
619
696
|
const destDir = treeDestFor(asset, paths, scope);
|
|
620
697
|
if (!destDir) continue;
|
|
621
698
|
const sourceDir = sourceDirFor(asset, { repoRoot: context.repoRoot }, fsImpl, HARNESS);
|
|
622
|
-
const result = verifyTree({ sourceDir, destDir, fsImpl, layout: asset.layout, plannedWrites: context.plannedWrites });
|
|
699
|
+
const result = verifyTree({ sourceDir, destDir, fsImpl, layout: asset.layout, transform: asset.transform, select: asset.select, plannedWrites: context.plannedWrites });
|
|
623
700
|
conflictsToStatuses(result.conflicts, asset.id, statuses);
|
|
624
701
|
for (const resource of result.resources) {
|
|
625
702
|
resources.push({
|
|
@@ -642,7 +719,7 @@ function verify(options = {}, impl = {}) {
|
|
|
642
719
|
resources.push(...mcp.resources);
|
|
643
720
|
|
|
644
721
|
// Hooks projection (project scope): the shims' bytes and the registered groups.
|
|
645
|
-
if (scope === 'project') {
|
|
722
|
+
if (scope === 'project' && context.hooksOff !== true) {
|
|
646
723
|
const hooks = verifyHooks({ options, paths, context, fsImpl });
|
|
647
724
|
statuses.push(...hooks.statuses);
|
|
648
725
|
resources.push(...hooks.resources);
|
|
@@ -183,6 +183,24 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
183
183
|
function settingsContent(fileName , raw , scope ) {
|
|
184
184
|
return fileName === 'settings.json' && scope !== 'global' ? raw.split(GLOBAL_HOOK_PREFIX).join(PROJECT_HOOK_PREFIX) : raw;
|
|
185
185
|
}
|
|
186
|
+
/** Hook tiers (maint-hooks): with Claude outside `hooks.harnesses` the managed settings carry no
|
|
187
|
+
* `hooks` block. Returns the managed text to write and the hooks it left out, which a merge strips
|
|
188
|
+
* from the live file so an earlier install's entries do not stay behind. */
|
|
189
|
+
function tieredSettings(fileName , raw , context ) {
|
|
190
|
+
if (fileName !== 'settings.json' || context?.hooksOff !== true) return { raw, droppedHooks: null };
|
|
191
|
+
let parsed;
|
|
192
|
+
try { parsed = JSON.parse(raw); } catch { return { raw, droppedHooks: null }; }
|
|
193
|
+
if (!parsed || parsed.hooks === undefined) return { raw, droppedHooks: null };
|
|
194
|
+
const { hooks, ...rest } = parsed;
|
|
195
|
+
return { raw: serializeSettings(rest), droppedHooks: hooks };
|
|
196
|
+
}
|
|
197
|
+
/** The live settings without DoFlow's own entries of `hooks`; every other key is left alone. */
|
|
198
|
+
function withoutManagedHooks(current , hooks ) {
|
|
199
|
+
if (!hooks || !current || typeof current !== 'object' || current.hooks === undefined) return current;
|
|
200
|
+
const { hooks: liveHooks, ...rest } = current;
|
|
201
|
+
const stripped = stripManagedSettings({ hooks: liveHooks, _: true }, { hooks });
|
|
202
|
+
return stripped?.hooks ? { ...rest, hooks: stripped.hooks } : rest;
|
|
203
|
+
}
|
|
186
204
|
function settingsLedgerResources(ledger , assetId ) {
|
|
187
205
|
const byFile = new Map();
|
|
188
206
|
for (const resource of ledger?.resources || []) {
|
|
@@ -242,7 +260,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
242
260
|
for (const fileName of SETTINGS_FILES) {
|
|
243
261
|
const sourceAbs = path.join(sourceDir, fileName);
|
|
244
262
|
if (!fs.existsSync(sourceAbs)) continue;
|
|
245
|
-
|
|
263
|
+
const tiered = tieredSettings(fileName, settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), context);
|
|
264
|
+
let managedRaw = tiered.raw;
|
|
246
265
|
if (fileName === 'settings.json' && context.statusline && !removing) {
|
|
247
266
|
// --statusline: point Claude Code's statusLine key at the projected script. It rides the
|
|
248
267
|
// same managed settings write as everything else this asset owns — one serialization, one
|
|
@@ -267,7 +286,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
267
286
|
const currentRaw = fs.readFileSync(target, 'utf8');
|
|
268
287
|
let merged;
|
|
269
288
|
try {
|
|
270
|
-
merged = mergeSettings(JSON.parse(currentRaw), JSON.parse(managedRaw));
|
|
289
|
+
merged = mergeSettings(withoutManagedHooks(JSON.parse(currentRaw), tiered.droppedHooks), JSON.parse(managedRaw));
|
|
271
290
|
} catch {
|
|
272
291
|
conflicts.push(`${fileName} is not valid JSON and cannot be merged`);
|
|
273
292
|
continue;
|
|
@@ -358,7 +377,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
358
377
|
for (const fileName of SETTINGS_FILES) {
|
|
359
378
|
const sourceAbs = path.join(sourceDir, fileName);
|
|
360
379
|
if (!fs.existsSync(sourceAbs)) continue;
|
|
361
|
-
const
|
|
380
|
+
const sourceText = tieredSettings(fileName, settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), context).raw;
|
|
381
|
+
const fingerprint = sha256(sourceText);
|
|
362
382
|
const target = settingsTarget(fileName, paths);
|
|
363
383
|
// 'absent' (not 'missing') matches the same convention the instructions asset already uses
|
|
364
384
|
// for "no file here" — 'missing' is reserved by the lifecycle layer's removal reconciliation
|
|
@@ -373,7 +393,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
373
393
|
let managed;
|
|
374
394
|
try {
|
|
375
395
|
current = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
376
|
-
managed = JSON.parse(
|
|
396
|
+
managed = JSON.parse(sourceText);
|
|
377
397
|
} catch {
|
|
378
398
|
conflicts.push(`${fileName} is not valid JSON`);
|
|
379
399
|
statuses.push({ assetId: asset.id, capability: 'settings', status: 'conflict', target });
|
|
@@ -9,18 +9,12 @@ const crypto = require('node:crypto');
|
|
|
9
9
|
const fs = require('node:fs');
|
|
10
10
|
const path = require('node:path');
|
|
11
11
|
const { atomicWrite } = require('./config.js');
|
|
12
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
12
13
|
|
|
13
14
|
const AGENT_DIRECTORY = 'agents';
|
|
14
15
|
const AGENT_KIND = 'custom-agent';
|
|
15
16
|
const REQUIRED_FIELDS = ['name', 'description', 'developer_instructions'];
|
|
16
|
-
const
|
|
17
|
-
'backend-architect', 'deep-research-agent', 'devops-architect', 'frontend-architect',
|
|
18
|
-
'quality-engineer', 'requirements-analyst', 'root-cause-analyst', 'security-engineer',
|
|
19
|
-
'system-architect', 'technical-writer',
|
|
20
|
-
]);
|
|
21
|
-
const IMPLEMENTATION_ROLES = new Set([
|
|
22
|
-
'performance-engineer', 'pm-agent', 'python-expert', 'refactoring-expert',
|
|
23
|
-
]);
|
|
17
|
+
const SPEC_DIRECTORY = path.join(REPO_ROOT, 'core', 'shared', 'agent-specs');
|
|
24
18
|
const ALLOWED_FIELDS = new Set([...REQUIRED_FIELDS, 'sandbox_mode']);
|
|
25
19
|
|
|
26
20
|
function fingerprint(content ) {
|
|
@@ -85,7 +79,21 @@ function parseAgentToml(source , { fileName } = {})
|
|
|
85
79
|
return values;
|
|
86
80
|
}
|
|
87
81
|
|
|
88
|
-
|
|
82
|
+
/** `access` of every shared agent spec by name (118 IC-002). A TOML whose name has no spec is the
|
|
83
|
+
* user's own agent and is not tied to any access. */
|
|
84
|
+
function specAccess(specDir = SPEC_DIRECTORY, fsImpl = fs) {
|
|
85
|
+
const access = new Map ();
|
|
86
|
+
if (!fsImpl.existsSync(specDir)) return access;
|
|
87
|
+
for (const entry of fsImpl.readdirSync(specDir)) {
|
|
88
|
+
if (!entry.endsWith('.md')) continue;
|
|
89
|
+
const frontmatter = fsImpl.readFileSync(path.join(specDir, entry), 'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
90
|
+
const value = frontmatter?.[1].match(/^access:\s*(\S+)\s*$/m)?.[1];
|
|
91
|
+
if (value) access.set(entry.slice(0, -'.md'.length), value);
|
|
92
|
+
}
|
|
93
|
+
return access;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function validateAgentContract(source , { fileName, access = specAccess() } = {}) {
|
|
89
97
|
const agent = parseAgentToml(source, { fileName });
|
|
90
98
|
for (const field of REQUIRED_FIELDS) {
|
|
91
99
|
if (typeof agent[field] !== 'string' || !agent[field].trim()) throw new Error(`Agent is missing required '${field}'`);
|
|
@@ -97,11 +105,12 @@ function validateAgentContract(source , { fileName }
|
|
|
97
105
|
if (agent.sandbox_mode !== undefined && !['read-only', 'workspace-write', 'danger-full-access'].includes(agent.sandbox_mode)) {
|
|
98
106
|
throw new Error(`Agent '${agent.name}' has an unsupported sandbox_mode`);
|
|
99
107
|
}
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
const specAccessValue = access.get(agent.name);
|
|
109
|
+
if (specAccessValue === 'read-only' && agent.sandbox_mode !== 'read-only') {
|
|
110
|
+
throw new Error(`Agent '${agent.name}' has access: read-only and must use sandbox_mode = "read-only"`);
|
|
102
111
|
}
|
|
103
|
-
if (
|
|
104
|
-
throw new Error(`
|
|
112
|
+
if (specAccessValue === 'write' && agent.sandbox_mode !== undefined) {
|
|
113
|
+
throw new Error(`Agent '${agent.name}' has access: write and must inherit its parent sandbox`);
|
|
105
114
|
}
|
|
106
115
|
return Object.freeze({ ...agent, fileName: filenameFor(agent.name) });
|
|
107
116
|
}
|
|
@@ -134,12 +143,13 @@ function discoverCodexAgents(sourceDir , fsImpl = fs) {
|
|
|
134
143
|
const directory = path.resolve(sourceDir);
|
|
135
144
|
if (!fsImpl.existsSync(directory)) throw new Error(`Codex agent source directory does not exist: ${sourceDir}`);
|
|
136
145
|
const agents = [];
|
|
146
|
+
const access = specAccess();
|
|
137
147
|
for (const entry of fsImpl.readdirSync(directory, { withFileTypes: true }).sort((a , b ) => a.name.localeCompare(b.name))) {
|
|
138
148
|
if (!entry.name.endsWith('.toml')) continue;
|
|
139
149
|
if (!entry.isFile()) throw new Error(`Codex agent source '${entry.name}' must be a regular file`);
|
|
140
150
|
const name = expectedName(entry.name);
|
|
141
151
|
const source = fsImpl.readFileSync(path.join(directory, entry.name), 'utf8');
|
|
142
|
-
agents.push({ ...validateAgentContract(source, { fileName: entry.name }), name, source, fingerprint: fingerprint(source) });
|
|
152
|
+
agents.push({ ...validateAgentContract(source, { fileName: entry.name, access }), name, source, fingerprint: fingerprint(source) });
|
|
143
153
|
}
|
|
144
154
|
if (!agents.length) throw new Error(`No Codex agent TOML contracts found in ${sourceDir}`);
|
|
145
155
|
return agents;
|
|
@@ -202,7 +212,7 @@ function reconcileCodexAgents(options ) {
|
|
|
202
212
|
}
|
|
203
213
|
|
|
204
214
|
module.exports = {
|
|
205
|
-
AGENT_DIRECTORY, AGENT_KIND,
|
|
215
|
+
AGENT_DIRECTORY, AGENT_KIND, fingerprint, specAccess,
|
|
206
216
|
parseAgentToml, validateAgentContract, agentDirectory, destinationFor, discoverCodexAgents,
|
|
207
217
|
planCodexAgents, applyCodexAgents, reconcileCodexAgents,
|
|
208
218
|
};
|
|
@@ -13,8 +13,9 @@ const { configPath, fingerprint: configFingerprint, parseToml, planCodexConfig,
|
|
|
13
13
|
const { renderServer, planCodexMcp, applyCodexMcp, ownedCodexMcpIds, userDefinedCodexMcpIds, serverTableText } = require('./mcp.js');
|
|
14
14
|
const { agentDirectory, discoverCodexAgents, planCodexAgents, applyCodexAgents } = require('./agents.js');
|
|
15
15
|
const { planCodexHooks, deployCodexHooks, removeCodexHookScripts, scriptFingerprint } = require('./hooks.js');
|
|
16
|
-
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices } = require('../copy-tree.js');
|
|
16
|
+
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveTransform } = require('../copy-tree.js');
|
|
17
17
|
const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
|
|
18
|
+
const { renderGuidancePointer } = require('../guidance-pointer.js');
|
|
18
19
|
const { nativeMcpCatalog } = require('../../registry/index.js');
|
|
19
20
|
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
20
21
|
|
|
@@ -275,6 +276,26 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
275
276
|
}
|
|
276
277
|
function sha256Text(text ) { return crypto.createHash('sha256').update(text).digest('hex'); }
|
|
277
278
|
|
|
279
|
+
function instructionBody(content ) {
|
|
280
|
+
if (typeof content !== 'string') return null;
|
|
281
|
+
const start = content.indexOf(MARKER_START);
|
|
282
|
+
if (start === -1) return null;
|
|
283
|
+
const bodyStart = start + MARKER_START.length;
|
|
284
|
+
const end = content.indexOf(MARKER_END, bodyStart);
|
|
285
|
+
if (end === -1 || content.indexOf(MARKER_START, end + MARKER_END.length) !== -1) return null;
|
|
286
|
+
let from = bodyStart;
|
|
287
|
+
if (content[from] === '\n') from += 1;
|
|
288
|
+
let to = end;
|
|
289
|
+
if (to > from && content[to - 1] === '\n') to -= 1;
|
|
290
|
+
return content.slice(from, to);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function renderedInstructionContent(asset , context , repoRoot ) {
|
|
294
|
+
const source = sourceDirFor(asset, repoRoot);
|
|
295
|
+
const content = fs.readFileSync(source, 'utf8');
|
|
296
|
+
return renderGuidancePointer(content, { scope: context.scope, scopeRoot: context.paths.root }).replace(/\s+$/, '');
|
|
297
|
+
}
|
|
298
|
+
|
|
278
299
|
function planInstructionsAsset({ assets, context, removing, repoRoot, claimedElsewhere = () => false } ) {
|
|
279
300
|
const asset = instructionsAsset(assets);
|
|
280
301
|
const changes = [];
|
|
@@ -298,18 +319,19 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
298
319
|
return { changes, conflicts };
|
|
299
320
|
}
|
|
300
321
|
const source = sourceDirFor(asset, repoRoot);
|
|
301
|
-
|
|
302
|
-
const
|
|
322
|
+
const content = renderedInstructionContent(asset, context, repoRoot);
|
|
323
|
+
const merged = mergeMarkedSection(source, target, { dryRun: true, content });
|
|
324
|
+
if (!merged.changed) return { changes, conflicts };
|
|
303
325
|
changes.push({ assetId: asset.id, target, source, operation: fs.existsSync(target) ? 'update' : 'create',
|
|
304
326
|
ownershipIdentity: 'doflow:codex:instructions:managed-section', kind: 'instructions-section', identity: 'AGENTS.md',
|
|
305
|
-
afterFingerprint: sha256Text(content), sourceVersion: 'registry-v1', projection: { renderer: 'codex-agents' } });
|
|
327
|
+
afterFingerprint: sha256Text(content), renderedContent: content, sourceVersion: 'registry-v1', projection: { renderer: 'codex-agents' } });
|
|
306
328
|
return { changes, conflicts };
|
|
307
329
|
}
|
|
308
330
|
|
|
309
331
|
function applyInstructionsAsset(changes ) {
|
|
310
332
|
for (const change of changes) {
|
|
311
333
|
if (change.projection?.renderer !== 'codex-agents' || change.operation === 'remove') continue;
|
|
312
|
-
mergeMarkedSection(change.source, change.target);
|
|
334
|
+
mergeMarkedSection(change.source, change.target, { content: change.renderedContent });
|
|
313
335
|
}
|
|
314
336
|
}
|
|
315
337
|
|
|
@@ -325,11 +347,14 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
325
347
|
if (!asset) return { statuses, resources };
|
|
326
348
|
const target = instructionsPath(context);
|
|
327
349
|
const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
|
|
328
|
-
const
|
|
329
|
-
|
|
350
|
+
const expected = renderedInstructionContent(asset, context, repoRoot);
|
|
351
|
+
const actual = instructionBody(existing);
|
|
352
|
+
const hasMarkers = existing !== null && existing.includes(MARKER_START) && existing.includes(MARKER_END);
|
|
353
|
+
const managed = actual !== null && actual === expected;
|
|
354
|
+
statuses.push({ assetId: asset.id, capability: asset.capability, status: managed ? 'managed' : (hasMarkers ? 'missing' : 'absent'), target });
|
|
330
355
|
if (managed) {
|
|
331
356
|
resources.push({ assetId: asset.id, target, ownershipIdentity: 'doflow:codex:instructions:managed-section',
|
|
332
|
-
kind: 'instructions-section', identity: 'AGENTS.md', fingerprint: sha256Text(
|
|
357
|
+
kind: 'instructions-section', identity: 'AGENTS.md', fingerprint: sha256Text(expected), sourceVersion: 'registry-v1',
|
|
333
358
|
projection: { renderer: 'codex-agents' } });
|
|
334
359
|
}
|
|
335
360
|
return { statuses, resources };
|
|
@@ -344,7 +369,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
344
369
|
const destDir = copyTreeDestDir(codexConfigDir(context), asset);
|
|
345
370
|
const sourceDir = sourceDirFor(asset, repoRoot);
|
|
346
371
|
const previousResources = ledgerFileResources(neutralResources, HARNESS, asset.id);
|
|
347
|
-
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(neutralResources, HARNESS), operation: removing ? 'remove' : 'apply', layout: asset.layout, keepModified: !removing,
|
|
372
|
+
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(neutralResources, HARNESS), operation: removing ? 'remove' : 'apply', layout: asset.layout, transform: asset.transform, keepModified: !removing,
|
|
348
373
|
// `force` is the CLI's --force reaching the one conflict class a plan can actually
|
|
349
374
|
// downgrade: a ledger-owned destination whose bytes were edited underneath us. With force,
|
|
350
375
|
// that is drift to heal rather than a refusal — exactly what `doflow reconcile` (always
|
|
@@ -362,6 +387,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
362
387
|
ownershipIdentity: `doflow:codex:copy-tree:${asset.id}:${change.relPath}`,
|
|
363
388
|
kind: 'copy-tree-file', identity: change.relPath,
|
|
364
389
|
afterFingerprint: change.fingerprint, fingerprint: change.fingerprint, sourceVersion: sourceVersion ?? 'unknown',
|
|
390
|
+
transformName: asset.transform || null,
|
|
365
391
|
...(change.kept ? { retained: true, retainedFor: [] } : {}),
|
|
366
392
|
projection: { renderer: 'copy-tree' },
|
|
367
393
|
});
|
|
@@ -371,9 +397,18 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
371
397
|
}
|
|
372
398
|
|
|
373
399
|
function applyCopyTreeAssets(changes ) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
400
|
+
let applied = 0;
|
|
401
|
+
const grouped = new Map();
|
|
402
|
+
for (const change of changes) {
|
|
403
|
+
if (change.projection?.renderer !== 'copy-tree' || change.operation === 'remove') continue;
|
|
404
|
+
const key = change.transformName || null;
|
|
405
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
406
|
+
grouped.get(key).push({ relPath: change.identity, target: change.target, source: change.source, operation: change.operation, fingerprint: change.fingerprint });
|
|
407
|
+
}
|
|
408
|
+
for (const [transformName, treeChanges] of grouped) {
|
|
409
|
+
applied += applyTree({ changes: treeChanges, transform: resolveTransform(transformName) }).applied;
|
|
410
|
+
}
|
|
411
|
+
return applied;
|
|
377
412
|
}
|
|
378
413
|
|
|
379
414
|
function removeCopyTreeAssets(changes ) {
|
|
@@ -389,7 +424,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
389
424
|
for (const asset of copyTreeAssets(assets)) {
|
|
390
425
|
const destDir = copyTreeDestDir(codexConfigDir(context), asset);
|
|
391
426
|
const sourceDir = sourceDirFor(asset, repoRoot);
|
|
392
|
-
const result = verifyTree({ sourceDir, destDir, layout: asset.layout, plannedWrites });
|
|
427
|
+
const result = verifyTree({ sourceDir, destDir, layout: asset.layout, transform: asset.transform, plannedWrites });
|
|
393
428
|
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
394
429
|
for (const resource of result.resources) {
|
|
395
430
|
resources.push({
|