@khoavu882/doflow 2.0.0-beta.5 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -16
- package/bin/doflow.js +22 -12
- package/core/.antigravity-plugin/plugin.json +1 -2
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +26 -9
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/hooks.json +0 -10
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +51 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -11
- package/core/harnesses/kiro/hooks/doflow.json +0 -10
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
- package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +35 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1445 -366
- package/core/registry/intake.json +53 -0
- package/core/registry/lifecycle.json +599 -996
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/intake-category/files/category.json +8 -0
- package/core/registry/package-templates/intake-category/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +272 -0
- package/core/registry/storage.json +687 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -6
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
- package/core/shared/scripts/doflow/bin/doflow-run +152 -34
- package/core/shared/skills/do/SKILL.md +14 -12
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +48 -148
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +23 -20
- package/core/shared/skills/do-diagnose/SKILL.md +18 -6
- package/core/shared/skills/do-document/SKILL.md +12 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +19 -7
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +10 -11
- package/core/shared/skills/do-flow/references/task_classes.md +5 -5
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +13 -14
- package/core/shared/skills/do-plan/SKILL.md +34 -20
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/shared/workflows/do-flow-chain.md +2 -0
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +143 -66
- package/src/adapters/claude/index.js +46 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +39 -3
- package/src/adapters/codex/index.js +61 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +291 -46
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +22 -3
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +337 -32
- package/src/cli/runtime-commands.js +220 -60
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +4 -16
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +472 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +20 -11
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/pointer.js +67 -12
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +158 -79
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-ledger.js +52 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +624 -18
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +281 -26
- package/src/runtime/intake/engine.js +677 -0
- package/src/runtime/intake/index.js +136 -0
- package/src/runtime/intake/manifest.js +140 -0
- package/src/runtime/intake/taxonomy.js +206 -0
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +88 -10
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +38 -7
- package/src/runtime/lifecycle/overview.js +26 -2
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +2 -2
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +487 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/attendance.js +137 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +273 -0
- package/src/runtime/roadmap/loop.js +1719 -0
- package/src/runtime/roadmap/merge.js +390 -0
- package/src/runtime/roadmap/rows.js +1122 -0
- package/src/runtime/roadmap/seed.js +1063 -0
- package/src/runtime/roadmap/worktree-cleanup.js +147 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +1 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +85 -14
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +373 -64
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verb-view.js +267 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -54
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +256 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +610 -67
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -251
- package/src/adapters/gemini/index.js +0 -429
|
@@ -22,6 +22,7 @@ const { STAGES: CHAIN_STAGES } = require('../decision-register.js');
|
|
|
22
22
|
const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.js');
|
|
23
23
|
const { resolveActiveFeature } = require('../feature-resolve.js');
|
|
24
24
|
const { SETTLE_AS, withDerivedDone } = require('./fold.js');
|
|
25
|
+
const { pageItems } = require('../verb-view.js');
|
|
25
26
|
const { appendEvents, readFold, byFromChannel, randomChars } = require('./event-store.js');
|
|
26
27
|
const { deriveStatuses } = require('./status.js');
|
|
27
28
|
const { writeIntent, kebabTitle } = require('./intent-writer.js');
|
|
@@ -73,7 +74,7 @@ function newFollowupId(taken ) {
|
|
|
73
74
|
|
|
74
75
|
function channelBy(channel ) {
|
|
75
76
|
const by = byFromChannel(channel || 'default');
|
|
76
|
-
if (!by) throw new FollowupUsageError(`unknown --channel '${channel}'. Valid: question, gate, prompt, default`);
|
|
77
|
+
if (!by) throw new FollowupUsageError(`unknown --channel '${channel}'. Valid: question, gate, prompt, resolution, default`);
|
|
77
78
|
return by;
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -334,12 +335,37 @@ function reportFollowup({ root, statement, input, release, feature, channel, now
|
|
|
334
335
|
|
|
335
336
|
// ── list ───────────────────────────────────────────────────────────────────────────────────────
|
|
336
337
|
|
|
337
|
-
/**
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
/** The most items one list answer holds, however large a `--limit` asks for (IC-004). */
|
|
339
|
+
const LIST_MAX_LIMIT = 100;
|
|
340
|
+
|
|
341
|
+
/** The key the fold orders on and a list cursor names: the creating event's time in ms, then its event id (IC-008). */
|
|
342
|
+
function followupOrderKey(item ) {
|
|
343
|
+
const created = item.history[0];
|
|
344
|
+
return { atMs: Date.parse(created.at), event: created.event };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* IC-006 `list`: `state` is open (default), taken, done, dismissed or all. `ids` keeps the named items (and, with no
|
|
349
|
+
* `state`, looks in every state); an id that is no item at all is the `unknown-id` refusal. With `cursor`, `limit` or
|
|
350
|
+
* `defaultLimit` the answer holds one page of `items` plus `shown` and `nextCursor`; with none it is the whole list.
|
|
351
|
+
*/
|
|
352
|
+
function listFollowups({ root, state, ids, cursor, limit, defaultLimit = null, hasBody, fsImpl = nodeFs, statuses, now = new Date() } ) {
|
|
353
|
+
const wanted = ids === undefined ? null : parseIds(ids);
|
|
354
|
+
const wantedState = state === undefined ? (wanted ? 'all' : 'open') : state;
|
|
355
|
+
if (wantedState !== 'all' && !STATES.includes(wantedState)) throw new FollowupUsageError(`--state must be one of ${[...STATES, 'all'].join(', ')} (got '${wantedState}')`);
|
|
340
356
|
const { fold, followups } = loadFollowups(root, { hasBody, fsImpl, statuses, now });
|
|
341
|
-
|
|
342
|
-
|
|
357
|
+
let matched = followups;
|
|
358
|
+
if (wanted) {
|
|
359
|
+
const known = new Set(followups.map((item ) => item.id));
|
|
360
|
+
const unknown = wanted.filter((id) => !known.has(id));
|
|
361
|
+
if (unknown.length) return refusal('list', 'unknown-id', `${unknown.join(', ')} ${unknown.length === 1 ? 'is' : 'are'} not a follow-up in this store. Nothing was listed.`);
|
|
362
|
+
matched = followups.filter((item ) => wanted.includes(item.id));
|
|
363
|
+
}
|
|
364
|
+
if (wantedState !== 'all') matched = matched.filter((item ) => item.state === wantedState);
|
|
365
|
+
const answer = { ok: true, action: 'list', state: wantedState, count: matched.length, items: matched, conflicts: fold.conflicts, unreadable: fold.unreadable, unreadableReasons: fold.unreadableReasons, next: [] };
|
|
366
|
+
if (cursor === undefined && limit === undefined && defaultLimit === null) return answer;
|
|
367
|
+
const page = pageItems(matched, { cursor, limit, defaultLimit, maxLimit: LIST_MAX_LIMIT, key: followupOrderKey });
|
|
368
|
+
return { ...answer, items: page.items, shown: page.shown, nextCursor: page.nextCursor };
|
|
343
369
|
}
|
|
344
370
|
|
|
345
371
|
// ── take, settle, promote ──────────────────────────────────────────────────────────────────────
|
|
@@ -350,6 +376,11 @@ function takeFollowups({ root, ids, slug, channel, now = new Date(), fsImpl = no
|
|
|
350
376
|
if (!slug) throw new FollowupUsageError('--slug is required for --action take');
|
|
351
377
|
const bad = invalidSlugRefusal(slug);
|
|
352
378
|
if (bad) throw new FollowupUsageError(bad.message);
|
|
379
|
+
// `list` shows a taken item whose feature finished as `done`, but the fold still holds it `taken`: say what the user was shown.
|
|
380
|
+
const shown = loadFollowups(root, { fsImpl, now }).followups.find((item ) => item.derived && list.includes(item.id));
|
|
381
|
+
if (shown) {
|
|
382
|
+
return refusal('take', 'illegal-transition', `${shown.id} is done because feature ${shown.takenBy} finished (derived from git and the release records, not recorded on the item); only an open item can be taken. Nothing was written.`);
|
|
383
|
+
}
|
|
353
384
|
const out = appendEvents(root, [{ type: 'followup.taken', by: channelBy(channel), data: { ids: list, feature: slug } }], { now, fsImpl });
|
|
354
385
|
if (!out.ok) return refusalFrom('take', out);
|
|
355
386
|
return { ok: true, action: 'take', ids: list, feature: slug, events: out.written.map((w ) => w.file), next: [] };
|
|
@@ -426,7 +457,7 @@ function promoteFollowups({ root, ids, title, channel, now = new Date(), fsImpl
|
|
|
426
457
|
}
|
|
427
458
|
|
|
428
459
|
module.exports = {
|
|
429
|
-
addFollowups, reportFollowup, listFollowups, takeFollowups, settleFollowups, promoteFollowups,
|
|
460
|
+
addFollowups, reportFollowup, listFollowups, followupOrderKey, takeFollowups, settleFollowups, promoteFollowups,
|
|
430
461
|
loadFollowups, readBatchFile, resolveFeatureSlug, parseIds, oneLine, channelBy,
|
|
431
462
|
FollowupUsageError, STAGES, STATES, SOURCE_KINDS, STATEMENT_MAX,
|
|
432
463
|
};
|
|
@@ -16,6 +16,8 @@ const { withDerivedDone } = require('./fold.js');
|
|
|
16
16
|
const { appendEvents, readFold, readEvents } = require('./event-store.js');
|
|
17
17
|
const { deriveStatuses, bucketize, behindNote } = require('./status.js');
|
|
18
18
|
const { FollowupUsageError, parseIds } = require('./followup.js');
|
|
19
|
+
const { describeActive } = require('../feature-pointer.js');
|
|
20
|
+
const { commandLine } = require('../verb-view.js');
|
|
19
21
|
|
|
20
22
|
/** The failure store, or null when its module cannot be loaded: the overview then reports no failures. */
|
|
21
23
|
function failureStoreOrNull() {
|
|
@@ -129,7 +131,7 @@ function goalView(goal , features , statuses , conflicts = [])
|
|
|
129
131
|
|
|
130
132
|
function nextLines({ shown, open, items, intents, goals, maintain, pendingItems, failures } ) {
|
|
131
133
|
const next = [];
|
|
132
|
-
if (shown < open) next.push(
|
|
134
|
+
if (shown < open) next.push(`Page through all ${open} open follow-ups: ${commandLine('followup', 'list', {})}`);
|
|
133
135
|
if (items.length) next.push(`Take items into the new feature when its folder exists: doflow-run lifecycle --action init --slug <slug> --take ${items[0].id}`);
|
|
134
136
|
if (intents.length) {
|
|
135
137
|
next.push(`Start from a promoted intent: /do-brainstorm --intent ${intents[0].path}, then doflow-run lifecycle --action init --slug <slug> --intent ${intents[0].path}`);
|
|
@@ -289,4 +291,26 @@ function featureStatus({ root, slug, now = new Date(), fsImpl = nodeFs } ) {
|
|
|
289
291
|
return result;
|
|
290
292
|
}
|
|
291
293
|
|
|
292
|
-
|
|
294
|
+
/**
|
|
295
|
+
* `status` with no `--slug` (row 135): the feature the runtime recorded for this checkout, against the
|
|
296
|
+
* branch's. A mismatch or an unreadable pointer is a refusal (exit 1) that names both sides and picks
|
|
297
|
+
* neither; an agreeing pointer also carries the tracked feature's derived status, as `--slug` does.
|
|
298
|
+
*/
|
|
299
|
+
function activeFeature({ root, cwd, now = new Date(), fsImpl = nodeFs } ) {
|
|
300
|
+
const active = describeActive({ storeRoot: root, cwd });
|
|
301
|
+
const here = `branch ${active.current.branch || 'unknown'}${active.current.slug ? `, branch feature ${active.current.slug}` : ''}`;
|
|
302
|
+
if (active.state === 'unreadable') {
|
|
303
|
+
return { ok: false, action: 'status', finding: 'feature-pointer-unreadable', message: `${active.file} cannot be read (${active.reason}); delete it to let the next feature start write it again.`, active };
|
|
304
|
+
}
|
|
305
|
+
if (active.state === 'mismatch') {
|
|
306
|
+
const p = active.pointer;
|
|
307
|
+
return { ok: false, action: 'status', finding: 'feature-mismatch', message: `the pointer names ${p.slug} on branch ${p.branch || 'unknown'}, but this checkout is on ${here}; neither is trusted, so check which feature you are on.`, active };
|
|
308
|
+
}
|
|
309
|
+
const result = { ok: true, action: 'status', active };
|
|
310
|
+
if (active.state === 'none') return { ...result, message: `no active feature is recorded for this checkout (${here}); pass --slug <slug> to ask about one.` };
|
|
311
|
+
// An untracked feature is refused by featureStatus; the pointer answer stands, named, without status fields.
|
|
312
|
+
const derived = featureStatus({ root, slug: active.slug, now, fsImpl });
|
|
313
|
+
return derived.ok ? { ...derived, active } : { ...result, slug: active.slug, message: `${active.slug} is tracked by its run only; \`lifecycle --action init\` to track it.` };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
module.exports = { buildOverview, initFeature, featureStatus, activeFeature, goalView, goalConflicts, goalConflictNext, DISCOVERY_SHOWN, MAINTAIN_SHOWN, FEATURES_SHOWN };
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* The machine-local report body (IC-005, DEC-014). A report's text can hold a crash trace, a log or
|
|
7
7
|
* pasted output, so it never enters the repository: the body is masked with the body profile (IC-012),
|
|
8
8
|
* cut to at most 1 MiB and written once under the same per-machine home the failure store uses,
|
|
9
|
-
* `<
|
|
9
|
+
* `<config home>/doflow/reports/<projectKey>/<FU-id>.txt`, folder 0700 and file
|
|
10
10
|
* 0600. The project event carries only a reference, the size and a masked 2 KB excerpt.
|
|
11
11
|
*
|
|
12
12
|
* `projectKey` is the first 12 hex characters of the SHA-256 of the IC-001 root's real path, so two
|
|
@@ -20,6 +20,7 @@ const nodeFs = require('node:fs');
|
|
|
20
20
|
const path = require('node:path');
|
|
21
21
|
const crypto = require('node:crypto');
|
|
22
22
|
const mask = require('../mask.js');
|
|
23
|
+
const { configHome } = require('../config-home.js');
|
|
23
24
|
|
|
24
25
|
/** A `--file` or `--stdin` source is read for at most this many bytes; the rest could not be kept anyway. */
|
|
25
26
|
const MAX_READ_BYTES = 16 * 1024 * 1024;
|
|
@@ -38,17 +39,14 @@ const EXCERPT_BYTES = 2048;
|
|
|
38
39
|
class ReportInputError extends Error {}
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
|
-
* The folder holding every project's report bodies, or null when no home resolves. It
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* a build or an install where the failure modules cannot be loaded (IC-016).
|
|
42
|
+
* The folder holding every project's report bodies, or null when no config home resolves. It sits in
|
|
43
|
+
* the same config home as the failure home (`../config-home.js`), `doflow/reports`, and takes that
|
|
44
|
+
* leaf helper rather than the failure module, so the follow-up service still loads in a build or an
|
|
45
|
+
* install where the failure modules cannot be loaded (IC-016).
|
|
46
46
|
*/
|
|
47
47
|
function reportsHome(env = process.env) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const home = env.HOME;
|
|
51
|
-
return typeof home === 'string' && path.isAbsolute(home) ? path.join(home, '.config', 'doflow', 'reports') : null;
|
|
48
|
+
const { dir } = configHome(env);
|
|
49
|
+
return dir === null ? null : path.join(dir, 'reports');
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
function realProjectRoot(root , fsImpl = nodeFs) {
|
|
@@ -9,18 +9,17 @@
|
|
|
9
9
|
* joined by any event that names more than one, and a unit goes only as a whole: when every item in
|
|
10
10
|
* it is settled in the fold (a follow-up done or dismissed, a goal done) and its newest event is
|
|
11
11
|
* older than the window. A unit whose removal would change anything else the fold shows (another
|
|
12
|
-
* item, a feature, a release, a conflict) is kept
|
|
12
|
+
* item, a feature, a release, a conflict) is kept, and so is a unit that an event of a type this
|
|
13
|
+
* version does not know (written by a newer DoFlow) names by id.
|
|
13
14
|
*
|
|
14
|
-
* `parseWindow` remains pure. `retentionWindow` composes
|
|
15
|
-
*
|
|
15
|
+
* `parseWindow` remains pure. `retentionWindow` composes the resolver-owned default with the project's
|
|
16
|
+
* prune setting; explicit values retain their existing meaning while pruning is off.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
const nodeFs = require('node:fs');
|
|
19
20
|
const { isDeepStrictEqual } = require('node:util');
|
|
20
|
-
const {
|
|
21
|
-
const {
|
|
22
|
-
const { resolveRoots, readPruneSetting } = require('../storage/roots.js');
|
|
23
|
-
const { foldInto, finalize } = require('./fold.js');
|
|
21
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
22
|
+
const { foldInto, finalize, EVENT_TYPES } = require('./fold.js');
|
|
24
23
|
|
|
25
24
|
const HOUR_MS = 3600000;
|
|
26
25
|
|
|
@@ -35,23 +34,46 @@ function parseWindow(env = process.env)
|
|
|
35
34
|
return { state: 'invalid', raw: String(raw) };
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
function invalidRaw(resolved , env ) {
|
|
38
|
+
for (let i = resolved.candidates.length - 1; i >= 0; i -= 1) {
|
|
39
|
+
const candidate = resolved.candidates[i];
|
|
40
|
+
if (candidate.status !== 'invalid') continue;
|
|
41
|
+
if (candidate.layer === 'env') return String(env.DOFLOW_RETENTION_HOURS ?? '');
|
|
42
|
+
try {
|
|
43
|
+
const raw = JSON.parse(candidate.display);
|
|
44
|
+
if (typeof raw === 'string' || typeof raw === 'number') return String(raw);
|
|
45
|
+
} catch { /* a malformed whole-file candidate has no display value */ }
|
|
46
|
+
return candidate.display || candidate.reason || candidate.code || '';
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Composes the retention setting and prune gate through the shared settings resolver. */
|
|
52
|
+
function retentionWindow({ env = process.env, projectRoot, fsImpl = nodeFs } = {}) {
|
|
40
53
|
const parsed = parseWindow(env);
|
|
41
54
|
if (parsed.state === 'invalid') return parsed;
|
|
42
|
-
if (parsed.state === 'on') return { ...parsed, source: 'env' };
|
|
43
|
-
let activeRegistry ;
|
|
44
55
|
try {
|
|
45
|
-
|
|
56
|
+
let settingsRegistry;
|
|
57
|
+
if (fsImpl !== nodeFs) {
|
|
58
|
+
const { loadSettingsRegistry } = require('../settings/registry.js');
|
|
59
|
+
const loaded = loadSettingsRegistry({ fsImpl });
|
|
60
|
+
if (!loaded.ok) throw new Error(loaded.detail);
|
|
61
|
+
settingsRegistry = loaded.registry;
|
|
62
|
+
}
|
|
63
|
+
const view = loadSettings({ projectRoot, env, fsImpl, ...(settingsRegistry ? { registry: settingsRegistry } : {}) });
|
|
64
|
+
const retention = view.get('storage.retentionHours', 'project');
|
|
65
|
+
if (retention.state === 'invalid') return { state: 'invalid', raw: invalidRaw(retention, env) };
|
|
66
|
+
if (retention.explicit) {
|
|
67
|
+
const hours = retention.value;
|
|
68
|
+
return { state: 'on', hours, ms: hours * HOUR_MS, source: retention.origin.layer };
|
|
69
|
+
}
|
|
70
|
+
if (view.get('storage.prune', 'project').value !== 'on') return { state: 'off' };
|
|
71
|
+
const hours = retention.value;
|
|
72
|
+
return { state: 'on', hours, ms: hours * HOUR_MS, source: 'default' };
|
|
46
73
|
} catch {
|
|
47
|
-
|
|
74
|
+
// Preserve an explicit environment window when the resolver registry is unavailable.
|
|
75
|
+
return parsed.state === 'on' ? { ...parsed, source: 'env' } : { state: 'off' };
|
|
48
76
|
}
|
|
49
|
-
const roots = resolveRoots({ registry: activeRegistry, projectRoot, env });
|
|
50
|
-
if (readPruneSetting({ registry: activeRegistry, roots, rootId: 'project-state', fsImpl }).setting !== 'on') return { state: 'off' };
|
|
51
|
-
const lifecycleStore = activeRegistry.stores.find((store ) => store.id === 'lifecycle-events');
|
|
52
|
-
if (!lifecycleStore) throw new Error('Storage registry has no lifecycle-events store');
|
|
53
|
-
const hours = lifecycleStore.bound.defaultHours;
|
|
54
|
-
return { state: 'on', hours, ms: hours * HOUR_MS, source: 'default' };
|
|
55
77
|
}
|
|
56
78
|
|
|
57
79
|
function isText(value ) { return typeof value === 'string' && value.trim() !== ''; }
|
|
@@ -113,6 +135,34 @@ function unitsOf(events , shouldStop ) {
|
|
|
113
135
|
return [...units.values()];
|
|
114
136
|
}
|
|
115
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Every string value and every object key in the data of the events whose type this version's fold
|
|
140
|
+
* does not know (a newer DoFlow wrote them). The fold ignores them, so retention cannot tell what they change; a unit that
|
|
141
|
+
* one of them names by id is kept whole rather than deleted from under it. Exact matches only:
|
|
142
|
+
* keeping too little is a lost record, keeping too much only a file that stays.
|
|
143
|
+
* @returns {Set<string>|null} null when `shouldStop` fired
|
|
144
|
+
*/
|
|
145
|
+
function unknownTypeStrings(events , shouldStop ) {
|
|
146
|
+
const known = new Set (EVENT_TYPES);
|
|
147
|
+
const found = new Set ();
|
|
148
|
+
for (const event of events) {
|
|
149
|
+
if (shouldStop?.()) return null;
|
|
150
|
+
if (known.has(event.type)) continue;
|
|
151
|
+
const pending = [event.data];
|
|
152
|
+
while (pending.length > 0) {
|
|
153
|
+
if (shouldStop?.()) return null;
|
|
154
|
+
const value = pending.pop();
|
|
155
|
+
if (typeof value === 'string') found.add(value);
|
|
156
|
+
else if (Array.isArray(value)) pending.push(...value);
|
|
157
|
+
else if (value && typeof value === 'object') {
|
|
158
|
+
for (const key of Object.keys(value)) found.add(key);
|
|
159
|
+
pending.push(...Object.values(value));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
|
|
116
166
|
function isSettled(state , key ) {
|
|
117
167
|
const [kind, id] = [key.slice(0, key.indexOf(':')), key.slice(key.indexOf(':') + 1)];
|
|
118
168
|
if (kind === 'followup') {
|
|
@@ -173,6 +223,12 @@ function selectExpired(events , { now, windowMs, shouldStop } = {})
|
|
|
173
223
|
}
|
|
174
224
|
const named = unitsOf(events, shouldStop);
|
|
175
225
|
if (!named || shouldStop?.()) return incomplete();
|
|
226
|
+
const mentioned = unknownTypeStrings(events, shouldStop);
|
|
227
|
+
if (!mentioned || shouldStop?.()) return incomplete();
|
|
228
|
+
// Selected exactly as 5bc9407b did (every settled, expired, unserved unit is a candidate), and only
|
|
229
|
+
// then are the units an unknown-type event names dropped. Protection removes units from that
|
|
230
|
+
// selection; it never changes which of the others are picked, so what is deleted is always a subset
|
|
231
|
+
// of what base would delete on the same events.
|
|
176
232
|
const candidates = [];
|
|
177
233
|
for (const unit of named) {
|
|
178
234
|
if (shouldStop?.()) return incomplete();
|
|
@@ -186,20 +242,28 @@ function selectExpired(events , { now, windowMs, shouldStop } = {})
|
|
|
186
242
|
if (eligible) candidates.push({ ...unit, newestAt: newest });
|
|
187
243
|
}
|
|
188
244
|
const full = finalize(state);
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
245
|
+
/** The refold-safe subset of `list`: all of it when that holds, else the oldest-first greedy pass. */
|
|
246
|
+
const safeSubset = (list ) => {
|
|
247
|
+
const allSafe = refoldEqual(events, full, list, now, shouldStop);
|
|
248
|
+
if (allSafe === null || shouldStop?.()) return null;
|
|
249
|
+
if (allSafe) return list;
|
|
250
|
+
const chosen = [];
|
|
251
|
+
const ordered = [...list].sort((a , b ) => Date.parse(a.newestAt) - Date.parse(b.newestAt));
|
|
252
|
+
if (shouldStop?.()) return null;
|
|
196
253
|
for (const unit of ordered) {
|
|
197
|
-
if (shouldStop?.()) return
|
|
198
|
-
const safe = refoldEqual(events, full, [...
|
|
199
|
-
if (safe === null) return
|
|
200
|
-
if (safe)
|
|
254
|
+
if (shouldStop?.()) return null;
|
|
255
|
+
const safe = refoldEqual(events, full, [...chosen, unit], now, shouldStop);
|
|
256
|
+
if (safe === null) return null;
|
|
257
|
+
if (safe) chosen.push(unit);
|
|
201
258
|
}
|
|
202
|
-
|
|
259
|
+
return chosen;
|
|
260
|
+
};
|
|
261
|
+
const baseSelected = safeSubset(candidates);
|
|
262
|
+
if (!baseSelected) return incomplete();
|
|
263
|
+
const unprotected = baseSelected.filter((unit ) => ![...unit.items].some((key ) => mentioned.has(key.slice(key.indexOf(':') + 1))));
|
|
264
|
+
// Dropping units can leave a set the refold no longer accepts; check it again, inside the subset.
|
|
265
|
+
const selected = unprotected.length === baseSelected.length ? baseSelected : safeSubset(unprotected);
|
|
266
|
+
if (!selected) return incomplete();
|
|
203
267
|
const units = [];
|
|
204
268
|
for (const unit of selected) {
|
|
205
269
|
if (shouldStop?.()) return incomplete();
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const fs = require('node:fs');
|
|
11
11
|
const path = require('node:path');
|
|
12
12
|
const { execFileSync } = require('node:child_process');
|
|
13
|
+
const { memoGit } = require('../git-memo.js');
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
|
|
@@ -61,11 +62,18 @@ function showToplevel(cwd ) {
|
|
|
61
62
|
* @returns {string} an absolute, real path
|
|
62
63
|
*/
|
|
63
64
|
function projectRoot(cwd = process.cwd()) {
|
|
65
|
+
// Inside a call scope (git-memo.ts) the answer git gave for this path is kept for the call. The
|
|
66
|
+
// fallback `real(cwd)` is not git's answer, so it is never kept: the path may become a repository.
|
|
67
|
+
return memoGit(`projectRoot:${path.resolve(cwd)}`, () => gitProjectRoot(cwd)) ?? real(cwd);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The root git names for `cwd`, or undefined when git names none (outside a repository). */
|
|
71
|
+
function gitProjectRoot(cwd ) {
|
|
64
72
|
let entries = [];
|
|
65
73
|
try { entries = worktreeEntries(git(cwd, ['worktree', 'list', '--porcelain'])); } catch { /* not a repository */ }
|
|
66
74
|
const first = entries[0];
|
|
67
75
|
if (first && !first.bare && isRealWorkTree(first)) return real(first.path);
|
|
68
|
-
return showToplevel(cwd) ||
|
|
76
|
+
return showToplevel(cwd) || undefined;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
module.exports = { projectRoot, worktreeEntries };
|
|
@@ -11,19 +11,20 @@
|
|
|
11
11
|
* the release tags and the base tag) come from `do-git-state.sh --lifecycle`, so bash and Node never
|
|
12
12
|
* derive them twice. The rest is a fixed number of plain `git` calls, however many features are
|
|
13
13
|
* tracked: one `rev-parse` pins the ref, one `for-each-ref` lists the branches, one
|
|
14
|
-
* `git log --first-parent --merges` lists the merges
|
|
15
|
-
* release tags' commits, and one `git rev-list --parents`
|
|
16
|
-
* and those tags. Every ancestry question (which merge
|
|
17
|
-
* merge) is then answered from that graph in memory,
|
|
14
|
+
* `git log --first-parent --merges` lists the merges and their `DoFlow-Row` trailers, one
|
|
15
|
+
* `git tag --list` (tagged mode) gives the release tags' commits, and one `git rev-list --parents`
|
|
16
|
+
* reads the commit graph of the pinned ref and those tags. Every ancestry question (which merge
|
|
17
|
+
* brought a branch tip in, which tag contains a merge) is then answered from that graph in memory,
|
|
18
|
+
* not by a git call per feature.
|
|
18
19
|
*
|
|
19
20
|
* Nothing here writes, fetches or reaches a network.
|
|
20
21
|
*
|
|
21
22
|
* Accepted ceilings, stated rather than hidden: a feature's merge evidence is only as good as the
|
|
22
|
-
* merge commit's subject and the branch name (a squash, a rebase and a
|
|
23
|
-
* so they reach the user through `notDetected` and `lifecycle --action
|
|
24
|
-
* the graph are read in full, without `--since` or a lower cut (a skewed
|
|
25
|
-
* merge), so their cost grows with the history of the integration ref, once
|
|
26
|
-
* have one-second resolution, so the lower bound is floored to the second.
|
|
23
|
+
* merge commit's `DoFlow-Row` trailer or subject and the branch name (a squash, a rebase and a
|
|
24
|
+
* fast-forward leave none, so they reach the user through `notDetected` and `lifecycle --action
|
|
25
|
+
* merged`); the merge log and the graph are read in full, without `--since` or a lower cut (a skewed
|
|
26
|
+
* clock must not hide a merge), so their cost grows with the history of the integration ref, once
|
|
27
|
+
* per call; git dates have one-second resolution, so the lower bound is floored to the second.
|
|
27
28
|
*/
|
|
28
29
|
|
|
29
30
|
const fs = require('node:fs');
|
|
@@ -69,13 +70,18 @@ function subjectNames(subject , slug ) {
|
|
|
69
70
|
return new RegExp(`(?:^|[^a-z0-9-])${escapeRegExp(slug)}(?:$|[^a-z0-9-])`).test(subject);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
/** First-parent merges of `ref`, oldest first. */
|
|
73
|
-
function readMerges(root , ref )
|
|
74
|
-
const out = git(root, ['log', '--first-parent', '--merges', '--format=%H%
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
/** First-parent merges of `ref`, oldest first, with any `DoFlow-Row` trailer. */
|
|
74
|
+
function readMerges(root , ref ) {
|
|
75
|
+
const out = git(root, ['log', '--first-parent', '--merges', '--format=%H%x00%P%x00%ct%x00%s%x00%(trailers:key=DoFlow-Row,separator=%x1f)%x00', ref]);
|
|
76
|
+
const fields = out.split('\0');
|
|
77
|
+
const merges = [];
|
|
78
|
+
for (let i = 0; i + 4 < fields.length; i += 5) {
|
|
79
|
+
const [rawSha, parents, ct, subject, rowTrailer] = fields.slice(i, i + 5);
|
|
80
|
+
const sha = rawSha.trim();
|
|
81
|
+
if (!sha) continue;
|
|
82
|
+
merges.push({ sha, parents: parents.split(' ').filter(Boolean), ct: Number(ct), subject, rowTrailer: rowTrailer.trim() });
|
|
83
|
+
}
|
|
84
|
+
return merges.reverse();
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
/** Branch refs by short name (`feat/x`, `origin/feat/x`) with their tips; local branches first. */
|
|
@@ -221,7 +227,18 @@ function findEvidence({ slug, lowerBound, merges, mergeBySha, newestCt, history,
|
|
|
221
227
|
if (merge) return { kind: 'branch', ref: tip.short, commit: merge.sha };
|
|
222
228
|
}
|
|
223
229
|
for (let i = merges.length - 1; i >= 0; i -= 1) {
|
|
224
|
-
|
|
230
|
+
const merge = merges[i];
|
|
231
|
+
if (merge.ct < lowerBound) continue;
|
|
232
|
+
if (merge.rowTrailer) {
|
|
233
|
+
// A present DoFlow-Row is authoritative: it can match only on an exact two-parent merge,
|
|
234
|
+
// and a mismatch must not fall through to a subject that names a different row.
|
|
235
|
+
const match = /^DoFlow-Row:\s*(.*?)\s*$/i.exec(merge.rowTrailer);
|
|
236
|
+
if (merge.parents.length === 2 && match && match[1] === slug) {
|
|
237
|
+
return { kind: 'trailer', ref: merge.sha.slice(0, 7), commit: merge.sha };
|
|
238
|
+
}
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (subjectNames(merge.subject, slug)) return { kind: 'merge-subject', ref: merge.sha.slice(0, 7), commit: merge.sha };
|
|
225
242
|
}
|
|
226
243
|
if (confirmed) return { kind: 'confirmed', ref: null, commit: null };
|
|
227
244
|
return null;
|
|
@@ -36,8 +36,18 @@ const LEGACY_EVENTS_REL = path.join(LEGACY_LIFECYCLE_REL, 'events');
|
|
|
36
36
|
const MARKER_REL = path.join(LIFECYCLE_REL, 'migrated.json');
|
|
37
37
|
const MIGRATING_PREFIX = 'events.migrating-';
|
|
38
38
|
const NOTICE = 'note: the lifecycle store is now .doflow/state/lifecycle/events; agent-docs/lifecycle/ is no longer read and can be deleted';
|
|
39
|
-
const removedLine = (count , hours ) => `retention: removed ${count} event files older than ${hours} h`;
|
|
40
|
-
|
|
39
|
+
const removedLine = (count , hours ) => `retention: removed ${count} event ${count === 1 ? 'file' : 'files'} older than ${hours} h`;
|
|
40
|
+
// A leading zero is rejected on purpose (`024` could be read as octal); the warning names it.
|
|
41
|
+
const invalidLine = (raw ) => `warning: DOFLOW_RETENTION_HOURS='${raw}' is not a positive whole number of hours${/^0[0-9]+$/.test(String(raw)) ? ' (no leading zeros)' : ''}; ignored, nothing removed`;
|
|
42
|
+
/**
|
|
43
|
+
* What `writeAtomic` leaves behind when a verb dies between creating its temp file and renaming it:
|
|
44
|
+
* `.retention-<pid>-<chars>.tmp` (the journal) and `.migrated-<pid>-<chars>.tmp` (the marker), in the
|
|
45
|
+
* lifecycle folder. The storage engine's work-store sweep matches `<name>.json.<pid>.tmp` inside
|
|
46
|
+
* record stores only, so nothing else ever removes them.
|
|
47
|
+
*/
|
|
48
|
+
const WRITE_TMP = /^\.(?:retention|migrated)-[0-9]+-[A-Za-z0-9]+\.tmp$/;
|
|
49
|
+
/** The age past which such a file is abandoned; the storage engine passes `settings.staleTmpHours`, and a test pins this to it. */
|
|
50
|
+
const STALE_TMP_MS = 24 * 3600000;
|
|
41
51
|
/** Bounds one pass, so its journal stays far below the reader's cap and its lock is held briefly. */
|
|
42
52
|
const MAX_PASS_FILES = 20000;
|
|
43
53
|
/** Unlinks between two refreshes of the lock's age. */
|
|
@@ -100,7 +110,8 @@ function fsyncCopy(fsImpl , file ) {
|
|
|
100
110
|
function fsyncFolder(fsImpl , dir ) {
|
|
101
111
|
let fd;
|
|
102
112
|
try { fd = fsImpl.openSync(dir, 'r'); } catch { return; }
|
|
103
|
-
|
|
113
|
+
// Windows opens a folder but refuses to flush it (EPERM); there is nothing to flush there.
|
|
114
|
+
try { fsImpl.fsyncSync(fd); } catch (error ) { if (!['EPERM', 'EINVAL', 'EISDIR', 'ENOTSUP'].includes(error?.code)) throw error; } finally { fsImpl.closeSync(fd); }
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
const migrationFailed = (code ) => ({
|
|
@@ -208,6 +219,30 @@ function refreshLock(fsImpl , root ) {
|
|
|
208
219
|
try { fsImpl.utimesSync(`${lockTarget(root)}.lock`, now, now); } catch { /* the lock folder is ours; a failed touch only shortens its life */ }
|
|
209
220
|
}
|
|
210
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Removes temp files a crashed writer left in the lifecycle folder, once they are older than
|
|
224
|
+
* `staleMs`. Runs under the store lock, which every writer of these files holds, so none of them
|
|
225
|
+
* belongs to a live pass unless its lock was taken over as stale. Best effort: a file that cannot
|
|
226
|
+
* be removed is left for the next pass. `shouldStop` is checked before each unlink and each unlink
|
|
227
|
+
* counts against `maxFiles`, so a bounded caller's budget and stop hold here too.
|
|
228
|
+
* @returns {{removed: number, partial: boolean}} partial: stopped or out of budget with files left
|
|
229
|
+
*/
|
|
230
|
+
function sweepStaleTmp(fsImpl , root , now , staleMs = STALE_TMP_MS, { maxFiles = Infinity, shouldStop } = {}) {
|
|
231
|
+
const dir = path.join(root, LIFECYCLE_REL);
|
|
232
|
+
let list ;
|
|
233
|
+
try { list = namesIn(fsImpl, dir); } catch { return { removed: 0, partial: false }; }
|
|
234
|
+
let removed = 0;
|
|
235
|
+
for (const name of list) {
|
|
236
|
+
if (!WRITE_TMP.test(name)) continue;
|
|
237
|
+
const file = path.join(dir, name);
|
|
238
|
+
const st = lstatOrNull(fsImpl, file);
|
|
239
|
+
if (!st || !st.isFile() || now.getTime() - st.mtimeMs <= staleMs) continue;
|
|
240
|
+
if (shouldStop?.() || removed >= maxFiles) return { removed, partial: true };
|
|
241
|
+
try { fsImpl.unlinkSync(file); removed += 1; } catch { /* left for the next pass */ }
|
|
242
|
+
}
|
|
243
|
+
return { removed, partial: false };
|
|
244
|
+
}
|
|
245
|
+
|
|
211
246
|
/** Unlinks pending event files without clearing the journal until every hidden name is finished. */
|
|
212
247
|
function removePending(fsImpl , root , names , onRemoved = () => {}, { maxFiles = Infinity, shouldStop } = {}) {
|
|
213
248
|
const dir = path.join(root, EVENTS_REL);
|
|
@@ -294,7 +329,7 @@ function retain(fsImpl , root , window , now , { maxFiles = MAX_
|
|
|
294
329
|
|
|
295
330
|
/** Runs a bounded lifecycle retention pass under a non-waiting store lock for the storage engine. */
|
|
296
331
|
function retainLocked(root , options = {}) {
|
|
297
|
-
const { window, now = new Date(), fsImpl = nodeFs, maxFiles = MAX_PASS_FILES, shouldStop } = options;
|
|
332
|
+
const { window, now = new Date(), fsImpl = nodeFs, maxFiles = MAX_PASS_FILES, shouldStop, staleTmpMs = STALE_TMP_MS } = options;
|
|
298
333
|
const bounded = Object.hasOwn(options, 'maxFiles') || Object.hasOwn(options, 'shouldStop');
|
|
299
334
|
const outcome = (removed , warning , partial = false, selected = [], code ) => bounded
|
|
300
335
|
? { removed, warning, partial, selected, ...(code ? { code } : {}) }
|
|
@@ -306,7 +341,9 @@ function retainLocked(root , options = {}) {
|
|
|
306
341
|
const release = tryAcquireLock(fsImpl, lockTarget(root));
|
|
307
342
|
if (!release) return outcome(0, [], true);
|
|
308
343
|
try {
|
|
309
|
-
const
|
|
344
|
+
const swept = sweepStaleTmp(fsImpl, root, now, staleTmpMs, { maxFiles, shouldStop });
|
|
345
|
+
if (swept.partial) return outcome(0, [], true);
|
|
346
|
+
const result = retain(fsImpl, root, window, now, { maxFiles: Math.max(0, maxFiles - swept.removed), shouldStop });
|
|
310
347
|
return outcome(result.removedCount, result.warning, result.partial, result.selected, result.code);
|
|
311
348
|
} finally {
|
|
312
349
|
release();
|
|
@@ -326,7 +363,7 @@ function needsPrune(fsImpl , root , window , now ) {
|
|
|
326
363
|
* @param {string} root projectRoot() result
|
|
327
364
|
* @param {{env?: Object, now?: Date, fsImpl?: Object}} [options] defaults process.env, new Date(), node:fs
|
|
328
365
|
* @returns {{ok: true, lines: string[]}
|
|
329
|
-
* | {ok: false, finding: 'store-migration-failed'|'store-locked', message: string, lines: string[]}}
|
|
366
|
+
* | {ok: false, finding: 'store-migration-pending'|'store-migration-failed'|'store-locked', message: string, lines: string[]}}
|
|
330
367
|
* `lines` are for stderr, without the verb prefix.
|
|
331
368
|
* @throws {StoreUnsafeError} a symlinked new store folder, or a symlinked old folder when a copy must read it
|
|
332
369
|
*/
|
|
@@ -345,6 +382,17 @@ function prepareStore(root , { env = process.env, now = new Date(), fsImp
|
|
|
345
382
|
try { migrate = legacyEventFiles(fsImpl, root).length > 0; } catch (error ) { return legacyUnreadable(error.code || error.message); }
|
|
346
383
|
}
|
|
347
384
|
}
|
|
385
|
+
if (env.DOFLOW_UPKEEP === 'off') {
|
|
386
|
+
if (migrate) {
|
|
387
|
+
return {
|
|
388
|
+
ok: false,
|
|
389
|
+
finding: 'store-migration-pending',
|
|
390
|
+
message: 'the lifecycle store has not been copied yet; run any followup, lifecycle or goal command once without DOFLOW_UPKEEP=off. Nothing was written.',
|
|
391
|
+
lines: [],
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return { ok: true, lines: [...notice, ...invalid] };
|
|
395
|
+
}
|
|
348
396
|
const retentionDue = readJournal(root, fsImpl).pending.length > 0 || needsPrune(fsImpl, root, window, now);
|
|
349
397
|
if (!migrate && !markerDue && !retentionDue) return { ok: true, lines: [...notice, ...invalid] };
|
|
350
398
|
if (migrate) refuseLinks(fsImpl, root, [LEGACY_LIFECYCLE_REL, LEGACY_EVENTS_REL]);
|
|
@@ -367,6 +415,7 @@ function prepareStore(root , { env = process.env, now = new Date(), fsImp
|
|
|
367
415
|
throw error;
|
|
368
416
|
}
|
|
369
417
|
try {
|
|
418
|
+
sweepStaleTmp(fsImpl, root, now);
|
|
370
419
|
// Decided again under the lock: another verb may have finished the copy while this one waited.
|
|
371
420
|
let marked = hasMarker(fsImpl, root);
|
|
372
421
|
if (!marked && storeHasEvents(fsImpl, root)) {
|
|
@@ -390,4 +439,4 @@ function prepareStore(root , { env = process.env, now = new Date(), fsImp
|
|
|
390
439
|
}
|
|
391
440
|
}
|
|
392
441
|
|
|
393
|
-
module.exports = { prepareStore, retainLocked, LEGACY_LIFECYCLE_REL, LEGACY_EVENTS_REL, MARKER_REL };
|
|
442
|
+
module.exports = { prepareStore, retainLocked, removedLine, STALE_TMP_MS, LEGACY_LIFECYCLE_REL, LEGACY_EVENTS_REL, MARKER_REL };
|