@khoavu882/doflow 2.0.0-beta.5 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -16
- package/bin/doflow.js +22 -12
- package/core/.antigravity-plugin/plugin.json +1 -2
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +26 -9
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/hooks.json +0 -10
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +51 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -11
- package/core/harnesses/kiro/hooks/doflow.json +0 -10
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
- package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +35 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1445 -366
- package/core/registry/intake.json +53 -0
- package/core/registry/lifecycle.json +599 -996
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/intake-category/files/category.json +8 -0
- package/core/registry/package-templates/intake-category/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +272 -0
- package/core/registry/storage.json +687 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -6
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
- package/core/shared/scripts/doflow/bin/doflow-run +152 -34
- package/core/shared/skills/do/SKILL.md +14 -12
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +48 -148
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +23 -20
- package/core/shared/skills/do-diagnose/SKILL.md +18 -6
- package/core/shared/skills/do-document/SKILL.md +12 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +19 -7
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +10 -11
- package/core/shared/skills/do-flow/references/task_classes.md +5 -5
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +13 -14
- package/core/shared/skills/do-plan/SKILL.md +34 -20
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/shared/workflows/do-flow-chain.md +2 -0
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +143 -66
- package/src/adapters/claude/index.js +46 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +39 -3
- package/src/adapters/codex/index.js +61 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +291 -46
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +22 -3
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +337 -32
- package/src/cli/runtime-commands.js +220 -60
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +4 -16
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +472 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +20 -11
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/pointer.js +67 -12
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +158 -79
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-ledger.js +52 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +624 -18
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +281 -26
- package/src/runtime/intake/engine.js +677 -0
- package/src/runtime/intake/index.js +136 -0
- package/src/runtime/intake/manifest.js +140 -0
- package/src/runtime/intake/taxonomy.js +206 -0
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +88 -10
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +38 -7
- package/src/runtime/lifecycle/overview.js +26 -2
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +2 -2
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +487 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/attendance.js +137 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +273 -0
- package/src/runtime/roadmap/loop.js +1719 -0
- package/src/runtime/roadmap/merge.js +390 -0
- package/src/runtime/roadmap/rows.js +1122 -0
- package/src/runtime/roadmap/seed.js +1063 -0
- package/src/runtime/roadmap/worktree-cleanup.js +147 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +1 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +85 -14
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +373 -64
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verb-view.js +267 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -54
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +256 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +610 -67
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -251
- package/src/adapters/gemini/index.js +0 -429
|
@@ -93,11 +93,18 @@ function classify(bodyLines , kinds , canonicalPointer
|
|
|
93
93
|
return { comments, moved, pointer };
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function writeAtomic(fsImpl , file , content ) {
|
|
96
|
+
function writeAtomic(fsImpl , file , content , unchangedSince ) {
|
|
97
97
|
fsImpl.mkdirSync(path.dirname(file), { recursive: true });
|
|
98
98
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
99
99
|
try {
|
|
100
100
|
fsImpl.writeFileSync(tmp, content, 'utf8');
|
|
101
|
+
// The text `content` was built from is compared with the file just before the rename, so an edit
|
|
102
|
+
// made since it was read is kept instead of replaced (FU-1jbmm3). This narrows the window to the
|
|
103
|
+
// gap between this read and the rename; it cannot close it, which would take a lock on the
|
|
104
|
+
// artifact itself that its editors would also have to take.
|
|
105
|
+
if (unchangedSince !== undefined && fsImpl.readFileSync(file, 'utf8') !== unchangedSince) {
|
|
106
|
+
throw new Error(`${path.basename(file)} changed while it was being compacted`);
|
|
107
|
+
}
|
|
101
108
|
fsImpl.renameSync(tmp, file);
|
|
102
109
|
} catch (error ) {
|
|
103
110
|
try { fsImpl.rmSync(tmp, { force: true }); } catch { /* nothing to clean */ }
|
|
@@ -189,7 +196,7 @@ function compactArtifact({ fsImpl, featureDir, file, date }
|
|
|
189
196
|
const body = [blank];
|
|
190
197
|
for (const c of comments) body.push(...c, blank);
|
|
191
198
|
body.push(pointerLine, end === lines.length ? '' : blank);
|
|
192
|
-
writeAtomic(fsImpl, file, [...lines.slice(0, start + 1), ...body, ...lines.slice(end)].join('\n'));
|
|
199
|
+
writeAtomic(fsImpl, file, [...lines.slice(0, start + 1), ...body, ...lines.slice(end)].join('\n'), text);
|
|
193
200
|
return { artifact: name, path: rel, archive: archiveRel, lines: moved.length };
|
|
194
201
|
}
|
|
195
202
|
|
|
@@ -204,7 +211,10 @@ function compactArtifact({ fsImpl, featureDir, file, date }
|
|
|
204
211
|
* @param {string} [options.date] YYYY-MM-DD stamped on the archive chunk; defaults to today (UTC)
|
|
205
212
|
* @param {Object} [options.fsImpl] fs implementation (tests substitute one)
|
|
206
213
|
*
|
|
207
|
-
* Not locked: concurrent callers can interleave archive appends
|
|
214
|
+
* Not locked: concurrent callers can interleave archive appends, and an artifact edited between its
|
|
215
|
+
* read and its rewrite is detected just before the rename and left as the editor wrote it (the
|
|
216
|
+
* artifact is reported in `failed`; the moved block is already in the archive and a later run does
|
|
217
|
+
* not copy it twice), but an edit landing in the last instant before the rename is not. Call it through
|
|
208
218
|
* `compactDecisions` / `runDecision` in decision-register.js, which hold the register lock.
|
|
209
219
|
*
|
|
210
220
|
* Every artifact is attempted. One that cannot be compacted is left as it was and reported in
|
|
@@ -8,7 +8,7 @@ const { readReadinessRecord, newerVersionOf, RECORD_VERSION } = require('./readi
|
|
|
8
8
|
const { listCheckouts } = require('./checkouts.js');
|
|
9
9
|
const { readTaskState } = require('./task-state.js');
|
|
10
10
|
const { resolveActiveFeature } = require('./feature-resolve.js');
|
|
11
|
-
const { isSafeSlug } = require('./task-scope.js');
|
|
11
|
+
const { isSafeSlug, flatRecordIsOurs, readRegularFileText, MAX_RECORD_FILE_BYTES } = require('./task-scope.js');
|
|
12
12
|
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
13
13
|
|
|
14
14
|
// The implementation gate: which stage of a workflow needs a READY readiness record before it is
|
|
@@ -19,6 +19,15 @@ const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
|
19
19
|
// orchestrator, `verify` and the edit hook (which prints the same bytes) say the same thing.
|
|
20
20
|
|
|
21
21
|
const GATE = 'doflow gate readiness-before-implementation';
|
|
22
|
+
const RUN_RECORD_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
|
|
23
|
+
const RUN_NAMESPACE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
24
|
+
/** Where a checkout keeps its run records, relative to the checkout; the one place the path is named. */
|
|
25
|
+
const RUN_STORE_REL = path.join('.doflow', 'state', 'orchestration');
|
|
26
|
+
|
|
27
|
+
/** The run store of the checkout at `root`. */
|
|
28
|
+
function runStoreDir(root ) {
|
|
29
|
+
return path.join(root, RUN_STORE_REL);
|
|
30
|
+
}
|
|
22
31
|
|
|
23
32
|
/** What clears a record a newer DoFlow wrote; `readiness` here refuses to overwrite it. */
|
|
24
33
|
const NEWER_NEXT = 'run this command with the DoFlow that wrote the record, or upgrade DoFlow here.';
|
|
@@ -55,13 +64,14 @@ function nextCommand({ taskId, slug, template } ) {
|
|
|
55
64
|
|
|
56
65
|
/**
|
|
57
66
|
* The refusal for one check code.
|
|
58
|
-
* @param {'missing'|'not-ready'|'wrong-template'|'unusable'|'ambiguous'} code
|
|
67
|
+
* @param {'missing'|'not-ready'|'wrong-template'|'unusable'|'ambiguous'|'store-conflict'|'ambiguous-run'} code
|
|
59
68
|
* @param {Object} options
|
|
60
69
|
* @param {string} options.taskId
|
|
61
70
|
* @param {string|null} [options.slug]
|
|
62
71
|
* @param {string} options.template the template the gated stage needs
|
|
63
72
|
* @param {Object|null} [options.record] the record read, for `not-ready` and `wrong-template`
|
|
64
|
-
* @param {Array<string>} [options.candidates] the files found, for `ambiguous
|
|
73
|
+
* @param {Array<string>} [options.candidates] the files found, for `ambiguous`; the worktree's then the
|
|
74
|
+
* main checkout's, for `store-conflict`
|
|
65
75
|
* @param {string|null} [options.detail] why the record cannot be used, for `unusable`
|
|
66
76
|
* @param {string|null} [options.file] the record's file, for `unusable` when a newer DoFlow wrote it
|
|
67
77
|
* @param {number|null} [options.newerVersion] that newer record version; `readiness` would refuse to
|
|
@@ -86,6 +96,8 @@ function refusalText(code , { taskId, slug = null, template, record = null,
|
|
|
86
96
|
return `${GATE}: task '${taskId}' has a readiness record that cannot be used (${detail}). ${tail}`;
|
|
87
97
|
case 'ambiguous':
|
|
88
98
|
return `${GATE}: task '${taskId}' has records in more than one other checkout (${candidates.join(', ')}). Next: run the command from the checkout that holds the one you mean, or run ${next} here. Nothing was changed.`;
|
|
99
|
+
case 'store-conflict':
|
|
100
|
+
return `${GATE}: task '${taskId}' has a readiness record in this worktree (${candidates[0]}) that differs from the main checkout's (${candidates[1]}). Next: keep one of the two and remove the other, then re-run. Nothing was changed.`;
|
|
89
101
|
case 'ambiguous-run':
|
|
90
102
|
// A readiness record written here cannot say which run is meant, so it is not offered.
|
|
91
103
|
return `${GATE}: task '${taskId}' has runs in more than one other checkout (${candidates.join(', ')}). Next: run verify from the checkout that holds the run you mean. Nothing was changed.`;
|
|
@@ -95,8 +107,8 @@ function refusalText(code , { taskId, slug = null, template, record = null,
|
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
/**
|
|
98
|
-
* Which harnesses run this gate before each source edit and which meet
|
|
99
|
-
* gate's own mapping in lifecycle.json (`pre-implementation-gate`): `supported`, or `different`
|
|
110
|
+
* Which harnesses run this gate's advisory hook before each source edit and which meet the runtime refusal first at
|
|
111
|
+
* handoff, from the gate's own mapping in lifecycle.json (`pre-implementation-gate`): `supported`, or `different`
|
|
100
112
|
* with no prerequisite, runs it; a prerequisite (such as Codex's hook review) is named, since until
|
|
101
113
|
* it is met nothing was checked; `unavailable` has no hook layer for it. Empty when the registry
|
|
102
114
|
* cannot be read.
|
|
@@ -122,8 +134,8 @@ function harnessHookNote({ repoRoot = REPO_ROOT } = {}) {
|
|
|
122
134
|
const parts = [];
|
|
123
135
|
if (runs.length) parts.push(`${runs.join(', ')} ${runs.length === 1 ? 'runs' : 'run'} this check before each source edit`);
|
|
124
136
|
for (const [key, ids] of gated) parts.push(`${ids.join(', ')} ${ids.length === 1 ? 'runs' : 'run'} it only after ${key}`);
|
|
125
|
-
if (none.length) parts.push(`${none.join(', ')} ${none.length === 1 ? 'has' : 'have'} no hook layer, so this refusal is ${none.length === 1 ? 'its' : 'their'}
|
|
126
|
-
return `Edit-time check: ${parts.join('; ')}.`;
|
|
137
|
+
if (none.length) parts.push(`${none.join(', ')} ${none.length === 1 ? 'has' : 'have'} no hook layer, so this refusal is ${none.length === 1 ? 'its' : 'their'} only check`);
|
|
138
|
+
return `Edit-time check: ${parts.join('; ')}. The hook only advises and never refuses an edit; this refusal is the runtime's.`;
|
|
127
139
|
} catch {
|
|
128
140
|
return '';
|
|
129
141
|
}
|
|
@@ -168,35 +180,269 @@ function runSlugOf(run , root ) {
|
|
|
168
180
|
return run && isSafeSlug(run.taskId) && isDir(folderOf(root, run.taskId)) ? run.taskId : null;
|
|
169
181
|
}
|
|
170
182
|
|
|
183
|
+
/** Whether a store directory can be a run namespace rather than a lock directory. */
|
|
184
|
+
function isRunNamespaceDir(name ) {
|
|
185
|
+
return RUN_NAMESPACE_PATTERN.test(name) && !name.includes('..') && !name.endsWith('.lock');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The run record files under one checkout's store: flat `<id>.json` and `<slug>/<id>.json`, by
|
|
189
|
+
* path, without reading them. The one listing `findRunFile` and `orchestrationRuns` share.
|
|
190
|
+
* With `listing` (IC-001, the list action), a symbolic link is never followed: a linked run file is
|
|
191
|
+
* kept with `linked: true` so it can be reported as unreadable, and a linked namespace directory is
|
|
192
|
+
* not entered, so nothing outside the store root is read or named. */
|
|
193
|
+
function runFilesAt(root , { listing = false } = {}) {
|
|
194
|
+
const store = runStoreDir(root);
|
|
195
|
+
const files = [];
|
|
196
|
+
const take = (entry ) => entry.isFile() || entry.isSymbolicLink();
|
|
197
|
+
let entries ;
|
|
198
|
+
try { entries = fs.readdirSync(store, { withFileTypes: true }); } catch { return []; }
|
|
199
|
+
for (const entry of entries) {
|
|
200
|
+
if (take(entry) && RUN_RECORD_PATTERN.test(entry.name)) {
|
|
201
|
+
files.push({ file: path.join(store, entry.name), namespace: null, linked: entry.isSymbolicLink() });
|
|
202
|
+
} else if ((entry.isDirectory() || (!listing && entry.isSymbolicLink())) && isRunNamespaceDir(entry.name)) {
|
|
203
|
+
let nested ;
|
|
204
|
+
try { nested = fs.readdirSync(path.join(store, entry.name), { withFileTypes: true }); } catch { continue; }
|
|
205
|
+
for (const item of nested) {
|
|
206
|
+
if (take(item) && RUN_RECORD_PATTERN.test(item.name)) {
|
|
207
|
+
files.push({ file: path.join(store, entry.name, item.name), namespace: entry.name, linked: entry.isSymbolicLink() || item.isSymbolicLink() });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return files.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The largest run file the list action and `readRunRecord` read; a bigger one is reported as `io` /
|
|
216
|
+
* refused (a journal is a few KiB). */
|
|
217
|
+
const MAX_RUN_FILE_BYTES = MAX_RECORD_FILE_BYTES;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* One run's record, read the way the list action reads it (FU-16c2wz): no link is followed on the
|
|
221
|
+
* way from the store folder `storeDir` to the file or at the file, and the file must be a regular
|
|
222
|
+
* file of at most `MAX_RUN_FILE_BYTES`, so a FIFO or a device cannot block the read and a link
|
|
223
|
+
* cannot make it read outside the store. A run file that is not there is `null`; one that must not
|
|
224
|
+
* be read throws `store-unreadable` naming the file, and nothing of it is read.
|
|
225
|
+
* @param {Object} fsImpl
|
|
226
|
+
* @param {string} file the run file, under `storeDir`
|
|
227
|
+
* @param {string} storeDir the run store folder it belongs to
|
|
228
|
+
* @returns {Object|null} the parsed record
|
|
229
|
+
*/
|
|
230
|
+
function readRunRecord(fsImpl , file , storeDir ) {
|
|
231
|
+
if (typeof fsImpl.lstatSync !== 'function' || typeof fsImpl.openSync !== 'function') return readTaskState(fsImpl, file);
|
|
232
|
+
const text = readRunText(fsImpl, file, storeDir);
|
|
233
|
+
return text === null ? null : readTaskState({ existsSync: () => true, readFileSync: () => text }, file);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** `readRunRecord`'s guarded read without the parse: the run file's text, `null` when it is not
|
|
237
|
+
* there, `store-unreadable` when it must not be read. */
|
|
238
|
+
function readRunText(fsImpl , file , storeDir ) {
|
|
239
|
+
const relative = path.relative(storeDir, file);
|
|
240
|
+
const parts = relative === '' || relative.startsWith('..') || path.isAbsolute(relative) ? [] : relative.split(path.sep);
|
|
241
|
+
const refuse = () => {
|
|
242
|
+
const failure = new Error(`Run file '${file}' is not a regular file inside the run store (a link, FIFO, device or folder, or larger than ${MAX_RUN_FILE_BYTES} bytes). Nothing was read.`);
|
|
243
|
+
failure.code = 'store-unreadable';
|
|
244
|
+
throw failure;
|
|
245
|
+
};
|
|
246
|
+
let at = storeDir;
|
|
247
|
+
for (const part of parts.slice(0, -1)) {
|
|
248
|
+
at = path.join(at, part);
|
|
249
|
+
let stat ;
|
|
250
|
+
try { stat = fsImpl.lstatSync(at); } catch (error ) {
|
|
251
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null;
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
if (stat.isSymbolicLink()) refuse();
|
|
255
|
+
}
|
|
256
|
+
let stat ;
|
|
257
|
+
try { stat = fsImpl.lstatSync(file); } catch (error ) {
|
|
258
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null;
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
if (!stat.isFile()) refuse();
|
|
262
|
+
const text = readRegularFileText(file, fsImpl);
|
|
263
|
+
if (text === null) refuse();
|
|
264
|
+
return text;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** The run files under one checkout's store, read, with each malformed or unreadable file isolated. */
|
|
268
|
+
function runEntriesAt(root , origin ) {
|
|
269
|
+
return runFilesAt(root, { listing: true }).map(({ file, namespace, linked }) => {
|
|
270
|
+
let record = null;
|
|
271
|
+
let error = null;
|
|
272
|
+
const bytes = linked ? null : readRegularFileText(file);
|
|
273
|
+
if (bytes === null) error = 'io';
|
|
274
|
+
else {
|
|
275
|
+
try { record = JSON.parse(bytes); } catch { error = 'unparseable'; }
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
file,
|
|
279
|
+
root,
|
|
280
|
+
origin,
|
|
281
|
+
slug: namespace || runSlugOf(record, root),
|
|
282
|
+
record,
|
|
283
|
+
error,
|
|
284
|
+
};
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* All orchestration journals in the current store and the other checkouts, when Git can list them.
|
|
290
|
+
* @param {{stateRoot: string, exec?: Function}} options
|
|
291
|
+
* @returns {{ok: true, entries: Array<Object>}|{ok: false, error: 'cannot-list-checkouts'}}
|
|
292
|
+
*/
|
|
293
|
+
function orchestrationRuns({ stateRoot, exec } ) {
|
|
294
|
+
const current = runEntriesAt(stateRoot, 'current');
|
|
295
|
+
const checkouts = listCheckouts({ cwd: stateRoot, ...(exec ? { exec } : {}) });
|
|
296
|
+
if (!checkouts.ok && checkouts.reason !== 'not-a-git-repository') {
|
|
297
|
+
return { ok: false, error: 'cannot-list-checkouts' };
|
|
298
|
+
}
|
|
299
|
+
if (!checkouts.ok || checkouts.sandbox) return { ok: true, entries: current };
|
|
300
|
+
|
|
301
|
+
const entries = [...current];
|
|
302
|
+
for (const root of checkouts.others) entries.push(...runEntriesAt(root, 'other'));
|
|
303
|
+
return { ok: true, entries };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Whether a flat record names no feature (no `featureSlug`, no `slug`): such a run is any feature's
|
|
307
|
+
* (`flatRecordIsOurs`), so a namespaced copy of the same id in the same store shadows it. */
|
|
308
|
+
function namesNoFeature(record ) {
|
|
309
|
+
return Boolean(record) && typeof record === 'object'
|
|
310
|
+
&& ![record.featureSlug, record.slug].some((value) => typeof value === 'string' && value !== '');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* IC-001/IC-003: one entry per logical run, the copy `status` reads, with `findRunFile`'s
|
|
315
|
+
* precedence. In one store, the namespaced `<slug>/<id>.json` wins over a flat `<id>.json` that
|
|
316
|
+
* names that feature (when the slug is the id, the flat file is the run's only path and wins).
|
|
317
|
+
* A flat file that names no feature, or that cannot be read or is a link, is keyed as `findRunFile`
|
|
318
|
+
* would first read it, the id as its slug, and stays its own entry. Across stores, the current
|
|
319
|
+
* store's copy of a key wins over every other checkout's; a current flat file `status` reads for
|
|
320
|
+
* any feature without a namespaced copy here (one naming no feature, or a link, which status refuses)
|
|
321
|
+
* also shadows the other checkouts' copies of that id. With no current copy, each other checkout
|
|
322
|
+
* that holds one keeps its entry (more than one is the case `status` refuses as ambiguous). A
|
|
323
|
+
* losing copy is dropped: `status` never reads it for this run.
|
|
324
|
+
* @param {Array<Object>} entries `orchestrationRuns(...).entries`
|
|
325
|
+
* @returns {Array<Object>} the winning entries, in input order
|
|
326
|
+
*/
|
|
327
|
+
function selectRunEntries(entries ) {
|
|
328
|
+
const stem = (entry ) => path.basename(entry.file, '.json');
|
|
329
|
+
const namespaceOf = (entry ) => {
|
|
330
|
+
const parts = path.relative(runStoreDir(entry.root), entry.file).split(path.sep);
|
|
331
|
+
return parts.length > 1 ? parts[0] : null;
|
|
332
|
+
};
|
|
333
|
+
const named = (entry ) => entry.record && !namesNoFeature(entry.record) && !entry.error;
|
|
334
|
+
const linked = (entry ) => {
|
|
335
|
+
try { return fs.lstatSync(entry.file).isSymbolicLink(); } catch { return false; }
|
|
336
|
+
};
|
|
337
|
+
const keyOf = (entry ) => {
|
|
338
|
+
const ns = namespaceOf(entry);
|
|
339
|
+
if (ns !== null) return `${ns}\0${stem(entry)}`;
|
|
340
|
+
if (named(entry) && entry.slug) return `${entry.slug}\0${stem(entry)}`;
|
|
341
|
+
return `${stem(entry)}\0${stem(entry)}`;
|
|
342
|
+
};
|
|
343
|
+
const rank = (entry ) => {
|
|
344
|
+
const ns = namespaceOf(entry);
|
|
345
|
+
if (ns === null) return named(entry) && entry.slug !== stem(entry) ? 1 : 0;
|
|
346
|
+
return ns === stem(entry) ? 2 : 0;
|
|
347
|
+
};
|
|
348
|
+
// Ids a current flat file answers for any feature: status reads it before any other checkout.
|
|
349
|
+
const wildcards = new Set(entries.filter((entry) => entry.origin === 'current' && namespaceOf(entry) === null
|
|
350
|
+
&& ((entry.record && namesNoFeature(entry.record)) || linked(entry))).map(stem));
|
|
351
|
+
const perRoot = new Map ();
|
|
352
|
+
for (const entry of entries) {
|
|
353
|
+
const local = `${entry.root}\0${keyOf(entry)}`;
|
|
354
|
+
const held = perRoot.get(local);
|
|
355
|
+
if (!held || rank(entry) < rank(held)) perRoot.set(local, entry);
|
|
356
|
+
}
|
|
357
|
+
const best = new Map ();
|
|
358
|
+
for (const entry of perRoot.values()) {
|
|
359
|
+
const key = keyOf(entry);
|
|
360
|
+
const held = best.get(key);
|
|
361
|
+
if (!held) best.set(key, [entry]);
|
|
362
|
+
else if (held[0].origin === 'current') continue;
|
|
363
|
+
else if (entry.origin === 'current') best.set(key, [entry]);
|
|
364
|
+
else held.push(entry);
|
|
365
|
+
}
|
|
366
|
+
const winners = new Set ();
|
|
367
|
+
for (const list of best.values()) {
|
|
368
|
+
for (const entry of list) {
|
|
369
|
+
if (entry.origin !== 'current' && wildcards.has(stem(entry))) continue;
|
|
370
|
+
winners.add(entry);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return entries.filter((entry) => winners.has(entry));
|
|
374
|
+
}
|
|
375
|
+
|
|
171
376
|
/**
|
|
172
377
|
* A task's run file: in this checkout, else in exactly one other checkout whose run belongs to the
|
|
173
|
-
* same feature.
|
|
174
|
-
*
|
|
378
|
+
* same feature. The run of a task id other than its feature's slug is `orchestration/<slug>/<id>.json`
|
|
379
|
+
* (115 C-1); a flat run of that id is read in its place only when it is this feature's or names none
|
|
380
|
+
* (D-3). Task ids repeat across features, so another checkout's run with the same id but another
|
|
381
|
+
* feature (or none) is not this task's and is never read or updated from here.
|
|
175
382
|
* @param {{stateRoot: string, taskId: string, slug: string|null, exec?: Function}} options
|
|
176
383
|
* @returns {{status: 'found'|'missing'|'ambiguous', file: string|null, root: string|null,
|
|
177
384
|
* origin: 'current'|'other'|null, candidates: Array<string>}}
|
|
178
385
|
*/
|
|
179
|
-
function findRunFile({ stateRoot, taskId, slug, exec } ) {
|
|
180
|
-
const
|
|
386
|
+
function findRunFile({ stateRoot, taskId, slug, exec, anyNamespace = false } ) {
|
|
387
|
+
const found = findRunFileFor({ stateRoot, taskId, slug, exec });
|
|
388
|
+
return anyNamespace && found.status === 'missing' ? findNamespacedRun({ stateRoot, taskId, exec }) : found;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* FU-wrnnaq: the run of `taskId` under whichever feature namespace holds it, for a caller that has
|
|
393
|
+
* no feature to name (`status` run from another branch). This is the run the list action shows for
|
|
394
|
+
* the id: this checkout's namespaced run first, else the other checkouts'; exactly one is the run,
|
|
395
|
+
* more than one is `ambiguous` (the feature must then be named), none is `missing`. `slug` is the
|
|
396
|
+
* namespace the run is in, for the caller to read it by.
|
|
397
|
+
* @returns {{status: 'found'|'missing'|'ambiguous', file: string|null, root: string|null,
|
|
398
|
+
* origin: 'current'|'other'|null, candidates: Array<string>, slug?: string, byFeature?: true}}
|
|
399
|
+
*/
|
|
400
|
+
function findNamespacedRun({ stateRoot, taskId, exec } ) {
|
|
401
|
+
const name = `${taskId}.json`;
|
|
181
402
|
const missing = { status: 'missing', file: null, root: null, origin: null, candidates: [] };
|
|
182
|
-
|
|
183
|
-
|
|
403
|
+
const inRoot = (root ) => runFilesAt(root).filter((entry) => entry.namespace !== null && path.basename(entry.file) === name);
|
|
404
|
+
const settle = (entries , root , origin ) => (
|
|
405
|
+
entries.length === 1
|
|
406
|
+
? { status: 'found', file: entries[0].file, root, origin, candidates: [], slug: entries[0].namespace }
|
|
407
|
+
: { ...missing, status: 'ambiguous', byFeature: true, candidates: entries.map((entry) => entry.file) });
|
|
408
|
+
const here = inRoot(stateRoot);
|
|
409
|
+
if (here.length > 0) return settle(here, stateRoot, 'current');
|
|
410
|
+
const checkouts = listCheckouts({ cwd: stateRoot, ...(exec ? { exec } : {}) });
|
|
411
|
+
if (!checkouts.ok || checkouts.sandbox) return missing;
|
|
412
|
+
const matches = checkouts.others.flatMap((root ) => inRoot(root).map((entry) => ({ ...entry, root })));
|
|
413
|
+
if (matches.length === 0) return missing;
|
|
414
|
+
return settle(matches, matches[0].root, 'other');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function findRunFileFor({ stateRoot, taskId, slug, exec } ) {
|
|
418
|
+
const store = RUN_STORE_REL;
|
|
419
|
+
const flat = path.join(store, `${taskId}.json`);
|
|
420
|
+
const namespaced = slug && slug !== taskId ? path.join(store, slug, `${taskId}.json`) : null;
|
|
421
|
+
const missing = { status: 'missing', file: null, root: null, origin: null, candidates: [] };
|
|
422
|
+
const here = runFilesAt(stateRoot);
|
|
423
|
+
const localNamespaced = namespaced && here.find((entry) => path.relative(stateRoot, entry.file) === namespaced);
|
|
424
|
+
const localFlat = here.find((entry) => path.relative(stateRoot, entry.file) === flat);
|
|
425
|
+
if (localNamespaced) return { status: 'found', file: localNamespaced.file, root: stateRoot, origin: 'current', candidates: [] };
|
|
426
|
+
if (localFlat && (!namespaced || flatRecordIsOurs(localFlat.file, slug, fs, true))) {
|
|
427
|
+
return { status: 'found', file: localFlat.file, root: stateRoot, origin: 'current', candidates: [] };
|
|
184
428
|
}
|
|
185
429
|
if (!slug) return missing;
|
|
186
430
|
const checkouts = listCheckouts({ cwd: stateRoot, ...(exec ? { exec } : {}) });
|
|
187
431
|
if (!checkouts.ok || checkouts.sandbox) return missing;
|
|
188
|
-
const matches
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
432
|
+
const matches = [];
|
|
433
|
+
for (const root of checkouts.others) {
|
|
434
|
+
const files = runFilesAt(root);
|
|
435
|
+
const found = [namespaced, flat].filter(Boolean).map((rel ) => files.find((entry) => path.relative(root, entry.file) === rel)).find((entry ) => {
|
|
436
|
+
if (!entry) return false;
|
|
437
|
+
// The same safe read as every other run read: a linked run or namespace folder, a FIFO or a
|
|
438
|
+
// device in another checkout is never opened, and is not a match.
|
|
439
|
+
try { return runSlugOf(JSON.parse(readRunText(fs, entry.file, runStoreDir(root)) ), root) === slug; } catch { return false; }
|
|
440
|
+
});
|
|
441
|
+
if (found) matches.push({ file: found.file, root });
|
|
442
|
+
}
|
|
197
443
|
if (matches.length === 0) return missing;
|
|
198
|
-
if (matches.length > 1) return { ...missing, status: 'ambiguous', candidates: matches.map((
|
|
199
|
-
return { status: 'found', file:
|
|
444
|
+
if (matches.length > 1) return { ...missing, status: 'ambiguous', candidates: matches.map((m) => m.file) };
|
|
445
|
+
return { status: 'found', file: matches[0].file, root: matches[0].root, origin: 'other', candidates: [] };
|
|
200
446
|
}
|
|
201
447
|
|
|
202
448
|
/** The slug a refusal's next command names: one whose feature folder exists in this checkout or in
|
|
@@ -231,7 +477,7 @@ function unusableDetail(record , now ) {
|
|
|
231
477
|
* @param {string} options.template
|
|
232
478
|
* @param {Date} [options.now]
|
|
233
479
|
* @param {Function} [options.exec]
|
|
234
|
-
* @returns {{ok: boolean, code: 'ready'|'missing'|'not-ready'|'wrong-template'|'unusable'|'ambiguous',
|
|
480
|
+
* @returns {{ok: boolean, code: 'ready'|'missing'|'not-ready'|'wrong-template'|'unusable'|'ambiguous'|'store-conflict',
|
|
235
481
|
* record: Object|null, file: string|null, origin: string|null, candidates: Array<string>,
|
|
236
482
|
* detail: string|null, nextSlug: string|null, message: string|null}}
|
|
237
483
|
*/
|
|
@@ -254,6 +500,7 @@ function checkReadiness({ stateRoot, taskId, slug = null, template, now = new Da
|
|
|
254
500
|
}),
|
|
255
501
|
});
|
|
256
502
|
if (read.status === 'ambiguous') return result('ambiguous');
|
|
503
|
+
if (read.status === 'conflict') return result('store-conflict');
|
|
257
504
|
if (read.status === 'missing') return result('missing');
|
|
258
505
|
const detail = read.status === 'unreadable' ? read.detail : unusableDetail(read.record, now);
|
|
259
506
|
if (detail) return result('unusable', detail);
|
|
@@ -284,7 +531,9 @@ function verifyReadinessCheck({ stateRoot, taskId, slug = null, now = new Date()
|
|
|
284
531
|
};
|
|
285
532
|
}
|
|
286
533
|
if (found.status === 'missing') return { applies: false, reason: 'no-run' };
|
|
287
|
-
|
|
534
|
+
// Reads only through the run store's safe reader; a run file that must not be read is refused by name.
|
|
535
|
+
const run = readRunRecord(fs, found.file, runStoreDir(found.root));
|
|
536
|
+
if (!run) return { applies: false, reason: 'no-run' };
|
|
288
537
|
if (run.state === 'COMPLETED' || run.state === 'REJECTED') return { applies: false, reason: 'run-finished' };
|
|
289
538
|
const stageNodes = (run.program || []).filter((n ) => n.type === 'stage');
|
|
290
539
|
const stage = gatedStage(stageNodes);
|
|
@@ -309,6 +558,7 @@ function verifyReadinessCheck({ stateRoot, taskId, slug = null, now = new Date()
|
|
|
309
558
|
}
|
|
310
559
|
|
|
311
560
|
module.exports = {
|
|
561
|
+
unusableDetail,
|
|
312
562
|
gatedStage,
|
|
313
563
|
heldClasses,
|
|
314
564
|
refusalText,
|
|
@@ -317,5 +567,10 @@ module.exports = {
|
|
|
317
567
|
verifyReadinessCheck,
|
|
318
568
|
featureSlugFor,
|
|
319
569
|
runSlugOf,
|
|
570
|
+
orchestrationRuns,
|
|
571
|
+
selectRunEntries,
|
|
320
572
|
findRunFile,
|
|
573
|
+
RUN_STORE_REL,
|
|
574
|
+
runStoreDir,
|
|
575
|
+
readRunRecord,
|
|
321
576
|
};
|