@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.3
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/LICENSE +21 -0
- package/README.md +132 -2
- package/bin/doflow.js +44 -0
- package/core/.antigravity-plugin/hooks.json +39 -0
- package/core/.antigravity-plugin/mcp_config.json +18 -0
- package/core/.antigravity-plugin/plugin.json +31 -0
- package/core/.claude-plugin/marketplace.json +20 -0
- package/core/.claude-plugin/plugin.json +21 -0
- package/core/.codex-plugin/marketplace.json +20 -0
- package/core/.codex-plugin/plugin.json +35 -0
- package/core/.plugin/plugin.json +19 -0
- package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
- package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
- package/core/harnesses/claude/hooks/lib.sh +304 -0
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
- package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
- package/core/harnesses/claude/hooks/post-compact.sh +74 -0
- package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
- package/core/harnesses/claude/hooks/session-end.sh +89 -0
- package/core/harnesses/claude/hooks/session-start.sh +5 -0
- package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -0
- package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
- package/core/harnesses/claude/settings/keybindings.json +20 -0
- package/core/harnesses/claude/settings/settings.json +201 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
- package/core/harnesses/codex/agents/core-implementer.toml +7 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
- package/core/harnesses/codex/agents/research-writer.toml +8 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
- package/core/harnesses/codex/agents/system-architect.toml +9 -0
- package/core/harnesses/codex/config/config.toml +10 -0
- package/core/harnesses/codex/hooks/hooks.json +122 -0
- package/core/harnesses/codex/hooks/lib.sh +335 -0
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
- package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
- package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
- package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
- package/core/harnesses/codex/hooks/session-end.sh +5 -0
- package/core/harnesses/codex/hooks/session-start.sh +5 -0
- package/core/harnesses/codex/hooks/stop-check.sh +8 -0
- package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
- package/core/harnesses/gemini/hooks/hooks.json +77 -0
- package/core/harnesses/gemini/hooks/lib.sh +302 -0
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
- package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
- package/core/harnesses/gemini/hooks/session-end.sh +5 -0
- package/core/harnesses/gemini/hooks/session-start.sh +5 -0
- package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
- package/core/harnesses/kiro/agents/doflow.json +58 -0
- package/core/harnesses/kiro/hooks/doflow.json +53 -0
- package/core/harnesses/kiro/hooks/lib.sh +324 -0
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
- package/core/harnesses/kiro/hooks/session-start.sh +9 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
- package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
- package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
- package/core/harnesses/shared/hooks/hooks.json +47 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
- package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
- package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
- package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
- package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
- package/core/harnesses/shared/locator/doflow-run +87 -0
- package/core/registry/advice.json +101 -0
- package/core/registry/assets.json +792 -0
- package/core/registry/capabilities.json +120 -0
- package/core/registry/contracts.json +189 -0
- package/core/registry/external-tools.json +191 -0
- package/core/registry/harnesses.json +2062 -0
- package/core/registry/lifecycle.json +231 -0
- package/core/registry/mcp.json +18 -0
- package/core/registry/models.json +79 -0
- package/core/registry/readiness-templates.json +118 -0
- package/core/registry/routes.json +47 -0
- package/core/registry/storage.json +1057 -0
- package/core/registry/verification.json +258 -0
- package/core/registry/workflows.json +790 -0
- package/core/shared/agent-specs/core-implementer.md +73 -0
- package/core/shared/agent-specs/quality-guardian.md +28 -0
- package/core/shared/agent-specs/research-writer.md +31 -0
- package/core/shared/agent-specs/spec-analyst.md +33 -0
- package/core/shared/agent-specs/system-architect.md +29 -0
- package/core/shared/guidance/DOFLOW_CORE.md +8 -0
- package/core/shared/guidance/FLAGS.md +25 -0
- package/core/shared/guidance/PRINCIPLES.md +61 -0
- package/core/shared/guidance/VERSION +1 -0
- package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
- package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
- package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
- package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
- package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
- package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
- package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
- package/core/shared/guidance/pointers/claude-gemini.md +2 -0
- package/core/shared/guidance/pointers/codex.md +6 -0
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
- package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
- package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
- package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
- package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
- package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
- package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
- package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
- package/core/shared/scripts/doflow/bin/doflow-run +592 -0
- package/core/shared/skills/do/SKILL.md +128 -0
- package/core/shared/skills/do/references/estimation.md +13 -0
- package/core/shared/skills/do/references/pm_routing.md +25 -0
- package/core/shared/skills/do/references/tool_matrix.md +52 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
- package/core/shared/skills/do-code-review/README.md +49 -0
- package/core/shared/skills/do-code-review/SKILL.md +333 -0
- package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
- package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
- package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
- package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
- package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
- package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
- package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
- package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
- package/core/shared/skills/do-code-review/content-types/config.md +83 -0
- package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
- package/core/shared/skills/do-code-review/languages/c.md +103 -0
- package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
- package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
- package/core/shared/skills/do-code-review/languages/dart.md +108 -0
- package/core/shared/skills/do-code-review/languages/go.md +92 -0
- package/core/shared/skills/do-code-review/languages/java.md +103 -0
- package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
- package/core/shared/skills/do-code-review/languages/php.md +109 -0
- package/core/shared/skills/do-code-review/languages/python.md +94 -0
- package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
- package/core/shared/skills/do-code-review/languages/rust.md +99 -0
- package/core/shared/skills/do-code-review/languages/shell.md +90 -0
- package/core/shared/skills/do-code-review/languages/swift.md +88 -0
- package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
- package/core/shared/skills/do-code-review/review-policy.json +47 -0
- package/core/shared/skills/do-code-review/rules/universal.md +49 -0
- package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
- package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
- package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
- package/core/shared/skills/do-constitution/SKILL.md +72 -0
- package/core/shared/skills/do-design/SKILL.md +212 -0
- package/core/shared/skills/do-diagnose/SKILL.md +156 -0
- package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
- package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
- package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
- package/core/shared/skills/do-document/SKILL.md +81 -0
- package/core/shared/skills/do-document/references/api-reference.md +42 -0
- package/core/shared/skills/do-document/references/deep_research.md +18 -0
- package/core/shared/skills/do-document/references/embedded-research.md +11 -0
- package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
- package/core/shared/skills/do-document/references/index_generator.md +9 -0
- package/core/shared/skills/do-document/references/user-guide.md +22 -0
- package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
- package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
- package/core/shared/skills/do-flow/SKILL.md +186 -0
- package/core/shared/skills/do-flow/references/task_classes.md +134 -0
- package/core/shared/skills/do-git/SKILL.md +115 -0
- package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
- package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
- package/core/shared/skills/do-git/references/release-ritual.md +64 -0
- package/core/shared/skills/do-implement/SKILL.md +222 -0
- package/core/shared/skills/do-plan/SKILL.md +219 -0
- package/core/shared/skills/do-test/SKILL.md +113 -0
- package/core/shared/templates/doflow/constitution-template.md +28 -0
- package/core/shared/templates/doflow/data-model-template.md +169 -0
- package/core/shared/templates/doflow/design-template.md +193 -0
- package/core/shared/templates/doflow/external-contract-template.md +35 -0
- package/core/shared/templates/doflow/intent-template.md +45 -0
- package/core/shared/templates/doflow/plan-template.md +151 -0
- package/core/shared/templates/doflow/question-log-template.md +51 -0
- package/core/shared/templates/doflow/requirement-template.md +126 -0
- package/core/shared/templates/doflow/specs-template.md +55 -0
- package/core/shared/templates/doflow/state-template.md +59 -0
- package/core/shared/workflows/do-flow-chain.md +17 -0
- package/package.json +46 -4
- package/src/adapters/antigravity/index.ts +631 -0
- package/src/adapters/claude/index.ts +548 -0
- package/src/adapters/claude/mcp.ts +94 -0
- package/src/adapters/codex/agents.ts +208 -0
- package/src/adapters/codex/config.ts +272 -0
- package/src/adapters/codex/hooks.ts +203 -0
- package/src/adapters/codex/index.ts +707 -0
- package/src/adapters/codex/mcp.ts +271 -0
- package/src/adapters/codex/native-projection.ts +34 -0
- package/src/adapters/copilot/index.ts +413 -0
- package/src/adapters/copy-tree.ts +620 -0
- package/src/adapters/gemini/hooks.ts +210 -0
- package/src/adapters/gemini/index.ts +390 -0
- package/src/adapters/hook-commands.ts +60 -0
- package/src/adapters/index.ts +106 -0
- package/src/adapters/kiro/index.ts +310 -0
- package/src/adapters/mcp-entries.ts +331 -0
- package/src/adapters/opencode/index.ts +466 -0
- package/src/adapters/pi/index.ts +279 -0
- package/src/adapters/pi/json-members.ts +185 -0
- package/src/adapters/pi/mcp.ts +373 -0
- package/src/adapters/resolve-source.ts +19 -0
- package/src/cli/commands/install.ts +218 -0
- package/src/cli/commands/list-backups.ts +21 -0
- package/src/cli/commands/reconcile.ts +120 -0
- package/src/cli/commands/remove.ts +82 -0
- package/src/cli/commands/rollback.ts +172 -0
- package/src/cli/commands/self-update.ts +33 -0
- package/src/cli/commands/status.ts +138 -0
- package/src/cli/commands/tools.ts +92 -0
- package/src/cli/commands/update.ts +162 -0
- package/src/cli/index.ts +568 -0
- package/src/cli/runtime-commands.ts +281 -0
- package/src/cli/shared.ts +218 -0
- package/src/helper/bash-helper.ts +34 -0
- package/src/helper/git.ts +24 -0
- package/src/helper/guardrails.ts +49 -0
- package/src/helper/harness-paths.ts +140 -0
- package/src/helper/marker-merge.ts +116 -0
- package/src/helper/prompt.ts +61 -0
- package/src/helper/repo-root.ts +29 -0
- package/src/helper/settings-merge.ts +151 -0
- package/src/helper/settings-scope.ts +33 -0
- package/src/helper/toml.ts +244 -0
- package/src/install/backup-set.ts +149 -0
- package/src/install/backup.ts +724 -0
- package/src/install/context.ts +58 -0
- package/src/install/manifest.ts +145 -0
- package/src/install/mcp.ts +254 -0
- package/src/install/paths.ts +84 -0
- package/src/install/targets.ts +90 -0
- package/src/install/tool-lifecycle.ts +151 -0
- package/src/lifecycle/bash-availability.ts +25 -0
- package/src/lifecycle/index.ts +812 -0
- package/src/lifecycle/mcp-index.ts +28 -0
- package/src/lifecycle/policies.ts +73 -0
- package/src/lifecycle/view.ts +175 -0
- package/src/registry/index.ts +611 -0
- package/src/release/sync-plugin-versions.ts +66 -0
- package/src/runtime/advice/cli.ts +283 -0
- package/src/runtime/advice/client.ts +324 -0
- package/src/runtime/advice/config.ts +189 -0
- package/src/runtime/advice/doctor.ts +169 -0
- package/src/runtime/advice/endpoint-guard.ts +134 -0
- package/src/runtime/advice/kinds.ts +154 -0
- package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
- package/src/runtime/advice/record.ts +166 -0
- package/src/runtime/advice/registry.ts +170 -0
- package/src/runtime/advice/strength.ts +66 -0
- package/src/runtime/brief-tools.ts +342 -0
- package/src/runtime/c4-project.ts +371 -0
- package/src/runtime/capability-router.ts +484 -0
- package/src/runtime/checkouts.ts +170 -0
- package/src/runtime/claims.ts +468 -0
- package/src/runtime/cli-result.ts +55 -0
- package/src/runtime/cli.ts +678 -0
- package/src/runtime/command-detect.ts +516 -0
- package/src/runtime/context-pack.ts +492 -0
- package/src/runtime/decision-register.ts +493 -0
- package/src/runtime/evidence-ledger.ts +338 -0
- package/src/runtime/failure/capture.ts +272 -0
- package/src/runtime/failure/classifier.ts +68 -0
- package/src/runtime/failure/cli.ts +178 -0
- package/src/runtime/failure/home.ts +64 -0
- package/src/runtime/failure/store.ts +226 -0
- package/src/runtime/feature-resolve.ts +79 -0
- package/src/runtime/freshness.ts +177 -0
- package/src/runtime/health.ts +863 -0
- package/src/runtime/history-compactor.ts +237 -0
- package/src/runtime/implementation-gate.ts +321 -0
- package/src/runtime/inventory/coverage.ts +218 -0
- package/src/runtime/inventory/identity.ts +65 -0
- package/src/runtime/inventory/index.ts +705 -0
- package/src/runtime/inventory/precedence.ts +134 -0
- package/src/runtime/inventory/read-scopes.ts +131 -0
- package/src/runtime/inventory/siblings.ts +192 -0
- package/src/runtime/knowledge/chunker.ts +124 -0
- package/src/runtime/knowledge/hybrid.ts +149 -0
- package/src/runtime/knowledge/index-store.ts +145 -0
- package/src/runtime/knowledge/retrieval.ts +194 -0
- package/src/runtime/leak-scan.ts +185 -0
- package/src/runtime/lifecycle/cli.ts +276 -0
- package/src/runtime/lifecycle/event-store.ts +340 -0
- package/src/runtime/lifecycle/fold.ts +353 -0
- package/src/runtime/lifecycle/followup.ts +432 -0
- package/src/runtime/lifecycle/goal.ts +249 -0
- package/src/runtime/lifecycle/intent-writer.ts +86 -0
- package/src/runtime/lifecycle/overview.ts +292 -0
- package/src/runtime/lifecycle/release.ts +241 -0
- package/src/runtime/lifecycle/report-store.ts +221 -0
- package/src/runtime/lifecycle/retention.ts +212 -0
- package/src/runtime/lifecycle/root.ts +71 -0
- package/src/runtime/lifecycle/status.ts +353 -0
- package/src/runtime/lifecycle/store-upkeep.ts +393 -0
- package/src/runtime/locator-resolve.ts +158 -0
- package/src/runtime/mask.ts +210 -0
- package/src/runtime/mcp-consultations.ts +412 -0
- package/src/runtime/mcp-reach.ts +157 -0
- package/src/runtime/model-router.ts +153 -0
- package/src/runtime/outcome.ts +467 -0
- package/src/runtime/reach.ts +148 -0
- package/src/runtime/readiness-record.ts +150 -0
- package/src/runtime/readiness.ts +435 -0
- package/src/runtime/recovery.ts +282 -0
- package/src/runtime/research-request.ts +231 -0
- package/src/runtime/retrieval-plan.ts +578 -0
- package/src/runtime/scaffold/artifacts.ts +217 -0
- package/src/runtime/scaffold/fingerprint.ts +56 -0
- package/src/runtime/scaffold/generate.ts +911 -0
- package/src/runtime/scaffold/languages.ts +245 -0
- package/src/runtime/storage/cli.ts +64 -0
- package/src/runtime/storage/engine.ts +249 -0
- package/src/runtime/storage/registry.ts +130 -0
- package/src/runtime/storage/roots.ts +63 -0
- package/src/runtime/storage/rules.ts +282 -0
- package/src/runtime/storage/upkeep.ts +243 -0
- package/src/runtime/storage/work-status.ts +326 -0
- package/src/runtime/task-attempts.ts +415 -0
- package/src/runtime/task-classifier.ts +550 -0
- package/src/runtime/task-scope.ts +130 -0
- package/src/runtime/task-state.ts +176 -0
- package/src/runtime/trace/indicators.ts +235 -0
- package/src/runtime/trace/ledger.ts +626 -0
- package/src/runtime/trace/render.ts +76 -0
- package/src/runtime/trace/views.ts +459 -0
- package/src/runtime/verification/contract-runner.ts +341 -0
- package/src/runtime/verification/engine.ts +1461 -0
- package/src/runtime/verification/registry.ts +130 -0
- package/src/runtime/verification/scope-bound.ts +418 -0
- package/src/runtime/verification/test-selection.ts +172 -0
- package/src/runtime/workflow-engine.ts +588 -0
- package/src/runtime/workflow-orchestrator.ts +953 -0
- package/src/runtime/worktree.ts +340 -0
- package/src/state/index.ts +193 -0
- package/src/state/lockfile.ts +136 -0
- package/src/state/run-lock.ts +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
const fs = require('node:fs');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { execFileSync } = require('node:child_process');
|
|
9
|
+
const { CapabilityRouter } = require('./capability-router.ts');
|
|
10
|
+
const { EvidenceLedger, VALID_EVIDENCE_KINDS, VALID_PROVENANCE } = require('./evidence-ledger.ts');
|
|
11
|
+
const { resolveLocator, describeResolution } = require('./locator-resolve.ts');
|
|
12
|
+
const { ClaimsManager } = require('./claims.ts');
|
|
13
|
+
const { measureFreshness } = require('./freshness.ts');
|
|
14
|
+
const { evaluateTaskReadiness } = require('./readiness.ts');
|
|
15
|
+
const { writeReadinessRecord } = require('./readiness-record.ts');
|
|
16
|
+
const { parseDeclaredScope } = require('./verification/scope-bound.ts');
|
|
17
|
+
const { loadRegistry } = require('../registry/index.ts');
|
|
18
|
+
const { REPO_ROOT } = require('../helper/repo-root.ts');
|
|
19
|
+
// One definition of the exit and usage helpers (design §4.2); `finish` is the local spelling of `finishRuntime`.
|
|
20
|
+
const { finishRuntime: finish, usageError } = require('./cli-result.ts');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handles `doflow capabilities` command execution.
|
|
24
|
+
* @param {Object} options
|
|
25
|
+
* @param {boolean} [options.json=false]
|
|
26
|
+
* @param {boolean} [options.check=false]
|
|
27
|
+
* @param {string} [options.repoRoot]
|
|
28
|
+
*/
|
|
29
|
+
function handleCapabilitiesCommand({ json = false, check = false, repoRoot }: any = {}) {
|
|
30
|
+
const root = repoRoot || REPO_ROOT;
|
|
31
|
+
const router = new CapabilityRouter({ repoRoot: root });
|
|
32
|
+
const report = router.getAllCapabilitiesHealth(check);
|
|
33
|
+
|
|
34
|
+
if (json) {
|
|
35
|
+
console.log(JSON.stringify({ capabilities: report }, null, 2));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log('\nDoFlow Abstract Capabilities & Resolved Providers:');
|
|
40
|
+
console.log('═'.repeat(78));
|
|
41
|
+
console.log(
|
|
42
|
+
'Capability'.padEnd(26) +
|
|
43
|
+
'Active Provider'.padEnd(22) +
|
|
44
|
+
'Status'.padEnd(14) +
|
|
45
|
+
'Description'
|
|
46
|
+
);
|
|
47
|
+
console.log('─'.repeat(78));
|
|
48
|
+
|
|
49
|
+
for (const item of report) {
|
|
50
|
+
const statusFormatted =
|
|
51
|
+
item.status === 'HEALTHY' ? '✓ HEALTHY' :
|
|
52
|
+
item.status === 'FALLBACK' ? '▲ FALLBACK' :
|
|
53
|
+
'✗ UNAVAIL';
|
|
54
|
+
|
|
55
|
+
console.log(
|
|
56
|
+
item.capability.padEnd(26) +
|
|
57
|
+
item.activeProvider.padEnd(22) +
|
|
58
|
+
statusFormatted.padEnd(14) +
|
|
59
|
+
item.description
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
console.log('═'.repeat(78));
|
|
63
|
+
console.log(`Mode: ${check ? 'Deep Smoke Check' : 'Fast Presence Check'} · Total Capabilities: ${report.length}\n`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Handles `doflow readiness` command execution.
|
|
68
|
+
*
|
|
69
|
+
* The four profile inputs below are the caller's own statements, not measurements, and they are
|
|
70
|
+
* passed through only when the caller actually made them. This verb previously asserted
|
|
71
|
+
* `verificationPlan: 'npm test'` and `scopeClear: true` unconditionally, which auto-satisfied the
|
|
72
|
+
* verification and scope requirement families for every task regardless of project state — the
|
|
73
|
+
* gate reported checkmarks it had not measured. Restoring them as *flags* keeps `READY` reachable
|
|
74
|
+
* without reinstating that defect: nothing here invents a value, and every value the caller did
|
|
75
|
+
* supply is named back in the output under `callerAsserted`, so a reader can tell which part of a
|
|
76
|
+
* verdict rests on evidence and which part rests on someone saying so.
|
|
77
|
+
*
|
|
78
|
+
* @param {Object} options
|
|
79
|
+
* @param {string} [options.taskClass='feature']
|
|
80
|
+
* @param {string} [options.taskId='default']
|
|
81
|
+
* @param {string} [options.verificationPlan] satisfies the verification requirement family
|
|
82
|
+
* @param {string} [options.scopeClear] satisfies the scope requirement family
|
|
83
|
+
* @param {string} [options.invariants] satisfies `invariants_captured`
|
|
84
|
+
* @param {boolean} [options.userDecisionPending=false] forces NEEDS_USER_DECISION
|
|
85
|
+
* @param {boolean} [options.json=false]
|
|
86
|
+
* @param {string} [options.repoRoot]
|
|
87
|
+
* @param {string} [options.stateRoot]
|
|
88
|
+
* @param {Date} [options.now] the evaluation clock the record is stamped with
|
|
89
|
+
* @param {string|null} [options.slug] `--slug`: the feature the record belongs to
|
|
90
|
+
* @returns {number} exit code: 0 once the evaluation is recorded, whatever its state
|
|
91
|
+
*/
|
|
92
|
+
function handleReadinessCommand({
|
|
93
|
+
taskClass = 'feature', taskId = 'default', json = false, repoRoot, stateRoot,
|
|
94
|
+
verificationPlan, scopeClear, invariants, userDecisionPending = false, mode = 'workflow', now, slug = null,
|
|
95
|
+
}: any = {}) {
|
|
96
|
+
// Two different roots, previously conflated into one. `root` locates the *registry* (the
|
|
97
|
+
// readiness templates ship inside the DoFlow package). `state` locates the invoking project's
|
|
98
|
+
// evidence, which belongs to the caller's repo — not to wherever DoFlow happens to be installed.
|
|
99
|
+
// Sharing one root put every project's per-task evidence inside the DoFlow install directory,
|
|
100
|
+
// which under an npm install is node_modules/ — shared across all projects and often read-only.
|
|
101
|
+
const root = repoRoot || REPO_ROOT;
|
|
102
|
+
const state = stateRoot || process.cwd();
|
|
103
|
+
// Only what the caller actually told us. An absent key must stay absent rather than become a
|
|
104
|
+
// falsy default, because the engine reads presence, not truth.
|
|
105
|
+
const profile: any = { taskId, taskClass };
|
|
106
|
+
if (typeof verificationPlan === 'string' && verificationPlan.trim() !== '') profile.verificationPlan = verificationPlan;
|
|
107
|
+
if (typeof scopeClear === 'string' && scopeClear.trim() !== '') profile.scopeClear = scopeClear;
|
|
108
|
+
if (typeof invariants === 'string' && invariants.trim() !== '') profile.invariants = invariants;
|
|
109
|
+
if (userDecisionPending) profile.userDecisionPending = true;
|
|
110
|
+
const callerAsserted = Object.keys(profile).filter((k) => k !== 'taskId' && k !== 'taskClass');
|
|
111
|
+
|
|
112
|
+
let report;
|
|
113
|
+
try {
|
|
114
|
+
report = evaluateTaskReadiness({ taskProfile: profile, repoRoot: root, projectRoot: state, mode });
|
|
115
|
+
} catch (error: any) {
|
|
116
|
+
return usageError('readiness', error.message, json, error);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The evaluation is recorded so the handoff, `verify` and the edit hook can later ask whether
|
|
120
|
+
// this task was READY before its work began. A scope that parses as a path list is also the
|
|
121
|
+
// declared scope `verify` bounds a change by; any other wording stays a statement only.
|
|
122
|
+
const inputs: any = {};
|
|
123
|
+
if (profile.verificationPlan) inputs.verificationPlan = profile.verificationPlan;
|
|
124
|
+
if (profile.scopeClear) inputs.scope = profile.scopeClear;
|
|
125
|
+
if (profile.invariants) inputs.invariants = profile.invariants;
|
|
126
|
+
if (profile.userDecisionPending) inputs.userDecisionPending = true;
|
|
127
|
+
const declared = inputs.scope ? parseDeclaredScope(inputs.scope) : null;
|
|
128
|
+
const declaredScope = declared && !declared.reason ? declared.paths : null;
|
|
129
|
+
let recorded: any;
|
|
130
|
+
try {
|
|
131
|
+
const { file, replacedUnreadable } = writeReadinessRecord({ stateRoot: state, taskId, slug, report, inputs, declaredScope, mode, now: now || new Date() });
|
|
132
|
+
recorded = { file, written: true, ...(replacedUnreadable ? { replacedUnreadable } : {}) };
|
|
133
|
+
} catch (error: any) {
|
|
134
|
+
recorded = { file: null, written: false, error: error.message };
|
|
135
|
+
}
|
|
136
|
+
const exitCode = recorded.written ? 0 : 1;
|
|
137
|
+
const reportNotRecorded = () => {
|
|
138
|
+
if (!recorded.written) console.error(`[ERROR] readiness: the evaluation was not recorded: ${recorded.error}`);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (json) {
|
|
142
|
+
console.log(JSON.stringify({ ...report, callerAsserted, record: recorded, declaredScope }, null, 2));
|
|
143
|
+
reportNotRecorded();
|
|
144
|
+
return finish(exitCode);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`\nDoFlow Task Readiness Evaluation [${report.taskClass.toUpperCase()}]:`);
|
|
148
|
+
console.log('═'.repeat(70));
|
|
149
|
+
console.log(`Task ID: ${report.taskId}`);
|
|
150
|
+
console.log(`Template: ${report.templateName}`);
|
|
151
|
+
console.log(`Overall State: ${report.state === 'READY' ? '✓ READY' : report.state === 'NEEDS_EVIDENCE' ? '▲ NEEDS EVIDENCE' : '✗ ' + report.state}`);
|
|
152
|
+
console.log(`Stage Entry: ${report.stageEntry.decision} (${report.executionMode} mode) — ${report.stageEntry.reason}`);
|
|
153
|
+
console.log(`Summary: ${report.summary}`);
|
|
154
|
+
if (recorded.written) console.log(`Recorded: ${path.relative(state, recorded.file)}`);
|
|
155
|
+
if (recorded.replacedUnreadable) {
|
|
156
|
+
console.log(`Replaced: the previous record could not be read; its bytes are kept in ${path.relative(state, recorded.replacedUnreadable)}`);
|
|
157
|
+
}
|
|
158
|
+
if (declared) {
|
|
159
|
+
console.log(declaredScope
|
|
160
|
+
? `Declared scope: ${declaredScope.join(', ')}`
|
|
161
|
+
: 'Scope: stated as text, not a path list; verify does not bound a change by it');
|
|
162
|
+
}
|
|
163
|
+
if (callerAsserted.length > 0) {
|
|
164
|
+
// Named, not hidden: these requirements were satisfied because the caller said so, and a
|
|
165
|
+
// verdict that mixes measured and stated inputs must say which is which.
|
|
166
|
+
console.log(`Caller-stated: ${callerAsserted.join(', ')} (asserted on the command line, not established by evidence)`);
|
|
167
|
+
}
|
|
168
|
+
console.log('─'.repeat(70));
|
|
169
|
+
console.log('Requirements Breakdown:');
|
|
170
|
+
|
|
171
|
+
for (const req of report.requirements || []) {
|
|
172
|
+
const mark = req.satisfied ? '✓ Satisfied' : req.required ? '✗ MISSING' : '○ Optional';
|
|
173
|
+
console.log(` ${req.id.padEnd(26)} ${mark.padEnd(14)} ${req.description}`);
|
|
174
|
+
if (!req.satisfied && req.recommendedAction) {
|
|
175
|
+
console.log(` └─ Recommended: [${req.recommendedAction.capability}] ${req.recommendedAction.action}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
console.log('═'.repeat(70) + '\n');
|
|
179
|
+
reportNotRecorded();
|
|
180
|
+
return finish(exitCode);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── the evidence write boundary (FR-007, plan task C.12) ──────────────────────────────────────
|
|
184
|
+
//
|
|
185
|
+
// `evidence` shipped as a read-only verb. `EvidenceLedger.addEvidence()`/`save()` existed and were
|
|
186
|
+
// tested, and nothing in the CLI called either — so every `evidenceKinds` requirement in every
|
|
187
|
+
// readiness template was unsatisfiable and the gate could only ever answer NEEDS_EVIDENCE. This
|
|
188
|
+
// section is the missing writer.
|
|
189
|
+
//
|
|
190
|
+
// It is almost entirely validation, deliberately. FR-007 makes two separations binding — model
|
|
191
|
+
// analysis stays distinguishable from repository fact, and retrieval relevance is never recorded
|
|
192
|
+
// as confidence — and a separation that is only documented is not enforced. Both are refused here,
|
|
193
|
+
// at the one point where an evidence record comes into existence.
|
|
194
|
+
//
|
|
195
|
+
// The refusals matter more than the writes. `addEvidence` builds an explicit record and ignores
|
|
196
|
+
// every key it does not recognise, so a field this boundary forwards blindly is a field the caller
|
|
197
|
+
// watches vanish while the command reports success. Nothing reaches the ledger unnamed.
|
|
198
|
+
|
|
199
|
+
/** Every field an evidence item may carry here. Anything else is refused, never dropped. */
|
|
200
|
+
const EVIDENCE_ITEM_FIELDS = ['kind', 'provenance', 'source', 'locator', 'content', 'taskId', 'establishes', 'observation'];
|
|
201
|
+
const EVIDENCE_SOURCE_FIELDS = ['provider', 'capability'];
|
|
202
|
+
const EVIDENCE_LOCATOR_FIELDS = ['file', 'line', 'symbol', 'uri'];
|
|
203
|
+
const EVIDENCE_OBSERVATION_FIELDS = ['command', 'exitCode'];
|
|
204
|
+
|
|
205
|
+
/** Kinds that record an execution and may carry a typed observation. */
|
|
206
|
+
const OBSERVATION_KINDS = new Set(['test-result', 'runtime-observation']);
|
|
207
|
+
|
|
208
|
+
/** Kinds that are by definition not a direct read of the repository. FR-007's second sentence. */
|
|
209
|
+
const NON_REPOSITORY_KINDS = new Set(['generated-analysis', 'user-statement']);
|
|
210
|
+
|
|
211
|
+
/** Fields a caller might plausibly send that this boundary refuses by name, with the reason.
|
|
212
|
+
* A named refusal beats the generic unknown-field error: each of these would otherwise be
|
|
213
|
+
* silently discarded by `addEvidence`, and a silent discard reads as a successful write. */
|
|
214
|
+
const EVIDENCE_REFUSED_FIELDS = new Map([
|
|
215
|
+
['id', 'evidence ids are minted by the ledger — a caller-chosen id can overwrite an unrelated record'],
|
|
216
|
+
['freshness', 'freshness is measured here from the working tree at write time, never asserted by the writer'],
|
|
217
|
+
['supports', 'link evidence to a claim with `claim --action link`, which updates both sides; a list written here would leave the claim unaware of it'],
|
|
218
|
+
['contradicts', 'link evidence to a claim with `claim --action link`, which updates both sides; a list written here would leave the claim unaware of it'],
|
|
219
|
+
['stage', 'the evidence record has no stage field — the batch *is* the stage boundary, so a stage name recorded here would be dropped'],
|
|
220
|
+
['status', "lifecycle status starts 'active' and changes only through `evidence --action supersede` — asserting it here would let a caller declare an item superseded without naming what replaces it"],
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
/** The names a retrieval score arrives under, in camelCase (`--relevance-score` normalises here).
|
|
224
|
+
* Exported because `bin/doflow.js` refuses the same names on the command line, and because
|
|
225
|
+
* `retrieval-plan.ts` and `outcome.ts` refuse them on the option object a declared plan and a
|
|
226
|
+
* terminal record are built from: one definition, three input surfaces (argv, a `--batch` JSON
|
|
227
|
+
* file, and a verb's own options), each with exactly one enforcement point. A second copy of
|
|
228
|
+
* this list is how one of them drifts. */
|
|
229
|
+
const EVIDENCE_SCORE_FIELDS = new Set([
|
|
230
|
+
'confidence', 'score', 'relevance', 'relevanceScore', 'similarity',
|
|
231
|
+
'certainty', 'probability', 'rank', 'distance', 'weight',
|
|
232
|
+
]);
|
|
233
|
+
|
|
234
|
+
/** The one refusal message for a score offered as a property of a fact. */
|
|
235
|
+
function scoreFieldRefusal(name: any) {
|
|
236
|
+
return `'${name}' is a retrieval score and this boundary records none (FR-007). Relevance is a `
|
|
237
|
+
+ 'property of a search, not of a fact: stored beside the fact it is read as confidence by the '
|
|
238
|
+
+ 'next worker, which is the specific interpretation the requirement forbids. Record the '
|
|
239
|
+
+ 'locator and the content instead — a reader who can see where a fact came from does not need '
|
|
240
|
+
+ 'to be told how sure to be.';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Refuses a score-shaped key anywhere in an item, including nested under source or locator. */
|
|
244
|
+
function assertNoScoreFields(value: any, prefix: any) {
|
|
245
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return;
|
|
246
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
247
|
+
const dotted = prefix ? `${prefix}.${key}` : key;
|
|
248
|
+
if (EVIDENCE_SCORE_FIELDS.has(key)) throw new Error(scoreFieldRefusal(dotted));
|
|
249
|
+
assertNoScoreFields(inner, dotted);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Normalises a locator given either as an object or as a `file[:line]` / URI string.
|
|
255
|
+
* @param {Object|string} raw
|
|
256
|
+
* @returns {Object}
|
|
257
|
+
*/
|
|
258
|
+
function parseLocator(raw: any) {
|
|
259
|
+
if (typeof raw === 'string') {
|
|
260
|
+
const text = raw.trim();
|
|
261
|
+
if (text === '') throw new Error('locator is empty');
|
|
262
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(text)) return { uri: text };
|
|
263
|
+
const withLine = text.match(/^(.+):(\d+)$/);
|
|
264
|
+
if (withLine) return { file: withLine[1], line: Number(withLine[2]) };
|
|
265
|
+
return { file: text };
|
|
266
|
+
}
|
|
267
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
268
|
+
throw new Error("locator must be an object, or a 'path/to/file[:line]' or URI string");
|
|
269
|
+
}
|
|
270
|
+
const locator: Record<string, any> = {};
|
|
271
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
272
|
+
if (!EVIDENCE_LOCATOR_FIELDS.includes(key)) {
|
|
273
|
+
throw new Error(`unknown locator field '${key}'. Accepted: ${EVIDENCE_LOCATOR_FIELDS.join(', ')}`);
|
|
274
|
+
}
|
|
275
|
+
locator[key] = value;
|
|
276
|
+
}
|
|
277
|
+
if (locator.line !== undefined && (!Number.isInteger(locator.line) || locator.line < 1)) {
|
|
278
|
+
throw new Error(`locator.line must be a positive integer, got '${locator.line}'`);
|
|
279
|
+
}
|
|
280
|
+
for (const key of ['file', 'symbol', 'uri']) {
|
|
281
|
+
if (locator[key] !== undefined && (typeof locator[key] !== 'string' || locator[key].trim() === '')) {
|
|
282
|
+
throw new Error(`locator.${key} must be a non-empty string`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!locator.file && !locator.uri) throw new Error('a locator needs at least a file or a uri');
|
|
286
|
+
return locator;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Validates one raw evidence item and returns the record fields the ledger will store.
|
|
291
|
+
*
|
|
292
|
+
* Throws on anything it cannot account for. Every rule here traces to FR-007 or to the
|
|
293
|
+
* vacuous-record family: a record with no statement and no locator asserts nothing, and storing it
|
|
294
|
+
* inflates the evidence count that the readiness gate then reads.
|
|
295
|
+
*
|
|
296
|
+
* @param {Object} raw
|
|
297
|
+
* @param {string} taskId the task the batch is being written to
|
|
298
|
+
* @param {string} [repoRoot] root a relative locator resolves against; defaults to cwd
|
|
299
|
+
* @returns {{kind:string, provenance:string, source:Object, locator:Object, content:(string|null)}}
|
|
300
|
+
*/
|
|
301
|
+
function validateEvidenceItem(raw: any, taskId: any, repoRoot: any) {
|
|
302
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('an evidence item must be a JSON object');
|
|
303
|
+
assertNoScoreFields(raw, '');
|
|
304
|
+
|
|
305
|
+
for (const key of Object.keys(raw)) {
|
|
306
|
+
if (EVIDENCE_REFUSED_FIELDS.has(key)) throw new Error(`'${key}' is not accepted here: ${EVIDENCE_REFUSED_FIELDS.get(key)}`);
|
|
307
|
+
if (!EVIDENCE_ITEM_FIELDS.includes(key)) {
|
|
308
|
+
throw new Error(`unknown evidence field '${key}'. Accepted: ${EVIDENCE_ITEM_FIELDS.join(', ')}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (raw.taskId !== undefined && raw.taskId !== taskId) {
|
|
313
|
+
throw new Error(`item names task '${raw.taskId}' but this write targets '${taskId}' — one write, one task`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const kinds = [...VALID_EVIDENCE_KINDS].sort().join(', ');
|
|
317
|
+
if (typeof raw.kind !== 'string' || raw.kind.trim() === '') throw new Error(`kind is required. Valid: ${kinds}`);
|
|
318
|
+
if (!VALID_EVIDENCE_KINDS.has(raw.kind)) throw new Error(`unknown evidence kind '${raw.kind}'. Valid: ${kinds}`);
|
|
319
|
+
|
|
320
|
+
const provenances = [...VALID_PROVENANCE].sort().join(', ');
|
|
321
|
+
if (typeof raw.provenance !== 'string' || raw.provenance.trim() === '') {
|
|
322
|
+
throw new Error('provenance is required and has no default here — defaulting it would file model '
|
|
323
|
+
+ `analysis as repository fact, the one distinction FR-007 makes binding. Valid: ${provenances}`);
|
|
324
|
+
}
|
|
325
|
+
if (!VALID_PROVENANCE.has(raw.provenance)) throw new Error(`unknown provenance '${raw.provenance}'. Valid: ${provenances}`);
|
|
326
|
+
if (NON_REPOSITORY_KINDS.has(raw.kind) && raw.provenance === 'extracted') {
|
|
327
|
+
throw new Error(`kind '${raw.kind}' cannot carry provenance 'extracted': it is not a read of the `
|
|
328
|
+
+ "repository, and labelling it as one is exactly how model analysis stops being distinguishable "
|
|
329
|
+
+ "from fact. Use 'inferred' (derived by analysis) or 'asserted' (stated by a person).");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (raw.source === undefined) {
|
|
333
|
+
throw new Error('source is required: which provider answered, under which capability. '
|
|
334
|
+
+ "It has no default — 'unknown/general' is a plausible-looking answer to a question nobody asked.");
|
|
335
|
+
}
|
|
336
|
+
if (!raw.source || typeof raw.source !== 'object' || Array.isArray(raw.source)) {
|
|
337
|
+
throw new Error('source must be an object with provider and capability');
|
|
338
|
+
}
|
|
339
|
+
for (const key of Object.keys(raw.source)) {
|
|
340
|
+
if (!EVIDENCE_SOURCE_FIELDS.includes(key)) {
|
|
341
|
+
throw new Error(`unknown source field '${key}'. Accepted: ${EVIDENCE_SOURCE_FIELDS.join(', ')}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const key of EVIDENCE_SOURCE_FIELDS) {
|
|
345
|
+
if (typeof raw.source[key] !== 'string' || raw.source[key].trim() === '') {
|
|
346
|
+
throw new Error(`source.${key} is required and must be a non-empty string`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const locator = raw.locator === undefined ? null : parseLocator(raw.locator);
|
|
351
|
+
// An item carrying an observation is anchored by the command it records — that command is what
|
|
352
|
+
// the next worker re-runs to check it — so the locator becomes optional there and only there.
|
|
353
|
+
if (raw.provenance === 'extracted' && !locator && raw.observation === undefined) {
|
|
354
|
+
throw new Error("provenance 'extracted' requires a locator — a fact read from the repository "
|
|
355
|
+
+ 'must name where it was read, or the next worker cannot check it and cannot tell when it goes stale');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// A locator that parses is not a locator that resolves (FR-004). Shape validation alone let an
|
|
359
|
+
// item naming line 799 of a 28-line file be recorded as support and counted by the readiness
|
|
360
|
+
// gate. The refusal names what the file actually offers, so the writer can correct it here rather
|
|
361
|
+
// than discover it at the gate.
|
|
362
|
+
if (raw.provenance === 'extracted' && locator) {
|
|
363
|
+
const resolution = resolveLocator({ locator, repoRoot });
|
|
364
|
+
if (!resolution.resolved) {
|
|
365
|
+
throw new Error(`the locator does not resolve: ${describeResolution(locator, resolution)}. `
|
|
366
|
+
+ "An 'extracted' item asserts a read of the repository, so a locator that points at nothing "
|
|
367
|
+
+ 'records a fact nobody can check');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (raw.content !== undefined && typeof raw.content !== 'string') throw new Error('content must be a string');
|
|
372
|
+
const content = typeof raw.content === 'string' && raw.content.trim() !== '' ? raw.content : null;
|
|
373
|
+
if (raw.provenance !== 'extracted' && !content) {
|
|
374
|
+
throw new Error(`provenance '${raw.provenance}' requires content — an inferred or asserted item `
|
|
375
|
+
+ 'is the statement itself, so a record without one records nothing while still counting as evidence');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Requirement bindings: which readiness-template requirements this item claims to establish.
|
|
379
|
+
// Declared by the writer, judged by the gate — the gate counts an item toward a requirement only
|
|
380
|
+
// when it is named here, so a structural item can no longer satisfy `architecture_mapped` and
|
|
381
|
+
// `blast_radius` at once by category alone (review R1).
|
|
382
|
+
let establishes = [];
|
|
383
|
+
if (raw.establishes !== undefined) {
|
|
384
|
+
const list = typeof raw.establishes === 'string' ? raw.establishes.split(',').map((s: any) => s.trim()) : raw.establishes;
|
|
385
|
+
if (!Array.isArray(list) || list.length === 0 || list.some((r) => typeof r !== 'string' || r.trim() === '')) {
|
|
386
|
+
throw new Error("'establishes' must be a requirement id, a comma-separated list of them, or a non-empty array of non-empty strings");
|
|
387
|
+
}
|
|
388
|
+
establishes = list.map((s) => s.trim());
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Typed observation: the execution behind a test-result or runtime-observation. Command and exit
|
|
392
|
+
// status are the two facts the gate can actually grade; tree identity and observation time are
|
|
393
|
+
// measured into freshness at this same write. Only an 'extracted' item may carry one — an
|
|
394
|
+
// observation asserts an execution happened, and an inferred item is by definition not one.
|
|
395
|
+
let observation = null;
|
|
396
|
+
if (raw.observation !== undefined) {
|
|
397
|
+
if (!raw.observation || typeof raw.observation !== 'object' || Array.isArray(raw.observation)) {
|
|
398
|
+
throw new Error('observation must be an object with command and exitCode');
|
|
399
|
+
}
|
|
400
|
+
for (const key of Object.keys(raw.observation)) {
|
|
401
|
+
if (!EVIDENCE_OBSERVATION_FIELDS.includes(key)) {
|
|
402
|
+
throw new Error(`unknown observation field '${key}'. Accepted: ${EVIDENCE_OBSERVATION_FIELDS.join(', ')}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (typeof raw.observation.command !== 'string' || raw.observation.command.trim() === '') {
|
|
406
|
+
throw new Error('observation.command is required: the exact command whose exit status is being recorded');
|
|
407
|
+
}
|
|
408
|
+
if (!Number.isInteger(raw.observation.exitCode) || raw.observation.exitCode < 0) {
|
|
409
|
+
throw new Error(`observation.exitCode must be a non-negative integer, got '${raw.observation.exitCode}'`);
|
|
410
|
+
}
|
|
411
|
+
if (!OBSERVATION_KINDS.has(raw.kind)) {
|
|
412
|
+
throw new Error(`kind '${raw.kind}' cannot carry an observation — only ${[...OBSERVATION_KINDS].join(', ')} record an execution`);
|
|
413
|
+
}
|
|
414
|
+
if (raw.provenance !== 'extracted') {
|
|
415
|
+
throw new Error(`provenance '${raw.provenance}' cannot carry an observation: an observation asserts a command `
|
|
416
|
+
+ 'was actually run, which is a read of the world, not an inference or an assertion. '
|
|
417
|
+
+ "Either record the execution as 'extracted', or drop the observation.");
|
|
418
|
+
}
|
|
419
|
+
observation = { command: raw.observation.command.trim(), exitCode: raw.observation.exitCode };
|
|
420
|
+
}
|
|
421
|
+
if (raw.kind === 'test-result' && raw.provenance === 'extracted' && !observation) {
|
|
422
|
+
throw new Error("an 'extracted' test-result requires an observation ({command, exitCode}) — a test result "
|
|
423
|
+
+ 'without the command and exit status behind it is a claim about an execution nobody can check, '
|
|
424
|
+
+ 'and it is exactly how a failed baseline was once accepted as a passing one');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
kind: raw.kind,
|
|
429
|
+
provenance: raw.provenance,
|
|
430
|
+
source: { provider: raw.source.provider, capability: raw.source.capability },
|
|
431
|
+
locator: locator || {},
|
|
432
|
+
content,
|
|
433
|
+
establishes,
|
|
434
|
+
observation,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** HEAD of the tree the evidence was observed in, or null outside a repo. Measured, not asserted. */
|
|
439
|
+
function headCommit(root: any) {
|
|
440
|
+
try {
|
|
441
|
+
return execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], {
|
|
442
|
+
encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
443
|
+
}).trim() || null;
|
|
444
|
+
} catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Reads a stage's evidence batch: a JSON array, or `{ "evidence": [...] }`.
|
|
452
|
+
* @param {string} batchPath a file path, or `-` for stdin
|
|
453
|
+
* @returns {Array<Object>}
|
|
454
|
+
*/
|
|
455
|
+
function readEvidenceBatch(batchPath: any) {
|
|
456
|
+
let text;
|
|
457
|
+
try {
|
|
458
|
+
text = batchPath === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(path.resolve(batchPath), 'utf8');
|
|
459
|
+
} catch (error: any) {
|
|
460
|
+
throw new Error(`cannot read the batch at '${batchPath}': ${error.message}`);
|
|
461
|
+
}
|
|
462
|
+
let data;
|
|
463
|
+
try {
|
|
464
|
+
data = JSON.parse(text);
|
|
465
|
+
} catch (error: any) {
|
|
466
|
+
throw new Error(`the batch at '${batchPath}' is not valid JSON: ${error.message}`);
|
|
467
|
+
}
|
|
468
|
+
if (Array.isArray(data)) return data;
|
|
469
|
+
if (data && typeof data === 'object') {
|
|
470
|
+
for (const key of Object.keys(data)) {
|
|
471
|
+
if (key === 'evidence') continue;
|
|
472
|
+
const named = EVIDENCE_REFUSED_FIELDS.has(key) ? ` — ${EVIDENCE_REFUSED_FIELDS.get(key)}` : '';
|
|
473
|
+
throw new Error(`unknown batch field '${key}'${named}. A batch is a JSON array of items, `
|
|
474
|
+
+ "or an object whose only key is 'evidence'");
|
|
475
|
+
}
|
|
476
|
+
if (!Array.isArray(data.evidence)) throw new Error("the batch field 'evidence' must be an array");
|
|
477
|
+
return data.evidence;
|
|
478
|
+
}
|
|
479
|
+
throw new Error('a batch must be a JSON array of evidence items, or an object with an `evidence` array');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* The `--action add` arm: validate the whole batch, then write it.
|
|
484
|
+
* @returns {number} exit code
|
|
485
|
+
*/
|
|
486
|
+
function addEvidence({ ledger, root, taskId, item, batchPath, json }: any) {
|
|
487
|
+
const flagged = item && Object.keys(item).length > 0;
|
|
488
|
+
if (flagged && batchPath) {
|
|
489
|
+
return usageError('evidence', '--batch and the single-item flags are two spellings of the same write; use one', json);
|
|
490
|
+
}
|
|
491
|
+
if (!flagged && !batchPath) {
|
|
492
|
+
return usageError('evidence', '--action add needs either --batch <file|-> or the single-item flags '
|
|
493
|
+
+ '(--kind, --provenance, --provider, --capability, --locator, --content)', json);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
let raws;
|
|
497
|
+
try {
|
|
498
|
+
raws = batchPath ? readEvidenceBatch(batchPath) : [item];
|
|
499
|
+
} catch (error: any) {
|
|
500
|
+
return usageError('evidence', error.message, json, error);
|
|
501
|
+
}
|
|
502
|
+
if (raws.length === 0) {
|
|
503
|
+
return usageError('evidence', 'the batch holds no evidence items — a stage that established nothing '
|
|
504
|
+
+ 'should record nothing, and an empty write reported as success is the vacuous-PASS defect in another costume', json);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// The whole batch is validated before any of it is written. A half-written stage batch is worse
|
|
508
|
+
// than a rejected one: the half that landed looks complete to every later reader.
|
|
509
|
+
const validated = [];
|
|
510
|
+
for (const [index, raw] of raws.entries()) {
|
|
511
|
+
try {
|
|
512
|
+
validated.push(validateEvidenceItem(raw, taskId, root));
|
|
513
|
+
} catch (error: any) {
|
|
514
|
+
const where = raws.length > 1 ? `batch item ${index + 1} of ${raws.length}: ` : '';
|
|
515
|
+
return usageError('evidence', `${where}${error.message}`, json, error);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const gitCommit = headCommit(root);
|
|
520
|
+
const written = [];
|
|
521
|
+
let stateFile;
|
|
522
|
+
try {
|
|
523
|
+
for (const record of validated) {
|
|
524
|
+
const id = ledger.addEvidence({
|
|
525
|
+
...record,
|
|
526
|
+
taskId,
|
|
527
|
+
freshness: measureFreshness(root, record.locator, gitCommit),
|
|
528
|
+
});
|
|
529
|
+
written.push(ledger.getEvidence(id));
|
|
530
|
+
}
|
|
531
|
+
stateFile = ledger.save(taskId);
|
|
532
|
+
} catch (error: any) {
|
|
533
|
+
return usageError('evidence', error.message, json, error);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (json) {
|
|
537
|
+
console.log(JSON.stringify({ action: 'add', taskId, written: written.length, stateFile, evidence: written }, null, 2));
|
|
538
|
+
return finish(0);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
console.log(`\nDoFlow Evidence Recorded [Task: ${taskId}]:`);
|
|
542
|
+
console.log('═'.repeat(78));
|
|
543
|
+
console.log('ID'.padEnd(16) + 'Kind'.padEnd(20) + 'Provenance'.padEnd(12) + 'Locator');
|
|
544
|
+
console.log('─'.repeat(78));
|
|
545
|
+
for (const record of written) {
|
|
546
|
+
console.log(
|
|
547
|
+
record.id.padEnd(16) +
|
|
548
|
+
record.kind.padEnd(20) +
|
|
549
|
+
record.provenance.padEnd(12) +
|
|
550
|
+
(locatorText(record.locator) || 'none')
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
console.log('─'.repeat(78));
|
|
554
|
+
console.log(`Wrote ${written.length} item(s) to ${stateFile}`);
|
|
555
|
+
console.log(`Commit at observation: ${gitCommit || 'not a git worktree — freshness cannot expire by diff'}`);
|
|
556
|
+
console.log('═'.repeat(78) + '\n');
|
|
557
|
+
return finish(0);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** One-line rendering of a locator for the table views. */
|
|
561
|
+
function locatorText(locator: any) {
|
|
562
|
+
if (!locator) return '';
|
|
563
|
+
if (locator.file) return locator.line ? `${locator.file}:${locator.line}` : locator.file;
|
|
564
|
+
return locator.uri || locator.symbol || '';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* The `--action supersede` arm: mark one already-recorded item superseded by another. Mirrors
|
|
569
|
+
* `handleClaimCommand`'s `supersede` branch — validate, write, save, report the same shape.
|
|
570
|
+
* @returns {number} exit code
|
|
571
|
+
*/
|
|
572
|
+
function supersedeEvidence({ ledger, taskId, evidenceId, replacedBy, json }: any) {
|
|
573
|
+
if (!evidenceId || !replacedBy) {
|
|
574
|
+
return usageError('evidence', '--evidence-id and --replaced-by are both required for --action supersede', json);
|
|
575
|
+
}
|
|
576
|
+
let status;
|
|
577
|
+
try {
|
|
578
|
+
status = ledger.supersedeEvidence(evidenceId, replacedBy);
|
|
579
|
+
} catch (error: any) {
|
|
580
|
+
return usageError('evidence', error.message, json, error);
|
|
581
|
+
}
|
|
582
|
+
const stateFile = ledger.save(taskId);
|
|
583
|
+
const record = ledger.getEvidence(evidenceId);
|
|
584
|
+
|
|
585
|
+
if (json) {
|
|
586
|
+
console.log(JSON.stringify({ action: 'supersede', taskId, evidence: record, status, stateFile }, null, 2));
|
|
587
|
+
return finish(0);
|
|
588
|
+
}
|
|
589
|
+
console.log(`\nDoFlow Evidence [Task: ${taskId}]:`);
|
|
590
|
+
console.log('═'.repeat(78));
|
|
591
|
+
console.log(`${record.id} ${status} → ${record.supersededBy}`);
|
|
592
|
+
console.log(`Wrote to ${stateFile}`);
|
|
593
|
+
console.log('═'.repeat(78) + '\n');
|
|
594
|
+
return finish(0);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Handles `doflow evidence` — record a stage's evidence batch, retire a stale item, or query
|
|
599
|
+
* what is recorded.
|
|
600
|
+
*
|
|
601
|
+
* @param {Object} options
|
|
602
|
+
* @param {string} [options.taskId='default']
|
|
603
|
+
* @param {'list'|'status'|'add'|'supersede'} [options.action='list']
|
|
604
|
+
* @param {Object} [options.item] single-item write, assembled from flags by bin/doflow.js
|
|
605
|
+
* @param {string} [options.batchPath] `add`: a JSON batch file, or `-` for stdin
|
|
606
|
+
* @param {string} [options.evidenceId] `supersede`: the item to retire
|
|
607
|
+
* @param {string} [options.replacedBy] `supersede`: the item that replaces it
|
|
608
|
+
* @param {boolean} [options.json=false]
|
|
609
|
+
* @param {string} [options.repoRoot]
|
|
610
|
+
* @param {string} [options.stateRoot]
|
|
611
|
+
* @returns {number} exit code
|
|
612
|
+
*/
|
|
613
|
+
function handleEvidenceCommand({ taskId = 'default', action = 'list', item = null, batchPath = null, evidenceId = null, replacedBy = null, json = false, repoRoot, stateRoot }: any = {}) {
|
|
614
|
+
// See handleReadinessCommand: evidence is the caller's project state, not DoFlow package state.
|
|
615
|
+
const root = stateRoot || repoRoot || process.cwd();
|
|
616
|
+
const isQuery = action === 'list' || action === 'status';
|
|
617
|
+
if (!isQuery && action !== 'add' && action !== 'supersede') {
|
|
618
|
+
return usageError('evidence', `unknown --action '${action}'. Valid: list, add, supersede`, json);
|
|
619
|
+
}
|
|
620
|
+
// A write argument on a read action is the defect this verb was reported for: it used to accept
|
|
621
|
+
// append-shaped flags, ignore them, and print the (unchanged) ledger as if the write had landed.
|
|
622
|
+
if (isQuery && ((item && Object.keys(item).length > 0) || batchPath)) {
|
|
623
|
+
return usageError('evidence', 'these are write arguments and this action only reads — re-run with --action add', json);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const ledger = new EvidenceLedger({ repoRoot: root });
|
|
627
|
+
try {
|
|
628
|
+
ledger.load(taskId);
|
|
629
|
+
} catch (error: any) {
|
|
630
|
+
return usageError('evidence', error.message, json, error);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (action === 'add') return addEvidence({ ledger, root, taskId, item, batchPath, json });
|
|
634
|
+
if (action === 'supersede') return supersedeEvidence({ ledger, taskId, evidenceId, replacedBy, json });
|
|
635
|
+
|
|
636
|
+
const items = ledger.queryEvidence({ taskId });
|
|
637
|
+
|
|
638
|
+
if (json) {
|
|
639
|
+
console.log(JSON.stringify({ taskId, evidenceCount: items.length, evidence: items }, null, 2));
|
|
640
|
+
return finish(0);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
console.log(`\nDoFlow Evidence Ledger [Task: ${taskId}]:`);
|
|
644
|
+
console.log('═'.repeat(78));
|
|
645
|
+
if (items.length === 0) {
|
|
646
|
+
console.log('No evidence items recorded for this task.');
|
|
647
|
+
} else {
|
|
648
|
+
console.log('ID'.padEnd(16) + 'Kind'.padEnd(20) + 'Status'.padEnd(12) + 'File Locator');
|
|
649
|
+
console.log('─'.repeat(78));
|
|
650
|
+
for (const entry of items) {
|
|
651
|
+
const loc = entry.locator?.file ? `${entry.locator.file}` : 'None';
|
|
652
|
+
console.log(
|
|
653
|
+
entry.id.padEnd(16) +
|
|
654
|
+
entry.kind.padEnd(20) +
|
|
655
|
+
entry.freshness.status.padEnd(12) +
|
|
656
|
+
loc
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
console.log('═'.repeat(78) + '\n');
|
|
661
|
+
return finish(0);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
module.exports = {
|
|
665
|
+
handleCapabilitiesCommand,
|
|
666
|
+
handleReadinessCommand,
|
|
667
|
+
handleEvidenceCommand,
|
|
668
|
+
// Exported for bin/doflow.js, which refuses the same names as command-line flags, and for the
|
|
669
|
+
// tests that pin the write boundary.
|
|
670
|
+
EVIDENCE_SCORE_FIELDS,
|
|
671
|
+
scoreFieldRefusal,
|
|
672
|
+
// The recursive refusal itself, not just the name set — `retrieval-plan.ts` applies it to the
|
|
673
|
+
// option object a plan is built from and `outcome.ts` to the one a terminal record is, so every
|
|
674
|
+
// boundary rejects the same shapes by the same rule rather than by implementations that agree
|
|
675
|
+
// today.
|
|
676
|
+
assertNoScoreFields,
|
|
677
|
+
validateEvidenceItem,
|
|
678
|
+
};
|