@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,588 @@
|
|
|
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.js');
|
|
8
|
+
const { finishRuntime, usageError } = require('./cli-result.js');
|
|
9
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a task class to the ordered stages that run for it.
|
|
14
|
+
*
|
|
15
|
+
* The engine holds no knowledge of any particular class. Everything it answers — which stages run,
|
|
16
|
+
* which of them mutate source, which readiness template gates them — is read from
|
|
17
|
+
* `core/registry/workflows.json`, so adding a class is a registry edit and not a code change.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Fields every stage must declare as a non-empty string. `readinessTemplate` and `optional` are
|
|
21
|
+
* checked separately, because for those two an *absent* key is the dangerous case: reading a
|
|
22
|
+
* missing `readinessTemplate` as "ungated" is how a gate disappears without anyone noticing. */
|
|
23
|
+
const STAGE_STRING_FIELDS = ['id', 'skill', 'kind', 'purpose'];
|
|
24
|
+
const GATE_STRING_FIELDS = ['id', 'name', 'afterStage', 'kind', 'trigger', 'prompt'];
|
|
25
|
+
|
|
26
|
+
const READINESS_REGISTRY_FILE = 'readiness-templates.json';
|
|
27
|
+
|
|
28
|
+
/** What a shipped skill is, as far as class routing is concerned. `stage` is the default and the
|
|
29
|
+
* only role the fit check can answer for; the other two are exemptions, and the registry makes each
|
|
30
|
+
* of them state why — an exemption nobody wrote a reason for is indistinguishable from an omission,
|
|
31
|
+
* and rots into a skill that quietly cannot be fit-checked. */
|
|
32
|
+
const CALLER_ROLES = Object.freeze({ ROUTER: 'router', STAGE: 'stage', STANDALONE: 'standalone' });
|
|
33
|
+
const CALLER_ROLE_VALUES = Object.freeze(Object.values(CALLER_ROLES));
|
|
34
|
+
|
|
35
|
+
function isPlainObject(value ) {
|
|
36
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isNonEmptyString(value ) {
|
|
40
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Accepts the several shapes a caller may already hold readiness templates in: the parsed
|
|
44
|
+
* registry document, its `templates` map, a Set, or a plain list of names. */
|
|
45
|
+
function normalizeTemplateNames(input ) {
|
|
46
|
+
if (Array.isArray(input)) return new Set(input);
|
|
47
|
+
if (input instanceof Set) return new Set(input);
|
|
48
|
+
if (isPlainObject(input)) {
|
|
49
|
+
const map = isPlainObject(input.templates) ? input.templates : input;
|
|
50
|
+
return new Set(Object.keys(map));
|
|
51
|
+
}
|
|
52
|
+
throw new TypeError(
|
|
53
|
+
'readinessTemplates must be an array, a Set, an object keyed by template name, '
|
|
54
|
+
+ 'or false to disable the cross-check',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function deepFreeze(value ) {
|
|
59
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
60
|
+
Object.freeze(value);
|
|
61
|
+
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class WorkflowEngine {
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {Object} [options]
|
|
77
|
+
* @param {string} [options.repoRoot]
|
|
78
|
+
* @param {string} [options.registryPath]
|
|
79
|
+
* @param {Object} [options.workflows] Pre-parsed registry document, bypassing the filesystem.
|
|
80
|
+
* @param {Object|Array<string>|Set<string>|false} [options.readinessTemplates] Template names to
|
|
81
|
+
* cross-check stage gates against, or `false` to skip the cross-check. When omitted, names are
|
|
82
|
+
* read from `readiness-templates.json` beside the workflow registry — but only if the workflow
|
|
83
|
+
* registry itself came from disk, so an injected document never forces a filesystem read.
|
|
84
|
+
* @param {Object} [options.fsImpl]
|
|
85
|
+
*/
|
|
86
|
+
constructor(options = {}) {
|
|
87
|
+
this.fsImpl = options.fsImpl || fs;
|
|
88
|
+
this.repoRoot = options.repoRoot || REPO_ROOT;
|
|
89
|
+
this.registryPath = options.registryPath
|
|
90
|
+
|| path.join(this.repoRoot, 'core', 'registry', 'workflows.json');
|
|
91
|
+
|
|
92
|
+
const injected = Boolean(options.workflows);
|
|
93
|
+
const document = options.workflows || this.loadRegistry();
|
|
94
|
+
|
|
95
|
+
if (options.readinessTemplates === false || options.readinessTemplates === null) {
|
|
96
|
+
this.knownReadinessTemplates = null;
|
|
97
|
+
} else if (options.readinessTemplates !== undefined) {
|
|
98
|
+
this.knownReadinessTemplates = normalizeTemplateNames(options.readinessTemplates);
|
|
99
|
+
} else {
|
|
100
|
+
// A stage naming a template that does not exist would gate on nothing while looking gated —
|
|
101
|
+
// the same shape of defect as a verification check with no command reporting PASS. Catch it
|
|
102
|
+
// at load rather than at the gate.
|
|
103
|
+
this.knownReadinessTemplates = injected ? null : this.loadReadinessTemplateNames();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const problems = WorkflowEngine.validateRegistry(document, {
|
|
107
|
+
readinessTemplates: this.knownReadinessTemplates,
|
|
108
|
+
});
|
|
109
|
+
if (problems.length > 0) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Invalid workflow registry '${this.registryPath}':\n - ${problems.join('\n - ')}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.version = document.version;
|
|
116
|
+
this.stageKinds = document.stageKinds;
|
|
117
|
+
this.classes = document.classes;
|
|
118
|
+
this.callers = document.callers;
|
|
119
|
+
this.resolvedCache = new Map();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
loadRegistry() {
|
|
123
|
+
return parseYamlFile(this.registryPath, this.fsImpl);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @returns {Set<string>} template names declared in the sibling readiness registry. */
|
|
127
|
+
loadReadinessTemplateNames() {
|
|
128
|
+
const templatePath = path.join(path.dirname(this.registryPath), READINESS_REGISTRY_FILE);
|
|
129
|
+
const document = parseYamlFile(templatePath, this.fsImpl);
|
|
130
|
+
if (!isPlainObject(document.templates) || Object.keys(document.templates).length === 0) {
|
|
131
|
+
throw new Error(`Readiness registry '${templatePath}' declares no templates`);
|
|
132
|
+
}
|
|
133
|
+
return new Set(Object.keys(document.templates));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Structural validation of a registry document. Returns every problem found rather than the
|
|
138
|
+
* first, so a guard can report the whole picture in one run.
|
|
139
|
+
* @param {Object} document
|
|
140
|
+
* @param {Object} [options]
|
|
141
|
+
* @param {Set<string>|null} [options.readinessTemplates]
|
|
142
|
+
* @returns {Array<string>}
|
|
143
|
+
*/
|
|
144
|
+
static validateRegistry(document , options = {}) {
|
|
145
|
+
const problems = [];
|
|
146
|
+
if (!isPlainObject(document)) {
|
|
147
|
+
return ['registry document is not an object'];
|
|
148
|
+
}
|
|
149
|
+
if (!isPlainObject(document.stageKinds) || Object.keys(document.stageKinds).length === 0) {
|
|
150
|
+
problems.push('`stageKinds` must be a non-empty object');
|
|
151
|
+
}
|
|
152
|
+
if (!isPlainObject(document.classes) || Object.keys(document.classes).length === 0) {
|
|
153
|
+
problems.push('`classes` must be a non-empty object');
|
|
154
|
+
}
|
|
155
|
+
if (!isPlainObject(document.callers) || Object.keys(document.callers).length === 0) {
|
|
156
|
+
problems.push('`callers` must be a non-empty object mapping every shipped skill to a role; '
|
|
157
|
+
+ 'without it the classifier cannot tell which skills a class can host');
|
|
158
|
+
}
|
|
159
|
+
if (problems.length > 0) return problems;
|
|
160
|
+
|
|
161
|
+
for (const [callerId, caller] of (Object.entries(document.callers) )) {
|
|
162
|
+
if (!isPlainObject(caller)) {
|
|
163
|
+
problems.push(`caller '${callerId}' must be an object`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!CALLER_ROLE_VALUES.includes(caller.role)) {
|
|
167
|
+
problems.push(
|
|
168
|
+
`caller '${callerId}' must declare a role of ${CALLER_ROLE_VALUES.join(', ')} `
|
|
169
|
+
+ `(received ${JSON.stringify(caller.role)})`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (caller.role !== CALLER_ROLES.STAGE && !isNonEmptyString(caller.reason)) {
|
|
173
|
+
problems.push(
|
|
174
|
+
`caller '${callerId}' has role '${caller.role}' and must state why in \`reason\`: `
|
|
175
|
+
+ 'an exemption from the fit check is a decision, not an omission',
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// A class must never be resolvable by default. If the registry ever grows a default-class key,
|
|
181
|
+
// fail here rather than let it silently absorb every unclassified task.
|
|
182
|
+
if ('defaultClass' in document) {
|
|
183
|
+
problems.push('`defaultClass` is not permitted: an unrecognized class is rejected, never coerced');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const knownTemplates = options.readinessTemplates || null;
|
|
187
|
+
const kindIds = new Set(Object.keys(document.stageKinds));
|
|
188
|
+
|
|
189
|
+
for (const [kindId, kind] of (Object.entries(document.stageKinds) )) {
|
|
190
|
+
if (!isPlainObject(kind)) {
|
|
191
|
+
problems.push(`stageKind '${kindId}' must be an object`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (typeof kind.mutatesSource !== 'boolean') {
|
|
195
|
+
problems.push(`stageKind '${kindId}' must declare a boolean \`mutatesSource\``);
|
|
196
|
+
}
|
|
197
|
+
if (!isNonEmptyString(kind.description)) {
|
|
198
|
+
problems.push(`stageKind '${kindId}' must declare a non-empty \`description\``);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const [classId, definition] of (Object.entries(document.classes) )) {
|
|
203
|
+
if (!isPlainObject(definition)) {
|
|
204
|
+
problems.push(`class '${classId}' must be an object`);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
for (const field of ['name', 'description', 'readinessNote']) {
|
|
208
|
+
if (!isNonEmptyString(definition[field])) {
|
|
209
|
+
problems.push(`class '${classId}' must declare a non-empty \`${field}\``);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (definition.handoff !== undefined && !isNonEmptyString(definition.handoff)) {
|
|
213
|
+
problems.push(`class '${classId}' declares a \`handoff\` that is not a non-empty string`);
|
|
214
|
+
}
|
|
215
|
+
if (!Array.isArray(definition.stages) || definition.stages.length === 0) {
|
|
216
|
+
problems.push(`class '${classId}' must declare a non-empty \`stages\` array`);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const stageIds = new Set();
|
|
221
|
+
let hasRequiredStage = false;
|
|
222
|
+
|
|
223
|
+
definition.stages.forEach((stage , index ) => {
|
|
224
|
+
const where = `class '${classId}' stage #${index + 1}`;
|
|
225
|
+
if (!isPlainObject(stage)) {
|
|
226
|
+
problems.push(`${where} must be an object`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
for (const field of STAGE_STRING_FIELDS) {
|
|
230
|
+
if (!isNonEmptyString(stage[field])) {
|
|
231
|
+
problems.push(`${where} must declare a non-empty \`${field}\``);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (isNonEmptyString(stage.id)) {
|
|
235
|
+
if (stageIds.has(stage.id)) {
|
|
236
|
+
problems.push(`class '${classId}' declares duplicate stage id '${stage.id}'`);
|
|
237
|
+
}
|
|
238
|
+
stageIds.add(stage.id);
|
|
239
|
+
}
|
|
240
|
+
if (isNonEmptyString(stage.kind) && !kindIds.has(stage.kind)) {
|
|
241
|
+
problems.push(`${where} names undeclared stage kind '${stage.kind}'`);
|
|
242
|
+
}
|
|
243
|
+
if (typeof stage.optional !== 'boolean') {
|
|
244
|
+
problems.push(`${where} must declare a boolean \`optional\` explicitly`);
|
|
245
|
+
} else if (!stage.optional) {
|
|
246
|
+
hasRequiredStage = true;
|
|
247
|
+
}
|
|
248
|
+
if (!Object.prototype.hasOwnProperty.call(stage, 'readinessTemplate')) {
|
|
249
|
+
problems.push(`${where} must declare \`readinessTemplate\` explicitly (null when ungated)`);
|
|
250
|
+
} else if (stage.readinessTemplate !== null) {
|
|
251
|
+
if (!isNonEmptyString(stage.readinessTemplate)) {
|
|
252
|
+
problems.push(`${where} \`readinessTemplate\` must be null or a template name`);
|
|
253
|
+
} else if (knownTemplates && !knownTemplates.has(stage.readinessTemplate)) {
|
|
254
|
+
problems.push(
|
|
255
|
+
`${where} references readiness template '${stage.readinessTemplate}', which does not exist`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// An edit that no readiness contract gates is the failure mode FR-008 exists to prevent,
|
|
260
|
+
// so the registry cannot express one.
|
|
261
|
+
const kind = isNonEmptyString(stage.kind) ? document.stageKinds[stage.kind] : null;
|
|
262
|
+
if (kind && kind.mutatesSource === true && stage.readinessTemplate === null) {
|
|
263
|
+
problems.push(`${where} mutates source but declares no readiness template`);
|
|
264
|
+
}
|
|
265
|
+
// `editTimeGate: false` leaves a gated stage to the handoff check alone; only a stage that
|
|
266
|
+
// edits source has an edit-time check to opt out of. Absent means the check applies.
|
|
267
|
+
if (Object.prototype.hasOwnProperty.call(stage, 'editTimeGate')) {
|
|
268
|
+
if (typeof stage.editTimeGate !== 'boolean') {
|
|
269
|
+
problems.push(`${where} \`editTimeGate\` must be a boolean`);
|
|
270
|
+
}
|
|
271
|
+
if (!kind || kind.mutatesSource !== true) {
|
|
272
|
+
problems.push(`${where} declares \`editTimeGate\` but does not mutate source`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (!hasRequiredStage) {
|
|
278
|
+
problems.push(
|
|
279
|
+
`class '${classId}' has no required stage; a workflow of only optional stages runs nothing`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const gates = definition.gates;
|
|
284
|
+
if (!Array.isArray(gates)) {
|
|
285
|
+
problems.push(`class '${classId}' must declare a \`gates\` array (empty when it has none)`);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const gateIds = new Set();
|
|
289
|
+
gates.forEach((gate, index) => {
|
|
290
|
+
const where = `class '${classId}' gate #${index + 1}`;
|
|
291
|
+
if (!isPlainObject(gate)) {
|
|
292
|
+
problems.push(`${where} must be an object`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
for (const field of GATE_STRING_FIELDS) {
|
|
296
|
+
if (!isNonEmptyString(gate[field])) {
|
|
297
|
+
problems.push(`${where} must declare a non-empty \`${field}\``);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (isNonEmptyString(gate.id)) {
|
|
301
|
+
if (gateIds.has(gate.id)) {
|
|
302
|
+
problems.push(`class '${classId}' declares duplicate gate id '${gate.id}'`);
|
|
303
|
+
}
|
|
304
|
+
gateIds.add(gate.id);
|
|
305
|
+
}
|
|
306
|
+
if (isNonEmptyString(gate.afterStage) && !stageIds.has(gate.afterStage)) {
|
|
307
|
+
problems.push(`${where} attaches to unknown stage '${gate.afterStage}'`);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return problems;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** @returns {Array<string>} declared class ids, in declaration order. */
|
|
316
|
+
listClasses() {
|
|
317
|
+
return Object.keys(this.classes);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* @param {*} taskClass
|
|
322
|
+
* @returns {boolean} true only for an exactly-matching declared class id.
|
|
323
|
+
*/
|
|
324
|
+
hasClass(taskClass ) {
|
|
325
|
+
return typeof taskClass === 'string'
|
|
326
|
+
&& Object.prototype.hasOwnProperty.call(this.classes, taskClass);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** @returns {Array<string>} declared caller ids, in declaration order. */
|
|
330
|
+
listCallers() {
|
|
331
|
+
return Object.keys(this.callers);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @param {*} skillId
|
|
336
|
+
* @returns {boolean} true only for an exactly-matching declared caller id.
|
|
337
|
+
*/
|
|
338
|
+
hasCaller(skillId ) {
|
|
339
|
+
return typeof skillId === 'string'
|
|
340
|
+
&& Object.prototype.hasOwnProperty.call(this.callers, skillId);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @param {string} skillId
|
|
345
|
+
* @returns {'router'|'stage'|'standalone'|null} null when the id is not a declared caller.
|
|
346
|
+
*/
|
|
347
|
+
callerRole(skillId ) {
|
|
348
|
+
return this.hasCaller(skillId) ? this.callers[skillId].role : null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @param {string} skillId
|
|
353
|
+
* @returns {string|null} the declared exemption reason, or null.
|
|
354
|
+
*/
|
|
355
|
+
callerReason(skillId ) {
|
|
356
|
+
if (!this.hasCaller(skillId)) return null;
|
|
357
|
+
const { reason } = this.callers[skillId];
|
|
358
|
+
return isNonEmptyString(reason) ? reason : null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Stage ids of one class whose `skill` is this id. Empty when the class does not name it.
|
|
363
|
+
* @param {string} taskClass
|
|
364
|
+
* @param {string} skillId
|
|
365
|
+
* @returns {Array<string>}
|
|
366
|
+
*/
|
|
367
|
+
stagesHosting(taskClass , skillId ) {
|
|
368
|
+
if (typeof skillId !== 'string' || !this.hasClass(taskClass)) return [];
|
|
369
|
+
return this.resolveWorkflow(taskClass).stages
|
|
370
|
+
.filter((stage ) => stage.skill === skillId)
|
|
371
|
+
.map((stage ) => stage.id);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Every class that names this skill at any stage, optional or not. Derived from the stage lists,
|
|
376
|
+
* never declared, so it cannot drift from them.
|
|
377
|
+
* @param {string} skillId
|
|
378
|
+
* @returns {Array<{taskClass: string, stageIds: Array<string>}>}
|
|
379
|
+
*/
|
|
380
|
+
classesHosting(skillId ) {
|
|
381
|
+
const hosting = [];
|
|
382
|
+
for (const taskClass of this.listClasses()) {
|
|
383
|
+
const stageIds = this.stagesHosting(taskClass, skillId);
|
|
384
|
+
if (stageIds.length > 0) hosting.push({ taskClass, stageIds });
|
|
385
|
+
}
|
|
386
|
+
return hosting;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** @param {string} kind @returns {boolean} */
|
|
390
|
+
isImplementationKind(kind ) {
|
|
391
|
+
const declared = this.stageKinds[kind];
|
|
392
|
+
if (!declared) {
|
|
393
|
+
throw new Error(
|
|
394
|
+
`Unknown stage kind '${kind}'. Declared: ${Object.keys(this.stageKinds).join(', ')}`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
return declared.mutatesSource === true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Summaries of every class, for presenting the choice before one is proposed.
|
|
402
|
+
* @returns {Array<Object>}
|
|
403
|
+
*/
|
|
404
|
+
listClassOptions() {
|
|
405
|
+
return this.listClasses().map((taskClass) => {
|
|
406
|
+
const workflow = this.resolveWorkflow(taskClass);
|
|
407
|
+
return {
|
|
408
|
+
taskClass,
|
|
409
|
+
name: workflow.name,
|
|
410
|
+
description: workflow.description,
|
|
411
|
+
stageCount: workflow.stages.length,
|
|
412
|
+
hasImplementationStage: workflow.hasImplementationStage,
|
|
413
|
+
requiresImplementationReadiness: workflow.requiresImplementationReadiness,
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Resolves a task class to its full workflow.
|
|
420
|
+
*
|
|
421
|
+
* Throws on a missing or unrecognized class. There is no fallback class: resolving an unknown
|
|
422
|
+
* input to `feature` would produce a confident answer about the wrong workflow.
|
|
423
|
+
* @param {string} taskClass
|
|
424
|
+
* @returns {Object} frozen ResolvedWorkflow
|
|
425
|
+
*/
|
|
426
|
+
resolveWorkflow(taskClass ) {
|
|
427
|
+
if (!isNonEmptyString(taskClass)) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
`A task class is required to resolve a workflow (received ${JSON.stringify(taskClass)}). `
|
|
430
|
+
+ `Valid classes: ${this.listClasses().join(', ')}`,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
if (!this.hasClass(taskClass)) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
`Unknown task class '${taskClass}'. Valid classes: ${this.listClasses().join(', ')}`,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
if (this.resolvedCache.has(taskClass)) {
|
|
439
|
+
return this.resolvedCache.get(taskClass);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const definition = this.classes[taskClass];
|
|
443
|
+
const gates = definition.gates.map((gate ) => ({ ...gate }));
|
|
444
|
+
|
|
445
|
+
const stages = definition.stages.map((stage , index ) => ({
|
|
446
|
+
...stage,
|
|
447
|
+
index,
|
|
448
|
+
mutatesSource: this.isImplementationKind(stage.kind),
|
|
449
|
+
kindDescription: this.stageKinds[stage.kind].description,
|
|
450
|
+
gatesAfter: gates.filter((gate ) => gate.afterStage === stage.id),
|
|
451
|
+
}));
|
|
452
|
+
|
|
453
|
+
const implementationStages = stages.filter((stage ) => stage.mutatesSource);
|
|
454
|
+
const readinessTemplates = [
|
|
455
|
+
...new Set(stages.map((stage ) => stage.readinessTemplate).filter(Boolean)),
|
|
456
|
+
];
|
|
457
|
+
|
|
458
|
+
const workflow = deepFreeze({
|
|
459
|
+
taskClass,
|
|
460
|
+
version: this.version,
|
|
461
|
+
name: definition.name,
|
|
462
|
+
description: definition.description,
|
|
463
|
+
handoff: definition.handoff || null,
|
|
464
|
+
readinessNote: definition.readinessNote,
|
|
465
|
+
stages,
|
|
466
|
+
stageIds: stages.map((stage ) => stage.id),
|
|
467
|
+
stageSkills: [...new Set(stages.map((stage ) => stage.skill))],
|
|
468
|
+
requiredStageIds: stages.filter((stage ) => !stage.optional).map((stage ) => stage.id),
|
|
469
|
+
optionalStageIds: stages.filter((stage ) => stage.optional).map((stage ) => stage.id),
|
|
470
|
+
gates,
|
|
471
|
+
gateIds: gates.map((gate ) => gate.id),
|
|
472
|
+
implementationStageIds: implementationStages.map((stage ) => stage.id),
|
|
473
|
+
hasImplementationStage: implementationStages.length > 0,
|
|
474
|
+
requiresImplementationReadiness: readinessTemplates.length > 0,
|
|
475
|
+
readinessTemplates,
|
|
476
|
+
terminalStage: stages[stages.length - 1],
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
this.resolvedCache.set(taskClass, workflow);
|
|
480
|
+
return workflow;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* @param {string} taskClass
|
|
485
|
+
* @param {string} stageId
|
|
486
|
+
* @returns {Object} the resolved stage
|
|
487
|
+
*/
|
|
488
|
+
describeStage(taskClass , stageId ) {
|
|
489
|
+
const workflow = this.resolveWorkflow(taskClass);
|
|
490
|
+
const stage = workflow.stages.find((candidate ) => candidate.id === stageId);
|
|
491
|
+
if (!stage) {
|
|
492
|
+
throw new Error(
|
|
493
|
+
`Unknown stage '${stageId}' for task class '${taskClass}'. `
|
|
494
|
+
+ `Stages: ${workflow.stageIds.join(' -> ')}`,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return stage;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* The next stage to run, given the stages already completed.
|
|
502
|
+
*
|
|
503
|
+
* An unrecognized completed-stage id throws: a caller that mistypes a stage id must not be told
|
|
504
|
+
* the workflow starts over from the beginning.
|
|
505
|
+
* @param {string} taskClass
|
|
506
|
+
* @param {Array<string>} [completedStageIds]
|
|
507
|
+
* @returns {Object|null} the next stage, or null when the workflow is exhausted
|
|
508
|
+
*/
|
|
509
|
+
nextStage(taskClass , completedStageIds = []) {
|
|
510
|
+
const workflow = this.resolveWorkflow(taskClass);
|
|
511
|
+
if (!Array.isArray(completedStageIds)) {
|
|
512
|
+
throw new TypeError('completedStageIds must be an array of stage ids');
|
|
513
|
+
}
|
|
514
|
+
const known = new Set(workflow.stageIds);
|
|
515
|
+
for (const completed of completedStageIds) {
|
|
516
|
+
if (!known.has(completed)) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
`Unknown completed stage '${completed}' for task class '${taskClass}'. `
|
|
519
|
+
+ `Stages: ${workflow.stageIds.join(' -> ')}`,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const done = new Set(completedStageIds);
|
|
524
|
+
return workflow.stages.find((stage ) => !done.has(stage.id)) || null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* @param {string} taskClass
|
|
529
|
+
* @param {string} stageId
|
|
530
|
+
* @returns {Array<Object>} gates that fire after the named stage
|
|
531
|
+
*/
|
|
532
|
+
gatesAfter(taskClass , stageId ) {
|
|
533
|
+
return this.describeStage(taskClass, stageId).gatesAfter;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Handles `doflow workflow` — resolve a task class to its ordered stages, gates and readiness
|
|
539
|
+
* templates. Prints `WorkflowEngine.resolveWorkflow`'s object verbatim.
|
|
540
|
+
*
|
|
541
|
+
* Unlike `classify`, this verb does not validate: an unknown class is something it cannot resolve,
|
|
542
|
+
* so it reports the valid set and exits 2 rather than returning an empty workflow.
|
|
543
|
+
*
|
|
544
|
+
* @param {Object} options
|
|
545
|
+
* @param {string|null} options.taskClass
|
|
546
|
+
* @param {boolean} [options.json=false]
|
|
547
|
+
* @returns {number} exit code
|
|
548
|
+
*/
|
|
549
|
+
function handleWorkflowCommand({ taskClass, json = false } = {}) {
|
|
550
|
+
const engine = new WorkflowEngine({ repoRoot: REPO_ROOT });
|
|
551
|
+
let workflow;
|
|
552
|
+
try {
|
|
553
|
+
workflow = engine.resolveWorkflow(taskClass);
|
|
554
|
+
} catch (error ) {
|
|
555
|
+
// The engine's own message already names every valid class; restating it here would be a
|
|
556
|
+
// second inventory to keep in step with the registry.
|
|
557
|
+
return usageError('workflow', error.message, json, error);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (json) { console.log(JSON.stringify(workflow, null, 2)); return finishRuntime(0); }
|
|
561
|
+
|
|
562
|
+
console.log(`\nDoFlow Workflow [${workflow.taskClass}] — ${workflow.name}:`);
|
|
563
|
+
console.log('═'.repeat(78));
|
|
564
|
+
console.log(workflow.description);
|
|
565
|
+
console.log('─'.repeat(78));
|
|
566
|
+
for (const stage of workflow.stages) {
|
|
567
|
+
const marks = [
|
|
568
|
+
stage.optional ? 'optional' : 'required',
|
|
569
|
+
stage.mutatesSource ? 'mutates source' : null,
|
|
570
|
+
stage.readinessTemplate ? `gated by ${stage.readinessTemplate}` : null,
|
|
571
|
+
].filter(Boolean).join(', ');
|
|
572
|
+
console.log(` ${String(stage.index + 1).padStart(2)}. ${stage.id.padEnd(20)} ${stage.skill || stage.kind}`);
|
|
573
|
+
console.log(` ${marks}`);
|
|
574
|
+
for (const gate of stage.gatesAfter) console.log(` ↳ gate: ${gate.id} — ${gate.description || gate.kind || ''}`);
|
|
575
|
+
}
|
|
576
|
+
console.log('─'.repeat(78));
|
|
577
|
+
console.log(`Implementation stages: ${workflow.hasImplementationStage ? workflow.implementationStageIds.join(', ') : 'none'}`);
|
|
578
|
+
console.log(`Readiness required: ${workflow.requiresImplementationReadiness ? workflow.readinessTemplates.join(', ') : 'no'}`);
|
|
579
|
+
if (workflow.readinessNote) console.log(`Note: ${workflow.readinessNote}`);
|
|
580
|
+
console.log('═'.repeat(78) + '\n');
|
|
581
|
+
return finishRuntime(0);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
module.exports = {
|
|
585
|
+
WorkflowEngine,
|
|
586
|
+
CALLER_ROLES,
|
|
587
|
+
handleWorkflowCommand,
|
|
588
|
+
};
|