@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
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const crypto = require('node:crypto');
|
|
8
|
+
const { taskStoreDir, taskRecord, resolveTaskScope } = require('./task-scope.js');
|
|
9
|
+
const { assertSafeTaskId } = require('./evidence-ledger.js');
|
|
10
|
+
const { readTaskState, updateTaskState } = require('./task-state.js');
|
|
11
|
+
const { storeFile, activeAttempt } = require('./task-attempts.js');
|
|
12
|
+
const { projectRoot: lifecycleProjectRoot } = require('./lifecycle/root.js');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Path of a task's retrieval-plan store.
|
|
16
|
+
*
|
|
17
|
+
* The task id is validated by the evidence ledger's own rule, unchanged: it becomes a filename, so
|
|
18
|
+
* it must never be able to name a path.
|
|
19
|
+
* @param {string} projectRoot
|
|
20
|
+
* @param {string} taskId
|
|
21
|
+
* @param {string|null} [slug]
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function planPath(projectRoot , taskId , slug = null) {
|
|
25
|
+
const dir = taskStoreDir({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug });
|
|
26
|
+
return path.join(dir, `${taskId}.json`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reads and validates one task's retrieval plan via readTaskState.
|
|
31
|
+
* Returns null when the plan file does not exist.
|
|
32
|
+
* @param {string} projectRoot
|
|
33
|
+
* @param {string} taskId
|
|
34
|
+
* @param {string|null} [slug]
|
|
35
|
+
* @param {any} [fsImpl]
|
|
36
|
+
* @returns {any|null}
|
|
37
|
+
*/
|
|
38
|
+
function readPlan(projectRoot , taskId , slug = null, fsImpl = fs) {
|
|
39
|
+
return readTaskState(fsImpl, taskRecord({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug, fsImpl }).read);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Updates a task's retrieval plan under the task-state lock.
|
|
44
|
+
* @param {string} projectRoot
|
|
45
|
+
* @param {string} taskId
|
|
46
|
+
* @param {string|null} slug
|
|
47
|
+
* @param {(disk: any|null) => any} build
|
|
48
|
+
* @param {any} [fsImpl]
|
|
49
|
+
* @returns {string} the written file path
|
|
50
|
+
*/
|
|
51
|
+
function updatePlan(projectRoot , taskId , slug , build , fsImpl = fs, prepare ) {
|
|
52
|
+
const { file, seed } = taskRecord({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug, fsImpl });
|
|
53
|
+
return updateTaskState({ fsImpl, file, build, prepare: () => { seed(); if (prepare) prepare(); } });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Move a syntactically unreadable plan aside before its replacement is written under the lock. */
|
|
57
|
+
function moveCorruptPlanAside(projectRoot , taskId , slug = null, fsImpl = fs) {
|
|
58
|
+
const file = planPath(projectRoot, taskId, slug);
|
|
59
|
+
if (!fsImpl.existsSync(file)) return null;
|
|
60
|
+
try {
|
|
61
|
+
JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
62
|
+
return null;
|
|
63
|
+
} catch (error ) {
|
|
64
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
65
|
+
const corruptFile = `${file}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
66
|
+
fsImpl.renameSync(file, corruptFile);
|
|
67
|
+
return corruptFile;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Mints a new plan id: rp_<base36 ms>_<4 hex>.
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function mintPlanId() {
|
|
76
|
+
return `rp_${Date.now().toString(36)}_${crypto.randomBytes(2).toString('hex')}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Formats declared intents from a plan: sorted, comma-joined, or 'none declared'.
|
|
81
|
+
*
|
|
82
|
+
* @param {any} plan
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
function declaredIntentList(plan , sep = ', ') {
|
|
86
|
+
const declaredIntents = Array.isArray(plan?.needs)
|
|
87
|
+
? plan.needs.map((n ) => n.intent).filter(Boolean).sort()
|
|
88
|
+
: [];
|
|
89
|
+
return declaredIntents.length > 0 ? declaredIntents.join(sep) : 'none declared';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolves a declared need from a plan for a given intent.
|
|
94
|
+
*
|
|
95
|
+
* @param {any} plan
|
|
96
|
+
* @param {string} intent
|
|
97
|
+
* @param {string} taskId
|
|
98
|
+
* @returns {{ ok: true, queryRef: string, need: any } | { ok: false, message: string }}
|
|
99
|
+
*/
|
|
100
|
+
function resolveNeed(plan , intent , taskId ) {
|
|
101
|
+
if (!plan) {
|
|
102
|
+
const message = `no retrieval plan is declared for task '${taskId}', so --need '${intent}' names nothing. Declare one first: doflow retrieval-plan --task-id ${taskId} --action declare --need ${intent}, then re-run.`;
|
|
103
|
+
return { ok: false, message };
|
|
104
|
+
}
|
|
105
|
+
const list = declaredIntentList(plan);
|
|
106
|
+
|
|
107
|
+
if (!plan.planId) {
|
|
108
|
+
const message = `the retrieval plan for task '${taskId}' was declared before query binding and has no plan id. Re-declare it: doflow retrieval-plan --task-id ${taskId} --action declare --need ${declaredIntentList(plan, ',')}, then re-run.`;
|
|
109
|
+
return { ok: false, message };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const need = Array.isArray(plan.needs) ? plan.needs.find((n ) => n.intent === intent) : null;
|
|
113
|
+
if (!need) {
|
|
114
|
+
const message = `'${intent}' is not a declared need of task '${taskId}' (plan ${plan.planId}). Declared: ${list}. Re-run with one of them, or re-declare the plan with this need.`;
|
|
115
|
+
return { ok: false, message };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!need.queryRef) {
|
|
119
|
+
const message = `the retrieval plan for task '${taskId}' was declared before query binding and has no plan id. Re-declare it: doflow retrieval-plan --task-id ${taskId} --action declare --need ${declaredIntentList(plan, ',')}, then re-run.`;
|
|
120
|
+
return { ok: false, message };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
queryRef: need.queryRef,
|
|
126
|
+
need,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Resolve the plan's query binding and the task's active attempt in one reusable operation. */
|
|
131
|
+
function resolveRetrievalBinding({ projectRoot, taskId, intent, slug = null, fsImpl = fs, attemptRoot = null } ) {
|
|
132
|
+
const plan = readPlan(projectRoot, taskId, slug, fsImpl);
|
|
133
|
+
const resolved = resolveNeed(plan, intent, taskId);
|
|
134
|
+
if (!resolved.ok) return resolved;
|
|
135
|
+
return {
|
|
136
|
+
ok: true,
|
|
137
|
+
plan,
|
|
138
|
+
queryRef: resolved.queryRef,
|
|
139
|
+
need: resolved.need,
|
|
140
|
+
attemptRef: activeAttemptRef(projectRoot, taskId, slug, fsImpl, attemptRoot),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Append one validated provider attempt under the retrieval-plan state lock. */
|
|
145
|
+
function appendRetrievalAttempt({
|
|
146
|
+
projectRoot, taskId, intent, provider, status, reason, note = null, indexState,
|
|
147
|
+
slug = null, attemptRef = undefined, fsImpl = fs, expectedCapability = undefined,
|
|
148
|
+
expectedPrimaryProvider = undefined, registeredProviders = undefined,
|
|
149
|
+
} ) {
|
|
150
|
+
const allowedIndexStates = ['FRESH', 'STALE', 'UNKNOWN', 'NOT_APPLICABLE'];
|
|
151
|
+
if (!allowedIndexStates.includes(indexState)) {
|
|
152
|
+
throw new Error(`attempt indexState '${indexState}' is invalid; expected one of ${allowedIndexStates.join(', ')}`);
|
|
153
|
+
}
|
|
154
|
+
const allowedReasons = {
|
|
155
|
+
EMPTY: ['empty-answer', 'index-stale', 'index-unverifiable'],
|
|
156
|
+
UNREACHED: ['provider-unavailable', 'policy-bypass', 'provider-error', 'timeout'],
|
|
157
|
+
UNVERIFIED: ['index-unverifiable'],
|
|
158
|
+
};
|
|
159
|
+
if (status === 'RETRIEVED') throw new Error('RETRIEVED is derived from evidence and cannot be appended as a provider attempt');
|
|
160
|
+
if (!Object.prototype.hasOwnProperty.call(allowedReasons, status) || !allowedReasons[status].includes(reason)) {
|
|
161
|
+
throw new Error(`attempt reason '${reason}' is not valid for status '${status}'`);
|
|
162
|
+
}
|
|
163
|
+
if (!Array.isArray(registeredProviders) || !registeredProviders.includes(provider)) {
|
|
164
|
+
throw new Error(`provider '${provider}' is not registered for this capability`);
|
|
165
|
+
}
|
|
166
|
+
if (note !== null && String(note).length > 160) throw new Error('attempt note is longer than 160 characters');
|
|
167
|
+
|
|
168
|
+
let savedAttempt = null;
|
|
169
|
+
const file = updatePlan(projectRoot, taskId, slug, (disk ) => {
|
|
170
|
+
if (disk === null) throw new Error(`the retrieval plan for task ${taskId} was removed during the update; re-declare it`);
|
|
171
|
+
const current = disk;
|
|
172
|
+
const targetNeed = Array.isArray(current.needs) ? current.needs.find((need ) => need.intent === intent) : null;
|
|
173
|
+
if (!targetNeed) throw new Error(`need '${intent}' missing from disk plan during update`);
|
|
174
|
+
if (expectedCapability !== undefined && targetNeed.capability !== expectedCapability) {
|
|
175
|
+
throw new Error(`need '${intent}' capability changed during update`);
|
|
176
|
+
}
|
|
177
|
+
if (expectedPrimaryProvider !== undefined && targetNeed.provider !== expectedPrimaryProvider) {
|
|
178
|
+
throw new Error(`need '${intent}' provider changed during update`);
|
|
179
|
+
}
|
|
180
|
+
if (provider === undefined || provider === null || String(provider).trim() === '') throw new Error('attempt provider is required');
|
|
181
|
+
if (!Array.isArray(targetNeed.attempts)) targetNeed.attempts = [];
|
|
182
|
+
const n = targetNeed.attempts.reduce((max , prior ) => Math.max(max, Number.isInteger(prior?.n) ? prior.n : 0), 0) + 1;
|
|
183
|
+
const role = provider === targetNeed.provider ? 'primary' : 'fallback';
|
|
184
|
+
savedAttempt = {
|
|
185
|
+
n, provider, capability: targetNeed.capability, role, status, reason,
|
|
186
|
+
note: note !== undefined && note !== null ? String(note) : null,
|
|
187
|
+
indexState,
|
|
188
|
+
attemptRef: attemptRef === undefined ? activeAttemptRef(projectRoot, taskId, slug, fsImpl) : attemptRef,
|
|
189
|
+
evidenceIds: [],
|
|
190
|
+
recordedAt: new Date().toISOString(),
|
|
191
|
+
recordedBy: 'caller',
|
|
192
|
+
};
|
|
193
|
+
targetNeed.attempts.push(savedAttempt);
|
|
194
|
+
return current;
|
|
195
|
+
}, fsImpl);
|
|
196
|
+
return { file, attempt: savedAttempt };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Reads the 070 active attempt ref for a task, returning `${taskId}#${token}` or null.
|
|
201
|
+
*
|
|
202
|
+
* Resolves the feature slug via resolveTaskScope, reads storeFile(lifecycleProjectRoot(projectRoot), featureSlug),
|
|
203
|
+
* and finds the active attempt for the task. If no slug, no store, no entry or no active attempt exists,
|
|
204
|
+
* or if any failure occurs finding or reading the store, returns null (IC-005: "no attempt" never fails the write).
|
|
205
|
+
*
|
|
206
|
+
* @param {string} projectRoot
|
|
207
|
+
* @param {string} taskId
|
|
208
|
+
* @param {string|null} [slug]
|
|
209
|
+
* @param {any} [fsImpl]
|
|
210
|
+
* @returns {string|null}
|
|
211
|
+
*/
|
|
212
|
+
function activeAttemptRef(projectRoot , taskId , slug = null, fsImpl = fs, attemptRoot = null) {
|
|
213
|
+
try {
|
|
214
|
+
const featureSlug = resolveTaskScope({ projectRoot, taskId, slug }).slug;
|
|
215
|
+
if (!featureSlug) return null;
|
|
216
|
+
|
|
217
|
+
const file = storeFile(attemptRoot || lifecycleProjectRoot(projectRoot), featureSlug);
|
|
218
|
+
if (!fsImpl.existsSync(file)) return null;
|
|
219
|
+
|
|
220
|
+
const data = readTaskState(fsImpl, file);
|
|
221
|
+
if (!data || !data.tasks || !data.tasks[taskId]) return null;
|
|
222
|
+
|
|
223
|
+
const active = activeAttempt(data.tasks[taskId]);
|
|
224
|
+
if (!active || active.token === undefined || active.token === null) return null;
|
|
225
|
+
|
|
226
|
+
return `${taskId}#${active.token}`;
|
|
227
|
+
} catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Binds active evidence to declared query refs and explains evidence that could have matched. */
|
|
233
|
+
function bindEvidence(plan , items ) {
|
|
234
|
+
const byNeed = new Map ();
|
|
235
|
+
const unbound = [];
|
|
236
|
+
const needs = Array.isArray(plan?.needs) ? plan.needs : [];
|
|
237
|
+
for (const need of needs) byNeed.set(need.intent, []);
|
|
238
|
+
for (const item of items) {
|
|
239
|
+
if (item.status !== 'active') continue;
|
|
240
|
+
const source = item.source || {};
|
|
241
|
+
const exact = needs.find((need ) => need.queryRef && need.queryRef === source.queryRef);
|
|
242
|
+
if (exact) {
|
|
243
|
+
if (plan.attemptRef && source.attemptRef && plan.attemptRef !== source.attemptRef) {
|
|
244
|
+
unbound.push({ id: item.id, cause: 'other-attempt' });
|
|
245
|
+
} else {
|
|
246
|
+
const matches = byNeed.get(exact.intent);
|
|
247
|
+
if (matches) matches.push(item);
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (source.queryRef) {
|
|
252
|
+
unbound.push({ id: item.id, cause: 'replaced-plan' });
|
|
253
|
+
} else if (needs.some((need ) => need.capability && need.capability === source.capability)) {
|
|
254
|
+
unbound.push({ id: item.id, cause: 'no-query-ref' });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { byNeed, unbound };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Selects the highest-precedence recorded attempt, with the earliest attempt winning ties. */
|
|
261
|
+
function deriveOutcome(need ) {
|
|
262
|
+
const precedence = { RETRIEVED: 0, EMPTY: 1, UNVERIFIED: 3, UNREACHED: 4 };
|
|
263
|
+
const attempts = (Array.isArray(need?.attempts) ? need.attempts : []).filter((attempt ) =>
|
|
264
|
+
!(attempt.recordedBy === 'derived' && attempt.basis !== 'report-need'
|
|
265
|
+
&& (!Array.isArray(attempt.evidenceIds) || attempt.evidenceIds.length === 0)));
|
|
266
|
+
const ranked = [...attempts].sort((a, b) => {
|
|
267
|
+
const rank = (attempt ) => attempt.status === 'EMPTY' && ['STALE', 'UNKNOWN'].includes(attempt.indexState)
|
|
268
|
+
? (attempt.indexState === 'STALE' ? 2 : 3)
|
|
269
|
+
: (precedence[attempt.status] ?? 4);
|
|
270
|
+
return rank(a) - rank(b) || a.n - b.n;
|
|
271
|
+
});
|
|
272
|
+
const winner = ranked[0];
|
|
273
|
+
return {
|
|
274
|
+
result: winner ? winner.status : 'UNREACHED',
|
|
275
|
+
resolvedBy: winner ? winner.n : null,
|
|
276
|
+
viaFallback: Boolean(winner && winner.role === 'fallback'),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = {
|
|
281
|
+
planPath,
|
|
282
|
+
readPlan,
|
|
283
|
+
updatePlan,
|
|
284
|
+
moveCorruptPlanAside,
|
|
285
|
+
mintPlanId,
|
|
286
|
+
resolveNeed,
|
|
287
|
+
resolveRetrievalBinding,
|
|
288
|
+
appendRetrievalAttempt,
|
|
289
|
+
activeAttemptRef,
|
|
290
|
+
declaredIntentList,
|
|
291
|
+
bindEvidence,
|
|
292
|
+
deriveOutcome,
|
|
293
|
+
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { spawnSync } = require('node:child_process');
|
|
8
|
+
const { validateFindings, countFindings, reviewAdvisories, invalidProjectCandidate, invalidPresetCandidate, telemetryProtocol } = require('./findings.js');
|
|
9
|
+
const { resolveActiveFeature } = require('../feature-resolve.js');
|
|
10
|
+
const { TelemetryWriter } = require('../trace/telemetry.js');
|
|
11
|
+
const { finishRuntime } = require('../cli-result.js');
|
|
12
|
+
const { handleReviewStart, validSlug } = require('./start.js');
|
|
13
|
+
const { crossCheckAuthor, authoringIdentities } = require('./identity.js');
|
|
14
|
+
const { isQualifiedId } = require('../../helper/package-ids.js');
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const TELEMETRY_COUNT_KEYS = Object.freeze([
|
|
23
|
+
'rounds', 'seats', 'seat_findings', 'converged', 'blocker', 'should_fix', 'note',
|
|
24
|
+
'open', 'fixed', 'refuted', 'user_accepted', 'pre_existing', 'escaped', 'confirmed',
|
|
25
|
+
'plausible', 'unverified_blockers', 'bug_classes',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// Inherited location variables would point the check at another repository (C-8).
|
|
29
|
+
const GIT_LOCATION_KEYS = Object.freeze([
|
|
30
|
+
'GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_COMMON_DIR',
|
|
31
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
function defaultGit(args , cwd , env ) {
|
|
35
|
+
const gitEnv = { ...env };
|
|
36
|
+
for (const key of GIT_LOCATION_KEYS) delete gitEnv[key];
|
|
37
|
+
return spawnSync('git', ['-C', cwd, ...args], { cwd, env: gitEnv, stdio: 'ignore' });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The project's `review.sameFamily` (119 L-C3), read for the checkout the verb runs in. A settings view that cannot
|
|
41
|
+
* load, or an invalid project or local value for the key (C-6: the resolver would fall back to the default without a
|
|
42
|
+
* word), is `warn` plus `unread`; the caller turns `unread` into an error. Never a crash, never a silent `refuse`. */
|
|
43
|
+
function readSameFamily(load , cwd , env ) {
|
|
44
|
+
try {
|
|
45
|
+
const resolution = load({ projectRoot: path.resolve(cwd), env }).get('review.sameFamily');
|
|
46
|
+
if (invalidProjectCandidate(resolution)) return { value: 'warn', unread: true };
|
|
47
|
+
return { value: resolution.value === 'refuse' ? 'refuse' : 'warn', unread: false };
|
|
48
|
+
} catch {
|
|
49
|
+
return { value: 'warn', unread: true };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The effective `review.preset` (C-2 closure), read for the checkout the verb runs in. A settings view that cannot
|
|
54
|
+
* load, or an invalid user, project or local value (the resolver would fall back to `auto` without a word), is `unread`;
|
|
55
|
+
* the caller turns `unread` into an error, so a broken setting never relaxes the check. */
|
|
56
|
+
function readPreset(load , cwd , env ) {
|
|
57
|
+
try {
|
|
58
|
+
const resolution = load({ projectRoot: path.resolve(cwd), env }).get('review.preset');
|
|
59
|
+
if (invalidPresetCandidate(resolution)) return { value: 'auto', unread: true };
|
|
60
|
+
return { value: typeof resolution.value === 'string' ? resolution.value : 'auto', unread: false };
|
|
61
|
+
} catch {
|
|
62
|
+
return { value: 'auto', unread: true };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A preset id: a core one as protocols.ts validates it (`^[a-z][a-z0-9-]*$`), or a package preset's qualified id
|
|
67
|
+
// (`<package>.<preset>`, 122 IC-003). Whether it is available and strict enough is the resolver's answer (IC-014).
|
|
68
|
+
const CORE_PRESET_ID = /^[a-z][a-z0-9-]*$/;
|
|
69
|
+
const presetIdShape = (value ) => typeof value === 'string' && (CORE_PRESET_ID.test(value) || isQualifiedId(value));
|
|
70
|
+
|
|
71
|
+
function usage(message , json , out ) {
|
|
72
|
+
out(json
|
|
73
|
+
? JSON.stringify({ ok: false, error: 'usage', message })
|
|
74
|
+
: `doflow review: ${message}`);
|
|
75
|
+
return finishRuntime(2);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hasValue(value ) {
|
|
79
|
+
return value !== undefined && value !== null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function emptyValue(value ) {
|
|
83
|
+
return typeof value === 'string' && value.trim().length === 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleReviewCommand({
|
|
87
|
+
action,
|
|
88
|
+
slug,
|
|
89
|
+
path: recordPath,
|
|
90
|
+
base,
|
|
91
|
+
preset,
|
|
92
|
+
adviceClient,
|
|
93
|
+
json = false,
|
|
94
|
+
cwd = process.cwd(),
|
|
95
|
+
env = process.env,
|
|
96
|
+
git = defaultGit,
|
|
97
|
+
loadSettings = (options ) => require('../settings/resolve.js').loadSettings(options),
|
|
98
|
+
writer,
|
|
99
|
+
resolve = resolveActiveFeature,
|
|
100
|
+
out = (line ) => console.log(line),
|
|
101
|
+
} = {}) {
|
|
102
|
+
if (action !== 'check' && action !== 'start') return usage('action must be check or start', json, out);
|
|
103
|
+
if (emptyValue(slug) || emptyValue(recordPath) || emptyValue(base) || emptyValue(preset)) return usage('flag values must not be empty', json, out);
|
|
104
|
+
if (action === 'start') {
|
|
105
|
+
// 095b IC-001: the start verb reads no record path, and the slug becomes a file name.
|
|
106
|
+
if (hasValue(recordPath)) return usage('--path is not valid with --action start', json, out);
|
|
107
|
+
if ((hasValue(slug) && !validSlug(slug)) || (hasValue(base) && typeof base !== 'string')) {
|
|
108
|
+
return usage('--slug must be a plain feature name and --base a string', json, out);
|
|
109
|
+
}
|
|
110
|
+
if (hasValue(preset) && !presetIdShape(preset)) {
|
|
111
|
+
return usage('--preset must be a preset id (lower-case letters, digits and hyphens, or <package>.<preset> for a package preset)', json, out);
|
|
112
|
+
}
|
|
113
|
+
return handleReviewStart({ slug: hasValue(slug) ? slug : null, base: hasValue(base) ? base : null, preset: hasValue(preset) ? preset : null, json, cwd, env, resolve, adviceClient, out });
|
|
114
|
+
}
|
|
115
|
+
if (hasValue(base)) return usage('--base is valid only with --action start', json, out);
|
|
116
|
+
if (hasValue(preset)) return usage('--preset is valid only with --action start', json, out);
|
|
117
|
+
if (hasValue(slug) && hasValue(recordPath)) return usage('use either --slug or --path, not both', json, out);
|
|
118
|
+
if ((hasValue(slug) && typeof slug !== 'string') || (hasValue(recordPath) && typeof recordPath !== 'string')) {
|
|
119
|
+
return usage('--slug and --path values must be strings', json, out);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const telemetryWriter = writer || new TelemetryWriter({ env, start: cwd });
|
|
123
|
+
let file = null;
|
|
124
|
+
let resolverMessage = null;
|
|
125
|
+
if (hasValue(recordPath)) {
|
|
126
|
+
file = path.resolve(cwd, recordPath);
|
|
127
|
+
} else {
|
|
128
|
+
let feature ;
|
|
129
|
+
try {
|
|
130
|
+
feature = (resolve )({ projectRoot: cwd, slug: hasValue(slug) ? slug : null });
|
|
131
|
+
} catch (error ) {
|
|
132
|
+
feature = { error: 'resolver-failed', message: error?.message || String(error) };
|
|
133
|
+
}
|
|
134
|
+
if (feature?.error || typeof feature?.featureFolder !== 'string' || !feature.featureFolder) {
|
|
135
|
+
resolverMessage = typeof feature?.message === 'string' && feature.message
|
|
136
|
+
? feature.message
|
|
137
|
+
: (typeof feature?.error === 'string' && feature.error ? feature.error : 'feature could not be resolved');
|
|
138
|
+
} else {
|
|
139
|
+
file = path.resolve(feature.featureFolder, 'review', 'findings.json');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const errors = [];
|
|
144
|
+
const errorDetails = new Map ();
|
|
145
|
+
const ancestryAdvisories = [];
|
|
146
|
+
const identityAdvisories = [];
|
|
147
|
+
let sameFamily = 'warn';
|
|
148
|
+
let reviewPreset = 'auto';
|
|
149
|
+
let authors = [];
|
|
150
|
+
let record = null;
|
|
151
|
+
if (file === null) {
|
|
152
|
+
errors.push('missing');
|
|
153
|
+
errorDetails.set('missing', resolverMessage || 'feature record could not be located');
|
|
154
|
+
} else {
|
|
155
|
+
let raw ;
|
|
156
|
+
try {
|
|
157
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
158
|
+
} catch (error ) {
|
|
159
|
+
if (error?.code === 'ENOENT') errors.push('missing');
|
|
160
|
+
else errors.push('unreadable');
|
|
161
|
+
if (error?.message) errorDetails.set(errors[0], error.message);
|
|
162
|
+
raw = '';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (errors.length === 0) {
|
|
166
|
+
// Settings are read only for a record that declares an author: nothing else depends on them (L-C3).
|
|
167
|
+
let peek = null;
|
|
168
|
+
try { peek = JSON.parse(raw); } catch { /* validateFindings reports `json` */ }
|
|
169
|
+
if (peek !== null && typeof peek === 'object' && Object.hasOwn(peek, 'author')) {
|
|
170
|
+
const read = readSameFamily(loadSettings, cwd, env);
|
|
171
|
+
sameFamily = read.value;
|
|
172
|
+
if (read.unread) errors.push('same-family-setting-unread');
|
|
173
|
+
}
|
|
174
|
+
// A record that omits `protocol` is held to the effective `review.preset` (C-2 closure), so it is read for every record.
|
|
175
|
+
if (peek !== null && typeof peek === 'object') {
|
|
176
|
+
const read = readPreset(loadSettings, cwd, env);
|
|
177
|
+
reviewPreset = read.value;
|
|
178
|
+
if (read.unread) errors.push('review-preset-setting-unread');
|
|
179
|
+
}
|
|
180
|
+
authors = authoringIdentities({ slug: peek?.slug, cwd, env, record: peek, fsImpl: fs });
|
|
181
|
+
const validation = validateFindings(raw, { sameFamily, preset: reviewPreset, authors });
|
|
182
|
+
for (const error of validation.errors) errors.push(error); // a spread overflows the stack at ~200k errors (C-6)
|
|
183
|
+
if (!validation.errors.includes('json')) {
|
|
184
|
+
try { record = JSON.parse(raw); } catch { record = null; }
|
|
185
|
+
}
|
|
186
|
+
if (validation.ok && record) {
|
|
187
|
+
if (Object.hasOwn(record, 'author')) identityAdvisories.push(...crossCheckAuthor({ record, cwd, env }));
|
|
188
|
+
for (const finding of record.converged) {
|
|
189
|
+
if (typeof finding.preExistingAt !== 'string') continue;
|
|
190
|
+
let status ;
|
|
191
|
+
try {
|
|
192
|
+
status = (git )(
|
|
193
|
+
['merge-base', '--is-ancestor', finding.preExistingAt, record.reviewedHead],
|
|
194
|
+
path.dirname(file),
|
|
195
|
+
env,
|
|
196
|
+
)?.status;
|
|
197
|
+
} catch {
|
|
198
|
+
status = null;
|
|
199
|
+
}
|
|
200
|
+
if (status === 1) errors.push(`preExistingAt-not-ancestor:${finding.id}`);
|
|
201
|
+
else if (status !== 0) ancestryAdvisories.push(`ancestry-unchecked:${finding.id}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const advisories = (record && validateFindings(record, { sameFamily, preset: reviewPreset, authors }).ok
|
|
208
|
+
? reviewAdvisories(record, { sameFamily, authors }).concat(identityAdvisories)
|
|
209
|
+
: []).concat(ancestryAdvisories).sort();
|
|
210
|
+
const valid = errors.length === 0;
|
|
211
|
+
const counts = valid && record ? countFindings(record) : null;
|
|
212
|
+
const result = { schema: 1, path: file, valid, errors, advisories, counts };
|
|
213
|
+
|
|
214
|
+
if (json) {
|
|
215
|
+
out(JSON.stringify(result));
|
|
216
|
+
} else {
|
|
217
|
+
out(`review check: ${valid ? 'valid' : 'invalid'} (${errors.length} errors, ${advisories.length} advisories)`);
|
|
218
|
+
for (const error of errors) {
|
|
219
|
+
const detail = errorDetails.get(error);
|
|
220
|
+
out(`error: ${error}${detail ? `: ${detail}` : ''}`);
|
|
221
|
+
}
|
|
222
|
+
for (const advisory of advisories) out(`advisory: ${advisory}`);
|
|
223
|
+
if (counts) out(Object.entries(counts).map(([key, value]) => `${key}=${value}`).join(' '));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Only a validated slug is a feature slug; an invalid record's slug can hold anything (C-1, IC-004).
|
|
227
|
+
const task = valid && record && typeof record.slug === 'string' ? record.slug : null;
|
|
228
|
+
const telemetryRecord = {
|
|
229
|
+
kind: 'review.run',
|
|
230
|
+
task,
|
|
231
|
+
depth: null,
|
|
232
|
+
risk: null,
|
|
233
|
+
protocol: null,
|
|
234
|
+
head: null,
|
|
235
|
+
...Object.fromEntries(TELEMETRY_COUNT_KEYS.map((key) => [key, null])),
|
|
236
|
+
advisories: advisories.length,
|
|
237
|
+
errors: errors.length,
|
|
238
|
+
};
|
|
239
|
+
if (counts && record) Object.assign(telemetryRecord, counts, { depth: record.depth ?? null, risk: record.risk ?? null, protocol: telemetryProtocol(record.protocol?.id), head: record.reviewedHead });
|
|
240
|
+
try { telemetryWriter.write([telemetryRecord]); } catch { /* telemetry never changes the check result */ }
|
|
241
|
+
|
|
242
|
+
return finishRuntime(valid ? 0 : 1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = { handleReviewCommand };
|
|
246
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// 122 IC-014: `dominates(P, B, context)`, the strictness comparison between two protocol presets. Pure:
|
|
4
|
+
// no filesystem, no throw. P dominates B when every field that makes a review or debate stricter is at
|
|
5
|
+
// least as strict in P. `context` carries the ordered core tiers (models.json `dispatchTiers.tiers`,
|
|
6
|
+
// loosest first) and the synthesizer lattice (protocols.json `synthesizerStrength`). An unknown tier or
|
|
7
|
+
// synthesizer has no rank and never dominates: no `indexOf` of -1 is read as a rank.
|
|
8
|
+
|
|
9
|
+
const FAMILY_STRICTNESS = { any: 0, 'different-from-author': 1 };
|
|
10
|
+
|
|
11
|
+
const isObject = (value ) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
12
|
+
|
|
13
|
+
/** A rank from `ranks`, or null when `key` has none. */
|
|
14
|
+
function rankOf(ranks , key ) {
|
|
15
|
+
return typeof key === 'string' && Object.hasOwn(ranks, key) && Number.isInteger(ranks[key]) ? ranks[key] : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** True when seat `p` covers seat `b`: a lens superset, a family at least as strict, a tier at least as high. */
|
|
19
|
+
function seatCovers(p , b , tierRank ) {
|
|
20
|
+
if (!isObject(p) || !isObject(b) || !isObject(p.model) || !isObject(b.model)) return false;
|
|
21
|
+
const pLenses = new Set(Array.isArray(p.lenses) ? p.lenses : []);
|
|
22
|
+
if (!(Array.isArray(b.lenses) ? b.lenses : []).every((lens ) => pLenses.has(lens))) return false;
|
|
23
|
+
const pFamily = rankOf(FAMILY_STRICTNESS, p.model.family ?? 'any');
|
|
24
|
+
const bFamily = rankOf(FAMILY_STRICTNESS, b.model.family ?? 'any');
|
|
25
|
+
if (pFamily === null || bFamily === null || pFamily < bFamily) return false;
|
|
26
|
+
const pTier = rankOf(tierRank, p.model.tier);
|
|
27
|
+
const bTier = rankOf(tierRank, b.model.tier);
|
|
28
|
+
return pTier !== null && bTier !== null && pTier >= bTier;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* True when every B seat maps to a distinct P seat that covers it: a maximum bipartite matching by
|
|
33
|
+
* augmenting paths (Kuhn), so a first-fit choice never hides a matching that exists. Seats are few.
|
|
34
|
+
*/
|
|
35
|
+
function seatsMatch(pSeats , bSeats , tierRank ) {
|
|
36
|
+
if (bSeats.length > pSeats.length) return false;
|
|
37
|
+
const covers = bSeats.map((b) => pSeats.map((p) => seatCovers(p, b, tierRank)));
|
|
38
|
+
const owner = new Array(pSeats.length).fill(-1); // P seat index -> B seat index
|
|
39
|
+
const augment = (bi , seen ) => {
|
|
40
|
+
for (let pi = 0; pi < pSeats.length; pi++) {
|
|
41
|
+
if (!covers[bi][pi] || seen[pi]) continue;
|
|
42
|
+
seen[pi] = true;
|
|
43
|
+
if (owner[pi] === -1 || augment(owner[pi], seen)) { owner[pi] = bi; return true; }
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
return bSeats.every((_, bi) => augment(bi, new Array(pSeats.length).fill(false)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function dominates(P , B , context = {}) {
|
|
51
|
+
if (!isObject(P) || !isObject(B) || !isObject(context)) return false;
|
|
52
|
+
const { tiers, synthesizerStrength } = context;
|
|
53
|
+
if (!Array.isArray(tiers) || !isObject(synthesizerStrength)) return false;
|
|
54
|
+
const tierRank = Object.fromEntries(tiers.map((tier , i ) => [tier, i]));
|
|
55
|
+
|
|
56
|
+
if (P.kind !== B.kind || P.decider !== B.decider) return false;
|
|
57
|
+
if (!Array.isArray(P.seats) || !Array.isArray(B.seats) || !seatsMatch(P.seats, B.seats, tierRank)) return false;
|
|
58
|
+
if (!isObject(P.rounds) || !isObject(B.rounds)) return false;
|
|
59
|
+
for (const field of ['default', 'min']) {
|
|
60
|
+
if (!Number.isInteger(P.rounds[field]) || !Number.isInteger(B.rounds[field]) || P.rounds[field] < B.rounds[field]) return false;
|
|
61
|
+
}
|
|
62
|
+
if (B.crossAnswer === true && P.crossAnswer !== true) return false;
|
|
63
|
+
const pSynth = rankOf(synthesizerStrength, P.synthesizer);
|
|
64
|
+
const bSynth = rankOf(synthesizerStrength, B.synthesizer);
|
|
65
|
+
return pSynth !== null && bSynth !== null && pSynth >= bSynth;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { dominates };
|