@khoavu882/doflow 2.0.0-beta.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -2
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +8 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +10 -2
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +2 -11
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +3 -2
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +47 -8
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1442 -363
- package/core/registry/lifecycle.json +485 -304
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +679 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +149 -34
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +18 -17
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +15 -58
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +4 -7
- package/core/shared/skills/do-plan/SKILL.md +26 -15
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +143 -66
- package/src/adapters/claude/index.js +24 -4
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/index.js +48 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +290 -44
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +322 -29
- package/src/cli/runtime-commands.js +209 -57
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +20 -3
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +20 -11
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +154 -78
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-ledger.js +34 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +2 -2
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +1 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +374 -64
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -251
- package/src/adapters/gemini/index.js +0 -429
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
const { spawnSync } = require('node:child_process');
|
|
29
29
|
const fs = require('node:fs');
|
|
30
30
|
const path = require('node:path');
|
|
31
|
+
const { telemetryOff } = require('../trace/telemetry.js');
|
|
32
|
+
const { checkKeyV2, headCommit, treeState } = require('./check-key.js');
|
|
33
|
+
const { runInflight } = require('./inflight.js');
|
|
34
|
+
const { levelOf, reuseApplies, v0Applies } = require('./registry.js');
|
|
35
|
+
const { evictReuse, lookupReuse, reuseDir, storeReuse } = require('./reuse.js');
|
|
36
|
+
const { resolveTestsImportingHelper } = require('./impact.js');
|
|
31
37
|
|
|
32
38
|
/** A failing check whose name contains one of these aborts the remaining checks: once the build is
|
|
33
39
|
* broken, every later result describes the broken build rather than the change. */
|
|
@@ -88,6 +94,216 @@ function extractFailedTests(text ) {
|
|
|
88
94
|
return [...seen];
|
|
89
95
|
}
|
|
90
96
|
|
|
97
|
+
/** The monotonic clock in nanoseconds, or null when it cannot be read. */
|
|
98
|
+
function monotonicNs() {
|
|
99
|
+
try {
|
|
100
|
+
return process.hrtime.bigint();
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Test output counts parser (088 E.2, IC-007).
|
|
108
|
+
* Parses full stdout+stderr for TAP (# tests N / # fail N) or spec (ℹ tests N / ℹ fail N).
|
|
109
|
+
* Pytest and unparseable outputs return { tests: null, testsFailed: null }.
|
|
110
|
+
* @param {unknown} text
|
|
111
|
+
* @returns {{ tests: number | null, testsFailed: number | null }}
|
|
112
|
+
*/
|
|
113
|
+
function parseTestCounts(text ) {
|
|
114
|
+
if (typeof text !== 'string') return { tests: null, testsFailed: null };
|
|
115
|
+
const tapTests = text.match(/^# tests (\d+)/m);
|
|
116
|
+
if (tapTests) {
|
|
117
|
+
const tapFail = text.match(/^# fail (\d+)/m);
|
|
118
|
+
return {
|
|
119
|
+
tests: parseInt(tapTests[1], 10),
|
|
120
|
+
testsFailed: tapFail ? parseInt(tapFail[1], 10) : 0,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const specTests = text.match(/^ℹ tests (\d+)/m);
|
|
124
|
+
if (specTests) {
|
|
125
|
+
const specFail = text.match(/^ℹ fail (\d+)/m);
|
|
126
|
+
return {
|
|
127
|
+
tests: parseInt(specTests[1], 10),
|
|
128
|
+
testsFailed: specFail ? parseInt(specFail[1], 10) : 0,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return { tests: null, testsFailed: null };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Reads child cpu ticks from /proc/self/stat on Linux (088 E.2, IC-008).
|
|
136
|
+
* Returns cutime + cstime as a number, or null on non-Linux or read fault.
|
|
137
|
+
* @param {Object} [fsImpl=fs]
|
|
138
|
+
* @param {string} [platform=process.platform]
|
|
139
|
+
* @returns {number | null}
|
|
140
|
+
*/
|
|
141
|
+
function childCpuTicks(fsImpl = fs, platform = process.platform) {
|
|
142
|
+
if (platform !== 'linux') return null;
|
|
143
|
+
try {
|
|
144
|
+
const statText = fsImpl.readFileSync('/proc/self/stat', 'utf8');
|
|
145
|
+
const lastParen = statText.lastIndexOf(')');
|
|
146
|
+
if (lastParen === -1) return null;
|
|
147
|
+
const rest = statText.slice(lastParen + 1).trim();
|
|
148
|
+
const tokens = rest.split(/\s+/);
|
|
149
|
+
// After the last ')', field 3 (state) is index 0.
|
|
150
|
+
// field 16 (cutime) is index 13, field 17 (cstime) is index 14.
|
|
151
|
+
const cutime = parseInt(tokens[13], 10);
|
|
152
|
+
const cstime = parseInt(tokens[14], 10);
|
|
153
|
+
if (!Number.isFinite(cutime) || !Number.isFinite(cstime)) return null;
|
|
154
|
+
return cutime + cstime;
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Test output failing files parser (088 G.3, IC-007).
|
|
162
|
+
* Extracts sorted unique repo-relative test file paths from TAP, spec, or pytest output.
|
|
163
|
+
* Drops non-test files, non-regular files (directories), files outside root, or parse faults.
|
|
164
|
+
* @param {unknown} text
|
|
165
|
+
* @param {Object} options
|
|
166
|
+
* @param {string} options.root project root
|
|
167
|
+
* @param {Object} options.convention convention object with isTest(path)
|
|
168
|
+
* @param {Object} [options.fsImpl=fs]
|
|
169
|
+
* @returns {Array<string>} sorted unique repo-relative paths
|
|
170
|
+
*/
|
|
171
|
+
function extractFailedFiles(
|
|
172
|
+
text ,
|
|
173
|
+
{
|
|
174
|
+
root,
|
|
175
|
+
convention,
|
|
176
|
+
found,
|
|
177
|
+
fsImpl = fs,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
= {},
|
|
184
|
+
) {
|
|
185
|
+
if (typeof text !== 'string' || !root || !convention || typeof convention.isTest !== 'function') {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let realRoot ;
|
|
190
|
+
try {
|
|
191
|
+
realRoot = fsImpl.realpathSync(root);
|
|
192
|
+
} catch {
|
|
193
|
+
realRoot = path.resolve(root);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const rawPaths = new Set ();
|
|
197
|
+
|
|
198
|
+
// 1. TAP: in a YAML block that follows a `not ok` line, ^\s*location:\s*'(.+):\d+:\d+'\s*$ (absolute, made relative to realpath(root))
|
|
199
|
+
const lines = text.split('\n');
|
|
200
|
+
let inNotOk = false;
|
|
201
|
+
let inYaml = false;
|
|
202
|
+
|
|
203
|
+
for (let i = 0; i < lines.length; i++) {
|
|
204
|
+
const line = lines[i];
|
|
205
|
+
if (/^\s*not ok\b/.test(line)) {
|
|
206
|
+
inNotOk = true;
|
|
207
|
+
inYaml = false;
|
|
208
|
+
} else if (/^\s*ok\b/.test(line)) {
|
|
209
|
+
inNotOk = false;
|
|
210
|
+
inYaml = false;
|
|
211
|
+
} else if (inNotOk && /^\s*---\s*$/.test(line)) {
|
|
212
|
+
inYaml = true;
|
|
213
|
+
} else if (inYaml && /^\s*\.\.\.\s*$/.test(line)) {
|
|
214
|
+
inYaml = false;
|
|
215
|
+
inNotOk = false;
|
|
216
|
+
} else if (inNotOk && inYaml) {
|
|
217
|
+
const match = line.match(/^\s*location:\s*['"]?(.+?):\d+:\d+['"]?\s*$/);
|
|
218
|
+
if (match) {
|
|
219
|
+
rawPaths.add(match[1]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 2. Spec: after the `✖ failing tests:` line, ^\s*test at (.+):\d+:\d+\s*$ (relative to root)
|
|
225
|
+
let afterFailingTests = false;
|
|
226
|
+
for (let i = 0; i < lines.length; i++) {
|
|
227
|
+
const line = lines[i];
|
|
228
|
+
if (/✖ failing tests:/.test(line)) {
|
|
229
|
+
afterFailingTests = true;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (afterFailingTests) {
|
|
233
|
+
const match = line.match(/^\s*test at (.+?):\d+:\d+\s*$/);
|
|
234
|
+
if (match) {
|
|
235
|
+
rawPaths.add(match[1]);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 3. Pytest: ^FAILED (\S+?)::
|
|
241
|
+
for (let i = 0; i < lines.length; i++) {
|
|
242
|
+
const line = lines[i];
|
|
243
|
+
const match = line.match(/^FAILED (\S+?)::/);
|
|
244
|
+
if (match) {
|
|
245
|
+
rawPaths.add(match[1]);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const validFiles = new Set ();
|
|
250
|
+
|
|
251
|
+
for (const raw of rawPaths) {
|
|
252
|
+
let relPath ;
|
|
253
|
+
if (path.isAbsolute(raw)) {
|
|
254
|
+
relPath = path.relative(realRoot, raw);
|
|
255
|
+
} else {
|
|
256
|
+
relPath = path.normalize(raw);
|
|
257
|
+
}
|
|
258
|
+
if (path.isAbsolute(relPath) || relPath.startsWith('..')) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const normalized = relPath.replace(/\\/g, '/');
|
|
262
|
+
const fullPath = path.join(realRoot, normalized);
|
|
263
|
+
|
|
264
|
+
let st ;
|
|
265
|
+
try {
|
|
266
|
+
st = fsImpl.statSync(fullPath);
|
|
267
|
+
} catch {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (!st || !st.isFile() || st.isDirectory()) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (convention.isTest(normalized)) {
|
|
274
|
+
validFiles.add(normalized);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
// IC-004: when location is a non-test helper under convention.roots, map to importing .test.* files
|
|
278
|
+
const underRoots = Array.isArray(convention.roots)
|
|
279
|
+
? convention.roots.some((r ) => normalized === r || normalized.startsWith(`${r}/`))
|
|
280
|
+
: normalized.startsWith('test/');
|
|
281
|
+
if (underRoots) {
|
|
282
|
+
const targetFound = found || convention.found || { source: 'package.json', testDirs: convention.roots || ['test'] };
|
|
283
|
+
const importing = resolveTestsImportingHelper({
|
|
284
|
+
root: realRoot,
|
|
285
|
+
found: targetFound,
|
|
286
|
+
helperFiles: [normalized],
|
|
287
|
+
fsImpl,
|
|
288
|
+
});
|
|
289
|
+
for (const t of importing) {
|
|
290
|
+
validFiles.add(t);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return [...validFiles].sort();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** What one status read says (089 IC-001); `ignored`, `gitRoot` and `links` are null when it faulted. */
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
91
307
|
class VerificationContractRunner {
|
|
92
308
|
cwd ;
|
|
93
309
|
defaultTimeoutMs ;
|
|
@@ -95,6 +311,20 @@ class VerificationContractRunner {
|
|
|
95
311
|
fsImpl ;
|
|
96
312
|
logDir ;
|
|
97
313
|
logWriteWarned ;
|
|
314
|
+
platform ;
|
|
315
|
+
env ;
|
|
316
|
+
convention ;
|
|
317
|
+
configDir ;
|
|
318
|
+
inflightContext ;
|
|
319
|
+
/** 089 IC-005: lookup of recorded PASSes, set only at the levels `policy.reuse.levels` lists. */
|
|
320
|
+
reuseContext ;
|
|
321
|
+
/** 089 IC-005: where an executed PASS is stored while reuse is on, at every level (L3 store-only). */
|
|
322
|
+
reuseStore ;
|
|
323
|
+
/** One `git status` per contract (089 IC-001): `ignored` and `gitRoot` are null when it faulted. */
|
|
324
|
+
_treeContext ;
|
|
325
|
+
/** Check result → `{ startedAt, startedMonoMs, durationMs, tests, testsFailed, cpuMs, resourceBasis, checkKey, shared, failedFirst }`
|
|
326
|
+
* for every executed or reused check; an in-run duplicate and a check with no command have no entry. */
|
|
327
|
+
metrics ;
|
|
98
328
|
|
|
99
329
|
/**
|
|
100
330
|
* @param {Object|string} [options] a string is accepted for parity with the Python's positional
|
|
@@ -102,6 +332,10 @@ class VerificationContractRunner {
|
|
|
102
332
|
* @param {string} [options.cwd] directory the checks run in; defaults to the current one
|
|
103
333
|
* @param {number} [options.defaultTimeoutMs=60000]
|
|
104
334
|
* @param {Function} [options.exec] injection seam for tests; same contract as `spawnSync`
|
|
335
|
+
* @param {string} [options.logDir] where full check logs go; the verify verb passes its resolved
|
|
336
|
+
* store's (engine.ts `verifyStateRoot`), so `<cwd>/.doflow` is only the unconfigured default
|
|
337
|
+
* @param {string} [options.platform=process.platform]
|
|
338
|
+
* @param {Object} [options.env=process.env]
|
|
105
339
|
*/
|
|
106
340
|
constructor(options = {}) {
|
|
107
341
|
const opts = typeof options === 'string' ? { cwd: options } : options;
|
|
@@ -110,6 +344,155 @@ class VerificationContractRunner {
|
|
|
110
344
|
this.exec = opts.exec || spawnSync;
|
|
111
345
|
this.fsImpl = opts.fsImpl || fs;
|
|
112
346
|
this.logDir = opts.logDir || path.join(this.cwd, '.doflow', 'state', 'verification', 'logs');
|
|
347
|
+
this.platform = opts.platform || process.platform;
|
|
348
|
+
this.env = opts.env || process.env;
|
|
349
|
+
this.convention = opts.convention || null;
|
|
350
|
+
this._treeContext = undefined;
|
|
351
|
+
this.metrics = new WeakMap();
|
|
352
|
+
if (opts.context) this.beginContract(opts.context);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Explicit engine seam: call once per active V0 or reuse evaluation, including retries.
|
|
356
|
+
* No execution policy is inferred from the environment or leaked into compiled contracts.
|
|
357
|
+
* 089 IC-005: `reuse === true` stores every executed PASS at a known level; lookup is enabled only
|
|
358
|
+
* where `reuseApplies` (L1/L2), so an L3 PASS may serve a later L2 run but L3 always executes. */
|
|
359
|
+
beginContract({ v0, reuse, registry, riskLevel, configDir } ) {
|
|
360
|
+
this._treeContext = undefined;
|
|
361
|
+
this.inflightContext = undefined;
|
|
362
|
+
this.reuseContext = undefined;
|
|
363
|
+
this.reuseStore = undefined;
|
|
364
|
+
this.configDir = configDir;
|
|
365
|
+
if (v0 === true && v0Applies(registry, riskLevel) && typeof configDir === 'string') {
|
|
366
|
+
this.inflightContext = { enabled: true, dir: path.join(configDir, 'state', 'verification', 'v0') };
|
|
367
|
+
}
|
|
368
|
+
if (reuse === true && typeof configDir === 'string') {
|
|
369
|
+
const level = levelOf(registry, riskLevel);
|
|
370
|
+
const dir = level ? reuseDir({ configDir, env: this.env, fsImpl: this.fsImpl }) : null;
|
|
371
|
+
if (level && typeof dir === 'string') {
|
|
372
|
+
this.reuseStore = { dir, level };
|
|
373
|
+
const maxAgeMs = registry?.policy?.reuse?.maxAgeMs;
|
|
374
|
+
if (reuseApplies(registry, riskLevel) && Number.isSafeInteger(maxAgeMs) && maxAgeMs > 0) {
|
|
375
|
+
this.reuseContext = { enabled: true, dir, maxAgeMs, level };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* One read of the working tree: HEAD, the status call's tree, ignored set and git root, and (only
|
|
383
|
+
* while reuse is on, where something reads it) the tracked symlinks' out-of-repo targets.
|
|
384
|
+
*/
|
|
385
|
+
readTreeContext() {
|
|
386
|
+
const head = headCommit(this.cwd);
|
|
387
|
+
const state = treeState(this.cwd, { fsImpl: this.fsImpl, configDir: this.configDir, links: Boolean(this.reuseStore) });
|
|
388
|
+
return state
|
|
389
|
+
? { head, tree: state.tree, ignored: state.ignored, gitRoot: state.gitRoot, links: state.links }
|
|
390
|
+
: { head, tree: null, ignored: null, gitRoot: null, links: null };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* 089 IC-004 (review C-1): after a check ran with reuse on, read the tree again. The cached
|
|
395
|
+
* context is replaced when it moved, so later checks key on the tree they actually run on.
|
|
396
|
+
* Returns `unchanged` true only when the check's own payload digest is unchanged, i.e. its inputs
|
|
397
|
+
* did not move during the run and a PASS may be stored under the key it was looked up with, and
|
|
398
|
+
* `key`, the key of the tree the check ran on (null when the read faulted or cannot be keyed).
|
|
399
|
+
* FU-kerery: a FAIL whose pre-run key was null evicts by this post-run key.
|
|
400
|
+
*/
|
|
401
|
+
rereadAfterRun(command , timeoutMs , payloadDigest ) {
|
|
402
|
+
const none = { unchanged: false, key: null };
|
|
403
|
+
if (!this.reuseStore) return none;
|
|
404
|
+
try {
|
|
405
|
+
const before = this._treeContext;
|
|
406
|
+
const after = this.readTreeContext();
|
|
407
|
+
if (after.tree === null) {
|
|
408
|
+
// 089 re-review N-a: a faulted read is not cached; the next check reads the tree again
|
|
409
|
+
// instead of keying null (and evicting nothing) for the rest of the contract.
|
|
410
|
+
this._treeContext = undefined;
|
|
411
|
+
return none;
|
|
412
|
+
}
|
|
413
|
+
if (!before || before.head !== after.head || before.tree !== after.tree || before.ignored !== after.ignored
|
|
414
|
+
|| before.gitRoot !== after.gitRoot || before.links !== after.links) {
|
|
415
|
+
this._treeContext = after;
|
|
416
|
+
}
|
|
417
|
+
if (typeof after.ignored !== 'string') return none;
|
|
418
|
+
const keyed = checkKeyV2({ command, timeoutMs, cwd: this.cwd, head: after.head, tree: after.tree,
|
|
419
|
+
ignored: after.ignored, gitRoot: after.gitRoot, links: after.links, configDir: this.configDir,
|
|
420
|
+
env: this.env, fsImpl: this.fsImpl });
|
|
421
|
+
return { unchanged: Boolean(payloadDigest) && keyed?.payloadDigest === payloadDigest, key: keyed?.key ?? null };
|
|
422
|
+
} catch {
|
|
423
|
+
this._treeContext = undefined; // unknown state: the next check reads it afresh
|
|
424
|
+
return none;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** 089 IC-003 (review C-2): an executed non-PASS removes the key's stored PASS, at every level. */
|
|
429
|
+
evictFailed(key ) {
|
|
430
|
+
if (!this.reuseStore || !key) return;
|
|
431
|
+
evictReuse({ dir: this.reuseStore.dir, key, fsImpl: this.fsImpl });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 089 IC-004: the recorded PASS for this key, as a check result labelled `reused`, or null. Its
|
|
436
|
+
* metrics entry says `shared: "reused"` with the record's ref; `durationMs` is the lookup time,
|
|
437
|
+
* and no cpu was spent. Lookup faults are misses (IC-003 never throws).
|
|
438
|
+
*/
|
|
439
|
+
lookupReused(checkName , command , key , payloadDigest , startedAt ) {
|
|
440
|
+
if (!this.reuseContext?.enabled || !key || !payloadDigest) return null;
|
|
441
|
+
const t0 = monotonicNs();
|
|
442
|
+
const found = lookupReuse({ dir: this.reuseContext.dir, key, payloadDigest, maxAgeMs: this.reuseContext.maxAgeMs, fsImpl: this.fsImpl });
|
|
443
|
+
if (!found || found.hit !== true) return null;
|
|
444
|
+
const t1 = monotonicNs();
|
|
445
|
+
const record = found.record;
|
|
446
|
+
const result = {
|
|
447
|
+
name: checkName,
|
|
448
|
+
command,
|
|
449
|
+
status: 'PASS',
|
|
450
|
+
exitCode: 0,
|
|
451
|
+
stdout: record.result.stdout,
|
|
452
|
+
stderr: record.result.stderr,
|
|
453
|
+
reused: { ref: record.ref, recordedAt: record.recordedAt },
|
|
454
|
+
};
|
|
455
|
+
this.metrics.set(result, {
|
|
456
|
+
startedAt,
|
|
457
|
+
durationMs: t0 !== null && t1 !== null ? Number(t1 - t0) / 1e6 : null,
|
|
458
|
+
tests: record.result.tests ?? null,
|
|
459
|
+
testsFailed: record.result.testsFailed ?? null,
|
|
460
|
+
cpuMs: null,
|
|
461
|
+
resourceBasis: null,
|
|
462
|
+
checkKey: key,
|
|
463
|
+
shared: 'reused',
|
|
464
|
+
sharedRef: record.ref,
|
|
465
|
+
failedFirst: 'none',
|
|
466
|
+
});
|
|
467
|
+
return result;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* 089 IC-002/IC-004: store an executed PASS while reuse is on. The stored result is the excerpt
|
|
472
|
+
* the report carries, elision marker included (the record bound leaves room for it), so a reused
|
|
473
|
+
* check shows exactly what the executed one showed.
|
|
474
|
+
*/
|
|
475
|
+
storeReused(result , key , payloadDigest , res ) {
|
|
476
|
+
if (!this.reuseStore || !key || !payloadDigest || result.status !== 'PASS' || result.exitCode !== 0) return;
|
|
477
|
+
if (typeof result.stdout !== 'string' || typeof result.stderr !== 'string') return;
|
|
478
|
+
const counts = res?.bounded === true ? res : parseTestCounts(`${res?.stdout || ''}\n${res?.stderr || ''}`);
|
|
479
|
+
storeReuse({
|
|
480
|
+
dir: this.reuseStore.dir,
|
|
481
|
+
key,
|
|
482
|
+
payloadDigest,
|
|
483
|
+
level: this.reuseStore.level,
|
|
484
|
+
result: {
|
|
485
|
+
status: 'PASS',
|
|
486
|
+
exitCode: 0,
|
|
487
|
+
stdout: result.stdout,
|
|
488
|
+
stderr: result.stderr,
|
|
489
|
+
truncated: res?.bounded === true ? res.truncated === true
|
|
490
|
+
: (res?.stdout || '').length > MAX_STREAM_CHARS || (res?.stderr || '').length > MAX_STREAM_CHARS,
|
|
491
|
+
tests: counts.tests ?? null,
|
|
492
|
+
testsFailed: counts.testsFailed ?? null,
|
|
493
|
+
},
|
|
494
|
+
fsImpl: this.fsImpl,
|
|
495
|
+
});
|
|
113
496
|
}
|
|
114
497
|
|
|
115
498
|
/**
|
|
@@ -154,11 +537,26 @@ class VerificationContractRunner {
|
|
|
154
537
|
* @param {string} [stdout]
|
|
155
538
|
* @param {string} [stderr]
|
|
156
539
|
*/
|
|
157
|
-
decorate(result , command , stdout , stderr ) {
|
|
540
|
+
decorate(result , command , stdout , stderr , execution ) {
|
|
541
|
+
if (execution?.bounded === true) {
|
|
542
|
+
for (const field of ['failedTests', 'failedFiles', 'logPath']) {
|
|
543
|
+
if (Object.hasOwn(execution, field)) result[field] = execution[field];
|
|
544
|
+
}
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
158
547
|
const full = `${stdout || ''}\n${stderr || ''}`;
|
|
159
548
|
if (result.status === 'FAIL') {
|
|
160
549
|
const failedTests = extractFailedTests(full);
|
|
161
550
|
if (failedTests.length > 0) result.failedTests = failedTests;
|
|
551
|
+
if (this.convention) {
|
|
552
|
+
const failedFiles = extractFailedFiles(full, {
|
|
553
|
+
root: this.cwd,
|
|
554
|
+
convention: this.convention,
|
|
555
|
+
found: (this.convention ).found || (this ).found,
|
|
556
|
+
fsImpl: this.fsImpl,
|
|
557
|
+
});
|
|
558
|
+
if (failedFiles.length > 0) result.failedFiles = failedFiles;
|
|
559
|
+
}
|
|
162
560
|
}
|
|
163
561
|
const truncated = (stdout || '').length > MAX_STREAM_CHARS || (stderr || '').length > MAX_STREAM_CHARS;
|
|
164
562
|
if ((result.status === 'FAIL' || truncated) && full.trim() !== '') {
|
|
@@ -166,6 +564,27 @@ class VerificationContractRunner {
|
|
|
166
564
|
}
|
|
167
565
|
}
|
|
168
566
|
|
|
567
|
+
/** Parse the full streams and write their log once, before publishing a bounded handoff.
|
|
568
|
+
* Keep the runner's existing head/tail excerpt so a follower reports the same diagnostics. */
|
|
569
|
+
summarizeExecution(res , name , command ) {
|
|
570
|
+
const diagnostics = { name, status: res.error || res.status !== 0 ? 'FAIL' : 'PASS' };
|
|
571
|
+
this.decorate(diagnostics, command, res.stdout, res.stderr);
|
|
572
|
+
const counts = parseTestCounts(`${res.stdout || ''}\n${res.stderr || ''}`);
|
|
573
|
+
return {
|
|
574
|
+
status: res.status,
|
|
575
|
+
signal: res.signal ?? null,
|
|
576
|
+
...(res.error ? { error: { message: res.error.message, code: res.error.code ?? null } } : {}),
|
|
577
|
+
stdout: truncate(res.stdout),
|
|
578
|
+
stderr: truncate(res.stderr),
|
|
579
|
+
truncated: (res.stdout || '').length > MAX_STREAM_CHARS || (res.stderr || '').length > MAX_STREAM_CHARS,
|
|
580
|
+
bounded: true,
|
|
581
|
+
...counts,
|
|
582
|
+
...(diagnostics.failedTests ? { failedTests: diagnostics.failedTests } : {}),
|
|
583
|
+
...(diagnostics.failedFiles ? { failedFiles: diagnostics.failedFiles } : {}),
|
|
584
|
+
...(Object.hasOwn(diagnostics, 'logPath') ? { logPath: diagnostics.logPath } : {}),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
169
588
|
/**
|
|
170
589
|
* Executes a single deterministic shell check.
|
|
171
590
|
* @param {string} name
|
|
@@ -196,16 +615,103 @@ class VerificationContractRunner {
|
|
|
196
615
|
if (dedupeCache && dedupeCache.has(dedupeKey)) {
|
|
197
616
|
return { ...dedupeCache.get(dedupeKey), name: checkName, deduplicated: true };
|
|
198
617
|
}
|
|
618
|
+
// Timing for telemetry (088 C.1, IC-006; 088 E.2, IC-007..IC-009) lives in `this.metrics`,
|
|
619
|
+
// never in the result, so the report is the same with telemetry on or off.
|
|
620
|
+
const startedAt = Date.now();
|
|
621
|
+
const t0 = monotonicNs();
|
|
622
|
+
const t0Cpu = childCpuTicks(this.fsImpl, this.platform);
|
|
623
|
+
let checkKeyVal = null;
|
|
624
|
+
let payloadDigest = null;
|
|
625
|
+
try {
|
|
626
|
+
if (!telemetryOff(this.env) || this.inflightContext?.enabled || this.reuseStore) {
|
|
627
|
+
if (this._treeContext === undefined) {
|
|
628
|
+
// 089 IC-001: one status call per contract yields the tree, the ignored set and the git
|
|
629
|
+
// root; when it faults all three are null and the key is null (never a partial key).
|
|
630
|
+
this._treeContext = this.readTreeContext();
|
|
631
|
+
}
|
|
632
|
+
const keyed = checkKeyV2({
|
|
633
|
+
command,
|
|
634
|
+
timeoutMs,
|
|
635
|
+
cwd: this.cwd,
|
|
636
|
+
head: this._treeContext.head,
|
|
637
|
+
tree: this._treeContext.tree,
|
|
638
|
+
ignored: this._treeContext.ignored,
|
|
639
|
+
gitRoot: this._treeContext.gitRoot,
|
|
640
|
+
links: this._treeContext.links,
|
|
641
|
+
configDir: this.configDir,
|
|
642
|
+
env: this.env,
|
|
643
|
+
fsImpl: this.fsImpl,
|
|
644
|
+
});
|
|
645
|
+
checkKeyVal = keyed?.key ?? null;
|
|
646
|
+
// Reuse trusts only a key whose ignored set came from this contract's own status call.
|
|
647
|
+
payloadDigest = typeof this._treeContext.ignored === 'string' ? keyed?.payloadDigest ?? null : null;
|
|
648
|
+
}
|
|
649
|
+
} catch {
|
|
650
|
+
checkKeyVal = null;
|
|
651
|
+
payloadDigest = null;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// 089 IC-004: after the in-run cache and the key, before in-flight sharing or execution.
|
|
655
|
+
const reusedResult = this.lookupReused(checkName, command, checkKeyVal, payloadDigest, startedAt);
|
|
656
|
+
if (reusedResult) {
|
|
657
|
+
if (dedupeCache) dedupeCache.set(dedupeKey, reusedResult);
|
|
658
|
+
return reusedResult;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
let sharing = { shared: 'none', sharedRef: null };
|
|
662
|
+
let timing = null;
|
|
199
663
|
const remember = (result ) => {
|
|
664
|
+
if (sharing.shared === 'follower') result.shared = { from: sharing.sharedRef };
|
|
665
|
+
if (timing) {
|
|
666
|
+
if (sharing.shared === 'follower') {
|
|
667
|
+
timing.durationMs = sharing.durationMs;
|
|
668
|
+
timing.cpuMs = null;
|
|
669
|
+
timing.resourceBasis = null;
|
|
670
|
+
}
|
|
671
|
+
this.metrics.set(result, timing);
|
|
672
|
+
}
|
|
200
673
|
if (dedupeCache) dedupeCache.set(dedupeKey, result);
|
|
201
674
|
return result;
|
|
202
675
|
};
|
|
676
|
+
const stop = (res ) => {
|
|
677
|
+
const t1 = monotonicNs();
|
|
678
|
+
const t1Cpu = childCpuTicks(this.fsImpl, this.platform);
|
|
679
|
+
let cpuMs = null;
|
|
680
|
+
let resourceBasis = null;
|
|
681
|
+
if (this.platform !== 'linux') {
|
|
682
|
+
cpuMs = null;
|
|
683
|
+
resourceBasis = 'unreached-platform';
|
|
684
|
+
} else if (t0Cpu === null || t1Cpu === null) {
|
|
685
|
+
cpuMs = null;
|
|
686
|
+
resourceBasis = 'unreached-read';
|
|
687
|
+
} else {
|
|
688
|
+
cpuMs = Math.max(0, (t1Cpu - t0Cpu) * 10);
|
|
689
|
+
resourceBasis = 'proc-children';
|
|
690
|
+
}
|
|
691
|
+
const full = `${res ? res.stdout || '' : ''}\n${res ? res.stderr || '' : ''}`;
|
|
692
|
+
const { tests, testsFailed } = res?.bounded === true ? res : parseTestCounts(full);
|
|
693
|
+
timing = {
|
|
694
|
+
startedAt,
|
|
695
|
+
startedMonoMs: t0 !== null ? Number(t0) / 1e6 : null,
|
|
696
|
+
durationMs: t0 !== null && t1 !== null ? Number(t1 - t0) / 1e6 : null,
|
|
697
|
+
tests,
|
|
698
|
+
testsFailed,
|
|
699
|
+
cpuMs,
|
|
700
|
+
resourceBasis,
|
|
701
|
+
checkKey: checkKeyVal,
|
|
702
|
+
shared: sharing.shared,
|
|
703
|
+
sharedRef: sharing.sharedRef,
|
|
704
|
+
failedFirst: 'none',
|
|
705
|
+
};
|
|
706
|
+
};
|
|
203
707
|
|
|
204
708
|
let res ;
|
|
205
709
|
try {
|
|
206
|
-
|
|
710
|
+
const execute = () => this.exec(command, {
|
|
207
711
|
shell: true,
|
|
208
712
|
cwd: this.cwd,
|
|
713
|
+
// The key hashed this.env; with sharing or reuse on the command runs in exactly that env.
|
|
714
|
+
...(this.inflightContext?.enabled || this.reuseStore ? { env: this.env } : {}),
|
|
209
715
|
encoding: 'utf8',
|
|
210
716
|
timeout: timeoutMs,
|
|
211
717
|
killSignal: 'SIGKILL',
|
|
@@ -214,7 +720,17 @@ class VerificationContractRunner {
|
|
|
214
720
|
// past anything a check legitimately prints. Only the first 2,000 chars are ever kept.
|
|
215
721
|
maxBuffer: 64 * 1024 * 1024,
|
|
216
722
|
});
|
|
723
|
+
if (this.inflightContext?.enabled && checkKeyVal) {
|
|
724
|
+
sharing = runInflight({ dir: this.inflightContext.dir, key: checkKeyVal, timeoutMs,
|
|
725
|
+
execute, summarize: (result ) => this.summarizeExecution(result, checkName, command), fsImpl: this.fsImpl });
|
|
726
|
+
res = sharing.result;
|
|
727
|
+
} else {
|
|
728
|
+
res = execute();
|
|
729
|
+
}
|
|
217
730
|
} catch (error ) {
|
|
731
|
+
stop();
|
|
732
|
+
const reread = this.rereadAfterRun(command, timeoutMs, payloadDigest);
|
|
733
|
+
this.evictFailed(checkKeyVal ?? reread.key);
|
|
218
734
|
// Mirrors the Python's bare `except Exception` arm: an unspawnable command is a FAIL, not a
|
|
219
735
|
// thrown error that aborts the whole contract.
|
|
220
736
|
return remember({
|
|
@@ -225,35 +741,46 @@ class VerificationContractRunner {
|
|
|
225
741
|
error: error.message,
|
|
226
742
|
});
|
|
227
743
|
}
|
|
744
|
+
stop(res);
|
|
745
|
+
// Reuse on: the check may have changed what the key hashed. Read again before anything is stored.
|
|
746
|
+
const reread = this.rereadAfterRun(command, timeoutMs, payloadDigest);
|
|
747
|
+
const unchanged = reread.unchanged;
|
|
748
|
+
// FU-kerery: a FAIL evicts by the key read before the run, or by the key of the tree it ran on
|
|
749
|
+
// when that read faulted (null).
|
|
750
|
+
const failedKey = checkKeyVal ?? reread.key;
|
|
751
|
+
const stdout = res.bounded === true ? res.stdout : truncate(res.stdout);
|
|
752
|
+
const stderr = res.bounded === true ? res.stderr : truncate(res.stderr);
|
|
228
753
|
|
|
229
754
|
// Defect fix (2): the Python's TimeoutExpired arm returned neither stdout nor stderr, so a
|
|
230
755
|
// check that hung after printing its first failure told you only that it hung. Whatever the
|
|
231
756
|
// process produced before the kill is exactly what a reader needs.
|
|
232
757
|
if (res.error && res.error.code === 'ETIMEDOUT') {
|
|
758
|
+
this.evictFailed(failedKey);
|
|
233
759
|
const result = {
|
|
234
760
|
name: checkName,
|
|
235
761
|
command,
|
|
236
762
|
status: 'FAIL',
|
|
237
763
|
exitCode: TIMEOUT_EXIT_CODE,
|
|
238
764
|
error: 'TimeoutExpired',
|
|
239
|
-
stdout
|
|
240
|
-
stderr
|
|
765
|
+
stdout,
|
|
766
|
+
stderr,
|
|
241
767
|
};
|
|
242
|
-
this.decorate(result, command, res.stdout, res.stderr);
|
|
768
|
+
this.decorate(result, command, res.stdout, res.stderr, res);
|
|
243
769
|
return remember(result);
|
|
244
770
|
}
|
|
245
771
|
|
|
246
772
|
if (res.error) {
|
|
773
|
+
this.evictFailed(failedKey);
|
|
247
774
|
const result = {
|
|
248
775
|
name: checkName,
|
|
249
776
|
command,
|
|
250
777
|
status: 'FAIL',
|
|
251
778
|
exitCode: 1,
|
|
252
779
|
error: res.error.message,
|
|
253
|
-
stdout
|
|
254
|
-
stderr
|
|
780
|
+
stdout,
|
|
781
|
+
stderr,
|
|
255
782
|
};
|
|
256
|
-
this.decorate(result, command, res.stdout, res.stderr);
|
|
783
|
+
this.decorate(result, command, res.stdout, res.stderr, res);
|
|
257
784
|
return remember(result);
|
|
258
785
|
}
|
|
259
786
|
|
|
@@ -268,13 +795,18 @@ class VerificationContractRunner {
|
|
|
268
795
|
command,
|
|
269
796
|
status: passed ? 'PASS' : 'FAIL',
|
|
270
797
|
exitCode,
|
|
271
|
-
stdout
|
|
272
|
-
stderr
|
|
798
|
+
stdout,
|
|
799
|
+
stderr,
|
|
273
800
|
};
|
|
274
801
|
if (res.signal) {
|
|
275
802
|
result.error = `Terminated by signal ${res.signal}`;
|
|
276
803
|
}
|
|
277
|
-
this.decorate(result, command, res.stdout, res.stderr);
|
|
804
|
+
this.decorate(result, command, res.stdout, res.stderr, res);
|
|
805
|
+
if (passed) {
|
|
806
|
+
if (unchanged) this.storeReused(result, checkKeyVal, payloadDigest, res);
|
|
807
|
+
} else {
|
|
808
|
+
this.evictFailed(failedKey);
|
|
809
|
+
}
|
|
278
810
|
return remember(result);
|
|
279
811
|
}
|
|
280
812
|
|
|
@@ -338,4 +870,7 @@ module.exports = {
|
|
|
338
870
|
MAX_STREAM_CHARS,
|
|
339
871
|
MAX_FAILED_TESTS,
|
|
340
872
|
extractFailedTests,
|
|
873
|
+
extractFailedFiles,
|
|
874
|
+
parseTestCounts,
|
|
875
|
+
childCpuTicks,
|
|
341
876
|
};
|