@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +132 -2
- package/bin/doflow.js +44 -0
- package/core/.antigravity-plugin/hooks.json +39 -0
- package/core/.antigravity-plugin/mcp_config.json +18 -0
- package/core/.antigravity-plugin/plugin.json +31 -0
- package/core/.claude-plugin/marketplace.json +20 -0
- package/core/.claude-plugin/plugin.json +21 -0
- package/core/.codex-plugin/marketplace.json +20 -0
- package/core/.codex-plugin/plugin.json +35 -0
- package/core/.plugin/plugin.json +19 -0
- package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
- package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
- package/core/harnesses/claude/hooks/lib.sh +304 -0
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
- package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
- package/core/harnesses/claude/hooks/post-compact.sh +74 -0
- package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
- package/core/harnesses/claude/hooks/session-end.sh +89 -0
- package/core/harnesses/claude/hooks/session-start.sh +5 -0
- package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -0
- package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
- package/core/harnesses/claude/settings/keybindings.json +20 -0
- package/core/harnesses/claude/settings/settings.json +201 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
- package/core/harnesses/codex/agents/core-implementer.toml +7 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
- package/core/harnesses/codex/agents/research-writer.toml +8 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
- package/core/harnesses/codex/agents/system-architect.toml +9 -0
- package/core/harnesses/codex/config/config.toml +10 -0
- package/core/harnesses/codex/hooks/hooks.json +122 -0
- package/core/harnesses/codex/hooks/lib.sh +335 -0
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
- package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
- package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
- package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
- package/core/harnesses/codex/hooks/session-end.sh +5 -0
- package/core/harnesses/codex/hooks/session-start.sh +5 -0
- package/core/harnesses/codex/hooks/stop-check.sh +8 -0
- package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
- package/core/harnesses/gemini/hooks/hooks.json +77 -0
- package/core/harnesses/gemini/hooks/lib.sh +302 -0
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
- package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
- package/core/harnesses/gemini/hooks/session-end.sh +5 -0
- package/core/harnesses/gemini/hooks/session-start.sh +5 -0
- package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
- package/core/harnesses/kiro/agents/doflow.json +58 -0
- package/core/harnesses/kiro/hooks/doflow.json +53 -0
- package/core/harnesses/kiro/hooks/lib.sh +324 -0
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
- package/core/harnesses/kiro/hooks/session-start.sh +9 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
- package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
- package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
- package/core/harnesses/shared/hooks/hooks.json +47 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
- package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
- package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
- package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
- package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
- package/core/harnesses/shared/locator/doflow-run +87 -0
- package/core/registry/advice.json +101 -0
- package/core/registry/assets.json +792 -0
- package/core/registry/capabilities.json +120 -0
- package/core/registry/contracts.json +189 -0
- package/core/registry/external-tools.json +191 -0
- package/core/registry/harnesses.json +2062 -0
- package/core/registry/lifecycle.json +231 -0
- package/core/registry/mcp.json +18 -0
- package/core/registry/models.json +79 -0
- package/core/registry/readiness-templates.json +118 -0
- package/core/registry/routes.json +47 -0
- package/core/registry/storage.json +1061 -0
- package/core/registry/verification.json +258 -0
- package/core/registry/workflows.json +790 -0
- package/core/shared/agent-specs/core-implementer.md +73 -0
- package/core/shared/agent-specs/quality-guardian.md +28 -0
- package/core/shared/agent-specs/research-writer.md +31 -0
- package/core/shared/agent-specs/spec-analyst.md +33 -0
- package/core/shared/agent-specs/system-architect.md +29 -0
- package/core/shared/guidance/DOFLOW_CORE.md +8 -0
- package/core/shared/guidance/FLAGS.md +25 -0
- package/core/shared/guidance/PRINCIPLES.md +61 -0
- package/core/shared/guidance/VERSION +1 -0
- package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
- package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
- package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
- package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
- package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
- package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
- package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
- package/core/shared/guidance/pointers/claude-gemini.md +2 -0
- package/core/shared/guidance/pointers/codex.md +6 -0
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
- package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
- package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
- package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
- package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
- package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
- package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
- package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
- package/core/shared/scripts/doflow/bin/doflow-run +592 -0
- package/core/shared/skills/do/SKILL.md +128 -0
- package/core/shared/skills/do/references/estimation.md +13 -0
- package/core/shared/skills/do/references/pm_routing.md +25 -0
- package/core/shared/skills/do/references/tool_matrix.md +52 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
- package/core/shared/skills/do-code-review/README.md +49 -0
- package/core/shared/skills/do-code-review/SKILL.md +333 -0
- package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
- package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
- package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
- package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
- package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
- package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
- package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
- package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
- package/core/shared/skills/do-code-review/content-types/config.md +83 -0
- package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
- package/core/shared/skills/do-code-review/languages/c.md +103 -0
- package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
- package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
- package/core/shared/skills/do-code-review/languages/dart.md +108 -0
- package/core/shared/skills/do-code-review/languages/go.md +92 -0
- package/core/shared/skills/do-code-review/languages/java.md +103 -0
- package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
- package/core/shared/skills/do-code-review/languages/php.md +109 -0
- package/core/shared/skills/do-code-review/languages/python.md +94 -0
- package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
- package/core/shared/skills/do-code-review/languages/rust.md +99 -0
- package/core/shared/skills/do-code-review/languages/shell.md +90 -0
- package/core/shared/skills/do-code-review/languages/swift.md +88 -0
- package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
- package/core/shared/skills/do-code-review/review-policy.json +47 -0
- package/core/shared/skills/do-code-review/rules/universal.md +49 -0
- package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
- package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
- package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
- package/core/shared/skills/do-constitution/SKILL.md +72 -0
- package/core/shared/skills/do-design/SKILL.md +212 -0
- package/core/shared/skills/do-diagnose/SKILL.md +156 -0
- package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
- package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
- package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
- package/core/shared/skills/do-document/SKILL.md +81 -0
- package/core/shared/skills/do-document/references/api-reference.md +42 -0
- package/core/shared/skills/do-document/references/deep_research.md +18 -0
- package/core/shared/skills/do-document/references/embedded-research.md +11 -0
- package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
- package/core/shared/skills/do-document/references/index_generator.md +9 -0
- package/core/shared/skills/do-document/references/user-guide.md +22 -0
- package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
- package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
- package/core/shared/skills/do-flow/SKILL.md +186 -0
- package/core/shared/skills/do-flow/references/task_classes.md +134 -0
- package/core/shared/skills/do-git/SKILL.md +115 -0
- package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
- package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
- package/core/shared/skills/do-git/references/release-ritual.md +64 -0
- package/core/shared/skills/do-implement/SKILL.md +222 -0
- package/core/shared/skills/do-plan/SKILL.md +219 -0
- package/core/shared/skills/do-test/SKILL.md +113 -0
- package/core/shared/templates/doflow/constitution-template.md +28 -0
- package/core/shared/templates/doflow/data-model-template.md +169 -0
- package/core/shared/templates/doflow/design-template.md +193 -0
- package/core/shared/templates/doflow/external-contract-template.md +35 -0
- package/core/shared/templates/doflow/intent-template.md +45 -0
- package/core/shared/templates/doflow/plan-template.md +151 -0
- package/core/shared/templates/doflow/question-log-template.md +51 -0
- package/core/shared/templates/doflow/requirement-template.md +126 -0
- package/core/shared/templates/doflow/specs-template.md +55 -0
- package/core/shared/templates/doflow/state-template.md +59 -0
- package/core/shared/workflows/do-flow-chain.md +17 -0
- package/package.json +48 -4
- package/src/adapters/antigravity/index.js +631 -0
- package/src/adapters/claude/index.js +548 -0
- package/src/adapters/claude/mcp.js +94 -0
- package/src/adapters/codex/agents.js +208 -0
- package/src/adapters/codex/config.js +272 -0
- package/src/adapters/codex/hooks.js +203 -0
- package/src/adapters/codex/index.js +707 -0
- package/src/adapters/codex/mcp.js +271 -0
- package/src/adapters/codex/native-projection.js +34 -0
- package/src/adapters/copilot/index.js +413 -0
- package/src/adapters/copy-tree.js +620 -0
- package/src/adapters/gemini/hooks.js +210 -0
- package/src/adapters/gemini/index.js +390 -0
- package/src/adapters/hook-commands.js +60 -0
- package/src/adapters/index.js +106 -0
- package/src/adapters/kiro/index.js +310 -0
- package/src/adapters/mcp-entries.js +331 -0
- package/src/adapters/opencode/index.js +466 -0
- package/src/adapters/pi/index.js +279 -0
- package/src/adapters/pi/json-members.js +185 -0
- package/src/adapters/pi/mcp.js +373 -0
- package/src/adapters/resolve-source.js +19 -0
- package/src/cli/commands/install.js +218 -0
- package/src/cli/commands/list-backups.js +21 -0
- package/src/cli/commands/reconcile.js +120 -0
- package/src/cli/commands/remove.js +82 -0
- package/src/cli/commands/rollback.js +172 -0
- package/src/cli/commands/self-update.js +33 -0
- package/src/cli/commands/status.js +138 -0
- package/src/cli/commands/tools.js +92 -0
- package/src/cli/commands/update.js +162 -0
- package/src/cli/index.js +568 -0
- package/src/cli/runtime-commands.js +281 -0
- package/src/cli/shared.js +218 -0
- package/src/helper/bash-helper.js +34 -0
- package/src/helper/git.js +24 -0
- package/src/helper/guardrails.js +49 -0
- package/src/helper/harness-paths.js +140 -0
- package/src/helper/marker-merge.js +116 -0
- package/src/helper/prompt.js +61 -0
- package/src/helper/repo-root.js +29 -0
- package/src/helper/settings-merge.js +151 -0
- package/src/helper/settings-scope.js +33 -0
- package/src/helper/toml.js +244 -0
- package/src/install/backup-set.js +149 -0
- package/src/install/backup.js +724 -0
- package/src/install/context.js +58 -0
- package/src/install/manifest.js +145 -0
- package/src/install/mcp.js +254 -0
- package/src/install/paths.js +84 -0
- package/src/install/targets.js +90 -0
- package/src/install/tool-lifecycle.js +151 -0
- package/src/lifecycle/bash-availability.js +25 -0
- package/src/lifecycle/index.js +812 -0
- package/src/lifecycle/mcp-index.js +28 -0
- package/src/lifecycle/policies.js +73 -0
- package/src/lifecycle/view.js +175 -0
- package/src/registry/index.js +611 -0
- package/src/release/build-package.js +91 -0
- package/src/release/changelog-section.js +70 -0
- package/src/release/sync-plugin-versions.js +66 -0
- package/src/runtime/advice/cli.js +283 -0
- package/src/runtime/advice/client.js +324 -0
- package/src/runtime/advice/config.js +189 -0
- package/src/runtime/advice/doctor.js +169 -0
- package/src/runtime/advice/endpoint-guard.js +134 -0
- package/src/runtime/advice/kinds.js +154 -0
- package/src/runtime/advice/providers/ollama-systemone.js +209 -0
- package/src/runtime/advice/record.js +166 -0
- package/src/runtime/advice/registry.js +170 -0
- package/src/runtime/advice/strength.js +66 -0
- package/src/runtime/brief-tools.js +342 -0
- package/src/runtime/c4-project.js +371 -0
- package/src/runtime/capability-router.js +484 -0
- package/src/runtime/checkouts.js +170 -0
- package/src/runtime/claims.js +468 -0
- package/src/runtime/cli-result.js +55 -0
- package/src/runtime/cli.js +678 -0
- package/src/runtime/command-detect.js +516 -0
- package/src/runtime/context-pack.js +492 -0
- package/src/runtime/decision-register.js +493 -0
- package/src/runtime/evidence-ledger.js +338 -0
- package/src/runtime/failure/capture.js +272 -0
- package/src/runtime/failure/classifier.js +68 -0
- package/src/runtime/failure/cli.js +178 -0
- package/src/runtime/failure/home.js +64 -0
- package/src/runtime/failure/store.js +226 -0
- package/src/runtime/feature-resolve.js +79 -0
- package/src/runtime/freshness.js +177 -0
- package/src/runtime/health.js +863 -0
- package/src/runtime/history-compactor.js +237 -0
- package/src/runtime/implementation-gate.js +321 -0
- package/src/runtime/inventory/coverage.js +218 -0
- package/src/runtime/inventory/identity.js +65 -0
- package/src/runtime/inventory/index.js +705 -0
- package/src/runtime/inventory/precedence.js +134 -0
- package/src/runtime/inventory/read-scopes.js +131 -0
- package/src/runtime/inventory/siblings.js +192 -0
- package/src/runtime/knowledge/chunker.js +124 -0
- package/src/runtime/knowledge/hybrid.js +149 -0
- package/src/runtime/knowledge/index-store.js +145 -0
- package/src/runtime/knowledge/retrieval.js +194 -0
- package/src/runtime/leak-scan.js +185 -0
- package/src/runtime/lifecycle/cli.js +276 -0
- package/src/runtime/lifecycle/event-store.js +340 -0
- package/src/runtime/lifecycle/fold.js +353 -0
- package/src/runtime/lifecycle/followup.js +432 -0
- package/src/runtime/lifecycle/goal.js +249 -0
- package/src/runtime/lifecycle/intent-writer.js +86 -0
- package/src/runtime/lifecycle/overview.js +292 -0
- package/src/runtime/lifecycle/release.js +241 -0
- package/src/runtime/lifecycle/report-store.js +221 -0
- package/src/runtime/lifecycle/retention.js +212 -0
- package/src/runtime/lifecycle/root.js +71 -0
- package/src/runtime/lifecycle/status.js +353 -0
- package/src/runtime/lifecycle/store-upkeep.js +393 -0
- package/src/runtime/locator-resolve.js +158 -0
- package/src/runtime/mask.js +210 -0
- package/src/runtime/mcp-consultations.js +412 -0
- package/src/runtime/mcp-reach.js +157 -0
- package/src/runtime/model-router.js +153 -0
- package/src/runtime/outcome.js +467 -0
- package/src/runtime/reach.js +148 -0
- package/src/runtime/readiness-record.js +150 -0
- package/src/runtime/readiness.js +435 -0
- package/src/runtime/recovery.js +282 -0
- package/src/runtime/research-request.js +231 -0
- package/src/runtime/retrieval-plan.js +578 -0
- package/src/runtime/scaffold/artifacts.js +217 -0
- package/src/runtime/scaffold/fingerprint.js +56 -0
- package/src/runtime/scaffold/generate.js +911 -0
- package/src/runtime/scaffold/languages.js +245 -0
- package/src/runtime/storage/cli.js +64 -0
- package/src/runtime/storage/engine.js +249 -0
- package/src/runtime/storage/registry.js +130 -0
- package/src/runtime/storage/roots.js +63 -0
- package/src/runtime/storage/rules.js +282 -0
- package/src/runtime/storage/upkeep.js +243 -0
- package/src/runtime/storage/work-status.js +326 -0
- package/src/runtime/task-attempts.js +415 -0
- package/src/runtime/task-classifier.js +550 -0
- package/src/runtime/task-scope.js +130 -0
- package/src/runtime/task-state.js +176 -0
- package/src/runtime/trace/indicators.js +235 -0
- package/src/runtime/trace/ledger.js +626 -0
- package/src/runtime/trace/render.js +76 -0
- package/src/runtime/trace/views.js +459 -0
- package/src/runtime/verification/contract-runner.js +341 -0
- package/src/runtime/verification/engine.js +1461 -0
- package/src/runtime/verification/registry.js +130 -0
- package/src/runtime/verification/scope-bound.js +418 -0
- package/src/runtime/verification/test-selection.js +172 -0
- package/src/runtime/workflow-engine.js +588 -0
- package/src/runtime/workflow-orchestrator.js +953 -0
- package/src/runtime/worktree.js +340 -0
- package/src/state/index.js +193 -0
- package/src/state/lockfile.js +136 -0
- package/src/state/run-lock.js +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// context.js — resolved-scope/properties view, shown by `install --dry-run` and `status`.
|
|
3
|
+
// Purpose: once install targets can be $HOME (global) OR a project path, it stops being obvious
|
|
4
|
+
// at a glance which one a given invocation resolves to — this makes that explicit for debugging.
|
|
5
|
+
//
|
|
6
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
7
|
+
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
8
|
+
// caller's `require('../install/context.js')` keeps working while the tree is still `.js`.
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const { sourceCommit: gitSourceCommit } = require('../helper/git.js');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `sourceCommit` lets a caller (bin/doflow.js) pass an already-resolved commit instead of this
|
|
24
|
+
* module spawning its own `git rev-parse`; omit it to resolve here (e.g. tests calling this module
|
|
25
|
+
* directly). Returns a plain properties object (used by both the text and --json renderers).
|
|
26
|
+
*/
|
|
27
|
+
function resolveContext({ repoRoot, global, projectRoot, targets, dirs, sourceCommit, managedResources } ) {
|
|
28
|
+
const context = {
|
|
29
|
+
scope: global ? 'global' : 'project',
|
|
30
|
+
scopeRoot: global ? path.dirname(dirs.claude) : path.resolve(projectRoot),
|
|
31
|
+
targets,
|
|
32
|
+
dirs: Object.fromEntries(targets.map((t) => [t, dirs[t]])),
|
|
33
|
+
repoRoot,
|
|
34
|
+
sourceCommit: sourceCommit ?? gitSourceCommit(repoRoot),
|
|
35
|
+
nodeVersion: process.version,
|
|
36
|
+
};
|
|
37
|
+
// The lifecycle engine may provide its ownership ledger for status/verify output. Do not add
|
|
38
|
+
// an empty field for existing callers: their JSON/text output remains backwards compatible.
|
|
39
|
+
if (managedResources !== undefined) context.managedResources = managedResources;
|
|
40
|
+
return context;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Human-readable "Resolved context" block, printed to stderr (keeps stdout clean like sync.sh's log()). */
|
|
44
|
+
function printContext(ctx ) {
|
|
45
|
+
const lines = [
|
|
46
|
+
'[CONTEXT] Resolved install context',
|
|
47
|
+
` scope : ${ctx.scope} (root: ${ctx.scopeRoot})`,
|
|
48
|
+
` targets : ${ctx.targets.join(', ')}`,
|
|
49
|
+
...ctx.targets.map((t ) => ` ${t.padEnd(7)}-> ${ctx.dirs[t]}`),
|
|
50
|
+
` repo root : ${ctx.repoRoot}`,
|
|
51
|
+
` source commit : ${ctx.sourceCommit}`,
|
|
52
|
+
` node : ${ctx.nodeVersion}`,
|
|
53
|
+
];
|
|
54
|
+
if (ctx.managedResources !== undefined) lines.push(` managed resources: ${ctx.managedResources.length}`);
|
|
55
|
+
console.error(lines.join('\n'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { resolveContext, printContext };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// manifest.js — port of sync.sh's write_manifest/read_manifest.
|
|
3
|
+
// PARITY: schema {script_version,last_operation,last_run,source_path,source_commit,last_backup_id,
|
|
4
|
+
// tools:{tool:{installed,last_updated}}}; atomic write via tmp file + rename (never partial-write
|
|
5
|
+
// the manifest, matching sync.sh's `mktemp` + `mv`).
|
|
6
|
+
//
|
|
7
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
8
|
+
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
9
|
+
// caller's `require('../install/manifest.js')` keeps working while the tree is still `.js`.
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const { sourceCommit: gitSourceCommit } = require('../helper/git.js');
|
|
13
|
+
|
|
14
|
+
// Explicit migration bridge: pre-registry installations stored this record beneath .claude.
|
|
15
|
+
// Lifecycle commands use the scope-neutral .doflow location; only the neutral-state importer
|
|
16
|
+
// calls the legacy reader below.
|
|
17
|
+
const LEGACY_MANIFEST_FILE_NAME = '.install-manifest.json';
|
|
18
|
+
const MANIFEST_FILE_NAME = LEGACY_MANIFEST_FILE_NAME;
|
|
19
|
+
|
|
20
|
+
function manifestPath(claudeDir ) {
|
|
21
|
+
return path.join(claudeDir, MANIFEST_FILE_NAME);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function canonicalManifestPath(scopeRoot ) {
|
|
25
|
+
return path.join(path.resolve(scopeRoot), '.doflow', MANIFEST_FILE_NAME);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `sourceCommit` lets a caller (bin/doflow.js) pass an already-resolved commit instead of this
|
|
44
|
+
* module spawning its own `git rev-parse`; omit it to resolve here (e.g. tests calling this module
|
|
45
|
+
* directly). An `mcp_servers` list a 1.18.0 install recorded is carried over as it is and never
|
|
46
|
+
* written anew: doflow.lock records each harness's MCP selection now, and the old list is read only
|
|
47
|
+
* as a fallback for a harness the lock lacks. `managedResources` is the optional ownership ledger
|
|
48
|
+
* for fine-grained Codex resources. Each record uses {target,scope,kind,identity,sourceVersion,
|
|
49
|
+
* fingerprint,selection,recoveryPoint}. Omit it to preserve a ledger written by a newer command.
|
|
50
|
+
*/
|
|
51
|
+
function writeManifest({ scopeRoot, claudeDir, scriptVersion, operation, repoRoot, backupId = '', tools, date, dryRun = false, sourceCommit, managedResources } ) {
|
|
52
|
+
if (!scopeRoot && !claudeDir) throw new Error('scopeRoot is required for lifecycle metadata');
|
|
53
|
+
const file = scopeRoot ? canonicalManifestPath(scopeRoot) : manifestPath(claudeDir );
|
|
54
|
+
if (dryRun) return file;
|
|
55
|
+
|
|
56
|
+
// Preserve per-tool last_updated for tools NOT part of this operation (matches sync.sh's
|
|
57
|
+
// incremental jq merge, which only touches the tools passed to write_manifest).
|
|
58
|
+
let existingTools = {};
|
|
59
|
+
let existingMcpServers ;
|
|
60
|
+
let existingManagedResources ;
|
|
61
|
+
const existingFile = file;
|
|
62
|
+
if (existingFile && fs.existsSync(existingFile)) {
|
|
63
|
+
try {
|
|
64
|
+
const existing = JSON.parse(fs.readFileSync(existingFile, 'utf8'));
|
|
65
|
+
existingTools = existing.tools || {};
|
|
66
|
+
existingMcpServers = existing.mcp_servers;
|
|
67
|
+
// `managedResources` is accepted as a short-lived compatibility alias for manifests
|
|
68
|
+
// written by pre-release lifecycle work. New writes use the established snake_case
|
|
69
|
+
// top-level convention, while resource field names stay camelCase as specified by the
|
|
70
|
+
// ownership contract.
|
|
71
|
+
existingManagedResources = existing.managed_resources ?? existing.managedResources;
|
|
72
|
+
} catch { /* start fresh */ }
|
|
73
|
+
}
|
|
74
|
+
const ts = date.toISOString().replace(/\.\d+Z$/, 'Z');
|
|
75
|
+
const toolsOut = { ...existingTools };
|
|
76
|
+
for (const t of tools) toolsOut[t] = { installed: true, last_updated: ts };
|
|
77
|
+
|
|
78
|
+
const manifest = {
|
|
79
|
+
script_version: scriptVersion,
|
|
80
|
+
last_operation: operation,
|
|
81
|
+
last_run: ts,
|
|
82
|
+
source_path: repoRoot,
|
|
83
|
+
source_commit: sourceCommit ?? gitSourceCommit(repoRoot),
|
|
84
|
+
last_backup_id: backupId,
|
|
85
|
+
tools: toolsOut,
|
|
86
|
+
mcp_servers: existingMcpServers,
|
|
87
|
+
};
|
|
88
|
+
const resources = managedResources ?? existingManagedResources;
|
|
89
|
+
if (resources !== undefined) manifest.managed_resources = resources;
|
|
90
|
+
|
|
91
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
92
|
+
// Security: the temp file must live next to `file` itself, not a shared os.tmpdir() — a
|
|
93
|
+
// world-writable /tmp plus a PID+timestamp-derived (i.e. guessable) name is a symlink-race
|
|
94
|
+
// waiting to happen (plant a symlink at the predicted path, the write clobbers its target).
|
|
95
|
+
// 'wx' (O_CREAT|O_EXCL) refuses to open through an existing symlink or file at all, and being
|
|
96
|
+
// on the same filesystem as `file` makes the final rename a guaranteed atomic same-fs op
|
|
97
|
+
// (os.tmpdir() offered no such guarantee — e.g. containers where /tmp is a separate tmpfs mount).
|
|
98
|
+
const tmp = path.join(path.dirname(file), `.install-manifest-${process.pid}-${date.getTime()}.json.tmp`);
|
|
99
|
+
fs.writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx' });
|
|
100
|
+
fs.renameSync(tmp, file); // atomic on the same filesystem, matches sync.sh's mktemp + mv
|
|
101
|
+
return file;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** null if no manifest yet. */
|
|
105
|
+
function readManifestFile(file ) {
|
|
106
|
+
if (!fs.existsSync(file)) return null;
|
|
107
|
+
try {
|
|
108
|
+
const m = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
109
|
+
return {
|
|
110
|
+
operation: m.last_operation ?? 'none',
|
|
111
|
+
lastRun: m.last_run ?? 'none',
|
|
112
|
+
sourceCommit: m.source_commit ?? 'none',
|
|
113
|
+
backupId: m.last_backup_id ?? 'none',
|
|
114
|
+
tools: m.tools ?? {},
|
|
115
|
+
scriptVersion: m.script_version ?? 'none',
|
|
116
|
+
mcpServers: m.mcp_servers ?? null,
|
|
117
|
+
// Older manifests have no ownership ledger. Returning an empty list gives lifecycle
|
|
118
|
+
// callers a migration-safe baseline without changing their legacy install state.
|
|
119
|
+
managedResources: Array.isArray(m.managed_resources)
|
|
120
|
+
? m.managed_resources
|
|
121
|
+
: (Array.isArray(m.managedResources) ? m.managedResources : []),
|
|
122
|
+
};
|
|
123
|
+
} catch {
|
|
124
|
+
return {
|
|
125
|
+
operation: 'error', lastRun: 'error', sourceCommit: 'error', backupId: 'error',
|
|
126
|
+
tools: {}, scriptVersion: 'error', mcpServers: null, managedResources: [],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Read only the canonical scope-neutral manifest used by lifecycle commands. */
|
|
132
|
+
function readInstallManifest({ scopeRoot } ) {
|
|
133
|
+
if (!scopeRoot) throw new Error('scopeRoot is required');
|
|
134
|
+
return readManifestFile(canonicalManifestPath(scopeRoot));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Read only the legacy path; neutral-state migration uses this deliberately. */
|
|
138
|
+
function readManifest(claudeDir ) {
|
|
139
|
+
return readManifestFile(manifestPath(claudeDir));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
LEGACY_MANIFEST_FILE_NAME, MANIFEST_FILE_NAME, manifestPath, canonicalManifestPath,
|
|
144
|
+
writeManifest, readManifest, readInstallManifest,
|
|
145
|
+
};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// mcp.js — MCP server selection: which of the registry's servers each harness gets, from --mcp,
|
|
3
|
+
// the interactive checkbox, doflow.lock, the ledger or a 1.18.0 manifest. Writing a harness's MCP
|
|
4
|
+
// file is that harness's adapter's job (src/adapters/<id>/), never this module's.
|
|
5
|
+
//
|
|
6
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
7
|
+
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
8
|
+
// caller's `require('../install/mcp.js')` keeps working while the tree is still `.js`. The
|
|
9
|
+
// `../registry` and `../state/lockfile` requires stay bare while those modules are still `.js`.
|
|
10
|
+
const { readSyncBlocking } = require('../helper/prompt.js');
|
|
11
|
+
const { selectMcpServers, nativeMcpCatalog, mcpCapable } = require('../registry/index.js');
|
|
12
|
+
const { pinnedSelections } = require('../state/lockfile.js');
|
|
13
|
+
|
|
14
|
+
const ESC = String.fromCharCode(27);
|
|
15
|
+
const CTRL_C = String.fromCharCode(3);
|
|
16
|
+
|
|
17
|
+
/** `registry` is a loaded registry (src/registry#loadRegistry); returns server names in registry
|
|
18
|
+
* declaration order. */
|
|
19
|
+
function readAllServers(registry ) {
|
|
20
|
+
return nativeMcpCatalog(selectMcpServers(registry)).allServers;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Parse `--mcp`: `all` and `none` are keywords that cannot be mixed with names or each other, an
|
|
24
|
+
* empty list and an unknown name are errors, names are deduplicated. `null` when the flag is absent. */
|
|
25
|
+
function parseMcpFlag(requested , catalogIds ) {
|
|
26
|
+
if (!requested) return null;
|
|
27
|
+
const keywords = requested.filter((s) => s === 'all' || s === 'none');
|
|
28
|
+
if (keywords.length) {
|
|
29
|
+
if (keywords.length !== requested.length) {
|
|
30
|
+
throw new Error(`--mcp keyword '${keywords[0]}' cannot be combined with server names`);
|
|
31
|
+
}
|
|
32
|
+
if (new Set(requested).size > 1) {
|
|
33
|
+
throw new Error("Choose either '--mcp all' or '--mcp none', not both");
|
|
34
|
+
}
|
|
35
|
+
return keywords[0] === 'all' ? [...catalogIds] : [];
|
|
36
|
+
}
|
|
37
|
+
if (requested.length === 0) {
|
|
38
|
+
throw new Error("--mcp requires at least one server; use '--mcp none' for an explicit empty selection");
|
|
39
|
+
}
|
|
40
|
+
const invalid = requested.filter((s) => !catalogIds.includes(s));
|
|
41
|
+
if (invalid.length) {
|
|
42
|
+
throw new Error(`Unknown MCP server(s): ${invalid.join(', ')} (valid: ${catalogIds.join(', ')})`);
|
|
43
|
+
}
|
|
44
|
+
return [...new Set(requested)];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const holdsRows = (ledger , harness ) => (ledger?.resources ?? []).some((row ) => row.harness === harness);
|
|
48
|
+
|
|
49
|
+
/** Ids of `lists` that the catalog still declares, as one union in registry order. */
|
|
50
|
+
const catalogUnion = (catalogIds , lists ) => catalogIds.filter((id) => lists.some((ids) => ids.includes(id)));
|
|
51
|
+
|
|
52
|
+
/** 1.18.0 wrote lock rows only for claude and codex, and only non-empty ones. Any other row was
|
|
53
|
+
* written by a later run, which already settled which of that harness's entries are DoFlow's. */
|
|
54
|
+
const LEGACY_LOCK_HARNESSES = ['claude', 'codex'];
|
|
55
|
+
|
|
56
|
+
function settledByLaterRun(rows , harness ) {
|
|
57
|
+
return harness in rows && (!LEGACY_LOCK_HARNESSES.includes(harness) || rows[harness].length === 0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The servers a 1.18.0 install recorded writing for this harness, from state no later run writes:
|
|
61
|
+
* the manifest's MCP list, Antigravity's rows that carry no fingerprint, and Copilot's whole-map
|
|
62
|
+
* registration row, which covers the manifest's list or, with none, the catalog 1.18.0 wrote. */
|
|
63
|
+
function legacyRecordedIds({ catalogIds, ledger, harness, manifestServers } ) {
|
|
64
|
+
const rows = (ledger?.resources ?? []).filter((row ) => row.harness === harness);
|
|
65
|
+
const ids = [...(manifestServers ?? [])];
|
|
66
|
+
for (const row of rows) if (row.kind === 'mcp-server' && (row.fingerprint ?? null) === null) ids.push(row.identity);
|
|
67
|
+
if (!manifestServers && rows.some((row ) => row.ownershipIdentity === `${harness}:mcp:registration`)) ids.push(...catalogIds);
|
|
68
|
+
return catalogUnion(catalogIds, [ids]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Per MCP-capable harness, the servers DoFlow may adopt when their entries already equal its own:
|
|
72
|
+
* only what a 1.18.0 install recorded for a harness the ledger holds, and only until a later run
|
|
73
|
+
* has recorded the harness's own selection. An entry a user wrote is never adoptable merely because
|
|
74
|
+
* DoFlow installed something else for that harness. Retired ids are dropped. */
|
|
75
|
+
function adoptableMcpIds({ registry, lock, ledger, harnesses, manifestServers = null } ) {
|
|
76
|
+
const catalogIds = readAllServers(registry);
|
|
77
|
+
const rows = pinnedSelections(lock);
|
|
78
|
+
return Object.fromEntries(harnesses.filter((harness) => mcpCapable(registry, harness)).map((harness) => {
|
|
79
|
+
if (!holdsRows(ledger, harness) || settledByLaterRun(rows, harness)) return [harness, []];
|
|
80
|
+
return [harness, legacyRecordedIds({ catalogIds, ledger, harness, manifestServers })];
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Servers another harness of this scope still has recorded: the union of the lock rows of the lock's
|
|
85
|
+
* harnesses that are not targeted, registry order. */
|
|
86
|
+
function retainedMcpIds(catalogIds , rows , targets ) {
|
|
87
|
+
return catalogUnion(catalogIds, Object.entries(rows).filter(([harness]) => !targets.includes(harness)).map(([, ids]) => ids));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Decide each targeted MCP-capable harness's servers. Per harness, the first step that applies:
|
|
92
|
+
* 1. --mcp <list>|all|none -> the parsed ids (flag)
|
|
93
|
+
* 2. install on a real TTY -> one checkbox for every harness; a null answer falls through (prompt)
|
|
94
|
+
* 3. a lock row -> that row (recorded)
|
|
95
|
+
* 4. ledger rows -> 'keep': the servers the harness owns now (kept)
|
|
96
|
+
* 5. a 1.18.0 manifest list -> that list (manifest)
|
|
97
|
+
* 6. otherwise -> none (default)
|
|
98
|
+
* Lock and manifest ids the registry retired are dropped and reported once through `onStale`; an
|
|
99
|
+
* explicit --mcp naming one stays an error. Pure apart from `promptFn` and `onStale`.
|
|
100
|
+
*/
|
|
101
|
+
function resolveMcpSelections({
|
|
102
|
+
cmd, requested, targets, registry, lock, ledger, manifestServers = null, interactive = false, promptFn, onStale,
|
|
103
|
+
} ) {
|
|
104
|
+
const catalogIds = readAllServers(registry);
|
|
105
|
+
const flagged = parseMcpFlag(requested, catalogIds);
|
|
106
|
+
const rows = pinnedSelections(lock);
|
|
107
|
+
const capable = targets.filter((harness ) => mcpCapable(registry, harness));
|
|
108
|
+
const retired = new Set ();
|
|
109
|
+
const known = (ids ) => {
|
|
110
|
+
for (const id of ids) if (!catalogIds.includes(id)) retired.add(id);
|
|
111
|
+
return catalogUnion(catalogIds, [ids]);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
let prompted = null;
|
|
115
|
+
if (!flagged && cmd === 'install' && interactive && capable.length) {
|
|
116
|
+
const recorded = capable.filter((harness ) => harness in rows).map((harness ) => rows[harness]);
|
|
117
|
+
const seed = recorded.length ? catalogUnion(catalogIds, recorded.map(known))
|
|
118
|
+
: manifestServers ? known(manifestServers) : [...catalogIds];
|
|
119
|
+
prompted = promptFn(catalogIds, seed);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const pick = (harness ) => {
|
|
123
|
+
if (flagged) return [flagged, 'flag'];
|
|
124
|
+
if (prompted !== null) return [prompted, 'prompt'];
|
|
125
|
+
if (harness in rows) return [known(rows[harness]), 'recorded'];
|
|
126
|
+
if (holdsRows(ledger, harness)) return ['keep', 'kept'];
|
|
127
|
+
if (manifestServers) return [known(manifestServers), 'manifest'];
|
|
128
|
+
return [[], 'default'];
|
|
129
|
+
};
|
|
130
|
+
const selections = {};
|
|
131
|
+
const sources = {};
|
|
132
|
+
for (const harness of capable) [selections[harness], sources[harness]] = pick(harness);
|
|
133
|
+
for (const [harness, ids] of Object.entries(rows)) if (!targets.includes(harness)) known(ids );
|
|
134
|
+
if (retired.size && onStale) onStale([...retired].sort());
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
selections,
|
|
138
|
+
sources,
|
|
139
|
+
adoptable: adoptableMcpIds({ registry, lock, ledger, harnesses: capable, manifestServers }),
|
|
140
|
+
retainedMcpIds: retainedMcpIds(catalogIds, rows, targets),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The selections a scope has recorded, for readers that never prompt (reconcile, status, inventory):
|
|
145
|
+
* each targeted MCP-capable harness's lock row, or 'keep' when the lock has none. Retired ids are
|
|
146
|
+
* dropped silently. */
|
|
147
|
+
function recordedMcpSelections({ registry, lock, ledger, targets, manifestServers = null } ) {
|
|
148
|
+
const catalogIds = readAllServers(registry);
|
|
149
|
+
const rows = pinnedSelections(lock);
|
|
150
|
+
const capable = targets.filter((harness ) => mcpCapable(registry, harness));
|
|
151
|
+
return {
|
|
152
|
+
selections: Object.fromEntries(capable.map((harness ) => [harness,
|
|
153
|
+
harness in rows ? catalogUnion(catalogIds, [rows[harness]]) : 'keep'])),
|
|
154
|
+
adoptable: adoptableMcpIds({ registry, lock, ledger, harnesses: capable, manifestServers }),
|
|
155
|
+
retainedMcpIds: retainedMcpIds(catalogIds, rows, targets),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const KEY = {
|
|
160
|
+
UP: `${ESC}[A`,
|
|
161
|
+
DOWN: `${ESC}[B`,
|
|
162
|
+
SPACE: ' ',
|
|
163
|
+
ENTER_CR: '\r',
|
|
164
|
+
ENTER_LF: '\n',
|
|
165
|
+
CTRL_C,
|
|
166
|
+
ALL: 'a',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Block for one raw-mode keypress, via prompt.js's readSyncBlocking (retries the EAGAIN-while-TTY
|
|
171
|
+
* case, bounded by a deadline for a genuinely unusable fd). Returns the decoded chunk, or null if
|
|
172
|
+
* the fd is unusable.
|
|
173
|
+
*/
|
|
174
|
+
function readKeypress(buf ) {
|
|
175
|
+
try {
|
|
176
|
+
const n = readSyncBlocking(0, buf);
|
|
177
|
+
return buf.toString('utf8', 0, n);
|
|
178
|
+
} catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Synchronous raw-mode checkbox prompt (arrow keys / j-k to move, space to toggle, 'a' to
|
|
185
|
+
* toggle-all, enter to confirm). Matches src/prompt.js's synchronous-read style — this CLI has no
|
|
186
|
+
* async control flow to hang off, so a real TTY read loop is built directly on fs.readSync(0, ...),
|
|
187
|
+
* the same primitive confirm()/promptLine() already use.
|
|
188
|
+
* Returns selected server names, or null if no usable TTY (caller falls back).
|
|
189
|
+
*/
|
|
190
|
+
function promptMcpCheckbox(servers , initialSelected , message = 'Select MCP servers to install:') {
|
|
191
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
192
|
+
if (servers.length === 0) return [];
|
|
193
|
+
|
|
194
|
+
const selected = new Set(initialSelected);
|
|
195
|
+
let cursor = 0;
|
|
196
|
+
const help = ' (up/down or j/k move, space toggle, a toggle-all, enter confirm)';
|
|
197
|
+
|
|
198
|
+
const render = (first ) => {
|
|
199
|
+
if (!first) process.stdout.write(`${ESC}[${servers.length + 2}A`);
|
|
200
|
+
console.log(message);
|
|
201
|
+
console.log(help);
|
|
202
|
+
for (let i = 0; i < servers.length; i++) {
|
|
203
|
+
const mark = selected.has(servers[i]) ? '[x]' : '[ ]';
|
|
204
|
+
const pointer = i === cursor ? '>' : ' ';
|
|
205
|
+
console.log(`${pointer} ${mark} ${servers[i]}`);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
let wasRaw = false;
|
|
210
|
+
let aborted = false;
|
|
211
|
+
try {
|
|
212
|
+
wasRaw = Boolean(process.stdin.isRaw);
|
|
213
|
+
process.stdin.setRawMode(true);
|
|
214
|
+
render(true);
|
|
215
|
+
const buf = Buffer.alloc(16);
|
|
216
|
+
for (;;) {
|
|
217
|
+
const chunk = readKeypress(buf);
|
|
218
|
+
if (chunk === null) {
|
|
219
|
+
aborted = true;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
if (chunk === KEY.ENTER_CR || chunk === KEY.ENTER_LF) break;
|
|
223
|
+
if (chunk === KEY.CTRL_C) {
|
|
224
|
+
console.log('\n[INFO] Aborted.');
|
|
225
|
+
process.exit(130);
|
|
226
|
+
}
|
|
227
|
+
if (chunk === KEY.UP || chunk === 'k') cursor = (cursor - 1 + servers.length) % servers.length;
|
|
228
|
+
else if (chunk === KEY.DOWN || chunk === 'j') cursor = (cursor + 1) % servers.length;
|
|
229
|
+
else if (chunk === KEY.SPACE) {
|
|
230
|
+
const s = servers[cursor];
|
|
231
|
+
if (selected.has(s)) selected.delete(s);
|
|
232
|
+
else selected.add(s);
|
|
233
|
+
} else if (chunk === KEY.ALL) {
|
|
234
|
+
if (selected.size === servers.length) selected.clear();
|
|
235
|
+
else for (const s of servers) selected.add(s);
|
|
236
|
+
}
|
|
237
|
+
render(false);
|
|
238
|
+
}
|
|
239
|
+
} finally {
|
|
240
|
+
process.stdin.setRawMode(wasRaw);
|
|
241
|
+
}
|
|
242
|
+
console.log('');
|
|
243
|
+
if (aborted) return null; // fd went unusable mid-prompt — caller falls back to manifest/all
|
|
244
|
+
return servers.filter((s) => selected.has(s));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
readAllServers,
|
|
249
|
+
parseMcpFlag,
|
|
250
|
+
resolveMcpSelections,
|
|
251
|
+
recordedMcpSelections,
|
|
252
|
+
adoptableMcpIds,
|
|
253
|
+
promptMcpCheckbox,
|
|
254
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
4
|
+
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
5
|
+
// caller's `require('../install/paths.js')` keeps working while the rest of the tree is still `.js`.
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const DOFLOW_DIR_NAME = '.doflow';
|
|
9
|
+
const BACKUP_DIR_NAME = 'backups';
|
|
10
|
+
const INSTALL_MANIFEST_FILE_NAME = '.install-manifest.json';
|
|
11
|
+
|
|
12
|
+
// Explicit migration bridge, mirroring manifest.js's: before 7de6d5f ("centralize lifecycle
|
|
13
|
+
// metadata under .doflow") both the install manifest and the backup root were anchored to the
|
|
14
|
+
// Claude harness directory, so restore points landed in `<scopeRoot>/.claude/backups` — $HOME
|
|
15
|
+
// globally, the project root otherwise. That commit moved the write location to `.doflow/` and
|
|
16
|
+
// bridged the manifest, leaving every pre-move restore point unreachable. The two names below
|
|
17
|
+
// exist so the READ side of rollback/list-backups can still see what the move left behind.
|
|
18
|
+
// Nothing may ever write, prune or delete here: those directories are a user's only recovery
|
|
19
|
+
// material and DoFlow no longer owns their lifecycle.
|
|
20
|
+
const LEGACY_BACKUP_ANCHOR_DIR_NAME = '.claude';
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
/** Read-only legacy backup location for a scope. Never a write or prune target — see the note above. */
|
|
35
|
+
function legacyBackupReadRoot({ scopeRoot } ) {
|
|
36
|
+
if (typeof scopeRoot !== 'string' || !scopeRoot) throw new Error('scopeRoot is required');
|
|
37
|
+
return path.join(path.resolve(scopeRoot), LEGACY_BACKUP_ANCHOR_DIR_NAME, BACKUP_DIR_NAME);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** True for `<anything>/.claude/backups` — used to refuse writes/prunes against the legacy root. */
|
|
41
|
+
function isLegacyBackupReadRoot(candidate ) {
|
|
42
|
+
if (typeof candidate !== 'string' || !candidate) return false;
|
|
43
|
+
const resolved = path.resolve(candidate);
|
|
44
|
+
return path.basename(resolved) === BACKUP_DIR_NAME
|
|
45
|
+
&& path.basename(path.dirname(resolved)) === LEGACY_BACKUP_ANCHOR_DIR_NAME;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Inverse of `doflowPaths().backupRoot`: recover the scope root from a canonical backup root so a
|
|
49
|
+
* read path handed only that root can still derive the legacy one. Returns null for any path that
|
|
50
|
+
* is not shaped `<scopeRoot>/.doflow/backups` (a test fixture, a hand-passed directory) — the
|
|
51
|
+
* bridge then stays inert rather than inventing a sibling `.claude` next to an arbitrary path. */
|
|
52
|
+
function scopeRootFromCanonicalBackupRoot(backupRoot ) {
|
|
53
|
+
if (typeof backupRoot !== 'string' || !backupRoot) return null;
|
|
54
|
+
const resolved = path.resolve(backupRoot);
|
|
55
|
+
if (path.basename(resolved) !== BACKUP_DIR_NAME) return null;
|
|
56
|
+
const doflowRoot = path.dirname(resolved);
|
|
57
|
+
if (path.basename(doflowRoot) !== DOFLOW_DIR_NAME) return null;
|
|
58
|
+
return path.dirname(doflowRoot);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Resolve DoFlow-owned lifecycle metadata without anchoring it to a harness directory. */
|
|
62
|
+
function doflowPaths({ scopeRoot } ) {
|
|
63
|
+
if (typeof scopeRoot !== 'string' || !scopeRoot) throw new Error('scopeRoot is required');
|
|
64
|
+
const root = path.resolve(scopeRoot);
|
|
65
|
+
const doflowRoot = path.join(root, DOFLOW_DIR_NAME);
|
|
66
|
+
return {
|
|
67
|
+
scopeRoot: root,
|
|
68
|
+
doflowRoot,
|
|
69
|
+
backupRoot: path.join(doflowRoot, BACKUP_DIR_NAME),
|
|
70
|
+
legacyBackupReadRoot: legacyBackupReadRoot({ scopeRoot: root }),
|
|
71
|
+
manifestPath: path.join(doflowRoot, INSTALL_MANIFEST_FILE_NAME),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
DOFLOW_DIR_NAME,
|
|
77
|
+
BACKUP_DIR_NAME,
|
|
78
|
+
INSTALL_MANIFEST_FILE_NAME,
|
|
79
|
+
LEGACY_BACKUP_ANCHOR_DIR_NAME,
|
|
80
|
+
doflowPaths,
|
|
81
|
+
legacyBackupReadRoot,
|
|
82
|
+
isLegacyBackupReadRoot,
|
|
83
|
+
scopeRootFromCanonicalBackupRoot,
|
|
84
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// targets.js — target validation + install-dir resolution.
|
|
3
|
+
// PARITY: sync.sh is global-only — $HOME/.{claude,codex,gemini}, hardcoded, NOT CLAUDE_CONFIG_DIR.
|
|
4
|
+
// doflow extends this with a second, DoFlow-only scope: project-local install (this repo's own
|
|
5
|
+
// .claude/ is a real precedent — Claude Code, Codex, and Copilot all support project-scoped config
|
|
6
|
+
// alongside global, see agent-docs/research/context-setup-claude-codex-copilot.md).
|
|
7
|
+
//
|
|
8
|
+
// Scope is explicit, never inferred: -g/--global -> $HOME-based (sync.sh parity); default (no -g) ->
|
|
9
|
+
// rooted at an explicit project path, defaulting to cwd ('.') when no path is given. The parity
|
|
10
|
+
// harness must pass -g for doflow, since sync.sh has no project mode to compare against.
|
|
11
|
+
//
|
|
12
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
13
|
+
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
14
|
+
// caller's `require('../install/targets.js')` keeps working while the rest of the tree is still `.js`.
|
|
15
|
+
const os = require('node:os');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
|
|
18
|
+
const VALID = ['claude', 'codex', 'gemini', 'copilot', 'kiro', 'opencode', 'pi', 'antigravity'];
|
|
19
|
+
|
|
20
|
+
/** The target used when `--target` is omitted. Deliberately one harness, not all of VALID: an
|
|
21
|
+
* install with no flag should touch the one tool the user almost certainly has, not write
|
|
22
|
+
* configuration into every harness DoFlow knows about — several of which may not be installed at
|
|
23
|
+
* all. That mattered less when VALID was three entries and matters more as it grows. Ask for the
|
|
24
|
+
* rest explicitly with `--target claude,codex,gemini`. */
|
|
25
|
+
const DEFAULT_TARGETS = ['claude'];
|
|
26
|
+
|
|
27
|
+
const TARGET_ALIASES = {
|
|
28
|
+
agy: 'antigravity',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Default to DEFAULT_TARGETS; validate any explicitly requested. */
|
|
32
|
+
function resolveTargets(requested ) {
|
|
33
|
+
const rawTargets = requested && requested.length ? requested : [...DEFAULT_TARGETS];
|
|
34
|
+
const targets = rawTargets.map((t) => TARGET_ALIASES[t] || t);
|
|
35
|
+
for (const t of targets) {
|
|
36
|
+
if (!VALID.includes(t)) {
|
|
37
|
+
throw new Error(`Unknown target: '${t}' (valid: ${VALID.join(', ')})`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return [...new Set(targets)];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Map tool -> install root.
|
|
50
|
+
* global=true -> $HOME/.{claude,codex,gemini,copilot,kiro,pi}, $HOME/.config/opencode
|
|
51
|
+
* (sync.sh parity for claude/codex/gemini; the four new harnesses follow
|
|
52
|
+
* each one's own documented global root — see below)
|
|
53
|
+
* global=false (dflt) -> <resolved projectRoot>/.{claude,codex,kiro,opencode,pi}, but .agents for
|
|
54
|
+
* gemini and .github for copilot — both deliberately asymmetric with their own global-scope
|
|
55
|
+
* directory, for reasons native to each harness (see per-key notes below).
|
|
56
|
+
*
|
|
57
|
+
* gemini -> project-scoped Gemini config follows the Antigravity customization convention, not
|
|
58
|
+
* `.gemini/`, so it is asymmetric with its own global directory and with the other
|
|
59
|
+
* targets.
|
|
60
|
+
* copilot -> Copilot has no single native root the way Claude's `.claude` is one: instructions,
|
|
61
|
+
* skills, agents, and MCP each live under a different path (see design.md §4's
|
|
62
|
+
* native-surfaces table). `.github` is the project root DoFlow treats as Copilot's
|
|
63
|
+
* "home" here because that is where `copilot-instructions.md` and `.github/agents`
|
|
64
|
+
* live; `~/.copilot` is the equivalent global root.
|
|
65
|
+
* kiro -> `.kiro` / `~/.kiro` is the one native root that actually holds everything Kiro
|
|
66
|
+
* reads (steering, agents, hooks, settings/mcp.json), so — unlike copilot — this is
|
|
67
|
+
* the clean, symmetric case.
|
|
68
|
+
* opencode -> Global config is `~/.config/opencode`, NOT `~/.opencode/` — a path DoFlow's own
|
|
69
|
+
* docs asserted incorrectly for several releases (see src/adapters/opencode/index.js).
|
|
70
|
+
* Project scope is `.opencode`, asymmetric with the global path the same way gemini's
|
|
71
|
+
* is.
|
|
72
|
+
* pi -> Global config is `~/.pi/agent` — note the `agent/` segment; `~/.pi/settings.json`
|
|
73
|
+
* is not a location Pi reads. Project scope is `.pi` (see
|
|
74
|
+
* src/adapters/pi/index.js's own `nativePaths()`, which this mirrors exactly).
|
|
75
|
+
*/
|
|
76
|
+
function toolDirs(opts = {}) {
|
|
77
|
+
const { global = false, projectRoot = '.' } = opts;
|
|
78
|
+
const root = global ? os.homedir() : path.resolve(projectRoot);
|
|
79
|
+
return {
|
|
80
|
+
claude: path.join(root, '.claude'),
|
|
81
|
+
codex: path.join(root, '.codex'),
|
|
82
|
+
gemini: global ? path.join(root, '.gemini') : path.join(root, '.agents'),
|
|
83
|
+
copilot: global ? path.join(root, '.copilot') : path.join(root, '.github'),
|
|
84
|
+
kiro: path.join(root, '.kiro'),
|
|
85
|
+
opencode: global ? path.join(root, '.config', 'opencode') : path.join(root, '.opencode'),
|
|
86
|
+
pi: global ? path.join(root, '.pi', 'agent') : path.join(root, '.pi'),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { VALID, DEFAULT_TARGETS, resolveTargets, toolDirs };
|