@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,279 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
// Pi adapter — a marker-merged instruction file plus copy-tree materialisation, the same shape
|
|
6
|
+
// Claude/Codex/Gemini already use for their own skills trees.
|
|
7
|
+
//
|
|
8
|
+
// Skills materialise into Pi's own directories (`~/.pi/agent/skills` global, `.pi/skills` project)
|
|
9
|
+
// via the shared copy-tree engine, rather than pointing Pi's settings.json at DoFlow's already
|
|
10
|
+
// -installed `~/.claude/skills` tree. Whether Pi discovers that directory unaided, or needs a
|
|
11
|
+
// `settings.json` entry naming it, is untested here — that is task B.3's job, not this adapter's;
|
|
12
|
+
// this adapter deliberately leaves settings.json untouched.
|
|
13
|
+
//
|
|
14
|
+
// MCP servers are merged per server into Pi's mcp.json (.pi/mcp.json project; ~/.pi/agent/mcp.json
|
|
15
|
+
// or $PI_CODING_AGENT_DIR/mcp.json user) by ./mcp.ts; Pi reads it through its built-in MCP from
|
|
16
|
+
// 0.99.0. Hooks are absent — Pi lifecycle handlers are TypeScript modules registered via pi.on(),
|
|
17
|
+
// executing with full system permissions, which is a different trust model from the shell scripts
|
|
18
|
+
// DoFlow ships.
|
|
19
|
+
//
|
|
20
|
+
// Evidence: https://pi.dev/docs/latest/settings, https://pi.dev/docs/latest/skills,
|
|
21
|
+
// https://pi.dev/docs/latest/quickstart, https://pi.dev/docs/latest/extensions,
|
|
22
|
+
// https://pi.dev/docs/latest/mcp
|
|
23
|
+
const fs = require('node:fs');
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
|
|
26
|
+
const { MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
|
|
27
|
+
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, sharedTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, sourceDirFor } = require('../copy-tree.js');
|
|
28
|
+
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
29
|
+
const { mcpRows, discoverPiMcp, ownedPiMcpIds, planPiMcp, applyPiMcp, verifyPiMcp } = require('./mcp.js');
|
|
30
|
+
|
|
31
|
+
const HARNESS = 'pi';
|
|
32
|
+
const AGENT_DIR_NOTICE = 'PI_CODING_AGENT_DIR is set: Pi reads its whole agent dir from it, but DoFlow moves only mcp.json there; skills and AGENTS.md stay in ~/.pi/agent.';
|
|
33
|
+
|
|
34
|
+
// Native path facts live in core/registry/harnesses.json under this harness's "paths" section and
|
|
35
|
+
// resolve through the shared harness-paths resolver; nothing below hardcodes them.
|
|
36
|
+
// createPiAdapter({ declaredPaths }) is the injection point buildAdapterRegistry() uses;
|
|
37
|
+
// module-level exports delegate to a default-configured instance so direct callers keep their
|
|
38
|
+
// historical shape.
|
|
39
|
+
function createPiAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS], env = process.env } = {}) {
|
|
40
|
+
// Parity note: Pi's historical nativePaths() derived even its user-scope root from scopeRoot
|
|
41
|
+
// (the CLI passes $HOME there for -g installs), never from a separate homeDir argument — so the
|
|
42
|
+
// resolver is fed no homeDir and "root" keeps resolving exactly as before.
|
|
43
|
+
function nativePaths({ scope, scopeRoot } = {}) {
|
|
44
|
+
const paths = resolveHarnessPaths(declaredPaths, { scope, scopeRoot });
|
|
45
|
+
// Pi's user-scope mcp.json follows $PI_CODING_AGENT_DIR when set. It is not declared in the
|
|
46
|
+
// registry because the path schema has no environment base (src/helper/harness-paths.js); only
|
|
47
|
+
// mcp moves, skills and AGENTS.md keep their declared locations.
|
|
48
|
+
if ((scope === 'user' || scope === 'global') && agentDirOverride()) paths.mcp = path.join(path.resolve(agentDirOverride()), 'mcp.json');
|
|
49
|
+
return paths;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function agentDirOverride() {
|
|
53
|
+
const agentDir = env.PI_CODING_AGENT_DIR;
|
|
54
|
+
return typeof agentDir === 'string' && agentDir.trim() !== '' ? agentDir.trim() : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function discover({ scope, scopeRoot, mcp = [], ledger, context = {}, fsImpl = fs } ) {
|
|
58
|
+
const paths = nativePaths({ scope, scopeRoot });
|
|
59
|
+
const instruction = fsImpl.existsSync(paths.instruction) ? fsImpl.readFileSync(paths.instruction, 'utf8') : null;
|
|
60
|
+
const rows = mcpRows(ledger);
|
|
61
|
+
const snapshot = discoverPiMcp({ selected: mcp, rows, file: paths.mcp, fsImpl });
|
|
62
|
+
// Pi reads mcp.json only for a selection or a row; the names in it are read here regardless.
|
|
63
|
+
const file = snapshot[paths.mcp] ?? discoverPiMcp({ selected: [true], rows: [], file: paths.mcp, fsImpl })[paths.mcp];
|
|
64
|
+
const members = file?.exists && file.doc?.ok ? file.doc.servers?.members ?? [] : null;
|
|
65
|
+
const mcpConfigured = members && members.map((member ) => member.key).sort();
|
|
66
|
+
return { paths, instruction, mcp: snapshot, mcpOwned: ownedPiMcpIds({ rows, snapshot }), mcpConfigured };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function render({ content = '' } = {}) {
|
|
70
|
+
return `${MARKER_START}\n${String(content).trimEnd()}\n${MARKER_END}\n`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Replace only the span between DoFlow's markers. An AGENTS.md with no DoFlow section is refused
|
|
74
|
+
* rather than appended to — Pi shares AGENTS.md with every other agent that reads it, so where
|
|
75
|
+
* DoFlow's content belongs is its owner's decision. Add an empty marker pair to opt in. */
|
|
76
|
+
function managedInstruction(existing , rendered ) {
|
|
77
|
+
if (existing === null) return { ok: true, operation: 'create', content: rendered };
|
|
78
|
+
const start = existing.indexOf(MARKER_START);
|
|
79
|
+
const end = existing.indexOf(MARKER_END);
|
|
80
|
+
if (start === -1 && end === -1) return { ok: false, conflict: 'AGENTS.md exists without a DoFlow managed section' };
|
|
81
|
+
if (start === -1 || end === -1 || end < start) return { ok: false, conflict: 'AGENTS.md has malformed DoFlow managed-section markers' };
|
|
82
|
+
const content = `${existing.slice(0, start)}${rendered}${existing.slice(end + MARKER_END.length).replace(/^\n?/, '')}`;
|
|
83
|
+
return { ok: true, operation: content === existing ? 'none' : 'merge', content };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function strippedInstruction(existing ) {
|
|
87
|
+
if (typeof existing !== 'string') return null;
|
|
88
|
+
const start = existing.indexOf(MARKER_START);
|
|
89
|
+
const end = existing.indexOf(MARKER_END);
|
|
90
|
+
if (start === -1 || end === -1 || end < start) return null;
|
|
91
|
+
let after = end + MARKER_END.length;
|
|
92
|
+
if (existing[after] === '\n') after += 1;
|
|
93
|
+
return `${existing.slice(0, start)}${existing.slice(after)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---- copy-tree assets (skills) ----
|
|
97
|
+
|
|
98
|
+
function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing, fsImpl = fs } ) {
|
|
99
|
+
const paths = nativePaths({ scope, scopeRoot });
|
|
100
|
+
const changes = [];
|
|
101
|
+
const conflicts = [];
|
|
102
|
+
const treeResults = [];
|
|
103
|
+
const notices = [];
|
|
104
|
+
for (const asset of copyTreeAssets(assets)) {
|
|
105
|
+
const destDir = sharedTreeDestDir(paths.root, asset.nativeDir) ?? copyTreeDestDir(paths.configDir, asset);
|
|
106
|
+
const sourceDir = sourceDirFor(asset, context, fsImpl, 'Pi');
|
|
107
|
+
const previousResources = ledgerFileResources(ledger?.resources, HARNESS, asset.id);
|
|
108
|
+
const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(ledger?.resources, HARNESS), operation: removing ? 'remove' : 'apply', fsImpl, layout: asset.layout, keepModified: !removing,
|
|
109
|
+
// Forwarded so the CLI's --force reaches planTree's conflict check; omitting it let
|
|
110
|
+
// planTree's own `force = false` default stand in silently. Gated on `!removing` for the
|
|
111
|
+
// reason codex/index.ts states in full: force heals drift on apply, but a hand-edited file
|
|
112
|
+
// is never deleted on removal, forced or not.
|
|
113
|
+
force: !removing && context?.force === true, });
|
|
114
|
+
treeResults.push(result);
|
|
115
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
116
|
+
// A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
|
|
117
|
+
// ownership row is released.
|
|
118
|
+
notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(paths.root, item.target)}; DoFlow no longer manages it`));
|
|
119
|
+
for (const change of result.changes) {
|
|
120
|
+
changes.push({
|
|
121
|
+
assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
|
|
122
|
+
ownershipIdentity: `doflow:pi:copy-tree:${asset.id}:${change.relPath}`,
|
|
123
|
+
kind: 'copy-tree-file', identity: change.relPath,
|
|
124
|
+
afterFingerprint: change.fingerprint, fingerprint: change.fingerprint, sourceVersion: 'registry-v1',
|
|
125
|
+
...(change.kept ? { retained: true, retainedFor: [] } : {}),
|
|
126
|
+
projection: { renderer: 'copy-tree' },
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function applyCopyTreeAssets(changes , { fsImpl = fs } = {}) {
|
|
134
|
+
const treeChanges = changes.filter((change ) => change.projection?.renderer === 'copy-tree' && change.operation !== 'remove')
|
|
135
|
+
.map((change ) => ({ relPath: change.identity, target: change.target, source: change.source, operation: change.operation, fingerprint: change.fingerprint }));
|
|
136
|
+
return applyTree({ changes: treeChanges, fsImpl }).applied;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function removeCopyTreeAssets(changes , { fsImpl = fs } = {}) {
|
|
140
|
+
const treeChanges = changes.filter((change ) => change.kind === 'copy-tree-file' && change.operation === 'remove' && !change.retained)
|
|
141
|
+
.map((change ) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
|
|
142
|
+
return removeTree({ changes: treeChanges, fsImpl }).removed;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl = fs } ) {
|
|
146
|
+
const paths = nativePaths({ scope, scopeRoot });
|
|
147
|
+
const statuses = [];
|
|
148
|
+
const resources = [];
|
|
149
|
+
const conflicts = [];
|
|
150
|
+
for (const asset of copyTreeAssets(assets)) {
|
|
151
|
+
const destDir = sharedTreeDestDir(paths.root, asset.nativeDir) ?? copyTreeDestDir(paths.configDir, asset);
|
|
152
|
+
const sourceDir = sourceDirFor(asset, context, fsImpl, 'Pi');
|
|
153
|
+
const result = verifyTree({ sourceDir, destDir, fsImpl, layout: asset.layout, plannedWrites: context?.plannedWrites });
|
|
154
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
155
|
+
for (const resource of result.resources) {
|
|
156
|
+
resources.push({
|
|
157
|
+
assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:pi:copy-tree:${asset.id}:${resource.relPath}`,
|
|
158
|
+
kind: 'copy-tree-file', identity: resource.relPath,
|
|
159
|
+
fingerprint: resource.fingerprint, sourceVersion: 'registry-v1',
|
|
160
|
+
projection: { renderer: 'copy-tree' },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
statuses.push({ harness: HARNESS, assetId: asset.id, capability: asset.capability, status: result.ok ? 'managed' : 'conflict', target: destDir });
|
|
164
|
+
}
|
|
165
|
+
return { statuses, resources, conflicts };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---- shared adapter contract ----
|
|
169
|
+
|
|
170
|
+
function plan({ scope, scopeRoot, assets = [], mcp = [], discovery, context = {}, ledger, fsImpl = fs } ) {
|
|
171
|
+
const found = discovery || discover({ scope, scopeRoot, mcp, ledger, context, fsImpl });
|
|
172
|
+
const changes = [];
|
|
173
|
+
const conflicts = [];
|
|
174
|
+
const removing = context.operation === 'remove';
|
|
175
|
+
|
|
176
|
+
const guidance = assets.find((asset ) => asset.capability === 'instructions');
|
|
177
|
+
if (guidance) {
|
|
178
|
+
const rendered = render({ content: fsImpl.readFileSync(path.resolve(context.repoRoot, guidance.source), 'utf8') });
|
|
179
|
+
const outcome = removing
|
|
180
|
+
? { ok: true, operation: 'remove', content: strippedInstruction(found.instruction) }
|
|
181
|
+
: managedInstruction(found.instruction, rendered);
|
|
182
|
+
if (!outcome.ok) conflicts.push(outcome.conflict);
|
|
183
|
+
else if (outcome.operation !== 'none' && outcome.content !== null) {
|
|
184
|
+
changes.push({ assetId: guidance.id, target: found.paths.instruction, operation: outcome.operation,
|
|
185
|
+
content: outcome.content, ownershipIdentity: `${HARNESS}:instructions:managed-section`,
|
|
186
|
+
fingerprint: fingerprint(outcome.content), harness: HARNESS, projection: { renderer: 'pi-instructions' } });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const copyTree = planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing, fsImpl });
|
|
191
|
+
changes.push(...copyTree.changes);
|
|
192
|
+
conflicts.push(...copyTree.conflicts);
|
|
193
|
+
|
|
194
|
+
const piMcp = planPiMcp({ selected: mcp, rows: mcpRows(ledger), file: found.paths.mcp, scope, removing, snapshot: found.mcp, assets });
|
|
195
|
+
changes.push(...piMcp.changes);
|
|
196
|
+
conflicts.push(...piMcp.conflicts);
|
|
197
|
+
|
|
198
|
+
const notices = [...copyTree.notices, ...piMcp.notices];
|
|
199
|
+
if (!removing && scope !== 'project' && agentDirOverride()) notices.push(AGENT_DIR_NOTICE);
|
|
200
|
+
return { changes, conflicts, notices, paths: found.paths };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function writeChange(change , fsImpl ) {
|
|
204
|
+
fsImpl.mkdirSync(path.dirname(change.target), { recursive: true });
|
|
205
|
+
fsImpl.writeFileSync(change.target, change.content, 'utf8');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isPiMcp(change ) { return change.projection?.renderer === 'pi-mcp'; }
|
|
209
|
+
|
|
210
|
+
function apply({ changes = [], fsImpl = fs } ) {
|
|
211
|
+
// MCP first: its base-hash check refuses a stale plan before any other Pi file is written.
|
|
212
|
+
let applied = applyPiMcp(changes.filter(isPiMcp), { fsImpl }).applied;
|
|
213
|
+
for (const change of changes) {
|
|
214
|
+
if (change.operation === 'remove' || change.projection?.renderer === 'copy-tree' || isPiMcp(change)) continue;
|
|
215
|
+
writeChange(change, fsImpl);
|
|
216
|
+
applied += 1;
|
|
217
|
+
}
|
|
218
|
+
applied += applyCopyTreeAssets(changes, { fsImpl });
|
|
219
|
+
// An update that no longer ships a copy-tree file carries its rows as removals inside the
|
|
220
|
+
// apply batch; the lifecycle calls remove() only for `doflow remove`.
|
|
221
|
+
removeCopyTreeAssets(changes, { fsImpl });
|
|
222
|
+
return { applied };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function remove({ changes = [], fsImpl = fs } ) {
|
|
226
|
+
let removed = applyPiMcp(changes.filter(isPiMcp), { fsImpl }).applied;
|
|
227
|
+
for (const change of changes) {
|
|
228
|
+
if (change.operation !== 'remove' || change.projection?.renderer === 'copy-tree' || isPiMcp(change)) continue;
|
|
229
|
+
if (change.content === null) continue;
|
|
230
|
+
writeChange(change, fsImpl);
|
|
231
|
+
removed += 1;
|
|
232
|
+
}
|
|
233
|
+
removed += removeCopyTreeAssets(changes, { fsImpl });
|
|
234
|
+
return { removed };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function verify({ scope, scopeRoot, assets = [], mcp = [], discovery, ledger, context = {}, fsImpl = fs } ) {
|
|
238
|
+
const found = discover({ scope, scopeRoot, mcp, ledger, context, fsImpl });
|
|
239
|
+
const statuses = [];
|
|
240
|
+
const resources = [];
|
|
241
|
+
const conflicts = [];
|
|
242
|
+
const instructionsAssetId = assets.find((asset ) => asset.capability === 'instructions')?.id ?? assets[0]?.id;
|
|
243
|
+
|
|
244
|
+
const hasSection = typeof found.instruction === 'string'
|
|
245
|
+
&& found.instruction.includes(MARKER_START) && found.instruction.includes(MARKER_END);
|
|
246
|
+
statuses.push({ harness: HARNESS, assetId: instructionsAssetId, capability: 'instructions',
|
|
247
|
+
status: hasSection ? 'managed' : 'absent', target: found.paths.instruction,
|
|
248
|
+
ownershipIdentity: `${HARNESS}:instructions:managed-section` });
|
|
249
|
+
if (hasSection) {
|
|
250
|
+
resources.push({ assetId: instructionsAssetId, target: found.paths.instruction,
|
|
251
|
+
ownershipIdentity: `${HARNESS}:instructions:managed-section`, fingerprint: fingerprint(found.instruction),
|
|
252
|
+
sourceVersion: context.sourceVersion ?? 'unknown', projection: { renderer: 'pi-instructions' } });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const copyTree = verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl });
|
|
256
|
+
statuses.push(...copyTree.statuses);
|
|
257
|
+
resources.push(...copyTree.resources);
|
|
258
|
+
conflicts.push(...copyTree.conflicts);
|
|
259
|
+
|
|
260
|
+
const piMcp = verifyPiMcp({ selected: mcp, rows: mcpRows(ledger), file: found.paths.mcp, snapshot: (discovery ?? found).mcp,
|
|
261
|
+
removing: context.operation === 'remove', fsImpl });
|
|
262
|
+
statuses.push(...piMcp.statuses);
|
|
263
|
+
resources.push(...piMcp.resources);
|
|
264
|
+
conflicts.push(...piMcp.conflicts);
|
|
265
|
+
|
|
266
|
+
return { ok: conflicts.length === 0 && !statuses.some((s) => s.status === 'invalid' || s.status === 'conflict'), resources, statuses, conflicts };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { nativePaths, discover, render, plan, apply, remove, verify };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const singleton = createPiAdapter();
|
|
273
|
+
|
|
274
|
+
module.exports = {
|
|
275
|
+
HARNESS, nativePaths: singleton.nativePaths, discover: singleton.discover, render: singleton.render,
|
|
276
|
+
plan: singleton.plan, apply: singleton.apply, remove: singleton.remove, verify: singleton.verify,
|
|
277
|
+
managedInstruction: (singleton ).managedInstruction, strippedInstruction: (singleton ).strippedInstruction,
|
|
278
|
+
createPiAdapter,
|
|
279
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
// Byte-preserving member edits on a JSON object's text. Every edit replaces exactly one span of the
|
|
6
|
+
// original string, so whitespace, key order, line endings and every member DoFlow does not touch
|
|
7
|
+
// survive as the user wrote them; re-serialising the parsed document would rewrite all of that.
|
|
8
|
+
// Pure string in, string out: no file I/O and no knowledge of who owns which member.
|
|
9
|
+
|
|
10
|
+
const SKELETON = '{\n "mcpServers": {}\n}\n';
|
|
11
|
+
const WHITESPACE = ' \t\n\r';
|
|
12
|
+
|
|
13
|
+
function isPlainObject(value ) {
|
|
14
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function skipWhitespace(text , i ) {
|
|
18
|
+
while (i < text.length && WHITESPACE.includes(text[i])) i += 1;
|
|
19
|
+
return i;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Index just past the string token that opens at `i`. */
|
|
23
|
+
function stringEnd(text , i ) {
|
|
24
|
+
for (i += 1; text[i] !== '"'; i += 1) if (text[i] === '\\') i += 1;
|
|
25
|
+
return i + 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The text has already passed JSON.parse, so the scanner only has to find spans, never validate.
|
|
29
|
+
// Only the top-level object and mcpServers need member spans; every other value is skipped by
|
|
30
|
+
// counting brackets, without recursion, so a deeply nested file cannot exhaust the stack.
|
|
31
|
+
function valueEnd(text , i ) {
|
|
32
|
+
const ch = text[i];
|
|
33
|
+
if (ch === '"') return stringEnd(text, i);
|
|
34
|
+
if (ch !== '{' && ch !== '[') {
|
|
35
|
+
while (i < text.length && !`,}]${WHITESPACE}`.includes(text[i])) i += 1;
|
|
36
|
+
return i;
|
|
37
|
+
}
|
|
38
|
+
let depth = 0;
|
|
39
|
+
for (;;) {
|
|
40
|
+
const c = text[i];
|
|
41
|
+
if (c === '"') { i = stringEnd(text, i); continue; }
|
|
42
|
+
if (c === '{' || c === '[') depth += 1;
|
|
43
|
+
else if (c === '}' || c === ']') { depth -= 1; if (depth === 0) return i + 1; }
|
|
44
|
+
i += 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Member spans of the object opening at `open`; `nested` names the one member whose object value
|
|
49
|
+
* is scanned for its own members too. */
|
|
50
|
+
function scanObject(text , open , nested = null) {
|
|
51
|
+
const members = [];
|
|
52
|
+
let i = skipWhitespace(text, open + 1);
|
|
53
|
+
while (text[i] !== '}') {
|
|
54
|
+
const keyStart = i;
|
|
55
|
+
const keyEnd = stringEnd(text, keyStart);
|
|
56
|
+
const key = JSON.parse(text.slice(keyStart, keyEnd));
|
|
57
|
+
const valueStart = skipWhitespace(text, skipWhitespace(text, keyEnd) + 1);
|
|
58
|
+
const node = key === nested && text[valueStart] === '{' ? scanObject(text, valueStart) : null;
|
|
59
|
+
const end = node ? node.close + 1 : valueEnd(text, valueStart);
|
|
60
|
+
members.push({ key, keyStart, valueStart, valueEnd: end, value: JSON.parse(text.slice(valueStart, end)), node });
|
|
61
|
+
i = skipWhitespace(text, end);
|
|
62
|
+
if (text[i] === ',') i = skipWhitespace(text, i + 1);
|
|
63
|
+
}
|
|
64
|
+
return { open, close: i, members };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** JSON.parse's message quotes the text around the error, which may be a token or a password, so
|
|
68
|
+
* only the position is reported. */
|
|
69
|
+
function invalidJsonReason(text , error ) {
|
|
70
|
+
const match = /at position (\d+)/.exec(error.message);
|
|
71
|
+
if (!match) return 'invalid JSON';
|
|
72
|
+
const before = text.slice(0, Number(match[1]));
|
|
73
|
+
const line = before.split('\n').length;
|
|
74
|
+
return `invalid JSON at line ${line} column ${before.length - before.lastIndexOf('\n')}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The whitespace that starts the line `at` sits on, or null when something else precedes it there. */
|
|
78
|
+
function lineIndent(text , at ) {
|
|
79
|
+
const prefix = text.slice(text.lastIndexOf('\n', at - 1) + 1, at);
|
|
80
|
+
return /^[ \t]*$/.test(prefix) ? prefix : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The line ending most of the lines in `[start, end)` use, or `fallback` when the span has none:
|
|
84
|
+
* one stray CRLF in an LF file does not turn every inserted line into CRLF. */
|
|
85
|
+
function lineEnding(text , start , end , fallback ) {
|
|
86
|
+
const span = text.slice(start, end);
|
|
87
|
+
const lines = span.split('\n').length - 1;
|
|
88
|
+
if (!lines) return fallback;
|
|
89
|
+
return (span.split('\r\n').length - 1) * 2 > lines ? '\r\n' : '\n';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Attach the formatting a container's new members follow: whether it spans lines, its line ending,
|
|
93
|
+
* the indent of its members and the indent of its closing brace. An empty container takes its parent's. */
|
|
94
|
+
function attachLayout(text , node , parent , unit , eol ) {
|
|
95
|
+
const parentIndent = parent ? parent.indent : '';
|
|
96
|
+
const empty = node.members.length === 0;
|
|
97
|
+
node.multiline = text.slice(node.open, node.close).includes('\n') || (empty && Boolean(parent?.multiline));
|
|
98
|
+
node.eol = lineEnding(text, node.open, node.close, parent ? parent.eol : eol);
|
|
99
|
+
node.indent = (!empty && lineIndent(text, node.members[0].keyStart)) || parentIndent + unit;
|
|
100
|
+
node.closeIndent = parentIndent;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function duplicateKey(node ) {
|
|
104
|
+
const seen = new Set ();
|
|
105
|
+
for (const member of node.members) {
|
|
106
|
+
if (seen.has(member.key)) return member.key;
|
|
107
|
+
seen.add(member.key);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readDocument(text ) {
|
|
113
|
+
let parsed;
|
|
114
|
+
try { parsed = JSON.parse(text); } catch (error ) { return { ok: false, reason: invalidJsonReason(text, error) }; }
|
|
115
|
+
if (!isPlainObject(parsed)) return { ok: false, reason: 'top level is not an object' };
|
|
116
|
+
if (Object.prototype.hasOwnProperty.call(parsed, 'mcpServers') && !isPlainObject(parsed.mcpServers)) {
|
|
117
|
+
return { ok: false, reason: 'mcpServers is not an object' };
|
|
118
|
+
}
|
|
119
|
+
let root;
|
|
120
|
+
try { root = scanObject(text, skipWhitespace(text, 0), 'mcpServers'); } catch { return { ok: false, reason: 'the file could not be scanned' }; }
|
|
121
|
+
const duplicateTop = duplicateKey(root);
|
|
122
|
+
if (duplicateTop !== null) return { ok: false, reason: `duplicate key '${duplicateTop}'` };
|
|
123
|
+
const servers = root.members.find((member ) => member.key === 'mcpServers')?.node ?? null;
|
|
124
|
+
const duplicateServer = servers ? duplicateKey(servers) : null;
|
|
125
|
+
if (duplicateServer !== null) return { ok: false, reason: `duplicate key '${duplicateServer}'` };
|
|
126
|
+
|
|
127
|
+
const eol = lineEnding(text, 0, text.length, '\n');
|
|
128
|
+
const spansLines = text.slice(root.open, root.close).includes('\n');
|
|
129
|
+
const unit = (spansLines && root.members.length && lineIndent(text, root.members[0].keyStart)) || ' ';
|
|
130
|
+
attachLayout(text, root, null, unit, eol);
|
|
131
|
+
if (servers) attachLayout(text, servers, root, unit, eol);
|
|
132
|
+
return { ok: true, root, servers, eol, unit };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function renderValue(value , container , layout ) {
|
|
136
|
+
if (!container.multiline) return JSON.stringify(value);
|
|
137
|
+
return JSON.stringify(value, null, layout.unit).split('\n').join(container.eol + container.indent);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function renderMember(key , value , container , layout ) {
|
|
141
|
+
return `${JSON.stringify(key)}: ${renderValue(value, container, layout)}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function splice(text , start , end , inserted ) {
|
|
145
|
+
return text.slice(0, start) + inserted + text.slice(end);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** `layout` is the readDocument result the container or member came from. */
|
|
149
|
+
function insertMember(text , container , key , value , layout ) {
|
|
150
|
+
const member = renderMember(key, value, container, layout);
|
|
151
|
+
const last = container.members[container.members.length - 1];
|
|
152
|
+
if (last) {
|
|
153
|
+
const inserted = container.multiline ? `,${container.eol}${container.indent}${member}` : `, ${member}`;
|
|
154
|
+
return splice(text, last.valueEnd, last.valueEnd, inserted);
|
|
155
|
+
}
|
|
156
|
+
const inserted = container.multiline
|
|
157
|
+
? `${container.eol}${container.indent}${member}${container.eol}${container.closeIndent}`
|
|
158
|
+
: member;
|
|
159
|
+
return splice(text, container.open + 1, container.close, inserted);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function replaceValue(text , member , value , layout ) {
|
|
163
|
+
const container = layout.servers?.members.includes(member) ? layout.servers : layout.root;
|
|
164
|
+
return splice(text, member.valueStart, member.valueEnd, renderValue(value, container, layout));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function removeMember(text , container , member ) {
|
|
168
|
+
const index = container.members.indexOf(member);
|
|
169
|
+
if (index === -1) throw new Error(`member '${member.key}' is not in this container`);
|
|
170
|
+
if (container.members.length === 1) return splice(text, container.open + 1, container.close, '');
|
|
171
|
+
if (index < container.members.length - 1) return splice(text, member.keyStart, container.members[index + 1].keyStart, '');
|
|
172
|
+
return splice(text, container.members[index - 1].valueEnd, member.valueEnd, '');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Set the text between an empty container's braces, which a removal of its only member drops. */
|
|
176
|
+
function replaceInner(text , container , inner ) {
|
|
177
|
+
if (container.members.length) throw new Error('only an empty container\'s inner text can be replaced');
|
|
178
|
+
return splice(text, container.open + 1, container.close, inner);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function renderNewDocument(entries ) {
|
|
182
|
+
return `${JSON.stringify({ mcpServers: Object.fromEntries(entries) }, null, 2)}\n`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = { SKELETON, readDocument, insertMember, replaceValue, removeMember, replaceInner, renderNewDocument };
|