@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.4
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 +1061 -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 +48 -4
- package/src/adapters/antigravity/index.js +631 -0
- package/src/adapters/claude/index.js +548 -0
- package/src/adapters/claude/mcp.js +94 -0
- package/src/adapters/codex/agents.js +208 -0
- package/src/adapters/codex/config.js +272 -0
- package/src/adapters/codex/hooks.js +203 -0
- package/src/adapters/codex/index.js +707 -0
- package/src/adapters/codex/mcp.js +271 -0
- package/src/adapters/codex/native-projection.js +34 -0
- package/src/adapters/copilot/index.js +413 -0
- package/src/adapters/copy-tree.js +620 -0
- package/src/adapters/gemini/hooks.js +210 -0
- package/src/adapters/gemini/index.js +390 -0
- package/src/adapters/hook-commands.js +60 -0
- package/src/adapters/index.js +106 -0
- package/src/adapters/kiro/index.js +310 -0
- package/src/adapters/mcp-entries.js +331 -0
- package/src/adapters/opencode/index.js +466 -0
- package/src/adapters/pi/index.js +279 -0
- package/src/adapters/pi/json-members.js +185 -0
- package/src/adapters/pi/mcp.js +373 -0
- package/src/adapters/resolve-source.js +19 -0
- package/src/cli/commands/install.js +218 -0
- package/src/cli/commands/list-backups.js +21 -0
- package/src/cli/commands/reconcile.js +120 -0
- package/src/cli/commands/remove.js +82 -0
- package/src/cli/commands/rollback.js +172 -0
- package/src/cli/commands/self-update.js +33 -0
- package/src/cli/commands/status.js +138 -0
- package/src/cli/commands/tools.js +92 -0
- package/src/cli/commands/update.js +162 -0
- package/src/cli/index.js +568 -0
- package/src/cli/runtime-commands.js +281 -0
- package/src/cli/shared.js +218 -0
- package/src/helper/bash-helper.js +34 -0
- package/src/helper/git.js +24 -0
- package/src/helper/guardrails.js +49 -0
- package/src/helper/harness-paths.js +140 -0
- package/src/helper/marker-merge.js +116 -0
- package/src/helper/prompt.js +61 -0
- package/src/helper/repo-root.js +29 -0
- package/src/helper/settings-merge.js +151 -0
- package/src/helper/settings-scope.js +33 -0
- package/src/helper/toml.js +244 -0
- package/src/install/backup-set.js +149 -0
- package/src/install/backup.js +724 -0
- package/src/install/context.js +58 -0
- package/src/install/manifest.js +145 -0
- package/src/install/mcp.js +254 -0
- package/src/install/paths.js +84 -0
- package/src/install/targets.js +90 -0
- package/src/install/tool-lifecycle.js +151 -0
- package/src/lifecycle/bash-availability.js +25 -0
- package/src/lifecycle/index.js +812 -0
- package/src/lifecycle/mcp-index.js +28 -0
- package/src/lifecycle/policies.js +73 -0
- package/src/lifecycle/view.js +175 -0
- package/src/registry/index.js +611 -0
- package/src/release/build-package.js +91 -0
- package/src/release/changelog-section.js +70 -0
- package/src/release/sync-plugin-versions.js +66 -0
- package/src/runtime/advice/cli.js +283 -0
- package/src/runtime/advice/client.js +324 -0
- package/src/runtime/advice/config.js +189 -0
- package/src/runtime/advice/doctor.js +169 -0
- package/src/runtime/advice/endpoint-guard.js +134 -0
- package/src/runtime/advice/kinds.js +154 -0
- package/src/runtime/advice/providers/ollama-systemone.js +209 -0
- package/src/runtime/advice/record.js +166 -0
- package/src/runtime/advice/registry.js +170 -0
- package/src/runtime/advice/strength.js +66 -0
- package/src/runtime/brief-tools.js +342 -0
- package/src/runtime/c4-project.js +371 -0
- package/src/runtime/capability-router.js +484 -0
- package/src/runtime/checkouts.js +170 -0
- package/src/runtime/claims.js +468 -0
- package/src/runtime/cli-result.js +55 -0
- package/src/runtime/cli.js +678 -0
- package/src/runtime/command-detect.js +516 -0
- package/src/runtime/context-pack.js +492 -0
- package/src/runtime/decision-register.js +493 -0
- package/src/runtime/evidence-ledger.js +338 -0
- package/src/runtime/failure/capture.js +272 -0
- package/src/runtime/failure/classifier.js +68 -0
- package/src/runtime/failure/cli.js +178 -0
- package/src/runtime/failure/home.js +64 -0
- package/src/runtime/failure/store.js +226 -0
- package/src/runtime/feature-resolve.js +79 -0
- package/src/runtime/freshness.js +177 -0
- package/src/runtime/health.js +863 -0
- package/src/runtime/history-compactor.js +237 -0
- package/src/runtime/implementation-gate.js +321 -0
- package/src/runtime/inventory/coverage.js +218 -0
- package/src/runtime/inventory/identity.js +65 -0
- package/src/runtime/inventory/index.js +705 -0
- package/src/runtime/inventory/precedence.js +134 -0
- package/src/runtime/inventory/read-scopes.js +131 -0
- package/src/runtime/inventory/siblings.js +192 -0
- package/src/runtime/knowledge/chunker.js +124 -0
- package/src/runtime/knowledge/hybrid.js +149 -0
- package/src/runtime/knowledge/index-store.js +145 -0
- package/src/runtime/knowledge/retrieval.js +194 -0
- package/src/runtime/leak-scan.js +185 -0
- package/src/runtime/lifecycle/cli.js +276 -0
- package/src/runtime/lifecycle/event-store.js +340 -0
- package/src/runtime/lifecycle/fold.js +353 -0
- package/src/runtime/lifecycle/followup.js +432 -0
- package/src/runtime/lifecycle/goal.js +249 -0
- package/src/runtime/lifecycle/intent-writer.js +86 -0
- package/src/runtime/lifecycle/overview.js +292 -0
- package/src/runtime/lifecycle/release.js +241 -0
- package/src/runtime/lifecycle/report-store.js +221 -0
- package/src/runtime/lifecycle/retention.js +212 -0
- package/src/runtime/lifecycle/root.js +71 -0
- package/src/runtime/lifecycle/status.js +353 -0
- package/src/runtime/lifecycle/store-upkeep.js +393 -0
- package/src/runtime/locator-resolve.js +158 -0
- package/src/runtime/mask.js +210 -0
- package/src/runtime/mcp-consultations.js +412 -0
- package/src/runtime/mcp-reach.js +157 -0
- package/src/runtime/model-router.js +153 -0
- package/src/runtime/outcome.js +467 -0
- package/src/runtime/reach.js +148 -0
- package/src/runtime/readiness-record.js +150 -0
- package/src/runtime/readiness.js +435 -0
- package/src/runtime/recovery.js +282 -0
- package/src/runtime/research-request.js +231 -0
- package/src/runtime/retrieval-plan.js +578 -0
- package/src/runtime/scaffold/artifacts.js +217 -0
- package/src/runtime/scaffold/fingerprint.js +56 -0
- package/src/runtime/scaffold/generate.js +911 -0
- package/src/runtime/scaffold/languages.js +245 -0
- package/src/runtime/storage/cli.js +64 -0
- package/src/runtime/storage/engine.js +249 -0
- package/src/runtime/storage/registry.js +130 -0
- package/src/runtime/storage/roots.js +63 -0
- package/src/runtime/storage/rules.js +282 -0
- package/src/runtime/storage/upkeep.js +243 -0
- package/src/runtime/storage/work-status.js +326 -0
- package/src/runtime/task-attempts.js +415 -0
- package/src/runtime/task-classifier.js +550 -0
- package/src/runtime/task-scope.js +130 -0
- package/src/runtime/task-state.js +176 -0
- package/src/runtime/trace/indicators.js +235 -0
- package/src/runtime/trace/ledger.js +626 -0
- package/src/runtime/trace/render.js +76 -0
- package/src/runtime/trace/views.js +459 -0
- package/src/runtime/verification/contract-runner.js +341 -0
- package/src/runtime/verification/engine.js +1461 -0
- package/src/runtime/verification/registry.js +130 -0
- package/src/runtime/verification/scope-bound.js +418 -0
- package/src/runtime/verification/test-selection.js +172 -0
- package/src/runtime/workflow-engine.js +588 -0
- package/src/runtime/workflow-orchestrator.js +953 -0
- package/src/runtime/worktree.js +340 -0
- package/src/state/index.js +193 -0
- package/src/state/lockfile.js +136 -0
- package/src/state/run-lock.js +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The task's terminal record (feature 011, plan task B.2, design component C2; FR-011).
|
|
7
|
+
*
|
|
8
|
+
* A task that ends leaves nothing behind today. Readiness answered at some point, verification
|
|
9
|
+
* answered at some point, evidence accumulated — and then the run stopped, and the next worker
|
|
10
|
+
* opening the same task finds four states scattered across three stores and no statement of how it
|
|
11
|
+
* ended. This module writes that statement: **one closed-vocabulary terminal state, with the basis
|
|
12
|
+
* it rests on recorded beside it**, so "this finished" and "this stopped" are never the same
|
|
13
|
+
* record.
|
|
14
|
+
*
|
|
15
|
+
* It mirrors `readiness.ts`'s four-state shape and reuses `verification`'s meaning of
|
|
16
|
+
* `INCONCLUSIVE` — a verdict over zero evidence is not a pass — and it owns no verdict of its own:
|
|
17
|
+
*
|
|
18
|
+
* - It **never re-evaluates readiness and never re-runs verification.** Re-running verification
|
|
19
|
+
* would execute the project's checks as a side effect of filing a record, and re-evaluating
|
|
20
|
+
* readiness here would grade the task under a profile this verb invented rather than the one
|
|
21
|
+
* the stage actually acted on. Both verdicts therefore arrive as the caller's own statement of
|
|
22
|
+
* what its run saw, validated against the vocabulary each owning module exports, and are
|
|
23
|
+
* labelled in the record as stated rather than measured (the `callerAsserted` precedent
|
|
24
|
+
* `readiness`'s own verb already sets). A basis nobody stated reads `NOT_RECORDED` — never a
|
|
25
|
+
* pass, never an empty string, never absent.
|
|
26
|
+
* - It **does not decide when a task ends.** The workflow's terminal stage decides that (design
|
|
27
|
+
* A1), so the writer is learned from `workflow-engine`'s `terminalStage` for the declared
|
|
28
|
+
* class. A caller naming a non-terminal `--stage` is refused with the stage that is terminal.
|
|
29
|
+
* - The two things it *does* measure it measures itself, at the write: how many records the
|
|
30
|
+
* evidence ledger holds for this task, and which declared retrieval needs the run never
|
|
31
|
+
* reached (read from `retrieval-plan`'s own record, by its own path function).
|
|
32
|
+
*
|
|
33
|
+
* `recordedAt` is stamped here and is never accepted from the caller, exactly as the evidence
|
|
34
|
+
* ledger stamps freshness at the write — a timestamp a caller supplies is a claim about when
|
|
35
|
+
* something happened, not a record of it.
|
|
36
|
+
*
|
|
37
|
+
* No state here is a number, a percentage or a confidence (NFR-001). `basis.evidenceCount` is the
|
|
38
|
+
* only integer the record carries and it is a **count of records**: how many things were filed, not
|
|
39
|
+
* how good they were. Score-shaped inputs are refused by name at this boundary through the same
|
|
40
|
+
* one definition `bin/doflow.js` and `retrieval-plan.ts` use.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const fs = require('node:fs');
|
|
44
|
+
const path = require('node:path');
|
|
45
|
+
const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.js');
|
|
46
|
+
const { taskStoreDir } = require('./task-scope.js');
|
|
47
|
+
// The two verdict vocabularies, imported from the modules that own them rather than respelled
|
|
48
|
+
// here. A second copy would let this file accept a readiness state readiness itself has retired.
|
|
49
|
+
const { READINESS_STATES } = require('./readiness.js');
|
|
50
|
+
const { VERIFICATION_STATUSES } = require('./verification/engine.js');
|
|
51
|
+
const { WorkflowEngine } = require('./workflow-engine.js');
|
|
52
|
+
// Read, not re-derived: the retrieval plan's location is `retrieval-plan.ts`'s to define, and the
|
|
53
|
+
// unreached items this record carries forward are the ones that record already holds.
|
|
54
|
+
const { planPath, UNREACHED } = require('./retrieval-plan.js');
|
|
55
|
+
// One definition of the score refusal, applied at a further boundary. See the note on
|
|
56
|
+
// EVIDENCE_SCORE_FIELDS in cli.js: a terminal state that carried a confidence would be exactly the
|
|
57
|
+
// number NFR-001 exists to keep out of the one record the next worker reads first.
|
|
58
|
+
const { assertNoScoreFields } = require('./cli.js');
|
|
59
|
+
const { finishRuntime, usageError } = require('./cli-result.js');
|
|
60
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
61
|
+
|
|
62
|
+
// ── the closed terminal vocabulary (design §4) ───────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/** The work was done and rests on something. Unavailable over an empty ledger — see `record`. */
|
|
65
|
+
const COMPLETED = 'COMPLETED';
|
|
66
|
+
/** The work stopped against an obstacle it could not pass. It is not finished and not abandoned. */
|
|
67
|
+
const BLOCKED = 'BLOCKED';
|
|
68
|
+
/** The work was deliberately dropped. A decision, and distinct from having been stopped. */
|
|
69
|
+
const ABANDONED = 'ABANDONED';
|
|
70
|
+
/**
|
|
71
|
+
* Verification's meaning, unchanged: a verdict over zero evidence is not a pass. This is what a
|
|
72
|
+
* task ending with nothing recorded is, and the reason `COMPLETED` cannot describe it.
|
|
73
|
+
*/
|
|
74
|
+
const INCONCLUSIVE = 'INCONCLUSIVE';
|
|
75
|
+
|
|
76
|
+
const OUTCOME_STATES = Object.freeze([COMPLETED, BLOCKED, ABANDONED, INCONCLUSIVE]);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* What a basis field says when nobody stated it.
|
|
80
|
+
*
|
|
81
|
+
* Deliberately a token and not `null`: an absent readiness state and a readiness state of `READY`
|
|
82
|
+
* must not be able to read the same way to anything scanning these records, and an empty field
|
|
83
|
+
* invites the reader to supply the missing half themselves.
|
|
84
|
+
*/
|
|
85
|
+
const NOT_RECORDED = 'NOT_RECORDED';
|
|
86
|
+
|
|
87
|
+
// ── the record ────────────────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Where a task's outcome lives, under the invoking project's state root.
|
|
91
|
+
*
|
|
92
|
+
* The task id is validated by the evidence ledger's own rule, unchanged: it becomes a filename, so
|
|
93
|
+
* it must never be able to name a path.
|
|
94
|
+
* @param {string} projectRoot
|
|
95
|
+
* @param {string} taskId
|
|
96
|
+
* @param {string|null} [slug] the feature the task belongs to, when not the branch's (task-scope.js)
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
function outcomePath(projectRoot , taskId , slug = null) {
|
|
100
|
+
const dir = taskStoreDir({ projectRoot, store: 'outcome', taskId: assertSafeTaskId(taskId), slug });
|
|
101
|
+
return path.join(dir, `${taskId}.json`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} projectRoot
|
|
106
|
+
* @param {string} taskId
|
|
107
|
+
* @returns {Object|null} the record, or null when no outcome was recorded
|
|
108
|
+
*/
|
|
109
|
+
function readOutcome(projectRoot , taskId , slug = null) {
|
|
110
|
+
const file = outcomePath(projectRoot, taskId, slug);
|
|
111
|
+
if (!fs.existsSync(file)) return null;
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
114
|
+
} catch (error ) {
|
|
115
|
+
throw new Error(`the outcome record at '${file}' is not readable: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} projectRoot
|
|
121
|
+
* @param {string} taskId
|
|
122
|
+
* @param {Object} record
|
|
123
|
+
* @returns {string} the file written
|
|
124
|
+
*/
|
|
125
|
+
function writeOutcome(projectRoot , taskId , record , slug = null) {
|
|
126
|
+
const file = outcomePath(projectRoot, taskId, slug);
|
|
127
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
128
|
+
fs.writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
|
|
129
|
+
return file;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── the basis, half measured and half stated ─────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* How many records the ledger holds for this task.
|
|
136
|
+
*
|
|
137
|
+
* A count of records and nothing more (NFR-001). It does not weigh them, does not judge whether
|
|
138
|
+
* they are the right ones — that is readiness's job, and readiness already did it — and it is the
|
|
139
|
+
* one number this whole record contains.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} projectRoot
|
|
142
|
+
* @param {string} taskId
|
|
143
|
+
* @returns {number}
|
|
144
|
+
*/
|
|
145
|
+
function countEvidence(projectRoot , taskId , slug = null) {
|
|
146
|
+
const ledger = new EvidenceLedger({ repoRoot: projectRoot, slug });
|
|
147
|
+
ledger.load(taskId);
|
|
148
|
+
return ledger.queryEvidence({ taskId }).length;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Declared items no run reached, carried forward from retrieval and from verification.
|
|
153
|
+
*
|
|
154
|
+
* Every entry names its source, because the two halves are not the same kind of gap: a retrieval
|
|
155
|
+
* need that was declared and never asked is a lookup that did not happen, while an unstated or
|
|
156
|
+
* inconclusive verification verdict is a check that did not establish anything. Collapsing them
|
|
157
|
+
* into one unlabelled list would leave a reader unable to tell which one they are looking at.
|
|
158
|
+
*
|
|
159
|
+
* The retrieval half is read from the plan record — its own `UNREACHED` result, computed by the
|
|
160
|
+
* module that owns that vocabulary, not recomputed here.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} projectRoot
|
|
163
|
+
* @param {string} taskId
|
|
164
|
+
* @param {string} verification the stated verification verdict, or NOT_RECORDED
|
|
165
|
+
* @returns {Array<string>}
|
|
166
|
+
*/
|
|
167
|
+
function unreachedItems(projectRoot , taskId , verification , slug = null) {
|
|
168
|
+
const items = [];
|
|
169
|
+
const file = planPath(projectRoot, taskId, slug);
|
|
170
|
+
if (fs.existsSync(file)) {
|
|
171
|
+
let plan;
|
|
172
|
+
try {
|
|
173
|
+
plan = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
174
|
+
} catch (error ) {
|
|
175
|
+
throw new Error(`the retrieval plan at '${file}' is not readable: ${error.message}`);
|
|
176
|
+
}
|
|
177
|
+
for (const need of plan.needs || []) {
|
|
178
|
+
if (need.result === UNREACHED) items.push(`retrieval:${need.intent}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (verification === NOT_RECORDED) {
|
|
182
|
+
items.push('verification:NOT_RECORDED — no verification verdict was stated for this task');
|
|
183
|
+
} else if (verification === 'INCONCLUSIVE') {
|
|
184
|
+
items.push('verification:INCONCLUSIVE — the contract reached a verdict over zero evidence');
|
|
185
|
+
}
|
|
186
|
+
return items;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Validates one caller-stated basis verdict against the vocabulary its owning module exports.
|
|
191
|
+
*
|
|
192
|
+
* An omitted verdict is `NOT_RECORDED`. An unrecognised one is refused with the valid set rather
|
|
193
|
+
* than stored: a basis field holding a token nothing else in the runtime uses would look like a
|
|
194
|
+
* verdict while meaning nothing.
|
|
195
|
+
*
|
|
196
|
+
* @param {string|undefined} value
|
|
197
|
+
* @param {Iterable<string>} vocabulary
|
|
198
|
+
* @param {string} flag the flag name, for the refusal
|
|
199
|
+
* @returns {{value: string, stated: boolean}}
|
|
200
|
+
*/
|
|
201
|
+
function statedVerdict(value , vocabulary , flag ) {
|
|
202
|
+
if (value === undefined || value === null || String(value).trim() === '') {
|
|
203
|
+
return { value: NOT_RECORDED, stated: false };
|
|
204
|
+
}
|
|
205
|
+
const token = String(value).trim();
|
|
206
|
+
const valid = [...vocabulary];
|
|
207
|
+
if (!valid.includes(token)) {
|
|
208
|
+
throw new Error(`unknown ${flag} '${token}'. Valid: ${valid.join(', ')}, or omit the flag to record ${NOT_RECORDED}`);
|
|
209
|
+
}
|
|
210
|
+
return { value: token, stated: true };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── actions ───────────────────────────────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* `--action record` — write the terminal state with its basis.
|
|
217
|
+
*
|
|
218
|
+
* One consistency rule is enforced here and it is the one design §4 states: **`COMPLETED` is not
|
|
219
|
+
* available over an empty ledger.** A task whose evidence ledger holds nothing has no basis for
|
|
220
|
+
* "the work was done and rests on something", and `INCONCLUSIVE` is the state that describes it —
|
|
221
|
+
* the same rule `verify` applies to a contract with no checks. It is a refusal rather than a silent
|
|
222
|
+
* downgrade, because rewriting the caller's word would hide the thing worth knowing.
|
|
223
|
+
*
|
|
224
|
+
* A record replaces any previous one for the task: the record is keyed by task id and stamps one
|
|
225
|
+
* `recordedAt`, so a second write is a second terminal statement, not an amendment to the first.
|
|
226
|
+
*
|
|
227
|
+
* @returns {number} exit code
|
|
228
|
+
*/
|
|
229
|
+
function recordOutcome({ projectRoot, packageRoot, taskId, taskClass, stage, state, readiness, verification, json, slug } ) {
|
|
230
|
+
if (state === undefined || state === null || String(state).trim() === '') {
|
|
231
|
+
return usageError('outcome',
|
|
232
|
+
`--action record needs --state <state>. Valid: ${OUTCOME_STATES.join(', ')}`, json);
|
|
233
|
+
}
|
|
234
|
+
const proposed = String(state).trim();
|
|
235
|
+
if (!OUTCOME_STATES.includes(proposed)) {
|
|
236
|
+
return usageError('outcome',
|
|
237
|
+
`unknown --state '${proposed}'. Valid: ${OUTCOME_STATES.join(', ')}`, json);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// The terminal stage is learned, never decided here (design A1). That is why the class is
|
|
241
|
+
// required on a write: without it there is no workflow to ask which stage is the last one, and
|
|
242
|
+
// guessing would put this module in charge of when a task ends.
|
|
243
|
+
if (typeof taskClass !== 'string' || taskClass.trim() === '') {
|
|
244
|
+
return usageError('outcome',
|
|
245
|
+
'--task-class is required to record an outcome: the workflow\'s terminal stage is what writes '
|
|
246
|
+
+ 'one (design A1), and the class is what names that stage. Run `doflow workflow --task-class <c>` '
|
|
247
|
+
+ 'to see it', json);
|
|
248
|
+
}
|
|
249
|
+
let workflow;
|
|
250
|
+
try {
|
|
251
|
+
workflow = new WorkflowEngine({ repoRoot: packageRoot }).resolveWorkflow(taskClass);
|
|
252
|
+
} catch (error ) {
|
|
253
|
+
return usageError('outcome', error.message, json, error);
|
|
254
|
+
}
|
|
255
|
+
const terminalStage = workflow.terminalStage;
|
|
256
|
+
if (stage !== undefined && stage !== null && String(stage).trim() !== '' && String(stage).trim() !== terminalStage.id) {
|
|
257
|
+
return usageError('outcome',
|
|
258
|
+
`stage '${String(stage).trim()}' is not the terminal stage of task class '${taskClass}' — `
|
|
259
|
+
+ `'${terminalStage.id}' is (${workflow.stageIds.join(' -> ')}). The outcome is written by the `
|
|
260
|
+
+ 'stage the workflow ends on, so a record from an earlier stage would state a task had ended '
|
|
261
|
+
+ 'while stages remained', json);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let basisReadiness;
|
|
265
|
+
let basisVerification;
|
|
266
|
+
try {
|
|
267
|
+
basisReadiness = statedVerdict(readiness, READINESS_STATES, '--readiness');
|
|
268
|
+
basisVerification = statedVerdict(verification, VERIFICATION_STATUSES, '--verification');
|
|
269
|
+
} catch (error ) {
|
|
270
|
+
return usageError('outcome', error.message, json, error);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let evidenceCount;
|
|
274
|
+
let unreached;
|
|
275
|
+
try {
|
|
276
|
+
evidenceCount = countEvidence(projectRoot, taskId, slug);
|
|
277
|
+
unreached = unreachedItems(projectRoot, taskId, basisVerification.value, slug);
|
|
278
|
+
} catch (error ) {
|
|
279
|
+
return usageError('outcome', error.message, json, error);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (proposed === COMPLETED && evidenceCount === 0) {
|
|
283
|
+
return usageError('outcome',
|
|
284
|
+
`'${COMPLETED}' is not available for task '${taskId}': its evidence ledger holds no records, so `
|
|
285
|
+
+ `there is nothing the completion rests on. ${INCONCLUSIVE} is the state that describes a verdict `
|
|
286
|
+
+ 'over zero evidence. Record the evidence with `doflow evidence --action add`, or record '
|
|
287
|
+
+ `--state ${INCONCLUSIVE}`, json);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const statedByCaller = [
|
|
291
|
+
...(basisReadiness.stated ? ['readiness'] : []),
|
|
292
|
+
...(basisVerification.stated ? ['verification'] : []),
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
const record = {
|
|
296
|
+
version: 1,
|
|
297
|
+
taskId,
|
|
298
|
+
state: proposed,
|
|
299
|
+
// Stamped here, never accepted from the caller: a caller-supplied timestamp is a claim about
|
|
300
|
+
// when a task ended, and this record's whole value is that it was written when it happened.
|
|
301
|
+
recordedAt: new Date().toISOString(),
|
|
302
|
+
taskClass,
|
|
303
|
+
writtenByStage: terminalStage.id,
|
|
304
|
+
basis: {
|
|
305
|
+
readiness: basisReadiness.value,
|
|
306
|
+
verification: basisVerification.value,
|
|
307
|
+
evidenceCount,
|
|
308
|
+
unreached,
|
|
309
|
+
},
|
|
310
|
+
// Which half of the basis rests on someone saying so. A record that mixes measured and stated
|
|
311
|
+
// inputs must say which is which, or the next reader treats both as measurements.
|
|
312
|
+
statedByCaller,
|
|
313
|
+
};
|
|
314
|
+
const file = writeOutcome(projectRoot, taskId, record, slug);
|
|
315
|
+
|
|
316
|
+
if (json) {
|
|
317
|
+
console.log(JSON.stringify({ action: 'record', ...record, stateFile: file }, null, 2));
|
|
318
|
+
return finishRuntime(0);
|
|
319
|
+
}
|
|
320
|
+
printOutcome(record, file, 'recorded');
|
|
321
|
+
return finishRuntime(0);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* `--action show` — emit the recorded outcome.
|
|
326
|
+
*
|
|
327
|
+
* Exits 1 when none exists, and that is the *only* thing exit 1 means here. A recorded `BLOCKED`
|
|
328
|
+
* exits 0, because the record answered: overloading the code with "the task did not complete"
|
|
329
|
+
* would make "no outcome was ever recorded" and "the outcome was recorded and it was not a
|
|
330
|
+
* completion" indistinguishable to every caller, which is the collapse this feature exists to undo.
|
|
331
|
+
*
|
|
332
|
+
* @returns {number} exit code
|
|
333
|
+
*/
|
|
334
|
+
function showOutcome({ projectRoot, taskId, json, slug } ) {
|
|
335
|
+
let record;
|
|
336
|
+
try {
|
|
337
|
+
record = readOutcome(projectRoot, taskId, slug);
|
|
338
|
+
} catch (error ) {
|
|
339
|
+
return usageError('outcome', error.message, json, error);
|
|
340
|
+
}
|
|
341
|
+
if (!record) {
|
|
342
|
+
const file = outcomePath(projectRoot, taskId, slug);
|
|
343
|
+
if (json) {
|
|
344
|
+
console.log(JSON.stringify({
|
|
345
|
+
action: 'show', taskId, state: null, stateFile: file,
|
|
346
|
+
summary: `no outcome is recorded for task '${taskId}'`,
|
|
347
|
+
}, null, 2));
|
|
348
|
+
} else {
|
|
349
|
+
console.log(`\nDoFlow Task Outcome [Task: ${taskId}]:`);
|
|
350
|
+
console.log('═'.repeat(78));
|
|
351
|
+
console.log(`No outcome is recorded for this task (looked in ${file}).`);
|
|
352
|
+
console.log('An unrecorded outcome is not a completed task and not a failed one — it is a task');
|
|
353
|
+
console.log('whose end nobody stated. The terminal stage records it with --action record.');
|
|
354
|
+
console.log('═'.repeat(78) + '\n');
|
|
355
|
+
}
|
|
356
|
+
return finishRuntime(1);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (json) {
|
|
360
|
+
console.log(JSON.stringify({ action: 'show', ...record, stateFile: outcomePath(projectRoot, taskId, slug) }, null, 2));
|
|
361
|
+
return finishRuntime(0);
|
|
362
|
+
}
|
|
363
|
+
printOutcome(record, outcomePath(projectRoot, taskId, slug), 'recorded at');
|
|
364
|
+
return finishRuntime(0);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** The one text rendering both actions print, so a shown record reads exactly as the written one. */
|
|
368
|
+
function printOutcome(record , file , verb ) {
|
|
369
|
+
const basis = record.basis || {};
|
|
370
|
+
console.log(`\nDoFlow Task Outcome [Task: ${record.taskId}] — ${record.state}:`);
|
|
371
|
+
console.log('═'.repeat(78));
|
|
372
|
+
console.log(`Task class: ${record.taskClass || 'not stated'}`);
|
|
373
|
+
console.log(`Written by: ${record.writtenByStage || 'not stated'} (the workflow's terminal stage)`);
|
|
374
|
+
console.log(`Recorded at: ${record.recordedAt}`);
|
|
375
|
+
console.log('─'.repeat(78));
|
|
376
|
+
console.log('Basis:');
|
|
377
|
+
console.log(` Readiness ${basis.readiness}`);
|
|
378
|
+
console.log(` Verification ${basis.verification}`);
|
|
379
|
+
console.log(` Evidence ${basis.evidenceCount} record(s) — a count of records, not a score`);
|
|
380
|
+
const stated = record.statedByCaller || [];
|
|
381
|
+
if (stated.length > 0) {
|
|
382
|
+
// Named, not hidden, exactly as the readiness verb names its own caller-asserted inputs.
|
|
383
|
+
console.log(` Caller-stated: ${stated.join(', ')} (stated by the run that recorded this, not measured here)`);
|
|
384
|
+
}
|
|
385
|
+
// NFR-004: what nothing reached is stated as prominently as what was decided.
|
|
386
|
+
const unreached = basis.unreached || [];
|
|
387
|
+
if (unreached.length > 0) {
|
|
388
|
+
console.log('─'.repeat(78));
|
|
389
|
+
console.log('Declared and never reached:');
|
|
390
|
+
for (const item of unreached) console.log(` ${item}`);
|
|
391
|
+
}
|
|
392
|
+
console.log('─'.repeat(78));
|
|
393
|
+
console.log(`Outcome ${verb} ${file}`);
|
|
394
|
+
console.log('═'.repeat(78) + '\n');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ── the verb ──────────────────────────────────────────────────────────────────────────────────
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Handles `doflow outcome` — record a task's terminal state with its basis, or show the one
|
|
401
|
+
* already recorded.
|
|
402
|
+
*
|
|
403
|
+
* @param {Object} options
|
|
404
|
+
* @param {string} options.taskId
|
|
405
|
+
* @param {'record'|'show'|'status'} [options.action='show']
|
|
406
|
+
* @param {string} [options.state] the terminal state, on `record`
|
|
407
|
+
* @param {string} [options.taskClass] the workflow whose terminal stage is writing, on `record`
|
|
408
|
+
* @param {string} [options.stage] the writing stage, refused when it is not the terminal one
|
|
409
|
+
* @param {string} [options.readiness] the readiness state the run saw
|
|
410
|
+
* @param {string} [options.verification] the verification verdict the run saw
|
|
411
|
+
* @param {boolean} [options.json=false]
|
|
412
|
+
* @param {string} [options.repoRoot] the DoFlow package root, holding the workflow registry
|
|
413
|
+
* @param {string} [options.stateRoot] the project the task belongs to, holding its state
|
|
414
|
+
* @returns {number} exit code
|
|
415
|
+
*/
|
|
416
|
+
function handleOutcomeCommand(options = {}) {
|
|
417
|
+
const {
|
|
418
|
+
taskId, action = 'show', state, taskClass, stage, readiness, verification,
|
|
419
|
+
json = false, repoRoot, stateRoot, slug,
|
|
420
|
+
} = options;
|
|
421
|
+
|
|
422
|
+
// NFR-001, at the boundary where a caller's input first becomes a record. `bin/doflow.js` already
|
|
423
|
+
// refuses every score-shaped flag name on the command line; this is the same rule applied to the
|
|
424
|
+
// option object, so a second input surface cannot arrive later without inheriting the refusal.
|
|
425
|
+
try {
|
|
426
|
+
assertNoScoreFields(options, '');
|
|
427
|
+
} catch (error ) {
|
|
428
|
+
return usageError('outcome', error.message, json, error);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// `'status'` is the CLI's shared `--action` default, owned by `tools`. Every read-action verb
|
|
432
|
+
// accepts it as an alias for its own default rather than reporting a usage error on an argument
|
|
433
|
+
// the caller never typed — `verify`, `claim` and `retrieval-plan` already do exactly this.
|
|
434
|
+
const resolvedAction = action === 'status' ? 'show' : action;
|
|
435
|
+
if (resolvedAction !== 'record' && resolvedAction !== 'show') {
|
|
436
|
+
return usageError('outcome', `unknown --action '${action}'. Valid: record, show (default)`, json);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// The project the task belongs to owns the state; the package root owns the workflow registry,
|
|
440
|
+
// and is a different directory in every install.
|
|
441
|
+
const projectRoot = stateRoot || process.cwd();
|
|
442
|
+
const packageRoot = repoRoot || REPO_ROOT;
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
assertSafeTaskId(taskId);
|
|
446
|
+
} catch (error ) {
|
|
447
|
+
return usageError('outcome', error.message, json, error);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (resolvedAction === 'record') {
|
|
451
|
+
return recordOutcome({
|
|
452
|
+
projectRoot, packageRoot, taskId, taskClass, stage, state, readiness, verification, json, slug,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return showOutcome({ projectRoot, taskId, json, slug });
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
module.exports = {
|
|
459
|
+
handleOutcomeCommand,
|
|
460
|
+
outcomePath,
|
|
461
|
+
OUTCOME_STATES,
|
|
462
|
+
COMPLETED,
|
|
463
|
+
BLOCKED,
|
|
464
|
+
ABANDONED,
|
|
465
|
+
INCONCLUSIVE,
|
|
466
|
+
NOT_RECORDED,
|
|
467
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Static reach: for each harness the ledger says is installed, can the skills it received find a
|
|
7
|
+
* dispatcher and a runtime? The answer mirrors the lookup order of the skill resolver and of
|
|
8
|
+
* `doflow-run` using stats only — no process is spawned and nothing touches the network — so
|
|
9
|
+
* `doctor` can say "installed, but a skill's first runtime call would stop" before a skill runs.
|
|
10
|
+
*
|
|
11
|
+
* Reach belongs to the `.doflow` root that holds the dispatcher, not to a harness: a harness is
|
|
12
|
+
* reached through a tree another harness installed in the same root.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const os = require('node:os');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { readLedger, stateRoot } = require('../state/index.js');
|
|
19
|
+
|
|
20
|
+
/** Where the dispatcher sits under a `.doflow` root; the skill resolver and the locator shim spell the same path. */
|
|
21
|
+
const REACH_DISPATCHER_REL = 'scripts/doflow/bin/doflow-run';
|
|
22
|
+
/** Where the Node runtime sits under a `.doflow` root; `doflow-run` execs it from there. */
|
|
23
|
+
const REACH_RUNTIME_REL = 'runtime/bin/doflow.js';
|
|
24
|
+
|
|
25
|
+
/** `dir`, then each parent, ending when the parent is the directory itself (`/`, `C:\`). */
|
|
26
|
+
function* ancestors(dir ) {
|
|
27
|
+
for (let current = dir; ; current = path.dirname(current)) {
|
|
28
|
+
yield current;
|
|
29
|
+
if (path.dirname(current) === current) return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function statIs(fsImpl , target , kind ) {
|
|
34
|
+
try { return fsImpl.statSync(target)[kind](); } catch { return false; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isExecutableFile(fsImpl , target ) {
|
|
38
|
+
if (!statIs(fsImpl, target, 'isFile')) return false;
|
|
39
|
+
try { fsImpl.accessSync(target, fs.constants.X_OK); return true; } catch { return false; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {Object} options
|
|
44
|
+
* @param {Object} options.registry loaded registry; its harness order orders the rows
|
|
45
|
+
* @param {string} options.projectRoot directory the project walk starts from
|
|
46
|
+
* @param {string} [options.homeDir]
|
|
47
|
+
* @param {Object} [options.fsImpl]
|
|
48
|
+
* @returns {{rows: Array<{harness: string, scope: 'project'|'global', installRoot: string, state: 'REACHED'|'NO-REACH'|'N/A', root?: string, reason?: string, fix?: string}>, unreadable: Array<{scope: string, ledger: string, detail: string}>}}
|
|
49
|
+
*/
|
|
50
|
+
function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs } ) {
|
|
51
|
+
const real = (target ) => { try { return fsImpl.realpathSync(target); } catch { return path.resolve(target); } };
|
|
52
|
+
const start = real(projectRoot);
|
|
53
|
+
const home = real(homeDir);
|
|
54
|
+
const homeDoflow = path.join(home, '.doflow');
|
|
55
|
+
const homeDispatcher = path.join(homeDoflow, REACH_DISPATCHER_REL);
|
|
56
|
+
|
|
57
|
+
// Every ledger on the walk is read, not only the nearest: a removed harness's residue in a
|
|
58
|
+
// subdirectory must not hide the install that encloses it. The walk stops at the home directory,
|
|
59
|
+
// whose own ledger is the global scope; nothing above it is read.
|
|
60
|
+
const scopes = [];
|
|
61
|
+
if (start !== home) {
|
|
62
|
+
const walk = [];
|
|
63
|
+
for (const dir of ancestors(start)) {
|
|
64
|
+
if (dir === home) break;
|
|
65
|
+
walk.push(dir);
|
|
66
|
+
}
|
|
67
|
+
for (const installRoot of walk.filter((dir ) => statIs(fsImpl, path.join(dir, '.doflow', 'state'), 'isDirectory'))) {
|
|
68
|
+
scopes.push({
|
|
69
|
+
scope: 'project',
|
|
70
|
+
installRoot,
|
|
71
|
+
stateDir: path.join(installRoot, '.doflow', 'state'),
|
|
72
|
+
from: start,
|
|
73
|
+
searched: `${path.join(start, '.doflow', REACH_DISPATCHER_REL)} and each directory above it, then ${homeDispatcher}`,
|
|
74
|
+
doflowDirs: [...ancestors(start)].map((dir) => path.join(dir, '.doflow')).concat(homeDoflow),
|
|
75
|
+
fix: (harness ) => `npx @khoavu882/doflow install ${installRoot} -t ${harness}`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
scopes.push({
|
|
80
|
+
scope: 'global',
|
|
81
|
+
installRoot: home,
|
|
82
|
+
stateDir: stateRoot({ scope: 'global', homeDir: home }),
|
|
83
|
+
from: home,
|
|
84
|
+
searched: homeDispatcher,
|
|
85
|
+
doflowDirs: [homeDoflow],
|
|
86
|
+
fix: (harness ) => `npx @khoavu882/doflow install -g -t ${harness}`,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const harnessIds = registry.harnesses.map((harness ) => harness.id);
|
|
90
|
+
const rows = [];
|
|
91
|
+
const unreadable = [];
|
|
92
|
+
const seen = new Set ();
|
|
93
|
+
const addRow = (row ) => {
|
|
94
|
+
// A stale outer ledger that says what a nearer one already said adds nothing.
|
|
95
|
+
const key = JSON.stringify([row.harness, row.scope, row.state, row.root, row.reason]);
|
|
96
|
+
if (seen.has(key)) return;
|
|
97
|
+
seen.add(key);
|
|
98
|
+
rows.push(row);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
for (const { scope, installRoot, stateDir, from, searched, doflowDirs, fix } of scopes) {
|
|
102
|
+
let ledger ;
|
|
103
|
+
try {
|
|
104
|
+
ledger = readLedger(stateDir, { fsImpl });
|
|
105
|
+
} catch (error ) {
|
|
106
|
+
unreadable.push({ scope, ledger: stateDir, detail: error.message });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!ledger) continue;
|
|
110
|
+
|
|
111
|
+
const resources = ledger.resources || [];
|
|
112
|
+
const installed = harnessIds.filter((id ) => resources.some((resource ) => resource.harness === id));
|
|
113
|
+
if (!installed.length) continue;
|
|
114
|
+
|
|
115
|
+
const root = doflowDirs.find((dir ) => isExecutableFile(fsImpl, path.join(dir, REACH_DISPATCHER_REL)));
|
|
116
|
+
const dispatcher = root && path.join(root, REACH_DISPATCHER_REL);
|
|
117
|
+
const configDir = [...ancestors(from)].map((dir) => path.join(dir, '.doflow')).find((dir) => statIs(fsImpl, dir, 'isDirectory'));
|
|
118
|
+
const runtime = [configDir, homeDoflow].filter(Boolean).some((dir) => statIs(fsImpl, path.join(dir, REACH_RUNTIME_REL), 'isFile'));
|
|
119
|
+
// The runtime is looked up in the nearest `.doflow` only, so a nearer one than this ledger's
|
|
120
|
+
// install hides that install, and installing there would not change what the skills find.
|
|
121
|
+
const hiddenBy = scope === 'project' && configDir && configDir !== path.join(installRoot, '.doflow') ? configDir : null;
|
|
122
|
+
|
|
123
|
+
for (const harness of installed) {
|
|
124
|
+
if (!resources.some((resource ) => resource.harness === harness && resource.assetId === 'skills.doflow')) {
|
|
125
|
+
addRow({ harness, scope, installRoot, state: 'N/A', reason: `no skills at ${scope} scope` });
|
|
126
|
+
} else if (dispatcher && runtime) {
|
|
127
|
+
addRow({ harness, scope, installRoot, state: 'REACHED', root });
|
|
128
|
+
} else {
|
|
129
|
+
addRow({
|
|
130
|
+
harness,
|
|
131
|
+
scope,
|
|
132
|
+
installRoot,
|
|
133
|
+
state: 'NO-REACH',
|
|
134
|
+
reason: dispatcher
|
|
135
|
+
? `dispatcher at ${dispatcher} but no ${REACH_RUNTIME_REL}`
|
|
136
|
+
: `no executable dispatcher at ${searched}`,
|
|
137
|
+
fix: hiddenBy
|
|
138
|
+
? `${hiddenBy} hides the install at ${installRoot}; remove it if it is stale, or run: npx @khoavu882/doflow install ${path.dirname(hiddenBy)} -t ${harness}`
|
|
139
|
+
: fix(harness),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { rows, unreadable };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { REACH_DISPATCHER_REL, REACH_RUNTIME_REL, evaluateReach };
|