@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,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { updateTaskState } = require('./task-state.ts');
|
|
8
|
+
const { resolveTaskScope } = require('./task-scope.ts');
|
|
9
|
+
const { findStateFile } = require('./checkouts.ts');
|
|
10
|
+
|
|
11
|
+
// The readiness record: the latest evaluation of one task, kept where `readiness` ran.
|
|
12
|
+
//
|
|
13
|
+
// An evaluation used to vanish once printed, so a later step could only evaluate again with
|
|
14
|
+
// whatever inputs it was handed at that moment. Recording it lets the handoff, `verify` and the
|
|
15
|
+
// edit hook ask one question — was this task READY before the work started — and read the answer
|
|
16
|
+
// from any checkout of the repository.
|
|
17
|
+
|
|
18
|
+
const RECORD_VERSION = 1;
|
|
19
|
+
const STORE = path.join('.doflow', 'state', 'readiness');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Where a task's record may be under one checkout, in the order every reader takes them: under the
|
|
23
|
+
* feature's namespace, then flat. The writer, `verify`, `orchestrate` and the edit hook all use this
|
|
24
|
+
* one list, so a record written anywhere is found by each of them from any checkout.
|
|
25
|
+
* @param {string} taskId
|
|
26
|
+
* @param {string|null} slug the task's feature slug, when one is known
|
|
27
|
+
* @returns {Array<string>} paths relative to a checkout root
|
|
28
|
+
*/
|
|
29
|
+
function recordCandidates(taskId: string, slug: any) {
|
|
30
|
+
const flat = path.join(STORE, `${taskId}.json`);
|
|
31
|
+
return slug && slug !== taskId ? [path.join(STORE, slug, `${taskId}.json`), flat] : [flat];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A record version this runtime cannot read because a newer DoFlow wrote it, else null. */
|
|
35
|
+
function newerVersionOf(record: any) {
|
|
36
|
+
const v = record && typeof record === 'object' ? record.version : undefined;
|
|
37
|
+
return Number.isInteger(v) && v > RECORD_VERSION ? v : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The refusal for a record a newer DoFlow wrote: it names the file and is never overwritten. */
|
|
41
|
+
function newerRecordText(file: any, version: any) {
|
|
42
|
+
return `the readiness record ${file} was written by a newer DoFlow (record version ${version}; this runtime reads ${RECORD_VERSION}), `
|
|
43
|
+
+ 'so it was left as it is. Run this command with the DoFlow that wrote the record, or upgrade DoFlow here.';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* What is already at `file` before a write: nothing, a record to replace, a record a newer DoFlow
|
|
48
|
+
* wrote, or bytes no evaluation can be read from.
|
|
49
|
+
* @returns {{kind: 'none'|'record'|'newer'|'unreadable', version?: number}}
|
|
50
|
+
*/
|
|
51
|
+
function priorRecord(file: any, fsImpl: any) {
|
|
52
|
+
let text;
|
|
53
|
+
try {
|
|
54
|
+
text = fsImpl.readFileSync(file, 'utf8');
|
|
55
|
+
} catch (error: any) {
|
|
56
|
+
if (error.code === 'ENOENT') return { kind: 'none' };
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
let data;
|
|
60
|
+
try {
|
|
61
|
+
data = JSON.parse(text);
|
|
62
|
+
} catch {
|
|
63
|
+
return { kind: 'unreadable' };
|
|
64
|
+
}
|
|
65
|
+
const newer = newerVersionOf(data);
|
|
66
|
+
if (newer !== null) return { kind: 'newer', version: newer };
|
|
67
|
+
// The state-file reader refuses any other version it did not write, so such bytes are set aside too.
|
|
68
|
+
if (data && typeof data === 'object' && data.version !== undefined && data.version !== RECORD_VERSION) return { kind: 'unreadable' };
|
|
69
|
+
return { kind: 'record' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Records one evaluation, replacing the previous one for the task. A previous file that cannot be
|
|
74
|
+
* read is moved to `<file>.unreadable` (replacing an earlier one there) so the refusal that sent the
|
|
75
|
+
* caller here can be cleared; one a newer DoFlow wrote is left alone and the write is refused.
|
|
76
|
+
* @param {Object} options
|
|
77
|
+
* @param {string} options.stateRoot
|
|
78
|
+
* @param {string} options.taskId
|
|
79
|
+
* @param {string|null} [options.slug]
|
|
80
|
+
* @param {Object} options.report an `evaluateTaskReadiness` result
|
|
81
|
+
* @param {Object} options.inputs only the caller-stated keys that were given
|
|
82
|
+
* @param {Array<string>|null} [options.declaredScope] the scope's paths when it parses as a path list
|
|
83
|
+
* @param {string} options.mode
|
|
84
|
+
* @param {Date} [options.now]
|
|
85
|
+
* @param {Object} [options.fsImpl]
|
|
86
|
+
* @returns {{file: string, record: Object, replacedUnreadable: string|null}} the `.unreadable` path when
|
|
87
|
+
* a previous file was set aside
|
|
88
|
+
*/
|
|
89
|
+
function writeReadinessRecord({ stateRoot, taskId, slug = null, report, inputs, declaredScope = null, mode, now = new Date(), fsImpl = fs }: any) {
|
|
90
|
+
const scope = resolveTaskScope({ projectRoot: stateRoot, taskId, slug });
|
|
91
|
+
const recordSlug = scope.slug || slug || null;
|
|
92
|
+
// The record a reader would find in this checkout is the one replaced; with none, the record goes
|
|
93
|
+
// where the task's evidence goes (under the feature's namespace when it has a register).
|
|
94
|
+
const file = recordCandidates(taskId, recordSlug).map((rel) => path.join(stateRoot, rel)).find((f) => fsImpl.existsSync(f))
|
|
95
|
+
|| path.join(stateRoot, STORE, ...(scope.namespace ? [scope.namespace] : []), `${taskId}.json`);
|
|
96
|
+
const record = {
|
|
97
|
+
version: RECORD_VERSION,
|
|
98
|
+
taskId,
|
|
99
|
+
slug: recordSlug,
|
|
100
|
+
taskClass: report.taskClass,
|
|
101
|
+
templateName: report.templateName,
|
|
102
|
+
state: report.state,
|
|
103
|
+
stageEntry: report.stageEntry ? report.stageEntry.decision : null,
|
|
104
|
+
executionMode: mode,
|
|
105
|
+
inputs: { ...inputs },
|
|
106
|
+
declaredScope: Array.isArray(declaredScope) ? [...declaredScope] : null,
|
|
107
|
+
unmet: (report.requirements || []).filter((r: any) => r.required && !r.satisfied).map((r: any) => r.id),
|
|
108
|
+
evidenceCount: Number.isInteger(report.evidenceCount) ? report.evidenceCount : 0,
|
|
109
|
+
evaluatedAt: now.toISOString(),
|
|
110
|
+
};
|
|
111
|
+
// Inspected and set aside under the write's own lock, so a concurrent writer can neither rename
|
|
112
|
+
// a record just written nor slip a newer one in between the check and the write.
|
|
113
|
+
let replacedUnreadable = null;
|
|
114
|
+
const prepare = () => {
|
|
115
|
+
const prior = priorRecord(file, fsImpl);
|
|
116
|
+
if (prior.kind === 'newer') throw new Error(newerRecordText(file, prior.version));
|
|
117
|
+
if (prior.kind === 'unreadable') {
|
|
118
|
+
replacedUnreadable = `${file}.unreadable`;
|
|
119
|
+
fsImpl.renameSync(file, replacedUnreadable);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
updateTaskState({ fsImpl, file, prepare, build: () => record });
|
|
123
|
+
return { file, record, replacedUnreadable };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The task's record in this checkout, else in exactly one other checkout, at the first of
|
|
128
|
+
* `recordCandidates` that exists in each. No resolver is run: the candidates depend only on the
|
|
129
|
+
* task id and the slug the caller names.
|
|
130
|
+
* @param {Object} options
|
|
131
|
+
* @param {string} options.stateRoot
|
|
132
|
+
* @param {string} options.taskId
|
|
133
|
+
* @param {string|null} [options.slug]
|
|
134
|
+
* @param {Function} [options.exec]
|
|
135
|
+
* @param {Object} [options.fsImpl]
|
|
136
|
+
* @returns {{status: 'found'|'missing'|'ambiguous'|'unreadable', record: Object|null, file: string|null,
|
|
137
|
+
* origin: 'current'|'other'|null, candidates: Array<string>, detail: string|null}}
|
|
138
|
+
*/
|
|
139
|
+
function readReadinessRecord({ stateRoot, taskId, slug = null, exec, fsImpl = fs }: any) {
|
|
140
|
+
const found = findStateFile({ stateRoot, relPath: recordCandidates(taskId, slug), ...(exec ? { exec } : {}), fsImpl });
|
|
141
|
+
const base = { record: null, file: found.file, origin: found.origin, candidates: found.candidates, detail: null };
|
|
142
|
+
if (found.status !== 'found') return { ...base, status: found.status };
|
|
143
|
+
try {
|
|
144
|
+
return { ...base, status: 'found', record: JSON.parse(fsImpl.readFileSync(found.file, 'utf8')) };
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
return { ...base, status: 'unreadable', detail: `unparsable JSON: ${error.message}` };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { writeReadinessRecord, readReadinessRecord, recordCandidates, newerVersionOf, RECORD_VERSION };
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { parseYamlFile } = require('./capability-router.ts');
|
|
8
|
+
const { resolveLocator, describeResolution } = require('./locator-resolve.ts');
|
|
9
|
+
const { REPO_ROOT } = require('../helper/repo-root.ts');
|
|
10
|
+
|
|
11
|
+
const READINESS_STATES = new Set([
|
|
12
|
+
'READY',
|
|
13
|
+
'NEEDS_EVIDENCE',
|
|
14
|
+
'NEEDS_USER_DECISION',
|
|
15
|
+
'BLOCKED',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
// ── The one stage-entry policy ───────────────────────────────────────────────────────────────────
|
|
19
|
+
//
|
|
20
|
+
// Whether a readiness state permits editing used to live in prose, three times, disagreeing:
|
|
21
|
+
// do-implement allowed edits under NEEDS_EVIDENCE and NEEDS_USER_DECISION, do-flow stopped on
|
|
22
|
+
// anything but READY, and readiness_gate.md said gather-or-ask first — so entering the same work
|
|
23
|
+
// through different skills changed whether unresolved prerequisites blocked editing (review R3,
|
|
24
|
+
// P1). The policy now lives here, once; skills consume the decision instead of re-deriving it.
|
|
25
|
+
//
|
|
26
|
+
// The standalone exemption is an explicit execution mode, not an inference from a missing evidence
|
|
27
|
+
// record: a caller doing a deliberate one-off fix passes `--mode standalone`, and only the
|
|
28
|
+
// NEEDS_EVIDENCE row softens for it — an owed user decision or contradicting evidence stops edits
|
|
29
|
+
// in every mode, because neither is cured by the work being small.
|
|
30
|
+
|
|
31
|
+
const EXECUTION_MODES = new Set(['workflow', 'standalone']);
|
|
32
|
+
const STAGE_ENTRY_DECISIONS = new Set(['ENTER', 'GATHER_FIRST', 'ASK_USER', 'STOP']);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Maps a computed readiness state and a declared execution mode to the one stage-entry decision.
|
|
36
|
+
* @param {string} state one of READINESS_STATES
|
|
37
|
+
* @param {string} [mode='workflow'] 'workflow' | 'standalone'
|
|
38
|
+
* @returns {{decision: string, reason: string}}
|
|
39
|
+
*/
|
|
40
|
+
function stageEntryFor(state: any, mode = 'workflow') {
|
|
41
|
+
if (!EXECUTION_MODES.has(mode)) {
|
|
42
|
+
throw new Error(`Unknown execution mode '${mode}'. Valid: ${[...EXECUTION_MODES].join(', ')} — the standalone exemption is declared, never inferred.`);
|
|
43
|
+
}
|
|
44
|
+
if (!READINESS_STATES.has(state)) {
|
|
45
|
+
throw new Error(`Unknown readiness state '${state}' has no stage-entry decision.`);
|
|
46
|
+
}
|
|
47
|
+
switch (state) {
|
|
48
|
+
case 'BLOCKED':
|
|
49
|
+
return { decision: 'STOP', reason: 'A claim on this task is conflicted — evidence disagrees with itself. No mode edits through that; resolve the contradiction first.' };
|
|
50
|
+
case 'NEEDS_USER_DECISION':
|
|
51
|
+
return { decision: 'ASK_USER', reason: 'A decision is owed by the user. Ask it and wait; editing first would decide it silently on their behalf.' };
|
|
52
|
+
case 'NEEDS_EVIDENCE':
|
|
53
|
+
return mode === 'standalone'
|
|
54
|
+
? { decision: 'ENTER', reason: 'Standalone mode: the unmet contract is reported, not enforced — a declared one-off edit outside a workflow run. Relay the missing requirements in the result.' }
|
|
55
|
+
: { decision: 'GATHER_FIRST', reason: 'Workflow mode: gather the missing requirements before entering the stage; do not start editing on the assumption it will work out.' };
|
|
56
|
+
case 'READY':
|
|
57
|
+
default:
|
|
58
|
+
return { decision: 'ENTER', reason: 'Every mandatory prerequisite is verified by fresh evidence.' };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class ReadinessEngine {
|
|
63
|
+
declare fsImpl: any;
|
|
64
|
+
declare repoRoot: any;
|
|
65
|
+
declare projectRoot: any;
|
|
66
|
+
declare templatePath: any;
|
|
67
|
+
declare templates: any;
|
|
68
|
+
/**
|
|
69
|
+
* @param {Object} [options]
|
|
70
|
+
* @param {string} [options.repoRoot] the DoFlow install, where readiness-templates.json lives
|
|
71
|
+
* @param {string} [options.projectRoot] the repository under work, which evidence locators name.
|
|
72
|
+
* Distinct from repoRoot on purpose: when the CLI runs inside another project, the templates
|
|
73
|
+
* come from the install and the locators must be resolved against that project, not the install.
|
|
74
|
+
* @param {string} [options.templatePath]
|
|
75
|
+
* @param {Object} [options.templates]
|
|
76
|
+
* @param {Object} [options.fsImpl]
|
|
77
|
+
*/
|
|
78
|
+
constructor(options: any = {}) {
|
|
79
|
+
this.fsImpl = options.fsImpl || fs;
|
|
80
|
+
this.repoRoot = options.repoRoot || REPO_ROOT;
|
|
81
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
82
|
+
this.templatePath = options.templatePath || path.join(this.repoRoot, 'core', 'registry', 'readiness-templates.json');
|
|
83
|
+
|
|
84
|
+
if (options.templates) {
|
|
85
|
+
this.templates = options.templates;
|
|
86
|
+
} else {
|
|
87
|
+
this.loadTemplates();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
loadTemplates() {
|
|
92
|
+
try {
|
|
93
|
+
const data = parseYamlFile(this.templatePath, this.fsImpl);
|
|
94
|
+
this.templates = data.templates || {};
|
|
95
|
+
} catch (error: any) {
|
|
96
|
+
throw new Error(`Failed to load readiness templates from '${this.templatePath}': ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Suggests retrieval actions when an evidence requirement is missing.
|
|
102
|
+
* @param {string} reqId
|
|
103
|
+
* @param {Object} reqDef
|
|
104
|
+
* @param {Object} taskProfile
|
|
105
|
+
* @returns {Object}
|
|
106
|
+
*/
|
|
107
|
+
suggestAction(reqId: any, reqDef: any, taskProfile: any) {
|
|
108
|
+
if (reqId === 'root_cause') {
|
|
109
|
+
return {
|
|
110
|
+
intent: 'trace-dependency',
|
|
111
|
+
capability: 'code.relationships',
|
|
112
|
+
action: 'Query structural call graphs or inspect git history to establish root cause evidence for the bug.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (reqId === 'affected_code' || reqId === 'target_identified') {
|
|
116
|
+
return {
|
|
117
|
+
intent: 'locate-known-symbol',
|
|
118
|
+
capability: 'code.exact-search',
|
|
119
|
+
action: 'Perform exact symbol or file search to record target code locators.',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (reqId === 'blast_radius' || reqId === 'architecture_mapped') {
|
|
123
|
+
return {
|
|
124
|
+
intent: 'estimate-blast-radius',
|
|
125
|
+
capability: 'code.impact-analysis',
|
|
126
|
+
action: 'Query caller trees and dependent modules using Graphify or exact search.',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (reqId === 'reproduction' || reqId === 'baseline_tests') {
|
|
130
|
+
return {
|
|
131
|
+
intent: 'verify-runtime-behavior',
|
|
132
|
+
capability: 'behavior.verify',
|
|
133
|
+
action: 'Execute automated tests or record runtime observation reproducing the behavior.',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
intent: 'locate-concept',
|
|
138
|
+
capability: 'code.semantic-search',
|
|
139
|
+
action: `Gather evidence for requirement '${reqId}' (${reqDef.description}).`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Evaluates task readiness against a declarative template.
|
|
145
|
+
* @param {Object} taskProfile
|
|
146
|
+
* @param {string} taskProfile.taskId
|
|
147
|
+
* @param {'bug'|'feature'|'refactor'|'trivial-edit'|'dependency-change'} [taskProfile.taskClass='feature']
|
|
148
|
+
* @param {string} [taskProfile.description]
|
|
149
|
+
* @param {string} [taskProfile.verificationPlan]
|
|
150
|
+
* @param {boolean} [taskProfile.userDecisionPending=false]
|
|
151
|
+
* @param {Object} evidenceLedger
|
|
152
|
+
* @param {Object} claimsManager
|
|
153
|
+
* @returns {Object} ReadinessReport
|
|
154
|
+
*/
|
|
155
|
+
evaluateReadiness(taskProfile: any, evidenceLedger: any, claimsManager: any) {
|
|
156
|
+
if (!taskProfile || typeof taskProfile !== 'object') {
|
|
157
|
+
throw new Error('taskProfile must be an object');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Fail closed on identity, for the same reason the unrecognized-requirement branch below does.
|
|
161
|
+
// These previously defaulted to `'default'` and `'feature'`, which meant a caller passing `id`
|
|
162
|
+
// instead of `taskId` — an easy slip, and one that actually happened while writing
|
|
163
|
+
// test/runtime/runtime-claim-status.test.js — got a confident READY/NEEDS_EVIDENCE verdict computed
|
|
164
|
+
// from an unrelated task's evidence and claims. A gate must never answer about something it did
|
|
165
|
+
// not evaluate; reported independently by three separate reviews of this runtime before being
|
|
166
|
+
// fixed here.
|
|
167
|
+
if (typeof taskProfile.taskId !== 'string' || taskProfile.taskId.trim() === '') {
|
|
168
|
+
const nearMiss = ['id', 'task_id', 'taskID'].find((k) => taskProfile[k] !== undefined);
|
|
169
|
+
throw new Error(
|
|
170
|
+
`taskProfile.taskId is required to evaluate readiness${nearMiss ? ` (received '${nearMiss}' instead)` : ''}` +
|
|
171
|
+
' — readiness is evaluated against one task\'s evidence and claims, so guessing the id would grade the wrong task.',
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (typeof taskProfile.taskClass !== 'string' || taskProfile.taskClass.trim() === '') {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`taskProfile.taskClass is required to evaluate readiness. Available: ${Object.keys(this.templates).join(', ')}`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const taskId = taskProfile.taskId;
|
|
181
|
+
const taskClass = taskProfile.taskClass;
|
|
182
|
+
const template = this.templates[taskClass];
|
|
183
|
+
|
|
184
|
+
if (!template) {
|
|
185
|
+
throw new Error(`Unknown task class '${taskClass}'. Available: ${Object.keys(this.templates).join(', ')}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const requirements = template.requirements || {};
|
|
189
|
+
const requirementResults = [];
|
|
190
|
+
let allSatisfied = true;
|
|
191
|
+
let hasBlocked = false;
|
|
192
|
+
|
|
193
|
+
// Check for user decision gate
|
|
194
|
+
if (taskProfile.userDecisionPending) {
|
|
195
|
+
return {
|
|
196
|
+
taskId,
|
|
197
|
+
taskClass,
|
|
198
|
+
state: 'NEEDS_USER_DECISION',
|
|
199
|
+
templateName: template.name,
|
|
200
|
+
summary: 'Task requires explicit user approval on an architectural or design decision.',
|
|
201
|
+
satisfiedRequirements: [],
|
|
202
|
+
missingRequirements: [],
|
|
203
|
+
claimsSummary: { supported: 0, hypotheses: 0, conflicts: 0 },
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Inspect claims
|
|
208
|
+
const taskClaims = claimsManager ? claimsManager.getClaims(taskId) : [];
|
|
209
|
+
const conflictedClaims = taskClaims.filter((c: any) => c.status === 'conflicted');
|
|
210
|
+
if (conflictedClaims.length > 0) {
|
|
211
|
+
hasBlocked = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const taskEvidence = evidenceLedger ? evidenceLedger.queryEvidence({ taskId, status: 'FRESH' }) : [];
|
|
215
|
+
|
|
216
|
+
// An item accepted before FR-004 existed, or one whose target file has since shrunk, can be
|
|
217
|
+
// FRESH and still point at nothing. *Stale* (the file changed at all) and *unresolvable* (the
|
|
218
|
+
// locator no longer names anything) are different verdicts and must not be collapsed: a stale
|
|
219
|
+
// item is still checkable by a human, an unresolvable one is not (FR-005).
|
|
220
|
+
// Stale and unresolvable are different failures and are reported apart: an unresolvable
|
|
221
|
+
// locator points at nothing, a stale one points at something that changed since it was read.
|
|
222
|
+
// A superseded item is excluded here, not from getAllEvidence(): superseding is a recorded
|
|
223
|
+
// human judgment that this specific item no longer needs re-checking (a fresher item already
|
|
224
|
+
// covers the same ground), the same way a retracted/superseded claim stops being read as live
|
|
225
|
+
// in evaluateClaim — it is not a claim that the file never changed. An item that genuinely went
|
|
226
|
+
// stale and was never superseded still forces NEEDS_EVIDENCE exactly as before.
|
|
227
|
+
const staleEvidence = (evidenceLedger ? evidenceLedger.getAllEvidence() : [])
|
|
228
|
+
.filter((item: any) => item.taskId === taskId && item.freshness?.status === 'STALE' && item.status !== 'superseded')
|
|
229
|
+
.map((item: any) => ({ evidenceId: item.id, locator: item.locator, reason: 'file-modified-since-recorded' }));
|
|
230
|
+
|
|
231
|
+
const unresolvableEvidence = [];
|
|
232
|
+
for (const item of taskEvidence) {
|
|
233
|
+
if (item.provenance !== 'extracted' || !item.locator || !item.locator.file) continue;
|
|
234
|
+
const resolution = resolveLocator({ locator: item.locator, repoRoot: this.projectRoot });
|
|
235
|
+
if (!resolution.resolved) {
|
|
236
|
+
unresolvableEvidence.push({
|
|
237
|
+
evidenceId: item.id,
|
|
238
|
+
locator: item.locator,
|
|
239
|
+
reason: resolution.reason,
|
|
240
|
+
detail: describeResolution(item.locator, resolution),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for (const [reqId, reqDef] of Object.entries<any>(requirements)) {
|
|
246
|
+
let isSatisfied = false;
|
|
247
|
+
let reason = null;
|
|
248
|
+
let matchedEvidenceIds = [];
|
|
249
|
+
|
|
250
|
+
// Check verification plan requirement
|
|
251
|
+
if (reqId === 'regression_verification' || reqId === 'verification_plan' || reqId === 'verification_command') {
|
|
252
|
+
if (taskProfile.verificationPlan || taskProfile.verificationCommand) {
|
|
253
|
+
isSatisfied = true;
|
|
254
|
+
} else {
|
|
255
|
+
reason = 'No verification command or test execution plan defined in task profile.';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Check claim status requirement (e.g. root_cause). A template can additionally demand a
|
|
259
|
+
// claim *role*: `root_cause` is only established by a claim declared as the root-cause claim,
|
|
260
|
+
// not by any supported claim the task happens to hold (review R1 — a supported claim about an
|
|
261
|
+
// unrelated invariant satisfied the bug template's root-cause requirement).
|
|
262
|
+
else if (reqDef.requiresClaimStatus) {
|
|
263
|
+
let matchingClaims = taskClaims.filter((c: any) => c.status === reqDef.requiresClaimStatus);
|
|
264
|
+
if (reqDef.requiresClaimRole) {
|
|
265
|
+
const withRole = matchingClaims.filter((c: any) => c.role === reqDef.requiresClaimRole);
|
|
266
|
+
if (withRole.length === 0 && matchingClaims.length > 0) {
|
|
267
|
+
reason = `${matchingClaims.length} claim(s) have status '${reqDef.requiresClaimStatus}' but none `
|
|
268
|
+
+ `declares role '${reqDef.requiresClaimRole}' — add the claim with --role ${reqDef.requiresClaimRole}, `
|
|
269
|
+
+ 'or re-state which claim answers this requirement.';
|
|
270
|
+
}
|
|
271
|
+
matchingClaims = withRole;
|
|
272
|
+
}
|
|
273
|
+
if (matchingClaims.length > 0) {
|
|
274
|
+
isSatisfied = true;
|
|
275
|
+
matchedEvidenceIds = matchingClaims.flatMap((c: any) => c.supportingEvidence);
|
|
276
|
+
} else if (!reason) {
|
|
277
|
+
reason = `Requires at least one claim with status '${reqDef.requiresClaimStatus}'`
|
|
278
|
+
+ `${reqDef.requiresClaimRole ? ` and role '${reqDef.requiresClaimRole}'` : ''}.`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Check evidence kind requirement. Kind alone is a category, not an outcome: to count, an
|
|
282
|
+
// item must also (a) be an extracted read rather than a model inference, (b) explicitly name
|
|
283
|
+
// this requirement in `establishes`, and (c) where the template demands an execution, carry a
|
|
284
|
+
// typed observation whose exit status matches. Before these checks, an inferred item reading
|
|
285
|
+
// "Baseline tests failed: 12 failures." satisfied `baseline_tests` by being kind test-result
|
|
286
|
+
// (review R1, P1 — reproduced through the public CLI).
|
|
287
|
+
else if (Array.isArray(reqDef.evidenceKinds) && reqDef.evidenceKinds.length > 0) {
|
|
288
|
+
const allowedKinds = new Set(reqDef.evidenceKinds);
|
|
289
|
+
const kindMatched = taskEvidence.filter((ev: any) => allowedKinds.has(ev.kind));
|
|
290
|
+
const extracted = kindMatched.filter((ev: any) => ev.provenance === 'extracted');
|
|
291
|
+
const bound = extracted.filter((ev: any) => Array.isArray(ev.establishes) && ev.establishes.includes(reqId));
|
|
292
|
+
let qualifying = bound;
|
|
293
|
+
if (reqDef.requiresObservation) {
|
|
294
|
+
qualifying = qualifying.filter((ev: any) => ev.observation && Number.isInteger(ev.observation.exitCode));
|
|
295
|
+
if (reqDef.expectedExit === 'zero') {
|
|
296
|
+
qualifying = qualifying.filter((ev: any) => ev.observation.exitCode === 0);
|
|
297
|
+
} else if (reqDef.expectedExit === 'nonzero') {
|
|
298
|
+
qualifying = qualifying.filter((ev: any) => ev.observation.exitCode !== 0);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (qualifying.length > 0) {
|
|
302
|
+
isSatisfied = true;
|
|
303
|
+
matchedEvidenceIds = qualifying.map((ev: any) => ev.id);
|
|
304
|
+
} else if (kindMatched.length === 0) {
|
|
305
|
+
reason = `Missing fresh evidence of kind: ${reqDef.evidenceKinds.join(', ')}.`;
|
|
306
|
+
} else if (extracted.length === 0) {
|
|
307
|
+
reason = `${kindMatched.length} item(s) of the right kind exist but none is 'extracted' — an inferred `
|
|
308
|
+
+ 'or asserted item is analysis, and analysis cannot establish a measured prerequisite.';
|
|
309
|
+
} else if (bound.length === 0) {
|
|
310
|
+
reason = `${extracted.length} extracted item(s) of the right kind exist but none declares it `
|
|
311
|
+
+ `establishes '${reqId}' — record evidence with establishes: ['${reqId}'] so the gate grades `
|
|
312
|
+
+ 'what the item was actually gathered to prove.';
|
|
313
|
+
} else if (reqDef.expectedExit === 'zero' && bound.some((ev: any) => ev.observation && ev.observation.exitCode !== 0)) {
|
|
314
|
+
const failed = bound.find((ev: any) => ev.observation && ev.observation.exitCode !== 0);
|
|
315
|
+
reason = `The bound observation ran '${failed.observation.command}' and exited ${failed.observation.exitCode}; `
|
|
316
|
+
+ 'this requirement needs an exit status of 0. A failed run does not establish a passing baseline.';
|
|
317
|
+
} else {
|
|
318
|
+
reason = 'The bound evidence lacks a typed observation ({command, exitCode})'
|
|
319
|
+
+ `${reqDef.expectedExit ? ` with a${reqDef.expectedExit === 'zero' ? ' passing (0)' : ' non-zero'} exit status` : ''} — `
|
|
320
|
+
+ 'record the execution itself, not a description of one.';
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Scope verified / scope clear
|
|
324
|
+
else if (reqId === 'scope_clear' || reqId === 'scope_verified' || reqId === 'invariants_captured') {
|
|
325
|
+
if (taskProfile.scopeClear || taskProfile.description || taskProfile.invariants) {
|
|
326
|
+
isSatisfied = true;
|
|
327
|
+
} else {
|
|
328
|
+
reason = `Prerequisite '${reqId}' not documented.`;
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
// Fail closed. A gate whose unrecognized requirements default to satisfied reports READY
|
|
332
|
+
// for prerequisites it never actually evaluated — the one failure mode a readiness contract
|
|
333
|
+
// exists to prevent. No shipped template reaches here today; a future one adding a
|
|
334
|
+
// requirement without `evidenceKinds`/`requiresClaimStatus` should surface as unmet, not
|
|
335
|
+
// silently pass.
|
|
336
|
+
isSatisfied = false;
|
|
337
|
+
reason = `Requirement '${reqId}' has no evaluator; treating as unmet.`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!isSatisfied && reqDef.required) {
|
|
341
|
+
allSatisfied = false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
requirementResults.push({
|
|
345
|
+
id: reqId,
|
|
346
|
+
description: reqDef.description,
|
|
347
|
+
required: Boolean(reqDef.required),
|
|
348
|
+
satisfied: isSatisfied,
|
|
349
|
+
reason,
|
|
350
|
+
evidenceIds: matchedEvidenceIds,
|
|
351
|
+
recommendedAction: isSatisfied ? null : this.suggestAction(reqId, reqDef, taskProfile),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let state = 'READY';
|
|
356
|
+
let summary = 'All mandatory readiness prerequisites are verified by fresh evidence.';
|
|
357
|
+
|
|
358
|
+
if (hasBlocked) {
|
|
359
|
+
state = 'BLOCKED';
|
|
360
|
+
summary = `Task is blocked by ${conflictedClaims.length} conflicted claim(s). Contradicting evidence must be resolved.`;
|
|
361
|
+
} else if (!allSatisfied) {
|
|
362
|
+
state = 'NEEDS_EVIDENCE';
|
|
363
|
+
const missingCount = requirementResults.filter((r) => r.required && !r.satisfied).length;
|
|
364
|
+
summary = `Task requires ${missingCount} additional evidence item(s) before implementation.`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// FR-005. Applied after the base verdict so it can never be overwritten by it, and appended
|
|
368
|
+
// rather than replacing, so a task that is short of evidence *and* holding an unresolvable
|
|
369
|
+
// locator reports both. Not BLOCKED: nothing contradicts anything here — the record simply no
|
|
370
|
+
// longer points at the repository, which is a different failure from evidence that disagrees
|
|
371
|
+
// with itself. Naming the specific items is the requirement; a silent downgrade would leave the
|
|
372
|
+
// reader to hunt for which item moved.
|
|
373
|
+
if (staleEvidence.length > 0) {
|
|
374
|
+
if (state === 'READY') state = 'NEEDS_EVIDENCE';
|
|
375
|
+
summary += ` ${staleEvidence.length} evidence item(s) went stale: the files they name have `
|
|
376
|
+
+ 'changed since they were recorded. Re-record them against the tree as it stands.';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (unresolvableEvidence.length > 0) {
|
|
380
|
+
if (state === 'READY') state = 'NEEDS_EVIDENCE';
|
|
381
|
+
summary += ` ${unresolvableEvidence.length} evidence item(s) have a locator that no longer `
|
|
382
|
+
+ `resolves: ${unresolvableEvidence.map((u) => u.detail).join('; ')}. `
|
|
383
|
+
+ 'Re-record them against the tree as it stands.';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
taskId,
|
|
388
|
+
taskClass,
|
|
389
|
+
state,
|
|
390
|
+
templateName: template.name,
|
|
391
|
+
summary,
|
|
392
|
+
requirements: requirementResults,
|
|
393
|
+
claimsSummary: {
|
|
394
|
+
total: taskClaims.length,
|
|
395
|
+
supported: taskClaims.filter((c: any) => c.status === 'supported').length,
|
|
396
|
+
hypotheses: taskClaims.filter((c: any) => c.status === 'hypothesis').length,
|
|
397
|
+
conflicts: conflictedClaims.length,
|
|
398
|
+
},
|
|
399
|
+
evidenceCount: taskEvidence.length,
|
|
400
|
+
unresolvableEvidence,
|
|
401
|
+
staleEvidence,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** One live snapshot for readiness inspection and orchestration transitions. Reads never persist
|
|
407
|
+
* derived freshness or claim status; every invocation evaluates the current project again.
|
|
408
|
+
* `mode` defaults to 'workflow' — fail closed: the standalone exemption must be declared. */
|
|
409
|
+
function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot = process.cwd(), mode = 'workflow' }: any) {
|
|
410
|
+
// Validated before any ledger work, not just inside stageEntryFor at the end — an invalid mode
|
|
411
|
+
// used to pay a full freshness/claims evaluation before erroring (review suggestion, 026).
|
|
412
|
+
if (!EXECUTION_MODES.has(mode)) {
|
|
413
|
+
throw new Error(`Unknown execution mode '${mode}'. Valid: ${[...EXECUTION_MODES].join(', ')} — the standalone exemption is declared, never inferred.`);
|
|
414
|
+
}
|
|
415
|
+
const { EvidenceLedger } = require('./evidence-ledger.ts');
|
|
416
|
+
const { ClaimsManager } = require('./claims.ts');
|
|
417
|
+
const { FreshnessValidator } = require('./freshness.ts');
|
|
418
|
+
const ledger = new EvidenceLedger({ repoRoot: projectRoot });
|
|
419
|
+
ledger.load(taskProfile.taskId);
|
|
420
|
+
new FreshnessValidator({ repoRoot: projectRoot }).validateLedgerFreshness(ledger);
|
|
421
|
+
const claims = new ClaimsManager({ evidenceLedger: ledger, repoRoot: projectRoot });
|
|
422
|
+
claims.load(taskProfile.taskId);
|
|
423
|
+
claims.evaluateAll();
|
|
424
|
+
const report = new ReadinessEngine({ repoRoot, projectRoot }).evaluateReadiness(taskProfile, ledger, claims);
|
|
425
|
+
return { ...report, executionMode: mode, stageEntry: stageEntryFor(report.state, mode) };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
module.exports = {
|
|
429
|
+
evaluateTaskReadiness,
|
|
430
|
+
stageEntryFor,
|
|
431
|
+
ReadinessEngine,
|
|
432
|
+
READINESS_STATES,
|
|
433
|
+
EXECUTION_MODES,
|
|
434
|
+
STAGE_ENTRY_DECISIONS,
|
|
435
|
+
};
|