@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
|
@@ -6,14 +6,15 @@ const fs = require('node:fs');
|
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const featureResolve = require('./feature-resolve.js');
|
|
8
8
|
|
|
9
|
-
// Which feature a task record belongs to (feature 045
|
|
9
|
+
// Which feature a task record belongs to (feature 045 IC-001/IC-002, 115 IC-004).
|
|
10
10
|
//
|
|
11
11
|
// Task ids are short and repeat across features (`A.1` exists in every feature that has a Phase A),
|
|
12
|
-
// and the stores key records by the bare id.
|
|
13
|
-
//
|
|
14
|
-
// register
|
|
15
|
-
// and a
|
|
16
|
-
//
|
|
12
|
+
// and the stores key records by the bare id. Every task record therefore lives under a subdirectory
|
|
13
|
+
// named for its feature whenever a feature slug resolves (`--slug`, else the branch's), with or
|
|
14
|
+
// without a decision register and wherever the feature folder is; a task id that is the feature's
|
|
15
|
+
// own slug, and a call with no feature at all, keep the flat layout. Records written flat before
|
|
16
|
+
// this rule stay readable in place: a namespaced read falls back to the flat file when it is this
|
|
17
|
+
// feature's or names no feature, and the first write lands in the namespace (D-3). Nothing is moved.
|
|
17
18
|
|
|
18
19
|
const SAFE_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
19
20
|
const SLUG_RULE = 'use letters, digits, dot, underscore or dash, start with a letter or digit, and no ".."';
|
|
@@ -34,19 +35,29 @@ function invalidSlugRefusal(slug )
|
|
|
34
35
|
return { error: 'invalid-slug', message: `slug "${slug}" is not a valid feature slug: ${SLUG_RULE}`, hint: SLUG_RULE };
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
-
const featureCache = new Map
|
|
38
|
+
/** projectRoot -> the branch's feature slug or null. One resolver spawn per root per process. */
|
|
39
|
+
const featureCache = new Map ();
|
|
39
40
|
|
|
40
41
|
/** The resolver's raw answer, including thrown failures, shared by every consumer in this process. */
|
|
41
42
|
const resolvedFeatureCache = new Map ();
|
|
42
43
|
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
44
|
+
/** The record slug of the CLI call in progress (115 C-1): `--slug`, else the feature of the checkout
|
|
45
|
+
* the verb runs in, computed once by the dispatcher (a function is called on first use, then kept)
|
|
46
|
+
* and used by every store the call touches. `undefined` means no dispatcher call is in progress, and
|
|
47
|
+
* a store resolves the branch of its own project root. */
|
|
48
|
+
let callSlug = undefined;
|
|
47
49
|
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
+
function setRecordSlug(slug ) {
|
|
51
|
+
callSlug = slug === undefined || typeof slug === 'function' ? slug : (isSafeSlug(slug) ? slug : null);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function currentCallSlug() {
|
|
55
|
+
if (typeof callSlug === 'function') {
|
|
56
|
+
let value = null;
|
|
57
|
+
try { value = callSlug(); } catch { /* a failed lookup routes nothing to a guessed feature */ }
|
|
58
|
+
callSlug = isSafeSlug(value) ? value : null;
|
|
59
|
+
}
|
|
60
|
+
return callSlug ;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
function clearTaskScopeCache() {
|
|
@@ -68,6 +79,12 @@ function resolveFeature({ projectRoot, slug = null }
|
|
|
68
79
|
try {
|
|
69
80
|
const value = featureResolve.resolveActiveFeature({ projectRoot, slug: slug || null });
|
|
70
81
|
resolvedFeatureCache.set(key, { value });
|
|
82
|
+
// The branch's answer is also the answer for its own slug named explicitly: one spawn serves both.
|
|
83
|
+
const named = !slug && !value?.error ? value?.paths?.feature_slug : null;
|
|
84
|
+
if (isSafeSlug(named)) {
|
|
85
|
+
const namedKey = `${path.resolve(projectRoot)}\0${named}`;
|
|
86
|
+
if (!resolvedFeatureCache.has(namedKey)) resolvedFeatureCache.set(namedKey, { value });
|
|
87
|
+
}
|
|
71
88
|
return value;
|
|
72
89
|
} catch (error) {
|
|
73
90
|
resolvedFeatureCache.set(key, { error });
|
|
@@ -75,73 +92,120 @@ function resolveFeature({ projectRoot, slug = null }
|
|
|
75
92
|
}
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
/**
|
|
79
|
-
*
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
if (fs.statSync(path.join(dir, 'agent-docs', 'doflow')).isDirectory()) return true;
|
|
86
|
-
} catch { /* not here */ }
|
|
87
|
-
const parent = path.dirname(dir);
|
|
88
|
-
if (parent === dir) return false;
|
|
89
|
-
dir = parent;
|
|
95
|
+
/** Whether a branch or a feature folder could name a feature here: a `.git` or an
|
|
96
|
+
* `agent-docs/doflow` above the root. With neither the resolver has nothing to read and is skipped. */
|
|
97
|
+
function mayNameFeature(projectRoot ) {
|
|
98
|
+
for (let dir = path.resolve(projectRoot); ; dir = path.dirname(dir)) {
|
|
99
|
+
if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'agent-docs', 'doflow'))) return true;
|
|
100
|
+
if (path.dirname(dir) === dir) return false;
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
/** The feature slug the resolver gives for a root's branch (or its one feature folder), else null. */
|
|
105
|
+
function branchSlugFor(projectRoot ) {
|
|
106
|
+
const key = path.resolve(projectRoot);
|
|
107
|
+
if (featureCache.has(key)) return featureCache.get(key) ;
|
|
108
|
+
let value = null;
|
|
97
109
|
try {
|
|
98
|
-
if (
|
|
99
|
-
const found = resolveFeature({ projectRoot
|
|
100
|
-
if (!found.error && found.paths && isSafeSlug(found.paths.feature_slug))
|
|
101
|
-
value = { slug: found.paths.feature_slug, hasRegister: found.paths.has_decisions === true, exists: fs.existsSync(found.featureDir) };
|
|
102
|
-
}
|
|
110
|
+
if (mayNameFeature(projectRoot)) {
|
|
111
|
+
const found = resolveFeature({ projectRoot });
|
|
112
|
+
if (!found.error && found.paths && isSafeSlug(found.paths.feature_slug)) value = found.paths.feature_slug;
|
|
103
113
|
}
|
|
104
114
|
} catch { /* a resolver failure is "no feature", never an exception */ }
|
|
105
115
|
featureCache.set(key, value);
|
|
106
116
|
return value;
|
|
107
117
|
}
|
|
108
118
|
|
|
119
|
+
/**
|
|
120
|
+
* The feature a call's records are keyed by when no `--slug` is given (115 C-1), as
|
|
121
|
+
* `featureSlugFor` names it but through the cached resolver: a task id that names a feature folder in
|
|
122
|
+
* the checkout is its own feature, else the branch's.
|
|
123
|
+
* @param {{projectRoot: string, taskId?: string|null}} options
|
|
124
|
+
* @returns {string|null}
|
|
125
|
+
*/
|
|
126
|
+
function callerFeatureSlug({ projectRoot, taskId = null } ) {
|
|
127
|
+
if (isSafeSlug(taskId)) {
|
|
128
|
+
try { if (fs.statSync(path.join(projectRoot, 'agent-docs', 'doflow', taskId )).isDirectory()) return taskId ; } catch { /* not a folder */ }
|
|
129
|
+
}
|
|
130
|
+
return branchSlugFor(projectRoot);
|
|
131
|
+
}
|
|
132
|
+
|
|
109
133
|
/**
|
|
110
134
|
* @param {Object} options
|
|
111
135
|
* @param {string} options.projectRoot
|
|
112
136
|
* @param {string} options.taskId
|
|
113
|
-
* @param {string|null} [options.slug] explicit feature; otherwise the
|
|
137
|
+
* @param {string|null} [options.slug] explicit feature; otherwise the call's record slug, then the branch
|
|
114
138
|
* @returns {{namespace: string|null, slug: string|null, reason: string}}
|
|
115
|
-
* `reason` is `namespaced` when a namespace applies, else `no-feature
|
|
116
|
-
* `feature-level-id`.
|
|
139
|
+
* `reason` is `namespaced` when a namespace applies, else `no-feature` or `feature-level-id`.
|
|
117
140
|
*/
|
|
118
141
|
function resolveTaskScope({ projectRoot, taskId, slug = null } ) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
if (!feature
|
|
122
|
-
if (taskId === feature
|
|
123
|
-
return { namespace: feature
|
|
142
|
+
const call = slug ? undefined : currentCallSlug();
|
|
143
|
+
const feature = slug ? (isSafeSlug(slug) ? slug : null) : call !== undefined ? call : branchSlugFor(projectRoot);
|
|
144
|
+
if (!feature) return { namespace: null, slug: null, reason: 'no-feature' };
|
|
145
|
+
if (taskId === feature) return { namespace: null, slug: feature, reason: 'feature-level-id' };
|
|
146
|
+
return { namespace: feature, slug: feature, reason: 'namespaced' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The largest record file `readRegularFileText` reads; a bigger one is refused (a record is a few KiB). */
|
|
150
|
+
const MAX_RECORD_FILE_BYTES = 1024 * 1024;
|
|
151
|
+
|
|
152
|
+
/** A record file's text, or null when it must not be read: not a regular file (a link, a FIFO, a
|
|
153
|
+
* device, a folder), larger than `MAX_RECORD_FILE_BYTES`, or any read error. The lifecycle reader's
|
|
154
|
+
* pattern (event-store.ts `readEventFile`): `lstat`, then `O_NOFOLLOW|O_NONBLOCK` so neither a link
|
|
155
|
+
* nor a FIFO swapped in after the `lstat` is followed or blocks, then `fstat` on the descriptor
|
|
156
|
+
* before reading. The one read every run-file probe and reader shares (FU-16c2wz). */
|
|
157
|
+
function readRegularFileText(file , fsImpl = fs) {
|
|
158
|
+
const flags = fs.constants;
|
|
159
|
+
let fd ;
|
|
160
|
+
try {
|
|
161
|
+
if (!fsImpl.lstatSync(file).isFile()) return null;
|
|
162
|
+
fd = fsImpl.openSync(file, flags.O_RDONLY | (flags.O_NOFOLLOW || 0) | (flags.O_NONBLOCK || 0));
|
|
163
|
+
const st = fsImpl.fstatSync(fd);
|
|
164
|
+
if (!st.isFile() || st.size > MAX_RECORD_FILE_BYTES) return null;
|
|
165
|
+
// Read at most one byte past the cap, so a file that grew after the fstat is still refused.
|
|
166
|
+
const buffer = Buffer.alloc(MAX_RECORD_FILE_BYTES + 1);
|
|
167
|
+
let length = 0;
|
|
168
|
+
for (let n = -1; n !== 0 && length <= MAX_RECORD_FILE_BYTES; length += n) {
|
|
169
|
+
n = fsImpl.readSync(fd, buffer, length, buffer.length - length, null);
|
|
170
|
+
}
|
|
171
|
+
return length > MAX_RECORD_FILE_BYTES ? null : buffer.subarray(0, length).toString('utf8');
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
} finally {
|
|
175
|
+
if (fd !== undefined) { try { fsImpl.closeSync(fd); } catch { /* the read is done */ } }
|
|
176
|
+
}
|
|
124
177
|
}
|
|
125
178
|
|
|
126
179
|
/**
|
|
127
|
-
* Whether
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
180
|
+
* Whether a flat record may be read as `slug`'s (D-3): it names that feature (`featureSlug` on a
|
|
181
|
+
* run, `slug` on a readiness record) or names none. A file that does not parse names nobody, so a
|
|
182
|
+
* namespaced read never falls back to it. The probe never blocks and follows no link: a flat file
|
|
183
|
+
* that is not a regular file of at most `MAX_RECORD_FILE_BYTES` is not read; `unsafeIsOurs` is then
|
|
184
|
+
* the answer, so a caller whose reader refuses such a file (the run store's `readRunRecord`) is
|
|
185
|
+
* pointed at it and reports it, and any other caller treats it as not ours.
|
|
186
|
+
* @param {string} file
|
|
187
|
+
* @param {string|null} slug
|
|
188
|
+
* @param {Object} [fsImpl]
|
|
189
|
+
* @param {boolean} [unsafeIsOurs]
|
|
133
190
|
* @returns {boolean}
|
|
134
191
|
*/
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
192
|
+
function flatRecordIsOurs(file , slug , fsImpl = fs, unsafeIsOurs = false) {
|
|
193
|
+
let data ;
|
|
194
|
+
try {
|
|
195
|
+
const text = typeof fsImpl.lstatSync === 'function' && typeof fsImpl.openSync === 'function'
|
|
196
|
+
? readRegularFileText(file, fsImpl) : fsImpl.readFileSync(file, 'utf8');
|
|
197
|
+
if (text === null) return unsafeIsOurs;
|
|
198
|
+
data = JSON.parse(text);
|
|
199
|
+
} catch { return false; }
|
|
200
|
+
const named = [data?.featureSlug, data?.slug].find((value) => typeof value === 'string' && value !== '');
|
|
201
|
+
return named === undefined || named === slug;
|
|
138
202
|
}
|
|
139
203
|
|
|
140
204
|
/**
|
|
141
|
-
* The directory a task's record file for one store
|
|
205
|
+
* The directory a task's record file for one store is written in (IC-002, 115 IC-004).
|
|
142
206
|
* @param {Object} options
|
|
143
207
|
* @param {string} options.projectRoot
|
|
144
|
-
* @param {string} options.store state subdirectory: evidence | research | outcome | retrieval
|
|
208
|
+
* @param {string} options.store state subdirectory: evidence | research | outcome | retrieval | ...
|
|
145
209
|
* @param {string} options.taskId
|
|
146
210
|
* @param {string|null} [options.slug]
|
|
147
211
|
* @returns {string}
|
|
@@ -152,4 +216,61 @@ function taskStoreDir({ projectRoot, store, taskId, slug = null }
|
|
|
152
216
|
return namespace ? path.join(base, namespace) : base;
|
|
153
217
|
}
|
|
154
218
|
|
|
155
|
-
|
|
219
|
+
/**
|
|
220
|
+
* One task record (115 C-1, D-3): `file`, where it is written (always the namespace when one
|
|
221
|
+
* applies); `read`, where it is read from (that file when it exists, else a flat file of the same
|
|
222
|
+
* name that `flatRecordIsOurs`); and `seed`, a `prepare` for the write lock that starts a namespaced
|
|
223
|
+
* file not written yet from that flat one, so a store that rebuilds from disk keeps what it read.
|
|
224
|
+
* @param {Object} options
|
|
225
|
+
* @param {string} options.projectRoot
|
|
226
|
+
* @param {string} options.store
|
|
227
|
+
* @param {string} options.taskId
|
|
228
|
+
* @param {string|null} [options.slug]
|
|
229
|
+
* @param {string} [options.name] the file name (default `<taskId>.json`)
|
|
230
|
+
* @param {Object} [options.fsImpl]
|
|
231
|
+
* @returns {{file: string, read: string, seed: () => void}}
|
|
232
|
+
*/
|
|
233
|
+
function taskRecord({ projectRoot, store, taskId, slug = null, name = null, fsImpl = fs } ) {
|
|
234
|
+
const scope = resolveTaskScope({ projectRoot, taskId, slug });
|
|
235
|
+
return recordIn({ base: path.join(projectRoot, '.doflow', 'state', store), namespace: scope.namespace, name: name || `${taskId}.json`, fsImpl });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* `taskRecord` for a store directory and a namespace already known (the orchestrator's run journal).
|
|
240
|
+
* @param {{base: string, namespace: string|null, name: string, fsImpl?: Object, refuseUnsafeFlat?: boolean}} options
|
|
241
|
+
* `refuseUnsafeFlat`: a flat file that is not a safely readable regular file is `read` (so the
|
|
242
|
+
* caller's safe reader refuses it by name) instead of being skipped
|
|
243
|
+
* @returns {{file: string, read: string, seed: () => void}}
|
|
244
|
+
*/
|
|
245
|
+
function recordIn({ base, namespace, name, fsImpl = fs, refuseUnsafeFlat = false } ) {
|
|
246
|
+
const file = path.join(base, ...(namespace ? [namespace] : []), name);
|
|
247
|
+
const flat = path.join(base, name);
|
|
248
|
+
const fallback = () => (namespace && !fsImpl.existsSync(file) && fsImpl.existsSync(flat)
|
|
249
|
+
&& flatRecordIsOurs(flat, namespace, fsImpl, refuseUnsafeFlat) ? flat : null);
|
|
250
|
+
const seed = () => {
|
|
251
|
+
const from = fallback();
|
|
252
|
+
// Required on use: the record mover (a declared writer of every task store) loads the storage registry.
|
|
253
|
+
if (from) require('./state-root.js').placeRecord(file, fsImpl.readFileSync(from), fsImpl);
|
|
254
|
+
};
|
|
255
|
+
return { file, read: fallback() || file, seed };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Whether an explicit, well-formed slug names no feature folder in any of the given checkouts. The
|
|
260
|
+
* records still go under that slug; a typo would start a namespace nobody reads, which is worth one
|
|
261
|
+
* line to the caller.
|
|
262
|
+
* @param {Object} options
|
|
263
|
+
* @param {Array<string>} options.projectRoots
|
|
264
|
+
* @param {string} options.slug
|
|
265
|
+
* @returns {boolean}
|
|
266
|
+
*/
|
|
267
|
+
function slugNamesNoFeature({ projectRoots, slug } ) {
|
|
268
|
+
return !projectRoots.some((projectRoot) => {
|
|
269
|
+
try {
|
|
270
|
+
const found = resolveFeature({ projectRoot, slug });
|
|
271
|
+
return !found.error && fs.existsSync(found.featureDir);
|
|
272
|
+
} catch { return false; }
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = { readRegularFileText, MAX_RECORD_FILE_BYTES, resolveFeature, resolveTaskScope, taskStoreDir, taskRecord, recordIn, flatRecordIsOurs, setRecordSlug, callerFeatureSlug, clearTaskScopeCache, isSafeSlug, invalidSlugRefusal, slugNamesNoFeature };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
// Task source records (feature 080, design C5, IC-013): what a task's declared `files:` looked like
|
|
6
|
+
// at claim, and how that compares with what they look like at resume.
|
|
7
|
+
//
|
|
8
|
+
// `measureSources` is a probe: it reads the filesystem, so the caller runs it before the ledger
|
|
9
|
+
// lock (claim before `mutate()`, resume before any lock). It starts no process: each file is hashed
|
|
10
|
+
// by 076's `sourceVersionFor` with a null tree state, which never runs git. `compareSources` is pure.
|
|
11
|
+
|
|
12
|
+
const fs = require('node:fs');
|
|
13
|
+
const path = require('node:path');
|
|
14
|
+
const { sourceVersionFor } = require('./source-version.js');
|
|
15
|
+
|
|
16
|
+
/** IC-013: a file larger than this is `unhashable`, `too-large`, and is never read. */
|
|
17
|
+
const MAX_HASH_BYTES = 1024 * 1024;
|
|
18
|
+
/** IC-013: only the first this-many `files:` entries are measured; more sets `sourcesTruncated`. */
|
|
19
|
+
const MAX_SOURCES = 64;
|
|
20
|
+
|
|
21
|
+
/** IC-013: `*`, `?`, whitespace or `(` anywhere, or a trailing `:<n>` / `:<n>-<n>` line annotation. */
|
|
22
|
+
const PATTERN = /[*?\s(]|:\d+(?:-\d+)?$/;
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/** The entry as resolved on disk: trimmed, leading `./` dropped (the IC-016 normalisation). */
|
|
47
|
+
function normalizeEntry(file ) {
|
|
48
|
+
return String(file).trim().replace(/^(\.\/)+/, '');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function exists(dir ) {
|
|
52
|
+
if (!dir) return false;
|
|
53
|
+
try {
|
|
54
|
+
return fs.statSync(dir).isDirectory();
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** One `lstat` per candidate root, in order; the first root holding the entry wins. */
|
|
61
|
+
function locate(entry , roots ) {
|
|
62
|
+
for (const root of roots) {
|
|
63
|
+
try {
|
|
64
|
+
return { root, stat: fs.lstatSync(path.resolve(root, entry)) };
|
|
65
|
+
} catch {
|
|
66
|
+
// Not under this root: try the next.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function measureOne(declared , roots ) {
|
|
73
|
+
const file = String(declared);
|
|
74
|
+
const entry = normalizeEntry(file);
|
|
75
|
+
if (PATTERN.test(entry)) return { file, kind: 'unverifiable', fileHash: null, why: 'pattern' };
|
|
76
|
+
|
|
77
|
+
const found = locate(entry, roots);
|
|
78
|
+
if (!found) return { file, kind: 'absent', fileHash: null };
|
|
79
|
+
const { root, stat } = found;
|
|
80
|
+
if (stat.isSymbolicLink()) return { file, kind: 'unhashable', fileHash: null, why: 'link' };
|
|
81
|
+
if (stat.isDirectory()) return { file, kind: 'unhashable', fileHash: null, why: 'directory' };
|
|
82
|
+
if (!stat.isFile()) return { file, kind: 'unhashable', fileHash: null, why: 'read-error' };
|
|
83
|
+
// The size check precedes any read: an oversized file is never opened.
|
|
84
|
+
if (stat.size > MAX_HASH_BYTES) return { file, kind: 'unhashable', fileHash: null, why: 'too-large' };
|
|
85
|
+
|
|
86
|
+
const fileHash = sourceVersionFor(root, { file: entry }, null).fileHash;
|
|
87
|
+
if (!fileHash) return { file, kind: 'unhashable', fileHash: null, why: 'read-error' };
|
|
88
|
+
return { file, kind: 'file', fileHash };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* IC-013: measures the task's first 64 `files:` entries, each resolved under `execWorktree` alone
|
|
93
|
+
* when one is given and exists (an entry absent there is `absent`), else under the `roots` (the
|
|
94
|
+
* `loadPlan` roots) in order where it exists. A missing `execWorktree` is reported as
|
|
95
|
+
* `execWorktreeMissing` and the roots are still measured.
|
|
96
|
+
*/
|
|
97
|
+
function measureSources(task , execWorktree , roots = []) {
|
|
98
|
+
const declared = Array.isArray(task?.files) ? task.files : [];
|
|
99
|
+
const execWorktreeMissing = Boolean(execWorktree) && !exists(execWorktree);
|
|
100
|
+
// An execution worktree that exists is the only place an entry is read: a file absent there is
|
|
101
|
+
// `absent` (a removal), never the main copy. Without one, or when it is gone, the plan roots serve.
|
|
102
|
+
const candidates = execWorktree && !execWorktreeMissing
|
|
103
|
+
? [path.resolve(execWorktree)]
|
|
104
|
+
: [...new Set(roots.map((root) => path.resolve(root)))];
|
|
105
|
+
const sources = declared.slice(0, MAX_SOURCES).map((file) => measureOne(file, candidates));
|
|
106
|
+
return {
|
|
107
|
+
sources,
|
|
108
|
+
sourcesTruncated: declared.length > MAX_SOURCES,
|
|
109
|
+
sourcesUnverifiable: sources.filter(opaque).map((s) => s.file),
|
|
110
|
+
execWorktreeMissing,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function opaque(s ) {
|
|
115
|
+
return s.kind === 'unhashable' || s.kind === 'unverifiable';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function changeOf(before , after ) {
|
|
119
|
+
if (before.kind === 'absent') return after.kind === 'absent' ? 'unchanged' : 'created';
|
|
120
|
+
if (after.kind === 'absent') return 'removed';
|
|
121
|
+
return before.fileHash === after.fileHash ? 'unchanged' : 'changed';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* IC-013: compares an attempt's recorded `sources` with a fresh measurement of the same entries.
|
|
126
|
+
* The whole task is `unverifiable` when the attempt has no `sources`, was truncated, the execution
|
|
127
|
+
* worktree is gone, a recorded entry is missing from `after`, or any entry is `unhashable` or
|
|
128
|
+
* `unverifiable` at either time.
|
|
129
|
+
*/
|
|
130
|
+
function compareSources(
|
|
131
|
+
before ,
|
|
132
|
+
after ,
|
|
133
|
+
) {
|
|
134
|
+
const unverifiable = { unverifiable: true, files: [] };
|
|
135
|
+
if (!before || !Array.isArray(before.sources) || before.sourcesTruncated) return unverifiable;
|
|
136
|
+
if (!after || after.execWorktreeMissing || after.sourcesTruncated) return unverifiable;
|
|
137
|
+
const now = new Map(after.sources.map((s) => [s.file, s]));
|
|
138
|
+
const files = [];
|
|
139
|
+
for (const was of before.sources) {
|
|
140
|
+
const is = now.get(was.file);
|
|
141
|
+
if (!is || opaque(was) || opaque(is)) return unverifiable;
|
|
142
|
+
files.push({ file: was.file, change: changeOf(was, is) });
|
|
143
|
+
}
|
|
144
|
+
return { unverifiable: false, files };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { MAX_HASH_BYTES, MAX_SOURCES, measureSources, compareSources };
|