@khoavu882/doflow 2.0.0-beta.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -2
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +8 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +10 -2
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +2 -11
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +3 -2
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +47 -8
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1442 -363
- package/core/registry/lifecycle.json +485 -304
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +679 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +149 -34
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +18 -17
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +15 -58
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +4 -7
- package/core/shared/skills/do-plan/SKILL.md +26 -15
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +143 -66
- package/src/adapters/claude/index.js +24 -4
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/index.js +48 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +290 -44
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +322 -29
- package/src/cli/runtime-commands.js +209 -57
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +20 -3
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +20 -11
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +154 -78
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-ledger.js +34 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +2 -2
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +1 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +374 -64
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -251
- package/src/adapters/gemini/index.js +0 -429
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `doflow rules`: the read-only view of the values the runtime owns for a project, each with its source, so
|
|
5
|
+
* guidance and policy text can point here instead of copying numbers. Four sections:
|
|
6
|
+
* review the protocol `review --action start` resolves per task class, attended and unattended (the real
|
|
7
|
+
* `resolveProtocol`; nothing is copied from the registries)
|
|
8
|
+
* gates per class and gate `autopilot` and `minSeats` from the packaged gates.json, and the loop limits
|
|
9
|
+
* tiers owner -> tier, and tier -> model per worker from `workers.models`
|
|
10
|
+
* settings the effective value and layer of the keys above
|
|
11
|
+
* A value that cannot be resolved prints as `unresolved:<reason>`. The verb writes nothing and spawns only `git`
|
|
12
|
+
* (to say whether develop holds a committed gate policy).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const { spawnSync } = require('node:child_process');
|
|
18
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
19
|
+
const { finishRuntime, usageError, captureCaught } = require('./cli-result.js');
|
|
20
|
+
const { resolveProtocol } = require('./review/resolve-protocol.js');
|
|
21
|
+
const { invalidProjectCandidate, invalidPresetCandidate } = require('./review/findings.js');
|
|
22
|
+
|
|
23
|
+
const REGISTRY = (file ) => path.join(REPO_ROOT, 'core', 'registry', file);
|
|
24
|
+
const PROTOCOLS_FILE = 'core/registry/protocols.json';
|
|
25
|
+
const GATES_FILE = 'core/registry/gates.json';
|
|
26
|
+
const MODELS_FILE = 'core/registry/models.json';
|
|
27
|
+
const SETTINGS_FILE = 'core/registry/settings.json';
|
|
28
|
+
const VERIFICATION_FILE = 'core/registry/verification.json';
|
|
29
|
+
// Keys of the settings section, beyond the ones `resolveProtocol` reads.
|
|
30
|
+
const REPORT_SETTINGS = ['review.preset', 'review.rounds', 'review.sameFamily', 'autopilot.enabled'];
|
|
31
|
+
const GIT_LOCATION_KEYS = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_COMMON_DIR', 'GIT_ALTERNATE_OBJECT_DIRECTORIES'];
|
|
32
|
+
|
|
33
|
+
const unresolved = (reason ) => `unresolved:${String(reason instanceof Error ? reason.message : reason).split('\n')[0].slice(0, 160)}`;
|
|
34
|
+
const isUnresolved = (value ) => typeof value === 'string' && value.startsWith('unresolved:');
|
|
35
|
+
|
|
36
|
+
/** Where an origin or layer name comes from: a registry file for a registry value, else the settings layer. */
|
|
37
|
+
function sourceOf(origin ) {
|
|
38
|
+
switch (origin) {
|
|
39
|
+
case 'registry': return PROTOCOLS_FILE;
|
|
40
|
+
case 'floor': return GATES_FILE;
|
|
41
|
+
case 'risk': return VERIFICATION_FILE;
|
|
42
|
+
case 'default': return SETTINGS_FILE;
|
|
43
|
+
case 'env': return 'environment';
|
|
44
|
+
default: return `settings:${origin}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const readJson = (file ) => JSON.parse(fs.readFileSync(REGISTRY(file), 'utf8'));
|
|
49
|
+
|
|
50
|
+
/** The effective `{ value, layer, source }` of a setting, or `{ value: 'unresolved:…' }` when the resolver cannot say. */
|
|
51
|
+
function settingEntry(settings , key , view ) {
|
|
52
|
+
try {
|
|
53
|
+
const resolution = view ? settings.get(key, view) : settings.get(key);
|
|
54
|
+
// As the protocol loader judges them: a preset key refuses any invalid layer, another review.* key an invalid project or local one.
|
|
55
|
+
const invalid = key.endsWith('.preset') ? invalidPresetCandidate(resolution) : key.startsWith('review.') ? invalidProjectCandidate(resolution) : undefined;
|
|
56
|
+
if (resolution.state === 'invalid' || invalid) {
|
|
57
|
+
const candidate = invalid ?? resolution.candidates.find((c ) => c.status === 'invalid');
|
|
58
|
+
return { value: unresolved(`invalid ${key}${candidate ? ` in ${candidate.source}: ${candidate.reason}` : ''}`), layer: null, source: null };
|
|
59
|
+
}
|
|
60
|
+
const layer = resolution.origin?.layer ?? 'default';
|
|
61
|
+
return { value: resolution.value ?? null, layer, source: sourceOf(layer) };
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return { value: unresolved(error), layer: null, source: null };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The keys `resolveProtocol` reads from the settings, from the registry's own `consumers` lists. */
|
|
68
|
+
function protocolSettingKeys() {
|
|
69
|
+
return Object.entries (readJson('settings.json').keys)
|
|
70
|
+
.filter(([key, spec]) => key.startsWith('review.') && (spec.consumers ?? []).some((c ) => c.endsWith('review/resolve-protocol.ts')))
|
|
71
|
+
.map(([key]) => key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function gitShow(project , args ) {
|
|
75
|
+
const env = { ...process.env };
|
|
76
|
+
for (const key of GIT_LOCATION_KEYS) delete env[key];
|
|
77
|
+
const res = spawnSync('git', ['-C', project, ...args], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 10000 });
|
|
78
|
+
return { status: res.status, stdout: typeof res.stdout === 'string' ? res.stdout : '' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Whether the project's local develop holds a committed gate policy, and whether it is the packaged one. The working tree is never read. */
|
|
82
|
+
function projectPolicyState(project , packagedText ) {
|
|
83
|
+
const develop = gitShow(project, ['rev-parse', '--verify', '-q', 'refs/heads/develop']);
|
|
84
|
+
if (develop.status === null) return unresolved('git cannot run');
|
|
85
|
+
if (develop.status !== 0) return unresolved('no local develop branch');
|
|
86
|
+
const blob = gitShow(project, ['show', `refs/heads/develop:${GATES_FILE}`]);
|
|
87
|
+
if (blob.status !== 0) return 'absent';
|
|
88
|
+
return blob.stdout === packagedText ? 'committed on develop, same as packaged' : 'committed on develop, differs from packaged';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
/** The gate policy as `review --action start` reads it (shipped gates.json narrowed by the project's override), and what the override did. */
|
|
94
|
+
function loadPolicyFor(project ) {
|
|
95
|
+
try {
|
|
96
|
+
const loaded = require('./review/start.js').loadGatePolicy(project);
|
|
97
|
+
if (!loaded.ok) return { policy: null, override: `invalid:${loaded.reason}`, reason: loaded.reason };
|
|
98
|
+
return { policy: loaded.policy, override: loaded.override, reason: null };
|
|
99
|
+
} catch (error) {
|
|
100
|
+
const reason = unresolved(error).slice('unresolved:'.length);
|
|
101
|
+
return { policy: null, override: `invalid:${reason}`, reason };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function gatesSection(taskClasses , project , loaded ) {
|
|
106
|
+
let packagedText = '';
|
|
107
|
+
try { packagedText = fs.readFileSync(REGISTRY('gates.json'), 'utf8'); } catch { /* the policy load has failed the same way */ }
|
|
108
|
+
const head = {
|
|
109
|
+
source: GATES_FILE, override: loaded.override,
|
|
110
|
+
projectPolicy: project ? projectPolicyState(project, packagedText) : unresolved('no project'),
|
|
111
|
+
};
|
|
112
|
+
const policy = loaded.policy;
|
|
113
|
+
if (!policy) return { policy: null, section: { ...head, classes: unresolved(loaded.reason), loop: unresolved(loaded.reason) } };
|
|
114
|
+
const classes = {};
|
|
115
|
+
for (const name of taskClasses) {
|
|
116
|
+
const gates = {};
|
|
117
|
+
for (const [gate, rule] of Object.entries (policy.classes?.[name] ?? {})) {
|
|
118
|
+
gates[gate] = {
|
|
119
|
+
autopilot: typeof rule?.autopilot === 'string' ? rule.autopilot : unresolved('autopilot not declared'),
|
|
120
|
+
minSeats: Number.isInteger(rule?.minSeats) && rule.minSeats > 0 ? rule.minSeats : 0,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
classes[name] = gates;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
policy,
|
|
127
|
+
section: {
|
|
128
|
+
...head,
|
|
129
|
+
classes,
|
|
130
|
+
loop: { maxSteps: policy.loop?.maxSteps ?? unresolved('loop.maxSteps not declared'), wip: policy.loop?.wip ?? unresolved('loop.wip not declared') },
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function protocolRow(registry , protocol ) {
|
|
136
|
+
const preset = registry.presets[protocol.id];
|
|
137
|
+
const origin = protocol.origins.seats === 'floor' ? 'floor' : protocol.origins.id;
|
|
138
|
+
return {
|
|
139
|
+
preset: protocol.id,
|
|
140
|
+
seats: { count: protocol.seats.length, roles: protocol.seats.map((seat ) => seat.agent) },
|
|
141
|
+
rounds: { min: preset.rounds.min, max: registry.hardRoundCap, default: preset.rounds.default, effective: protocol.rounds },
|
|
142
|
+
crossAnswer: protocol.crossAnswer,
|
|
143
|
+
synthesizer: protocol.synthesizer,
|
|
144
|
+
origin,
|
|
145
|
+
source: sourceOf(origin),
|
|
146
|
+
...(protocol.disagreements.length ? { disagreements: protocol.disagreements } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The review section: `resolveProtocol` per class, attended (floor 0) and unattended (the class's gate-b floor). */
|
|
151
|
+
function reviewSection(taskClasses , settings , gatePolicy , gatesReason ) {
|
|
152
|
+
const classes = {};
|
|
153
|
+
let inputs ;
|
|
154
|
+
try {
|
|
155
|
+
const protocols = require('./review/protocols.js').loadProtocols();
|
|
156
|
+
if (!protocols.ok) throw new Error(protocols.errors.join('; '));
|
|
157
|
+
const { loadVerificationRegistry, reviewDepthPolicy } = require('./verification/registry.js');
|
|
158
|
+
const verification = loadVerificationRegistry({ repoRoot: REPO_ROOT });
|
|
159
|
+
const settingsView = {};
|
|
160
|
+
for (const key of protocolSettingKeys()) {
|
|
161
|
+
const entry = settingEntry(settings, key);
|
|
162
|
+
if (isUnresolved(entry.value)) throw new Error(String(entry.value).slice('unresolved:'.length));
|
|
163
|
+
settingsView[key] = { value: entry.value, layer: entry.layer };
|
|
164
|
+
}
|
|
165
|
+
inputs = { registry: protocols.registry, settingsView, risk: verification.policy.defaultRiskLevel, riskPolicy: reviewDepthPolicy(verification) };
|
|
166
|
+
} catch (error) {
|
|
167
|
+
for (const name of taskClasses) classes[name] = { attended: unresolved(error), unattended: unresolved(error) };
|
|
168
|
+
return { classes };
|
|
169
|
+
}
|
|
170
|
+
for (const name of taskClasses) {
|
|
171
|
+
const row = (unattended ) => {
|
|
172
|
+
if (unattended && gatesReason) return unresolved(gatesReason);
|
|
173
|
+
try {
|
|
174
|
+
const resolved = resolveProtocol({
|
|
175
|
+
kind: 'review', taskClass: name, risk: inputs.risk, paths: null, runPreset: null, advicePick: null,
|
|
176
|
+
settingsView: inputs.settingsView, gatePolicy, unattended, registry: inputs.registry, riskPolicy: inputs.riskPolicy,
|
|
177
|
+
});
|
|
178
|
+
return resolved.ok ? protocolRow(inputs.registry, resolved.protocol) : unresolved(`${resolved.reason}:${resolved.field}`);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return unresolved(error);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
classes[name] = { attended: row(false), unattended: row(true) };
|
|
184
|
+
}
|
|
185
|
+
return { classes };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function tiersSection(env ) {
|
|
189
|
+
let tiers ;
|
|
190
|
+
try {
|
|
191
|
+
tiers = readJson('models.json').dispatchTiers;
|
|
192
|
+
if (!tiers) throw new Error(`${MODELS_FILE} has no dispatchTiers`);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return unresolved(error);
|
|
195
|
+
}
|
|
196
|
+
let models ;
|
|
197
|
+
try {
|
|
198
|
+
const { readBindings } = require('./worker/models.js');
|
|
199
|
+
const bindings = readBindings({ env, projectRoot: null });
|
|
200
|
+
const { knownWorkerIds } = require('../registry/worker-ids.js');
|
|
201
|
+
const workers = knownWorkerIds({ registry: { harnesses: readJson('harnesses.json').harnesses } }).ids;
|
|
202
|
+
models = bindings.state === 'invalid'
|
|
203
|
+
? unresolved(`workers.models ${bindings.reason}`)
|
|
204
|
+
: Object.fromEntries(workers.map((worker) => [worker, Object.fromEntries(tiers.tiers.map((tier ) => [tier, bindings.map?.[worker]?.[tier] ?? 'unset']))]));
|
|
205
|
+
} catch (error) {
|
|
206
|
+
models = unresolved(error);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
source: MODELS_FILE, default: tiers.default, tiers: tiers.tiers, byOwner: tiers.byOwner,
|
|
210
|
+
modelsSource: 'settings:user workers.models (global-only)', models,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The report for `project` (null with `global`: the user layer only). */
|
|
215
|
+
function buildRulesReport({ taskClass = null, global = false, project = null, env = process.env } = {}) {
|
|
216
|
+
let taskClasses ;
|
|
217
|
+
try {
|
|
218
|
+
taskClasses = Object.keys(readJson('gates.json').classes);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return { schema: 1, review: unresolved(error), gates: unresolved(error), tiers: tiersSection(env), settings: {} };
|
|
221
|
+
}
|
|
222
|
+
if (taskClass) taskClasses = taskClasses.filter((name) => name === taskClass);
|
|
223
|
+
|
|
224
|
+
let settings = null;
|
|
225
|
+
let settingsReason = null;
|
|
226
|
+
try {
|
|
227
|
+
settings = require('./settings/resolve.js').loadSettings({ projectRoot: project, global, env: { ...env } });
|
|
228
|
+
} catch (error) {
|
|
229
|
+
settingsReason = unresolved(error);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let globalSettings = settings;
|
|
233
|
+
if (settings && !global) {
|
|
234
|
+
try { globalSettings = require('./settings/resolve.js').loadSettings({ global: true, env: { ...env } }); } catch (error) { globalSettings = null; }
|
|
235
|
+
}
|
|
236
|
+
const loadedPolicy = loadPolicyFor(global ? null : project);
|
|
237
|
+
const gates = gatesSection(taskClasses, global ? null : project, loadedPolicy);
|
|
238
|
+
const report = { schema: 1 };
|
|
239
|
+
report.review = settings
|
|
240
|
+
? reviewSection(taskClasses, settings, gates.policy, loadedPolicy.reason)
|
|
241
|
+
: { classes: Object.fromEntries(taskClasses.map((name) => [name, { attended: settingsReason, unattended: settingsReason }])) };
|
|
242
|
+
report.review.override = loadedPolicy.override;
|
|
243
|
+
report.gates = gates.section;
|
|
244
|
+
report.tiers = tiersSection(env);
|
|
245
|
+
report.settings = {};
|
|
246
|
+
for (const key of REPORT_SETTINGS) {
|
|
247
|
+
// The autopilot switch is global-only: read it from the global view whatever the project says.
|
|
248
|
+
const view = key === 'autopilot.enabled' ? globalSettings : settings;
|
|
249
|
+
report.settings[key] = view
|
|
250
|
+
? settingEntry(view, key, key === 'autopilot.enabled' ? 'global' : undefined)
|
|
251
|
+
: { value: settingsReason ?? unresolved('settings unreadable'), layer: null, source: null };
|
|
252
|
+
}
|
|
253
|
+
return report;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function seatsText(seats ) {
|
|
257
|
+
const counts = new Map ();
|
|
258
|
+
for (const role of seats.roles) counts.set(role, (counts.get(role) ?? 0) + 1);
|
|
259
|
+
return `${seats.count} seat${seats.count === 1 ? '' : 's'} (${[...counts].map(([role, n]) => (n > 1 ? `${role} x${n}` : role)).join(', ')})`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function protocolText(row ) {
|
|
263
|
+
if (typeof row === 'string') return row;
|
|
264
|
+
return `${row.preset}: ${seatsText(row.seats)}, rounds ${row.rounds.effective} (min ${row.rounds.min}, max ${row.rounds.max}), cross-answer ${row.crossAnswer ? 'on' : 'off'}, synthesizer ${row.synthesizer}, origin=${row.origin}${row.disagreements ? ` [ignored: ${row.disagreements.map((d ) => `${d.field}=${d.value} ${d.reason}`).join('; ')}]` : ''}`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** The compact text form (about 45 lines for all nine classes). */
|
|
268
|
+
function rulesLines(report ) {
|
|
269
|
+
const lines = [];
|
|
270
|
+
if (typeof report.review === 'string') lines.push(`review: ${report.review}`);
|
|
271
|
+
else lines.push(`review: protocol per task class, as \`review --action start\` resolves it (origin: registry=${PROTOCOLS_FILE}, floor=${GATES_FILE} gate-b minSeats, user|project|local=settings layer; gate override: ${report.review.override ?? 'none'})`);
|
|
272
|
+
for (const [name, rows] of Object.entries (report.review.classes ?? {})) {
|
|
273
|
+
lines.push(` ${name} attended: ${protocolText(rows.attended)}`);
|
|
274
|
+
lines.push(` ${name} unattended: ${protocolText(rows.unattended)}`);
|
|
275
|
+
}
|
|
276
|
+
lines.push('');
|
|
277
|
+
if (typeof report.gates === 'string') lines.push(`gates: ${report.gates}`);
|
|
278
|
+
else {
|
|
279
|
+
lines.push(`gates: ${report.gates.source}; override: ${report.gates.override}; develop policy: ${report.gates.projectPolicy}`);
|
|
280
|
+
const none = [];
|
|
281
|
+
if (typeof report.gates.classes === 'string') lines.push(` ${report.gates.classes}`);
|
|
282
|
+
else for (const [name, gates] of Object.entries (report.gates.classes)) {
|
|
283
|
+
const entries = Object.entries (gates);
|
|
284
|
+
if (entries.length === 0) none.push(name);
|
|
285
|
+
for (const [gate, rule] of entries) lines.push(` ${name} ${gate}: autopilot=${rule.autopilot} minSeats=${rule.minSeats}`);
|
|
286
|
+
}
|
|
287
|
+
if (none.length) lines.push(` no gates declared: ${none.join(', ')}`);
|
|
288
|
+
if (typeof report.gates.loop !== 'string') lines.push(` loop: maxSteps=${report.gates.loop.maxSteps} wip=${report.gates.loop.wip}`);
|
|
289
|
+
}
|
|
290
|
+
lines.push('');
|
|
291
|
+
if (typeof report.tiers === 'string') lines.push(`tiers: ${report.tiers}`);
|
|
292
|
+
else {
|
|
293
|
+
lines.push(`tiers: owner -> tier from ${report.tiers.source} (default ${report.tiers.default}; tiers ${report.tiers.tiers.join(', ')})`);
|
|
294
|
+
lines.push(` ${Object.entries(report.tiers.byOwner).map(([owner, tier]) => `${owner}=${tier}`).join(' ')}`);
|
|
295
|
+
if (typeof report.tiers.models === 'string') lines.push(` models: ${report.tiers.models}`);
|
|
296
|
+
else {
|
|
297
|
+
const set = Object.entries (report.tiers.models).filter(([, byTier]) => Object.values(byTier).some((model) => model !== 'unset'));
|
|
298
|
+
lines.push(` model per worker and tier (${report.tiers.modelsSource}): ${set.length === 0 ? `unset for all ${Object.keys(report.tiers.models).length} workers` : ''}`.trimEnd());
|
|
299
|
+
for (const [worker, byTier] of set) lines.push(` ${worker}: ${Object.entries(byTier).map(([tier, model]) => `${tier}=${model}`).join(' ')}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
lines.push('');
|
|
303
|
+
lines.push('settings: effective value and layer');
|
|
304
|
+
for (const [key, entry] of Object.entries (report.settings)) {
|
|
305
|
+
lines.push(` ${key} = ${isUnresolved(entry.value) ? entry.value : JSON.stringify(entry.value)}${entry.layer ? ` (${entry.layer}, ${entry.source})` : ''}`);
|
|
306
|
+
}
|
|
307
|
+
return lines;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function handleRulesCommand({ taskClass, json = false, global = false, positional = [], env = process.env, cwd = process.cwd(), out = (line ) => console.log(line) } = {}) {
|
|
311
|
+
const usage = (message ) => usageError('rules', message, false);
|
|
312
|
+
try {
|
|
313
|
+
if (positional.length > 1) return usage(`unexpected positional argument '${positional[1]}'`);
|
|
314
|
+
if (global && positional.length > 0) return usage('-g reads the user layer only and takes no project');
|
|
315
|
+
let project = null;
|
|
316
|
+
if (!global) {
|
|
317
|
+
project = path.resolve(cwd, positional[0] ?? '.');
|
|
318
|
+
if (!fs.existsSync(project) || !fs.statSync(project).isDirectory()) return usage(`project '${positional[0]}' is not a directory`);
|
|
319
|
+
}
|
|
320
|
+
if (taskClass != null) {
|
|
321
|
+
let known = [];
|
|
322
|
+
try { known = Object.keys(readJson('gates.json').classes); } catch { /* every class is then unchecked; the report says unresolved */ }
|
|
323
|
+
if (known.length > 0 && !known.includes(taskClass)) return usage(`--task-class '${taskClass}' is not a task class (${known.join(', ')})`);
|
|
324
|
+
}
|
|
325
|
+
const report = buildRulesReport({ taskClass, global, project, env });
|
|
326
|
+
if (json) out(JSON.stringify(report));
|
|
327
|
+
else for (const line of rulesLines(report)) out(line);
|
|
328
|
+
return finishRuntime(0);
|
|
329
|
+
} catch (error ) {
|
|
330
|
+
captureCaught(error, 'rules', 1);
|
|
331
|
+
console.error(`[ERROR] rules: ${error?.message || String(error)}`);
|
|
332
|
+
return finishRuntime(1);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
module.exports = { handleRulesCommand, buildRulesReport, rulesLines };
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Artifact parsing — turns the text of `requirement.md`, `design.md` and `plan.md` into the
|
|
7
7
|
* structures `scaffold.js`'s generator reads: a requirement index and acceptance criteria, a
|
|
8
|
-
* component index and whatever
|
|
8
|
+
* component index and whatever API / Interface Contracts declares as an interface, and the task list that is where the
|
|
9
9
|
* intended file layout actually comes from.
|
|
10
10
|
*
|
|
11
11
|
* Pure text-in, structure-out: nothing here touches the filesystem or knows about the scaffold
|
|
@@ -93,7 +93,7 @@ function parseRequirement(text ) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* `design.md` → the component index (traceability) and whatever
|
|
96
|
+
* `design.md` → the component index (traceability) and whatever API / Interface Contracts declares as an interface
|
|
97
97
|
* (carried through verbatim rather than paraphrased).
|
|
98
98
|
*/
|
|
99
99
|
function parseDesign(text ) {
|
|
@@ -113,7 +113,7 @@ function parseDesign(text ) {
|
|
|
113
113
|
gaps.push({ artifact: 'design.md', what: 'component index', why: 'no `| Cn | … |` table row found under a "Components & Boundaries" heading' });
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
//
|
|
116
|
+
// the Interface Contracts subsections name their component in the heading — "### 4.5 Scaffold output contract (C16)".
|
|
117
117
|
// That attribution is what lets a declared interface reach the file that implements it, instead
|
|
118
118
|
// of being restated from memory somewhere downstream.
|
|
119
119
|
const contractBody = section(text, /API\s*\/?\s*Interface Contracts|Interface Contracts/i);
|
|
@@ -151,20 +151,57 @@ function parseDesign(text ) {
|
|
|
151
151
|
* `external-contract:` names the case this generator cannot help with: a dependency with no local
|
|
152
152
|
* repo to scan — a vendor API, a SaaS integration, a service in another org — whose contract exists
|
|
153
153
|
* only as a document. The doc is an *input* to the scaffold, never part of it.
|
|
154
|
+
*
|
|
155
|
+
* `tier:` (106 review R-1) is the task line's dispatch tier, read by dispatch the way brief-tools
|
|
156
|
+
* reads it. It never opens the metadata, and the parsed task carries `tier` only when the line has
|
|
157
|
+
* one, so a line without it parses exactly as before; the line hash and the graph tuples list their
|
|
158
|
+
* fields and do not read it.
|
|
159
|
+
*/
|
|
160
|
+
const TASK_KEYS = ['owner', 'files', 'depends-on', 'external-contract', 'acceptance', 'budget', 'tier'];
|
|
161
|
+
|
|
162
|
+
/** DEC-021: the keys that open the metadata wherever they follow whitespace or a dash (8ce5ad7). */
|
|
163
|
+
const LEGACY_KEY_PATTERN = /(?:^|[\s—-])(owner|files|depends-on|external-contract):\s/;
|
|
164
|
+
/** DEC-021: the 080 keys open the metadata only after the em-dash separator. */
|
|
165
|
+
const NEW_KEY_PATTERN = /—\s*(acceptance|budget):\s/;
|
|
166
|
+
|
|
167
|
+
/** A `depends-on:` value naming another task of the same plan (IC-001). */
|
|
168
|
+
const TASK_ID = /^[A-Za-z]+\.\d+$/;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The `**Task graph:**` header before the first `## ` section (IC-001, DEC-008): `'v1'` opts the
|
|
172
|
+
* plan into the graph rules, absence is `null`, and any other value is kept as `{ invalid }` for
|
|
173
|
+
* IC-003's `bad-header`.
|
|
154
174
|
*/
|
|
155
|
-
|
|
175
|
+
function parseGraphHeader(text ) {
|
|
176
|
+
for (const line of text.split(/\r?\n/)) {
|
|
177
|
+
if (/^##\s/.test(line)) break;
|
|
178
|
+
const match = line.match(/^\*\*Task graph:\*\*\s*(.*)$/);
|
|
179
|
+
if (match) return match[1].trim() === 'v1' ? 'v1' : { invalid: match[1].trim() };
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** `budget: attempts=<n>`, n in 1..999; anything else is kept verbatim as `{ invalid }`. */
|
|
185
|
+
function parseBudget(value ) {
|
|
186
|
+
if (value === undefined) return null;
|
|
187
|
+
const raw = value.replace(/`/g, '').trim();
|
|
188
|
+
const match = raw.match(/^attempts=([0-9]+)$/);
|
|
189
|
+
const attempts = match ? Number(match[1]) : NaN;
|
|
190
|
+
return attempts >= 1 && attempts <= 999 ? { attempts } : { invalid: raw };
|
|
191
|
+
}
|
|
156
192
|
|
|
157
193
|
/** `plan.md` → the task list, which is where the intended file layout actually comes from. */
|
|
158
194
|
function parsePlan(text ) {
|
|
159
195
|
const gaps = [];
|
|
160
196
|
const tasks = [];
|
|
197
|
+
const graphHeader = parseGraphHeader(text);
|
|
161
198
|
|
|
162
199
|
// Anchored on the whole heading: `### Task Summary` is orientation, not the task list, and a
|
|
163
200
|
// loose match would read the summary table's rows as tasks.
|
|
164
201
|
const body = section(text, /^##\s+(?:\d+\.\s*)?Tasks\s*$/i);
|
|
165
202
|
if (body === null) {
|
|
166
203
|
gaps.push({ artifact: 'plan.md', what: 'task list', why: 'no "Tasks" section' });
|
|
167
|
-
return { tasks, gaps };
|
|
204
|
+
return { tasks, gaps, graphHeader };
|
|
168
205
|
}
|
|
169
206
|
|
|
170
207
|
for (const line of joinWrapped(body)) {
|
|
@@ -174,8 +211,13 @@ function parsePlan(text ) {
|
|
|
174
211
|
if (!match) continue;
|
|
175
212
|
const [, mark, id, rest] = match;
|
|
176
213
|
|
|
177
|
-
|
|
178
|
-
|
|
214
|
+
// DEC-021 new-keys-open-after-separator: the four pre-080 keys open the metadata as on 8ce5ad7;
|
|
215
|
+
// `acceptance:` and `budget:` open it only after the ` — ` separator, so prose such as
|
|
216
|
+
// "Record the acceptance: criteria" in a description keeps its 8ce5ad7 parse and line hash.
|
|
217
|
+
// Once the metadata has started, every `;` chunk parses all six keys.
|
|
218
|
+
const legacyAt = rest.search(LEGACY_KEY_PATTERN);
|
|
219
|
+
const newAt = rest.search(NEW_KEY_PATTERN);
|
|
220
|
+
const keyAt = legacyAt === -1 ? newAt : newAt === -1 ? legacyAt : Math.min(legacyAt, newAt);
|
|
179
221
|
const head = (keyAt === -1 ? rest : rest.slice(0, keyAt)).replace(/[\s—-]+$/, '').trim();
|
|
180
222
|
const meta = keyAt === -1 ? '' : rest.slice(keyAt).replace(/^[\s—-]+/, '');
|
|
181
223
|
|
|
@@ -190,6 +232,9 @@ function parsePlan(text ) {
|
|
|
190
232
|
}
|
|
191
233
|
|
|
192
234
|
const list = (value ) => (value ? value.split(',').map((v) => v.replace(/`/g, '').trim()).filter(Boolean) : []);
|
|
235
|
+
const dependsOn = list(fields['depends-on']);
|
|
236
|
+
// A near-miss starts with a task id but carries more text (`A.1 (after review)`).
|
|
237
|
+
const nearMiss = (value ) => !TASK_ID.test(value) && TASK_ID.test(value.split(/[\s(]/)[0]);
|
|
193
238
|
|
|
194
239
|
tasks.push({
|
|
195
240
|
id,
|
|
@@ -199,15 +244,21 @@ function parsePlan(text ) {
|
|
|
199
244
|
stories,
|
|
200
245
|
owner: fields.owner || null,
|
|
201
246
|
files: list(fields.files),
|
|
202
|
-
dependsOn
|
|
247
|
+
dependsOn,
|
|
203
248
|
externalContract: fields['external-contract'] || null,
|
|
249
|
+
parallel: /\[P\]/.test(head),
|
|
250
|
+
taskDeps: dependsOn.filter((value) => TASK_ID.test(value)),
|
|
251
|
+
nearMissDeps: dependsOn.filter(nearMiss),
|
|
252
|
+
acceptance: list(fields.acceptance),
|
|
253
|
+
budget: parseBudget(fields.budget),
|
|
254
|
+
...(fields.tier === undefined ? {} : { tier: fields.tier.replace(/`/g, '').trim() }),
|
|
204
255
|
});
|
|
205
256
|
}
|
|
206
257
|
|
|
207
258
|
if (tasks.length === 0) {
|
|
208
259
|
gaps.push({ artifact: 'plan.md', what: 'task list', why: 'the Tasks section holds no `- [ ] <phase>.<n> …` items — there is no intended file layout to mirror' });
|
|
209
260
|
}
|
|
210
|
-
return { tasks, gaps };
|
|
261
|
+
return { tasks, gaps, graphHeader };
|
|
211
262
|
}
|
|
212
263
|
|
|
213
264
|
module.exports = {
|
|
@@ -336,13 +336,13 @@ function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message,
|
|
|
336
336
|
? reqs.map((r ) => `requirement: ${r.id} — ${r.summary}`)
|
|
337
337
|
: ['requirement: none — no user story on the owning task resolves to a requirement']),
|
|
338
338
|
...(comps.length
|
|
339
|
-
? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md
|
|
339
|
+
? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md Components & Boundaries)`)
|
|
340
340
|
: ['component: none — no design component serves this task\'s requirements']),
|
|
341
341
|
];
|
|
342
342
|
|
|
343
343
|
const bodyParts = [''];
|
|
344
344
|
if (declared.length) {
|
|
345
|
-
header.push(`shape: declared in design.md
|
|
345
|
+
header.push(`shape: declared in design.md API / Interface Contracts (${[...new Set(declared.map((d ) => d.component))].join(', ')}), quoted below`);
|
|
346
346
|
for (const decl of declared) {
|
|
347
347
|
const lines = decl.block.split('\n');
|
|
348
348
|
const shown = lines.slice(0, 40);
|
|
@@ -354,11 +354,11 @@ function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message,
|
|
|
354
354
|
].join('\n'));
|
|
355
355
|
}
|
|
356
356
|
} else {
|
|
357
|
-
header.push('shape: not declared in design.md
|
|
357
|
+
header.push('shape: not declared in design.md API / Interface Contracts — placeholder signature below');
|
|
358
358
|
// Not a skip — the file was emitted. Conflating the two would let 29 "we wrote it, but the
|
|
359
359
|
// design never said what it should look like" rows hide inside a skip count, which is the
|
|
360
360
|
// opposite of what the manifest is for.
|
|
361
|
-
shapeUndeclared.push({ path: path.join('src', file) + STUB_SUFFIX, why: 'design.md
|
|
361
|
+
shapeUndeclared.push({ path: path.join('src', file) + STUB_SUFFIX, why: 'design.md API / Interface Contracts declares no interface for this file\'s component — the signature below is a placeholder, not a contract' });
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
const pkg = snake(words(path.basename(path.dirname(file)) || 'scaffold')).replace(/_/g, '');
|
|
@@ -422,7 +422,7 @@ function planTestStubs({ requirement, componentsForRequirement, language, slug,
|
|
|
422
422
|
'reads as coverage. Replace the body, do not delete the failure.',
|
|
423
423
|
'',
|
|
424
424
|
`requirement: ${id}${req ? ` — ${req.summary}` : ' — not present in the requirement index'}`,
|
|
425
|
-
...(comps.length ? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md
|
|
425
|
+
...(comps.length ? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md Components & Boundaries)`) : []),
|
|
426
426
|
`criteria: ${cases.length} from requirement.md §6`,
|
|
427
427
|
`language: ${language.id} (detected from ${language.signal} by command-detect.ts)`,
|
|
428
428
|
];
|
|
@@ -442,15 +442,27 @@ function planTestStubs({ requirement, componentsForRequirement, language, slug,
|
|
|
442
442
|
/**
|
|
443
443
|
* contracts/ — partitioned here, generated by the prose algorithm. See the module header for why
|
|
444
444
|
* the walk-up is not reimplemented in code.
|
|
445
|
+
* A task-id `depends-on:` value (`taskDeps`) is an edge in the task graph, not a service, so it
|
|
446
|
+
* gets no frame. A task whose `external-contract:` is left with no external value after that
|
|
447
|
+
* filter is pushed onto `notEvaluated` as `external-contract-orphaned` rather than dropped (IC-001).
|
|
445
448
|
* @param {Array<Object>} tasks from `plan.tasks`
|
|
446
449
|
* @param {Set<string>} ownedPaths every path a task in this plan already owns
|
|
450
|
+
* @param {Array<Object>} notEvaluated mutated with one entry per orphaned `external-contract:`
|
|
447
451
|
* @returns {Array<Object>} externalDependencies
|
|
448
452
|
*/
|
|
449
|
-
function partitionExternalDependencies(tasks , ownedPaths ) {
|
|
453
|
+
function partitionExternalDependencies(tasks , ownedPaths , notEvaluated ) {
|
|
450
454
|
const externalDependencies = [];
|
|
451
455
|
const dependencyTasks = new Map ();
|
|
452
456
|
for (const task of tasks) {
|
|
453
|
-
|
|
457
|
+
const taskDeps = new Set(task.taskDeps || []);
|
|
458
|
+
const external = task.dependsOn.filter((dep ) => !taskDeps.has(dep));
|
|
459
|
+
if (task.externalContract && external.length === 0) {
|
|
460
|
+
notEvaluated.push({
|
|
461
|
+
what: `plan.md → ${task.id} external-contract: ${task.externalContract}`,
|
|
462
|
+
why: 'external-contract-orphaned — no external depends-on: value remains once task-id dependencies are removed, so no contract frame is generated from it',
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
for (const dep of external) {
|
|
454
466
|
if (!dependencyTasks.has(dep)) dependencyTasks.set(dep, []);
|
|
455
467
|
dependencyTasks.get(dep) .push(task);
|
|
456
468
|
}
|
|
@@ -714,7 +726,7 @@ function generateScaffold(options = {}) {
|
|
|
714
726
|
const planned = new Map([...sourcePlanned, ...testPlanned]);
|
|
715
727
|
|
|
716
728
|
const ownedPaths = new Set([...byPath.keys()]);
|
|
717
|
-
const externalDependencies = partitionExternalDependencies(plan.tasks, ownedPaths);
|
|
729
|
+
const externalDependencies = partitionExternalDependencies(plan.tasks, ownedPaths, notEvaluated);
|
|
718
730
|
|
|
719
731
|
const { written, unchanged, preserved } = applyPlannedFiles({ planned, blocked, scaffoldDir, fsImpl });
|
|
720
732
|
|
|
@@ -777,7 +789,7 @@ function renderManifest(result , planned ) {
|
|
|
777
789
|
'_Nothing generated._');
|
|
778
790
|
|
|
779
791
|
lines.push('## Emitted without a declared shape', '');
|
|
780
|
-
lines.push('These files exist and are traceable, but `design.md`
|
|
792
|
+
lines.push('These files exist and are traceable, but `design.md` API / Interface Contracts never declared an interface for',
|
|
781
793
|
'them, so their signature is a placeholder rather than a contract. Reviewing the placeholder as',
|
|
782
794
|
'if it were the designed API is the mistake this section exists to prevent.', '');
|
|
783
795
|
table(['Path', 'Why'], result.shapeUndeclared.map((s ) => [s.path, s.why]),
|