@khoavu882/doflow 2.0.0-beta.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -2
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +8 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +10 -2
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +2 -11
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +3 -2
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +47 -8
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1442 -363
- package/core/registry/lifecycle.json +485 -304
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +679 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +149 -34
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +18 -17
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +15 -58
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +4 -7
- package/core/shared/skills/do-plan/SKILL.md +26 -15
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +143 -66
- package/src/adapters/claude/index.js +24 -4
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/index.js +48 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +290 -44
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +322 -29
- package/src/cli/runtime-commands.js +209 -57
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +20 -3
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +20 -11
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +154 -78
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-ledger.js +34 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +2 -2
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +1 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +374 -64
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -251
- package/src/adapters/gemini/index.js +0 -429
|
@@ -13,23 +13,36 @@ const UNCHECKED = /^\s*- \[ \] /m;
|
|
|
13
13
|
const UNCHECKED_TASK = /^\s*- \[ \] (\S+)/gm;
|
|
14
14
|
const SAFE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/** The record extension a store declares in its `match` pattern: `.jsonl` for a JSON-lines store, else `.json`. */
|
|
17
|
+
function recordExtension(match ) {
|
|
18
|
+
return typeof match === 'string' && /\\\.jsonl\$$/.test(match) ? '.jsonl' : '.json';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function workRecordStores(fsImpl ) {
|
|
17
22
|
try {
|
|
18
23
|
const registry = loadStorageRegistry({ repoRoot: REPO_ROOT, fsImpl });
|
|
19
|
-
return
|
|
20
|
-
.filter((store ) => store.root === 'project-state' && store.entry === 'record' && store.rule === 'work')
|
|
21
|
-
.map((store ) => [store.id, store.path]));
|
|
24
|
+
return registry.stores.filter((store ) => store.root === 'project-state' && store.entry === 'record' && store.rule === 'work');
|
|
22
25
|
} catch {
|
|
23
|
-
return
|
|
26
|
+
return [];
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
function recordStorePaths(fsImpl ) {
|
|
31
|
+
return new Map(workRecordStores(fsImpl).map((store ) => [store.id, store.path]));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function recordStoreExtensions(fsImpl ) {
|
|
35
|
+
return new Map(workRecordStores(fsImpl).map((store ) => [store.id, recordExtension(store.match)]));
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
function createWorkCache({ projectRoot, fsImpl = fs } ) {
|
|
28
39
|
return {
|
|
29
40
|
projectRoot: path.resolve(projectRoot),
|
|
30
41
|
fsImpl,
|
|
31
42
|
recordStorePaths: recordStorePaths(fsImpl),
|
|
43
|
+
recordExtensions: recordStoreExtensions(fsImpl),
|
|
32
44
|
runNames: null,
|
|
45
|
+
runLocations: null,
|
|
33
46
|
runListError: false,
|
|
34
47
|
featureNames: null,
|
|
35
48
|
featureListError: false,
|
|
@@ -44,7 +57,7 @@ function createWorkCache({ projectRoot, fsImpl = fs }
|
|
|
44
57
|
* taken at selection. A run reopened or a plan task unchecked after selection keeps the record.
|
|
45
58
|
*/
|
|
46
59
|
function refreshedWorkCache(cache ) {
|
|
47
|
-
return { ...cache, runNames: null, runListError: false, featureNames: null, featureListError: false,
|
|
60
|
+
return { ...cache, runNames: null, runLocations: null, runListError: false, featureNames: null, featureListError: false,
|
|
48
61
|
runs: new Map(), plans: new Map() };
|
|
49
62
|
}
|
|
50
63
|
|
|
@@ -63,6 +76,16 @@ function safeSegment(value ) {
|
|
|
63
76
|
return typeof value === 'string' && SAFE_SEGMENT.test(value) && !value.includes('..');
|
|
64
77
|
}
|
|
65
78
|
|
|
79
|
+
/** Whether `name` can be a run namespace directory (a feature slug), not a `<file>.lock` directory. */
|
|
80
|
+
function isRunNamespaceDir(name ) {
|
|
81
|
+
return safeSegment(name) && !name.endsWith('.lock');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Every run id the orchestration store holds, flat or under a `<slug>/` namespace (115 C-1: a task id
|
|
86
|
+
* other than its feature's slug lives in `orchestration/<slug>/`). `cache.runLocations` maps each id
|
|
87
|
+
* to the directories that hold it ('' is the flat store), namespaces sorted, flat last.
|
|
88
|
+
*/
|
|
66
89
|
function listRunNames(cache , shouldStop ) {
|
|
67
90
|
if (shouldStop?.()) return { names: [], incomplete: true };
|
|
68
91
|
if (cache.runNames !== null) return { names: cache.runNames, incomplete: false };
|
|
@@ -71,35 +94,115 @@ function listRunNames(cache , shouldStop )
|
|
|
71
94
|
try { entries = cache.fsImpl.readdirSync(dir, { withFileTypes: true }); }
|
|
72
95
|
catch (error ) {
|
|
73
96
|
if (error?.code === 'ENOENT') entries = [];
|
|
74
|
-
else { cache.runNames = []; cache.runListError = true; return { names: cache.runNames, incomplete: false }; }
|
|
97
|
+
else { cache.runNames = []; cache.runLocations = new Map(); cache.runListError = true; return { names: cache.runNames, incomplete: false }; }
|
|
75
98
|
}
|
|
76
99
|
if (shouldStop?.()) return { names: [], incomplete: true };
|
|
77
|
-
const
|
|
100
|
+
const locations = new Map ();
|
|
101
|
+
const add = (id , where ) => {
|
|
102
|
+
if (!locations.has(id)) locations.set(id, []);
|
|
103
|
+
(locations.get(id) ).push(where);
|
|
104
|
+
};
|
|
105
|
+
const namespaces = [];
|
|
78
106
|
for (const entry of entries) {
|
|
79
107
|
if (shouldStop?.()) return { names: [], incomplete: true };
|
|
80
|
-
if (entry.name.endsWith('.json'))
|
|
108
|
+
if (entry.name.endsWith('.json')) add(entry.name.slice(0, -5), '');
|
|
109
|
+
else if (entry.isDirectory() && isRunNamespaceDir(entry.name)) namespaces.push(entry.name);
|
|
110
|
+
}
|
|
111
|
+
for (const namespace of namespaces.sort()) {
|
|
112
|
+
let inner ;
|
|
113
|
+
try { inner = cache.fsImpl.readdirSync(path.join(dir, namespace), { withFileTypes: true }); }
|
|
114
|
+
catch (error ) {
|
|
115
|
+
if (error?.code === 'ENOENT') continue;
|
|
116
|
+
cache.runNames = []; cache.runLocations = new Map(); cache.runListError = true;
|
|
117
|
+
return { names: cache.runNames, incomplete: false };
|
|
118
|
+
}
|
|
119
|
+
for (const file of inner) {
|
|
120
|
+
if (shouldStop?.()) return { names: [], incomplete: true };
|
|
121
|
+
if (file.name.endsWith('.json')) add(file.name.slice(0, -5), namespace);
|
|
122
|
+
}
|
|
81
123
|
}
|
|
82
124
|
if (shouldStop?.()) return { names: [], incomplete: true };
|
|
83
|
-
|
|
84
|
-
|
|
125
|
+
// Namespaces come first in the order listed; the flat copy (a legacy or seed source) is last.
|
|
126
|
+
for (const where of locations.values()) where.sort((x, y) => (x === '' ? 1 : y === '' ? -1 : x < y ? -1 : x > y ? 1 : 0));
|
|
127
|
+
cache.runNames = [...locations.keys()];
|
|
128
|
+
cache.runLocations = locations;
|
|
129
|
+
return { names: cache.runNames, incomplete: false };
|
|
85
130
|
}
|
|
86
131
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
const
|
|
132
|
+
function readRunAt(cache , runId , where ) {
|
|
133
|
+
// The flat copy keeps its bare id as the key: the pruner reads that snapshot back by run id.
|
|
134
|
+
const key = where ? `${where}/${runId}` : runId;
|
|
135
|
+
if (cache.runs.has(key)) return cache.runs.get(key);
|
|
136
|
+
const file = path.join(stateRoot(cache.projectRoot), 'orchestration', ...(where ? [where] : []), `${runId}.json`);
|
|
90
137
|
let result ;
|
|
91
138
|
try {
|
|
92
|
-
|
|
139
|
+
// A namespaced copy is a file base never read (N-4): a link, a FIFO or a device there reads as
|
|
140
|
+
// unreadable (the record is kept) and never blocks. The flat copy keeps its plain read (FU-d88kp0).
|
|
141
|
+
const text = where && typeof cache.fsImpl.lstatSync === 'function' && typeof cache.fsImpl.openSync === 'function'
|
|
142
|
+
? require('../task-scope.js').readRegularFileText(file, cache.fsImpl) : cache.fsImpl.readFileSync(file, 'utf8');
|
|
143
|
+
if (text === null) throw new Error('not a regular file');
|
|
144
|
+
const value = JSON.parse(text);
|
|
93
145
|
result = value && typeof value === 'object' && !Array.isArray(value) && RUN_STATES.has(value.state)
|
|
94
|
-
? { ok: true, value }
|
|
95
|
-
: { ok: false };
|
|
146
|
+
? { ok: true, value, where }
|
|
147
|
+
: { ok: false, where };
|
|
96
148
|
} catch {
|
|
97
|
-
result = { ok: false };
|
|
149
|
+
result = { ok: false, where };
|
|
98
150
|
}
|
|
99
|
-
cache.runs.set(
|
|
151
|
+
cache.runs.set(key, result);
|
|
100
152
|
return result;
|
|
101
153
|
}
|
|
102
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Whether a namespaced copy of a run keeps the record open (C-1, N-1). A namespaced copy may only
|
|
157
|
+
* keep a record: a record is judged as 135ce230 judged it, by the flat run copy alone, and a
|
|
158
|
+
* namespaced copy never makes a record finished. The copies a record owns are its own namespace's
|
|
159
|
+
* (a namespaced record) or the namespace named by its own `featureSlug`/`slug` (a flat record); it
|
|
160
|
+
* never owns another feature's copy. The run is chosen as a flat run is: the exact id, the record's
|
|
161
|
+
* namespace, then the longest delimited prefix. An owned copy that is RUNNING or AWAITING_GATE
|
|
162
|
+
* holds the record open and one that cannot be read keeps it undecidable. A flat record that names
|
|
163
|
+
* no namespace of its own and could be claimed by two or more namespaced copies is undecidable (fail
|
|
164
|
+
* closed), not guessed.
|
|
165
|
+
*/
|
|
166
|
+
function namespacedHold(cache , taskId , namespace , recordValue , shouldStop ) {
|
|
167
|
+
const recordNs = safeSegment(namespace) ? namespace : null;
|
|
168
|
+
const named = recordNs ? null : (stringField(recordValue, 'featureSlug') || stringField(recordValue, 'slug'));
|
|
169
|
+
const own = recordNs || (named !== null && isRunNamespaceDir(named) ? named : null);
|
|
170
|
+
const locations = cache.runLocations || new Map();
|
|
171
|
+
const choose = (accept ) => {
|
|
172
|
+
let exact = false, byNamespace = false, prefix = null;
|
|
173
|
+
for (const [candidate, held] of locations) {
|
|
174
|
+
if (shouldStop?.()) return undefined;
|
|
175
|
+
if (!held.some((w ) => w !== '' && accept(w))) continue;
|
|
176
|
+
if (candidate === taskId) exact = true;
|
|
177
|
+
else if (recordNs && candidate === recordNs) byNamespace = true;
|
|
178
|
+
else if ((taskId.startsWith(`${candidate}-`) || taskId.startsWith(`${candidate}.`))
|
|
179
|
+
&& (!prefix || candidate.length > prefix.length || (candidate.length === prefix.length && candidate < prefix))) prefix = candidate;
|
|
180
|
+
}
|
|
181
|
+
return exact ? taskId : byNamespace ? recordNs : prefix;
|
|
182
|
+
};
|
|
183
|
+
if (own) {
|
|
184
|
+
const id = choose((w ) => w === own);
|
|
185
|
+
if (id === undefined || shouldStop?.()) return { incomplete: true };
|
|
186
|
+
if (id) {
|
|
187
|
+
const read = readRunAt(cache, id, own);
|
|
188
|
+
if (shouldStop?.()) return { incomplete: true };
|
|
189
|
+
if (!read.ok) return { unreadable: true, id };
|
|
190
|
+
if (RUN_OPEN_STATES.includes(read.value.state)) return { open: true, id };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!recordNs && !own) {
|
|
194
|
+
const id = choose(() => true);
|
|
195
|
+
if (id === undefined || shouldStop?.()) return { incomplete: true };
|
|
196
|
+
if (id && (locations.get(id) || []).filter((w ) => w !== '').length > 1) return { unreadable: true, id };
|
|
197
|
+
}
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The flat run copy `runId`, the only copy a record is judged by (135ce230). */
|
|
202
|
+
function readRun(cache , runId ) {
|
|
203
|
+
return readRunAt(cache, runId, '');
|
|
204
|
+
}
|
|
205
|
+
|
|
103
206
|
function resolveRun(cache , taskId , namespace , shouldStop ) {
|
|
104
207
|
const listed = listRunNames(cache, shouldStop);
|
|
105
208
|
if (listed.incomplete) return { id: null, unreadable: false, incomplete: true };
|
|
@@ -107,6 +210,7 @@ function resolveRun(cache , taskId , namespace , shouldStop
|
|
|
107
210
|
let exact = false, namespaced = false, prefix = null;
|
|
108
211
|
for (const candidate of listed.names) {
|
|
109
212
|
if (shouldStop?.()) return { id: null, unreadable: false, incomplete: true };
|
|
213
|
+
if (!(cache.runLocations.get(candidate) || []).includes('')) continue;
|
|
110
214
|
if (candidate === taskId) exact = true;
|
|
111
215
|
else if (safeSegment(namespace) && candidate === namespace) namespaced = true;
|
|
112
216
|
else if ((taskId.startsWith(`${candidate}-`) || taskId.startsWith(`${candidate}.`))
|
|
@@ -120,16 +224,26 @@ function resolveRun(cache , taskId , namespace , shouldStop
|
|
|
120
224
|
return read.ok ? { id, value: read.value, unreadable: false } : { id, value: null, unreadable: true };
|
|
121
225
|
}
|
|
122
226
|
|
|
123
|
-
function
|
|
227
|
+
function parseJsonLines(text ) {
|
|
228
|
+
const lines = text.split('\n').filter((line ) => line.trim() !== '');
|
|
229
|
+
if (lines.length === 0) throw new Error('empty JSON-lines record');
|
|
230
|
+
const values = lines.map((line ) => JSON.parse(line));
|
|
231
|
+
return values[values.length - 1];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function readRecord({ projectRoot, storePath, recordId, namespace, mtimeMs, fsImpl, extension = '.json' } ) {
|
|
124
235
|
const storeSegments = typeof storePath === 'string' && storePath !== '' ? storePath.split('/') : [];
|
|
125
236
|
if (typeof storePath !== 'string' || storeSegments.some((segment ) => !safeSegment(segment))
|
|
126
237
|
|| !safeSegment(recordId)
|
|
127
238
|
|| (namespace !== null && namespace !== undefined && !safeSegment(namespace))) {
|
|
128
239
|
return { ok: false };
|
|
129
240
|
}
|
|
130
|
-
const file = path.join(stateRoot(projectRoot), ...storeSegments, ...(namespace ? [namespace] : []), `${recordId}
|
|
241
|
+
const file = path.join(stateRoot(projectRoot), ...storeSegments, ...(namespace ? [namespace] : []), `${recordId}${extension}`);
|
|
131
242
|
try {
|
|
132
|
-
const
|
|
243
|
+
const text = fsImpl.readFileSync(file, 'utf8');
|
|
244
|
+
// A `.jsonl` record is one JSON value per line, appended over time: every line must parse (fail
|
|
245
|
+
// closed otherwise) and the last one is the record's current value.
|
|
246
|
+
const value = extension === '.jsonl' ? parseJsonLines(text) : JSON.parse(text);
|
|
133
247
|
const mtime = mtimeMs === undefined ? fsImpl.statSync(file).mtimeMs : mtimeMs;
|
|
134
248
|
if (!Number.isFinite(mtime)) return { ok: false };
|
|
135
249
|
return { ok: true, value, mtimeMs: mtime };
|
|
@@ -226,6 +340,33 @@ function planWithUncheckedTask(cache , taskId , shouldStop
|
|
|
226
340
|
return { found: false, unreadable: false };
|
|
227
341
|
}
|
|
228
342
|
|
|
343
|
+
/** IC-028: why a task-attempts ledger is open work, or null when it holds neither an active or
|
|
344
|
+
* uncleared cancelled attempt nor a run whose control is other than `running`. A cancel a resume has
|
|
345
|
+
* cleared (the same `clearedCancels` the resume report uses) is settled work, not open work. */
|
|
346
|
+
function openLedgerReason(ledger ) {
|
|
347
|
+
const tasks = ledger && typeof ledger.tasks === 'object' && ledger.tasks ? Object.entries (ledger.tasks) : [];
|
|
348
|
+
let cleared = null;
|
|
349
|
+
for (const [, entry] of tasks) {
|
|
350
|
+
const attempts = entry && Array.isArray(entry.attempts) ? entry.attempts : [];
|
|
351
|
+
for (const a of attempts) {
|
|
352
|
+
if (!a) continue;
|
|
353
|
+
if (a.state === 'active') return 'attempt-open';
|
|
354
|
+
if (a.state !== 'cancelled') continue;
|
|
355
|
+
// Loaded only when a cancelled attempt exists: task-run is a heavy module for a storage pass.
|
|
356
|
+
// Paired by the attempt object, not by (task, token): a malformed ledger can repeat or omit
|
|
357
|
+
// tokens, and an uncleared cancel must never be taken for a cleared one.
|
|
358
|
+
if (cleared === null) cleared = new Set(require('../task-run.js').clearedCancels(ledger).map((c ) => c.attempt));
|
|
359
|
+
if (!cleared.has(a)) return 'attempt-open';
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const runs = ledger && typeof ledger.runs === 'object' && ledger.runs ? Object.values (ledger.runs) : [];
|
|
363
|
+
if (runs.some((run ) => !run || run.control !== 'running')) return 'run-control-open';
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** IC-037: the dispatch run states that settle a dispatch store; any other run state is open. */
|
|
368
|
+
const DISPATCH_RUN_SETTLED = Object.freeze(['completed', 'cancelled']);
|
|
369
|
+
|
|
229
370
|
function answer(state , reason , run , feature , since = null) {
|
|
230
371
|
return { state, since, reason, run, feature };
|
|
231
372
|
}
|
|
@@ -234,6 +375,47 @@ function incompleteAnswer(run = null, feature = null) {
|
|
|
234
375
|
return { ...answer('undecidable', 'pass-budget-exhausted', run, feature), incomplete: true };
|
|
235
376
|
}
|
|
236
377
|
|
|
378
|
+
/**
|
|
379
|
+
* A loop run record (`roadmap/runs/<runId>.json`) is open while its `closedAt` is null (the loop
|
|
380
|
+
* resumes any such run), while it is the newest record in `runs/`, while a merge intent in
|
|
381
|
+
* `roadmap/intents/` names its `runId`, or while `roadmap/lease.json` names it. Any other record is
|
|
382
|
+
* finished as of its mtime. An unreadable source is never called finished.
|
|
383
|
+
*/
|
|
384
|
+
function roadmapRunStatus(project , fileSystem , runId , mtimeMs ) {
|
|
385
|
+
if (!safeSegment(runId)) return answer('undecidable', 'record-unreadable', null, null);
|
|
386
|
+
const dir = path.join(stateRoot(project), 'roadmap');
|
|
387
|
+
const unreadable = () => answer('undecidable', 'record-unreadable', runId, null);
|
|
388
|
+
let record ;
|
|
389
|
+
try { record = JSON.parse(fileSystem.readFileSync(path.join(dir, 'runs', `${runId}.json`), 'utf8')); }
|
|
390
|
+
catch { return unreadable(); }
|
|
391
|
+
if (!record || typeof record !== 'object' || !Object.hasOwn(record, 'closedAt')) return unreadable();
|
|
392
|
+
if (record.closedAt === null) return answer('open', 'roadmap-run-open', runId, null);
|
|
393
|
+
const list = (folder ) => {
|
|
394
|
+
try { return fileSystem.readdirSync(folder); } catch (error ) { return error.code === 'ENOENT' ? [] : null; }
|
|
395
|
+
};
|
|
396
|
+
const runs = list(path.join(dir, 'runs'));
|
|
397
|
+
if (runs === null) return unreadable();
|
|
398
|
+
let newest = null;
|
|
399
|
+
for (const name of runs) {
|
|
400
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/.test(name)) continue;
|
|
401
|
+
let stat ;
|
|
402
|
+
try { stat = fileSystem.statSync(path.join(dir, 'runs', name)); } catch { return unreadable(); }
|
|
403
|
+
if (!newest || stat.mtimeMs > newest.mtimeMs || (stat.mtimeMs === newest.mtimeMs && name > newest.name)) newest = { name, mtimeMs: stat.mtimeMs };
|
|
404
|
+
}
|
|
405
|
+
const open = answer('open', 'roadmap-run-open', runId, null);
|
|
406
|
+
if (newest?.name === `${runId}.json`) return open;
|
|
407
|
+
const intents = list(path.join(dir, 'intents'));
|
|
408
|
+
if (intents === null) return unreadable();
|
|
409
|
+
for (const name of intents) {
|
|
410
|
+
if (!name.endsWith('.json')) continue;
|
|
411
|
+
try { if (JSON.parse(fileSystem.readFileSync(path.join(dir, 'intents', name), 'utf8'))?.runId === runId) return open; }
|
|
412
|
+
catch { return unreadable(); }
|
|
413
|
+
}
|
|
414
|
+
try { if (JSON.parse(fileSystem.readFileSync(path.join(dir, 'lease.json'), 'utf8'))?.runId === runId) return open; }
|
|
415
|
+
catch (error ) { if (error.code !== 'ENOENT') return unreadable(); }
|
|
416
|
+
return answer('finished', 'roadmap-run-finished', runId, null, Number.isFinite(mtimeMs) ? mtimeMs : null);
|
|
417
|
+
}
|
|
418
|
+
|
|
237
419
|
/**
|
|
238
420
|
* The one definition of whether a per-task record is still open work (IC-004).
|
|
239
421
|
* Reads fail closed: an unreadable run, record, or relevant plan is never called finished. An
|
|
@@ -243,11 +425,23 @@ function incompleteAnswer(run = null, feature = null) {
|
|
|
243
425
|
function workStatus({
|
|
244
426
|
projectRoot, store, taskId, namespace = null, mtimeMs, cache, fsImpl, shouldStop,
|
|
245
427
|
} ) {
|
|
428
|
+
const recordId = taskId;
|
|
429
|
+
const workId = typeof taskId === 'string' ? taskId.replace(/_claims$/, '') : taskId;
|
|
430
|
+
|
|
431
|
+
// Roadmap archive location is authoritative: open intents never age out, while archived
|
|
432
|
+
// intents use their own mtime and do not depend on the feature plan remaining on disk.
|
|
433
|
+
if (store === 'roadmap' && namespace === 'intents')
|
|
434
|
+
return answer('open', 'roadmap-intent-open', workId, null);
|
|
435
|
+
if (store === 'roadmap' && namespace === 'done')
|
|
436
|
+
return answer('finished', 'roadmap-intent-done', workId, null, Number.isFinite(mtimeMs) ? mtimeMs : null);
|
|
437
|
+
// The tripwire's remembered event ids are tiny and bounded by those ids: kept.
|
|
438
|
+
if (store === 'roadmap' && namespace === 'tripwire')
|
|
439
|
+
return answer('open', 'roadmap-tripwire-kept', workId, null);
|
|
440
|
+
|
|
246
441
|
const project = path.resolve(projectRoot);
|
|
247
442
|
const fileSystem = fsImpl || cache?.fsImpl || fs;
|
|
443
|
+
if (store === 'roadmap' && namespace === 'runs') return roadmapRunStatus(project, fileSystem, workId, mtimeMs);
|
|
248
444
|
const pass = cacheFor({ projectRoot: project, fsImpl: fileSystem, cache });
|
|
249
|
-
const recordId = taskId;
|
|
250
|
-
const workId = typeof taskId === 'string' ? taskId.replace(/_claims$/, '') : taskId;
|
|
251
445
|
|
|
252
446
|
if (shouldStop?.()) return incompleteAnswer();
|
|
253
447
|
if (!safeSegment(workId)) return answer('undecidable', 'record-unreadable', null, null);
|
|
@@ -274,7 +468,8 @@ function workStatus({
|
|
|
274
468
|
record = { ok: false };
|
|
275
469
|
}
|
|
276
470
|
} else {
|
|
277
|
-
record = readRecord({ projectRoot: project, storePath, recordId, namespace, mtimeMs, fsImpl: fileSystem
|
|
471
|
+
record = readRecord({ projectRoot: project, storePath, recordId, namespace, mtimeMs, fsImpl: fileSystem,
|
|
472
|
+
extension: pass.recordExtensions?.get(store) || '.json' });
|
|
278
473
|
}
|
|
279
474
|
if (shouldStop?.()) return incompleteAnswer(resolvedRun.id);
|
|
280
475
|
if (!record.ok) return answer('undecidable', 'record-unreadable', resolvedRun.id, null);
|
|
@@ -284,6 +479,30 @@ function workStatus({
|
|
|
284
479
|
return answer('open', 'run-open', resolvedRun.id, null);
|
|
285
480
|
}
|
|
286
481
|
|
|
482
|
+
// N-1: a namespaced copy of a run may keep the record open or undecidable, never finished.
|
|
483
|
+
const hold = namespacedHold(pass, workId, namespace, record.value, shouldStop);
|
|
484
|
+
if (hold.incomplete || shouldStop?.()) return incompleteAnswer(resolvedRun.id);
|
|
485
|
+
if (hold.open) return answer('open', 'run-open', hold.id, null);
|
|
486
|
+
if (hold.unreadable) return answer('undecidable', 'run-unreadable', hold.id, null);
|
|
487
|
+
|
|
488
|
+
// IC-028: a task-attempts ledger stays open while any attempt is active or cancelled and not cleared
|
|
489
|
+
// by a resume, or any run is not `running`, so a paused or cancelled run whose boxes are all ticked
|
|
490
|
+
// is never pruned.
|
|
491
|
+
if (store === 'task-attempts') {
|
|
492
|
+
const ledgerOpen = openLedgerReason(record.value);
|
|
493
|
+
if (ledgerOpen) return answer('open', ledgerOpen, resolvedRun.id, null);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// IC-037 (mirrors IC-028): a dispatch store stays open while its run is not `completed` or
|
|
497
|
+
// `cancelled`, so a paused, blocked or resume-required run on a fully ticked plan keeps the
|
|
498
|
+
// record that names its kept worktrees.
|
|
499
|
+
if (store === 'dispatch') {
|
|
500
|
+
const dispatchRun = record.value && typeof record.value === 'object' ? record.value.run : null;
|
|
501
|
+
if (dispatchRun !== null && dispatchRun !== undefined && !DISPATCH_RUN_SETTLED.includes(dispatchRun?.state)) {
|
|
502
|
+
return answer('open', 'dispatch-run-open', resolvedRun.id, null);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
287
506
|
// W5: feature metadata is ordered; folder-name inference is the final fallback.
|
|
288
507
|
let feature = stringField(run, 'featureSlug')
|
|
289
508
|
|| stringField(record.value, 'featureSlug')
|
|
@@ -323,4 +542,4 @@ function workStatus({
|
|
|
323
542
|
return answer('finished', run ? 'run-finished' : 'no-run', resolvedRun.id, feature, since);
|
|
324
543
|
}
|
|
325
544
|
|
|
326
|
-
module.exports = { workStatus, createWorkCache, refreshedWorkCache, RUN_OPEN_STATES };
|
|
545
|
+
module.exports = { workStatus, recordExtension, createWorkCache, refreshedWorkCache, RUN_OPEN_STATES };
|