@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
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
const fs = require('node:fs');
|
|
16
16
|
const path = require('node:path');
|
|
17
17
|
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
18
|
+
const { contentDigest } = require('../runtime/packages/digest.js');
|
|
18
19
|
|
|
19
20
|
/** Every distribution manifest that repeats the release version, relative to the repo root. */
|
|
20
21
|
const PLUGIN_MANIFESTS = [
|
|
@@ -23,15 +24,50 @@ const PLUGIN_MANIFESTS = [
|
|
|
23
24
|
'core/.plugin/plugin.json',
|
|
24
25
|
];
|
|
25
26
|
|
|
27
|
+
/** The point registry that lists the bundled first-party packages (122 IC-015), relative to the repo root. */
|
|
28
|
+
const POINTS_FILE = 'core/registry/contribution-points.json';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 122 PN-8: a bundled package's manifest repeats the release version (`version`, and `doflow` as the exact range
|
|
32
|
+
* `=<version>`), and its directory's content digest is pinned in `contribution-points.json`. A bump changes the
|
|
33
|
+
* manifest bytes, so the three move together: rewrite the manifest, hash the directory, pin the digest. Returns the
|
|
34
|
+
* files it rewrote. Targeted replacement keeps the formatting of both files.
|
|
35
|
+
*/
|
|
36
|
+
function syncBundledPackages(version , repoRoot , fsImpl ) {
|
|
37
|
+
const pointsFile = path.join(repoRoot, POINTS_FILE);
|
|
38
|
+
let pointsText = fsImpl.readFileSync(pointsFile, 'utf8');
|
|
39
|
+
const points = JSON.parse(pointsText);
|
|
40
|
+
const rewritten = [];
|
|
41
|
+
for (const entry of points.bundled) {
|
|
42
|
+
const manifestRel = path.posix.join(entry.dir, 'doflow-package.json');
|
|
43
|
+
const manifestFile = path.join(repoRoot, manifestRel);
|
|
44
|
+
const text = fsImpl.readFileSync(manifestFile, 'utf8');
|
|
45
|
+
const next = text
|
|
46
|
+
.replace(/("version":\s*")[^"]*"/, (_m , head ) => `${head}${version}"`)
|
|
47
|
+
.replace(/("doflow":\s*")[^"]*"/, (_m , head ) => `${head}=${version}"`);
|
|
48
|
+
if (next !== text) {
|
|
49
|
+
fsImpl.writeFileSync(manifestFile, next, 'utf8');
|
|
50
|
+
rewritten.push(manifestRel);
|
|
51
|
+
}
|
|
52
|
+
const digest = contentDigest(path.join(repoRoot, entry.dir), { limits: points.limits, fsImpl });
|
|
53
|
+
if (digest !== entry.contentDigest) {
|
|
54
|
+
pointsText = pointsText.replace(entry.contentDigest, digest);
|
|
55
|
+
rewritten.push(POINTS_FILE);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (rewritten.includes(POINTS_FILE)) fsImpl.writeFileSync(pointsFile, pointsText, 'utf8');
|
|
59
|
+
return [...new Set(rewritten)];
|
|
60
|
+
}
|
|
61
|
+
|
|
26
62
|
/**
|
|
27
63
|
* Rewrites each plugin manifest's version to package.json's, preserving formatting by targeted
|
|
28
|
-
* string replacement rather than re-serializing the whole file.
|
|
64
|
+
* string replacement rather than re-serializing the whole file. The bundled packages follow (`bundled`).
|
|
29
65
|
* @param {Object} [options]
|
|
30
66
|
* @param {string} [options.repoRoot]
|
|
31
67
|
* @param {Object} [options.fsImpl]
|
|
32
|
-
* @returns {{version: string, updated: Array<string>, unchanged: Array<string>}}
|
|
68
|
+
* @returns {{version: string, updated: Array<string>, unchanged: Array<string>, bundled: Array<string>}}
|
|
33
69
|
*/
|
|
34
|
-
function syncPluginVersions({ repoRoot = REPO_ROOT, fsImpl = fs } = {})
|
|
70
|
+
function syncPluginVersions({ repoRoot = REPO_ROOT, fsImpl = fs } = {}) {
|
|
35
71
|
const pkg = JSON.parse(fsImpl.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
|
|
36
72
|
if (typeof pkg.version !== 'string' || pkg.version.trim() === '') {
|
|
37
73
|
throw new Error('package.json declares no version to project');
|
|
@@ -54,13 +90,13 @@ function syncPluginVersions({ repoRoot = REPO_ROOT, fsImpl = fs }
|
|
|
54
90
|
fsImpl.writeFileSync(file, next, 'utf8');
|
|
55
91
|
updated.push(rel);
|
|
56
92
|
}
|
|
57
|
-
return { version: pkg.version, updated, unchanged };
|
|
93
|
+
return { version: pkg.version, updated, unchanged, bundled: syncBundledPackages(pkg.version, repoRoot, fsImpl) };
|
|
58
94
|
}
|
|
59
95
|
|
|
60
96
|
if (require.main === module) {
|
|
61
97
|
const result = syncPluginVersions();
|
|
62
|
-
for (const rel of result.updated) console.log(`synced ${rel} -> ${result.version}`);
|
|
63
|
-
if (result.updated.length === 0) console.log(`all plugin manifests already at ${result.version}`);
|
|
98
|
+
for (const rel of [...result.updated, ...result.bundled]) console.log(`synced ${rel} -> ${result.version}`);
|
|
99
|
+
if (result.updated.length === 0 && result.bundled.length === 0) console.log(`all plugin manifests and bundled packages already at ${result.version}`);
|
|
64
100
|
}
|
|
65
101
|
|
|
66
|
-
module.exports = { syncPluginVersions, PLUGIN_MANIFESTS };
|
|
102
|
+
module.exports = { syncPluginVersions, PLUGIN_MANIFESTS, POINTS_FILE };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The bench bar that act mode requires. User decision D3 (2026-10-26): act is unreachable in 084. The bar may only be
|
|
4
|
+
// met by a DoFlow-owned scored bench corpus, none ships yet, and a file in the caller's project is never read, so the
|
|
5
|
+
// bar is always unmet. Producing that corpus is the future row "trusted bench scores" (not yet registered).
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function evaluateBenchBar(kind , model ) {
|
|
15
|
+
return {
|
|
16
|
+
kind,
|
|
17
|
+
model,
|
|
18
|
+
passed: false,
|
|
19
|
+
reason: `no trusted bench scores ship for kind '${kind}' and model '${model}'`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
evaluateBenchBar,
|
|
25
|
+
};
|
|
@@ -9,11 +9,14 @@ const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
|
9
9
|
const { loadAdviceRegistry } = require('./registry.js');
|
|
10
10
|
const { loadStorageRegistry } = require('../storage/registry.js');
|
|
11
11
|
const { resolveRoots } = require('../storage/roots.js');
|
|
12
|
-
const { resolveSetting
|
|
12
|
+
const { resolveSetting } = require('./config.js');
|
|
13
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
14
|
+
const { writeLayer } = require('../settings/write.js');
|
|
13
15
|
const { createAdviceClient } = require('./client.js');
|
|
14
16
|
const { materializeKind, validateQuestionSet, validateStaticAnswer } = require('./kinds.js');
|
|
15
17
|
const { findPick, appendOutcome } = require('./record.js');
|
|
16
18
|
const { strengthWords } = require('./strength.js');
|
|
19
|
+
const { projectLayerSource } = require('../settings/project-layers.js');
|
|
17
20
|
|
|
18
21
|
function loadSources(repoRoot ) {
|
|
19
22
|
const read = (name ) => JSON.parse(fs.readFileSync(path.join(repoRoot, 'core/registry', name), 'utf8'));
|
|
@@ -28,11 +31,39 @@ function loadRuntime({ projectRoot, global = false } = {}) {
|
|
|
28
31
|
const advice = loadAdviceRegistry({ repoRoot: REPO_ROOT });
|
|
29
32
|
const storage = loadStorageRegistry({ repoRoot: REPO_ROOT });
|
|
30
33
|
const roots = resolveRoots({ registry: storage, projectRoot, global });
|
|
34
|
+
const projectLayers = projectLayerSource(projectRoot);
|
|
31
35
|
const sources = loadSources(REPO_ROOT);
|
|
32
36
|
const adviceRegistry = advice.ok ? advice.registry : null;
|
|
33
|
-
return { adviceRegistry, adviceError: advice.ok ? null : advice.detail, storage, roots, sources };
|
|
37
|
+
return { adviceRegistry, adviceError: advice.ok ? null : advice.detail, storage, roots, sources, projectLayers };
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
function emitDeprecatedNotes(runtime , loadSettingsImpl = loadSettings) {
|
|
41
|
+
emitDeprecatedNotesByCode(runtime, loadSettingsImpl);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function emitDeprecatedNotesByCode(runtime , loadSettingsImpl = loadSettings) {
|
|
45
|
+
if (!runtime.adviceRegistry) return;
|
|
46
|
+
const roots = runtime.roots || {};
|
|
47
|
+
const projectRoot = roots['project-doflow'] ? path.dirname(roots['project-doflow']) : null;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const view = loadSettingsImpl({ projectRoot, fsImpl: fs });
|
|
51
|
+
const enabled = view.get('advice.enabled', 'global');
|
|
52
|
+
const canReadProject = enabled.value === true && enabled.state === 'ok' && enabled.origin.via === null;
|
|
53
|
+
for (const note of view.notes || []) {
|
|
54
|
+
if (note.code !== 'deprecated') continue;
|
|
55
|
+
const sourceLayer = view.files.find((file ) => file.path === note.source)?.layer;
|
|
56
|
+
if (!note.key || !['user', 'project'].includes(sourceLayer)) continue;
|
|
57
|
+
if (sourceLayer === 'project' && !canReadProject) continue;
|
|
58
|
+
let resolution;
|
|
59
|
+
try { resolution = view.get(note.key, sourceLayer === 'user' ? 'global' : 'project'); } catch { continue; }
|
|
60
|
+
if (!resolution.origin.legacy || resolution.origin.source !== note.source) continue;
|
|
61
|
+
console.error('note: ' + note.text);
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// Advice remains usable when legacy-source reporting fails.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
36
67
|
function emitResult(result , { json, kind } ) {
|
|
37
68
|
if (result.reason === 'over-limit') {
|
|
38
69
|
return usageError('advise', result.detail || 'request exceeds an advice limit', json);
|
|
@@ -141,7 +172,7 @@ function writeFailure(message , json ) {
|
|
|
141
172
|
}
|
|
142
173
|
|
|
143
174
|
async function handleConfigure(options , runtime ) {
|
|
144
|
-
const { adviceRegistry, adviceError, roots, sources } = runtime;
|
|
175
|
+
const { adviceRegistry, adviceError, roots, sources, projectLayers } = runtime;
|
|
145
176
|
const { json, project, global, force } = options;
|
|
146
177
|
const hasModel = typeof options.model === 'string' && options.model.length > 0;
|
|
147
178
|
const hasOff = options.off === true;
|
|
@@ -152,17 +183,28 @@ async function handleConfigure(options , runtime ) {
|
|
|
152
183
|
if (project && (global || !options.projectRoot)) return usageError('advise', '--project requires a project root', json);
|
|
153
184
|
if (adviceError || !adviceRegistry) return writeFailure(`advice registry is invalid: ${adviceError || 'missing registry'}`, json);
|
|
154
185
|
|
|
186
|
+
if (!project && !roots['config-home']) return usageError('advise', 'no config home resolves; set an absolute HOME or XDG_CONFIG_HOME, or use --project', json);
|
|
187
|
+
emitDeprecatedNotes(runtime);
|
|
188
|
+
|
|
155
189
|
const file = project
|
|
156
|
-
? path.join(roots['project-doflow'], '
|
|
157
|
-
: path.join(roots['config-home'], '
|
|
190
|
+
? path.join(projectLayers.layersFrom || roots['project-doflow'], 'settings.json')
|
|
191
|
+
: path.join(roots['config-home'], 'settings.json');
|
|
158
192
|
const fsImpl = fs;
|
|
193
|
+
const inheritedFromMain = !!project && projectLayers.checkout === 'main';
|
|
194
|
+
const discloseWrite = (setting ) => {
|
|
195
|
+
const result = { written: file, setting };
|
|
196
|
+
if (inheritedFromMain) result.inheritedFromMainCheckout = true;
|
|
197
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
198
|
+
else if (inheritedFromMain) console.log(`(inherited from the main checkout)`);
|
|
199
|
+
};
|
|
159
200
|
if (hasOff) {
|
|
160
|
-
const
|
|
161
|
-
const written = writeSetting({ file, level: project ? 'project' : 'user', setting, force, fsImpl });
|
|
201
|
+
const written = writeLayer({ file, set: { 'advice.enabled': false }, force, fsImpl });
|
|
162
202
|
if (!written.ok) return writeFailure(written.detail, json);
|
|
163
203
|
const effective = resolveSetting({ adviceRegistry, roots, fsImpl });
|
|
164
|
-
if (json)
|
|
165
|
-
else console.log(project
|
|
204
|
+
if (json) discloseWrite(effective);
|
|
205
|
+
else console.log(project
|
|
206
|
+
? `Decision model for this project: off (wrote ${file}${inheritedFromMain ? ' (inherited from the main checkout)' : ''}).`
|
|
207
|
+
: `Decision model off (wrote ${file}).`);
|
|
166
208
|
return finishRuntime(0);
|
|
167
209
|
}
|
|
168
210
|
|
|
@@ -172,7 +214,7 @@ async function handleConfigure(options , runtime ) {
|
|
|
172
214
|
if (project && userSetting.state !== 'on') {
|
|
173
215
|
return writeFailure('turn the decision model on at user level first; a project cannot enable it', json);
|
|
174
216
|
}
|
|
175
|
-
const providerId = userSetting.provider
|
|
217
|
+
const providerId = userSetting.provider ?? loadSettings({ global: true }).get('advice.provider', 'global').value;
|
|
176
218
|
const provider = adviceRegistry.providers.find((entry ) => entry.id === providerId);
|
|
177
219
|
if (!provider) return writeFailure(`provider '${providerId}' is not in core/registry/advice.json`, json);
|
|
178
220
|
const endpoint = project
|
|
@@ -185,14 +227,14 @@ async function handleConfigure(options , runtime ) {
|
|
|
185
227
|
if (failure) return writeFailure(failure, json);
|
|
186
228
|
|
|
187
229
|
const setting = project
|
|
188
|
-
? {
|
|
189
|
-
: {
|
|
190
|
-
const written =
|
|
230
|
+
? { 'advice.model': options.model }
|
|
231
|
+
: { 'advice.enabled': true, 'advice.provider': provider.id, 'advice.endpoint': endpoint, 'advice.model': options.model };
|
|
232
|
+
const written = writeLayer({ file, set: setting, force, fsImpl });
|
|
191
233
|
if (!written.ok) return writeFailure(written.detail, json);
|
|
192
234
|
const effective = resolveSetting({ adviceRegistry, roots, fsImpl });
|
|
193
|
-
if (json)
|
|
235
|
+
if (json) discloseWrite(effective);
|
|
194
236
|
else console.log(project
|
|
195
|
-
? `Decision model for this project: ${options.model} (wrote ${file}).`
|
|
237
|
+
? `Decision model for this project: ${options.model} (wrote ${file}${inheritedFromMain ? ' (inherited from the main checkout)' : ''}).`
|
|
196
238
|
: `Decision model on: ${options.model} at ${endpoint} (wrote ${file}).`);
|
|
197
239
|
return finishRuntime(0);
|
|
198
240
|
}
|
|
@@ -274,10 +316,11 @@ async function handleAdviseCommand(options = {}) {
|
|
|
274
316
|
const shaped = { ...options, projectRoot };
|
|
275
317
|
if (action === 'configure') return await handleConfigure(shaped, runtime);
|
|
276
318
|
if (action === 'outcome') return handleOutcome(shaped, runtime);
|
|
319
|
+
emitDeprecatedNotes(runtime);
|
|
277
320
|
return await handleAsk(shaped, runtime);
|
|
278
321
|
} catch (error ) {
|
|
279
322
|
return writeFailure(error?.message || 'advice command failed', options.json);
|
|
280
323
|
}
|
|
281
324
|
}
|
|
282
325
|
|
|
283
|
-
module.exports = { handleAdviseCommand };
|
|
326
|
+
module.exports = { handleAdviseCommand, emitDeprecatedNotes };
|
|
@@ -6,6 +6,7 @@ const fs = require('node:fs');
|
|
|
6
6
|
const http = require('node:http');
|
|
7
7
|
const https = require('node:https');
|
|
8
8
|
const { resolveSetting } = require('./config.js');
|
|
9
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
9
10
|
const { checkEndpoint } = require('./endpoint-guard.js');
|
|
10
11
|
const { strengthOf } = require('./strength.js');
|
|
11
12
|
const { validateQuestionSet, validateStaticAnswer } = require('./kinds.js');
|
|
@@ -41,7 +42,14 @@ function networkReason(error ) {
|
|
|
41
42
|
? 'timeout' : 'unreachable';
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
function
|
|
45
|
+
function timeoutError(message ) {
|
|
46
|
+
const error = new Error(message);
|
|
47
|
+
error.code = 'ADVICE_TIMEOUT';
|
|
48
|
+
return error;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** `signal`, when given, aborts the request the way its own timeout does: the request is destroyed and the timer cleared. */
|
|
52
|
+
function requestAction({ endpoint, action, pinnedLookup, timeoutMs, signal, httpModule = http, httpsModule = https } ) {
|
|
45
53
|
return new Promise ((resolve, reject) => {
|
|
46
54
|
let request ;
|
|
47
55
|
let settled = false;
|
|
@@ -49,15 +57,18 @@ function requestAction({ endpoint, action, pinnedLookup, timeoutMs, httpModule =
|
|
|
49
57
|
if (settled) return;
|
|
50
58
|
settled = true;
|
|
51
59
|
clearTimeout(timer);
|
|
60
|
+
signal?.removeEventListener?.('abort', onAbort);
|
|
52
61
|
if (error) reject(error);
|
|
53
62
|
else resolve(response);
|
|
54
63
|
};
|
|
55
|
-
const
|
|
56
|
-
const error = new Error(`decision model request timed out after ${timeoutMs}ms`);
|
|
57
|
-
error.code = 'ADVICE_TIMEOUT';
|
|
64
|
+
const stop = (error ) => {
|
|
58
65
|
request?.destroy(error);
|
|
59
66
|
finish(error);
|
|
60
|
-
}
|
|
67
|
+
};
|
|
68
|
+
const onAbort = () => stop(timeoutError('decision model request aborted by the caller'));
|
|
69
|
+
const timer = setTimeout(() => stop(timeoutError(`decision model request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
70
|
+
if (signal?.aborted) { onAbort(); return; }
|
|
71
|
+
signal?.addEventListener?.('abort', onAbort, { once: true });
|
|
61
72
|
|
|
62
73
|
try {
|
|
63
74
|
const url = new URL(endpoint);
|
|
@@ -117,7 +128,11 @@ function createAdviceClient({
|
|
|
117
128
|
httpsModule = https,
|
|
118
129
|
clock = () => new Date(),
|
|
119
130
|
} = {}) {
|
|
120
|
-
|
|
131
|
+
/**
|
|
132
|
+
* `model`, when given, replaces the setting's model for this call: it is the model asked, recorded and reported.
|
|
133
|
+
* `signal`, when given, ends the call early: an in-flight request is destroyed and the call is unavailable with 'timeout'.
|
|
134
|
+
*/
|
|
135
|
+
async function ask({ questionSet, kind, staticAnswer, caller, taskId = null, record = true, model, signal } = {}) {
|
|
121
136
|
const startedAt = Date.now();
|
|
122
137
|
let setting = null;
|
|
123
138
|
const effectiveKind = typeof kind === 'string' && kind ? kind : 'adhoc';
|
|
@@ -137,6 +152,7 @@ function createAdviceClient({
|
|
|
137
152
|
if (setting.state !== 'on') {
|
|
138
153
|
return emptyAdviceResult('off', setting.reason, setting.notes?.[0] || null, setting, durationSince(startedAt));
|
|
139
154
|
}
|
|
155
|
+
if (typeof model === 'string' && model.trim()) setting = { ...setting, model: model.trim() };
|
|
140
156
|
|
|
141
157
|
const provider = adviceRegistry?.providers?.find((entry ) => entry.id === setting.provider);
|
|
142
158
|
const adapter = provider && ADAPTERS[provider.adapter];
|
|
@@ -166,14 +182,17 @@ function createAdviceClient({
|
|
|
166
182
|
return unavailable('over-limit', `request body is ${bodyBytes} bytes; the limit is ${adviceRegistry.limits.maxBodyBytes} (core/registry/advice.json limits.maxBodyBytes)`);
|
|
167
183
|
}
|
|
168
184
|
|
|
185
|
+
if (signal?.aborted) return unavailable('timeout', 'decision model request aborted by the caller');
|
|
169
186
|
const endpointCheck = await checkEndpoint(setting.endpoint, {
|
|
170
187
|
lookup,
|
|
171
188
|
file: setting.decidedBy?.user || 'provider default',
|
|
172
189
|
timeoutMs: Math.max(0, adviceRegistry.timeoutMs - durationSince(startedAt)),
|
|
190
|
+
signal,
|
|
173
191
|
});
|
|
174
192
|
if (!endpointCheck.ok) return unavailable(endpointCheck.reason || 'non-loopback', endpointCheck.message);
|
|
175
193
|
const remainingMs = adviceRegistry.timeoutMs - durationSince(startedAt);
|
|
176
194
|
if (remainingMs <= 0) return unavailable('timeout', 'decision model request budget exhausted');
|
|
195
|
+
if (signal?.aborted) return unavailable('timeout', 'decision model request aborted by the caller');
|
|
177
196
|
|
|
178
197
|
let response;
|
|
179
198
|
try {
|
|
@@ -182,6 +201,7 @@ function createAdviceClient({
|
|
|
182
201
|
action: request,
|
|
183
202
|
pinnedLookup: endpointCheck.pinnedLookup,
|
|
184
203
|
timeoutMs: remainingMs,
|
|
204
|
+
signal,
|
|
185
205
|
httpModule,
|
|
186
206
|
httpsModule,
|
|
187
207
|
});
|
|
@@ -242,16 +262,19 @@ function createAdviceClient({
|
|
|
242
262
|
}
|
|
243
263
|
}
|
|
244
264
|
|
|
245
|
-
async function inspect({ endpoint, model, provider: providerId
|
|
265
|
+
async function inspect({ endpoint, model, provider: providerId, file, minVersion, timeoutMs } = {}) {
|
|
246
266
|
const unavailable = (state , message , version = null, versionOk = false) => ({
|
|
247
267
|
endpoint: { state, version, versionOk, message: message ?? null },
|
|
248
268
|
models: [],
|
|
249
269
|
model: { installed: false, decisionCapable: false },
|
|
250
270
|
});
|
|
251
271
|
try {
|
|
252
|
-
const
|
|
272
|
+
const effectiveProviderId = providerId
|
|
273
|
+
?? resolveSetting({ adviceRegistry, roots, fsImpl }).provider
|
|
274
|
+
?? loadSettings({ global: true, env: {}, fsImpl }).get('advice.provider', 'global').value;
|
|
275
|
+
const provider = adviceRegistry?.providers?.find((entry ) => entry.id === effectiveProviderId);
|
|
253
276
|
const adapter = provider && ADAPTERS[provider.adapter];
|
|
254
|
-
if (!provider || !adapter) return unavailable('refused', `provider '${
|
|
277
|
+
if (!provider || !adapter) return unavailable('refused', `provider '${effectiveProviderId}' is unavailable`);
|
|
255
278
|
const probeLimit = Number.isFinite(timeoutMs) ? timeoutMs : adviceRegistry.probeTimeoutMs;
|
|
256
279
|
const budgetMs = Number.isFinite(adviceRegistry.doctorBudgetMs) ? adviceRegistry.doctorBudgetMs : probeLimit * 4;
|
|
257
280
|
const startedAt = Date.now();
|
|
@@ -5,14 +5,7 @@
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const { resolveRoots } = require('../storage/roots.js');
|
|
8
|
-
|
|
9
|
-
const SETTING_FILE = 'decision-model.json';
|
|
10
|
-
const PROJECT_NOTE = 'a project cannot enable the decision model or choose its endpoint';
|
|
11
|
-
const KNOWN_KEYS = new Set(['schema', 'enabled', 'provider', 'endpoint', 'model']);
|
|
12
|
-
|
|
13
|
-
function isObject(value ) {
|
|
14
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
-
}
|
|
8
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
16
9
|
|
|
17
10
|
function offResult(reason , decidedBy , notes = [], setting = {}) {
|
|
18
11
|
return {
|
|
@@ -26,65 +19,58 @@ function offResult(reason , decidedBy , notes = [], setting
|
|
|
26
19
|
};
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
return { exists: true, value: JSON.parse(fsImpl.readFileSync(file, 'utf8')) };
|
|
33
|
-
} catch (error ) {
|
|
34
|
-
if (error?.code === 'ENOENT') return { exists: false };
|
|
35
|
-
return { exists: true, error };
|
|
36
|
-
}
|
|
22
|
+
function projectLayer(layer ) {
|
|
23
|
+
return layer === 'project' || layer === 'local';
|
|
37
24
|
}
|
|
38
25
|
|
|
39
|
-
function
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const provider = providers.find((item ) => item?.id === providerId);
|
|
47
|
-
if (typeof providerId !== 'string' || !provider) return { error: `user setting provider '${providerId}' is not in advice.json providers` };
|
|
48
|
-
|
|
49
|
-
let endpoint = provider.defaultEndpoint;
|
|
50
|
-
if (value.endpoint !== undefined) {
|
|
51
|
-
if (typeof value.endpoint !== 'string' || !value.endpoint.trim()) return { error: 'user setting endpoint must be a URL string' };
|
|
52
|
-
try { new URL(value.endpoint); } catch { return { error: 'user setting endpoint must be a URL string' }; }
|
|
53
|
-
endpoint = value.endpoint;
|
|
54
|
-
}
|
|
55
|
-
if (typeof endpoint !== 'string' || !endpoint) return { error: `providers.${providerId}.defaultEndpoint is missing` };
|
|
56
|
-
|
|
57
|
-
if (value.model !== undefined && (typeof value.model !== 'string' || !value.model.trim())) {
|
|
58
|
-
return { error: 'user setting model must be a non-empty string' };
|
|
26
|
+
function errorNote(error , offBy = null) {
|
|
27
|
+
if (error.legacy && error.layer === 'user') {
|
|
28
|
+
if (error.code === 'unreadable' || error.code === 'not-file'
|
|
29
|
+
|| (error.code === 'unparseable' && error.reason.startsWith('not valid JSON'))) {
|
|
30
|
+
return `user setting at ${error.source} could not be read: ${error.reason}`;
|
|
31
|
+
}
|
|
32
|
+
return `${error.reason} (${error.source})`;
|
|
59
33
|
}
|
|
60
|
-
if (
|
|
61
|
-
|
|
34
|
+
if (projectLayer(error.layer)) {
|
|
35
|
+
if (offBy && error.source === offBy) return `project setting at ${error.source} is invalid: ${error.reason}; advice is off`;
|
|
36
|
+
return `project setting at ${error.source} ignored: ${error.reason}`;
|
|
62
37
|
}
|
|
38
|
+
return `user setting at ${error.source} is invalid${error.key ? ` for '${error.key}'` : ''}: ${error.reason}`;
|
|
39
|
+
}
|
|
63
40
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
41
|
+
function settingNotes(view , includeProject , includeDeprecated = false, offBy = null) {
|
|
42
|
+
const notes = view.notes
|
|
43
|
+
.filter((note ) => {
|
|
44
|
+
if (note.code !== 'deprecated' || !includeDeprecated) return note.code !== 'deprecated';
|
|
45
|
+
const sourceLayer = view.files.find((file ) => file.path === note.source)?.layer;
|
|
46
|
+
if (!note.key || !['user', 'project'].includes(sourceLayer)) return false;
|
|
47
|
+
const resolution = view.get(note.key, sourceLayer === 'user' ? 'global' : 'project');
|
|
48
|
+
return resolution.origin.legacy && resolution.origin.source === note.source;
|
|
49
|
+
})
|
|
50
|
+
.filter((note ) => includeProject || !projectLayer(view.files.find((file ) => file.path === note.source)?.layer))
|
|
51
|
+
.map((note ) => note.text);
|
|
52
|
+
const seen = new Set ();
|
|
53
|
+
for (const error of view.errors) {
|
|
54
|
+
if (!includeProject && projectLayer(error.layer)) continue;
|
|
55
|
+
const key = error.legacy
|
|
56
|
+
? `${error.layer}\0${error.source}`
|
|
57
|
+
: `${error.layer}\0${error.source}\0${error.key}\0${error.code}`;
|
|
58
|
+
if (seen.has(key)) continue;
|
|
59
|
+
seen.add(key);
|
|
60
|
+
notes.push(errorNote(error, offBy));
|
|
61
|
+
}
|
|
62
|
+
return notes;
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
function
|
|
74
|
-
if (
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
function modelSource(setting ) {
|
|
66
|
+
if (setting.value === null || setting.value === undefined) return null;
|
|
67
|
+
if (setting.origin.layer === 'user') return 'user';
|
|
68
|
+
return projectLayer(setting.origin.layer) ? 'project' : null;
|
|
69
|
+
}
|
|
78
70
|
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
if (Object.hasOwn(value, key)) notes.push(`project key '${key}' ignored: ${PROJECT_NOTE}`);
|
|
83
|
-
}
|
|
84
|
-
for (const key of Object.keys(value)) {
|
|
85
|
-
if (!['schema', 'enabled', 'model', 'endpoint', 'provider'].includes(key)) notes.push(`unknown project key '${key}' ignored`);
|
|
86
|
-
}
|
|
87
|
-
return { value: { disabled: value.enabled === false, model: value.model ?? null }, notes };
|
|
71
|
+
function decidedSource(setting , layer ) {
|
|
72
|
+
if (layer === 'user') return setting.origin.layer === 'user' ? setting.origin.source : null;
|
|
73
|
+
return projectLayer(setting.origin.layer) ? setting.origin.source : null;
|
|
88
74
|
}
|
|
89
75
|
|
|
90
76
|
function resolveSetting({
|
|
@@ -95,95 +81,74 @@ function resolveSetting({
|
|
|
95
81
|
global = false,
|
|
96
82
|
env = process.env,
|
|
97
83
|
fsImpl = fs,
|
|
84
|
+
includeDeprecated = false,
|
|
98
85
|
} = {}) {
|
|
99
|
-
const notes = [];
|
|
100
86
|
let resolvedRoots = roots;
|
|
101
87
|
try {
|
|
102
88
|
if (!resolvedRoots) resolvedRoots = resolveRoots({ registry: storageRegistry, projectRoot, global, env });
|
|
103
89
|
} catch (error ) {
|
|
104
90
|
return offResult('config-invalid', { user: null, project: null }, [`storage roots could not be resolved: ${error.message}`]);
|
|
105
91
|
}
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return offResult('config-invalid', { user: userFile, project: null }, [`user setting at ${userFile} could not be read: ${userRead.error.message}`]);
|
|
92
|
+
const resolverEnv = { ...env };
|
|
93
|
+
if (resolvedRoots?.['config-home']) {
|
|
94
|
+
resolverEnv.XDG_CONFIG_HOME = path.dirname(resolvedRoots['config-home']);
|
|
95
|
+
} else {
|
|
96
|
+
delete resolverEnv.XDG_CONFIG_HOME;
|
|
97
|
+
delete resolverEnv.HOME;
|
|
113
98
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
notes.push(...user.notes);
|
|
118
|
-
|
|
119
|
-
const base = { ...user.value, modelFrom: user.value.model ? 'user' : null };
|
|
120
|
-
if (!user.value.enabled) return offResult('user-disabled', decidedBy, notes, base);
|
|
99
|
+
const resolverProjectRoot = !global && resolvedRoots?.['project-doflow']
|
|
100
|
+
? path.dirname(resolvedRoots['project-doflow'])
|
|
101
|
+
: null;
|
|
121
102
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
103
|
+
let view ;
|
|
104
|
+
try {
|
|
105
|
+
view = loadSettings({ projectRoot: resolverProjectRoot, global, env: resolverEnv, fsImpl });
|
|
106
|
+
const enabledGlobal = view.get('advice.enabled', 'global');
|
|
107
|
+
const canReadProject = !global && enabledGlobal.value === true
|
|
108
|
+
&& enabledGlobal.state === 'ok' && enabledGlobal.origin.via === null;
|
|
109
|
+
const enabled = canReadProject ? view.get('advice.enabled', 'project') : enabledGlobal;
|
|
110
|
+
const provider = view.get('advice.provider', 'global');
|
|
111
|
+
const endpoint = view.get('advice.endpoint', 'global');
|
|
112
|
+
const modelGlobal = view.get('advice.model', 'global');
|
|
113
|
+
const model = canReadProject && enabled.value === true ? view.get('advice.model', 'project') : modelGlobal;
|
|
114
|
+
const decidedBy = {
|
|
115
|
+
user: decidedSource(enabledGlobal, 'user'),
|
|
116
|
+
project: decidedSource(enabled, 'project') || decidedSource(model, 'project'),
|
|
117
|
+
};
|
|
118
|
+
let reason = null;
|
|
119
|
+
if (enabled.state === 'invalid' || enabled.origin.via === 'fail-safe' || enabled.origin.via === 'cross-key') {
|
|
120
|
+
reason = 'config-invalid';
|
|
121
|
+
} else if (enabled.value !== true) {
|
|
122
|
+
if (enabled.origin.layer === 'default' || enabled.origin.via === 'no-config-home') reason = 'not-configured';
|
|
123
|
+
else if (projectLayer(enabled.origin.layer)) reason = 'project-disabled';
|
|
124
|
+
else reason = 'user-disabled';
|
|
144
125
|
}
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
state: 'on', reason: null, provider: base.provider, endpoint: base.endpoint,
|
|
148
|
-
model: base.model, decidedBy, modelFrom: base.modelFrom, notes,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
126
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
if (fsImpl.existsSync(file)) {
|
|
160
|
-
try {
|
|
161
|
-
const parsed = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
162
|
-
if (!isObject(parsed)) throw new Error('setting must be a JSON object');
|
|
163
|
-
existing = parsed;
|
|
164
|
-
} catch (error ) {
|
|
165
|
-
if (!force) return { ok: false, reason: 'config-invalid', detail: `${file}: ${error.message}` };
|
|
166
|
-
}
|
|
127
|
+
// A project file that turned advice off (fail-safe, reason config-invalid) is not "ignored": say advice is off.
|
|
128
|
+
const notes = settingNotes(view, canReadProject, includeDeprecated, reason === 'config-invalid' ? decidedBy.project : null);
|
|
129
|
+
if (reason === 'not-configured') return offResult(reason, decidedBy, notes);
|
|
130
|
+
if (reason === 'config-invalid' && (enabledGlobal.state === 'invalid'
|
|
131
|
+
|| enabledGlobal.origin.via === 'fail-safe' || enabledGlobal.origin.via === 'cross-key')) {
|
|
132
|
+
return offResult(reason, decidedBy, notes);
|
|
167
133
|
}
|
|
168
134
|
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
fsImpl.writeFileSync(temporary, `${JSON.stringify(output, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
179
|
-
fsImpl.renameSync(temporary, file);
|
|
180
|
-
} finally {
|
|
181
|
-
try { if (fsImpl.existsSync(temporary)) fsImpl.unlinkSync(temporary); } catch { /* keep the primary result */ }
|
|
135
|
+
const effectiveModel = reason === 'config-invalid' ? modelGlobal : model;
|
|
136
|
+
const effectiveModelFrom = modelSource(effectiveModel);
|
|
137
|
+
if (reason) {
|
|
138
|
+
return offResult(reason, decidedBy, notes, {
|
|
139
|
+
provider: provider.value,
|
|
140
|
+
endpoint: endpoint.value,
|
|
141
|
+
model: effectiveModel.value,
|
|
142
|
+
modelFrom: effectiveModelFrom,
|
|
143
|
+
});
|
|
182
144
|
}
|
|
183
|
-
return {
|
|
145
|
+
return {
|
|
146
|
+
state: 'on', reason: null, provider: provider.value, endpoint: endpoint.value,
|
|
147
|
+
model: effectiveModel.value, decidedBy, modelFrom: effectiveModelFrom, notes,
|
|
148
|
+
};
|
|
184
149
|
} catch (error ) {
|
|
185
|
-
return {
|
|
150
|
+
return offResult('config-invalid', { user: null, project: null }, [`settings resolver could not be loaded: ${error.message}`]);
|
|
186
151
|
}
|
|
187
152
|
}
|
|
188
153
|
|
|
189
|
-
module.exports = { resolveSetting
|
|
154
|
+
module.exports = { resolveSetting };
|