@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +132 -2
- package/bin/doflow.js +44 -0
- package/core/.antigravity-plugin/hooks.json +39 -0
- package/core/.antigravity-plugin/mcp_config.json +18 -0
- package/core/.antigravity-plugin/plugin.json +31 -0
- package/core/.claude-plugin/marketplace.json +20 -0
- package/core/.claude-plugin/plugin.json +21 -0
- package/core/.codex-plugin/marketplace.json +20 -0
- package/core/.codex-plugin/plugin.json +35 -0
- package/core/.plugin/plugin.json +19 -0
- package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
- package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
- package/core/harnesses/claude/hooks/lib.sh +304 -0
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
- package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
- package/core/harnesses/claude/hooks/post-compact.sh +74 -0
- package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
- package/core/harnesses/claude/hooks/session-end.sh +89 -0
- package/core/harnesses/claude/hooks/session-start.sh +5 -0
- package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -0
- package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
- package/core/harnesses/claude/settings/keybindings.json +20 -0
- package/core/harnesses/claude/settings/settings.json +201 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
- package/core/harnesses/codex/agents/core-implementer.toml +7 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
- package/core/harnesses/codex/agents/research-writer.toml +8 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
- package/core/harnesses/codex/agents/system-architect.toml +9 -0
- package/core/harnesses/codex/config/config.toml +10 -0
- package/core/harnesses/codex/hooks/hooks.json +122 -0
- package/core/harnesses/codex/hooks/lib.sh +335 -0
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
- package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
- package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
- package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
- package/core/harnesses/codex/hooks/session-end.sh +5 -0
- package/core/harnesses/codex/hooks/session-start.sh +5 -0
- package/core/harnesses/codex/hooks/stop-check.sh +8 -0
- package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
- package/core/harnesses/gemini/hooks/hooks.json +77 -0
- package/core/harnesses/gemini/hooks/lib.sh +302 -0
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
- package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
- package/core/harnesses/gemini/hooks/session-end.sh +5 -0
- package/core/harnesses/gemini/hooks/session-start.sh +5 -0
- package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
- package/core/harnesses/kiro/agents/doflow.json +58 -0
- package/core/harnesses/kiro/hooks/doflow.json +53 -0
- package/core/harnesses/kiro/hooks/lib.sh +324 -0
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
- package/core/harnesses/kiro/hooks/session-start.sh +9 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
- package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
- package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
- package/core/harnesses/shared/hooks/hooks.json +47 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
- package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
- package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
- package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
- package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
- package/core/harnesses/shared/locator/doflow-run +87 -0
- package/core/registry/advice.json +101 -0
- package/core/registry/assets.json +792 -0
- package/core/registry/capabilities.json +120 -0
- package/core/registry/contracts.json +189 -0
- package/core/registry/external-tools.json +191 -0
- package/core/registry/harnesses.json +2062 -0
- package/core/registry/lifecycle.json +231 -0
- package/core/registry/mcp.json +18 -0
- package/core/registry/models.json +79 -0
- package/core/registry/readiness-templates.json +118 -0
- package/core/registry/routes.json +47 -0
- package/core/registry/storage.json +1057 -0
- package/core/registry/verification.json +258 -0
- package/core/registry/workflows.json +790 -0
- package/core/shared/agent-specs/core-implementer.md +73 -0
- package/core/shared/agent-specs/quality-guardian.md +28 -0
- package/core/shared/agent-specs/research-writer.md +31 -0
- package/core/shared/agent-specs/spec-analyst.md +33 -0
- package/core/shared/agent-specs/system-architect.md +29 -0
- package/core/shared/guidance/DOFLOW_CORE.md +8 -0
- package/core/shared/guidance/FLAGS.md +25 -0
- package/core/shared/guidance/PRINCIPLES.md +61 -0
- package/core/shared/guidance/VERSION +1 -0
- package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
- package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
- package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
- package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
- package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
- package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
- package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
- package/core/shared/guidance/pointers/claude-gemini.md +2 -0
- package/core/shared/guidance/pointers/codex.md +6 -0
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
- package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
- package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
- package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
- package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
- package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
- package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
- package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
- package/core/shared/scripts/doflow/bin/doflow-run +592 -0
- package/core/shared/skills/do/SKILL.md +128 -0
- package/core/shared/skills/do/references/estimation.md +13 -0
- package/core/shared/skills/do/references/pm_routing.md +25 -0
- package/core/shared/skills/do/references/tool_matrix.md +52 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
- package/core/shared/skills/do-code-review/README.md +49 -0
- package/core/shared/skills/do-code-review/SKILL.md +333 -0
- package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
- package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
- package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
- package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
- package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
- package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
- package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
- package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
- package/core/shared/skills/do-code-review/content-types/config.md +83 -0
- package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
- package/core/shared/skills/do-code-review/languages/c.md +103 -0
- package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
- package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
- package/core/shared/skills/do-code-review/languages/dart.md +108 -0
- package/core/shared/skills/do-code-review/languages/go.md +92 -0
- package/core/shared/skills/do-code-review/languages/java.md +103 -0
- package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
- package/core/shared/skills/do-code-review/languages/php.md +109 -0
- package/core/shared/skills/do-code-review/languages/python.md +94 -0
- package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
- package/core/shared/skills/do-code-review/languages/rust.md +99 -0
- package/core/shared/skills/do-code-review/languages/shell.md +90 -0
- package/core/shared/skills/do-code-review/languages/swift.md +88 -0
- package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
- package/core/shared/skills/do-code-review/review-policy.json +47 -0
- package/core/shared/skills/do-code-review/rules/universal.md +49 -0
- package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
- package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
- package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
- package/core/shared/skills/do-constitution/SKILL.md +72 -0
- package/core/shared/skills/do-design/SKILL.md +212 -0
- package/core/shared/skills/do-diagnose/SKILL.md +156 -0
- package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
- package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
- package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
- package/core/shared/skills/do-document/SKILL.md +81 -0
- package/core/shared/skills/do-document/references/api-reference.md +42 -0
- package/core/shared/skills/do-document/references/deep_research.md +18 -0
- package/core/shared/skills/do-document/references/embedded-research.md +11 -0
- package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
- package/core/shared/skills/do-document/references/index_generator.md +9 -0
- package/core/shared/skills/do-document/references/user-guide.md +22 -0
- package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
- package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
- package/core/shared/skills/do-flow/SKILL.md +186 -0
- package/core/shared/skills/do-flow/references/task_classes.md +134 -0
- package/core/shared/skills/do-git/SKILL.md +115 -0
- package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
- package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
- package/core/shared/skills/do-git/references/release-ritual.md +64 -0
- package/core/shared/skills/do-implement/SKILL.md +222 -0
- package/core/shared/skills/do-plan/SKILL.md +219 -0
- package/core/shared/skills/do-test/SKILL.md +113 -0
- package/core/shared/templates/doflow/constitution-template.md +28 -0
- package/core/shared/templates/doflow/data-model-template.md +169 -0
- package/core/shared/templates/doflow/design-template.md +193 -0
- package/core/shared/templates/doflow/external-contract-template.md +35 -0
- package/core/shared/templates/doflow/intent-template.md +45 -0
- package/core/shared/templates/doflow/plan-template.md +151 -0
- package/core/shared/templates/doflow/question-log-template.md +51 -0
- package/core/shared/templates/doflow/requirement-template.md +126 -0
- package/core/shared/templates/doflow/specs-template.md +55 -0
- package/core/shared/templates/doflow/state-template.md +59 -0
- package/core/shared/workflows/do-flow-chain.md +17 -0
- package/package.json +46 -4
- package/src/adapters/antigravity/index.ts +631 -0
- package/src/adapters/claude/index.ts +548 -0
- package/src/adapters/claude/mcp.ts +94 -0
- package/src/adapters/codex/agents.ts +208 -0
- package/src/adapters/codex/config.ts +272 -0
- package/src/adapters/codex/hooks.ts +203 -0
- package/src/adapters/codex/index.ts +707 -0
- package/src/adapters/codex/mcp.ts +271 -0
- package/src/adapters/codex/native-projection.ts +34 -0
- package/src/adapters/copilot/index.ts +413 -0
- package/src/adapters/copy-tree.ts +620 -0
- package/src/adapters/gemini/hooks.ts +210 -0
- package/src/adapters/gemini/index.ts +390 -0
- package/src/adapters/hook-commands.ts +60 -0
- package/src/adapters/index.ts +106 -0
- package/src/adapters/kiro/index.ts +310 -0
- package/src/adapters/mcp-entries.ts +331 -0
- package/src/adapters/opencode/index.ts +466 -0
- package/src/adapters/pi/index.ts +279 -0
- package/src/adapters/pi/json-members.ts +185 -0
- package/src/adapters/pi/mcp.ts +373 -0
- package/src/adapters/resolve-source.ts +19 -0
- package/src/cli/commands/install.ts +218 -0
- package/src/cli/commands/list-backups.ts +21 -0
- package/src/cli/commands/reconcile.ts +120 -0
- package/src/cli/commands/remove.ts +82 -0
- package/src/cli/commands/rollback.ts +172 -0
- package/src/cli/commands/self-update.ts +33 -0
- package/src/cli/commands/status.ts +138 -0
- package/src/cli/commands/tools.ts +92 -0
- package/src/cli/commands/update.ts +162 -0
- package/src/cli/index.ts +568 -0
- package/src/cli/runtime-commands.ts +281 -0
- package/src/cli/shared.ts +218 -0
- package/src/helper/bash-helper.ts +34 -0
- package/src/helper/git.ts +24 -0
- package/src/helper/guardrails.ts +49 -0
- package/src/helper/harness-paths.ts +140 -0
- package/src/helper/marker-merge.ts +116 -0
- package/src/helper/prompt.ts +61 -0
- package/src/helper/repo-root.ts +29 -0
- package/src/helper/settings-merge.ts +151 -0
- package/src/helper/settings-scope.ts +33 -0
- package/src/helper/toml.ts +244 -0
- package/src/install/backup-set.ts +149 -0
- package/src/install/backup.ts +724 -0
- package/src/install/context.ts +58 -0
- package/src/install/manifest.ts +145 -0
- package/src/install/mcp.ts +254 -0
- package/src/install/paths.ts +84 -0
- package/src/install/targets.ts +90 -0
- package/src/install/tool-lifecycle.ts +151 -0
- package/src/lifecycle/bash-availability.ts +25 -0
- package/src/lifecycle/index.ts +812 -0
- package/src/lifecycle/mcp-index.ts +28 -0
- package/src/lifecycle/policies.ts +73 -0
- package/src/lifecycle/view.ts +175 -0
- package/src/registry/index.ts +611 -0
- package/src/release/sync-plugin-versions.ts +66 -0
- package/src/runtime/advice/cli.ts +283 -0
- package/src/runtime/advice/client.ts +324 -0
- package/src/runtime/advice/config.ts +189 -0
- package/src/runtime/advice/doctor.ts +169 -0
- package/src/runtime/advice/endpoint-guard.ts +134 -0
- package/src/runtime/advice/kinds.ts +154 -0
- package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
- package/src/runtime/advice/record.ts +166 -0
- package/src/runtime/advice/registry.ts +170 -0
- package/src/runtime/advice/strength.ts +66 -0
- package/src/runtime/brief-tools.ts +342 -0
- package/src/runtime/c4-project.ts +371 -0
- package/src/runtime/capability-router.ts +484 -0
- package/src/runtime/checkouts.ts +170 -0
- package/src/runtime/claims.ts +468 -0
- package/src/runtime/cli-result.ts +55 -0
- package/src/runtime/cli.ts +678 -0
- package/src/runtime/command-detect.ts +516 -0
- package/src/runtime/context-pack.ts +492 -0
- package/src/runtime/decision-register.ts +493 -0
- package/src/runtime/evidence-ledger.ts +338 -0
- package/src/runtime/failure/capture.ts +272 -0
- package/src/runtime/failure/classifier.ts +68 -0
- package/src/runtime/failure/cli.ts +178 -0
- package/src/runtime/failure/home.ts +64 -0
- package/src/runtime/failure/store.ts +226 -0
- package/src/runtime/feature-resolve.ts +79 -0
- package/src/runtime/freshness.ts +177 -0
- package/src/runtime/health.ts +863 -0
- package/src/runtime/history-compactor.ts +237 -0
- package/src/runtime/implementation-gate.ts +321 -0
- package/src/runtime/inventory/coverage.ts +218 -0
- package/src/runtime/inventory/identity.ts +65 -0
- package/src/runtime/inventory/index.ts +705 -0
- package/src/runtime/inventory/precedence.ts +134 -0
- package/src/runtime/inventory/read-scopes.ts +131 -0
- package/src/runtime/inventory/siblings.ts +192 -0
- package/src/runtime/knowledge/chunker.ts +124 -0
- package/src/runtime/knowledge/hybrid.ts +149 -0
- package/src/runtime/knowledge/index-store.ts +145 -0
- package/src/runtime/knowledge/retrieval.ts +194 -0
- package/src/runtime/leak-scan.ts +185 -0
- package/src/runtime/lifecycle/cli.ts +276 -0
- package/src/runtime/lifecycle/event-store.ts +340 -0
- package/src/runtime/lifecycle/fold.ts +353 -0
- package/src/runtime/lifecycle/followup.ts +432 -0
- package/src/runtime/lifecycle/goal.ts +249 -0
- package/src/runtime/lifecycle/intent-writer.ts +86 -0
- package/src/runtime/lifecycle/overview.ts +292 -0
- package/src/runtime/lifecycle/release.ts +241 -0
- package/src/runtime/lifecycle/report-store.ts +221 -0
- package/src/runtime/lifecycle/retention.ts +212 -0
- package/src/runtime/lifecycle/root.ts +71 -0
- package/src/runtime/lifecycle/status.ts +353 -0
- package/src/runtime/lifecycle/store-upkeep.ts +393 -0
- package/src/runtime/locator-resolve.ts +158 -0
- package/src/runtime/mask.ts +210 -0
- package/src/runtime/mcp-consultations.ts +412 -0
- package/src/runtime/mcp-reach.ts +157 -0
- package/src/runtime/model-router.ts +153 -0
- package/src/runtime/outcome.ts +467 -0
- package/src/runtime/reach.ts +148 -0
- package/src/runtime/readiness-record.ts +150 -0
- package/src/runtime/readiness.ts +435 -0
- package/src/runtime/recovery.ts +282 -0
- package/src/runtime/research-request.ts +231 -0
- package/src/runtime/retrieval-plan.ts +578 -0
- package/src/runtime/scaffold/artifacts.ts +217 -0
- package/src/runtime/scaffold/fingerprint.ts +56 -0
- package/src/runtime/scaffold/generate.ts +911 -0
- package/src/runtime/scaffold/languages.ts +245 -0
- package/src/runtime/storage/cli.ts +64 -0
- package/src/runtime/storage/engine.ts +249 -0
- package/src/runtime/storage/registry.ts +130 -0
- package/src/runtime/storage/roots.ts +63 -0
- package/src/runtime/storage/rules.ts +282 -0
- package/src/runtime/storage/upkeep.ts +243 -0
- package/src/runtime/storage/work-status.ts +326 -0
- package/src/runtime/task-attempts.ts +415 -0
- package/src/runtime/task-classifier.ts +550 -0
- package/src/runtime/task-scope.ts +130 -0
- package/src/runtime/task-state.ts +176 -0
- package/src/runtime/trace/indicators.ts +235 -0
- package/src/runtime/trace/ledger.ts +626 -0
- package/src/runtime/trace/render.ts +76 -0
- package/src/runtime/trace/views.ts +459 -0
- package/src/runtime/verification/contract-runner.ts +341 -0
- package/src/runtime/verification/engine.ts +1461 -0
- package/src/runtime/verification/registry.ts +130 -0
- package/src/runtime/verification/scope-bound.ts +418 -0
- package/src/runtime/verification/test-selection.ts +172 -0
- package/src/runtime/workflow-engine.ts +588 -0
- package/src/runtime/workflow-orchestrator.ts +953 -0
- package/src/runtime/worktree.ts +340 -0
- package/src/state/index.ts +193 -0
- package/src/state/lockfile.ts +136 -0
- package/src/state/run-lock.ts +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
// Task-attempt ledger (feature 070, roadmap P2A first slice).
|
|
6
|
+
//
|
|
7
|
+
// `do-execute-plan` used to decide a plan task was done from a `[x]` checkbox and a `complete` row
|
|
8
|
+
// in state.md, both written by the agent that did the work. This module is the record that does
|
|
9
|
+
// not rely on either: one active attempt per plan task, a monotonic fencing token per attempt, and
|
|
10
|
+
// a COMPLETED result only against a passing, fresh, task-bound `test-result` from the evidence
|
|
11
|
+
// ledger.
|
|
12
|
+
//
|
|
13
|
+
// What is stored is attempts only. Task structure (ids, phases, the line each task was claimed
|
|
14
|
+
// against) is read from plan.md by `parsePlan` on every call, so the ledger cannot drift from the
|
|
15
|
+
// plan it describes. Persistence goes through `updateTaskState` (lock, re-read, atomic rename), and
|
|
16
|
+
// every decision below is made INSIDE that lock against what is on disk, which is what makes two
|
|
17
|
+
// processes claiming one task yield exactly one `claimed`.
|
|
18
|
+
//
|
|
19
|
+
// The store lives under the clone's main worktree (the lifecycle project root), so every linked
|
|
20
|
+
// worktree of one clone reads and writes the same file (FR-007).
|
|
21
|
+
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const crypto = require('node:crypto');
|
|
25
|
+
const { updateTaskState, readTaskState } = require('./task-state.ts');
|
|
26
|
+
const { parsePlan } = require('./scaffold/artifacts.ts');
|
|
27
|
+
const { EvidenceLedger } = require('./evidence-ledger.ts');
|
|
28
|
+
const { projectRoot: lifecycleProjectRoot } = require('./lifecycle/root.ts');
|
|
29
|
+
const { isSafeSlug } = require('./task-scope.ts');
|
|
30
|
+
|
|
31
|
+
const SCHEMA_VERSION = 1;
|
|
32
|
+
const MODES = Object.freeze(['manual', 'sequential', 'subagent']);
|
|
33
|
+
const RESULTS = Object.freeze(['COMPLETED', 'BLOCKED', 'ABANDONED', 'INCONCLUSIVE']);
|
|
34
|
+
const ACTIONS = Object.freeze(['claim', 'finish', 'release', 'status']);
|
|
35
|
+
const TASK_ID = /^[A-Za-z]+\.\d+$/;
|
|
36
|
+
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._ -]*$/;
|
|
37
|
+
|
|
38
|
+
/** A failure that is the caller's or the environment's, not a finding: exit 2. */
|
|
39
|
+
class TaskAttemptError extends Error {
|
|
40
|
+
declare code: any;
|
|
41
|
+
constructor(code: any, message: any) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = 'TaskAttemptError';
|
|
44
|
+
this.code = code;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Thrown inside the locked build to abandon a write without touching the store. */
|
|
49
|
+
class NoWrite extends Error {}
|
|
50
|
+
|
|
51
|
+
function storeFile(root: any, slug: any) {
|
|
52
|
+
return path.join(root, '.doflow', 'state', 'task-attempts', `${slug}.json`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function uniq(values: any) { return [...new Set(values)]; }
|
|
56
|
+
|
|
57
|
+
function assertSlug(slug: any) {
|
|
58
|
+
if (!isSafeSlug(slug)) throw new TaskAttemptError('usage', `'${slug}' is not a valid feature slug`);
|
|
59
|
+
return slug;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── plan.md ────────────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/** The hash a claim records and `finish` compares: everything about the task's plan line except
|
|
65
|
+
* its checkbox, so ticking the box after a finish is not an edit of the task. */
|
|
66
|
+
function taskLineHash(task: any) {
|
|
67
|
+
const material = JSON.stringify([
|
|
68
|
+
task.id, task.description, task.stories, task.owner, task.files, task.dependsOn, task.externalContract,
|
|
69
|
+
]);
|
|
70
|
+
return `sha256:${crypto.createHash('sha256').update(material).digest('hex')}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The plan's tasks, in plan order, with phase order taken from first appearance.
|
|
75
|
+
* @returns {{planPath: string, tasks: Array<{id: string, phase: string, phaseIndex: number, checked: boolean, hash: string}>}}
|
|
76
|
+
*/
|
|
77
|
+
function loadPlan({ roots, slug, fsImpl = fs }: any) {
|
|
78
|
+
const relative = path.join('agent-docs', 'doflow', slug, 'plan.md');
|
|
79
|
+
const found = roots.map((root: any) => path.join(root, relative)).find((file: any) => fsImpl.existsSync(file));
|
|
80
|
+
if (!found) throw new TaskAttemptError('plan-unreadable', `no plan.md for feature '${slug}' (looked for ${relative} under ${roots.join(', ')})`);
|
|
81
|
+
let text;
|
|
82
|
+
try { text = fsImpl.readFileSync(found, 'utf8'); } catch (error: any) {
|
|
83
|
+
throw new TaskAttemptError('plan-unreadable', `cannot read ${found}: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
const parsed = parsePlan(text);
|
|
86
|
+
if (parsed.tasks.length === 0) {
|
|
87
|
+
throw new TaskAttemptError('plan-malformed', `${found} holds no \`- [ ] <phase>.<n>\` tasks: ${parsed.gaps.map((g: any) => g.why).join('; ') || 'nothing to track'}`);
|
|
88
|
+
}
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
for (const task of parsed.tasks) {
|
|
91
|
+
if (seen.has(task.id)) throw new TaskAttemptError('plan-malformed', `plan task id '${task.id}' appears more than once in ${found}`);
|
|
92
|
+
seen.add(task.id);
|
|
93
|
+
}
|
|
94
|
+
const phases = uniq(parsed.tasks.map((t: any) => t.phase));
|
|
95
|
+
return {
|
|
96
|
+
planPath: relative.split(path.sep).join('/'),
|
|
97
|
+
tasks: parsed.tasks.map((t: any) => ({
|
|
98
|
+
id: t.id, phase: t.phase, phaseIndex: phases.indexOf(t.phase), checked: t.done, hash: taskLineHash(t),
|
|
99
|
+
})),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── store ──────────────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
function unreadable(file: any, why: any) {
|
|
106
|
+
return new TaskAttemptError('store-unreadable', `${why} Refusing to read or write '${file}'; it was left unchanged.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The store as an object, or a fresh empty one when the file does not exist. Shape-checked: a file
|
|
110
|
+
* that parses but is not a ledger is as unusable as one that does not parse. */
|
|
111
|
+
function normalizeStore(data: any, file: any) {
|
|
112
|
+
if (data === null || data === undefined) return { version: SCHEMA_VERSION, revision: 0, planPath: null, tasks: {} };
|
|
113
|
+
if (typeof data !== 'object' || Array.isArray(data) || data.version !== SCHEMA_VERSION
|
|
114
|
+
|| typeof data.tasks !== 'object' || data.tasks === null || Array.isArray(data.tasks)) {
|
|
115
|
+
throw unreadable(file, 'It is not a version-1 task-attempt ledger.');
|
|
116
|
+
}
|
|
117
|
+
for (const [id, entry] of (Object.entries(data.tasks) as [string, any][])) {
|
|
118
|
+
if (!entry || !Number.isInteger(entry.nextToken) || !Array.isArray(entry.attempts)) {
|
|
119
|
+
throw unreadable(file, `Task '${id}' has no readable attempt list.`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return data;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function readStore(file: any, fsImpl: any) {
|
|
126
|
+
try {
|
|
127
|
+
return normalizeStore(readTaskState(fsImpl, file), file);
|
|
128
|
+
} catch (error: any) {
|
|
129
|
+
if (error instanceof TaskAttemptError) throw error;
|
|
130
|
+
throw unreadable(file, error.message);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** One locked read-decide-write. `apply(store)` returns `{result, write}`; with `write` false the
|
|
135
|
+
* store is not touched, which is how a refusal or a stale token changes nothing. */
|
|
136
|
+
function mutate({ file, fsImpl, apply }: any) {
|
|
137
|
+
let outcome: any = null;
|
|
138
|
+
try {
|
|
139
|
+
updateTaskState({
|
|
140
|
+
fsImpl, file,
|
|
141
|
+
build: (disk: any) => {
|
|
142
|
+
const store = normalizeStore(disk, file);
|
|
143
|
+
store.revision = (Number.isInteger(store.revision) ? store.revision : 0) + 1;
|
|
144
|
+
outcome = apply(store);
|
|
145
|
+
if (!outcome.write) throw new NoWrite();
|
|
146
|
+
return store;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
} catch (error: any) {
|
|
150
|
+
if (error instanceof NoWrite) return outcome.result;
|
|
151
|
+
if (error instanceof TaskAttemptError) throw error;
|
|
152
|
+
throw unreadable(file, error.message);
|
|
153
|
+
}
|
|
154
|
+
return outcome.result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function entryFor(store: any, taskId: any) {
|
|
158
|
+
if (!store.tasks[taskId]) store.tasks[taskId] = { nextToken: 1, attempts: [] };
|
|
159
|
+
return store.tasks[taskId];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const activeAttempt = (entry: any) => (entry ? entry.attempts.find((a: any) => a.state === 'active') || null : null);
|
|
163
|
+
const doneAttempt = (entry: any) => (entry ? entry.attempts.find((a: any) => a.state === 'finished' && a.result === 'COMPLETED') || null : null);
|
|
164
|
+
|
|
165
|
+
function latestFinished(entry: any) {
|
|
166
|
+
const finished = entry ? entry.attempts.filter((a: any) => a.state === 'finished') : [];
|
|
167
|
+
return finished.length ? finished.reduce((a: any, b: any) => (b.token > a.token ? b : a)) : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── evidence ───────────────────────────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/** Evidence ids recorded in any task file under the evidence store, to tell "belongs to another task"
|
|
173
|
+
* from "does not exist". Reads only; a file that cannot be read simply does not match. */
|
|
174
|
+
function evidenceBelongsElsewhere({ roots, slug, evidenceId, fsImpl }: any) {
|
|
175
|
+
for (const root of roots) {
|
|
176
|
+
const base = path.join(root, '.doflow', 'state', 'evidence');
|
|
177
|
+
for (const dir of [base, path.join(base, slug)]) {
|
|
178
|
+
let names = [];
|
|
179
|
+
try { names = fsImpl.readdirSync(dir); } catch { continue; }
|
|
180
|
+
for (const name of names) {
|
|
181
|
+
if (!name.endsWith('.json')) continue;
|
|
182
|
+
try {
|
|
183
|
+
const data = JSON.parse(fsImpl.readFileSync(path.join(dir, name), 'utf8'));
|
|
184
|
+
if (Array.isArray(data.evidence) && data.evidence.some((item: any) => item && item.id === evidenceId)) return true;
|
|
185
|
+
} catch { /* unreadable files do not match */ }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The refusal reason for `evidenceId` as proof of a COMPLETED attempt, or null when it qualifies (FR-004). */
|
|
193
|
+
function evidenceRefusal({ evidenceId, slug, taskId, startedAt, roots, fsImpl }: any) {
|
|
194
|
+
if (!evidenceId) return 'evidence-required';
|
|
195
|
+
const allowed = [slug, taskId];
|
|
196
|
+
let item = null;
|
|
197
|
+
for (const root of roots) {
|
|
198
|
+
for (const owner of allowed) {
|
|
199
|
+
const ledger = new EvidenceLedger({ repoRoot: root, slug, fsImpl });
|
|
200
|
+
try { ledger.load(owner); } catch { return 'evidence-unreadable'; }
|
|
201
|
+
item = ledger.getEvidence(evidenceId);
|
|
202
|
+
if (item) break;
|
|
203
|
+
}
|
|
204
|
+
if (item) break;
|
|
205
|
+
}
|
|
206
|
+
if (!item) return evidenceBelongsElsewhere({ roots, slug, evidenceId, fsImpl }) ? 'evidence-wrong-task' : 'evidence-not-found';
|
|
207
|
+
if (!allowed.includes(item.taskId)) return 'evidence-wrong-task';
|
|
208
|
+
if (item.status !== 'active') return 'evidence-superseded';
|
|
209
|
+
if (item.kind !== 'test-result') return 'evidence-wrong-kind';
|
|
210
|
+
if (!item.observation || item.observation.exitCode !== 0) return 'evidence-not-passing';
|
|
211
|
+
if (item.freshness?.status === 'STALE') return 'evidence-stale';
|
|
212
|
+
const observed = Date.parse(item.freshness?.observedAt);
|
|
213
|
+
if (!Number.isFinite(observed) || observed < Date.parse(startedAt)) return 'evidence-before-attempt';
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── actions ────────────────────────────────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
function rootsFor({ cwd, lifecycleRoot }: any) {
|
|
220
|
+
return uniq([path.resolve(cwd), lifecycleRoot]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function claim({ file, fsImpl, roots, slug, task, owner, mode, now = new Date() }: any) {
|
|
224
|
+
if (!TASK_ID.test(task || '')) throw new TaskAttemptError('usage', '--task is required: a plan task id such as A.1');
|
|
225
|
+
if (!owner || !SAFE_NAME.test(owner)) throw new TaskAttemptError('usage', '--owner is required: the name of the agent or person taking the task');
|
|
226
|
+
if (!MODES.includes(mode)) throw new TaskAttemptError('usage', `--mode is required: one of ${MODES.join(', ')}`);
|
|
227
|
+
const plan = loadPlan({ roots, slug, fsImpl });
|
|
228
|
+
return mutate({
|
|
229
|
+
file, fsImpl,
|
|
230
|
+
apply: (store: any) => {
|
|
231
|
+
const refuse = (reason: any, extra: any = {}) => ({ write: false, result: { disposition: 'refused', taskId: task, reason, ...extra } });
|
|
232
|
+
const target = plan.tasks.find((t: any) => t.id === task);
|
|
233
|
+
if (!target) return refuse('unknown-task');
|
|
234
|
+
if (doneAttempt(store.tasks[task])) return refuse('already-done');
|
|
235
|
+
const active = activeAttempt(store.tasks[task]);
|
|
236
|
+
if (active) return refuse('active-attempt', { activeToken: active.token, owner: active.owner, startedAt: active.startedAt });
|
|
237
|
+
const unmet = plan.tasks.filter((t: any) => t.phaseIndex < target.phaseIndex && !doneAttempt(store.tasks[t.id])).map((t: any) => t.id);
|
|
238
|
+
if (unmet.length) return refuse('unmet-phase', { unmet });
|
|
239
|
+
const entry = entryFor(store, task);
|
|
240
|
+
const token = entry.nextToken;
|
|
241
|
+
entry.nextToken += 1;
|
|
242
|
+
entry.attempts.push({
|
|
243
|
+
token, owner, mode, startedAt: now.toISOString(), taskLineHash: target.hash,
|
|
244
|
+
state: 'active', result: null, evidenceId: null, reason: null,
|
|
245
|
+
});
|
|
246
|
+
store.planPath = plan.planPath;
|
|
247
|
+
return { write: true, result: { disposition: 'claimed', taskId: task, token, revision: store.revision } };
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function stale(taskId: any, token: any, reason: any) {
|
|
253
|
+
return { write: false, result: { disposition: 'stale', taskId, token, reason } };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function finish({ file, fsImpl, roots, slug, task, token, result, evidenceId }: any) {
|
|
257
|
+
if (!TASK_ID.test(task || '')) throw new TaskAttemptError('usage', '--task is required: a plan task id such as A.1');
|
|
258
|
+
if (!Number.isInteger(token) || token < 1) throw new TaskAttemptError('usage', '--token is required: the token claim returned');
|
|
259
|
+
if (!RESULTS.includes(result)) throw new TaskAttemptError('usage', `--result is required: one of ${RESULTS.join(', ')}`);
|
|
260
|
+
return mutate({
|
|
261
|
+
file, fsImpl,
|
|
262
|
+
apply: (store: any) => {
|
|
263
|
+
const active = activeAttempt(store.tasks[task]);
|
|
264
|
+
if (!active) return stale(task, token, 'no-active-attempt');
|
|
265
|
+
if (active.token !== token) return stale(task, token, 'token-mismatch');
|
|
266
|
+
const refuse = (reason: any) => ({ write: false, result: { disposition: 'refused', taskId: task, token, reason } });
|
|
267
|
+
const plan = loadPlan({ roots, slug, fsImpl });
|
|
268
|
+
const line = plan.tasks.find((t: any) => t.id === task);
|
|
269
|
+
if (!line) return refuse('unknown-task');
|
|
270
|
+
if (line.hash !== active.taskLineHash) return refuse('plan-changed');
|
|
271
|
+
if (result === 'COMPLETED') {
|
|
272
|
+
const why = evidenceRefusal({ evidenceId, slug, taskId: task, startedAt: active.startedAt, roots, fsImpl });
|
|
273
|
+
if (why) return refuse(why);
|
|
274
|
+
}
|
|
275
|
+
active.state = 'finished';
|
|
276
|
+
active.result = result;
|
|
277
|
+
active.evidenceId = evidenceId || null;
|
|
278
|
+
return { write: true, result: { disposition: 'applied', taskId: task, token, result, revision: store.revision } };
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function release({ file, fsImpl, task, token, reason }: any) {
|
|
284
|
+
if (!TASK_ID.test(task || '')) throw new TaskAttemptError('usage', '--task is required: a plan task id such as A.1');
|
|
285
|
+
if (!Number.isInteger(token) || token < 1) throw new TaskAttemptError('usage', '--token is required: the token claim returned');
|
|
286
|
+
if (!reason || !String(reason).trim()) throw new TaskAttemptError('usage', '--reason is required: why the attempt is being released');
|
|
287
|
+
return mutate({
|
|
288
|
+
file, fsImpl,
|
|
289
|
+
apply: (store: any) => {
|
|
290
|
+
const active = activeAttempt(store.tasks[task]);
|
|
291
|
+
if (!active) return stale(task, token, 'no-active-attempt');
|
|
292
|
+
if (active.token !== token) return stale(task, token, 'token-mismatch');
|
|
293
|
+
active.state = 'released';
|
|
294
|
+
active.reason = String(reason).trim();
|
|
295
|
+
return { write: true, result: { disposition: 'released', taskId: task, token, revision: store.revision } };
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Completion read from the ledger and the plan, never from checkboxes (FR-005). */
|
|
301
|
+
function status({ file, fsImpl, roots, slug }: any) {
|
|
302
|
+
const plan = loadPlan({ roots, slug, fsImpl });
|
|
303
|
+
const store = readStore(file, fsImpl);
|
|
304
|
+
const tasks = plan.tasks.map((t: any) => {
|
|
305
|
+
const entry = store.tasks[t.id];
|
|
306
|
+
const active = activeAttempt(entry);
|
|
307
|
+
const done = doneAttempt(entry);
|
|
308
|
+
const last = latestFinished(entry);
|
|
309
|
+
let derived;
|
|
310
|
+
if (done) derived = 'done';
|
|
311
|
+
else if (active) derived = 'running';
|
|
312
|
+
else if (last && last.result === 'BLOCKED') derived = 'blocked';
|
|
313
|
+
else if (plan.tasks.every((o: any) => o.phaseIndex >= t.phaseIndex || doneAttempt(store.tasks[o.id]))) derived = 'ready';
|
|
314
|
+
else derived = 'pending';
|
|
315
|
+
return {
|
|
316
|
+
id: t.id, phase: t.phase, derived,
|
|
317
|
+
activeToken: active ? active.token : null,
|
|
318
|
+
doneToken: done ? done.token : null,
|
|
319
|
+
active: active ? { owner: active.owner, mode: active.mode, startedAt: active.startedAt } : null,
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
return {
|
|
323
|
+
complete: tasks.length > 0 && tasks.every((t: any) => t.derived === 'done'),
|
|
324
|
+
revision: store.revision,
|
|
325
|
+
tasks,
|
|
326
|
+
unclaimedChecked: plan.tasks.filter((t: any) => t.checked && !doneAttempt(store.tasks[t.id])).map((t: any) => t.id),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* The orchestrator's question (FR-006): does this feature have a ledger, and is it complete?
|
|
332
|
+
* `none` when no store exists (a feature that predates the ledger); `complete` or `incomplete`
|
|
333
|
+
* otherwise. A store or plan that cannot be read throws, so the gate refuses rather than guessing.
|
|
334
|
+
* @returns {{state: 'none'|'complete'|'incomplete', slug: string|null, incomplete: Array<string>}}
|
|
335
|
+
*/
|
|
336
|
+
function checkCompletion({ slug, cwd, fsImpl = fs }: any) {
|
|
337
|
+
if (!isSafeSlug(slug)) return { state: 'none', slug: null, incomplete: [] };
|
|
338
|
+
const lifecycleRoot = lifecycleProjectRoot(cwd);
|
|
339
|
+
const file = storeFile(lifecycleRoot, slug);
|
|
340
|
+
if (!fsImpl.existsSync(file)) return { state: 'none', slug, incomplete: [] };
|
|
341
|
+
const answer = status({ file, fsImpl, roots: rootsFor({ cwd, lifecycleRoot }), slug });
|
|
342
|
+
const incomplete = answer.tasks.filter((t: any) => t.derived !== 'done').map((t: any) => t.id);
|
|
343
|
+
return { state: answer.complete ? 'complete' : 'incomplete', slug, incomplete };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── CLI ────────────────────────────────────────────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
function failure(code: any, message: any, json: any) {
|
|
349
|
+
const { finishRuntime } = require('./cli-result.ts');
|
|
350
|
+
if (json) console.log(JSON.stringify({ ok: false, error: code, message }, null, 2));
|
|
351
|
+
else console.error(`doflow task-attempt: ${message}`);
|
|
352
|
+
return finishRuntime(2);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function describe(result: any) {
|
|
356
|
+
const parts = [`${result.disposition}`];
|
|
357
|
+
if (result.taskId) parts.push(`task ${result.taskId}`);
|
|
358
|
+
if (result.token !== undefined) parts.push(`token ${result.token}`);
|
|
359
|
+
if (result.reason) parts.push(`(${result.reason})`);
|
|
360
|
+
return parts.join(' ');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* `doflow task-attempt --action claim|finish|release|status --slug <slug>`. Exit 0 applied, 1 refused
|
|
365
|
+
* or stale, 2 malformed input or an unreadable store.
|
|
366
|
+
* @param {Object} options
|
|
367
|
+
* @param {string} options.cwd the invoking checkout
|
|
368
|
+
* @param {(cwd: string) => string|null} [options.resolveSlug] fallback when `slug` is not given
|
|
369
|
+
*/
|
|
370
|
+
function handleTaskAttemptCommand({
|
|
371
|
+
action = 'status', slug = null, task, owner, mode, token, result, evidenceId, reason, json = false,
|
|
372
|
+
cwd = process.cwd(), resolveSlug = null, fsImpl = fs, now = new Date(),
|
|
373
|
+
}: any = {}) {
|
|
374
|
+
const { finishRuntime } = require('./cli-result.ts');
|
|
375
|
+
try {
|
|
376
|
+
if (!ACTIONS.includes(action)) throw new TaskAttemptError('usage', `unknown action '${action}'; valid: ${ACTIONS.join(' | ')}`);
|
|
377
|
+
const feature = slug || (resolveSlug ? resolveSlug(cwd) : null);
|
|
378
|
+
if (!feature) throw new TaskAttemptError('usage', '--slug is required: no feature could be resolved from the current branch');
|
|
379
|
+
assertSlug(feature);
|
|
380
|
+
let tokenNumber;
|
|
381
|
+
if (token !== undefined && token !== null) {
|
|
382
|
+
if (!/^[0-9]+$/.test(String(token))) throw new TaskAttemptError('usage', `--token expects the whole number claim returned, got '${token}'`);
|
|
383
|
+
tokenNumber = Number(token);
|
|
384
|
+
}
|
|
385
|
+
const lifecycleRoot = lifecycleProjectRoot(cwd);
|
|
386
|
+
const file = storeFile(lifecycleRoot, feature);
|
|
387
|
+
const roots = rootsFor({ cwd, lifecycleRoot });
|
|
388
|
+
const common = { file, fsImpl, roots, slug: feature };
|
|
389
|
+
|
|
390
|
+
let answer;
|
|
391
|
+
switch (action) {
|
|
392
|
+
case 'claim': answer = claim({ ...common, task, owner, mode, now }); break;
|
|
393
|
+
case 'finish': answer = finish({ ...common, task, token: tokenNumber, result, evidenceId }); break;
|
|
394
|
+
case 'release': answer = release({ ...common, task, token: tokenNumber, reason }); break;
|
|
395
|
+
default: answer = status(common);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const ok = action === 'status' || ['claimed', 'applied', 'released'].includes(answer.disposition);
|
|
399
|
+
if (json) console.log(JSON.stringify(answer, null, 2));
|
|
400
|
+
else if (action === 'status') {
|
|
401
|
+
console.log(`Task attempts for ${feature}: ${answer.complete ? 'complete' : 'incomplete'}`);
|
|
402
|
+
for (const t of answer.tasks) console.log(` ${t.id} ${t.derived}${t.activeToken ? ` (token ${t.activeToken}, ${t.active.owner} since ${t.active.startedAt})` : ''}`);
|
|
403
|
+
if (answer.unclaimedChecked.length) console.log(`Checked in plan.md with no COMPLETED attempt: ${answer.unclaimedChecked.join(', ')}`);
|
|
404
|
+
} else (ok ? console.log : console.error)(describe(answer));
|
|
405
|
+
return finishRuntime(ok ? 0 : 1);
|
|
406
|
+
} catch (error: any) {
|
|
407
|
+
if (error instanceof TaskAttemptError) return failure(error.code, error.message, json);
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
module.exports = {
|
|
413
|
+
handleTaskAttemptCommand, checkCompletion, claim, finish, release, status, loadPlan, taskLineHash,
|
|
414
|
+
storeFile, TaskAttemptError, MODES, RESULTS, ACTIONS,
|
|
415
|
+
};
|