@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
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Worker readiness (feature 081 B.1 and B.2): discover (IC-004), probe facts and the probe
|
|
7
|
+
* record (IC-005), model-backed checks (IC-006), freshness (IC-007), the identity-forwarding
|
|
8
|
+
* observation (IC-023), and the needs vocabulary, eligibility, describe and select (IC-008,
|
|
9
|
+
* IC-009, IC-010). DEC-002, DEC-003, DEC-004, DEC-006, DEC-009, DEC-018, DEC-019, DEC-020,
|
|
10
|
+
* DEC-031, DEC-044. Describe and select write nothing.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here reads a credential: login is learned from a probe's exit status only, probe stdout
|
|
13
|
+
* is read up to 64 KiB and never stored, and the only paths read under the home directory are the
|
|
14
|
+
* unit binding `stateDir()/sessions/<unit>/bound` and `sessions.log` (IC-030).
|
|
15
|
+
*
|
|
16
|
+
* Harness descriptors come from the registry's `worker` blocks, or from `descriptors`
|
|
17
|
+
* (`{ <harnessId>: <worker block> }`) passed in process by tests (DEC-036). When `descriptors` is
|
|
18
|
+
* given it is the whole set: only the harnesses it names are discovered, in registry order, so a
|
|
19
|
+
* test never looks up a real harness binary that shares the node directory (NFR-002).
|
|
20
|
+
*
|
|
21
|
+
* The probe record is written through `updateTaskState`, which stamps the store's own `version`;
|
|
22
|
+
* the IC-005 record, whose `version` is the harness's version banner, is therefore kept under
|
|
23
|
+
* `record` in the file: `{ harness, record: { harness, at, binaryPath, version, descriptorDigest,
|
|
24
|
+
* facts, modelChecks } }`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('node:fs');
|
|
28
|
+
const path = require('node:path');
|
|
29
|
+
const crypto = require('node:crypto');
|
|
30
|
+
const { execFileSync } = require('node:child_process');
|
|
31
|
+
const { workerCatalogue } = require('./catalogue.js');
|
|
32
|
+
const { workerGate } = require('./active.js');
|
|
33
|
+
const { bindingsOf } = require('./models.js');
|
|
34
|
+
const { knownWorkerIds } = require('../../registry/worker-ids.js');
|
|
35
|
+
const { runProbe, PROBE_TIMEOUT_MS } = require('../health.js');
|
|
36
|
+
const { updateTaskState, readTaskState } = require('../task-state.js');
|
|
37
|
+
const { stateDir } = require('../context/identity.js');
|
|
38
|
+
const { configHome } = require('../config-home.js');
|
|
39
|
+
const { loadRegistry } = require('../../registry/index.js');
|
|
40
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
41
|
+
const { spawnBounded } = require('./process-tree.js');
|
|
42
|
+
const { parserFor, PARSERS } = require('./parsers.js');
|
|
43
|
+
const { enforcementFor, UnknownEnforcementTarget } = require('../../lifecycle/coverage.js');
|
|
44
|
+
|
|
45
|
+
/** A recorded fact is effective for 24 hours against a matching discover (DEC-019). */
|
|
46
|
+
const PROBE_MAX_AGE_MS = 86400000;
|
|
47
|
+
/** Probe stdout is read up to this many bytes and never stored. */
|
|
48
|
+
const PROBE_STDOUT_MAX = 65536;
|
|
49
|
+
const VERSION_MAX = 80;
|
|
50
|
+
/** IC-023: how much of `sessions.log` is read for a START line. */
|
|
51
|
+
const SESSIONS_LOG_LINES = 500;
|
|
52
|
+
const FACTS = Object.freeze(['installed', 'runnable', 'hooksLoaded', 'identityForwarded', 'guidanceLoaded', 'login', 'quota', 'model']);
|
|
53
|
+
|
|
54
|
+
/** A failure that is the caller's, not a refusal: exit 2 `usage`. */
|
|
55
|
+
class ReadinessError extends Error {
|
|
56
|
+
|
|
57
|
+
constructor(code , message ) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.name = 'ReadinessError';
|
|
60
|
+
this.code = code;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const iso = (ms ) => new Date(ms).toISOString();
|
|
65
|
+
const clock = (now ) => (typeof now === 'function' ? now : () => (typeof now === 'number' ? now : Date.now()));
|
|
66
|
+
const fact = (state , reason , at , basis = 'free') => ({ state, reason, at, basis });
|
|
67
|
+
const supported = (entry ) => entry?.status === 'supported';
|
|
68
|
+
|
|
69
|
+
const { descriptorDigest } = require('./catalogue.js');
|
|
70
|
+
|
|
71
|
+
/** `[{ harness, worker }]` in registry order: the registry's blocks, or exactly the injected ones. */
|
|
72
|
+
function workerDescriptors({ descriptors, registry, view } = {}) {
|
|
73
|
+
const reg = registry || loadRegistry({ repoRoot: REPO_ROOT });
|
|
74
|
+
const ids = knownWorkerIds({ registry: reg, view }).ids;
|
|
75
|
+
if (!descriptors) return workerCatalogue({ registry: reg, view }).map((entry ) => ({ harness: entry.id, worker: entry.worker }));
|
|
76
|
+
const unknown = Object.keys(descriptors).filter((id) => !ids.includes(id));
|
|
77
|
+
if (unknown.length) throw new ReadinessError('usage', `descriptors name unregistered harness(es): ${unknown.join(', ')}; registered: ${ids.join(', ')}`);
|
|
78
|
+
return ids.filter((id) => Object.hasOwn(descriptors, id)).map((id) => ({ harness: id, worker: descriptors[id] }));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The first executable `binary` on `env.PATH`, or null. Starts no process. */
|
|
82
|
+
function findOnPath(binary , env , platform = process.platform) {
|
|
83
|
+
const dirs = String(env?.PATH ?? '').split(path.delimiter).filter(Boolean);
|
|
84
|
+
const suffixes = platform === 'win32'
|
|
85
|
+
? ['', ...String(env?.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)]
|
|
86
|
+
: [''];
|
|
87
|
+
for (const dir of dirs) {
|
|
88
|
+
for (const suffix of suffixes) {
|
|
89
|
+
const candidate = path.join(dir, binary + suffix);
|
|
90
|
+
try {
|
|
91
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
92
|
+
if (fs.statSync(candidate).isFile()) return candidate;
|
|
93
|
+
} catch {
|
|
94
|
+
// not here
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The first output line, control characters removed, trimmed, cut to 80 characters. */
|
|
102
|
+
function versionLine(output ) {
|
|
103
|
+
const first = [...String(output ?? '').split('\n')[0]]
|
|
104
|
+
.filter((character) => {
|
|
105
|
+
const point = character.codePointAt(0) ;
|
|
106
|
+
return point >= 0x20 && point !== 0x7f;
|
|
107
|
+
})
|
|
108
|
+
.join('')
|
|
109
|
+
.trim();
|
|
110
|
+
return first ? first.slice(0, VERSION_MAX) : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** IC-004 for one harness. */
|
|
114
|
+
function discoverOne({ harness, worker } , { env, cwd, now, platform } ) {
|
|
115
|
+
const cli = worker?.cli;
|
|
116
|
+
if (!supported(cli)) {
|
|
117
|
+
return { harness, binary: null, present: false, path: null, version: null, reason: 'cli-unknown', at: iso(now()) };
|
|
118
|
+
}
|
|
119
|
+
const found = findOnPath(cli.binary, env, platform);
|
|
120
|
+
if (!found) return { harness, binary: cli.binary, present: false, path: null, version: null, reason: 'not-on-path', at: iso(now()) };
|
|
121
|
+
const execFileImpl = (bin , args , options ) => execFileSync(bin, args, { ...options, env, cwd });
|
|
122
|
+
const result = runProbe([found, ...cli.versionArgv], { execFileImpl, timeoutMs: PROBE_TIMEOUT_MS });
|
|
123
|
+
const reason = result.ok ? null : (result.timedOut ? 'timeout' : 'version-failed');
|
|
124
|
+
return { harness, binary: cli.binary, present: true, path: found, version: result.ok ? versionLine(result.output) : null, reason, at: iso(now()) };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* IC-004: looks every harness's binary up on `env.PATH` and runs its `versionArgv`. Writes nothing.
|
|
129
|
+
* @returns {{ disposition: 'discovered', at: string, harnesses: Object[] }}
|
|
130
|
+
*/
|
|
131
|
+
function discover({ descriptors, registry, view, env = process.env, cwd = process.cwd(), now = Date.now, platform = process.platform } = {}) {
|
|
132
|
+
const tick = clock(now);
|
|
133
|
+
const at = iso(tick());
|
|
134
|
+
const harnesses = workerDescriptors({ descriptors, registry, view }).map((entry) => discoverOne(entry, { env, cwd, now: tick, platform }));
|
|
135
|
+
return { disposition: 'discovered', at, harnesses };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** A fresh forwarding-probe identity (IC-023). */
|
|
139
|
+
function newProbeIdentity() {
|
|
140
|
+
return { run: `probe${crypto.randomBytes(4).toString('hex')}`, lineage: `p${crypto.randomBytes(4).toString('hex')}` };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The caller's environment with IC-023's probe identity set over it. */
|
|
144
|
+
function probeEnv(env , identity , root ) {
|
|
145
|
+
return {
|
|
146
|
+
...env,
|
|
147
|
+
DOFLOW_RUN_ID: identity.run,
|
|
148
|
+
DOFLOW_LINEAGE_ID: identity.lineage,
|
|
149
|
+
DOFLOW_ATTEMPT: '1',
|
|
150
|
+
DOFLOW_SESSION_ROLE: 'worker',
|
|
151
|
+
DOFLOW_WORKTREE: root,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** True when `S/sessions/<unit>/bound` is a regular, non-empty file. Never follows a link. */
|
|
156
|
+
function bindingObserved(env , unit ) {
|
|
157
|
+
const root = stateDir(env);
|
|
158
|
+
if (root === null) return false;
|
|
159
|
+
try {
|
|
160
|
+
const stat = fs.lstatSync(path.join(root, 'sessions', unit, 'bound'));
|
|
161
|
+
return stat.isFile() && stat.size > 0;
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function samePath(a , b ) {
|
|
168
|
+
if (a === b) return true;
|
|
169
|
+
try {
|
|
170
|
+
return fs.realpathSync.native(a) === fs.realpathSync.native(b);
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** True when the last 500 lines of `sessions.log` hold a START line at or after `startedAtMs`,
|
|
177
|
+
* truncated to the second, for `cwd`. */
|
|
178
|
+
function sessionStartLogged(env , cwd , startedAtMs ) {
|
|
179
|
+
const { dir } = configHome(env ?? {});
|
|
180
|
+
if (dir === null) return false;
|
|
181
|
+
let text ;
|
|
182
|
+
try {
|
|
183
|
+
text = fs.readFileSync(path.join(dir, 'sessions.log'), 'utf8');
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const since = Math.floor(startedAtMs / 1000) * 1000;
|
|
188
|
+
for (const line of text.split('\n').filter(Boolean).slice(-SESSIONS_LOG_LINES)) {
|
|
189
|
+
const match = /^(\S+) START (\S+) (.+)$/.exec(line);
|
|
190
|
+
if (!match) continue;
|
|
191
|
+
const ts = Date.parse(match[1]);
|
|
192
|
+
if (Number.isFinite(ts) && ts >= since && samePath(match[3], cwd)) return true;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* IC-023: `hooksLoaded` and `identityForwarded` after a forwarding probe for `identity` ended.
|
|
199
|
+
* `attempt` names the unit's last part: 1 for a probe, the claim token for a run's `launchObserved`.
|
|
200
|
+
*/
|
|
201
|
+
function observeForwarding({ env, identity, attempt = 1, cwd, startedAtMs, timedOut = false, at, basis = 'free' } ) {
|
|
202
|
+
if (timedOut) return { hooksLoaded: fact('unknown', 'timeout', at, basis), identityForwarded: fact('unknown', 'timeout', at, basis) };
|
|
203
|
+
if (bindingObserved(env, `L.${identity.run}.${identity.lineage}.${attempt}`)) {
|
|
204
|
+
return { hooksLoaded: fact('yes', 'unit-binding-observed', at, basis), identityForwarded: fact('yes', 'unit-binding-observed', at, basis) };
|
|
205
|
+
}
|
|
206
|
+
if (sessionStartLogged(env, cwd, startedAtMs)) {
|
|
207
|
+
return { hooksLoaded: fact('yes', 'session-start-logged', at, basis), identityForwarded: fact('no', 'no-unit-binding', at, basis) };
|
|
208
|
+
}
|
|
209
|
+
return { hooksLoaded: fact('no', 'no-session-start-observed', at, basis), identityForwarded: fact('unknown', 'hooks-not-loaded', at, basis) };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** A bounded probe run: the spawn result plus its start and end times. */
|
|
213
|
+
async function runBounded(binary , argv , { env, cwd, timeoutMs, now } ) {
|
|
214
|
+
const startedAtMs = now();
|
|
215
|
+
const result = await spawnBounded({ binary, argv, cwd, env, timeoutMs, maxStdout: PROBE_STDOUT_MAX });
|
|
216
|
+
return { result, startedAtMs, at: iso(now()) };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** `yes` on exit 0, `no` on another exit code, `unknown` for a timeout, a signal or a spawn error. */
|
|
220
|
+
function exitFact(result , at , basis = 'free') {
|
|
221
|
+
if (result.status === 'timeout') return fact('unknown', 'timeout', at, basis);
|
|
222
|
+
if (result.status === 'error') return fact('unknown', `spawn-failed ${result.error}`, at, basis);
|
|
223
|
+
if (result.code === 0) return fact('yes', 'exit 0', at, basis);
|
|
224
|
+
if (Number.isInteger(result.code)) return fact('no', `exit ${result.code}`, at, basis);
|
|
225
|
+
return fact('unknown', `signal ${result.signal}`, at, basis);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The guidance rule: exit 0 with the marker in stdout is `yes`, exit 0 without it `no`. */
|
|
229
|
+
function guidanceFact(result , marker , at , basis = 'free') {
|
|
230
|
+
const exit = exitFact(result, at, basis);
|
|
231
|
+
if (exit.state !== 'yes') return exit.state === 'no' ? fact('unknown', exit.reason, at, basis) : exit;
|
|
232
|
+
if (!marker) return fact('unknown', 'no-marker-declared', at, basis);
|
|
233
|
+
return String(result.stdout).includes(marker) ? fact('yes', 'marker-found', at, basis) : fact('no', 'marker-absent', at, basis);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function installedFact(found ) {
|
|
237
|
+
if (found.reason === null) return fact('yes', 'version-answered', found.at);
|
|
238
|
+
if (found.reason === 'not-on-path' || found.reason === 'version-failed') return fact('no', found.reason, found.at);
|
|
239
|
+
return fact('unknown', found.reason, found.at);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** IC-005: the eight free facts for one harness. */
|
|
243
|
+
async function freeFacts(worker , found , { env, root, now, probeIdentity } ) {
|
|
244
|
+
const facts = { installed: installedFact(found) };
|
|
245
|
+
const bounded = (argv , childEnv = env) => runBounded(found.path, argv, { env: childEnv, cwd: root, timeoutMs: PROBE_TIMEOUT_MS, now });
|
|
246
|
+
const gate = (entry ) => {
|
|
247
|
+
if (!supported(entry)) return fact('unknown', 'no-free-probe-declared', iso(now()));
|
|
248
|
+
if (!found.present) return fact('unknown', 'not-installed', iso(now()));
|
|
249
|
+
return null;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
facts.runnable = gate(worker.runnableProbe);
|
|
253
|
+
if (!facts.runnable) {
|
|
254
|
+
const { result, at } = await bounded(worker.runnableProbe.argv);
|
|
255
|
+
facts.runnable = exitFact(result, at);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const hooksGate = gate(worker.hooksProbe);
|
|
259
|
+
if (hooksGate) {
|
|
260
|
+
facts.hooksLoaded = hooksGate;
|
|
261
|
+
facts.identityForwarded = { ...hooksGate };
|
|
262
|
+
} else {
|
|
263
|
+
const identity = probeIdentity();
|
|
264
|
+
const { result, startedAtMs, at } = await bounded(worker.hooksProbe.argv, probeEnv(env, identity, root));
|
|
265
|
+
Object.assign(facts, observeForwarding({ env, identity, cwd: root, startedAtMs, timedOut: result.status === 'timeout', at }));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
facts.guidanceLoaded = gate(worker.guidanceProbe);
|
|
269
|
+
if (!facts.guidanceLoaded) {
|
|
270
|
+
const { result, at } = await bounded(worker.guidanceProbe.argv);
|
|
271
|
+
facts.guidanceLoaded = guidanceFact(result, worker.guidanceProbe.marker, at);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
facts.login = gate(worker.loginProbe);
|
|
275
|
+
if (!facts.login) {
|
|
276
|
+
const { result, at } = await bounded(worker.loginProbe.argv);
|
|
277
|
+
facts.login = exitFact(result, at);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const at = iso(now());
|
|
281
|
+
facts.quota = fact('unknown', 'no-free-probe', at);
|
|
282
|
+
facts.model = fact('unknown', 'no-free-probe', at);
|
|
283
|
+
return facts;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** IC-006: runs one declared model check and returns the facts it establishes and its usage. */
|
|
287
|
+
async function runModelCheck(worker , found , check , budgetTokens , { env, root, now, probeIdentity } ) {
|
|
288
|
+
const identity = probeIdentity();
|
|
289
|
+
const { result, startedAtMs, at } = await runBounded(found.path, check.argv, {
|
|
290
|
+
env: probeEnv(env, identity, root), cwd: root, timeoutMs: check.timeoutSeconds * 1000, now,
|
|
291
|
+
});
|
|
292
|
+
const basis = 'model-backed';
|
|
293
|
+
const facts = {};
|
|
294
|
+
const establishes = new Set(check.establishes);
|
|
295
|
+
if (establishes.has('hooksLoaded') || establishes.has('identityForwarded')) {
|
|
296
|
+
const observed = observeForwarding({ env, identity, cwd: root, startedAtMs, timedOut: result.status === 'timeout', at, basis });
|
|
297
|
+
for (const key of ['hooksLoaded', 'identityForwarded']) if (establishes.has(key)) facts[key] = observed[key];
|
|
298
|
+
}
|
|
299
|
+
if (establishes.has('guidanceLoaded')) {
|
|
300
|
+
facts.guidanceLoaded = guidanceFact(result, supported(worker.guidanceProbe) ? worker.guidanceProbe.marker : undefined, at, basis);
|
|
301
|
+
}
|
|
302
|
+
if (establishes.has('login')) facts.login = exitFact(result, at, basis);
|
|
303
|
+
|
|
304
|
+
let measured = null;
|
|
305
|
+
if (supported(worker.structuredOutput)) {
|
|
306
|
+
const parser = parserFor(worker.structuredOutput.parser).create();
|
|
307
|
+
for (const line of String(result.stdout).split('\n')) if (line) parser.line(line);
|
|
308
|
+
measured = parser.end().usage?.tokens ?? null;
|
|
309
|
+
}
|
|
310
|
+
const tokens = measured ?? check.estimateTokens;
|
|
311
|
+
const usage = { tokens, basis: measured === null ? 'estimated' : 'measured', budget: budgetTokens };
|
|
312
|
+
return { facts, summary: { at, usage, overBudget: tokens > budgetTokens } };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function probeFile(root , harness ) {
|
|
316
|
+
return path.join(root, '.doflow', 'state', 'worker-probes', `${harness}.json`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** The IC-005 record for `harness` under `root`, or null when it was never probed. */
|
|
320
|
+
function readProbeRecord(root , harness ) {
|
|
321
|
+
const data = readTaskState(fs, probeFile(root, harness));
|
|
322
|
+
return data && data.record && typeof data.record === 'object' ? data.record : null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const sameBinding = (a , b ) => Boolean(a) && Boolean(b)
|
|
326
|
+
&& a.binaryPath === b.binaryPath && a.version === b.version && a.descriptorDigest === b.descriptorDigest;
|
|
327
|
+
|
|
328
|
+
/** Fresh when `at` is at most 24 hours old and not in the future: a skewed clock never extends it. */
|
|
329
|
+
const freshAt = (at , nowMs ) => {
|
|
330
|
+
const ms = Date.parse(at);
|
|
331
|
+
return Number.isFinite(ms) && nowMs - ms >= 0 && nowMs - ms <= PROBE_MAX_AGE_MS;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The record to write over `prior`: a free `unknown` keeps a fresh model-backed `yes` or `no`
|
|
336
|
+
* recorded for the same binary path, version and digest (IC-005). Pure: runs inside the lock.
|
|
337
|
+
*/
|
|
338
|
+
function mergeRecord(prior , next , nowMs ) {
|
|
339
|
+
const same = sameBinding(prior, next);
|
|
340
|
+
const facts = { ...next.facts };
|
|
341
|
+
if (same) {
|
|
342
|
+
for (const name of FACTS) {
|
|
343
|
+
const old = prior.facts?.[name];
|
|
344
|
+
const fresh = facts[name];
|
|
345
|
+
if (fresh?.basis === 'free' && fresh.state === 'unknown'
|
|
346
|
+
&& old?.basis === 'model-backed' && (old.state === 'yes' || old.state === 'no') && freshAt(old.at, nowMs)) {
|
|
347
|
+
facts[name] = old;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return { ...next, facts, modelChecks: { ...(same ? prior.modelChecks : {}), ...next.modelChecks } };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function writeRecord(root , record , nowMs ) {
|
|
355
|
+
let written = null;
|
|
356
|
+
updateTaskState({
|
|
357
|
+
fsImpl: fs,
|
|
358
|
+
file: probeFile(root, record.harness),
|
|
359
|
+
build: (disk ) => {
|
|
360
|
+
written = mergeRecord(disk?.record ?? null, record, nowMs);
|
|
361
|
+
return { harness: record.harness, record: written };
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
return written;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* IC-007: the effective facts of `record` against `current`, a discover entry carrying
|
|
369
|
+
* `descriptorDigest`. A fact older than 24 hours, or a record whose binary path, version or digest
|
|
370
|
+
* differs, reads `unknown`, `probe-stale`; a harness with no record reads `not-probed`.
|
|
371
|
+
*/
|
|
372
|
+
function effectiveFacts(record , current , now = Date.now) {
|
|
373
|
+
const nowMs = clock(now)();
|
|
374
|
+
const out = {};
|
|
375
|
+
if (!record) {
|
|
376
|
+
for (const name of FACTS) out[name] = { state: 'unknown', reason: 'not-probed', at: null };
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
const matches = Boolean(current) && record.binaryPath === current.path && record.version === current.version
|
|
380
|
+
&& record.descriptorDigest === current.descriptorDigest;
|
|
381
|
+
for (const name of FACTS) {
|
|
382
|
+
const recorded = record.facts?.[name];
|
|
383
|
+
if (!recorded) out[name] = { state: 'unknown', reason: 'not-probed', at: null };
|
|
384
|
+
else if (!matches || !freshAt(recorded.at, nowMs)) out[name] = { state: 'unknown', reason: 'probe-stale', at: recorded.at };
|
|
385
|
+
else out[name] = recorded;
|
|
386
|
+
}
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** IC-006's usage checks, before any process starts. Returns a refusal or null. */
|
|
391
|
+
function checkModelRequest(entries , harness , modelCheck , budgetTokens ) {
|
|
392
|
+
if (modelCheck === undefined || modelCheck === null) return null;
|
|
393
|
+
if (!harness) throw new ReadinessError('usage', '--model-check needs --harness: a model check belongs to one harness');
|
|
394
|
+
const worker = entries[0].worker;
|
|
395
|
+
const declared = (worker.modelChecks || []).map((check ) => check.id);
|
|
396
|
+
const check = (worker.modelChecks || []).find((item ) => item.id === modelCheck);
|
|
397
|
+
if (!check) throw new ReadinessError('usage', `harness '${harness}' declares no model check '${modelCheck}'; declared: ${declared.length ? declared.join(', ') : '(none)'}`);
|
|
398
|
+
if (budgetTokens === undefined || budgetTokens === null) {
|
|
399
|
+
return { disposition: 'refused', reason: 'budget-required', harness, modelCheck, estimateTokens: check.estimateTokens };
|
|
400
|
+
}
|
|
401
|
+
if (!Number.isInteger(budgetTokens) || budgetTokens < 1) throw new ReadinessError('usage', `--budget-tokens '${budgetTokens}' is not a positive integer`);
|
|
402
|
+
if (check.estimateTokens > budgetTokens) {
|
|
403
|
+
return { disposition: 'refused', reason: 'budget-insufficient', harness, modelCheck, estimateTokens: check.estimateTokens, budgetTokens };
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* IC-005 and IC-006: discover, then per harness the free probes, at most one model check after
|
|
410
|
+
* them, and the probe record. Harnesses are probed one after another, so one harness's START line
|
|
411
|
+
* in `sessions.log` is never read as another's.
|
|
412
|
+
* @returns {Promise<{ disposition: 'probed', harnesses: Object[] } | { disposition: 'refused', reason: string }>}
|
|
413
|
+
*/
|
|
414
|
+
async function probe({
|
|
415
|
+
descriptors, registry, coverage, harness, needs, modelCheck, budgetTokens,
|
|
416
|
+
env = process.env, cwd = process.cwd(), root = cwd, now = Date.now, platform = process.platform,
|
|
417
|
+
probeIdentity = newProbeIdentity,
|
|
418
|
+
} = {}) {
|
|
419
|
+
const tick = clock(now);
|
|
420
|
+
const all = workerDescriptors({ descriptors, registry });
|
|
421
|
+
const entries = harness ? all.filter((entry) => entry.harness === harness) : all;
|
|
422
|
+
if (harness && entries.length === 0) {
|
|
423
|
+
throw new ReadinessError('usage', `unknown harness '${harness}'; known: ${all.map((entry) => entry.harness).join(', ')}`);
|
|
424
|
+
}
|
|
425
|
+
const cov = coverage === undefined ? registry?.coverage : coverage;
|
|
426
|
+
const valid = validateNeeds(needs, cov);
|
|
427
|
+
const refusal = checkModelRequest(entries, harness, modelCheck, budgetTokens);
|
|
428
|
+
if (refusal) return refusal;
|
|
429
|
+
|
|
430
|
+
// `root` is the lifecycle project root (IC-005): probe processes run there and the record lives
|
|
431
|
+
// there; discover resolves PATH from the caller's `cwd`, as describe, select and run do.
|
|
432
|
+
const results = [];
|
|
433
|
+
for (const entry of entries) {
|
|
434
|
+
const found = discoverOne(entry, { env, cwd, now: tick, platform });
|
|
435
|
+
const facts = await freeFacts(entry.worker, found, { env, root, now: tick, probeIdentity });
|
|
436
|
+
const modelChecks = {};
|
|
437
|
+
let ran ;
|
|
438
|
+
if (modelCheck !== undefined && modelCheck !== null) {
|
|
439
|
+
const check = entry.worker.modelChecks.find((item ) => item.id === modelCheck);
|
|
440
|
+
if (found.present) {
|
|
441
|
+
const outcome = await runModelCheck(entry.worker, found, check, budgetTokens, { env, root, now: tick, probeIdentity });
|
|
442
|
+
Object.assign(facts, outcome.facts);
|
|
443
|
+
modelChecks[modelCheck] = outcome.summary;
|
|
444
|
+
ran = { id: modelCheck, ran: true, ...outcome.summary };
|
|
445
|
+
} else {
|
|
446
|
+
ran = { id: modelCheck, ran: false, reason: 'not-installed' };
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const atMs = tick();
|
|
450
|
+
const record = writeRecord(root, {
|
|
451
|
+
harness: entry.harness,
|
|
452
|
+
at: iso(atMs),
|
|
453
|
+
binaryPath: found.path,
|
|
454
|
+
version: found.version,
|
|
455
|
+
descriptorDigest: descriptorDigest(entry.worker),
|
|
456
|
+
facts,
|
|
457
|
+
modelChecks,
|
|
458
|
+
}, atMs);
|
|
459
|
+
// FR-003: task-eligible, IC-008 over the effective facts of the record just written; never stored.
|
|
460
|
+
const effective = effectiveFacts(record, { path: found.path, version: found.version, descriptorDigest: record.descriptorDigest }, atMs);
|
|
461
|
+
const taskEligible = eligibility(requirementsFor(entry, effective, valid, cov));
|
|
462
|
+
results.push({ harness: entry.harness, facts: record.facts, taskEligible, recordedAt: record.at, ...(ran ? { modelCheck: ran } : {}) });
|
|
463
|
+
}
|
|
464
|
+
return { disposition: 'probed', harnesses: results };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** IC-008: requirements every task carries, in report order. */
|
|
468
|
+
const BASELINE = Object.freeze(['installed', 'runnable', 'one-shot', 'structured-output', 'read-only', 'spawn-denial']);
|
|
469
|
+
/** IC-013 (DEC-020): the baseline of an edit task, `read-only` replaced by `isolated-edit`. */
|
|
470
|
+
const EDIT_BASELINE = Object.freeze(['installed', 'runnable', 'one-shot', 'structured-output', 'isolated-edit', 'spawn-denial']);
|
|
471
|
+
/** IC-008: requirements a task adds with `--need`, besides `blocking-hook:<policy>`. */
|
|
472
|
+
const NEED_IDS = Object.freeze(['hooks-loaded', 'identity-forwarding', 'guidance-loaded', 'login']);
|
|
473
|
+
const BLOCKING_HOOK = 'blocking-hook:';
|
|
474
|
+
/** IC-001's capability keys, in declaration order (IC-009 `declared`). */
|
|
475
|
+
const CAPABILITIES = Object.freeze([
|
|
476
|
+
'cli', 'oneShot', 'structuredOutput', 'readOnly', 'spawnDenial', 'runnableProbe', 'hooksProbe', 'guidanceProbe', 'loginProbe',
|
|
477
|
+
'persistentSession', 'resume', 'steering',
|
|
478
|
+
]);
|
|
479
|
+
const FACT_REQUIREMENTS = Object.freeze({
|
|
480
|
+
installed: 'installed', runnable: 'runnable', 'hooks-loaded': 'hooksLoaded',
|
|
481
|
+
'identity-forwarding': 'identityForwarded', 'guidance-loaded': 'guidanceLoaded', login: 'login',
|
|
482
|
+
});
|
|
483
|
+
const CAPABILITY_REQUIREMENTS = Object.freeze({
|
|
484
|
+
'one-shot': 'oneShot', 'structured-output': 'structuredOutput', 'read-only': 'readOnly', 'spawn-denial': 'spawnDenial',
|
|
485
|
+
'isolated-edit': 'isolatedEdit',
|
|
486
|
+
});
|
|
487
|
+
const FACT_RESULT = Object.freeze({ yes: 'met', no: 'failed', unknown: 'unknown' });
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* DEC-044: the `blocking-hook` policies, built from the coverage rows because coverage's
|
|
491
|
+
* `POLICY_ORDER` is not exported and `src/lifecycle/coverage.js` does not change (IC-032).
|
|
492
|
+
*/
|
|
493
|
+
function policyVocabulary(coverage ) {
|
|
494
|
+
const policies = new Set ((coverage?.rows || []).map((row ) => row.policy));
|
|
495
|
+
policies.delete('*');
|
|
496
|
+
return policies;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** The need ids as given, deduplicated; an unknown id or policy is a `usage` error listing the valid ones. */
|
|
500
|
+
function validateNeeds(needs , coverage ) {
|
|
501
|
+
const list = needs === undefined || needs === null ? [] : (Array.isArray(needs) ? needs : [needs]);
|
|
502
|
+
const policies = policyVocabulary(coverage);
|
|
503
|
+
for (const need of list) {
|
|
504
|
+
if (typeof need === 'string' && need.startsWith(BLOCKING_HOOK)) {
|
|
505
|
+
const policy = need.slice(BLOCKING_HOOK.length);
|
|
506
|
+
if (!policies.has(policy)) {
|
|
507
|
+
throw new ReadinessError('usage', `unknown policy '${policy}' in --need '${need}'; valid policies: ${[...policies].join(', ') || '(none)'}`);
|
|
508
|
+
}
|
|
509
|
+
} else if (!NEED_IDS.includes(need)) {
|
|
510
|
+
throw new ReadinessError('usage', `unknown need '${need}'; valid needs: ${[...NEED_IDS, `${BLOCKING_HOOK}<policy>`].join(', ')}; policies: ${[...policies].join(', ') || '(none)'}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return [...new Set (list)];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function factRequirement(requirement , value ) {
|
|
517
|
+
const state = value?.state ?? 'unknown';
|
|
518
|
+
return { requirement, result: FACT_RESULT[state] ?? 'unknown', reason: value?.reason ?? 'not-probed', at: value?.at ?? null };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function capabilityRequirement(requirement , entry ) {
|
|
522
|
+
if (!entry || typeof entry !== 'object') return { requirement, result: 'unknown', reason: 'not-declared' };
|
|
523
|
+
if (entry.status === 'supported') {
|
|
524
|
+
if (requirement === 'structured-output' && !Object.hasOwn(PARSERS, entry.parser)) {
|
|
525
|
+
return { requirement, result: 'failed', reason: `parser-unknown ${entry.parser}` };
|
|
526
|
+
}
|
|
527
|
+
return { requirement, result: 'met', reason: 'supported' };
|
|
528
|
+
}
|
|
529
|
+
if (entry.status === 'unsupported') return { requirement, result: 'failed', reason: entry.reason || 'unsupported' };
|
|
530
|
+
return { requirement, result: 'unknown', reason: entry.reason || 'unknown' };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* `blocking-hook:<policy>`: a known profile, an enforced coverage answer and loaded hooks. An
|
|
535
|
+
* `UnknownEnforcementTarget` is a `failed` requirement carrying its `code`, never raised (DEC-044).
|
|
536
|
+
*/
|
|
537
|
+
function blockingHookRequirement(requirement , harness , worker , facts , coverage ) {
|
|
538
|
+
if (worker?.profile === null || worker?.profile === undefined) return { requirement, result: 'failed', reason: 'profile-unknown' };
|
|
539
|
+
let answer ;
|
|
540
|
+
try {
|
|
541
|
+
answer = enforcementFor(coverage, { harness, policy: requirement.slice(BLOCKING_HOOK.length), profile: worker.profile });
|
|
542
|
+
} catch (error ) {
|
|
543
|
+
if (error instanceof UnknownEnforcementTarget) return { requirement, result: 'failed', reason: error.code };
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
if (!answer.enforced) return { requirement, result: 'failed', reason: answer.reason };
|
|
547
|
+
const hooks = factRequirement(requirement, facts?.hooksLoaded);
|
|
548
|
+
if (hooks.result !== 'met') return { ...hooks, reason: `hooksLoaded ${hooks.reason}` };
|
|
549
|
+
return { requirement, result: 'met', reason: answer.reason, at: hooks.at };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* IC-008: the baseline and `needs` evaluated for one harness to `met`, `failed` or `unknown`, each
|
|
554
|
+
* with a reason; fact-backed requirements also carry the fact's `at`. `descriptor` is a
|
|
555
|
+
* `workerDescriptors` entry `{ harness, worker }`; `facts` are IC-007 effective facts. With `edit`
|
|
556
|
+
* the baseline is `EDIT_BASELINE` (IC-013), without it `BASELINE`.
|
|
557
|
+
*/
|
|
558
|
+
function requirementsFor(descriptor , facts , needs , coverage , edit = false) {
|
|
559
|
+
const { harness, worker } = descriptor;
|
|
560
|
+
return [...(edit ? EDIT_BASELINE : BASELINE), ...validateNeeds(needs, coverage)].map((requirement) => {
|
|
561
|
+
if (Object.hasOwn(FACT_REQUIREMENTS, requirement)) return factRequirement(requirement, facts?.[FACT_REQUIREMENTS[requirement]]);
|
|
562
|
+
if (Object.hasOwn(CAPABILITY_REQUIREMENTS, requirement)) return capabilityRequirement(requirement, worker?.[CAPABILITY_REQUIREMENTS[requirement]]);
|
|
563
|
+
return blockingHookRequirement(requirement, harness, worker, facts, coverage);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Eligible when every applicable requirement is `met` (DEC-009, DEC-020); the others are gaps. */
|
|
568
|
+
function eligibility(requirements ) {
|
|
569
|
+
const gaps = requirements.filter((item) => item.result !== 'met').map(({ requirement, result, reason }) => ({ requirement, result, reason }));
|
|
570
|
+
return { eligible: gaps.length === 0, gaps };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Registry, coverage and descriptors for one call; IC-009 filters only when active is supplied. */
|
|
574
|
+
function readinessContext({ descriptors, registry, coverage, active, view } ) {
|
|
575
|
+
const reg = registry || loadRegistry({ repoRoot: REPO_ROOT });
|
|
576
|
+
const all = workerDescriptors({ descriptors, registry: reg, view });
|
|
577
|
+
const entries = active ? all.filter((entry) => workerGate(active, entry.harness) === null) : all;
|
|
578
|
+
return { entries, known: all.map((entry) => entry.harness), coverage: coverage === undefined ? reg.coverage : coverage };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Discover, then the IC-007 effective facts and IC-008 evaluation of one harness. Writes nothing. */
|
|
582
|
+
function evaluateHarness(entry , needs , coverage , { env, cwd, root, now, platform, edit } ) {
|
|
583
|
+
const found = discoverOne(entry, { env, cwd, now, platform });
|
|
584
|
+
const record = readProbeRecord(root, entry.harness);
|
|
585
|
+
const facts = effectiveFacts(record, { ...found, descriptorDigest: descriptorDigest(entry.worker) }, now);
|
|
586
|
+
const requirements = requirementsFor(entry, facts, needs, coverage, edit === true);
|
|
587
|
+
return { harness: entry.harness, facts, recordedAt: record?.at ?? null, requirements, ...eligibility(requirements) };
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** IC-014: describe a worker's enabled state and rung without changing eligibility. */
|
|
591
|
+
function enabledOf(active , id )
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
{
|
|
596
|
+
const origin = { layer: active.origin.layer, source: active.origin.source };
|
|
597
|
+
if (active.state === 'unset') return { state: 'default', position: null, origin };
|
|
598
|
+
if (active.state === 'invalid') return { state: 'unlisted', position: null, origin };
|
|
599
|
+
const index = active.workers.indexOf(id);
|
|
600
|
+
if (index !== -1) return { state: 'enabled', position: index + 1, origin };
|
|
601
|
+
if (active.narrowedFrom?.includes(id)) return { state: 'disabled', position: null, origin };
|
|
602
|
+
return { state: 'unlisted', position: null, origin };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* IC-009: what one harness declares, its effective probe facts with a `stale` flag, and IC-008's
|
|
607
|
+
* requirements for the baseline and `needs`. IC-014 adds enabled, active, primary and bindings when
|
|
608
|
+
* supplied, without filtering the described harness. Read-only.
|
|
609
|
+
*/
|
|
610
|
+
function describe({
|
|
611
|
+
descriptors, registry, coverage, view, harness, needs, active, primary, bindings, edit = false,
|
|
612
|
+
env = process.env, cwd = process.cwd(), root = cwd, now = Date.now, platform = process.platform,
|
|
613
|
+
} = {}) {
|
|
614
|
+
const context = readinessContext({ descriptors, registry, coverage, view });
|
|
615
|
+
const entry = context.entries.find((item) => item.harness === harness);
|
|
616
|
+
if (!entry) throw new ReadinessError('usage', `unknown harness '${harness}'; known: ${context.entries.map((item) => item.harness).join(', ')}`);
|
|
617
|
+
const valid = validateNeeds(needs, context.coverage);
|
|
618
|
+
const evaluated = evaluateHarness(entry, valid, context.coverage, { env, cwd, root, now: clock(now), platform, edit });
|
|
619
|
+
const declared = { profile: entry.worker?.profile ?? null, modelChecks: (entry.worker?.modelChecks || []).map((check ) => check.id) };
|
|
620
|
+
for (const key of CAPABILITIES) {
|
|
621
|
+
const value = entry.worker?.[key];
|
|
622
|
+
declared[key] = value
|
|
623
|
+
? { status: value.status, verifiedOn: value.verifiedOn, ...(value.reason ? { reason: value.reason } : {}) }
|
|
624
|
+
: { status: 'unknown', verifiedOn: null, reason: 'not-declared' };
|
|
625
|
+
}
|
|
626
|
+
const probeFacts = {};
|
|
627
|
+
for (const name of FACTS) probeFacts[name] = { ...evaluated.facts[name], stale: evaluated.facts[name].reason === 'probe-stale' };
|
|
628
|
+
return {
|
|
629
|
+
disposition: 'described', harness, declared, probe: probeFacts,
|
|
630
|
+
requirements: evaluated.requirements, eligible: evaluated.eligible,
|
|
631
|
+
...(active !== undefined ? { enabled: enabledOf(active, harness), active: { ...active } } : {}),
|
|
632
|
+
...(primary !== undefined ? { primary } : {}),
|
|
633
|
+
...(bindings !== undefined ? {
|
|
634
|
+
bindings: bindingsOf({
|
|
635
|
+
worker: harness, descriptor: entry.worker, bindings,
|
|
636
|
+
tiers: (registry || loadRegistry({ repoRoot: REPO_ROOT })).dispatchTiers?.tiers ?? [],
|
|
637
|
+
}),
|
|
638
|
+
} : {}),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* IC-010: the first eligible harness in registry order, or `none-eligible` with each gap and the
|
|
644
|
+
* fallback modes. Claims nothing and writes nothing (DEC-002).
|
|
645
|
+
*/
|
|
646
|
+
function select({
|
|
647
|
+
descriptors, registry, coverage, view, harness, needs, active, edit = false, includeDisabled = false,
|
|
648
|
+
env = process.env, cwd = process.cwd(), root = cwd, now = Date.now, platform = process.platform,
|
|
649
|
+
} = {}) {
|
|
650
|
+
const context = readinessContext({ descriptors, registry, coverage, active, view });
|
|
651
|
+
let entries = harness ? context.entries.filter((item) => item.harness === harness) : context.entries;
|
|
652
|
+
if (harness && !context.known.includes(harness)) {
|
|
653
|
+
throw new ReadinessError('usage', `unknown harness '${harness}'; known: ${context.known.join(', ')}`);
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
657
|
+
const enabled = loadSettings({ projectRoot: root, env }).get('workers.enabled', 'project');
|
|
658
|
+
if (!includeDisabled && enabled?.state === 'ok' && Array.isArray(enabled.value)) {
|
|
659
|
+
entries = entries.filter((item) => enabled.value.includes(item.harness));
|
|
660
|
+
}
|
|
661
|
+
} catch {
|
|
662
|
+
// workers.enabled setting absent or failed to load
|
|
663
|
+
}
|
|
664
|
+
const valid = validateNeeds(needs, context.coverage);
|
|
665
|
+
const tick = clock(now);
|
|
666
|
+
const evaluated = entries.map((entry) => evaluateHarness(entry, valid, context.coverage, { env, cwd, root, now: tick, platform, edit }));
|
|
667
|
+
const candidates = evaluated.map(({ harness: id, eligible, gaps }) => ({ harness: id, eligible, gaps }));
|
|
668
|
+
const chosen = evaluated.find((item) => item.eligible);
|
|
669
|
+
if (!chosen) {
|
|
670
|
+
return {
|
|
671
|
+
disposition: 'none-eligible',
|
|
672
|
+
candidates,
|
|
673
|
+
fallback: { modes: ['sequential', 'manual'], rule: 'claim with --mode sequential or manual; a subagent claim is run only by worker --action run' },
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
const met = chosen.requirements.map((item ) => item.requirement).join(', ');
|
|
677
|
+
return {
|
|
678
|
+
disposition: 'selected',
|
|
679
|
+
harness: chosen.harness,
|
|
680
|
+
reason: `first eligible harness in registry order; met: ${met}; probed at ${chosen.recordedAt}`,
|
|
681
|
+
candidates,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
module.exports = {
|
|
686
|
+
PROBE_TIMEOUT_MS,
|
|
687
|
+
PROBE_MAX_AGE_MS,
|
|
688
|
+
PROBE_STDOUT_MAX,
|
|
689
|
+
FACTS,
|
|
690
|
+
ReadinessError,
|
|
691
|
+
descriptorDigest,
|
|
692
|
+
workerDescriptors,
|
|
693
|
+
findOnPath,
|
|
694
|
+
discover,
|
|
695
|
+
probe,
|
|
696
|
+
newProbeIdentity,
|
|
697
|
+
observeForwarding,
|
|
698
|
+
bindingObserved,
|
|
699
|
+
effectiveFacts,
|
|
700
|
+
readProbeRecord,
|
|
701
|
+
probeFile,
|
|
702
|
+
BASELINE,
|
|
703
|
+
EDIT_BASELINE,
|
|
704
|
+
NEED_IDS,
|
|
705
|
+
policyVocabulary,
|
|
706
|
+
validateNeeds,
|
|
707
|
+
capabilityRequirement,
|
|
708
|
+
requirementsFor,
|
|
709
|
+
eligibility,
|
|
710
|
+
enabledOf,
|
|
711
|
+
describe,
|
|
712
|
+
select,
|
|
713
|
+
};
|