@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,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// `doflow reconcile` — converge observed state onto the doflow.lock pin: report drift, then heal
|
|
3
|
+
// it on confirmation. Never re-prompts for MCP; selections ride exactly as pinned.
|
|
4
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
5
|
+
const { toolDirs } = require('../../install/targets.ts');
|
|
6
|
+
const { resolveContext, printContext } = require('../../install/context.ts');
|
|
7
|
+
const { writeManifest } = require('../../install/manifest.ts');
|
|
8
|
+
const { confirm } = require('../../helper/prompt.ts');
|
|
9
|
+
const { sourceCommit } = require('../../helper/git.ts');
|
|
10
|
+
const { loadRegistry } = require('../../registry/index.ts');
|
|
11
|
+
const { applyLifecycle, recordMcpOwnership } = require('../../lifecycle/index.ts');
|
|
12
|
+
const {
|
|
13
|
+
registryLifecycleView, assertSafeRegistryPlan,
|
|
14
|
+
} = require('../../lifecycle/view.ts');
|
|
15
|
+
const { recordedMcpSelections } = require('../../install/mcp.ts');
|
|
16
|
+
const {
|
|
17
|
+
REPO_ROOT, SCRIPT_DIR, pkg, scopeOf, installPaths, scopeSelectionState, printRecordedMcpOwnership, buildAdapterRegistry,
|
|
18
|
+
refuseHomeRootedProject, holdRunLock, checkpointRunLock,
|
|
19
|
+
} = require('../shared.ts');
|
|
20
|
+
|
|
21
|
+
/** Classify desired-vs-observed drift for one lifecycle view. The plan IS the diff: its changes
|
|
22
|
+
* are the operations needed to converge, its conflicts and prerequisites are the drift that must
|
|
23
|
+
* not be silently healed. */
|
|
24
|
+
function reconcileReport(view: any): any {
|
|
25
|
+
const drifts = [];
|
|
26
|
+
const perHarness = new Map();
|
|
27
|
+
for (const target of view.plan.targets) {
|
|
28
|
+
if (target.skipped) continue;
|
|
29
|
+
const entry: any = { create: 0, update: 0, remove: 0 };
|
|
30
|
+
for (const change of target.changes) {
|
|
31
|
+
if (entry[change.operation] !== undefined) entry[change.operation] += 1;
|
|
32
|
+
drifts.push({ harness: target.harness, operation: change.operation, assetId: change.assetId, target: change.target });
|
|
33
|
+
}
|
|
34
|
+
perHarness.set(target.harness, entry);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
drifts,
|
|
38
|
+
conflicts: view.plan.conflicts,
|
|
39
|
+
prerequisites: view.plan.prerequisites,
|
|
40
|
+
perHarness: Object.fromEntries(perHarness),
|
|
41
|
+
clean: drifts.length === 0 && view.plan.conflicts.length === 0,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function printReconcileReport(report: any, lock: any): void {
|
|
46
|
+
console.log(`[INFO] Reconciling against doflow.lock (sourceVersion ${lock.sourceVersion ?? 'unknown'})`);
|
|
47
|
+
for (const [harness, counts] of Object.entries<any>(report.perHarness)) {
|
|
48
|
+
const total = counts.create + counts.update + counts.remove;
|
|
49
|
+
console.log(`[INFO] ${harness}: ${total === 0 ? 'clean' : `${total} drift(s) (${counts.create} create, ${counts.update} update, ${counts.remove} remove)`}`);
|
|
50
|
+
}
|
|
51
|
+
for (const conflict of report.conflicts) console.log(`[CONFLICT] ${conflict.harness}: ${conflict.reason}`);
|
|
52
|
+
for (const prerequisite of report.prerequisites) console.log(`[PENDING-TRUST] ${prerequisite.harness}: ${prerequisite.prerequisite}`);
|
|
53
|
+
if (report.clean) console.log('[OK] Observed state matches doflow.lock.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function cmdReconcile(o: any): void {
|
|
57
|
+
refuseHomeRootedProject(o, 'reconcile');
|
|
58
|
+
const scope = scopeOf(o);
|
|
59
|
+
const hold = holdRunLock(o, scope, 'reconcile');
|
|
60
|
+
try {
|
|
61
|
+
const dirs = toolDirs(scope);
|
|
62
|
+
const lifecyclePaths = installPaths(scope);
|
|
63
|
+
const { lock, ledger, manifestServers } = scopeSelectionState(scope);
|
|
64
|
+
// A harness the ledger holds but the lock does not pin is installed and outside reconcile's reach.
|
|
65
|
+
// Said, not converged: the lock records choices, and reconcile has no choice to record for it.
|
|
66
|
+
const pinned = new Set((lock?.targets ?? []).map((entry: any) => entry.harness));
|
|
67
|
+
const unpinned = [...new Set((ledger?.resources ?? []).map((resource: any) => resource.harness))].filter((harness) => !pinned.has(harness)).sort();
|
|
68
|
+
for (const harness of unpinned) {
|
|
69
|
+
const count = ledger.resources.filter((resource: any) => resource.harness === harness).length;
|
|
70
|
+
console.log(`[WARN] ${harness}: installed (ledger holds ${count} resource(s)) but absent from doflow.lock; reconcile does not converge it.`);
|
|
71
|
+
}
|
|
72
|
+
if (!lock || !lock.targets.length) {
|
|
73
|
+
// Reconcile converges onto what install pinned; with no pin there is no desired state to
|
|
74
|
+
// converge to, and guessing one from the registry would silently adopt targets the user
|
|
75
|
+
// never chose.
|
|
76
|
+
console.log('[INFO] No doflow.lock in this scope — nothing pinned to reconcile against. Run `doflow install` first.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const targets = lock.targets.map((entry: any) => entry.harness);
|
|
80
|
+
const registry = loadRegistry({ repoRoot: REPO_ROOT });
|
|
81
|
+
printContext(resolveContext({ repoRoot: REPO_ROOT, targets, dirs, sourceCommit: sourceCommit(SCRIPT_DIR), ...scope }));
|
|
82
|
+
// Each harness converges onto its own pinned selection — reconcile never re-prompts and never
|
|
83
|
+
// lends one harness's servers to another.
|
|
84
|
+
const recorded = recordedMcpSelections({ registry, lock, ledger, targets, manifestServers });
|
|
85
|
+
const lifecycleView = registryLifecycleView({ registry, repoRoot: REPO_ROOT, scope, dirs, targets,
|
|
86
|
+
mcpSelections: recorded.selections, mcpAdoptable: recorded.adoptable, retainedMcpIds: [], force: true });
|
|
87
|
+
if (!lifecycleView.plan.safe) { assertSafeRegistryPlan(lifecycleView); return; }
|
|
88
|
+
|
|
89
|
+
const report = reconcileReport(lifecycleView);
|
|
90
|
+
printReconcileReport(report, lock);
|
|
91
|
+
if (o.json) console.log(JSON.stringify({ scope: lock.scope, sourceVersion: lock.sourceVersion, targets, unpinned, ...report }, null, 2));
|
|
92
|
+
if (o.dryRun) {
|
|
93
|
+
console.log('[DRY] Reconcile plan complete — no changes written');
|
|
94
|
+
if (!report.clean) process.exitCode = 1; // CI-friendly: drifted check must fail loudly.
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (report.clean) {
|
|
98
|
+
// Converged already; an entry the plan finds DoFlow's without writing it still needs its row.
|
|
99
|
+
const owned = recordMcpOwnership({ plan: lifecycleView.plan, registry: lifecycleView.registry, adapters: lifecycleView.adapters,
|
|
100
|
+
stateRoot: lifecycleView.stateRoot, ledger: lifecycleView.ledger });
|
|
101
|
+
printRecordedMcpOwnership(owned.recorded);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!confirm(`Reconcile ${targets.join(', ')} by applying ${report.drifts.length} change(s)?`, o.force)) {
|
|
106
|
+
console.error('[INFO] Aborted.');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
checkpointRunLock(hold);
|
|
110
|
+
applyLifecycle({ plan: lifecycleView.plan, registry: lifecycleView.registry,
|
|
111
|
+
adapters: buildAdapterRegistry(),
|
|
112
|
+
stateRoot: lifecycleView.stateRoot, ledger: lifecycleView.ledger });
|
|
113
|
+
writeManifest({ scopeRoot: lifecyclePaths.scopeRoot, scriptVersion: pkg.version, operation: 'update', repoRoot: SCRIPT_DIR, sourceCommit: sourceCommit(SCRIPT_DIR), backupId: '', tools: targets, date: new Date() });
|
|
114
|
+
console.log('[OK] Reconciliation complete — state converged onto doflow.lock.');
|
|
115
|
+
} finally {
|
|
116
|
+
hold?.release();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = cmdReconcile;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// `doflow remove` — remove only lifecycle-owned native resources; user-owned files are preserved
|
|
3
|
+
// and what remains standing (shared destinations) is reported, not implied.
|
|
4
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { resolveTargets, toolDirs } = require('../../install/targets.ts');
|
|
8
|
+
const { confirm } = require('../../helper/prompt.ts');
|
|
9
|
+
const { loadRegistry } = require('../../registry/index.ts');
|
|
10
|
+
const { storageForgetScope } = require('../../runtime/storage/upkeep.ts');
|
|
11
|
+
const { removeLifecycle, retentionSummary } = require('../../lifecycle/index.ts');
|
|
12
|
+
const {
|
|
13
|
+
codexScope, registryLifecycleView, printRegistryLifecycle, LIFECYCLE_HARNESSES,
|
|
14
|
+
assertSafeRegistryPlan, lockDocument, recordLock,
|
|
15
|
+
} = require('../../lifecycle/view.ts');
|
|
16
|
+
const { recordedMcpSelections } = require('../../install/mcp.ts');
|
|
17
|
+
const {
|
|
18
|
+
REPO_ROOT, scopeOf, scopeSelectionState, buildAdapterRegistry, refuseHomeRootedProject, holdRunLock, checkpointRunLock,
|
|
19
|
+
} = require('../shared.ts');
|
|
20
|
+
|
|
21
|
+
function cmdRemove(o: any): void {
|
|
22
|
+
refuseHomeRootedProject(o, 'remove');
|
|
23
|
+
const targets = resolveTargets(o.targets);
|
|
24
|
+
const scope = scopeOf(o);
|
|
25
|
+
const hold = holdRunLock(o, scope, 'remove');
|
|
26
|
+
try {
|
|
27
|
+
const dirs = toolDirs(scope);
|
|
28
|
+
const lifecycleTargets = targets.filter((t: string) => LIFECYCLE_HARNESSES.includes(t));
|
|
29
|
+
if (!lifecycleTargets.length) {
|
|
30
|
+
console.log('[INFO] No lifecycle-owned native resources selected; legacy compatibility assets are never broadly removed.');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const registry = loadRegistry({ repoRoot: REPO_ROOT });
|
|
34
|
+
// Read before the removal rewrites the ledger: an entry DoFlow wrote before it kept MCP rows is
|
|
35
|
+
// removed only while it still equals DoFlow's own rendering.
|
|
36
|
+
const { lock, ledger, manifestServers } = scopeSelectionState(scope);
|
|
37
|
+
// No selection: every removed harness's MCP selection is none, so it removes what it owns. The
|
|
38
|
+
// harnesses that stay keep their recorded servers in the MCP index.
|
|
39
|
+
const { adoptable: mcpAdoptable, retainedMcpIds } = recordedMcpSelections({ registry, lock, ledger, targets: lifecycleTargets, manifestServers });
|
|
40
|
+
const view = registryLifecycleView({ registry, repoRoot: REPO_ROOT, scope, dirs, targets: lifecycleTargets, mcpAdoptable, retainedMcpIds, operation: 'remove',
|
|
41
|
+
permissions: o.permissions === true, statusline: o.statusline === true });
|
|
42
|
+
if (!view.plan.safe) { assertSafeRegistryPlan(view); return; }
|
|
43
|
+
if (o.dryRun) {
|
|
44
|
+
printRegistryLifecycle(view, '[DRY]');
|
|
45
|
+
console.log('[DRY] Remove plan complete — no changes written');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!confirm(`Remove DoFlow-owned native resources for: ${lifecycleTargets.join(', ')}? User-owned files are preserved.`, o.force)) {
|
|
49
|
+
// Exit 1, not 0: a declined prompt is a decision, and it must not share an exit code with a
|
|
50
|
+
// completed run. With no stdin the prompt auto-declines, so `doflow install <path>` in a script
|
|
51
|
+
// or CI step printed "Aborted.", wrote zero files, and reported success. It also silently
|
|
52
|
+
// corrupted a set of install-timing measurements during the D.4 sweep, which is how it surfaced.
|
|
53
|
+
console.error('[INFO] Aborted.');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
checkpointRunLock(hold);
|
|
57
|
+
const result = removeLifecycle({ registry: view.registry,
|
|
58
|
+
adapters: buildAdapterRegistry(),
|
|
59
|
+
scope: codexScope(scope), scopeRoot: scope.global ? os.homedir() : path.resolve(scope.projectRoot),
|
|
60
|
+
targets: lifecycleTargets, mcpAdoptable, retainedMcpIds, stateRoot: view.stateRoot, ledger: view.ledger,
|
|
61
|
+
context: view.plan.targets[0].adapterInput.context });
|
|
62
|
+
// Shared destinations (one .doflow/scripts tree for claude/codex/gemini, one .agents for
|
|
63
|
+
// gemini/copilot) mean a removal can legitimately leave files standing. Saying only "removed"
|
|
64
|
+
// would be half the truth, so what was kept and who still claims it is printed, not implied.
|
|
65
|
+
for (const line of retentionSummary(result.retained)) console.log(`[INFO] ${line}`);
|
|
66
|
+
console.log(`[OK] Removed ${result.ledger.resources.length === 0 ? 'all' : 'eligible'} native resource(s) for ${lifecycleTargets.join(', ')}; ${result.ledger.resources.length} owned record(s) remain.`);
|
|
67
|
+
|
|
68
|
+
// Re-pin what still stands: every harness the ledger still holds keeps its pin and its recorded
|
|
69
|
+
// selection, a removed harness with nothing left leaves the lock, and the lock goes only when no
|
|
70
|
+
// harness remains. Shared destinations are handled naturally — their claims belong to whichever
|
|
71
|
+
// harness still holds them in the ledger.
|
|
72
|
+
const next = lockDocument({ registry, scope: codexScope(scope), scopeRoot: scope.global ? os.homedir() : path.resolve(scope.projectRoot),
|
|
73
|
+
previous: lock, ledger: result.ledger });
|
|
74
|
+
const removalLock = recordLock(scope.global ? { scope: 'global', homeDir: os.homedir() } : { scope: 'project', projectRoot: path.resolve(scope.projectRoot) }, next);
|
|
75
|
+
if (lock || next) console.log(`[INFO] doflow.lock: ${removalLock.summary}`);
|
|
76
|
+
if (lock && !next) storageForgetScope({ scope: scope.global ? 'global' : 'project', scopeRoot: scope.global ? os.homedir() : path.resolve(scope.projectRoot) });
|
|
77
|
+
} finally {
|
|
78
|
+
hold?.release();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = cmdRemove;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// `doflow rollback` — restore from a backup (interactive pick when the id is omitted). Works out the
|
|
3
|
+
// whole restore first, then snapshots exactly the files it will overwrite (regardless of
|
|
4
|
+
// --no-backup), then restores. A per-file backup restores every file it holds unless --target
|
|
5
|
+
// narrows it, and the ledger rows of exactly the files it restores; a backup from an earlier DoFlow
|
|
6
|
+
// restores per tool, as it always did.
|
|
7
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { resolveTargets, toolDirs } = require('../../install/targets.ts');
|
|
10
|
+
const { resolveContext, printContext } = require('../../install/context.ts');
|
|
11
|
+
const {
|
|
12
|
+
BackupError, createFileBackup, planRestore, executeRestore, listBackups, formatBytes,
|
|
13
|
+
} = require('../../install/backup.ts');
|
|
14
|
+
const { backupSetFromPaths } = require('../../install/backup-set.ts');
|
|
15
|
+
const { writeManifest } = require('../../install/manifest.ts');
|
|
16
|
+
const { stateRoot, ledgerPath } = require('../../state/index.ts');
|
|
17
|
+
const { confirm, promptLine } = require('../../helper/prompt.ts');
|
|
18
|
+
const { sourceCommit } = require('../../helper/git.ts');
|
|
19
|
+
const { captureCaught } = require('../../runtime/cli-result.ts');
|
|
20
|
+
const {
|
|
21
|
+
REPO_ROOT, SCRIPT_DIR, pkg, installPaths, printBackupTable, holdRunLock, checkpointRunLock,
|
|
22
|
+
} = require('../shared.ts');
|
|
23
|
+
|
|
24
|
+
const LISTED_ABSENT = 10;
|
|
25
|
+
|
|
26
|
+
function fail(error: any): void {
|
|
27
|
+
captureCaught(error, 'rollback', 1);
|
|
28
|
+
console.error(`[ERROR] ${error.message}`);
|
|
29
|
+
console.error('[ERROR] Use --list-backups to see available backups');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cmdRollback(o: any): void {
|
|
34
|
+
const targets = resolveTargets(o.targets);
|
|
35
|
+
const dirs = toolDirs({ global: o.global, projectRoot: '.' });
|
|
36
|
+
const scope = { global: o.global, projectRoot: '.' };
|
|
37
|
+
const hold = holdRunLock(o, scope, 'rollback');
|
|
38
|
+
try {
|
|
39
|
+
const scopeName = o.global ? 'global' : 'project';
|
|
40
|
+
const lifecyclePaths = installPaths(scope);
|
|
41
|
+
const backupRoot = lifecyclePaths.backupRoot;
|
|
42
|
+
const commit = sourceCommit(SCRIPT_DIR);
|
|
43
|
+
const explicitTargets = o.targets.length > 0;
|
|
44
|
+
|
|
45
|
+
let bid = o.positional[0] || '';
|
|
46
|
+
if (!bid) {
|
|
47
|
+
printBackupTable(listBackups(backupRoot), backupRoot);
|
|
48
|
+
bid = promptLine('Enter backup ID to restore (or press Enter to cancel): ');
|
|
49
|
+
if (!bid) { console.error('[INFO] Aborted.'); process.exit(1); }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Worked out before anything is written. Reads only, so a bad id fails here, before the prompt.
|
|
53
|
+
let plan: any;
|
|
54
|
+
try {
|
|
55
|
+
const neutralStateRoot = stateRoot({ scope: scopeName, projectRoot: lifecyclePaths.scopeRoot, homeDir: lifecyclePaths.scopeRoot });
|
|
56
|
+
plan = planRestore({ bid, backupRoot, scope: scopeName, scopeRoot: lifecyclePaths.scopeRoot, targets, explicitTargets, dirs,
|
|
57
|
+
ledgerFile: ledgerPath(neutralStateRoot) });
|
|
58
|
+
} catch (error) {
|
|
59
|
+
fail(error);
|
|
60
|
+
}
|
|
61
|
+
// A per-file backup restores the harnesses it holds (only those in --target when given), so the
|
|
62
|
+
// banner and the install manifest name those, not the default target.
|
|
63
|
+
const restoredTools = plan.format === 2
|
|
64
|
+
? [...new Set(plan.restore.flatMap((item: any) => item.harnesses))].filter((h) => !explicitTargets || targets.includes(h)).sort()
|
|
65
|
+
: targets;
|
|
66
|
+
printContext(resolveContext({ repoRoot: REPO_ROOT, targets: restoredTools, dirs, sourceCommit: commit, global: o.global, projectRoot: '.' }));
|
|
67
|
+
|
|
68
|
+
// PARITY-with-UX: install/update skip the confirm prompt entirely under --dry-run (nothing
|
|
69
|
+
// destructive happens, so there's nothing to confirm) — rollback used to prompt regardless of
|
|
70
|
+
// --dry-run, which meant a non-interactive `doflow rollback <id> --dry-run` (no --force) would
|
|
71
|
+
// block on stdin instead of just previewing. Match install/update's convention here.
|
|
72
|
+
if (!o.dryRun && !confirm(`Restore from '${bid}'? This overwrites your current config.`, o.force)) {
|
|
73
|
+
// Exit 1, not 0: a declined prompt is a decision, and it must not share an exit code with a
|
|
74
|
+
// completed run. With no stdin the prompt auto-declines, so `doflow install <path>` in a script
|
|
75
|
+
// or CI step printed "Aborted.", wrote zero files, and reported success. It also silently
|
|
76
|
+
// corrupted a set of install-timing measurements during the D.4 sweep, which is how it surfaced.
|
|
77
|
+
console.error('[INFO] Aborted.');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
checkpointRunLock(hold);
|
|
81
|
+
|
|
82
|
+
if (plan.format === 1 && plan.type === 'full') {
|
|
83
|
+
for (const { tool, dstDir } of plan.v1.tools) {
|
|
84
|
+
console.error(`[INFO] ${bid} is a whole-home archive from an earlier DoFlow; restoring ${tool}.tar.gz into ${dstDir}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
snapshotBeforeRestore({ o, plan, bid, backupRoot, lifecyclePaths, scopeName, commit });
|
|
89
|
+
|
|
90
|
+
let result: any;
|
|
91
|
+
try {
|
|
92
|
+
result = executeRestore(plan, { backupRoot, dryRun: o.dryRun });
|
|
93
|
+
} catch (error) {
|
|
94
|
+
fail(error);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const writeRollbackManifest = () => writeManifest({ scopeRoot: lifecyclePaths.scopeRoot, scriptVersion: pkg.version, operation: 'rollback',
|
|
98
|
+
repoRoot: SCRIPT_DIR, sourceCommit: commit, backupId: bid, tools: restoredTools, date: new Date(), dryRun: o.dryRun });
|
|
99
|
+
if (result.legacy) {
|
|
100
|
+
writeRollbackManifest();
|
|
101
|
+
console.log(o.dryRun ? '[DRY] Dry run complete' : `[OK] Rollback to '${bid}' complete!`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (result.untargeted > 0) console.error(`[INFO] Skipped ${result.untargeted} file(s) of harnesses not in --target`);
|
|
106
|
+
const left = result.absent.length;
|
|
107
|
+
if (left > 0) {
|
|
108
|
+
console.error(`[INFO] Left in place ${left} file(s) that did not exist when the backup was taken:`);
|
|
109
|
+
for (const file of result.absent.slice(0, LISTED_ABSENT)) console.error(` ${file}`);
|
|
110
|
+
if (left > LISTED_ABSENT) {
|
|
111
|
+
console.error(` ... and ${left - LISTED_ABSENT} more (listed with "existed": false in ${path.join(plan.bkDir, '.manifest.json')})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const problems = [...result.refused, ...result.failed];
|
|
115
|
+
for (const problem of problems) console.error(`[ERROR] Could not restore ${problem.path}: ${problem.reason}`);
|
|
116
|
+
const restored = result.restored.length;
|
|
117
|
+
|
|
118
|
+
if (o.dryRun) {
|
|
119
|
+
console.log(`[DRY] Would restore ${restored} file(s), leave ${left} in place, could not restore ${problems.length}`);
|
|
120
|
+
console.log('[DRY] Dry run complete');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (problems.length > 0) {
|
|
124
|
+
console.error(`[ERROR] Rollback to '${bid}' incomplete: restored ${restored}, could not restore ${problems.length}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
writeRollbackManifest();
|
|
128
|
+
console.log(`[OK] Rollback to '${bid}' complete: restored ${restored} file(s)${left > 0 ? `, left ${left} in place` : ''}`);
|
|
129
|
+
} finally {
|
|
130
|
+
hold?.release();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A backup of exactly the files the restore will overwrite, taken regardless of --no-backup:
|
|
135
|
+
* rollback is destructive enough that skipping it is not offered. Skipped when the restore
|
|
136
|
+
* overwrites nothing, so an empty snapshot never pushes a real restore point out of retention. */
|
|
137
|
+
function snapshotBeforeRestore({ o, plan, bid, backupRoot, lifecyclePaths, scopeName, commit }: any): void {
|
|
138
|
+
if (o.dryRun) {
|
|
139
|
+
console.log(`[DRY] Would snapshot ${plan.snapshot.length} file(s) before restoring`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const nothing = '[INFO] Pre-rollback snapshot: nothing to snapshot (the restore overwrites no file)';
|
|
143
|
+
if (plan.snapshot.length === 0) {
|
|
144
|
+
console.error(nothing);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
console.error('[INFO] Creating pre-rollback safety snapshot...');
|
|
148
|
+
let snapshot;
|
|
149
|
+
try {
|
|
150
|
+
const neutralStateRoot = stateRoot({ scope: scopeName, projectRoot: lifecyclePaths.scopeRoot, homeDir: lifecyclePaths.scopeRoot });
|
|
151
|
+
const set = backupSetFromPaths({
|
|
152
|
+
items: plan.snapshot, scope: scopeName, scopeRoot: lifecyclePaths.scopeRoot,
|
|
153
|
+
exclude: [backupRoot, neutralStateRoot, lifecyclePaths.manifestPath, path.join(lifecyclePaths.doflowRoot, 'doflow.lock')],
|
|
154
|
+
// A backup restores the ledger rows of its files (backup.ts mergeRestoredLedger), so the
|
|
155
|
+
// snapshot of what the restore overwrites holds the current ledger too.
|
|
156
|
+
records: [ledgerPath(neutralStateRoot)],
|
|
157
|
+
});
|
|
158
|
+
snapshot = createFileBackup({ operation: 'pre-rollback', set, backupRoot, repoRoot: SCRIPT_DIR, sourceCommit: commit,
|
|
159
|
+
version: pkg.version, date: new Date(), restores: bid });
|
|
160
|
+
} catch (error: any) {
|
|
161
|
+
if (!(error instanceof BackupError)) throw error;
|
|
162
|
+
console.error(`[ERROR] Pre-rollback snapshot failed, nothing was restored: ${error.message}`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
if (!snapshot) {
|
|
166
|
+
console.error(nothing);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
console.error(`[INFO] Pre-rollback snapshot: ${snapshot.id} (${snapshot.files} file(s), ${formatBytes(snapshot.bytes)})`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = cmdRollback;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// `doflow self-update` — git fetch/pull the checkout, then re-run the install path so the
|
|
3
|
+
// installed trees match the freshly pulled source.
|
|
4
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
5
|
+
const { execFileSync } = require('node:child_process');
|
|
6
|
+
const { REPO_ROOT } = require('../shared.ts');
|
|
7
|
+
const cmdInstall = require('./install.ts');
|
|
8
|
+
|
|
9
|
+
function cmdSelfUpdate(o: any): void {
|
|
10
|
+
console.log('[INFO] Self-update: checking for upstream changes...');
|
|
11
|
+
let pulled = false;
|
|
12
|
+
let fetchOk = false;
|
|
13
|
+
try {
|
|
14
|
+
execFileSync('git', ['-C', REPO_ROOT, 'fetch', '--depth=1', 'origin'], { stdio: ['ignore', 'ignore', 'pipe'], timeout: 10000 });
|
|
15
|
+
fetchOk = true;
|
|
16
|
+
} catch {
|
|
17
|
+
console.error('[WARN] git fetch failed (offline or no remote configured) — installing from current HEAD');
|
|
18
|
+
}
|
|
19
|
+
if (fetchOk) {
|
|
20
|
+
try {
|
|
21
|
+
execFileSync('git', ['-C', REPO_ROOT, 'pull', '--ff-only'], { stdio: ['ignore', 'ignore', 'pipe'], timeout: 10000 });
|
|
22
|
+
pulled = true;
|
|
23
|
+
const commit = execFileSync('git', ['-C', REPO_ROOT, 'rev-parse', '--short', 'HEAD']).toString().trim();
|
|
24
|
+
console.log(`[INFO] Updated to commit: ${commit}`);
|
|
25
|
+
} catch {
|
|
26
|
+
console.error('[WARN] Fast-forward not possible — using current local state');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
console.log(`[INFO] Running install (after ${pulled ? 'a git pull' : 'checking for updates'})...`);
|
|
30
|
+
cmdInstall(o);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = cmdSelfUpdate;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// `doflow status` — resolved context plus installed state from the manifest, with per-harness
|
|
3
|
+
// lifecycle verification. Must stay usable even when the local registry is invalid (--json for
|
|
4
|
+
// scripting).
|
|
5
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { resolveTargets, toolDirs } = require('../../install/targets.ts');
|
|
8
|
+
const { resolveContext, printContext } = require('../../install/context.ts');
|
|
9
|
+
const { readInstallManifest } = require('../../install/manifest.ts');
|
|
10
|
+
const { sourceCommit } = require('../../helper/git.ts');
|
|
11
|
+
const { loadRegistry, mcpCapable } = require('../../registry/index.ts');
|
|
12
|
+
const { recordedMcpSelections } = require('../../install/mcp.ts');
|
|
13
|
+
const { pinnedSelections } = require('../../state/lockfile.ts');
|
|
14
|
+
const { readLedger } = require('../../state/index.ts');
|
|
15
|
+
const { verifyLifecycle } = require('../../lifecycle/index.ts');
|
|
16
|
+
const { registryLifecycleView, LIFECYCLE_HARNESSES } = require('../../lifecycle/view.ts');
|
|
17
|
+
const { REPO_ROOT, SCRIPT_DIR, installPaths, scopeOf, scopeSelectionState } = require('../shared.ts');
|
|
18
|
+
|
|
19
|
+
function cmdStatus(o: any): void {
|
|
20
|
+
const targets = resolveTargets(o.targets);
|
|
21
|
+
const scope = scopeOf(o);
|
|
22
|
+
const dirs = toolDirs(scope);
|
|
23
|
+
const lifecyclePaths = installPaths(scope);
|
|
24
|
+
const ctx: any = resolveContext({ repoRoot: REPO_ROOT, targets, dirs, sourceCommit: sourceCommit(SCRIPT_DIR), ...scope });
|
|
25
|
+
const manifest = readInstallManifest({ scopeRoot: lifecyclePaths.scopeRoot });
|
|
26
|
+
let registryView: any = null;
|
|
27
|
+
// Per MCP-capable target, the servers doflow.lock records for it, or null when it records none.
|
|
28
|
+
let mcpSelections: any = null;
|
|
29
|
+
try {
|
|
30
|
+
const registry = loadRegistry({ repoRoot: REPO_ROOT });
|
|
31
|
+
const { lock, ledger, manifestServers } = scopeSelectionState(scope);
|
|
32
|
+
const recorded = recordedMcpSelections({ registry, lock, ledger, targets, manifestServers });
|
|
33
|
+
const rows = pinnedSelections(lock);
|
|
34
|
+
mcpSelections = Object.fromEntries(targets.filter((harness: string) => mcpCapable(registry, harness))
|
|
35
|
+
.map((harness: string) => [harness, rows[harness] ?? null]));
|
|
36
|
+
registryView = registryLifecycleView({ registry, repoRoot: REPO_ROOT, scope, dirs, targets,
|
|
37
|
+
mcpSelections: recorded.selections, mcpAdoptable: recorded.adoptable, retainedMcpIds: recorded.retainedMcpIds });
|
|
38
|
+
ctx.registry = {
|
|
39
|
+
directory: registryView.registry.directory,
|
|
40
|
+
versions: registryView.registry.versions,
|
|
41
|
+
stateRoot: registryView.stateRoot,
|
|
42
|
+
ledgerPresent: Boolean(readLedger(registryView.stateRoot)),
|
|
43
|
+
plan: { changes: registryView.plan.changes.length, conflicts: registryView.plan.conflicts, prerequisites: registryView.plan.prerequisites },
|
|
44
|
+
};
|
|
45
|
+
// Per-harness status must derive from that harness's own plan target, not the flattened
|
|
46
|
+
// registryView.plan.changes/conflicts across every target — otherwise, e.g., Codex having
|
|
47
|
+
// pending changes when it isn't installed yet would make Claude's line falsely report
|
|
48
|
+
// 'drift-or-pending-change' even though Claude itself has nothing pending.
|
|
49
|
+
const harnessPlan = (harness: string) => registryView.plan.targets.find((target: any) => target.harness === harness);
|
|
50
|
+
// Hook-wiring status needs the same general per-harness verification every install/update run
|
|
51
|
+
// already flows through (src/lifecycle's verifyLifecycle) rather than a Codex-only hardcode:
|
|
52
|
+
// it is the one place that can tell 'installed and active' apart from 'installed but pending a
|
|
53
|
+
// prerequisite' (Codex's unreviewed trust, Gemini's live hook-trust check) from 'absent'.
|
|
54
|
+
const verification = verifyLifecycle({ plan: registryView.plan, adapters: registryView.adapters, context: { registry } });
|
|
55
|
+
const hooksFor = (harness: string) => verification.verifications.find((item: any) => item.harness === harness)?.hookWiring ?? null;
|
|
56
|
+
const harnessStatus = (harness: string) => {
|
|
57
|
+
const target = harnessPlan(harness);
|
|
58
|
+
if (!target) return { status: 'verified', resources: [], errors: [], hooks: hooksFor(harness) };
|
|
59
|
+
const resources = registryView.ledger.resources.filter((resource: any) => resource.harness === harness);
|
|
60
|
+
// A conflict is reported first: a user-owned file in the way is not the same as nothing being
|
|
61
|
+
// installed. With no conflict, a harness the ledger owns nothing of was never installed: its
|
|
62
|
+
// whole plan is pending, which is not drift from an installation that does not exist.
|
|
63
|
+
return {
|
|
64
|
+
status: target.conflicts.length ? 'conflict-or-invalid'
|
|
65
|
+
: !resources.length ? 'not-installed'
|
|
66
|
+
: (target.changes.length ? 'drift-or-pending-change' : 'verified'),
|
|
67
|
+
resources,
|
|
68
|
+
errors: target.conflicts,
|
|
69
|
+
hooks: hooksFor(harness),
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
// Every lifecycle-wired harness gets the same per-harness status treatment — not just the
|
|
73
|
+
// four that historically had it — so 'doflow status --target copilot/opencode/pi --json'
|
|
74
|
+
// reports 'verified'/'conflict-or-invalid' instead of silently omitting the harness.
|
|
75
|
+
for (const harness of LIFECYCLE_HARNESSES) {
|
|
76
|
+
if (targets.includes(harness)) ctx[harness] = harnessStatus(harness);
|
|
77
|
+
}
|
|
78
|
+
} catch (error: any) {
|
|
79
|
+
// Status must remain usable for an existing installation even if a local registry is invalid.
|
|
80
|
+
ctx.registry = { status: 'invalid', error: error.message };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (o.json) {
|
|
84
|
+
console.log(JSON.stringify({ context: ctx, manifest, codex: ctx.codex ?? null, mcpSelections }, null, 2));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const hookLine = (label: string, hooks: any) => {
|
|
89
|
+
if (!hooks || hooks.status === 'absent') return null;
|
|
90
|
+
const prereqSuffix = hooks.prerequisites.length ? ` (unmet: ${hooks.prerequisites.join(', ')})` : '';
|
|
91
|
+
return ` ${label} hooks: ${hooks.status}${prereqSuffix}`;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
printContext(ctx);
|
|
95
|
+
if (!manifest) {
|
|
96
|
+
console.log("[WARN] No install manifest found — run 'doflow install' to get started");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log('\nInstall Status');
|
|
101
|
+
console.log(` Last operation: ${manifest.operation}`);
|
|
102
|
+
console.log(` Last run: ${manifest.lastRun}`);
|
|
103
|
+
console.log(` Source commit: ${manifest.sourceCommit}`);
|
|
104
|
+
console.log(` Last backup ID: ${manifest.backupId}`);
|
|
105
|
+
console.log(` Script version: ${manifest.scriptVersion}`);
|
|
106
|
+
if (mcpSelections && Object.keys(mcpSelections).length) {
|
|
107
|
+
const recordedText = (ids: any) => (ids === null ? 'not recorded' : ids.join(', ') || 'none');
|
|
108
|
+
console.log(` MCP selections: ${Object.entries(mcpSelections).map(([harness, ids]) => `${harness} ${recordedText(ids)}`).join('; ')}`);
|
|
109
|
+
}
|
|
110
|
+
// Printed in the same order LIFECYCLE_HARNESSES declares, so every wired harness gets a line —
|
|
111
|
+
// not just the four that historically had a hand-written block — with Codex keeping its extra
|
|
112
|
+
// capability-gap line since that note is Codex-specific, not a general per-harness property.
|
|
113
|
+
for (const harness of LIFECYCLE_HARNESSES) {
|
|
114
|
+
const status = ctx[harness];
|
|
115
|
+
if (!status) continue;
|
|
116
|
+
const label = `${harness.charAt(0).toUpperCase()}${harness.slice(1)}`;
|
|
117
|
+
console.log(` ${`${label} verification:`.padEnd(24)}${status.status}`);
|
|
118
|
+
console.log(` ${`${label} resources:`.padEnd(24)}${status.resources.length} manifest-owned`);
|
|
119
|
+
const line = hookLine(label, status.hooks);
|
|
120
|
+
if (line) console.log(line);
|
|
121
|
+
if (status.errors.length) console.log(` ${`${label} issues:`.padEnd(24)}${status.errors.join('; ')}`);
|
|
122
|
+
if (harness === 'codex') {
|
|
123
|
+
console.log(' Codex capability gaps: no Claude-only event emulation is installed; review docs/capability-map.md#codex-capability-detail');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (ctx.registry) {
|
|
127
|
+
console.log(` Registry lifecycle: ${ctx.registry.status === 'invalid' ? ctx.registry.error : `${ctx.registry.plan.changes} pending, ${ctx.registry.plan.conflicts.length} conflict(s)`}`);
|
|
128
|
+
if (ctx.registry.status !== 'invalid') console.log(` Neutral state: ${ctx.registry.stateRoot}${ctx.registry.ledgerPresent ? ' (ledger present)' : ' (not yet created)'}`);
|
|
129
|
+
}
|
|
130
|
+
console.log('\n TOOL STATUS LAST UPDATED');
|
|
131
|
+
for (const tool of LIFECYCLE_HARNESSES) {
|
|
132
|
+
const t = manifest.tools[tool];
|
|
133
|
+
const status = t?.installed ? 'installed' : 'not installed';
|
|
134
|
+
console.log(` ${tool.padEnd(12)} ${status.padEnd(14)} ${t?.last_updated ?? 'never'}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = cmdStatus;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// `doflow tools` — inspect or manage registered external tools (status/install/update/uninstall).
|
|
3
|
+
// --force is deliberately unavailable: every mutation gets its own confirmation.
|
|
4
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
5
|
+
const { confirm, promptLine } = require('../../helper/prompt.ts');
|
|
6
|
+
const { loadRegistry } = require('../../registry/index.ts');
|
|
7
|
+
const { commandText, planToolLifecycle, executeToolLifecycle } = require('../../install/tool-lifecycle.ts');
|
|
8
|
+
const { REPO_ROOT } = require('../shared.ts');
|
|
9
|
+
|
|
10
|
+
function selectExternalTools(registry: any, o: any): any[] {
|
|
11
|
+
let requested = o.tools;
|
|
12
|
+
if (!requested) {
|
|
13
|
+
const interactive = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
14
|
+
if (!interactive) throw new Error("doflow tools: --tool is required when stdin is not an interactive terminal");
|
|
15
|
+
const answer = promptLine('Select tools (rtk, graphify; comma-separated): ');
|
|
16
|
+
requested = answer.split(',').map((item: string) => item.trim()).filter(Boolean);
|
|
17
|
+
}
|
|
18
|
+
if (!requested.length) throw new Error('doflow tools: select at least one tool');
|
|
19
|
+
const available = new Set(registry.externalTools.map((tool: any) => tool.id));
|
|
20
|
+
const unknown = [...new Set(requested)].filter((id) => !available.has(id));
|
|
21
|
+
if (unknown.length) throw new Error(`doflow tools: unknown tool id(s): ${unknown.join(', ')}; expected: ${[...available].join(', ')}`);
|
|
22
|
+
const wanted = new Set(requested);
|
|
23
|
+
return registry.externalTools.filter((tool: any) => wanted.has(tool.id));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function toolJsonResults(plan: any, execution: any = null): any[] {
|
|
27
|
+
const executed = new Map((execution?.results || []).map((result: any) => [result.tool, result]));
|
|
28
|
+
return plan.tools.map((item: any) => ({
|
|
29
|
+
tool: item.tool.id,
|
|
30
|
+
state: item.state,
|
|
31
|
+
inspections: item.inspections,
|
|
32
|
+
prerequisites: item.prerequisites.map((prerequisite: any) => ({ name: prerequisite.name, available: prerequisite.available })),
|
|
33
|
+
action: item.action,
|
|
34
|
+
result: executed.get(item.tool.id) ?? (item.action.applicable
|
|
35
|
+
? { tool: item.tool.id, status: 'not-attempted', command: [...item.action.command] }
|
|
36
|
+
: { tool: item.tool.id, status: 'skipped', reason: item.action.reason }),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cmdTools(o: any): void {
|
|
41
|
+
if (o.force) throw new Error("doflow tools: --force is not supported; each lifecycle command requires its own confirmation");
|
|
42
|
+
if (!['status', 'install', 'update', 'uninstall'].includes(o.action)) {
|
|
43
|
+
throw new Error(`doflow tools: unsupported action '${o.action}'; expected: status, install, update, uninstall`);
|
|
44
|
+
}
|
|
45
|
+
const registry = loadRegistry({ repoRoot: REPO_ROOT });
|
|
46
|
+
const tools = selectExternalTools(registry, o);
|
|
47
|
+
const plan = planToolLifecycle({ registry: { ...registry, externalTools: tools }, action: o.action });
|
|
48
|
+
|
|
49
|
+
if (o.dryRun) {
|
|
50
|
+
if (!o.json) {
|
|
51
|
+
console.log(`[DRY] External-tool ${o.action} plan:`);
|
|
52
|
+
for (const item of plan.tools) {
|
|
53
|
+
if (item.action.applicable) console.log(`[DRY] ${item.tool.id}: ${commandText(item.action.command)}`);
|
|
54
|
+
else console.log(`[DRY] ${item.tool.id}: skipped (${item.action.reason})`);
|
|
55
|
+
}
|
|
56
|
+
console.log('[DRY] Dry run complete — no lifecycle commands executed');
|
|
57
|
+
}
|
|
58
|
+
if (o.json) console.log(JSON.stringify({ action: o.action, dryRun: true, results: toolJsonResults(plan) }, null, 2));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (o.action === 'status') {
|
|
63
|
+
if (o.json) {
|
|
64
|
+
console.log(JSON.stringify({ action: o.action, dryRun: false, results: toolJsonResults(plan) }, null, 2));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const item of plan.tools) {
|
|
68
|
+
console.log(`${item.tool.displayName}: ${item.state}${item.action.reason ? ` (${item.action.reason})` : ''}`);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const execution = executeToolLifecycle({
|
|
74
|
+
plan,
|
|
75
|
+
displayCommand: (command: string[], tool: any) => console.error(`[INFO] ${tool.displayName}: ${commandText(command)}`),
|
|
76
|
+
confirmCommand: (command: string[], tool: any, action: string) => confirm(`Run ${tool.displayName} ${action}: ${commandText(command)}?`, false),
|
|
77
|
+
});
|
|
78
|
+
// Keep processing independent tools so the user receives every outcome, but make a confirmed
|
|
79
|
+
// command failure visible to scripts and CI through the process result as well as the report.
|
|
80
|
+
const hasFailures = execution.results.some((result: any) => result.status === 'failed');
|
|
81
|
+
if (o.json) {
|
|
82
|
+
console.log(JSON.stringify({ action: o.action, dryRun: false, results: toolJsonResults(plan, execution) }, null, 2));
|
|
83
|
+
if (hasFailures) process.exitCode = 1;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
for (const result of execution.results) {
|
|
87
|
+
console.log(`${result.tool}: ${result.status}${result.reason ? ` (${result.reason})` : ''}`);
|
|
88
|
+
}
|
|
89
|
+
if (hasFailures) process.exitCode = 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = cmdTools;
|