@khoavu882/doflow 2.0.0-beta.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -3
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
- package/core/harnesses/kiro/hooks/session-start.sh +12 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
- package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
- package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +72 -80
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +60 -33
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1542 -277
- package/core/registry/lifecycle.json +2837 -37
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +701 -30
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +154 -35
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +24 -23
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +7 -9
- package/core/shared/skills/do-plan/SKILL.md +29 -19
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +226 -113
- package/src/adapters/claude/index.js +39 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +100 -22
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +294 -48
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +38 -13
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +120 -0
- package/src/adapters/pi/index.js +48 -16
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +3 -2
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +3 -2
- package/src/cli/index.js +338 -33
- package/src/cli/runtime-commands.js +243 -52
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +74 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +434 -86
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +686 -32
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +514 -0
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +27 -12
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +221 -277
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +100 -0
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +1106 -245
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +41 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +77 -10
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +3 -3
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +127 -16
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +402 -73
- package/src/runtime/retrieval-record.js +293 -0
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +63 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +290 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +202 -56
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +440 -65
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -5
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -210
- package/src/adapters/gemini/index.js +0 -390
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The settings resolver (086 IC-006, DD-2, DD-5, DD-6; data-model §3-4). `loadSettings()` returns a
|
|
7
|
+
* view; `view.get(key, 'project' | 'global')` folds that key bottom-up from its registry default:
|
|
8
|
+
*
|
|
9
|
+
* default → user legacy → user → (project view only: project legacy → project → local) → env
|
|
10
|
+
*
|
|
11
|
+
* `-g` (the global view) omits the project layers, which also gives the prune and retention root split.
|
|
12
|
+
* Each step is one candidate, and every candidate is kept with a status, so `config list` can say why a
|
|
13
|
+
* value applies and what it beat. The rules, in order:
|
|
14
|
+
* 1. a project or local value of a `global-only` key, or a non-project value of a
|
|
15
|
+
* `project-only` key, is `ignored-scope`, never validated;
|
|
16
|
+
* 2. an invalid value takes the key's fail-safe where its layer could have widened the key (user
|
|
17
|
+
* layers; project layers of a `project-overridable` key; env with `direction: any`), else it is
|
|
18
|
+
* skipped and the key falls through (FR-008, C7/C8/C13/C16);
|
|
19
|
+
* 3. a narrowing comparison (project layers of a `narrow-only` key, any layer of a `combine:
|
|
20
|
+
* narrowest` key, env with `direction: narrow`) applies only a narrower value; an equal one is
|
|
21
|
+
* `redundant` and never takes the origin (C6), a wider one is `not-narrower`. A value the key's
|
|
22
|
+
* order does not rank is never ranked (no `indexOf` of -1): a contributed `review.preset` (122
|
|
23
|
+
* IC-014) is compared by dominance with the value below, anything else is `not-narrower`;
|
|
24
|
+
* 3b. a `combine: union` key (a string-list) adds each valid candidate's items that are not yet present, in
|
|
25
|
+
* fold order; every contributor stays `decided`, a candidate adding nothing is `redundant`;
|
|
26
|
+
* 3c. a `combine: subset` key at project/local layers intersects the value below with the candidate,
|
|
27
|
+
* preserving the order below; installed additions are `not-narrower`, dangling ids are notes
|
|
28
|
+
* (IC-004). A valid user candidate overrides under rule 4;
|
|
29
|
+
* a key declaring `unknownMembers: warn` (122 IC-011) keeps a member its list does not hold, as one note;
|
|
30
|
+
* 4. anything else overrides, an equal value included (so the deciding file is the highest one that
|
|
31
|
+
* sets it, as `doflow storage` names it today, OI-3).
|
|
32
|
+
* After the fold: a key named as `{gate}` by another key's fail-safe takes its own fail-safe when one
|
|
33
|
+
* of those keys hit rule 2; `requires` rules (advice enabled with no model, C7); `needsConfigHome`.
|
|
34
|
+
*
|
|
35
|
+
* Reads are lazy and bounded: a key reads only its own sources, every path is `stat`ed and read at
|
|
36
|
+
* most once per view, `legacy.ts` and `lexical.ts` are required only when a key needs them (legacy
|
|
37
|
+
* after a `stat` hit). No file is written, no process spawned. A settings fault never throws; it is a
|
|
38
|
+
* candidate status plus an `errors` entry. Only an unusable registry throws (IC-001), and so does a
|
|
39
|
+
* caller's mistake (an unknown key, the project view of a `-g` load).
|
|
40
|
+
*
|
|
41
|
+
* Views are memoised per process for the real environment and file system only; a writer calls
|
|
42
|
+
* `clearSettingsCache()` after it writes.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const nodeFs = require('node:fs');
|
|
46
|
+
const path = require('node:path');
|
|
47
|
+
const { isOffWord } = require('../off-words.js');
|
|
48
|
+
const { classifyWorkerId, retiredAdvisory } = require('../../registry/worker-ids.js');
|
|
49
|
+
|
|
50
|
+
function maskUrl(value ) {
|
|
51
|
+
return require('./shared.js').maskUrl(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/** A candidate before the fold: its value, or why it has none. */
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
const NEW_FILE = { user: 'settings.json', project: 'settings.json', local: 'settings.local.json' } ;
|
|
84
|
+
|
|
85
|
+
function isObject(value ) {
|
|
86
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function absolute(value ) {
|
|
90
|
+
return typeof value === 'string' && path.isAbsolute(value) ? value : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** How a value is shown: JSON text, a url without its user-info. */
|
|
94
|
+
function displayOf(spec , raw ) {
|
|
95
|
+
if (raw === undefined) return '';
|
|
96
|
+
return JSON.stringify(spec.type === 'url' && typeof raw === 'string' ? maskUrl(raw) : raw) ?? '';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A view of `fsImpl` that remembers every `stat` and every file's bytes, errors included, so each path
|
|
101
|
+
* is stat'ed and read at most once per view whichever readers need it (the JSON read and the lexical
|
|
102
|
+
* read of the same `settings.json` share one read).
|
|
103
|
+
*/
|
|
104
|
+
function onceFs(fsImpl ) {
|
|
105
|
+
const stats = new Map ();
|
|
106
|
+
const bytes = new Map ();
|
|
107
|
+
const remembered = (map , key , run ) => {
|
|
108
|
+
if (!map.has(key)) {
|
|
109
|
+
try { map.set(key, { value: run() }); } catch (error) { map.set(key, { error }); }
|
|
110
|
+
}
|
|
111
|
+
const entry = map.get(key) ;
|
|
112
|
+
if ('error' in entry) throw entry.error;
|
|
113
|
+
return entry.value ;
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
statSync: (p ) => remembered(stats, p, () => fsImpl.statSync(p)),
|
|
117
|
+
accessSync: (p , mode ) => fsImpl.accessSync(p, mode),
|
|
118
|
+
lstatSync: (p ) => fsImpl.lstatSync(p),
|
|
119
|
+
realpathSync: (p ) => fsImpl.realpathSync(p),
|
|
120
|
+
readFileSync(p , options ) {
|
|
121
|
+
const buffer = remembered(bytes, p, () => {
|
|
122
|
+
const read = fsImpl.readFileSync(p);
|
|
123
|
+
return Buffer.isBuffer(read) ? read : Buffer.from(String(read));
|
|
124
|
+
});
|
|
125
|
+
const encoding = typeof options === 'string' ? options : options?.encoding;
|
|
126
|
+
return encoding ? buffer.toString(encoding) : buffer;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function registryOrThrow() {
|
|
132
|
+
const { loadSettingsRegistry } = require('./registry.js');
|
|
133
|
+
const loaded = loadSettingsRegistry();
|
|
134
|
+
if (loaded.ok) return loaded.registry;
|
|
135
|
+
const error = new Error(`settings registry unavailable: ${loaded.detail}`);
|
|
136
|
+
error.reason = loaded.reason;
|
|
137
|
+
error.detail = loaded.detail;
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The place of `value` in the key's order (narrowest first), or null when the order does not hold it. */
|
|
142
|
+
function rank(spec , value ) {
|
|
143
|
+
if (spec.order === 'ascending') return value;
|
|
144
|
+
if (spec.order === 'descending') return -value;
|
|
145
|
+
const at = spec.order.indexOf(value);
|
|
146
|
+
return at === -1 ? null : at;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 122 IC-014: how much narrower the `review.preset` value `candidate` is than `lower` when one of them is a contributed
|
|
151
|
+
* preset (no rank): -1 narrower, 0 equal, null not narrower (no dominance relation is looser). `auto` stands for the
|
|
152
|
+
* picks the risk rule can make: a value is narrower than `auto` when it dominates the strictest of them (depth `deep`),
|
|
153
|
+
* and `auto` is narrower than a value only when the loosest of them (depth `single`) dominates it.
|
|
154
|
+
*/
|
|
155
|
+
function presetNarrowerBy(registry , candidate , lower ) {
|
|
156
|
+
if (!registry) return null;
|
|
157
|
+
const { atLeastAsStrict } = require('../review/resolve-protocol.js');
|
|
158
|
+
const order = registry.order?.review ?? [];
|
|
159
|
+
const ofDepth = (depth ) => order.find((id) => registry.presets?.[id]?.depth === depth);
|
|
160
|
+
const geq = (a , b ) => a !== undefined && atLeastAsStrict(registry, 'review', a, b);
|
|
161
|
+
if (candidate === 'auto') return geq(ofDepth('single'), lower) ? -1 : null;
|
|
162
|
+
if (lower === 'auto') {
|
|
163
|
+
const deep = ofDepth('deep');
|
|
164
|
+
return deep !== undefined && geq(candidate, deep) ? -1 : null;
|
|
165
|
+
}
|
|
166
|
+
if (!geq(candidate, lower)) return null;
|
|
167
|
+
return geq(lower, candidate) ? 0 : -1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const memo = new Map ();
|
|
171
|
+
|
|
172
|
+
/** Drops every memoised view; writers call it after a write so the next load sees the file. */
|
|
173
|
+
function clearSettingsCache() {
|
|
174
|
+
memo.clear();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The registry a view limited to `only` reads: a list-bound key outside `only` has no allowed values (its value in a
|
|
181
|
+
* layer file is then invalid, and no such key can be read from the view), so reading a layer file never resolves another
|
|
182
|
+
* key's value list.
|
|
183
|
+
*/
|
|
184
|
+
function narrowed(registry , only ) {
|
|
185
|
+
return { ...registry, enumValues: (key ) => (only.includes(key) ? registry.enumValues(key) : null) };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param projectRoot the project root DoFlow resolves for the verb (absolute; anything else means no
|
|
190
|
+
* project layers)
|
|
191
|
+
* @param global `-g`: the global view, no project layers
|
|
192
|
+
* @param registry a loaded settings registry; defaults to the shipped one (throws when unusable)
|
|
193
|
+
* @param protocols a merged protocol registry the `review.preset` fold compares contributed presets in; defaults to
|
|
194
|
+
* the runtime's own merged view, loaded only when such a comparison happens
|
|
195
|
+
* @param only the keys the view answers (`get` of any other key throws), for a reader the other keys' value lists
|
|
196
|
+
* depend on: the package view reads `packages.disabled` this way, because the `review.preset` and `debate.preset`
|
|
197
|
+
* lists hold the presets of active packages (122 IC-014), so validating them while reading the same layer file would
|
|
198
|
+
* load the package view again. A key in `only` must not depend on such a key itself (`packages.ids` reads the lock).
|
|
199
|
+
*/
|
|
200
|
+
function loadSettings({ projectRoot = null, global = false, env = process.env, registry, fsImpl = nodeFs, protocols, only } = {}) {
|
|
201
|
+
const { configHome } = require('../config-home.js');
|
|
202
|
+
const loaded = registry ?? registryOrThrow();
|
|
203
|
+
const reg = only ? narrowed(loaded, only) : loaded;
|
|
204
|
+
const home = configHome(env);
|
|
205
|
+
const root = global ? null : absolute(projectRoot);
|
|
206
|
+
let cacheKey = null;
|
|
207
|
+
if (env === process.env && fsImpl === nodeFs && !registry && protocols === undefined) {
|
|
208
|
+
const bound = [...reg.keys.values()].filter((spec ) => spec.env).map((spec ) => env[spec.env.name] ?? null);
|
|
209
|
+
cacheKey = JSON.stringify([global ? 'global' : 'project', root, home.dir, only ?? null, ...bound]);
|
|
210
|
+
if (memo.has(cacheKey)) return memo.get(cacheKey);
|
|
211
|
+
}
|
|
212
|
+
const view = buildView(reg, home, root, global, env, fsImpl, protocols, only ?? null);
|
|
213
|
+
if (cacheKey) memo.set(cacheKey, view);
|
|
214
|
+
return view;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function buildView(registry , home , root , global , env , fsImpl , protocols , only ) {
|
|
218
|
+
const { REGISTRY_FILE } = require('./registry.js');
|
|
219
|
+
const { REF_META } = require('./refs.js');
|
|
220
|
+
const { readLayerFile, validateValue } = require('./files.js');
|
|
221
|
+
const { projectLayerSource } = require('./project-layers.js');
|
|
222
|
+
const scope = global ? 'global' : 'project';
|
|
223
|
+
const io = onceFs(fsImpl);
|
|
224
|
+
const layers = root ? projectLayerSource(root, io) : { layersFrom: null, checkout: null };
|
|
225
|
+
const projectCheckout = layers.layersFrom === null ? null : layers.checkout === 'main' ? 'main' : 'own';
|
|
226
|
+
|
|
227
|
+
const files = [];
|
|
228
|
+
const errors = [];
|
|
229
|
+
const notes = [];
|
|
230
|
+
const seen = new Set ();
|
|
231
|
+
const once = (tag ) => (seen.has(tag) ? false : (seen.add(tag), true));
|
|
232
|
+
let presetRegistry ;
|
|
233
|
+
/** The rank delta of `candidate` over `lower` (negative: narrower), or null when neither rank nor dominance relates them. */
|
|
234
|
+
const narrowerBy = (spec , candidate , lower ) => {
|
|
235
|
+
const at = rank(spec, candidate);
|
|
236
|
+
const below = rank(spec, lower);
|
|
237
|
+
if (at !== null && below !== null) return at - below;
|
|
238
|
+
if (spec.orderRef !== 'protocols.reviewPresetOrder') return null;
|
|
239
|
+
if (presetRegistry === undefined) {
|
|
240
|
+
if (protocols !== undefined) presetRegistry = protocols;
|
|
241
|
+
else {
|
|
242
|
+
try {
|
|
243
|
+
const loaded = require('../review/protocols.js').loadProtocols();
|
|
244
|
+
presetRegistry = loaded.ok ? loaded.registry : null;
|
|
245
|
+
} catch { presetRegistry = null; }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return presetNarrowerBy(presetRegistry, candidate, lower);
|
|
249
|
+
};
|
|
250
|
+
const reads = new Map ();
|
|
251
|
+
const resolved = new Map ();
|
|
252
|
+
const resolving = new Set ();
|
|
253
|
+
|
|
254
|
+
const keyOfField = (field ) => {
|
|
255
|
+
for (const [name, spec] of registry.keys) {
|
|
256
|
+
if (spec.legacy.some((entry ) => entry.kind === 'decision-model' && entry.field === field)) return name;
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/** What a user decision-model.json is checked against, from the registry (no default kept in code). */
|
|
262
|
+
function providers() {
|
|
263
|
+
const providerKey = keyOfField('provider');
|
|
264
|
+
const endpointKey = keyOfField('endpoint');
|
|
265
|
+
const providerSpec = providerKey ? registry.keys.get(providerKey) : null;
|
|
266
|
+
const endpointSpec = endpointKey ? registry.keys.get(endpointKey) : null;
|
|
267
|
+
return {
|
|
268
|
+
defaultProvider: providerSpec?.default,
|
|
269
|
+
ids: (providerKey && registry.enumValues(providerKey)) || [],
|
|
270
|
+
defaultEndpoint(id ) {
|
|
271
|
+
if (!endpointSpec) return null;
|
|
272
|
+
if (!endpointSpec.defaultRef) return endpointSpec.default;
|
|
273
|
+
try { return registry.resolveRef(endpointSpec.defaultRef, { [providerKey ]: id }); } catch { return null; }
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function record(entry ) {
|
|
279
|
+
files.push(entry);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function settingsFile(layer , file , checkout ) {
|
|
283
|
+
if (!reads.has(file)) {
|
|
284
|
+
const read = readLayerFile(file, { registry, fsImpl: io });
|
|
285
|
+
record({ layer, path: file, kind: 'settings', legacyKind: null, checkout, status: read.status, code: read.code, offset: read.offset });
|
|
286
|
+
for (const note of read.notes) notes.push({ code: note.code, key: note.key, source: file, text: note.text });
|
|
287
|
+
reads.set(file, read);
|
|
288
|
+
}
|
|
289
|
+
return reads.get(file);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* A `string-list` key declaring `unknownMembers: warn` (122 IC-011) keeps a member its list does not hold, because a
|
|
294
|
+
* project file travels to a machine that has not recorded it: the value stands if it passes every other rule, and the
|
|
295
|
+
* unrecorded members are one note. A list that breaks any other rule keeps its fault.
|
|
296
|
+
*/
|
|
297
|
+
function keepUnknownMembers(key , spec , entry , file ) {
|
|
298
|
+
if (validateValue({ ...spec, valuesRef: undefined }, entry.raw)) return entry;
|
|
299
|
+
const known = registry.enumValues(key) ?? [];
|
|
300
|
+
const unknown = (entry.raw ).filter((item) => !known.includes(item));
|
|
301
|
+
if (once(`unknown-member\0${key}\0${file}`)) {
|
|
302
|
+
notes.push({ code: 'unknown-member', key, source: file, text: `${key} names ${unknown.join(', ')}, which this machine has not recorded (kept)` });
|
|
303
|
+
}
|
|
304
|
+
return { raw: entry.raw, value: entry.raw, error: null };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** A legacy file read: a `stat` first, and `legacy.ts` only on a hit. */
|
|
308
|
+
function legacyFile(layer , entry , file , checkout ) {
|
|
309
|
+
if (!reads.has(file)) {
|
|
310
|
+
let hit = false;
|
|
311
|
+
try { io.statSync(file); hit = true; } catch (error ) {
|
|
312
|
+
// 2.0 read decision-model.json without a stat: any error but ENOENT made it invalid
|
|
313
|
+
hit = entry.kind === 'decision-model' && error?.code !== 'ENOENT';
|
|
314
|
+
}
|
|
315
|
+
let read = { status: 'absent' };
|
|
316
|
+
if (hit) {
|
|
317
|
+
const legacy = require('./legacy.js');
|
|
318
|
+
if (entry.kind === 'decision-model') {
|
|
319
|
+
read = legacy.readDecisionModel(file, layer, layer === 'user' ? providers() : null, io);
|
|
320
|
+
for (const note of read.notes) {
|
|
321
|
+
notes.push({ code: note.code, key: note.field ? keyOfField(note.field) : null, source: file, text: note.text });
|
|
322
|
+
}
|
|
323
|
+
} else if (entry.kind === 'line-onoff') {
|
|
324
|
+
const value = legacy.readLineOnOff(file, io);
|
|
325
|
+
read = value === null ? { status: 'absent' } : { status: 'ok', value };
|
|
326
|
+
} else {
|
|
327
|
+
read = legacy.readSentinel(file, io) ? { status: 'ok', value: entry.value } : { status: 'absent' };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
record({ layer, path: file, kind: 'legacy', legacyKind: entry.kind, checkout, status: read.status, code: read.code ?? null, offset: read.offset ?? null });
|
|
331
|
+
reads.set(file, read);
|
|
332
|
+
}
|
|
333
|
+
return reads.get(file);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function newPath(layer ) {
|
|
337
|
+
return layer === 'user' ? path.join(home.dir, NEW_FILE.user) : path.join(layers.layersFrom , NEW_FILE.project);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** 127 IC-005: the retired record a `harnesses.primaryIds` enum value names, else null (any other id stays an enum fault). */
|
|
341
|
+
function retiredHarnessOf(spec , value ) {
|
|
342
|
+
if (spec.valuesRef !== 'harnesses.primaryIds' || typeof value !== 'string') return null;
|
|
343
|
+
return (registry.retired?.() ?? []).find((entry ) => entry.id === value) ?? null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** The candidates of one key in one view, lowest layer first (the env last). */
|
|
347
|
+
function rawCandidates(key , spec , viewName ) {
|
|
348
|
+
const out = [];
|
|
349
|
+
// The legacy file's deprecation note waits until the new file has been read: it tells the reader to set the
|
|
350
|
+
// key there, which is noise when settings.json already holds it (FU-3xc3fj).
|
|
351
|
+
const deprecated = [];
|
|
352
|
+
const legacyAt = (layer , base , checkout ) => {
|
|
353
|
+
for (const entry of spec.legacy) {
|
|
354
|
+
if (entry.layer !== layer) continue;
|
|
355
|
+
const file = path.join(base, entry.path);
|
|
356
|
+
const read = legacyFile(layer, entry, file, checkout);
|
|
357
|
+
const base0 = { layer, source: file, legacy: true, checkout };
|
|
358
|
+
if (read.status === 'absent') continue;
|
|
359
|
+
if (read.status === 'invalid') {
|
|
360
|
+
out.push({ ...base0, display: '', value: null, fault: { code: read.code, reason: read.reason, offset: read.offset } });
|
|
361
|
+
} else if (entry.kind === 'decision-model') {
|
|
362
|
+
if (!read.fields.has(entry.field)) continue;
|
|
363
|
+
const value = read.fields.get(entry.field);
|
|
364
|
+
out.push({ ...base0, display: displayOf(spec, value), value, fault: null });
|
|
365
|
+
} else {
|
|
366
|
+
out.push({ ...base0, display: displayOf(spec, read.value), value: read.value, fault: null });
|
|
367
|
+
}
|
|
368
|
+
deprecated.push({ layer, file });
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
const fileAt = (layer , file , checkout ) => {
|
|
372
|
+
const base0 = { layer, source: file, legacy: false, checkout };
|
|
373
|
+
if (layer === 'user' && spec.shellRead === 'lexical-v1') {
|
|
374
|
+
// Valid JSON still gets the schema check (so invalid enum values are visible in config list);
|
|
375
|
+
// malformed JSON keeps the shell-compatible lexical fallback (IC-004).
|
|
376
|
+
const read = settingsFile(layer, file, checkout);
|
|
377
|
+
if (read.status === 'ok') {
|
|
378
|
+
const entry = read.values.get(key);
|
|
379
|
+
if (entry?.error) {
|
|
380
|
+
out.push({ ...base0, display: displayOf(spec, entry.raw), value: null,
|
|
381
|
+
fault: { ...entry.error, offset: null } });
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Even parseable values use the literal line rule; duplicate keys keep every reader aligned.
|
|
386
|
+
const value = require('./lexical.js').readLexicalCapture(file, io);
|
|
387
|
+
if (value !== null) out.push({ ...base0, display: displayOf(spec, value), value, fault: null });
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const read = settingsFile(layer, file, checkout);
|
|
391
|
+
if (read.status === 'absent') return;
|
|
392
|
+
if (read.status === 'invalid') {
|
|
393
|
+
out.push({ ...base0, display: '', value: null, fault: { code: read.code, reason: read.reason, offset: read.offset } });
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
let entry = read.values.get(key);
|
|
397
|
+
if (!entry) return;
|
|
398
|
+
const retired = entry.error?.code === 'enum' ? retiredHarnessOf(spec, entry.raw) : null;
|
|
399
|
+
if (retired) {
|
|
400
|
+
out.push({ ...base0, display: displayOf(spec, entry.raw), value: null, fault: null, retired });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (spec.unknownMembers === 'warn' && entry.error?.code === 'enum') entry = keepUnknownMembers(key, spec, entry, file);
|
|
404
|
+
out.push({ ...base0, display: displayOf(spec, entry.raw), value: entry.value, fault: entry.error ? { ...entry.error, offset: null } : null });
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
if (home.dir) {
|
|
408
|
+
legacyAt('user', home.dir, null);
|
|
409
|
+
fileAt('user', path.join(home.dir, NEW_FILE.user), null);
|
|
410
|
+
}
|
|
411
|
+
if (viewName === 'project' && layers.layersFrom) {
|
|
412
|
+
legacyAt('project', layers.layersFrom, projectCheckout);
|
|
413
|
+
fileAt('project', path.join(layers.layersFrom, NEW_FILE.project), projectCheckout);
|
|
414
|
+
fileAt('local', path.join(layers.layersFrom, NEW_FILE.local), projectCheckout);
|
|
415
|
+
}
|
|
416
|
+
if (spec.env) {
|
|
417
|
+
const raw = env[spec.env.name];
|
|
418
|
+
if (typeof raw === 'string' && raw !== '') {
|
|
419
|
+
const base0 = { layer: 'env' , source: spec.env.name, legacy: false, checkout: null, display: raw };
|
|
420
|
+
if (spec.env.parse === 'positive-int') {
|
|
421
|
+
// 2.0 parseWindow (lifecycle/retention.ts)
|
|
422
|
+
const ok = /^[1-9][0-9]*$/.test(raw) && Number.isSafeInteger(Number(raw));
|
|
423
|
+
out.push({ ...base0, value: ok ? Number(raw) : null, fault: ok ? null : { code: 'type', reason: /^0[0-9]+$/.test(raw) ? 'must be a positive whole number (no leading zeros)' : 'must be a positive whole number', offset: null } });
|
|
424
|
+
} else if (spec.env.parse === 'verbatim') {
|
|
425
|
+
// 120 IC-002: taken as written and judged like any file value
|
|
426
|
+
const fault = validateValue(spec, raw, registry.enumValues(key));
|
|
427
|
+
const retired = fault?.code === 'enum' ? retiredHarnessOf(spec, raw) : null;
|
|
428
|
+
if (retired) out.push({ ...base0, value: null, fault: null, retired });
|
|
429
|
+
else out.push({ ...base0, value: fault ? null : raw, fault: fault ? { ...fault, offset: null } : null });
|
|
430
|
+
} else if (isOffWord(raw)) {
|
|
431
|
+
// 2.0 envSetting + OFF_VALUES (failure/home.ts): only an off word has an effect
|
|
432
|
+
out.push({ ...base0, value: 'off', fault: null });
|
|
433
|
+
} else {
|
|
434
|
+
out.push({ ...base0, value: null, fault: null, noEffect: true });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
for (const { layer, file } of deprecated) {
|
|
439
|
+
const held = reads.get(newPath(layer));
|
|
440
|
+
if (held?.status === 'ok' && held.values.has(key)) continue;
|
|
441
|
+
if (once(`deprecated\0${file}\0${key}`)) {
|
|
442
|
+
notes.push({ code: 'deprecated', key, source: file, text: `${file} is deprecated; set "${key}" in ${newPath(layer)} (read through 2.x)` });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return out;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function defaultOf(spec , viewName ) {
|
|
449
|
+
if (Object.hasOwn(spec, 'default')) return spec.default;
|
|
450
|
+
const deps = {};
|
|
451
|
+
for (const dep of REF_META[spec.defaultRef]?.dependsOn ?? []) deps[dep] = resolve(dep, viewName).resolution.value;
|
|
452
|
+
return registry.resolveRef(spec.defaultRef, deps);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function fold(key , viewName ) {
|
|
456
|
+
const spec = registry.keys.get(key);
|
|
457
|
+
const fallback = defaultOf(spec, viewName);
|
|
458
|
+
let value = fallback;
|
|
459
|
+
let state = 'ok';
|
|
460
|
+
let origin = { layer: 'default', source: REGISTRY_FILE, legacy: false, checkout: null, via: null };
|
|
461
|
+
let holder = null;
|
|
462
|
+
let tripped = null;
|
|
463
|
+
const candidates = [];
|
|
464
|
+
const take = (c , via , keepHolder = false) => {
|
|
465
|
+
if (!keepHolder && holder && holder.status === 'decided') {
|
|
466
|
+
holder.status = holder.layer === c.layer && holder.legacy && !c.legacy ? 'shadowed' : 'overridden';
|
|
467
|
+
}
|
|
468
|
+
origin = { layer: c.layer, source: c.source, legacy: c.legacy, checkout: c.checkout, via };
|
|
469
|
+
holder = c;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
for (const raw of rawCandidates(key, spec, viewName)) {
|
|
473
|
+
const c = {
|
|
474
|
+
layer: raw.layer, source: raw.source, legacy: raw.legacy, checkout: raw.checkout, display: raw.display,
|
|
475
|
+
value: raw.fault ? null : raw.value, status: 'decided', reason: null, code: null,
|
|
476
|
+
};
|
|
477
|
+
candidates.push(c);
|
|
478
|
+
const projectLayer = c.layer === 'project' || c.layer === 'local';
|
|
479
|
+
const bound = c.layer === 'env';
|
|
480
|
+
if (!projectLayer && spec.scope === 'project-only') {
|
|
481
|
+
Object.assign(c, { status: 'ignored-scope', value: null, reason: 'project-only: only a project can set it' });
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (raw.noEffect) {
|
|
485
|
+
Object.assign(c, { status: 'no-effect', value: null, reason: 'only off, 0, false or no has an effect' });
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (projectLayer && spec.scope === 'global-only') {
|
|
489
|
+
Object.assign(c, { status: 'ignored-scope', value: null, reason: 'global-only: a project cannot set it' });
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (raw.retired) {
|
|
493
|
+
const reason = retiredAdvisory(key, raw.retired);
|
|
494
|
+
Object.assign(c, { status: 'no-effect', value: null, code: 'retired', reason });
|
|
495
|
+
if (once(`retired\0${key}\0${c.source}\0${raw.retired.id}`)) notes.push({ code: 'retired', key, source: c.source, text: reason });
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (raw.fault) {
|
|
499
|
+
Object.assign(c, { status: 'invalid', reason: raw.fault.reason, code: raw.fault.code });
|
|
500
|
+
if (once(`error\0${key}\0${c.layer}\0${c.source}`)) {
|
|
501
|
+
errors.push({ key, layer: c.layer, source: c.source, legacy: c.legacy, code: raw.fault.code, reason: raw.fault.reason, offset: raw.fault.offset });
|
|
502
|
+
}
|
|
503
|
+
const canWiden = c.layer === 'user'
|
|
504
|
+
|| (projectLayer && spec.scope === 'project-overridable')
|
|
505
|
+
|| (bound && spec.env?.direction === 'any');
|
|
506
|
+
if (!canWiden || !Object.hasOwn(spec, 'failSafe')) continue;
|
|
507
|
+
const safe = spec.failSafe;
|
|
508
|
+
if (isObject(safe) && typeof safe.gate === 'string') {
|
|
509
|
+
tripped ??= c; // keeps its lower value; the gate key reacts
|
|
510
|
+
} else if (isObject(safe)) {
|
|
511
|
+
take(c, 'fail-safe'); // {state: 'invalid'}: the consumer's invalid path
|
|
512
|
+
state = 'invalid';
|
|
513
|
+
} else {
|
|
514
|
+
take(c, 'fail-safe');
|
|
515
|
+
value = safe;
|
|
516
|
+
state = 'ok';
|
|
517
|
+
}
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
// IC-002, IC-004: report each dangling worker once per key, source and id, across both views.
|
|
521
|
+
// 127 IC-005: a retired id is reported as retired and dropped from the value instead.
|
|
522
|
+
if (spec.type === 'worker-list' || spec.type === 'worker-models' || spec.type === 'worker-billing') {
|
|
523
|
+
const known = registry.enumValues(key) ?? [];
|
|
524
|
+
const retired = registry.retired?.() ?? [];
|
|
525
|
+
const isList = spec.type === 'worker-list';
|
|
526
|
+
const ids = isList ? c.value : Object.keys(c.value );
|
|
527
|
+
const gone = new Set ();
|
|
528
|
+
for (const id of ids) {
|
|
529
|
+
if (id === 'primary') continue;
|
|
530
|
+
const kind = classifyWorkerId(id, known, retired);
|
|
531
|
+
if (kind === 'retired') {
|
|
532
|
+
gone.add(id);
|
|
533
|
+
if (once(`retired\0${key}\0${c.source}\0${id}`)) {
|
|
534
|
+
notes.push({ code: 'retired', key, source: c.source, text: retiredAdvisory(key, retired.find((entry ) => entry.id === id)) });
|
|
535
|
+
}
|
|
536
|
+
} else if (kind === 'dangling' && spec.type !== 'worker-billing' && once(`dangling-worker\0${key}\0${c.source}\0${id}`)) {
|
|
537
|
+
notes.push({ code: 'dangling-worker', key, source: c.source, text: `${key}: worker '${id}' is not installed (ignored)` });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (gone.size > 0) {
|
|
541
|
+
const kept = isList
|
|
542
|
+
? (c.value ).filter((id) => !gone.has(id))
|
|
543
|
+
: Object.fromEntries(Object.entries(c.value ).filter(([id]) => !gone.has(id)));
|
|
544
|
+
if ((isList ? (kept ).length : Object.keys(kept).length) === 0) {
|
|
545
|
+
Object.assign(c, { status: 'no-effect', value: null, code: 'retired', reason: 'every worker it names is retired' });
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
c.value = kept;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (spec.combine === 'union') {
|
|
552
|
+
const added = (c.value ).filter((item) => !(value ).includes(item));
|
|
553
|
+
if (added.length === 0) {
|
|
554
|
+
Object.assign(c, { status: 'redundant', reason: 'every item is already present' });
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
take(c, null, true); // both the earlier contributor and this one apply
|
|
558
|
+
value = [...(value ), ...added];
|
|
559
|
+
state = 'ok';
|
|
560
|
+
tripped = null;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
// IC-004 rule 3c: a project allowlist can remove entries, never add or reorder them.
|
|
564
|
+
if (spec.combine === 'subset' && projectLayer) {
|
|
565
|
+
if (state === 'invalid') {
|
|
566
|
+
Object.assign(c, { status: 'no-effect', reason: 'the value below is invalid' });
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
const below = value ;
|
|
570
|
+
const candidate = c.value ;
|
|
571
|
+
const isModelList = spec.type === 'model-list';
|
|
572
|
+
const itemKind = isModelList ? 'models' : 'workers';
|
|
573
|
+
const known = isModelList ? [] : (registry.enumValues(key) ?? []);
|
|
574
|
+
const added = isModelList
|
|
575
|
+
? candidate.filter((id) => !below.includes(id))
|
|
576
|
+
: candidate.filter((id) => !below.includes(id) && (id === 'primary' || known.includes(id)));
|
|
577
|
+
if (added.length > 0) {
|
|
578
|
+
Object.assign(c, { status: 'not-narrower', reason: `narrow-only: a project may only remove ${itemKind} ('${added[0]}' is not in the value below)` });
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const result = below.filter((id) => candidate.includes(id));
|
|
582
|
+
if (result.length === below.length) {
|
|
583
|
+
Object.assign(c, { status: 'redundant', reason: 'equal to the value below' });
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (result.length === 0) {
|
|
587
|
+
Object.assign(c, { status: 'no-effect', reason: `no ${itemKind} remain in the intersection` });
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
take(c, null);
|
|
591
|
+
value = result;
|
|
592
|
+
state = 'ok';
|
|
593
|
+
tripped = null;
|
|
594
|
+
const ordered = candidate.filter((id) => result.includes(id));
|
|
595
|
+
if (ordered.some((id, index) => id !== result[index])) {
|
|
596
|
+
c.code = 'order-ignored';
|
|
597
|
+
c.reason = `a project cannot reorder ${itemKind}; the order below applies`;
|
|
598
|
+
}
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const narrowing = (projectLayer && spec.scope === 'narrow-only') || spec.combine === 'narrowest'
|
|
602
|
+
|| (bound && spec.env?.direction === 'narrow');
|
|
603
|
+
if (narrowing) {
|
|
604
|
+
// an unset value (null) is the widest, so any valid candidate narrows it
|
|
605
|
+
const delta = value === null ? -1 : narrowerBy(spec, c.value, value);
|
|
606
|
+
if (delta === 0) {
|
|
607
|
+
Object.assign(c, { status: 'redundant', reason: 'equal to the value below' });
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (delta === null || delta > 0) {
|
|
611
|
+
Object.assign(c, { status: 'not-narrower', reason: projectLayer && spec.scope === 'narrow-only' ? 'narrow-only: a project may only narrow it' : 'only a narrower value applies' });
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
take(c, null);
|
|
616
|
+
value = c.value;
|
|
617
|
+
state = 'ok';
|
|
618
|
+
tripped = null;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (origin.via === null) {
|
|
622
|
+
// a key that names this one as its gate hit its fail-safe: this key takes its own
|
|
623
|
+
for (const [other, otherSpec] of registry.keys) {
|
|
624
|
+
if (!(isObject(otherSpec.failSafe) && otherSpec.failSafe.gate === key)) continue;
|
|
625
|
+
const forced = resolve(other, viewName).tripped;
|
|
626
|
+
if (!forced) continue;
|
|
627
|
+
if (holder && (holder ).status === 'decided') (holder ).status = 'overridden';
|
|
628
|
+
origin = { layer: forced.layer, source: forced.source, legacy: forced.legacy, checkout: forced.checkout, via: 'fail-safe' };
|
|
629
|
+
value = spec.failSafe;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (origin.via === null && Array.isArray(spec.requires)) {
|
|
634
|
+
for (const rule of spec.requires) {
|
|
635
|
+
if (value !== rule.when) continue;
|
|
636
|
+
const needed = resolve(rule.key, scope === 'global' ? 'global' : rule.view).resolution;
|
|
637
|
+
if (needed.value !== null) continue;
|
|
638
|
+
const reason = `is ${JSON.stringify(rule.when)} but "${rule.key}" has no value`;
|
|
639
|
+
if (once(`error\0${key}\0cross-key\0${origin.source}`)) {
|
|
640
|
+
errors.push({ key, layer: origin.layer, source: origin.source ?? REGISTRY_FILE, legacy: origin.legacy, code: 'cross-key', reason, offset: null });
|
|
641
|
+
}
|
|
642
|
+
origin = { ...origin, via: 'cross-key' };
|
|
643
|
+
value = spec.failSafe;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (spec.needsConfigHome && home.dir === null) {
|
|
648
|
+
const previous = holder ;
|
|
649
|
+
if (previous?.status === 'decided') previous.status = 'overridden';
|
|
650
|
+
origin = { layer: 'default', source: null, legacy: false, checkout: null, via: 'no-config-home' };
|
|
651
|
+
value = spec.failSafe;
|
|
652
|
+
state = 'ok';
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const resolution = {
|
|
656
|
+
key, view: viewName, value, state, explicit: origin.layer !== 'default', origin, candidates,
|
|
657
|
+
meta: {
|
|
658
|
+
type: spec.type,
|
|
659
|
+
values: registry.enumValues(key),
|
|
660
|
+
order: spec.order ?? null,
|
|
661
|
+
default: fallback,
|
|
662
|
+
scope: spec.scope, combine: spec.combine, securityRelevant: spec.securityRelevant,
|
|
663
|
+
env: spec.env, flag: spec.flag, shellRead: spec.shellRead, views: spec.views ?? ['project'],
|
|
664
|
+
needsConfigHome: spec.needsConfigHome, description: spec.description,
|
|
665
|
+
},
|
|
666
|
+
};
|
|
667
|
+
return { resolution, tripped };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function resolve(key , viewName ) {
|
|
671
|
+
const id = `${viewName}\0${key}`;
|
|
672
|
+
if (!resolved.has(id)) {
|
|
673
|
+
if (resolving.has(id)) throw new Error(`settings key "${key}" depends on itself`);
|
|
674
|
+
resolving.add(id);
|
|
675
|
+
try { resolved.set(id, fold(key, viewName)); } finally { resolving.delete(id); }
|
|
676
|
+
}
|
|
677
|
+
return resolved.get(id) ;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** One key's resolution in a view (the load's scope by default). Plain data, shared: do not mutate. */
|
|
681
|
+
function get(key , viewName = scope) {
|
|
682
|
+
if (!registry.keys.has(key)) throw new Error(`unknown settings key "${key}"`);
|
|
683
|
+
if (only && !only.includes(key)) throw new Error(`settings key "${key}" is not in this view (only ${only.join(', ')})`);
|
|
684
|
+
if (viewName !== 'project' && viewName !== 'global') throw new Error(`unknown settings view "${viewName}"`);
|
|
685
|
+
if (viewName === 'project' && scope === 'global') throw new Error('the project view is not available in a global (-g) load');
|
|
686
|
+
return resolve(key, viewName).resolution;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** Every key resolved: the `config list --json` payload (IC-012). */
|
|
690
|
+
function report() {
|
|
691
|
+
const keys = {};
|
|
692
|
+
for (const [name, spec] of registry.keys) {
|
|
693
|
+
keys[name] = scope === 'project' && Array.isArray(spec.views) && spec.views.includes('global')
|
|
694
|
+
? { project: get(name, 'project'), global: get(name, 'global') }
|
|
695
|
+
: get(name);
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
schema: 1, scope, configHome: view.configHome, project: view.project,
|
|
699
|
+
files: [...files], keys, errors: [...errors], notes: [...notes],
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const view = {
|
|
704
|
+
schema: 1 ,
|
|
705
|
+
scope,
|
|
706
|
+
configHome: { dir: home.dir, ignored: [...home.ignored] },
|
|
707
|
+
project: { root, layersFrom: layers.layersFrom, checkout: layers.layersFrom === null ? null : layers.checkout },
|
|
708
|
+
files, errors, notes, get, report,
|
|
709
|
+
};
|
|
710
|
+
return view;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
module.exports = { loadSettings, clearSettingsCache };
|