@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
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/** IC-009: local, replayable merge plumbing. The caller owns the one-step lease and gate approval. */
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const cp = require('node:child_process');
|
|
7
|
+
const { parseSeed, deriveStatus, render, renderRoadmapFile } = require('./rows.js');
|
|
8
|
+
const { gatherGitFacts } = require('./facts.js');
|
|
9
|
+
|
|
10
|
+
const PHASES = ['intent', 'built', 'ff-started', 'ff-done', 'rendered', 'cleaned'];
|
|
11
|
+
const SHA = /^[a-f0-9]{40}$/;
|
|
12
|
+
const ROW = /^[0-9]{3}([a-z]|-[A-Z])?$/;
|
|
13
|
+
|
|
14
|
+
function intentDirectory(root ) {
|
|
15
|
+
return path.join(root, '.doflow', 'state', 'roadmap', 'intents');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function doneDirectory(root ) {
|
|
19
|
+
return path.join(root, '.doflow', 'state', 'roadmap', 'done');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readIntent(file , fsImpl = fs) {
|
|
23
|
+
let raw ;
|
|
24
|
+
try { raw = fsImpl.readFileSync(file, 'utf8'); }
|
|
25
|
+
catch (error ) { if (error.code === 'ENOENT') return null; throw error; }
|
|
26
|
+
const value = JSON.parse(raw);
|
|
27
|
+
if (value.schema !== 1 || !ROW.test(value.row) || !PHASES.includes(value.phase)
|
|
28
|
+
|| !SHA.test(value.developHead) || !SHA.test(value.featureHead)
|
|
29
|
+
|| typeof value.slug !== 'string' || !value.slug.startsWith(`${value.row}-`)
|
|
30
|
+
|| value.featureBranch !== `feat/${value.slug}` || !path.isAbsolute(value.developWorktree)
|
|
31
|
+
|| !value.identity?.name || !value.identity?.email || !/^\d+ [+-]\d{4}$/.test(value.identity?.date)
|
|
32
|
+
|| typeof value.message !== 'string') throw new Error(`unreadable merge intent: ${file}`);
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Read failures are errors, never an empty list of open intents (G16). */
|
|
37
|
+
function readOpenIntents({ root, fsImpl = fs } ) {
|
|
38
|
+
const dir = intentDirectory(root);
|
|
39
|
+
let names ;
|
|
40
|
+
try { names = fsImpl.readdirSync(dir); }
|
|
41
|
+
catch (error ) { if (error.code === 'ENOENT') return []; throw error; }
|
|
42
|
+
return names.filter((name) => /^\d{3}(?:[a-z]|-[A-Z])?\.json$/.test(name)).sort()
|
|
43
|
+
.map((name) => readIntent(path.join(dir, name), fsImpl));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeIntent(file , intent , fsImpl = fs) {
|
|
47
|
+
fsImpl.mkdirSync(path.dirname(file), { recursive: true });
|
|
48
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
49
|
+
try {
|
|
50
|
+
fsImpl.writeFileSync(temp, `${JSON.stringify(intent)}\n`, { mode: 0o600 });
|
|
51
|
+
fsImpl.renameSync(temp, file);
|
|
52
|
+
} finally {
|
|
53
|
+
try { fsImpl.unlinkSync(temp); } catch (error ) { if (error.code !== 'ENOENT') throw error; }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Prove death without spawning ps. A live/reused/inaccessible pid is conservatively unknown.
|
|
58
|
+
function childAlive(proc ) {
|
|
59
|
+
if (!Number.isInteger(proc?.pid) || proc.pid <= 0) return 'unknown';
|
|
60
|
+
try { process.kill(proc.pid, 0); return 'unknown'; }
|
|
61
|
+
catch (error ) { return error.code === 'ESRCH' ? false : 'unknown'; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merge + lean gate + render + non-force cleanup. No model, shell, hooks or network launcher.
|
|
66
|
+
* postMergeCheck({cwd, head}) is mandatory for completion: the caller answers whether a passing
|
|
67
|
+
* verification is recorded for that develop head; this module runs no check itself.
|
|
68
|
+
* onPhase is an in-process fault seam, called only after the phase is durably recorded.
|
|
69
|
+
* isEnabled is re-read at each boundary. Under an off switch a recorded ff-started is only settled:
|
|
70
|
+
* a develop already at the commit records ff-done, a proven-dead partial is repaired, and no new
|
|
71
|
+
* fast-forward is spawned.
|
|
72
|
+
*/
|
|
73
|
+
async function mergeRoadmapRow(options ) {
|
|
74
|
+
const { root, row, runId, gate, approvedHead, fsImpl = fs, env = process.env,
|
|
75
|
+
execFileSync = cp.execFileSync, spawn = cp.spawn, now = Date.now, alive = childAlive } = options;
|
|
76
|
+
if (!ROW.test(row?.id) || !/^[0-9]{3}(?:[a-z]|-[A-Z])?-[A-Za-z0-9][A-Za-z0-9-]*$/.test(row?.slug)
|
|
77
|
+
|| !row.slug.startsWith(`${row.id}-`) || typeof row.title !== 'string' || row.title.trim().length === 0
|
|
78
|
+
|| /[\r\n\u2028\u2029]/.test(row.title)) throw new Error('invalid merge row');
|
|
79
|
+
const file = path.join(intentDirectory(root), `${row.id}.json`);
|
|
80
|
+
let intent = readIntent(file, fsImpl);
|
|
81
|
+
const done = path.join(doneDirectory(root), `${row.id}.json`);
|
|
82
|
+
if (!intent) intent = readIntent(done, fsImpl);
|
|
83
|
+
let fileWrites = 0;
|
|
84
|
+
const result = (extra ) => ({ schema: 1, row: row.id, runId: intent?.runId || runId,
|
|
85
|
+
phase: intent?.phase, commit: intent?.commit, fileWrites, ...extra });
|
|
86
|
+
const deny = (reason ) => result({ outcome: 'denied', reason });
|
|
87
|
+
const stopped = () => {
|
|
88
|
+
const state = options.isEnabled?.() ?? { enabled: true };
|
|
89
|
+
return state === false || state.enabled === false
|
|
90
|
+
? result({ outcome: 'stopped', annotations: { switch: state.switch || 'setting' } }) : null;
|
|
91
|
+
};
|
|
92
|
+
const phase = async (next ) => {
|
|
93
|
+
intent.phase = next;
|
|
94
|
+
writeIntent(file, intent, fsImpl);
|
|
95
|
+
await options.onPhase?.(next, JSON.parse(JSON.stringify(intent)));
|
|
96
|
+
};
|
|
97
|
+
// Ignore inherited git routing variables: every command must address the named checkout.
|
|
98
|
+
const environment = { ...env };
|
|
99
|
+
for (const key of Object.keys(environment)) if (/^GIT_(?:DIR|WORK_TREE|COMMON_DIR|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES|CONFIG_COUNT|CONFIG_KEY_\d+|CONFIG_VALUE_\d+)$/.test(key)) delete environment[key];
|
|
100
|
+
const execOptions = { cwd: root, encoding: 'utf8', env: environment, stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: 64 * 1024 * 1024 };
|
|
101
|
+
const version = String(execFileSync('git', ['--version'], execOptions)).match(/git version (\d+)\.(\d+)/);
|
|
102
|
+
if (!version || +version[1] < 2 || (+version[1] === 2 && +version[2] < 38)) return deny('git-too-old');
|
|
103
|
+
const checkouts = String(execFileSync('git', ['-C', root, 'worktree', 'list', '--porcelain', '-z'], execOptions))
|
|
104
|
+
.split('\0\0').filter(Boolean).map((block) => {
|
|
105
|
+
const fields = block.split('\0');
|
|
106
|
+
const listed = fields.find((s) => s.startsWith('worktree '))?.slice(9);
|
|
107
|
+
// git prints C:/x on Windows; the caller holds C:\x
|
|
108
|
+
return { worktree: listed === undefined ? listed : path.resolve(listed),
|
|
109
|
+
branch: fields.find((s) => s.startsWith('branch '))?.slice(7) };
|
|
110
|
+
});
|
|
111
|
+
const develop = intent?.developWorktree || options.developWorktree
|
|
112
|
+
|| checkouts.find((entry) => entry.branch === 'refs/heads/develop')?.worktree;
|
|
113
|
+
if (!develop || !fsImpl.existsSync(develop) || !checkouts.some((entry) => entry.worktree === path.resolve(develop))) return deny('no-develop-worktree');
|
|
114
|
+
let currentBranch ;
|
|
115
|
+
try { currentBranch = String(execFileSync('git', ['-C', develop, 'symbolic-ref', '-q', 'HEAD'], execOptions)).trim(); }
|
|
116
|
+
catch (error ) { if (error.status === 1) return deny('develop-wrong-branch'); throw error; }
|
|
117
|
+
if (currentBranch !== 'refs/heads/develop') return deny('develop-wrong-branch');
|
|
118
|
+
const head = () => String(execFileSync('git', ['-C', develop, 'rev-parse', 'HEAD'], execOptions)).trim();
|
|
119
|
+
const porcelain = () => String(execFileSync('git', ['-C', develop, 'status', '--porcelain', '--untracked-files=all'], execOptions));
|
|
120
|
+
const branch = `feat/${row.slug}`;
|
|
121
|
+
const featureHead = () => String(execFileSync('git', ['-C', root, 'rev-parse', '--verify', `refs/heads/${branch}`], execOptions)).trim();
|
|
122
|
+
const checkFeature = () => { try { return featureHead() === intent.featureHead; } catch { return false; } };
|
|
123
|
+
if (intent && (intent.row !== row.id || intent.slug !== row.slug)) throw new Error(`merge intent identity mismatch: ${file}`);
|
|
124
|
+
if (intent?.phase === 'cleaned') {
|
|
125
|
+
if (head() !== intent.commit) return deny('develop-moved');
|
|
126
|
+
if (fsImpl.existsSync(file)) { fsImpl.mkdirSync(path.dirname(done), { recursive: true }); fsImpl.renameSync(file, done); }
|
|
127
|
+
return result({ outcome: 'merged', cleanup: 'cleaned' });
|
|
128
|
+
}
|
|
129
|
+
if (!intent) {
|
|
130
|
+
const stop = stopped(); if (stop) return stop;
|
|
131
|
+
if (porcelain() !== '') return deny('develop-dirty');
|
|
132
|
+
let feature ;
|
|
133
|
+
try { feature = featureHead(); } catch { return deny('head-moved'); }
|
|
134
|
+
if (!SHA.test(approvedHead) || feature !== approvedHead) return deny('head-moved');
|
|
135
|
+
if (gate?.id !== 'gate-b' || !/^sha256:[a-f0-9]{64}(?:\+sha256:[a-f0-9]{64})?$/.test(gate?.digest)) throw new Error('merge requires gate-b approval digest');
|
|
136
|
+
const name = String(execFileSync('git', ['-C', develop, 'config', '--get', 'user.name'], execOptions)).trim();
|
|
137
|
+
const email = String(execFileSync('git', ['-C', develop, 'config', '--get', 'user.email'], execOptions)).trim();
|
|
138
|
+
if (!name || !email || /[\r\n<>]/.test(name + email)) throw new Error('invalid develop identity');
|
|
139
|
+
intent = { schema: 1, row: row.id, slug: row.slug, runId, phase: 'intent', developWorktree: develop,
|
|
140
|
+
developHead: head(), featureBranch: branch, featureHead: feature,
|
|
141
|
+
featureWorktree: options.featureWorktree || checkouts.find((entry) => entry.branch === `refs/heads/${branch}`)?.worktree || null,
|
|
142
|
+
gate: { id: gate.id, digest: gate.digest }, identity: { name, email, date: `${Math.floor(now() / 1000)} +0000` },
|
|
143
|
+
message: `merge: ${row.id} ${row.title}\n\nDoFlow-Row: ${row.id}\nDoFlow-Policy: ${gate.id}@${gate.digest}\n` };
|
|
144
|
+
await phase('intent');
|
|
145
|
+
}
|
|
146
|
+
// Git is the authority, even if a kill happened before the phase write.
|
|
147
|
+
let builtValid = false;
|
|
148
|
+
if (SHA.test(intent.commit) && SHA.test(intent.tree)) {
|
|
149
|
+
try {
|
|
150
|
+
execFileSync('git', ['-C', root, 'cat-file', '-e', intent.commit], execOptions);
|
|
151
|
+
const shape = String(execFileSync('git', ['-C', root, 'show', '-s', '--format=%T %P', intent.commit], execOptions)).trim();
|
|
152
|
+
builtValid = shape === `${intent.tree} ${intent.developHead} ${intent.featureHead}`;
|
|
153
|
+
} catch { /* Missing or mismatched object: rebuild only while still before the ff. */ }
|
|
154
|
+
}
|
|
155
|
+
if (!builtValid && !['intent', 'built'].includes(intent.phase)) return deny('develop-moved');
|
|
156
|
+
// HEAD at the built commit outranks a stale phase: the merge already happened, never make another.
|
|
157
|
+
if (builtValid && head() === intent.commit && PHASES.indexOf(intent.phase) < PHASES.indexOf('ff-done')) await phase('ff-done');
|
|
158
|
+
if (['intent', 'built'].includes(intent.phase)) {
|
|
159
|
+
if (head() !== intent.developHead) return deny('develop-moved');
|
|
160
|
+
if (!checkFeature()) return deny('head-moved');
|
|
161
|
+
if (porcelain() !== '') return deny('develop-dirty');
|
|
162
|
+
if (!builtValid) {
|
|
163
|
+
let tree ;
|
|
164
|
+
try { tree = String(execFileSync('git', ['-C', root, 'merge-tree', '--write-tree', intent.developHead, intent.featureHead], execOptions)).trim(); }
|
|
165
|
+
catch (error ) { if (error.status === 1) return deny('merge-conflict'); throw error; }
|
|
166
|
+
if (!SHA.test(tree)) throw new Error('unreadable merge-tree result');
|
|
167
|
+
const stop = stopped(); if (stop) return stop;
|
|
168
|
+
const identity = intent.identity;
|
|
169
|
+
const commit = String(execFileSync('git', ['-C', root, 'commit-tree', tree, '-p', intent.developHead, '-p', intent.featureHead, '--no-gpg-sign'], {
|
|
170
|
+
...execOptions, input: intent.message, env: { ...environment, GIT_AUTHOR_NAME: identity.name, GIT_AUTHOR_EMAIL: identity.email,
|
|
171
|
+
GIT_COMMITTER_NAME: identity.name, GIT_COMMITTER_EMAIL: identity.email, GIT_AUTHOR_DATE: identity.date, GIT_COMMITTER_DATE: identity.date },
|
|
172
|
+
})).trim();
|
|
173
|
+
if (!SHA.test(commit)) throw new Error('unreadable commit-tree result');
|
|
174
|
+
intent.tree = tree; intent.commit = commit;
|
|
175
|
+
await phase('built');
|
|
176
|
+
}
|
|
177
|
+
const stop = stopped(); if (stop) return stop;
|
|
178
|
+
if (!checkFeature()) return deny('head-moved');
|
|
179
|
+
if (head() !== intent.developHead) return deny('develop-moved');
|
|
180
|
+
if (porcelain() !== '') return deny('develop-dirty');
|
|
181
|
+
await phase('ff-started');
|
|
182
|
+
}
|
|
183
|
+
if (intent.phase === 'ff-started') {
|
|
184
|
+
if (head() !== intent.commit) {
|
|
185
|
+
if (head() !== intent.developHead) return deny('develop-moved');
|
|
186
|
+
if (!checkFeature()) return deny('head-moved');
|
|
187
|
+
// Any recorded child must be proven dead before retrying or touching an index lock.
|
|
188
|
+
if (intent.ff && alive(intent.ff) !== false) return deny('ff-running');
|
|
189
|
+
const lock = gitPath(develop, 'index.lock', execFileSync, execOptions);
|
|
190
|
+
if (porcelain() !== '' || fsImpl.existsSync(lock)) {
|
|
191
|
+
// Settling a proven-dead partial is allowed under an off switch; it starts no new merge.
|
|
192
|
+
if (!intent.ff) return deny('develop-dirty');
|
|
193
|
+
if (!repairFastForward({ intent, root, develop, execFileSync, execOptions, fsImpl, now, staleLockSeconds: options.staleLockSeconds ?? 120 })) return deny('develop-dirty');
|
|
194
|
+
} else {
|
|
195
|
+
// A clean develop at its old head: a new fast-forward would start here, so the switch decides.
|
|
196
|
+
const stop = stopped(); if (stop) return stop;
|
|
197
|
+
// Disable repo hooks; only the supplied git binary is launched by mechanical work.
|
|
198
|
+
await new Promise ((resolve, reject) => {
|
|
199
|
+
const child = spawn('git', ['-C', develop, '-c', 'core.hooksPath=/dev/null', 'merge', '--ff-only', intent.commit], {
|
|
200
|
+
cwd: develop, env: environment, stdio: ['ignore', 'ignore', 'pipe'],
|
|
201
|
+
});
|
|
202
|
+
child.stderr?.resume();
|
|
203
|
+
child.once('error', reject);
|
|
204
|
+
child.once('spawn', () => {
|
|
205
|
+
intent.ff = { pid: child.pid, start: null, startedAt: new Date(now()).toISOString() };
|
|
206
|
+
try { writeIntent(file, intent, fsImpl); } catch (error) { reject(error); }
|
|
207
|
+
});
|
|
208
|
+
child.once('close', (code ) => code === 0 ? resolve() : reject(new Error(`fast-forward failed (${code})`)));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (head() !== intent.commit) return deny('develop-moved');
|
|
213
|
+
await phase('ff-done');
|
|
214
|
+
}
|
|
215
|
+
if (head() !== intent.commit) return deny('develop-moved');
|
|
216
|
+
if (porcelain() !== '') return deny('develop-dirty');
|
|
217
|
+
const stop = stopped(); if (stop) return stop;
|
|
218
|
+
if (intent.phase === 'ff-done') {
|
|
219
|
+
let checked ;
|
|
220
|
+
try { checked = await options.postMergeCheck?.({ cwd: develop, head: intent.commit }); }
|
|
221
|
+
catch { checked = { ok: false }; }
|
|
222
|
+
if (checked?.ok !== true || head() !== intent.commit || porcelain() !== '') {
|
|
223
|
+
intent.status = 'merged-unverified'; writeIntent(file, intent, fsImpl);
|
|
224
|
+
return result({ outcome: 'merged-unverified', status: 'merged-unverified' });
|
|
225
|
+
}
|
|
226
|
+
delete intent.status;
|
|
227
|
+
intent.verifiedHead = intent.commit;
|
|
228
|
+
writeIntent(file, intent, fsImpl);
|
|
229
|
+
const stop = stopped(); if (stop) return stop;
|
|
230
|
+
const facts = (options.gatherFacts || gatherGitFacts)({ root });
|
|
231
|
+
if (facts.unreadable?.length) return result({ outcome: 'denied', error: 'cannot-read-facts', sources: facts.unreadable });
|
|
232
|
+
const seedFile = options.seedPath || path.join(root, 'agent-docs', 'roadmap', 'rows.json');
|
|
233
|
+
const rows = parseSeed(fsImpl.readFileSync(seedFile, 'utf8')).rows;
|
|
234
|
+
const { statuses } = deriveStatus(rows, facts);
|
|
235
|
+
const rendered = renderRoadmapFile({ roadmapPath: options.roadmapPath || path.join(root, 'agent-docs', 'roadmap', 'ROADMAP.md'), content: render(rows, statuses), fsImpl });
|
|
236
|
+
fileWrites += rendered.written ? 1 : 0;
|
|
237
|
+
await phase('rendered');
|
|
238
|
+
}
|
|
239
|
+
const stopCleanup = stopped(); if (stopCleanup) return stopCleanup;
|
|
240
|
+
if (intent.verifiedHead !== intent.commit) {
|
|
241
|
+
intent.status = 'merged-unverified'; writeIntent(file, intent, fsImpl);
|
|
242
|
+
return result({ outcome: 'merged-unverified', status: 'merged-unverified' });
|
|
243
|
+
}
|
|
244
|
+
if (head() !== intent.commit) return deny('develop-moved');
|
|
245
|
+
const artifacts = {};
|
|
246
|
+
if (!cleanupFeature({ intent, root, develop, execFileSync, execOptions, fsImpl, report: artifacts })) return result({ outcome: 'merged', cleanup: 'cleanup-kept', artifacts });
|
|
247
|
+
await phase('cleaned');
|
|
248
|
+
fsImpl.mkdirSync(path.dirname(done), { recursive: true }); fsImpl.renameSync(file, done);
|
|
249
|
+
return result({ outcome: 'merged', cleanup: 'cleaned', artifacts });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** `rev-parse --git-path` answers relative to the checkout it ran in (a main checkout's `.git/...`). */
|
|
253
|
+
function gitPath(checkout , name , execFileSync , execOptions ) {
|
|
254
|
+
return path.resolve(checkout, String(execFileSync('git', ['-C', checkout, 'rev-parse', '--git-path', name], execOptions)).trim());
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* All dirty paths must be changed by the merge. A working file must equal the commit's blob; a staged
|
|
259
|
+
* blob must equal the commit's or the old head's version of that path (a third version is a person's).
|
|
260
|
+
*/
|
|
261
|
+
function repairFastForward({ intent, root, develop, execFileSync, execOptions, fsImpl, now, staleLockSeconds } ) {
|
|
262
|
+
const lock = gitPath(develop, 'index.lock', execFileSync, execOptions);
|
|
263
|
+
if (fsImpl.existsSync(lock) && now() - fsImpl.statSync(lock).mtimeMs <= staleLockSeconds * 1000) return false;
|
|
264
|
+
const changed = new Set(String(execFileSync('git', ['-C', root, 'diff', '--name-only', '-z', intent.developHead, intent.commit], execOptions)).split('\0').filter(Boolean));
|
|
265
|
+
const unstaged = String(execFileSync('git', ['-C', develop, 'diff', '--name-only', '-z'], execOptions));
|
|
266
|
+
const staged = String(execFileSync('git', ['-C', develop, 'diff', '--cached', '--name-only', '-z'], execOptions)).split('\0').filter(Boolean);
|
|
267
|
+
const untracked = String(execFileSync('git', ['-C', develop, 'ls-files', '-o', '--exclude-standard', '-z'], execOptions));
|
|
268
|
+
const blob = (rev , name ) => {
|
|
269
|
+
try { return String(execFileSync('git', ['-C', root, 'rev-parse', '--verify', '-q', `${rev}:${name}`], execOptions)).trim(); }
|
|
270
|
+
catch { return null; }
|
|
271
|
+
};
|
|
272
|
+
for (const name of new Set([...(unstaged + untracked).split('\0').filter(Boolean), ...staged])) {
|
|
273
|
+
if (!changed.has(name)) return false;
|
|
274
|
+
const full = path.join(develop, name);
|
|
275
|
+
// Symlinks, missing paths and type/mode changes need human resolution.
|
|
276
|
+
let stat ;
|
|
277
|
+
try { stat = fsImpl.lstatSync(full); } catch { return false; }
|
|
278
|
+
if (!stat.isFile() || stat.isSymbolicLink()) return false;
|
|
279
|
+
const expected = blob(intent.commit, name);
|
|
280
|
+
if (!expected) return false;
|
|
281
|
+
const actual = String(execFileSync('git', ['-C', develop, 'hash-object', '--no-filters', '--', name], execOptions)).trim();
|
|
282
|
+
if (actual !== expected) return false;
|
|
283
|
+
}
|
|
284
|
+
if (staged.length) {
|
|
285
|
+
// `ls-files -s -z`: "<mode> <blob> <stage>\t<path>\0". Only stage-0 entries of a known version pass.
|
|
286
|
+
const index = new Map ();
|
|
287
|
+
for (const entry of String(execFileSync('git', ['-C', develop, 'ls-files', '-s', '-z'], execOptions)).split('\0').filter(Boolean)) {
|
|
288
|
+
const match = entry.match(/^[0-7]{6} ([0-9a-f]{40}) ([0-3])\t(.+)$/s);
|
|
289
|
+
if (!match) return false;
|
|
290
|
+
if (match[2] !== '0' || index.has(match[3])) index.set(match[3], 'unmerged');
|
|
291
|
+
else index.set(match[3], match[1]);
|
|
292
|
+
}
|
|
293
|
+
for (const name of staged) {
|
|
294
|
+
const sha = index.get(name);
|
|
295
|
+
if (!sha || (sha !== blob(intent.commit, name) && sha !== blob(intent.developHead, name))) return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// All refusal checks precede the first write. read-tree rewrites the index before the ref update,
|
|
299
|
+
// which compares the old head atomically; when that compare-and-swap fails the index bytes read
|
|
300
|
+
// here are put back, so a refused repair leaves develop's index as it found it.
|
|
301
|
+
if (fsImpl.existsSync(lock)) fsImpl.unlinkSync(lock);
|
|
302
|
+
const indexFile = gitPath(develop, 'index', execFileSync, execOptions);
|
|
303
|
+
let saved = null;
|
|
304
|
+
try { saved = fsImpl.readFileSync(indexFile); } catch (error ) { if (error.code !== 'ENOENT') throw error; }
|
|
305
|
+
execFileSync('git', ['-C', develop, 'read-tree', intent.commit], execOptions);
|
|
306
|
+
try { execFileSync('git', ['-C', develop, 'update-ref', 'refs/heads/develop', intent.commit, intent.developHead], execOptions); }
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (saved) fsImpl.writeFileSync(indexFile, saved);
|
|
309
|
+
else fsImpl.rmSync(indexFile, { force: true });
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
try { execFileSync('git', ['-C', develop, 'checkout-index', '-a'], execOptions); }
|
|
313
|
+
catch (error ) {
|
|
314
|
+
// Without force, checkout-index reports already-existing paths with exit 1. It still writes
|
|
315
|
+
// missing paths; refresh and the final porcelain assertion decide whether repair completed.
|
|
316
|
+
if (error.status !== 1) throw error;
|
|
317
|
+
}
|
|
318
|
+
execFileSync('git', ['-C', develop, 'update-index', '-q', '--refresh'], execOptions);
|
|
319
|
+
return String(execFileSync('git', ['-C', develop, 'status', '--porcelain', '--untracked-files=all'], execOptions)) === '';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Copy the worktree's gitignored feature folder into the main checkout; a main-checkout file already
|
|
324
|
+
* there is never overwritten. Identical bytes are reported as `skipped`; differing bytes as `conflicts`
|
|
325
|
+
* (the worktree copy would be lost on removal, so the caller keeps the worktree). Symlinks and other
|
|
326
|
+
* non-file entries throw (fail closed).
|
|
327
|
+
*/
|
|
328
|
+
function copyFeatureFolder(from , to , fsImpl , report , rel = '') {
|
|
329
|
+
fsImpl.mkdirSync(to, { recursive: true });
|
|
330
|
+
for (const entry of fsImpl.readdirSync(from, { withFileTypes: true })) {
|
|
331
|
+
const name = path.join(rel, entry.name);
|
|
332
|
+
if (entry.isDirectory()) copyFeatureFolder(path.join(from, entry.name), path.join(to, entry.name), fsImpl, report, name);
|
|
333
|
+
else if (!entry.isFile()) throw new Error(`cannot copy ${name}: not a regular file`);
|
|
334
|
+
else {
|
|
335
|
+
try { fsImpl.copyFileSync(path.join(from, entry.name), path.join(to, entry.name), fsImpl.constants.COPYFILE_EXCL); report.copied++; }
|
|
336
|
+
catch (error ) {
|
|
337
|
+
if (error.code !== 'EEXIST') throw error;
|
|
338
|
+
const same = fsImpl.readFileSync(path.join(from, entry.name)).equals(fsImpl.readFileSync(path.join(to, entry.name)));
|
|
339
|
+
(same ? report.skipped : report.conflicts).push(name);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** `report` (optional) receives `copied`, `skipped` and, when the cleanup is kept, `failure`. */
|
|
346
|
+
function cleanupFeature({ intent, root, develop, execFileSync, execOptions, fsImpl, report = {} } ) {
|
|
347
|
+
report.copied = 0; report.skipped = []; report.conflicts = [];
|
|
348
|
+
const feature = intent.featureWorktree;
|
|
349
|
+
const branch = intent.featureBranch;
|
|
350
|
+
try {
|
|
351
|
+
let featureHead ;
|
|
352
|
+
try { featureHead = String(execFileSync('git', ['-C', root, 'rev-parse', '--verify', `refs/heads/${branch}`], execOptions)).trim(); }
|
|
353
|
+
catch { return !feature || !fsImpl.existsSync(feature); }
|
|
354
|
+
if (featureHead !== intent.featureHead) return false;
|
|
355
|
+
// Only a tip already on develop may lose its worktree: a branch someone kept committing on keeps
|
|
356
|
+
// both the worktree (and its gitignored agent-docs/.doflow files) and the branch.
|
|
357
|
+
try { execFileSync('git', ['-C', root, 'merge-base', '--is-ancestor', featureHead, 'refs/heads/develop'], execOptions); }
|
|
358
|
+
catch { return false; }
|
|
359
|
+
if (feature && fsImpl.existsSync(feature)) {
|
|
360
|
+
const current = String(execFileSync('git', ['-C', feature, 'symbolic-ref', '-q', 'HEAD'], execOptions)).trim();
|
|
361
|
+
if (current !== `refs/heads/${branch}`) return false;
|
|
362
|
+
if (String(execFileSync('git', ['-C', feature, 'status', '--porcelain', '--untracked-files=all'], execOptions)) !== '') return false;
|
|
363
|
+
const slug = branch.replace(/^feat\//, '');
|
|
364
|
+
const folder = path.join(feature, 'agent-docs', 'doflow', slug);
|
|
365
|
+
// The folder is gitignored, so `worktree remove` would delete it: copy it first, keep on any failure.
|
|
366
|
+
if (slug && !slug.includes('/') && fsImpl.existsSync(folder)) {
|
|
367
|
+
try { copyFeatureFolder(folder, path.join(root, 'agent-docs', 'doflow', slug), fsImpl, report); }
|
|
368
|
+
catch (error ) { report.failure = `artifact copy failed: ${error.message}`; return false; }
|
|
369
|
+
if (report.conflicts.length) {
|
|
370
|
+
report.failure = `kept: main checkout copy differs from the worktree copy of ${report.conflicts.join(', ')}`;
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
execFileSync('git', ['-C', develop, 'worktree', 'remove', '--', feature], execOptions);
|
|
375
|
+
}
|
|
376
|
+
execFileSync('git', ['-C', develop, 'branch', '-d', '--', branch], execOptions);
|
|
377
|
+
return true;
|
|
378
|
+
} catch (error ) { report.failure ||= error.message; return false; }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
module.exports = { mergeRoadmapRow, readOpenIntents, writeIntent, cleanupFeature };
|