@khoavu882/doflow 2.0.0-beta.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -3
- 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 +7 -17
- 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-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
- package/core/harnesses/kiro/hooks/session-start.sh +12 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
- 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 +65 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
- package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
- package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +72 -80
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +60 -33
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1542 -277
- package/core/registry/lifecycle.json +2837 -37
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +701 -30
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +154 -35
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +24 -23
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
- 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 +28 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +7 -9
- package/core/shared/skills/do-plan/SKILL.md +29 -19
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +226 -113
- package/src/adapters/claude/index.js +39 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +100 -22
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +294 -48
- 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 +38 -13
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +120 -0
- package/src/adapters/pi/index.js +48 -16
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +3 -2
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +3 -2
- package/src/cli/index.js +338 -33
- package/src/cli/runtime-commands.js +243 -52
- 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 +74 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +434 -86
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +686 -32
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +514 -0
- 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 +27 -12
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +221 -277
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +100 -0
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +1106 -245
- 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-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +41 -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 +77 -10
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +3 -3
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +127 -16
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +402 -73
- package/src/runtime/retrieval-record.js +293 -0
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +63 -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 +290 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +202 -56
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +440 -65
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- 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 -21
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
- 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 -5
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- 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 -210
- package/src/adapters/gemini/index.js +0 -390
package/src/runtime/cli.js
CHANGED
|
@@ -5,16 +5,18 @@
|
|
|
5
5
|
const crypto = require('node:crypto');
|
|
6
6
|
const fs = require('node:fs');
|
|
7
7
|
const path = require('node:path');
|
|
8
|
-
const { execFileSync } = require('node:child_process');
|
|
9
8
|
const { CapabilityRouter } = require('./capability-router.js');
|
|
10
|
-
const { EvidenceLedger
|
|
11
|
-
const { resolveLocator, describeResolution } = require('./locator-resolve.js');
|
|
9
|
+
const { EvidenceLedger } = require('./evidence-ledger.js');
|
|
12
10
|
const { ClaimsManager } = require('./claims.js');
|
|
13
|
-
const {
|
|
11
|
+
const { measureTreeState } = require('./source-version.js');
|
|
12
|
+
const { fileRootFor } = require('./freshness.js');
|
|
13
|
+
const { toEvidence, parseLocator, validateEvidenceItem, EVIDENCE_REFUSED_FIELDS } = require('./candidate.js');
|
|
14
|
+
const { sourceModeLookup } = require('./retrieval-plan.js');
|
|
14
15
|
const { evaluateTaskReadiness } = require('./readiness.js');
|
|
15
|
-
const { writeReadinessRecord } = require('./readiness-record.js');
|
|
16
|
+
const { writeReadinessRecord, readReadinessRecord } = require('./readiness-record.js');
|
|
17
|
+
const { resolveTaskScope } = require('./task-scope.js');
|
|
16
18
|
const { parseDeclaredScope } = require('./verification/scope-bound.js');
|
|
17
|
-
const { loadRegistry } = require('../registry/index.js');
|
|
19
|
+
const { loadRegistry, agentToolScopeView } = require('../registry/index.js');
|
|
18
20
|
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
19
21
|
// One definition of the exit and usage helpers (design §4.2); `finish` is the local spelling of `finishRuntime`.
|
|
20
22
|
const { finishRuntime: finish, usageError } = require('./cli-result.js');
|
|
@@ -30,9 +32,10 @@ function handleCapabilitiesCommand({ json = false, check = false, repoRoot }
|
|
|
30
32
|
const root = repoRoot || REPO_ROOT;
|
|
31
33
|
const router = new CapabilityRouter({ repoRoot: root });
|
|
32
34
|
const report = router.getAllCapabilitiesHealth(check);
|
|
35
|
+
const agentToolScope = agentToolScopeView(loadRegistry({ repoRoot: root, sources: 'skip' }));
|
|
33
36
|
|
|
34
37
|
if (json) {
|
|
35
|
-
console.log(JSON.stringify({ capabilities: report }, null, 2));
|
|
38
|
+
console.log(JSON.stringify({ capabilities: report, agentToolScope }, null, 2));
|
|
36
39
|
return;
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -60,7 +63,36 @@ function handleCapabilitiesCommand({ json = false, check = false, repoRoot }
|
|
|
60
63
|
);
|
|
61
64
|
}
|
|
62
65
|
console.log('═'.repeat(78));
|
|
63
|
-
console.log(`Mode: ${check ? 'Deep Smoke Check' : 'Fast Presence Check'} · Total Capabilities: ${report.length}
|
|
66
|
+
console.log(`Mode: ${check ? 'Deep Smoke Check' : 'Fast Presence Check'} · Total Capabilities: ${report.length}`);
|
|
67
|
+
console.log('\nAgent tool scope per harness (unenforced: no tool field is projected):');
|
|
68
|
+
for (const row of agentToolScope) console.log(` ${row.harness.padEnd(14)}${row.status === 'native' ? `native ${row.field}` : `unenforced ${row.reason}`}`);
|
|
69
|
+
console.log('');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Fills `profile` with the caller-stated inputs the task's last readiness record for this template
|
|
74
|
+
* holds and this call did not pass. Returns the profile keys it filled.
|
|
75
|
+
* @returns {Array<string>}
|
|
76
|
+
*/
|
|
77
|
+
function carriedInputs({ profile, taskClass, taskId, slug, state } ) {
|
|
78
|
+
const keyOf = { verificationPlan: 'verificationPlan', scope: 'scopeClear', invariants: 'invariants' };
|
|
79
|
+
let prior = null;
|
|
80
|
+
try {
|
|
81
|
+
const scope = resolveTaskScope({ projectRoot: state, taskId, slug });
|
|
82
|
+
const read = readReadinessRecord({ stateRoot: state, taskId, slug: scope.slug || slug || null });
|
|
83
|
+
if (read.status === 'found' && read.record && read.record.taskClass === taskClass) prior = read.record;
|
|
84
|
+
} catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const filled = [];
|
|
88
|
+
for (const [inputKey, profileKey] of Object.entries(keyOf)) {
|
|
89
|
+
const value = prior && prior.inputs && prior.inputs[inputKey];
|
|
90
|
+
if (profile[profileKey ] === undefined && typeof value === 'string' && value.trim() !== '') {
|
|
91
|
+
profile[profileKey ] = value;
|
|
92
|
+
filled.push(profileKey );
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return filled;
|
|
64
96
|
}
|
|
65
97
|
|
|
66
98
|
/**
|
|
@@ -90,7 +122,7 @@ function handleCapabilitiesCommand({ json = false, check = false, repoRoot }
|
|
|
90
122
|
* @returns {number} exit code: 0 once the evaluation is recorded, whatever its state
|
|
91
123
|
*/
|
|
92
124
|
function handleReadinessCommand({
|
|
93
|
-
taskClass = 'feature', taskId = 'default', json = false, repoRoot, stateRoot,
|
|
125
|
+
taskClass = 'feature', taskId = 'default', json = false, repoRoot, stateRoot, sourceRoot,
|
|
94
126
|
verificationPlan, scopeClear, invariants, userDecisionPending = false, mode = 'workflow', now, slug = null,
|
|
95
127
|
} = {}) {
|
|
96
128
|
// Two different roots, previously conflated into one. `root` locates the *registry* (the
|
|
@@ -107,11 +139,16 @@ function handleReadinessCommand({
|
|
|
107
139
|
if (typeof scopeClear === 'string' && scopeClear.trim() !== '') profile.scopeClear = scopeClear;
|
|
108
140
|
if (typeof invariants === 'string' && invariants.trim() !== '') profile.invariants = invariants;
|
|
109
141
|
if (userDecisionPending) profile.userDecisionPending = true;
|
|
142
|
+
// An input not passed this call is the one the task's last evaluation for this template stated, so
|
|
143
|
+
// a bare re-check after evidence changes does not discard the scope and plan a READY verdict rested
|
|
144
|
+
// on. Passing an input replaces it. Ceiling: an input can be replaced but not dropped, and
|
|
145
|
+
// `--user-decision-pending` is never carried.
|
|
146
|
+
const carried = carriedInputs({ profile, taskClass, taskId, slug, state });
|
|
110
147
|
const callerAsserted = Object.keys(profile).filter((k) => k !== 'taskId' && k !== 'taskClass');
|
|
111
148
|
|
|
112
149
|
let report;
|
|
113
150
|
try {
|
|
114
|
-
report = evaluateTaskReadiness({ taskProfile: profile, repoRoot: root, projectRoot: state, mode });
|
|
151
|
+
report = evaluateTaskReadiness({ taskProfile: profile, repoRoot: root, projectRoot: state, treeRoot: sourceRoot || state, sourceRoot: sourceRoot || state, mode });
|
|
115
152
|
} catch (error ) {
|
|
116
153
|
return usageError('readiness', error.message, json, error);
|
|
117
154
|
}
|
|
@@ -139,7 +176,7 @@ function handleReadinessCommand({
|
|
|
139
176
|
};
|
|
140
177
|
|
|
141
178
|
if (json) {
|
|
142
|
-
console.log(JSON.stringify({ ...report, callerAsserted, record: recorded, declaredScope }, null, 2));
|
|
179
|
+
console.log(JSON.stringify({ ...report, callerAsserted, carriedFromLastRecord: carried, record: recorded, declaredScope }, null, 2));
|
|
143
180
|
reportNotRecorded();
|
|
144
181
|
return finish(exitCode);
|
|
145
182
|
}
|
|
@@ -164,6 +201,7 @@ function handleReadinessCommand({
|
|
|
164
201
|
// Named, not hidden: these requirements were satisfied because the caller said so, and a
|
|
165
202
|
// verdict that mixes measured and stated inputs must say which is which.
|
|
166
203
|
console.log(`Caller-stated: ${callerAsserted.join(', ')} (asserted on the command line, not established by evidence)`);
|
|
204
|
+
if (carried.length > 0) console.log(`Carried: ${carried.join(', ')} (stated by the last evaluation of this task; pass the flag to replace it)`);
|
|
167
205
|
}
|
|
168
206
|
console.log('─'.repeat(70));
|
|
169
207
|
console.log('Requirements Breakdown:');
|
|
@@ -196,263 +234,23 @@ function handleReadinessCommand({
|
|
|
196
234
|
// every key it does not recognise, so a field this boundary forwards blindly is a field the caller
|
|
197
235
|
// watches vanish while the command reports success. Nothing reaches the ledger unnamed.
|
|
198
236
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
/** Kinds that record an execution and may carry a typed observation. */
|
|
206
|
-
const OBSERVATION_KINDS = new Set(['test-result', 'runtime-observation']);
|
|
207
|
-
|
|
208
|
-
/** Kinds that are by definition not a direct read of the repository. FR-007's second sentence. */
|
|
209
|
-
const NON_REPOSITORY_KINDS = new Set(['generated-analysis', 'user-statement']);
|
|
210
|
-
|
|
211
|
-
/** Fields a caller might plausibly send that this boundary refuses by name, with the reason.
|
|
212
|
-
* A named refusal beats the generic unknown-field error: each of these would otherwise be
|
|
213
|
-
* silently discarded by `addEvidence`, and a silent discard reads as a successful write. */
|
|
214
|
-
const EVIDENCE_REFUSED_FIELDS = new Map([
|
|
215
|
-
['id', 'evidence ids are minted by the ledger — a caller-chosen id can overwrite an unrelated record'],
|
|
216
|
-
['freshness', 'freshness is measured here from the working tree at write time, never asserted by the writer'],
|
|
217
|
-
['supports', 'link evidence to a claim with `claim --action link`, which updates both sides; a list written here would leave the claim unaware of it'],
|
|
218
|
-
['contradicts', 'link evidence to a claim with `claim --action link`, which updates both sides; a list written here would leave the claim unaware of it'],
|
|
219
|
-
['stage', 'the evidence record has no stage field — the batch *is* the stage boundary, so a stage name recorded here would be dropped'],
|
|
220
|
-
['status', "lifecycle status starts 'active' and changes only through `evidence --action supersede` — asserting it here would let a caller declare an item superseded without naming what replaces it"],
|
|
221
|
-
]);
|
|
222
|
-
|
|
223
|
-
/** The names a retrieval score arrives under, in camelCase (`--relevance-score` normalises here).
|
|
224
|
-
* Exported because `bin/doflow.js` refuses the same names on the command line, and because
|
|
225
|
-
* `retrieval-plan.ts` and `outcome.ts` refuse them on the option object a declared plan and a
|
|
226
|
-
* terminal record are built from: one definition, three input surfaces (argv, a `--batch` JSON
|
|
227
|
-
* file, and a verb's own options), each with exactly one enforcement point. A second copy of
|
|
228
|
-
* this list is how one of them drifts. */
|
|
229
|
-
const EVIDENCE_SCORE_FIELDS = new Set([
|
|
230
|
-
'confidence', 'score', 'relevance', 'relevanceScore', 'similarity',
|
|
231
|
-
'certainty', 'probability', 'rank', 'distance', 'weight',
|
|
232
|
-
]);
|
|
233
|
-
|
|
234
|
-
/** The one refusal message for a score offered as a property of a fact. */
|
|
235
|
-
function scoreFieldRefusal(name ) {
|
|
236
|
-
return `'${name}' is a retrieval score and this boundary records none (FR-007). Relevance is a `
|
|
237
|
-
+ 'property of a search, not of a fact: stored beside the fact it is read as confidence by the '
|
|
238
|
-
+ 'next worker, which is the specific interpretation the requirement forbids. Record the '
|
|
239
|
-
+ 'locator and the content instead — a reader who can see where a fact came from does not need '
|
|
240
|
-
+ 'to be told how sure to be.';
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** Refuses a score-shaped key anywhere in an item, including nested under source or locator. */
|
|
244
|
-
function assertNoScoreFields(value , prefix ) {
|
|
245
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return;
|
|
246
|
-
for (const [key, inner] of Object.entries(value)) {
|
|
247
|
-
const dotted = prefix ? `${prefix}.${key}` : key;
|
|
248
|
-
if (EVIDENCE_SCORE_FIELDS.has(key)) throw new Error(scoreFieldRefusal(dotted));
|
|
249
|
-
assertNoScoreFields(inner, dotted);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Normalises a locator given either as an object or as a `file[:line]` / URI string.
|
|
255
|
-
* @param {Object|string} raw
|
|
256
|
-
* @returns {Object}
|
|
257
|
-
*/
|
|
258
|
-
function parseLocator(raw ) {
|
|
259
|
-
if (typeof raw === 'string') {
|
|
260
|
-
const text = raw.trim();
|
|
261
|
-
if (text === '') throw new Error('locator is empty');
|
|
262
|
-
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(text)) return { uri: text };
|
|
263
|
-
const withLine = text.match(/^(.+):(\d+)$/);
|
|
264
|
-
if (withLine) return { file: withLine[1], line: Number(withLine[2]) };
|
|
265
|
-
return { file: text };
|
|
266
|
-
}
|
|
267
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
268
|
-
throw new Error("locator must be an object, or a 'path/to/file[:line]' or URI string");
|
|
269
|
-
}
|
|
270
|
-
const locator = {};
|
|
271
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
272
|
-
if (!EVIDENCE_LOCATOR_FIELDS.includes(key)) {
|
|
273
|
-
throw new Error(`unknown locator field '${key}'. Accepted: ${EVIDENCE_LOCATOR_FIELDS.join(', ')}`);
|
|
274
|
-
}
|
|
275
|
-
locator[key] = value;
|
|
276
|
-
}
|
|
277
|
-
if (locator.line !== undefined && (!Number.isInteger(locator.line) || locator.line < 1)) {
|
|
278
|
-
throw new Error(`locator.line must be a positive integer, got '${locator.line}'`);
|
|
279
|
-
}
|
|
280
|
-
for (const key of ['file', 'symbol', 'uri']) {
|
|
281
|
-
if (locator[key] !== undefined && (typeof locator[key] !== 'string' || locator[key].trim() === '')) {
|
|
282
|
-
throw new Error(`locator.${key} must be a non-empty string`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (!locator.file && !locator.uri) throw new Error('a locator needs at least a file or a uri');
|
|
286
|
-
return locator;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Validates one raw evidence item and returns the record fields the ledger will store.
|
|
291
|
-
*
|
|
292
|
-
* Throws on anything it cannot account for. Every rule here traces to FR-007 or to the
|
|
293
|
-
* vacuous-record family: a record with no statement and no locator asserts nothing, and storing it
|
|
294
|
-
* inflates the evidence count that the readiness gate then reads.
|
|
295
|
-
*
|
|
296
|
-
* @param {Object} raw
|
|
297
|
-
* @param {string} taskId the task the batch is being written to
|
|
298
|
-
* @param {string} [repoRoot] root a relative locator resolves against; defaults to cwd
|
|
299
|
-
* @returns {{kind:string, provenance:string, source:Object, locator:Object, content:(string|null)}}
|
|
300
|
-
*/
|
|
301
|
-
function validateEvidenceItem(raw , taskId , repoRoot ) {
|
|
302
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('an evidence item must be a JSON object');
|
|
303
|
-
assertNoScoreFields(raw, '');
|
|
304
|
-
|
|
305
|
-
for (const key of Object.keys(raw)) {
|
|
306
|
-
if (EVIDENCE_REFUSED_FIELDS.has(key)) throw new Error(`'${key}' is not accepted here: ${EVIDENCE_REFUSED_FIELDS.get(key)}`);
|
|
307
|
-
if (!EVIDENCE_ITEM_FIELDS.includes(key)) {
|
|
308
|
-
throw new Error(`unknown evidence field '${key}'. Accepted: ${EVIDENCE_ITEM_FIELDS.join(', ')}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (raw.taskId !== undefined && raw.taskId !== taskId) {
|
|
313
|
-
throw new Error(`item names task '${raw.taskId}' but this write targets '${taskId}' — one write, one task`);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const kinds = [...VALID_EVIDENCE_KINDS].sort().join(', ');
|
|
317
|
-
if (typeof raw.kind !== 'string' || raw.kind.trim() === '') throw new Error(`kind is required. Valid: ${kinds}`);
|
|
318
|
-
if (!VALID_EVIDENCE_KINDS.has(raw.kind)) throw new Error(`unknown evidence kind '${raw.kind}'. Valid: ${kinds}`);
|
|
319
|
-
|
|
320
|
-
const provenances = [...VALID_PROVENANCE].sort().join(', ');
|
|
321
|
-
if (typeof raw.provenance !== 'string' || raw.provenance.trim() === '') {
|
|
322
|
-
throw new Error('provenance is required and has no default here — defaulting it would file model '
|
|
323
|
-
+ `analysis as repository fact, the one distinction FR-007 makes binding. Valid: ${provenances}`);
|
|
324
|
-
}
|
|
325
|
-
if (!VALID_PROVENANCE.has(raw.provenance)) throw new Error(`unknown provenance '${raw.provenance}'. Valid: ${provenances}`);
|
|
326
|
-
if (NON_REPOSITORY_KINDS.has(raw.kind) && raw.provenance === 'extracted') {
|
|
327
|
-
throw new Error(`kind '${raw.kind}' cannot carry provenance 'extracted': it is not a read of the `
|
|
328
|
-
+ "repository, and labelling it as one is exactly how model analysis stops being distinguishable "
|
|
329
|
-
+ "from fact. Use 'inferred' (derived by analysis) or 'asserted' (stated by a person).");
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (raw.source === undefined) {
|
|
333
|
-
throw new Error('source is required: which provider answered, under which capability. '
|
|
334
|
-
+ "It has no default — 'unknown/general' is a plausible-looking answer to a question nobody asked.");
|
|
335
|
-
}
|
|
336
|
-
if (!raw.source || typeof raw.source !== 'object' || Array.isArray(raw.source)) {
|
|
337
|
-
throw new Error('source must be an object with provider and capability');
|
|
338
|
-
}
|
|
339
|
-
for (const key of Object.keys(raw.source)) {
|
|
340
|
-
if (!EVIDENCE_SOURCE_FIELDS.includes(key)) {
|
|
341
|
-
throw new Error(`unknown source field '${key}'. Accepted: ${EVIDENCE_SOURCE_FIELDS.join(', ')}`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
for (const key of EVIDENCE_SOURCE_FIELDS) {
|
|
345
|
-
if (typeof raw.source[key] !== 'string' || raw.source[key].trim() === '') {
|
|
346
|
-
throw new Error(`source.${key} is required and must be a non-empty string`);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const locator = raw.locator === undefined ? null : parseLocator(raw.locator);
|
|
351
|
-
// An item carrying an observation is anchored by the command it records — that command is what
|
|
352
|
-
// the next worker re-runs to check it — so the locator becomes optional there and only there.
|
|
353
|
-
if (raw.provenance === 'extracted' && !locator && raw.observation === undefined) {
|
|
354
|
-
throw new Error("provenance 'extracted' requires a locator — a fact read from the repository "
|
|
355
|
-
+ 'must name where it was read, or the next worker cannot check it and cannot tell when it goes stale');
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// A locator that parses is not a locator that resolves (FR-004). Shape validation alone let an
|
|
359
|
-
// item naming line 799 of a 28-line file be recorded as support and counted by the readiness
|
|
360
|
-
// gate. The refusal names what the file actually offers, so the writer can correct it here rather
|
|
361
|
-
// than discover it at the gate.
|
|
362
|
-
if (raw.provenance === 'extracted' && locator) {
|
|
363
|
-
const resolution = resolveLocator({ locator, repoRoot });
|
|
364
|
-
if (!resolution.resolved) {
|
|
365
|
-
throw new Error(`the locator does not resolve: ${describeResolution(locator, resolution)}. `
|
|
366
|
-
+ "An 'extracted' item asserts a read of the repository, so a locator that points at nothing "
|
|
367
|
-
+ 'records a fact nobody can check');
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (raw.content !== undefined && typeof raw.content !== 'string') throw new Error('content must be a string');
|
|
372
|
-
const content = typeof raw.content === 'string' && raw.content.trim() !== '' ? raw.content : null;
|
|
373
|
-
if (raw.provenance !== 'extracted' && !content) {
|
|
374
|
-
throw new Error(`provenance '${raw.provenance}' requires content — an inferred or asserted item `
|
|
375
|
-
+ 'is the statement itself, so a record without one records nothing while still counting as evidence');
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Requirement bindings: which readiness-template requirements this item claims to establish.
|
|
379
|
-
// Declared by the writer, judged by the gate — the gate counts an item toward a requirement only
|
|
380
|
-
// when it is named here, so a structural item can no longer satisfy `architecture_mapped` and
|
|
381
|
-
// `blast_radius` at once by category alone (review R1).
|
|
382
|
-
let establishes = [];
|
|
383
|
-
if (raw.establishes !== undefined) {
|
|
384
|
-
const list = typeof raw.establishes === 'string' ? raw.establishes.split(',').map((s ) => s.trim()) : raw.establishes;
|
|
385
|
-
if (!Array.isArray(list) || list.length === 0 || list.some((r) => typeof r !== 'string' || r.trim() === '')) {
|
|
386
|
-
throw new Error("'establishes' must be a requirement id, a comma-separated list of them, or a non-empty array of non-empty strings");
|
|
387
|
-
}
|
|
388
|
-
establishes = list.map((s) => s.trim());
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Typed observation: the execution behind a test-result or runtime-observation. Command and exit
|
|
392
|
-
// status are the two facts the gate can actually grade; tree identity and observation time are
|
|
393
|
-
// measured into freshness at this same write. Only an 'extracted' item may carry one — an
|
|
394
|
-
// observation asserts an execution happened, and an inferred item is by definition not one.
|
|
395
|
-
let observation = null;
|
|
396
|
-
if (raw.observation !== undefined) {
|
|
397
|
-
if (!raw.observation || typeof raw.observation !== 'object' || Array.isArray(raw.observation)) {
|
|
398
|
-
throw new Error('observation must be an object with command and exitCode');
|
|
399
|
-
}
|
|
400
|
-
for (const key of Object.keys(raw.observation)) {
|
|
401
|
-
if (!EVIDENCE_OBSERVATION_FIELDS.includes(key)) {
|
|
402
|
-
throw new Error(`unknown observation field '${key}'. Accepted: ${EVIDENCE_OBSERVATION_FIELDS.join(', ')}`);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (typeof raw.observation.command !== 'string' || raw.observation.command.trim() === '') {
|
|
406
|
-
throw new Error('observation.command is required: the exact command whose exit status is being recorded');
|
|
407
|
-
}
|
|
408
|
-
if (!Number.isInteger(raw.observation.exitCode) || raw.observation.exitCode < 0) {
|
|
409
|
-
throw new Error(`observation.exitCode must be a non-negative integer, got '${raw.observation.exitCode}'`);
|
|
410
|
-
}
|
|
411
|
-
if (!OBSERVATION_KINDS.has(raw.kind)) {
|
|
412
|
-
throw new Error(`kind '${raw.kind}' cannot carry an observation — only ${[...OBSERVATION_KINDS].join(', ')} record an execution`);
|
|
413
|
-
}
|
|
414
|
-
if (raw.provenance !== 'extracted') {
|
|
415
|
-
throw new Error(`provenance '${raw.provenance}' cannot carry an observation: an observation asserts a command `
|
|
416
|
-
+ 'was actually run, which is a read of the world, not an inference or an assertion. '
|
|
417
|
-
+ "Either record the execution as 'extracted', or drop the observation.");
|
|
418
|
-
}
|
|
419
|
-
observation = { command: raw.observation.command.trim(), exitCode: raw.observation.exitCode };
|
|
420
|
-
}
|
|
421
|
-
if (raw.kind === 'test-result' && raw.provenance === 'extracted' && !observation) {
|
|
422
|
-
throw new Error("an 'extracted' test-result requires an observation ({command, exitCode}) — a test result "
|
|
423
|
-
+ 'without the command and exit status behind it is a claim about an execution nobody can check, '
|
|
424
|
-
+ 'and it is exactly how a failed baseline was once accepted as a passing one');
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return {
|
|
428
|
-
kind: raw.kind,
|
|
429
|
-
provenance: raw.provenance,
|
|
430
|
-
source: { provider: raw.source.provider, capability: raw.source.capability },
|
|
431
|
-
locator: locator || {},
|
|
432
|
-
content,
|
|
433
|
-
establishes,
|
|
434
|
-
observation,
|
|
435
|
-
};
|
|
436
|
-
}
|
|
237
|
+
const {
|
|
238
|
+
EVIDENCE_SCORE_FIELDS,
|
|
239
|
+
scoreFieldRefusal,
|
|
240
|
+
assertNoScoreFields,
|
|
241
|
+
} = require('./evidence-fields.js');
|
|
437
242
|
|
|
438
|
-
/** HEAD of the tree the evidence was observed in, or null outside a repo. Measured, not asserted. */
|
|
439
|
-
function headCommit(root ) {
|
|
440
|
-
try {
|
|
441
|
-
return execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], {
|
|
442
|
-
encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
443
|
-
}).trim() || null;
|
|
444
|
-
} catch {
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
243
|
|
|
449
244
|
|
|
450
245
|
/**
|
|
451
246
|
* Reads a stage's evidence batch: a JSON array, or `{ "evidence": [...] }`.
|
|
452
247
|
* @param {string} batchPath a file path, or `-` for stdin
|
|
248
|
+
* @param {string} taskId
|
|
249
|
+
* @param {string} root
|
|
250
|
+
* @param {string|null} [slug]
|
|
453
251
|
* @returns {Array<Object>}
|
|
454
252
|
*/
|
|
455
|
-
function readEvidenceBatch(batchPath ) {
|
|
253
|
+
function readEvidenceBatch(batchPath , taskId , root , slug = null) {
|
|
456
254
|
let text;
|
|
457
255
|
try {
|
|
458
256
|
text = batchPath === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(path.resolve(batchPath), 'utf8');
|
|
@@ -469,7 +267,9 @@ function readEvidenceBatch(batchPath ) {
|
|
|
469
267
|
if (data && typeof data === 'object') {
|
|
470
268
|
for (const key of Object.keys(data)) {
|
|
471
269
|
if (key === 'evidence') continue;
|
|
472
|
-
const
|
|
270
|
+
const refusedVal = EVIDENCE_REFUSED_FIELDS.get(key);
|
|
271
|
+
const reason = typeof refusedVal === 'function' ? refusedVal(taskId, root, slug) : refusedVal;
|
|
272
|
+
const named = reason ? ` — ${reason.endsWith('.') ? reason.slice(0, -1) : reason}` : '';
|
|
473
273
|
throw new Error(`unknown batch field '${key}'${named}. A batch is a JSON array of items, `
|
|
474
274
|
+ "or an object whose only key is 'evidence'");
|
|
475
275
|
}
|
|
@@ -479,11 +279,35 @@ function readEvidenceBatch(batchPath ) {
|
|
|
479
279
|
throw new Error('a batch must be a JSON array of evidence items, or an object with an `evidence` array');
|
|
480
280
|
}
|
|
481
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Refuses an evidence item whose file locator names a directory, whatever its provenance: a locator
|
|
284
|
+
* points into a file. An absence item has no locator, and a locator that does not parse is left to
|
|
285
|
+
* `toEvidence`, which names what is wrong with it.
|
|
286
|
+
* @throws {Error} naming the locator
|
|
287
|
+
*/
|
|
288
|
+
function refuseDirectoryLocator(raw , sourceRoot , root ) {
|
|
289
|
+
if (!raw || typeof raw !== 'object' || raw.absence || raw.locator === undefined) return;
|
|
290
|
+
let locator;
|
|
291
|
+
try {
|
|
292
|
+
locator = parseLocator(raw.locator);
|
|
293
|
+
} catch {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!locator.file) return;
|
|
297
|
+
let isDirectory = false;
|
|
298
|
+
try {
|
|
299
|
+
isDirectory = fs.statSync(path.resolve(fileRootFor(sourceRoot, root, locator), locator.file)).isDirectory();
|
|
300
|
+
} catch { /* missing or unreadable: the resolver judges that */ }
|
|
301
|
+
if (isDirectory) {
|
|
302
|
+
throw new Error(`the locator '${locator.file}' is a directory, and a directory is not a valid locator; name a file (path[:line])`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
482
306
|
/**
|
|
483
307
|
* The `--action add` arm: validate the whole batch, then write it.
|
|
484
308
|
* @returns {number} exit code
|
|
485
309
|
*/
|
|
486
|
-
function addEvidence({ ledger, root, taskId, item, batchPath, json } ) {
|
|
310
|
+
function addEvidence({ ledger, root, sourceRoot = root, attemptRoot, taskId, item, batchPath, json, slug = null } ) {
|
|
487
311
|
const flagged = item && Object.keys(item).length > 0;
|
|
488
312
|
if (flagged && batchPath) {
|
|
489
313
|
return usageError('evidence', '--batch and the single-item flags are two spellings of the same write; use one', json);
|
|
@@ -495,7 +319,7 @@ function addEvidence({ ledger, root, taskId, item, batchPath, json } ) {
|
|
|
495
319
|
|
|
496
320
|
let raws;
|
|
497
321
|
try {
|
|
498
|
-
raws = batchPath ? readEvidenceBatch(batchPath) : [item];
|
|
322
|
+
raws = batchPath ? readEvidenceBatch(batchPath, taskId, root, slug) : [item];
|
|
499
323
|
} catch (error ) {
|
|
500
324
|
return usageError('evidence', error.message, json, error);
|
|
501
325
|
}
|
|
@@ -506,26 +330,29 @@ function addEvidence({ ledger, root, taskId, item, batchPath, json } ) {
|
|
|
506
330
|
|
|
507
331
|
// The whole batch is validated before any of it is written. A half-written stage batch is worse
|
|
508
332
|
// than a rejected one: the half that landed looks complete to every later reader.
|
|
333
|
+
// The tree observed is the checkout the verb runs in (115 IC-007), one measurement for every item.
|
|
334
|
+
const treeState = measureTreeState(sourceRoot);
|
|
335
|
+
const providerFreshness = new Map();
|
|
336
|
+
let sourceModeOf = null;
|
|
509
337
|
const validated = [];
|
|
510
338
|
for (const [index, raw] of raws.entries()) {
|
|
511
339
|
try {
|
|
512
|
-
|
|
340
|
+
if (raw && raw.absence && !sourceModeOf) {
|
|
341
|
+
sourceModeOf = sourceModeLookup(new CapabilityRouter({ repoRoot: REPO_ROOT }).capabilities);
|
|
342
|
+
}
|
|
343
|
+
refuseDirectoryLocator(raw, sourceRoot, root);
|
|
344
|
+
validated.push(toEvidence({ item: raw }, { taskId, root, treeState, probeRoot: sourceRoot, sourceRoot, attemptRoot, slug, providerFreshness, sourceModeOf }));
|
|
513
345
|
} catch (error ) {
|
|
514
346
|
const where = raws.length > 1 ? `batch item ${index + 1} of ${raws.length}: ` : '';
|
|
515
347
|
return usageError('evidence', `${where}${error.message}`, json, error);
|
|
516
348
|
}
|
|
517
349
|
}
|
|
518
350
|
|
|
519
|
-
const gitCommit = headCommit(root);
|
|
520
351
|
const written = [];
|
|
521
352
|
let stateFile;
|
|
522
353
|
try {
|
|
523
354
|
for (const record of validated) {
|
|
524
|
-
const id = ledger.addEvidence(
|
|
525
|
-
...record,
|
|
526
|
-
taskId,
|
|
527
|
-
freshness: measureFreshness(root, record.locator, gitCommit),
|
|
528
|
-
});
|
|
355
|
+
const id = ledger.addEvidence(record);
|
|
529
356
|
written.push(ledger.getEvidence(id));
|
|
530
357
|
}
|
|
531
358
|
stateFile = ledger.save(taskId);
|
|
@@ -552,7 +379,7 @@ function addEvidence({ ledger, root, taskId, item, batchPath, json } ) {
|
|
|
552
379
|
}
|
|
553
380
|
console.log('─'.repeat(78));
|
|
554
381
|
console.log(`Wrote ${written.length} item(s) to ${stateFile}`);
|
|
555
|
-
console.log(`Commit at observation: ${gitCommit || 'not a git worktree — freshness cannot expire by diff'}`);
|
|
382
|
+
console.log(`Commit at observation: ${treeState.gitCommit || (treeState.prefix !== null ? 'not measured' : 'not a git worktree — freshness cannot expire by diff')}`);
|
|
556
383
|
console.log('═'.repeat(78) + '\n');
|
|
557
384
|
return finish(0);
|
|
558
385
|
}
|
|
@@ -594,28 +421,69 @@ function supersedeEvidence({ ledger, taskId, evidenceId, replacedBy, json }
|
|
|
594
421
|
return finish(0);
|
|
595
422
|
}
|
|
596
423
|
|
|
424
|
+
/**
|
|
425
|
+
* The `--action retire --stale` arm: retire every active item the ledger classes stale right now,
|
|
426
|
+
* judged the way readiness judges them (the tree the verb runs in). Items are retired without a
|
|
427
|
+
* replacement and the reason is recorded on each; fresh items are not touched.
|
|
428
|
+
* @returns {number} exit code
|
|
429
|
+
*/
|
|
430
|
+
function retireStaleEvidence({ ledger, root, sourceRoot, taskId, slug, stale, json } ) {
|
|
431
|
+
if (!stale) {
|
|
432
|
+
return usageError('evidence', '--action retire retires every stale item and needs --stale; '
|
|
433
|
+
+ 'to retire one item, point it at its replacement with --action supersede', json);
|
|
434
|
+
}
|
|
435
|
+
// Staleness is derived, never stored by a read: classify on a separate copy so the ledger being
|
|
436
|
+
// saved changes only the items retired here.
|
|
437
|
+
const { FreshnessValidator } = require('./freshness.js');
|
|
438
|
+
const probe = new EvidenceLedger({ repoRoot: root, slug });
|
|
439
|
+
let retired ;
|
|
440
|
+
try {
|
|
441
|
+
probe.load(taskId);
|
|
442
|
+
new FreshnessValidator({ repoRoot: root, treeRoot: sourceRoot, sourceRoot }).validateLedgerFreshness(probe);
|
|
443
|
+
const staleItems = probe.getAllEvidence().filter((item ) => item.taskId === taskId
|
|
444
|
+
&& item.freshness?.status === 'STALE' && item.status !== 'superseded');
|
|
445
|
+
for (const item of staleItems) {
|
|
446
|
+
ledger.retireEvidence(item.id, `stale: ${item.freshness.staleReason || 'marked stale'}`);
|
|
447
|
+
}
|
|
448
|
+
retired = staleItems.map((item ) => ledger.getEvidence(item.id));
|
|
449
|
+
if (retired.length > 0) ledger.save(taskId);
|
|
450
|
+
} catch (error ) {
|
|
451
|
+
return usageError('evidence', error.message, json, error);
|
|
452
|
+
}
|
|
453
|
+
const ids = retired.map((item ) => item.id);
|
|
454
|
+
if (json) {
|
|
455
|
+
console.log(JSON.stringify({ action: 'retire', taskId, retired: ids.length, ids, evidence: retired }, null, 2));
|
|
456
|
+
return finish(0);
|
|
457
|
+
}
|
|
458
|
+
console.log(ids.length === 0
|
|
459
|
+
? `Evidence [Task: ${taskId}]: no stale items to retire.`
|
|
460
|
+
: `Evidence [Task: ${taskId}]: retired ${ids.length} stale item(s):\n${retired.map((item ) => ` ${item.id} ${item.retiredReason}`).join('\n')}`);
|
|
461
|
+
return finish(0);
|
|
462
|
+
}
|
|
463
|
+
|
|
597
464
|
/**
|
|
598
465
|
* Handles `doflow evidence` — record a stage's evidence batch, retire a stale item, or query
|
|
599
466
|
* what is recorded.
|
|
600
467
|
*
|
|
601
468
|
* @param {Object} options
|
|
602
469
|
* @param {string} [options.taskId='default']
|
|
603
|
-
* @param {'list'|'status'|'add'|'supersede'} [options.action='list']
|
|
470
|
+
* @param {'list'|'status'|'add'|'supersede'|'retire'} [options.action='list']
|
|
604
471
|
* @param {Object} [options.item] single-item write, assembled from flags by bin/doflow.js
|
|
605
472
|
* @param {string} [options.batchPath] `add`: a JSON batch file, or `-` for stdin
|
|
606
473
|
* @param {string} [options.evidenceId] `supersede`: the item to retire
|
|
607
474
|
* @param {string} [options.replacedBy] `supersede`: the item that replaces it
|
|
475
|
+
* @param {boolean} [options.stale] `retire`: retire every item the ledger classes stale
|
|
608
476
|
* @param {boolean} [options.json=false]
|
|
609
477
|
* @param {string} [options.repoRoot]
|
|
610
478
|
* @param {string} [options.stateRoot]
|
|
611
479
|
* @returns {number} exit code
|
|
612
480
|
*/
|
|
613
|
-
function handleEvidenceCommand({ taskId = 'default', action = 'list', item = null, batchPath = null, evidenceId = null, replacedBy = null, json = false, repoRoot, stateRoot } = {}) {
|
|
481
|
+
function handleEvidenceCommand({ taskId = 'default', action = 'list', item = null, batchPath = null, evidenceId = null, replacedBy = null, stale = false, json = false, repoRoot, stateRoot, sourceRoot, attemptRoot, slug = null } = {}) {
|
|
614
482
|
// See handleReadinessCommand: evidence is the caller's project state, not DoFlow package state.
|
|
615
483
|
const root = stateRoot || repoRoot || process.cwd();
|
|
616
484
|
const isQuery = action === 'list' || action === 'status';
|
|
617
|
-
if (!isQuery && action !== 'add' && action !== 'supersede') {
|
|
618
|
-
return usageError('evidence', `unknown --action '${action}'. Valid: list, add, supersede`, json);
|
|
485
|
+
if (!isQuery && action !== 'add' && action !== 'supersede' && action !== 'retire') {
|
|
486
|
+
return usageError('evidence', `unknown --action '${action}'. Valid: list, add, supersede, retire`, json);
|
|
619
487
|
}
|
|
620
488
|
// A write argument on a read action is the defect this verb was reported for: it used to accept
|
|
621
489
|
// append-shaped flags, ignore them, and print the (unchanged) ledger as if the write had landed.
|
|
@@ -623,15 +491,16 @@ function handleEvidenceCommand({ taskId = 'default', action = 'list', item = nul
|
|
|
623
491
|
return usageError('evidence', 'these are write arguments and this action only reads — re-run with --action add', json);
|
|
624
492
|
}
|
|
625
493
|
|
|
626
|
-
const ledger = new EvidenceLedger({ repoRoot: root });
|
|
494
|
+
const ledger = new EvidenceLedger({ repoRoot: root, slug });
|
|
627
495
|
try {
|
|
628
496
|
ledger.load(taskId);
|
|
629
497
|
} catch (error ) {
|
|
630
498
|
return usageError('evidence', error.message, json, error);
|
|
631
499
|
}
|
|
632
500
|
|
|
633
|
-
if (action === 'add') return addEvidence({ ledger, root, taskId, item, batchPath, json });
|
|
501
|
+
if (action === 'add') return addEvidence({ ledger, root, sourceRoot: sourceRoot || root, attemptRoot, taskId, item, batchPath, json, slug });
|
|
634
502
|
if (action === 'supersede') return supersedeEvidence({ ledger, taskId, evidenceId, replacedBy, json });
|
|
503
|
+
if (action === 'retire') return retireStaleEvidence({ ledger, root, sourceRoot: sourceRoot || root, taskId, slug, stale, json });
|
|
635
504
|
|
|
636
505
|
const items = ledger.queryEvidence({ taskId });
|
|
637
506
|
|
|
@@ -661,10 +530,85 @@ function handleEvidenceCommand({ taskId = 'default', action = 'list', item = nul
|
|
|
661
530
|
return finish(0);
|
|
662
531
|
}
|
|
663
532
|
|
|
533
|
+
const EMIT_NUDGES = ['nudge', 'suppress', 'none'];
|
|
534
|
+
const EMIT_MAX_BYTES = /^[1-9][0-9]{0,9}$/;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* 078 IC-005: `doflow-run context-emit`, a thin wrapper over emitContext(). Identity is read from
|
|
538
|
+
* the environment alone (IC-007); no flag sets it. Exits 0 emitted or silent, 2 usage (including a
|
|
539
|
+
* launch without a launched identity, or an invalid identity value), 3 refused.
|
|
540
|
+
*/
|
|
541
|
+
function handleContextEmitCommand({ event, cwd, session, harness, nudge, maxBytes, taskId, slug, stateRoot, peek = false, json = false, env = process.env } = {}) {
|
|
542
|
+
// Required on use: context-pack.ts loads the whole pack compiler, which no other verb here needs.
|
|
543
|
+
const { emitContext } = require('./context-pack.js');
|
|
544
|
+
const { resolveIdentity } = require('./context/identity.js');
|
|
545
|
+
if (typeof cwd !== 'string' || cwd === '') return usageError('context-emit', '--cwd <dir> is required', json);
|
|
546
|
+
const dir = path.resolve(cwd);
|
|
547
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return usageError('context-emit', `--cwd '${cwd}' is not a directory`, json);
|
|
548
|
+
if (nudge !== undefined && !EMIT_NUDGES.includes(nudge)) return usageError('context-emit', `--nudge must be one of ${EMIT_NUDGES.join(', ')}`, json);
|
|
549
|
+
if (maxBytes !== undefined && !EMIT_MAX_BYTES.test(String(maxBytes))) return usageError('context-emit', '--max-bytes must be a positive integer', json);
|
|
550
|
+
const sessionId = typeof session === 'string' ? session : '';
|
|
551
|
+
const identity = resolveIdentity(env, { cwd: dir, sessionId });
|
|
552
|
+
if (identity.diagnostics.includes('identity-invalid')) {
|
|
553
|
+
return usageError('context-emit', 'DOFLOW_RUN_ID or DOFLOW_LINEAGE_ID holds an invalid value', json);
|
|
554
|
+
}
|
|
555
|
+
let result ;
|
|
556
|
+
try {
|
|
557
|
+
result = emitContext({
|
|
558
|
+
event, identity, cwd: dir, sessionId, env, peek: peek === true,
|
|
559
|
+
...(harness ? { harness } : {}),
|
|
560
|
+
...(nudge ? { nudge } : {}),
|
|
561
|
+
...(maxBytes !== undefined ? { maxBytes: Number(maxBytes) } : {}),
|
|
562
|
+
...(taskId ? { taskId } : {}),
|
|
563
|
+
...(slug ? { slug } : {}),
|
|
564
|
+
...(stateRoot ? { stateRoot } : {}),
|
|
565
|
+
});
|
|
566
|
+
} catch (error ) {
|
|
567
|
+
if (error?.code === 'USAGE_ERROR') return usageError('context-emit', error.message.replace(/^emitContext: /, ''), json);
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
if (json) {
|
|
571
|
+
console.log(JSON.stringify({ v: 1, event, identity, decision: result.decision, text: result.text, pack: result.pack, committed: result.committed }));
|
|
572
|
+
} else if (result.text !== null) {
|
|
573
|
+
process.stdout.write(result.text.endsWith('\n') ? result.text : `${result.text}\n`);
|
|
574
|
+
}
|
|
575
|
+
return finish(result.decision.reason === 'refused:mandatory-over-budget' ? 3 : 0);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* 078 IC-013: `doflow-run context-grant`, a thin wrapper over grantSummary(). The source scope
|
|
580
|
+
* defaults to the caller's identity. Exits 0 created, 1 not found or already granted, 2 usage,
|
|
581
|
+
* 3 over budget (refused before any write).
|
|
582
|
+
*/
|
|
583
|
+
function handleContextGrantCommand({ summary, toRun, toLineage, fromWorktree, fromRun, fromLineage, maxBytes, json = false, env = process.env } = {}) {
|
|
584
|
+
const { grantSummary } = require('./context/summary-store.js');
|
|
585
|
+
let granted ;
|
|
586
|
+
try {
|
|
587
|
+
granted = grantSummary({
|
|
588
|
+
summary, toRun, toLineage, env,
|
|
589
|
+
...(fromWorktree ? { fromWorktree: path.resolve(fromWorktree) } : {}),
|
|
590
|
+
...(fromRun ? { fromRun } : {}),
|
|
591
|
+
...(fromLineage ? { fromLineage } : {}),
|
|
592
|
+
...(maxBytes !== undefined ? { maxBytes } : {}),
|
|
593
|
+
});
|
|
594
|
+
} catch (error ) {
|
|
595
|
+
if (error?.name !== 'SummaryStoreError') throw error;
|
|
596
|
+
if (error.exitCode === 2) return usageError('context-grant', error.message, json);
|
|
597
|
+
if (json) console.log(JSON.stringify({ v: 1, ok: false, error: error.code, summary: error.message }));
|
|
598
|
+
else console.error(`doflow context-grant: ${error.message}`);
|
|
599
|
+
return finish(error.exitCode);
|
|
600
|
+
}
|
|
601
|
+
if (json) console.log(JSON.stringify({ v: 1, ok: true, ...granted }));
|
|
602
|
+
else console.log(`granted ${granted.summaryId} (${granted.bytes} B) -> ${granted.path}`);
|
|
603
|
+
return finish(0);
|
|
604
|
+
}
|
|
605
|
+
|
|
664
606
|
module.exports = {
|
|
665
607
|
handleCapabilitiesCommand,
|
|
666
608
|
handleReadinessCommand,
|
|
667
609
|
handleEvidenceCommand,
|
|
610
|
+
handleContextEmitCommand,
|
|
611
|
+
handleContextGrantCommand,
|
|
668
612
|
// Exported for bin/doflow.js, which refuses the same names as command-line flags, and for the
|
|
669
613
|
// tests that pin the write boundary.
|
|
670
614
|
EVIDENCE_SCORE_FIELDS,
|