@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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { acquireLock } = require('./task-state.js');
|
|
9
|
+
const { loadStorageRegistry } = require('./storage/registry.js');
|
|
10
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
11
|
+
|
|
12
|
+
// Where a task's records live (115 IC-004) and how records a linked worktree wrote before that rule
|
|
13
|
+
// reach the main checkout (IC-006): one locked sweep per redirected call.
|
|
14
|
+
//
|
|
15
|
+
// A linked, non-sandbox worktree reads and writes every per-task record under the main checkout's
|
|
16
|
+
// `.doflow/state`, whatever folders the worktree holds: a conditional rule flipped mid-run when
|
|
17
|
+
// do-brainstorm created the feature folder in the worktree and split one task's records across two
|
|
18
|
+
// stores. A DoFlow sandbox (`.doflow-worktree-base`) keeps its own store, and `--global` is $HOME.
|
|
19
|
+
|
|
20
|
+
/** This module's path as `core/registry/storage.json` names it in a store's `writers`. */
|
|
21
|
+
const ADOPTER = 'src/runtime/state-root.js';
|
|
22
|
+
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* IC-004: the root a task-store verb or `orchestrate` reads and writes its records in.
|
|
26
|
+
* @param {Object} o parsed CLI options (`global`, `positional`)
|
|
27
|
+
* @param {Object|null} checkouts a `listCheckouts` result, or null when none was listed
|
|
28
|
+
* @returns {{root: string, redirected: boolean}}
|
|
29
|
+
*/
|
|
30
|
+
function taskStateRoot(o , checkouts ) {
|
|
31
|
+
if (!o.global && checkouts?.ok && checkouts.isLinked && !checkouts.sandbox && checkouts.main) {
|
|
32
|
+
return { root: checkouts.main, redirected: true };
|
|
33
|
+
}
|
|
34
|
+
return { root: o.global ? os.homedir() : path.resolve(o.positional?.[0] || '.'), redirected: false };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The project-state stores a worktree's records are adopted from: the ones that declare this module
|
|
39
|
+
* among their `writers` in `core/registry/storage.json`, so the list is the registry's, not a copy.
|
|
40
|
+
* @param {Object} [registry] a loaded storage registry (default: the shipped one)
|
|
41
|
+
* @returns {Array<{id: string, path: string, match: RegExp}>}
|
|
42
|
+
*/
|
|
43
|
+
function adoptedStores(registry = loadStorageRegistry({ repoRoot: REPO_ROOT })) {
|
|
44
|
+
return registry.stores
|
|
45
|
+
.filter((store ) => store.root === 'project-state' && Array.isArray(store.writers) && store.writers.includes(ADOPTER))
|
|
46
|
+
.map((store ) => ({ id: store.id, path: store.path, match: new RegExp(store.match) }));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Whether `name` is a namespace directory of a store (a feature slug), not a `<file>.lock` directory. */
|
|
50
|
+
function isNamespaceDir(name ) {
|
|
51
|
+
return SAFE_ID.test(name) && !name.includes('..') && !name.endsWith('.lock');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The record files of one store directory of the worktree, by the store's exact `match`: flat files
|
|
55
|
+
* and the files of each `<slug>/` directory, as paths relative to the store. */
|
|
56
|
+
function storeFiles(dir , match , fsImpl ) {
|
|
57
|
+
const out = [];
|
|
58
|
+
let entries ;
|
|
59
|
+
try { entries = fsImpl.readdirSync(dir, { withFileTypes: true }); } catch { return out; }
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
// A link is listed as it stands, file or directory, so the sweep refuses it instead of skipping it.
|
|
62
|
+
if (entry.isSymbolicLink() && (match.test(entry.name) || isNamespaceDir(entry.name))) out.push(entry.name);
|
|
63
|
+
else if (entry.isFile() && match.test(entry.name)) out.push(entry.name);
|
|
64
|
+
else if (entry.isDirectory() && isNamespaceDir(entry.name)) {
|
|
65
|
+
let inner ;
|
|
66
|
+
try { inner = fsImpl.readdirSync(path.join(dir, entry.name), { withFileTypes: true }); } catch { continue; }
|
|
67
|
+
for (const file of inner) if ((file.isFile() || file.isSymbolicLink()) && match.test(file.name)) out.push(path.join(entry.name, file.name));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out.sort();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The first link among `root`'s own path components down to `file` (the root itself is the
|
|
74
|
+
* checkout and is not owned here), or null. A component that does not exist yet is not a link. */
|
|
75
|
+
function linkOnTheWay(root , file , fsImpl ) {
|
|
76
|
+
let at = root;
|
|
77
|
+
for (const part of path.relative(root, file).split(path.sep)) {
|
|
78
|
+
at = path.join(at, part);
|
|
79
|
+
let stat ;
|
|
80
|
+
try { stat = fsImpl.lstatSync(at); } catch { return null; }
|
|
81
|
+
if (stat.isSymbolicLink()) return at;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The task id a flat record file names: `<taskId>.json` or the claims file `<taskId>_claims.json`. */
|
|
87
|
+
function taskIdOf(name ) {
|
|
88
|
+
const base = name.slice(0, -'.json'.length);
|
|
89
|
+
return base.endsWith('_claims') ? base.slice(0, -'_claims'.length) : base;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The feature a flat record names (`featureSlug` on a run, `slug` on a readiness record), or null. */
|
|
93
|
+
function ownSlugOf(bytes ) {
|
|
94
|
+
try {
|
|
95
|
+
const data = JSON.parse(bytes.toString('utf8'));
|
|
96
|
+
const named = [data?.featureSlug, data?.slug].find((v) => typeof v === 'string' && SAFE_ID.test(v) && !v.includes('..'));
|
|
97
|
+
return named || null;
|
|
98
|
+
} catch { return null; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Writes `bytes` to `to` through a temporary file beside it and a rename, so a reader never sees a
|
|
102
|
+
* partial record. The one write path for a record copied between stores or checkouts. */
|
|
103
|
+
function placeRecord(to , bytes , fsImpl = fs) {
|
|
104
|
+
const tmp = `${to}.${process.pid}.tmp`;
|
|
105
|
+
fsImpl.writeFileSync(tmp, bytes);
|
|
106
|
+
fsImpl.renameSync(tmp, to);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function realOrNull(p , fsImpl ) {
|
|
110
|
+
try { return fsImpl.realpathSync(p); } catch { return null; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* IC-006 (115 C-3, D-1, D-4): moves every record a linked worktree holds in its own `.doflow/state`
|
|
115
|
+
* to the main checkout's, before the verb reads any of them. One sweep per redirected call, over
|
|
116
|
+
* every adopted store, whatever task the call names.
|
|
117
|
+
*
|
|
118
|
+
* Nothing is swept when the two state directories are one directory (a worktree whose
|
|
119
|
+
* `.doflow/state` links to the main checkout's). Per file: a link on either side's own path, or a
|
|
120
|
+
* source and target that are one file (`dev:ino`), refuses that file; otherwise the target's lock and
|
|
121
|
+
* then the source's are taken, the source is re-read under them (gone means another sweep moved it),
|
|
122
|
+
* and it goes to the same relative path, except that a flat record of a task id other than its
|
|
123
|
+
* feature's slug goes under `<store>/<slug>/` (the slug it names, else the call's `recordSlug`, so it
|
|
124
|
+
* lands where every reader looks). Equal bytes at the target delete the source; no target means a
|
|
125
|
+
* copy to a temporary file, a rename into place, a re-read compared with the source, and only then
|
|
126
|
+
* the source's deletion; other bytes leave both and are reported. Files already moved stay moved.
|
|
127
|
+
* @param {Object} options
|
|
128
|
+
* @param {string} options.mainRoot the main checkout
|
|
129
|
+
* @param {string} options.worktreeRoot the linked worktree the verb runs in
|
|
130
|
+
* @param {string|null|(() => string|null)} [options.recordSlug] the call's feature slug, asked for only
|
|
131
|
+
* when a flat record names no feature of its own
|
|
132
|
+
* @param {Object} [options.registry] storage registry (default: the shipped one)
|
|
133
|
+
* @param {Object} [options.fsImpl]
|
|
134
|
+
* @returns {{ok: boolean, adopted: string[], removed: string[], error?: 'store-conflict',
|
|
135
|
+
* conflicts?: Array<{worktree: string, main: string, reason: 'differs'|'link'|'same-file'}>}}
|
|
136
|
+
*/
|
|
137
|
+
function sweepWorktreeRecords({ mainRoot, worktreeRoot, recordSlug = null, registry, fsImpl = fs } ) {
|
|
138
|
+
const done = { ok: true, adopted: [] , removed: [] };
|
|
139
|
+
const conflicts = [];
|
|
140
|
+
const fromState = path.join(worktreeRoot, '.doflow', 'state');
|
|
141
|
+
const toState = path.join(mainRoot, '.doflow', 'state');
|
|
142
|
+
const fromReal = realOrNull(fromState, fsImpl);
|
|
143
|
+
if (fromReal === null || fromReal === realOrNull(toState, fsImpl)) return done;
|
|
144
|
+
let callSlug ;
|
|
145
|
+
const slugOfCall = () => {
|
|
146
|
+
if (callSlug === undefined) {
|
|
147
|
+
let value = null;
|
|
148
|
+
try { value = typeof recordSlug === 'function' ? recordSlug() : recordSlug; } catch { /* no feature */ }
|
|
149
|
+
callSlug = typeof value === 'string' && SAFE_ID.test(value) && !value.includes('..') ? value : null;
|
|
150
|
+
}
|
|
151
|
+
return callSlug;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
for (const store of adoptedStores(registry)) {
|
|
155
|
+
const fromDir = path.join(fromState, store.path);
|
|
156
|
+
let dirStat ;
|
|
157
|
+
try { dirStat = fsImpl.lstatSync(fromDir); } catch { continue; }
|
|
158
|
+
if (dirStat.isSymbolicLink()) {
|
|
159
|
+
conflicts.push({ worktree: fromDir, main: path.join(toState, store.path), reason: 'link' });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (!dirStat.isDirectory()) continue;
|
|
163
|
+
for (const rel of storeFiles(fromDir, store.match, fsImpl)) {
|
|
164
|
+
const from = path.join(fromDir, rel);
|
|
165
|
+
if (linkOnTheWay(worktreeRoot, from, fsImpl)) {
|
|
166
|
+
conflicts.push({ worktree: from, main: path.join(toState, store.path, rel), reason: 'link' });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
let toRel = rel;
|
|
170
|
+
if (!rel.includes(path.sep)) {
|
|
171
|
+
let bytes = null;
|
|
172
|
+
try { bytes = fsImpl.readFileSync(from); } catch { continue; }
|
|
173
|
+
const slug = ownSlugOf(bytes ) || slugOfCall();
|
|
174
|
+
if (slug && slug !== taskIdOf(rel)) toRel = path.join(slug, rel);
|
|
175
|
+
}
|
|
176
|
+
const to = path.join(toState, store.path, toRel);
|
|
177
|
+
if (linkOnTheWay(mainRoot, to, fsImpl)) { conflicts.push({ worktree: from, main: to, reason: 'link' }); continue; }
|
|
178
|
+
try {
|
|
179
|
+
const a = fsImpl.lstatSync(from);
|
|
180
|
+
const b = fsImpl.lstatSync(to);
|
|
181
|
+
if (a.dev === b.dev && a.ino === b.ino) { conflicts.push({ worktree: from, main: to, reason: 'same-file' }); continue; }
|
|
182
|
+
} catch { /* one side is absent: not one file */ }
|
|
183
|
+
|
|
184
|
+
fsImpl.mkdirSync(path.dirname(to), { recursive: true });
|
|
185
|
+
const releaseTarget = acquireLock(fsImpl, to);
|
|
186
|
+
try {
|
|
187
|
+
const releaseSource = acquireLock(fsImpl, from);
|
|
188
|
+
try {
|
|
189
|
+
let bytes ;
|
|
190
|
+
try { bytes = fsImpl.readFileSync(from); } catch (error ) {
|
|
191
|
+
if (error.code === 'ENOENT') continue; // another sweep moved it
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
let there = null;
|
|
195
|
+
try { there = fsImpl.readFileSync(to); } catch (error ) { if (error.code !== 'ENOENT') throw error; }
|
|
196
|
+
if (there !== null) {
|
|
197
|
+
if (!bytes.equals(there)) { conflicts.push({ worktree: from, main: to, reason: 'differs' }); continue; }
|
|
198
|
+
fsImpl.unlinkSync(from);
|
|
199
|
+
done.removed.push(from);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
placeRecord(to, bytes, fsImpl);
|
|
203
|
+
if (!bytes.equals(fsImpl.readFileSync(to))) { conflicts.push({ worktree: from, main: to, reason: 'differs' }); continue; }
|
|
204
|
+
fsImpl.unlinkSync(from);
|
|
205
|
+
done.adopted.push(to);
|
|
206
|
+
} finally {
|
|
207
|
+
releaseSource();
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
releaseTarget();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return conflicts.length ? { ...done, ok: false, error: 'store-conflict', conflicts } : done;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { taskStateRoot, sweepWorktreeRecords, placeRecord, adoptedStores, ADOPTER };
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const fs = require('node:fs');
|
|
7
7
|
const { loadStorageRegistry } = require('./registry.js');
|
|
8
|
-
const { resolveRoots,
|
|
8
|
+
const { resolveRoots, createPruneSettingsResolver } = require('./roots.js');
|
|
9
9
|
const { runPass } = require('./engine.js');
|
|
10
10
|
const { upkeepAfterVerb } = require('./upkeep.js');
|
|
11
11
|
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
@@ -29,10 +29,15 @@ function handleStorageCommand({ action = 'report', json = false, global = false,
|
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
const roots = resolveRoots({ registry, projectRoot: global ? null : path.resolve(projectRoot), global, env });
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
32
|
+
let settingsView = null;
|
|
33
|
+
const resolvePruneSetting = createPruneSettingsResolver({ roots, fsImpl, onView: (view ) => { settingsView = view; } });
|
|
34
|
+
const projectSetting = resolvePruneSetting('project-state');
|
|
35
|
+
const userSetting = resolvePruneSetting('user-state');
|
|
36
|
+
let deprecationNotes = [];
|
|
37
|
+
try {
|
|
38
|
+
if (settingsView) deprecationNotes = settingsView.notes.filter((note ) => note.code === 'deprecated' && note.key === 'storage.prune');
|
|
39
|
+
} catch { /* a settings fault does not change storage's fail-closed report */ }
|
|
40
|
+
const setting = Object.fromEntries(Object.keys(roots).map(rootId => [rootId, resolvePruneSetting(rootId)]));
|
|
36
41
|
const mode = action === 'prune' ? 'prune' : 'report';
|
|
37
42
|
const result = runPass({ registry, roots, mode, setting, now: Date.now(), env, fsImpl });
|
|
38
43
|
const removed = result.stores.reduce((sum , row ) => sum + row.removed, 0);
|
|
@@ -42,6 +47,7 @@ function handleStorageCommand({ action = 'report', json = false, global = false,
|
|
|
42
47
|
// IC-013: an explicitly set DOFLOW_RETENTION_HOURS still removes settled lifecycle events.
|
|
43
48
|
console.log(removed === 0 ? `storage: pruning is off (${decision}); nothing removed`
|
|
44
49
|
: `storage: pruning is off (${decision}); DOFLOW_RETENTION_HOURS still applied to lifecycle events: removed ${removed} ${entries}`);
|
|
50
|
+
for (const note of deprecationNotes) console.log(`note: ${note.text}`);
|
|
45
51
|
return;
|
|
46
52
|
}
|
|
47
53
|
if (json) {
|
|
@@ -53,6 +59,7 @@ function handleStorageCommand({ action = 'report', json = false, global = false,
|
|
|
53
59
|
return;
|
|
54
60
|
}
|
|
55
61
|
console.log(`Settings: project ${projectSetting.setting}${projectSetting.decidedBy ? ` (${projectSetting.decidedBy})` : ' (default)'}; user ${userSetting.setting}${userSetting.decidedBy ? ` (${userSetting.decidedBy})` : ' (default)'}`);
|
|
62
|
+
for (const note of deprecationNotes) console.log(`note: ${note.text}`);
|
|
56
63
|
for (const row of result.stores) {
|
|
57
64
|
const names = row.selected.slice(0, 20).map((entry ) => entry.name);
|
|
58
65
|
const more = row.selected.length > 20 ? `\n ... and ${row.selected.length - 20} more` : '';
|
|
@@ -7,7 +7,7 @@ const path = require('node:path');
|
|
|
7
7
|
const { randomBytes } = require('node:crypto');
|
|
8
8
|
const { formatBound } = require('./registry.js');
|
|
9
9
|
const { resolveStorePath } = require('./roots.js');
|
|
10
|
-
const { selectCandidates, recordStillCandidate, protectedLogs, removableRules, ruleOrder, projectRoot } = require('./rules.js');
|
|
10
|
+
const { selectCandidates, recordStillCandidate, protectedLogs, unfinishedRuns, removableRules, ruleOrder, projectRoot } = require('./rules.js');
|
|
11
11
|
const { retainLocked } = require('../lifecycle/store-upkeep.js');
|
|
12
12
|
const { parseWindow, selectExpired } = require('../lifecycle/retention.js');
|
|
13
13
|
const { readEvents } = require('../lifecycle/event-store.js');
|
|
@@ -192,7 +192,7 @@ function runPass({registry,roots,stores,mode,setting={},now=Date.now(),budgetMs,
|
|
|
192
192
|
row.selected=selection.files.map((name )=>({name,reason:'settled',bytes:stat(fsImpl,path.join(location,name))?.size || 0}));
|
|
193
193
|
} else {
|
|
194
194
|
let output;
|
|
195
|
-
try { output=retainLocked(project,{fsImpl,now:new Date(now),window,maxFiles:Math.max(0,cap-removals),shouldStop}); }
|
|
195
|
+
try { output=retainLocked(project,{fsImpl,now:new Date(now),window,maxFiles:Math.max(0,cap-removals),shouldStop,staleTmpMs:registry.settings.staleTmpHours*3600000}); }
|
|
196
196
|
catch(error ) { row.failed.push({name:store.id,code:error.code || 'EIO'}); continue; }
|
|
197
197
|
row.selected=output.selected.map(({name,bytes} )=>({name,reason:'settled',bytes}));
|
|
198
198
|
row.removed=output.removed; removals+=row.removed;
|
|
@@ -222,6 +222,13 @@ function runPass({registry,roots,stores,mode,setting={},now=Date.now(),budgetMs,
|
|
|
222
222
|
row.protected=candidates.filter((c )=>protectedSet.has(c.name)).length;
|
|
223
223
|
candidates=candidates.filter((c )=>!protectedSet.has(c.name));
|
|
224
224
|
}
|
|
225
|
+
if (store.protect==='unfinished-run' && candidates.length) {
|
|
226
|
+
// Fails closed: a file whose run cannot be shown terminal is kept, never reported as a failure.
|
|
227
|
+
const kept=unfinishedRuns({roots,fsImpl,names:candidates.map((c )=>c.name),shouldStop});
|
|
228
|
+
if (kept.incomplete) { result.partial=true; break; }
|
|
229
|
+
row.protected=candidates.filter((c )=>kept.names.has(c.name)).length;
|
|
230
|
+
candidates=candidates.filter((c )=>!kept.names.has(c.name));
|
|
231
|
+
}
|
|
225
232
|
const orderedCandidates=store.entry==='record' ? removalOrder(candidates)
|
|
226
233
|
: [...candidates].sort((a,b)=>a.mtimeMs-b.mtimeMs || a.name.localeCompare(b.name));
|
|
227
234
|
if (shouldStop()) { result.partial=true; break; }
|
|
@@ -18,6 +18,9 @@ const RULES = new Map([
|
|
|
18
18
|
['existing', []],
|
|
19
19
|
['control', []],
|
|
20
20
|
]);
|
|
21
|
+
// open-work keeps logs an unfinished task's record names; unfinished-run keeps a worker-model file
|
|
22
|
+
// while its worker run or dispatch is not terminal or cannot be resolved (row 120 IC-005).
|
|
23
|
+
const PROTECTS = new Set(['open-work', 'unfinished-run']);
|
|
21
24
|
const MOMENTS = new Set(['verb', 'session', 'verify', 'install', 'lifecycle-verb', 'uninstall']);
|
|
22
25
|
const STORE_ID = /^[a-z][a-z0-9-]*$/;
|
|
23
26
|
|
|
@@ -70,8 +73,18 @@ function validateStorageRegistry(registry )
|
|
|
70
73
|
for (const field of RULES.get(store.rule) || []) if (!positiveInteger(store.bound[field])) error(errors, `${where}.bound.${field}`, 'must be a positive integer');
|
|
71
74
|
if (['existing', 'control'].includes(store.rule) && (typeof store.bound.text !== 'string' || !store.bound.text)) error(errors, `${where}.bound.text`, 'must be a non-empty string');
|
|
72
75
|
if (store.rule === 'existing' && store.bound.value !== undefined && !positiveInteger(store.bound.value)) error(errors, `${where}.bound.value`, 'must be a positive integer when present');
|
|
76
|
+
if (store.rule === 'count-and-age' && store.bound.either !== undefined && typeof store.bound.either !== 'boolean') error(errors, `${where}.bound.either`, 'must be a boolean when present');
|
|
77
|
+
const allowed = new Set([
|
|
78
|
+
...(RULES.get(store.rule) || []),
|
|
79
|
+
...(['existing', 'control'].includes(store.rule) ? ['text'] : []),
|
|
80
|
+
...(store.rule === 'existing' ? ['value'] : []),
|
|
81
|
+
...(store.rule === 'count-and-age' ? ['either'] : []),
|
|
82
|
+
]);
|
|
83
|
+
for (const key of Object.keys(store.bound)) {
|
|
84
|
+
if (!allowed.has(key)) error(errors, `${where}.bound`, `unknown key '${key}'`);
|
|
85
|
+
}
|
|
73
86
|
}
|
|
74
|
-
if (store.protect !== null && store.protect
|
|
87
|
+
if (store.protect !== null && !PROTECTS.has(store.protect)) error(errors, `${where}.protect`, 'must be open-work, unfinished-run or null');
|
|
75
88
|
if (!Array.isArray(store.moments) || store.moments.some((moment ) => !MOMENTS.has(moment)) || new Set(store.moments).size !== store.moments.length) error(errors, `${where}.moments`, 'must be a unique subset of known moments');
|
|
76
89
|
if (store.rule === 'existing' && store.moments?.length) error(errors, `${where}.moments`, 'must be empty for existing stores');
|
|
77
90
|
if (!Array.isArray(store.writers) || store.writers.some((writer ) => typeof writer !== 'string' || !writer)) error(errors, `${where}.writers`, 'must be an array of repository-relative paths');
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
|
-
const os = require('node:os');
|
|
7
6
|
const path = require('node:path');
|
|
7
|
+
const { configHome } = require('../config-home.js');
|
|
8
8
|
|
|
9
9
|
function absolute(value ) { return typeof value === 'string' && path.isAbsolute(value) ? value : null; }
|
|
10
10
|
|
|
11
11
|
/** Resolves IC-001's five physical roots; -g/global deliberately omits project roots. */
|
|
12
12
|
function resolveRoots({ registry, projectRoot, global = false, env = process.env } = {}) {
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
13
|
+
// No home variable means no home base and no config home: nothing falls back to the account home (DD-7).
|
|
14
|
+
const home = absolute(env.HOME);
|
|
15
|
+
const xdgBase = configHome(env).base;
|
|
16
16
|
const project = global ? null : absolute(projectRoot);
|
|
17
17
|
const bases = { project, home, 'xdg-config': xdgBase };
|
|
18
18
|
return Object.fromEntries(Object.entries(registry.roots).map(([id, root] ) => {
|
|
@@ -21,21 +21,71 @@ function resolveRoots({ registry, projectRoot, global = false, env = process.env
|
|
|
21
21
|
}));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
/** Creates one IC-014 resolver for all roots in a command; a project view can also read global scope. */
|
|
25
|
+
function createPruneSettingsResolver({ roots, fsImpl = fs, onView } = {}) {
|
|
26
|
+
const resolverEnv = {};
|
|
27
|
+
if (roots['config-home']) resolverEnv.XDG_CONFIG_HOME = path.dirname(roots['config-home']);
|
|
28
|
+
let view = null;
|
|
29
|
+
let loadedView = false;
|
|
30
|
+
return (rootId ) => {
|
|
31
|
+
if (!loadedView) {
|
|
32
|
+
loadedView = true;
|
|
33
|
+
try {
|
|
34
|
+
let settingsRegistry;
|
|
35
|
+
if (fsImpl !== fs) {
|
|
36
|
+
const { loadSettingsRegistry } = require('../settings/registry.js');
|
|
37
|
+
const loaded = loadSettingsRegistry({ fsImpl });
|
|
38
|
+
if (!loaded.ok) throw new Error(loaded.detail);
|
|
39
|
+
settingsRegistry = loaded.registry;
|
|
40
|
+
}
|
|
41
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
42
|
+
const projectRoot = roots['project-doflow'] ? path.dirname(roots['project-doflow']) : null;
|
|
43
|
+
view = loadSettings({ projectRoot, env: resolverEnv, fsImpl, ...(settingsRegistry ? { registry: settingsRegistry } : {}) });
|
|
44
|
+
} catch {
|
|
45
|
+
// A settings registry fault fails closed for every root, as it did for each old call.
|
|
46
|
+
view = null;
|
|
47
|
+
}
|
|
48
|
+
if (typeof onView === 'function') onView(view);
|
|
49
|
+
}
|
|
50
|
+
if (!view) return { setting: 'off', decidedBy: null };
|
|
51
|
+
try {
|
|
52
|
+
const usesProject = rootId === 'project-doflow' || rootId === 'project-state';
|
|
53
|
+
const resolved = view.get('storage.prune', usesProject ? 'project' : 'global');
|
|
54
|
+
return {
|
|
55
|
+
setting: resolved.value === 'on' ? 'on' : 'off',
|
|
56
|
+
decidedBy: resolved.origin.layer === 'default' ? null : resolved.origin.source,
|
|
57
|
+
};
|
|
58
|
+
} catch {
|
|
59
|
+
return { setting: 'off', decidedBy: null };
|
|
60
|
+
}
|
|
61
|
+
};
|
|
29
62
|
}
|
|
30
63
|
|
|
31
64
|
/** Reads IC-014 and reports exactly which present file made the decision, if any. */
|
|
32
|
-
function readPruneSetting({
|
|
33
|
-
const projectFile = roots['project-doflow'] ? path.join(roots['project-doflow'], registry.settings.optOutFile) : null;
|
|
34
|
-
const userFile = roots['config-home'] ? path.join(roots['config-home'], registry.settings.optOutFile) : null;
|
|
65
|
+
function readPruneSetting({ roots, rootId, fsImpl = fs } = {}) {
|
|
35
66
|
const usesProject = rootId === 'project-doflow' || rootId === 'project-state';
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
67
|
+
const global = !usesProject;
|
|
68
|
+
const projectRoot = !global && roots['project-doflow'] ? path.dirname(roots['project-doflow']) : null;
|
|
69
|
+
const resolverEnv = {};
|
|
70
|
+
if (roots['config-home']) resolverEnv.XDG_CONFIG_HOME = path.dirname(roots['config-home']);
|
|
71
|
+
try {
|
|
72
|
+
let settingsRegistry;
|
|
73
|
+
if (fsImpl !== fs) {
|
|
74
|
+
const { loadSettingsRegistry } = require('../settings/registry.js');
|
|
75
|
+
const loaded = loadSettingsRegistry({ fsImpl });
|
|
76
|
+
if (!loaded.ok) throw new Error(loaded.detail);
|
|
77
|
+
settingsRegistry = loaded.registry;
|
|
78
|
+
}
|
|
79
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
80
|
+
const view = loadSettings({ projectRoot, global, env: resolverEnv, fsImpl, ...(settingsRegistry ? { registry: settingsRegistry } : {}) });
|
|
81
|
+
const resolved = view.get('storage.prune', global ? 'global' : 'project');
|
|
82
|
+
return {
|
|
83
|
+
setting: resolved.value === 'on' ? 'on' : 'off',
|
|
84
|
+
decidedBy: resolved.origin.layer === 'default' ? null : resolved.origin.source,
|
|
85
|
+
};
|
|
86
|
+
} catch {
|
|
87
|
+
return { setting: 'off', decidedBy: null };
|
|
88
|
+
}
|
|
39
89
|
}
|
|
40
90
|
|
|
41
91
|
function linked(file , fsImpl ) {
|
|
@@ -60,4 +110,4 @@ function resolveStorePath({ store, roots, fsImpl = fs } = {})
|
|
|
60
110
|
return current;
|
|
61
111
|
}
|
|
62
112
|
|
|
63
|
-
module.exports = { resolveRoots, readPruneSetting, resolveStorePath };
|
|
113
|
+
module.exports = { resolveRoots, readPruneSetting, createPruneSettingsResolver, resolveStorePath };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { workStatus, createWorkCache, refreshedWorkCache } = require('./work-status.js');
|
|
7
|
+
const { workStatus, recordExtension, createWorkCache, refreshedWorkCache } = require('./work-status.js');
|
|
8
8
|
const { projectKey } = require('../lifecycle/report-store.js');
|
|
9
9
|
const { readEvents } = require('../lifecycle/event-store.js');
|
|
10
10
|
|
|
@@ -50,7 +50,7 @@ function workCandidates(store , entries , context ) {
|
|
|
50
50
|
const project = projectRoot(roots);
|
|
51
51
|
if (!project) return [];
|
|
52
52
|
const cache = context.cache || createWorkCache({ projectRoot: project, fsImpl });
|
|
53
|
-
const recordMatch = new RegExp(store.match), selected = [];
|
|
53
|
+
const recordMatch = new RegExp(store.match), extensionLength = recordExtension(store.match).length, selected = [];
|
|
54
54
|
const visit = (dir , namespace = null) => {
|
|
55
55
|
let children;
|
|
56
56
|
try { children = names(fsImpl, dir); } catch { return true; }
|
|
@@ -61,7 +61,7 @@ function workCandidates(store , entries , context ) {
|
|
|
61
61
|
if (TMP.test(name) && now - stat.mtimeMs > settings.staleTmpHours * HOUR) {
|
|
62
62
|
selected.push({ name: namespace ? `${namespace}/${name}` : name, reason: 'stale-tmp', bytes: stat.size, mtimeMs: stat.mtimeMs, recordStat: stat });
|
|
63
63
|
} else if (recordMatch.test(name)) {
|
|
64
|
-
const result = workStatus({ projectRoot: project, store: store.id, taskId: name.slice(0, -
|
|
64
|
+
const result = workStatus({ projectRoot: project, store: store.id, taskId: name.slice(0, -extensionLength), namespace, mtimeMs: stat.mtimeMs, cache, fsImpl, shouldStop });
|
|
65
65
|
if (result.incomplete || shouldStop?.()) return false;
|
|
66
66
|
if (result.state === 'finished' && now - result.since > store.bound.finishedDays * DAY)
|
|
67
67
|
selected.push({ name: namespace ? `${namespace}/${name}` : name, reason: 'finished-work', bytes: stat.size, mtimeMs: result.since, recordStat: stat });
|
|
@@ -92,7 +92,7 @@ function recordStillCandidate(store , candidate , info , context
|
|
|
92
92
|
if (!snapshot?.ok || JSON.stringify(current) !== JSON.stringify(snapshot.value)) return false;
|
|
93
93
|
}
|
|
94
94
|
const fresh = cache ? refreshedWorkCache(cache) : undefined;
|
|
95
|
-
const status = workStatus({ projectRoot: projectRoot(roots), store: store.id, taskId: name.slice(0, -
|
|
95
|
+
const status = workStatus({ projectRoot: projectRoot(roots), store: store.id, taskId: name.slice(0, -recordExtension(store.match).length), namespace, mtimeMs: info.mtimeMs, cache: fresh, fsImpl, shouldStop });
|
|
96
96
|
return !status.incomplete && !shouldStop?.() && status.state === 'finished'
|
|
97
97
|
&& status.since === candidate.mtimeMs && now - status.since > store.bound.finishedDays * DAY;
|
|
98
98
|
}
|
|
@@ -138,7 +138,7 @@ function protectedLogs({ registry, roots, fsImpl, cache, shouldStop } ) {
|
|
|
138
138
|
continue;
|
|
139
139
|
}
|
|
140
140
|
if (!stat.isFile() || !recordMatch.test(name)) continue;
|
|
141
|
-
const status = workStatus({ projectRoot: project, store: store.id, taskId: name.slice(0, -
|
|
141
|
+
const status = workStatus({ projectRoot: project, store: store.id, taskId: name.slice(0, -recordExtension(store.match).length), namespace, mtimeMs: stat.mtimeMs, cache: workCache, fsImpl, shouldStop });
|
|
142
142
|
if (status.incomplete || shouldStop?.()) return false;
|
|
143
143
|
if (status.state === 'finished') continue;
|
|
144
144
|
try {
|
|
@@ -158,6 +158,73 @@ function protectedLogs({ registry, roots, fsImpl, cache, shouldStop } ) {
|
|
|
158
158
|
}
|
|
159
159
|
return { names: protectedNames, incomplete: false };
|
|
160
160
|
}
|
|
161
|
+
const WORKER_UNIT = /^L\.([A-Za-z0-9][A-Za-z0-9_-]{0,55})\.([A-Za-z0-9][A-Za-z0-9_-]{0,55})\.([1-9][0-9]{0,3})\.json$/;
|
|
162
|
+
/** Every `<slug>.json` store file of `.doflow/state/<dir>` read by `read`; [] when the folder is
|
|
163
|
+
* absent, null when a link sits on the path or any file cannot be read (the caller fails closed). */
|
|
164
|
+
function stateStores(project , dir , read , fsImpl , shouldStop ) {
|
|
165
|
+
let current = project;
|
|
166
|
+
for (const segment of ['.doflow', 'state', dir]) {
|
|
167
|
+
current = path.join(current, segment);
|
|
168
|
+
let info;
|
|
169
|
+
try { info = fsImpl.lstatSync(current); }
|
|
170
|
+
catch (error ) { if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return []; return null; }
|
|
171
|
+
if (info.isSymbolicLink() || !info.isDirectory()) return null;
|
|
172
|
+
}
|
|
173
|
+
const stores = [];
|
|
174
|
+
let list;
|
|
175
|
+
try { list = names(fsImpl, current); } catch { return null; }
|
|
176
|
+
for (const name of list) {
|
|
177
|
+
if (shouldStop?.()) return null;
|
|
178
|
+
if (!name.endsWith('.json')) continue;
|
|
179
|
+
const info = safeStat(fsImpl, path.join(current, name));
|
|
180
|
+
if (!info || info.isSymbolicLink() || !info.isFile()) return null;
|
|
181
|
+
try { stores.push(read(path.join(current, name), fsImpl)); } catch { return null; }
|
|
182
|
+
}
|
|
183
|
+
return stores;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Protect kind `unfinished-run` (row 120 IC-005): of the candidate names `L.<run>.<lineage>.<attempt>.json`,
|
|
187
|
+
* the ones to keep. A name is released only when this project's worker run store holds at least one
|
|
188
|
+
* run whose `identity` is that unit and every such run is terminal, and no dispatch naming one of
|
|
189
|
+
* those runs (`workerRunId`) is still open. Terminal is the one end phase each store has: a worker
|
|
190
|
+
* run's `phase: 'ended'` (run-store.ts PHASES, which `transition` never leaves) and a dispatch's
|
|
191
|
+
* `phase: 'ended'` (dispatch/store.ts NEXT_PHASES, add-only once reached). Everything else is kept
|
|
192
|
+
* (fail closed): an unparsable name, no project, an unknown unit (another project's file, or a run
|
|
193
|
+
* record pruned first), a link on a store path or any store file that cannot be read.
|
|
194
|
+
*/
|
|
195
|
+
function unfinishedRuns({ roots, fsImpl, names: candidates, shouldStop } ) {
|
|
196
|
+
const all = new Set (candidates);
|
|
197
|
+
const project = projectRoot(roots);
|
|
198
|
+
if (!project) return { names: all, incomplete: false };
|
|
199
|
+
const { readRuns } = require('../worker/run-store.js');
|
|
200
|
+
const { readDispatch } = require('../dispatch/store.js');
|
|
201
|
+
const runStores = stateStores(project, 'worker-runs', (file, impl) => ({ runs: readRuns(file, impl) }), fsImpl, shouldStop);
|
|
202
|
+
if (shouldStop?.()) return { names: all, incomplete: true };
|
|
203
|
+
const dispatchStores = runStores && stateStores(project, 'dispatch', readDispatch, fsImpl, shouldStop);
|
|
204
|
+
if (shouldStop?.()) return { names: all, incomplete: true };
|
|
205
|
+
if (!runStores || !dispatchStores) return { names: all, incomplete: false };
|
|
206
|
+
const units = new Map ();
|
|
207
|
+
for (const store of runStores) for (const [runId, run] of Object.entries(store.runs) ) {
|
|
208
|
+
const id = run?.identity;
|
|
209
|
+
if (!id || typeof id.run !== 'string' || typeof id.lineage !== 'string' || typeof id.attempt !== 'string') continue;
|
|
210
|
+
const key = `${id.run}.${id.lineage}.${id.attempt}`;
|
|
211
|
+
const unit = units.get(key) || { ended: true, runIds: [] };
|
|
212
|
+
unit.ended = unit.ended && run.phase === 'ended';
|
|
213
|
+
unit.runIds.push(runId);
|
|
214
|
+
units.set(key, unit);
|
|
215
|
+
}
|
|
216
|
+
const openRuns = new Set ();
|
|
217
|
+
for (const store of dispatchStores) for (const dispatch of Object.values(store.dispatches) ) {
|
|
218
|
+
if (dispatch.phase !== 'ended' && typeof dispatch.workerRunId === 'string') openRuns.add(dispatch.workerRunId);
|
|
219
|
+
}
|
|
220
|
+
const kept = new Set ();
|
|
221
|
+
for (const name of candidates) {
|
|
222
|
+
const match = WORKER_UNIT.exec(name);
|
|
223
|
+
const unit = match && units.get(`${match[1]}.${match[2]}.${match[3]}`);
|
|
224
|
+
if (!unit || !unit.ended || unit.runIds.some((runId) => openRuns.has(runId))) kept.add(name);
|
|
225
|
+
}
|
|
226
|
+
return { names: kept, incomplete: false };
|
|
227
|
+
}
|
|
161
228
|
function lifecyclePathsSafe(project , fsImpl ) {
|
|
162
229
|
for (const rel of [path.join('.doflow'), path.join('.doflow', 'state'), path.join('.doflow', 'state', 'lifecycle'), path.join('.doflow', 'state', 'lifecycle', 'events')]) {
|
|
163
230
|
try { if (fsImpl.lstatSync(path.join(project, rel)).isSymbolicLink()) return false; }
|
|
@@ -236,14 +303,19 @@ function selectCandidates(store , entries , context ) {
|
|
|
236
303
|
const { now, fsImpl, storeDir, roots, shouldStop } = context;
|
|
237
304
|
switch (store.rule) {
|
|
238
305
|
case 'count-and-age': {
|
|
239
|
-
const
|
|
306
|
+
const hasTimestamp = (entry ) => /^[0-9]{13}/.test(entry.name);
|
|
307
|
+
const entryTime = (entry ) => hasTimestamp(entry) ? Number(entry.name.slice(0, 13)) : entry.stat.mtimeMs;
|
|
308
|
+
const ordered = [...entries].sort((a, b) => entryTime(b) - entryTime(a) || a.name.localeCompare(b.name));
|
|
240
309
|
if (stopped(context)) return null;
|
|
241
310
|
const selected = [];
|
|
242
311
|
for (let index = 0; index < ordered.length; index += 1) {
|
|
243
312
|
if (shouldStop?.()) return null;
|
|
244
|
-
const entry = ordered[index], time =
|
|
245
|
-
|
|
246
|
-
|
|
313
|
+
const entry = ordered[index], time = entryTime(entry);
|
|
314
|
+
const beyondCount = index >= store.bound.keepNewest, beyondAge = now - time > store.bound.maxAgeDays * DAY;
|
|
315
|
+
// Preserve timestamped stores' count-or-age policy; mtime entries must exceed both bounds unless bound.either is set.
|
|
316
|
+
const eitherBound = hasTimestamp(entry) || store.bound?.either === true;
|
|
317
|
+
if (eitherBound ? beyondCount || beyondAge : beyondCount && beyondAge)
|
|
318
|
+
selected.push({ name: entry.name, reason: beyondCount ? 'count' : 'age', bytes: entry.stat.size, mtimeMs: time });
|
|
247
319
|
}
|
|
248
320
|
return selected;
|
|
249
321
|
}
|
|
@@ -279,4 +351,4 @@ function selectCandidates(store , entries , context ) {
|
|
|
279
351
|
default: throw new Error(`Unknown storage rule '${store.rule}'`);
|
|
280
352
|
}
|
|
281
353
|
}
|
|
282
|
-
module.exports={selectCandidates,recordStillCandidate,protectedLogs,removableRules,ruleOrder,projectRoot,BODY};
|
|
354
|
+
module.exports={selectCandidates,recordStillCandidate,protectedLogs,unfinishedRuns,removableRules,ruleOrder,projectRoot,BODY};
|
|
@@ -9,7 +9,7 @@ const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
|
9
9
|
const { loadStorageRegistry } = require('./registry.js');
|
|
10
10
|
const { resolveRoots, readPruneSetting } = require('./roots.js');
|
|
11
11
|
const engine = require('./engine.js');
|
|
12
|
-
const { RunLedger } = require('../trace/ledger.js');
|
|
12
|
+
const { RunLedger, outerProjectRoot } = require('../trace/ledger.js');
|
|
13
13
|
|
|
14
14
|
// Load once: a not-due moment only reads the three small markers, never the catalogue again.
|
|
15
15
|
let registry ;
|
|
@@ -147,7 +147,10 @@ function passForRoot({ roots, rootId, stores, now, env, fsImpl, throttled = fals
|
|
|
147
147
|
|
|
148
148
|
/** IC-009: silent throttled upkeep after a verb, using that verb's resolved store root. */
|
|
149
149
|
function upkeepAfterVerb({ projectRoot, now = Date.now(), env = process.env, fsImpl = fs } = {}) {
|
|
150
|
+
// IC-004: the read switch, exact `off` like DOFLOW_TELEMETRY; nothing at all is written.
|
|
151
|
+
if (env.DOFLOW_UPKEEP === 'off') return;
|
|
150
152
|
return silently(() => {
|
|
153
|
+
projectRoot = projectRoot && outerProjectRoot(projectRoot);
|
|
151
154
|
const roots = resolveRoots({ registry, projectRoot, env });
|
|
152
155
|
for (const rootId of ['project-state', 'user-state']) {
|
|
153
156
|
try { ensureIgnore({ roots, rootId, projectRoot, fsImpl }); } catch { /* continue other roots */ }
|
|
@@ -165,14 +168,19 @@ function upkeepAfterVerb({ projectRoot, now = Date.now(), env = process.env, fsI
|
|
|
165
168
|
|
|
166
169
|
/** IC-010: the verify moment is project-only and unthrottled. Wiring belongs to D.3. */
|
|
167
170
|
function upkeepAfterVerify({ projectRoot, now = Date.now(), env = process.env, fsImpl = fs } = {}) {
|
|
171
|
+
// IC-004: the switch covers every upkeep moment, so `off` prunes, locks and writes nothing here either.
|
|
172
|
+
if (env.DOFLOW_UPKEEP === 'off') return;
|
|
168
173
|
return silently(() => {
|
|
174
|
+
projectRoot = projectRoot && outerProjectRoot(projectRoot);
|
|
169
175
|
const roots = resolveRoots({ registry, projectRoot, env });
|
|
170
|
-
return passForRoot({ roots, rootId: 'project-state', stores: ['verification-logs'], now, env, fsImpl });
|
|
176
|
+
return passForRoot({ roots, rootId: 'project-state', stores: ['verification-logs', 'verification-reuse'], now, env, fsImpl });
|
|
171
177
|
});
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
/** IC-011: only this scope's recovery store; install/update call sites belong to D.4. */
|
|
175
181
|
function storageAfterInstall({ scope, scopeRoot, now = Date.now(), env = process.env, fsImpl = fs } = {}) {
|
|
182
|
+
// IC-004: no ignore file, prune pass or lock at install time under the switch either.
|
|
183
|
+
if (env.DOFLOW_UPKEEP === 'off') return;
|
|
176
184
|
return silently(() => {
|
|
177
185
|
if (!['project', 'global'].includes(scope)) return;
|
|
178
186
|
const global = scope === 'global';
|