@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,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The precedence resolver (design C4, contract IC-003 consumer, FR-004).
|
|
7
|
+
*
|
|
8
|
+
* Given a logical asset present at one or more scopes and the harness's recorded
|
|
9
|
+
* `scopePrecedence` block (`core/registry/harnesses.json`), names the copy the harness will load —
|
|
10
|
+
* or withholds that verdict and states why. This module is pure and synchronous: it looks at
|
|
11
|
+
* nothing on disk and reads no registry itself: the caller passes the one harness's precedence
|
|
12
|
+
* block, already loaded.
|
|
13
|
+
*
|
|
14
|
+
* **The winner is derived from `order` and `mode` together, never from either alone** (IC-003).
|
|
15
|
+
* An order alone is ambiguous between naming the winner first and naming it last, and the
|
|
16
|
+
* recorded corpus contains both conventions for the same field: `claude` records
|
|
17
|
+
* `["user","project"]` with `mode: "first-wins"` (the first entry, `user`, wins); `copilot`
|
|
18
|
+
* records `["project","user"]` with `mode: "last-wins"` (the *last* entry, `user`, wins — the same
|
|
19
|
+
* winner as `claude`, reached from the opposite order convention); `codex` records
|
|
20
|
+
* `["user","project"]` with `mode: "last-wins"` (the last entry, `project`, wins — a naive
|
|
21
|
+
* first-match reading of that same order would get it backwards).
|
|
22
|
+
*
|
|
23
|
+
* A `merged` mode means the copies are combined rather than chosen between, so no single copy
|
|
24
|
+
* "wins". An `unestablished` mode means the order may be documented (`gemini` documents one) or
|
|
25
|
+
* may not (`pi`, `antigravity` document neither), but either way the rule for choosing between
|
|
26
|
+
* copies is not — so no winner is named. Those two cases are separate states under IC-003 and the
|
|
27
|
+
* withheld reason says which: a documented order with an undocumented winner names the order it
|
|
28
|
+
* knows, an undocumented order says the order itself is unestablished. Neither may report as the
|
|
29
|
+
* other. All are withheld verdicts, never a guessed default
|
|
30
|
+
* (design A3: "if a default is later wanted, it becomes a recorded order with its basis stated,
|
|
31
|
+
* not an implicit fallback").
|
|
32
|
+
*
|
|
33
|
+
* An asset present at only one scope has no verdict to withhold and is not "shadowed" at all: it
|
|
34
|
+
* is reported plainly, with that one scope named as trivially where the harness's copy comes from,
|
|
35
|
+
* regardless of whether the harness has any precedence block, or what it says.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} ShadowVerdict The `SHADOW_VERDICT` entity of the feature's data model.
|
|
40
|
+
* @property {string[]} presentAt The scopes at which this logical asset has a recorded copy, in
|
|
41
|
+
* the order given by the caller. Always at least one entry.
|
|
42
|
+
* @property {boolean} shadowed Whether more than one scope holds a copy of this asset. `false`
|
|
43
|
+
* means there is exactly one copy and nothing to resolve between.
|
|
44
|
+
* @property {string|null} winner The scope whose copy the harness will load. Set to the sole
|
|
45
|
+
* present scope when `shadowed` is `false`; set to the resolved scope when `shadowed` is `true`
|
|
46
|
+
* and the precedence block names an unambiguous winner (`first-wins`/`last-wins`); `null` exactly
|
|
47
|
+
* when `shadowed` is `true` and the verdict is withheld.
|
|
48
|
+
* @property {string|null} withheldReason Populated exactly when `winner` is `null`; states why no
|
|
49
|
+
* winner is named. `null` in every other case, including the un-shadowed case, which never
|
|
50
|
+
* withholds anything.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the shadow verdict for one logical asset at one harness.
|
|
55
|
+
*
|
|
56
|
+
* @param {string[]} presentScopes The scopes at which a copy of this logical asset is recorded.
|
|
57
|
+
* Order is not significant here — it is not read as a precedence order, only as "where does a
|
|
58
|
+
* copy exist". Deduplicated internally; malformed entries (non-string, empty) are dropped.
|
|
59
|
+
* @param {Object|null|undefined} precedence The harness's recorded `scopePrecedence` block
|
|
60
|
+
* (`core/registry/harnesses.json`), exactly as stored — at minimum `{ mode, order? }` — or
|
|
61
|
+
* `null`/`undefined` when the harness has none recorded yet (IC-003's fifth state: the question
|
|
62
|
+
* has not been asked). Never defaulted by this function.
|
|
63
|
+
* @returns {ShadowVerdict|null} `null` when `presentScopes` carries no usable scope name — there is
|
|
64
|
+
* no asset to resolve a verdict for. Otherwise a `ShadowVerdict`.
|
|
65
|
+
*/
|
|
66
|
+
function resolveShadowVerdict(presentScopes , precedence ) {
|
|
67
|
+
const scopes = Array.isArray(presentScopes)
|
|
68
|
+
? [...new Set(presentScopes.filter((scope ) => typeof scope === 'string' && scope !== ''))]
|
|
69
|
+
: [];
|
|
70
|
+
if (scopes.length === 0) return null;
|
|
71
|
+
|
|
72
|
+
if (scopes.length === 1) {
|
|
73
|
+
return { presentAt: scopes, shadowed: false, winner: scopes[0], withheldReason: null };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// More than one scope holds a copy: a verdict is either named or withheld, never defaulted.
|
|
77
|
+
if (!precedence || typeof precedence !== 'object') {
|
|
78
|
+
return {
|
|
79
|
+
presentAt: scopes,
|
|
80
|
+
shadowed: true,
|
|
81
|
+
winner: null,
|
|
82
|
+
withheldReason: 'no scope-precedence is recorded for this harness',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { mode, order } = precedence;
|
|
87
|
+
|
|
88
|
+
if (mode === 'merged') {
|
|
89
|
+
return {
|
|
90
|
+
presentAt: scopes,
|
|
91
|
+
shadowed: true,
|
|
92
|
+
winner: null,
|
|
93
|
+
withheldReason: 'the harness merges copies from every scope rather than choosing between them',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (mode === 'unestablished') {
|
|
98
|
+
// IC-003 keeps these two states distinct: an order established with the mode explicitly
|
|
99
|
+
// unestablished (`gemini`) is not the same finding as an order sought and found undocumented
|
|
100
|
+
// (`pi`, `antigravity`). Both withhold the verdict; only one of them knows the reading order.
|
|
101
|
+
const orderIsEstablished = Array.isArray(order) && order.length > 0;
|
|
102
|
+
return {
|
|
103
|
+
presentAt: scopes,
|
|
104
|
+
shadowed: true,
|
|
105
|
+
winner: null,
|
|
106
|
+
withheldReason: orderIsEstablished
|
|
107
|
+
? `the harness's consultation order is established (${order.join(' then ')}) but its rule for choosing between copies is not`
|
|
108
|
+
: "the harness's scope-resolution order is unestablished",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (mode === 'first-wins' || mode === 'last-wins') {
|
|
113
|
+
if (!Array.isArray(order) || order.length === 0) {
|
|
114
|
+
return {
|
|
115
|
+
presentAt: scopes,
|
|
116
|
+
shadowed: true,
|
|
117
|
+
winner: null,
|
|
118
|
+
withheldReason: `the harness's precedence block records mode "${mode}" but no consultation order`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const winner = mode === 'first-wins' ? order[0] : order[order.length - 1];
|
|
122
|
+
return { presentAt: scopes, shadowed: true, winner, withheldReason: null };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// An absent or unrecognized mode is never guessed at (IC-003: order alone is ambiguous).
|
|
126
|
+
return {
|
|
127
|
+
presentAt: scopes,
|
|
128
|
+
shadowed: true,
|
|
129
|
+
winner: null,
|
|
130
|
+
withheldReason: `the harness's precedence block records no usable resolution mode (got ${JSON.stringify(mode ?? null)})`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { resolveShadowVerdict };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* C2 — dual-scope reader (FR-002, NFR-002).
|
|
7
|
+
*
|
|
8
|
+
* The rest of the CLI resolves scope once per invocation, as a boolean, and derives every root
|
|
9
|
+
* from it; a cross-scope report cannot be assembled that way. This module calls the existing
|
|
10
|
+
* scope-resolved lifecycle view twice — once per scope descriptor it constructs — and returns one
|
|
11
|
+
* snapshot per scope. It changes no other caller's scope handling and it reads only: nothing here
|
|
12
|
+
* writes, moves, or creates recorded state (NFR-001).
|
|
13
|
+
*
|
|
14
|
+
* Two constraints shape the interface:
|
|
15
|
+
*
|
|
16
|
+
* - **The global scope root is not an argument (design R7).** `registryLifecycleView` derives it
|
|
17
|
+
* from `os.homedir()` whenever the scope descriptor carries `global: true`, and silently ignores
|
|
18
|
+
* a `projectRoot` on that same descriptor. So this reader supplies only the project root; the
|
|
19
|
+
* global root is inherited from the environment, which is correct at runtime. A test that needs
|
|
20
|
+
* a synthetic global root must relocate `os.homedir` around the (synchronous) call.
|
|
21
|
+
* - **A scope with nothing recorded is a value, not a failure (NFR-002).** A repository never
|
|
22
|
+
* installed into is the ordinary state, so its snapshot is returned empty and `recorded: false`
|
|
23
|
+
* rather than raising, and reporting that absence creates no ledger on disk.
|
|
24
|
+
*
|
|
25
|
+
* Two further properties are load-bearing for the currency judgement downstream, because that
|
|
26
|
+
* judgement is read from the plan this module returns beside the resources:
|
|
27
|
+
*
|
|
28
|
+
* - **The restriction restricts the resources, not only the plan.** `targets` bounds the plan the
|
|
29
|
+
* view computes, so a resource of an unrequested harness has no plan to be judged against. It is
|
|
30
|
+
* therefore left out of the snapshot rather than returned beside a plan that never looked at it —
|
|
31
|
+
* IC-001 makes `targets` a restriction to named harnesses, so a harness outside it is not part of
|
|
32
|
+
* what was asked. Returning the whole ledger here made every unrequested harness's copy read as
|
|
33
|
+
* current, which is the one thing this reader must not let a consumer conclude.
|
|
34
|
+
* - **The plan is computed with the selections the scope recorded.** `planLifecycle` gives each
|
|
35
|
+
* harness the MCP servers handed to it for that harness, so an empty list is not a neutral
|
|
36
|
+
* default: it is a positive statement that no server was chosen, and the plan then proposes to
|
|
37
|
+
* remove every one the harness owns. `doflow reconcile` and `doflow status` derive each
|
|
38
|
+
* harness's selection from the scope's own `doflow.lock` and ledger through
|
|
39
|
+
* `recordedMcpSelections` (`src/install/mcp.js`); this reader calls the same function per scope,
|
|
40
|
+
* so the plan a currency judgement is derived from is the plan the repair command would produce.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const os = require('node:os');
|
|
44
|
+
const { registryLifecycleView, LIFECYCLE_HARNESSES } = require('../../lifecycle/view.js');
|
|
45
|
+
const { readLock } = require('../../state/lockfile.js');
|
|
46
|
+
const { stateRoot, readLedger } = require('../../state/index.js');
|
|
47
|
+
const { recordedMcpSelections } = require('../../install/mcp.js');
|
|
48
|
+
const { readInstallManifest } = require('../../install/manifest.js');
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} ScopeSnapshot The `SCOPE_SNAPSHOT` entity of the feature's data model.
|
|
52
|
+
* @property {'global'|'project'} scope Which scope was read.
|
|
53
|
+
* @property {string} scopeRoot The root that scope resolved to — home directory, or project root.
|
|
54
|
+
* @property {string} stateRoot The neutral state root beneath `scopeRoot`; may not exist on disk.
|
|
55
|
+
* @property {boolean} recorded Whether anything is recorded at this scope. Derived from the
|
|
56
|
+
* ledger's contents rather than from the ledger file's existence, so a scope holding an empty
|
|
57
|
+
* ledger and a scope holding no ledger at all report alike — both have nothing recorded. It
|
|
58
|
+
* describes the **scope**, not the restriction: a scope holding a full install reports `true`
|
|
59
|
+
* under a restriction that matches none of it, because "nothing is recorded here" would be false.
|
|
60
|
+
* @property {string[]} targets The harnesses this snapshot was read for — what `resources` is
|
|
61
|
+
* restricted to, stated rather than left for a consumer to infer from the rows present.
|
|
62
|
+
* @property {Array<Object>} resources The `MANAGED_RESOURCE` records of the requested harnesses,
|
|
63
|
+
* exactly as stored. Read only.
|
|
64
|
+
* @property {Object} ledger The whole neutral ledger, unrestricted, for consumers needing more than
|
|
65
|
+
* the requested harnesses' resources.
|
|
66
|
+
* @property {Object} plan The lifecycle plan for this scope and these harnesses, already computed by
|
|
67
|
+
* the view; it is what a currency judgement is derived from downstream.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/** @returns {ScopeSnapshot} */
|
|
71
|
+
function snapshot(scope , view , targets ) {
|
|
72
|
+
const { ledger } = view;
|
|
73
|
+
const wanted = new Set(targets);
|
|
74
|
+
return {
|
|
75
|
+
scope,
|
|
76
|
+
scopeRoot: ledger.scopeRoot,
|
|
77
|
+
stateRoot: view.stateRoot,
|
|
78
|
+
recorded: ledger.resources.length > 0 || Object.keys(ledger.targets).length > 0,
|
|
79
|
+
targets,
|
|
80
|
+
resources: ledger.resources.filter((resource ) => wanted.has(resource?.harness)),
|
|
81
|
+
ledger,
|
|
82
|
+
plan: view.plan,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Read the recorded state of both scopes within one invocation.
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} options
|
|
90
|
+
* @param {Object} options.registry Registry loaded once by the caller and threaded through, as
|
|
91
|
+
* every other lifecycle-view call site does.
|
|
92
|
+
* @param {string} options.repoRoot Root of the DoFlow source the view reads native sources from.
|
|
93
|
+
* @param {string} options.projectRoot Root of the project scope. The global scope's root is not a
|
|
94
|
+
* parameter — see R7 above.
|
|
95
|
+
* @param {string[]} [options.targets] Harnesses to read. Defaults to every lifecycle harness,
|
|
96
|
+
* which is what an inventory of the whole install wants. Both the plan and the returned resources
|
|
97
|
+
* are restricted to these.
|
|
98
|
+
* @param {{global: Object|null, project: Object|null}} [options.locks] Lock documents per scope, for
|
|
99
|
+
* a caller that already read them (the report reads them anyway, for repair coverage). Omitted,
|
|
100
|
+
* each scope's lock is read from disk — `readLock` returns `null` for a scope with none, and a
|
|
101
|
+
* scope with no lock is a normal value here, not a failure (NFR-002).
|
|
102
|
+
* @returns {{global: ScopeSnapshot, project: ScopeSnapshot, scopes: ScopeSnapshot[]}}
|
|
103
|
+
* @throws when a lock file exists but cannot be parsed. Propagated deliberately: the plan would be
|
|
104
|
+
* computed from a selection nobody can read, so a currency judgement derived from it would be
|
|
105
|
+
* fiction. `reconcile` fails on the same document.
|
|
106
|
+
*/
|
|
107
|
+
function readScopes({ registry, repoRoot, projectRoot, targets = LIFECYCLE_HARNESSES, locks } ) {
|
|
108
|
+
const wanted = [...new Set(targets)];
|
|
109
|
+
const resolved = locks ?? {
|
|
110
|
+
// The global lock root is the home directory, the same root design R7 keeps out of this
|
|
111
|
+
// signature; `readLock` defaults to `os.homedir()`, so both halves agree without a parameter.
|
|
112
|
+
global: readLock({ scope: 'global' }),
|
|
113
|
+
project: readLock({ scope: 'project', projectRoot }),
|
|
114
|
+
};
|
|
115
|
+
const read = (scope ) => {
|
|
116
|
+
// Each scope's own ledger, at the root the view itself reads it from.
|
|
117
|
+
const ledger = readLedger(scope === 'global'
|
|
118
|
+
? stateRoot({ scope: 'global', homeDir: os.homedir() }) : stateRoot({ scope: 'project', projectRoot }));
|
|
119
|
+
const manifestServers = readInstallManifest({ scopeRoot: scope === 'global' ? os.homedir() : projectRoot })?.mcpServers ?? null;
|
|
120
|
+
const recorded = recordedMcpSelections({ registry, lock: resolved?.[scope] ?? null, ledger, targets: wanted, manifestServers });
|
|
121
|
+
return registryLifecycleView({
|
|
122
|
+
registry, repoRoot, targets: wanted, mcpSelections: recorded.selections, mcpAdoptable: recorded.adoptable,
|
|
123
|
+
scope: scope === 'global' ? { global: true } : { global: false, projectRoot },
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const global = snapshot('global', read('global'), wanted);
|
|
127
|
+
const project = snapshot('project', read('project'), wanted);
|
|
128
|
+
return { global, project, scopes: [global, project] };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = { readScopes };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* C5 — sibling inspector (design C5, FR-007, R6).
|
|
10
|
+
*
|
|
11
|
+
* Reports files DoFlow does not manage that sit in a directory the harness loads **wholesale** —
|
|
12
|
+
* every file present becomes operative context — and that also holds a managed asset. Such a file
|
|
13
|
+
* is live context DoFlow did not put there, whatever it is called.
|
|
14
|
+
*
|
|
15
|
+
* **What this module deliberately does not report.** A directory the harness resolves **by name**
|
|
16
|
+
* is not inspected at all, not even to count its entries. There the harness loads the named file
|
|
17
|
+
* and nothing else, so a neighbouring file cannot take effect however it is named: a superseded
|
|
18
|
+
* `SKILL.md.pre-v1.2.0-backup-*` sitting beside `SKILL.md` is inert, and reporting it is noise.
|
|
19
|
+
* Within one scope DoFlow owns the path a same-named asset would occupy, so a same-scope name
|
|
20
|
+
* collision cannot arise either; genuine cross-scope outranking is FR-004's question, answered by
|
|
21
|
+
* `precedence.js`. This module therefore applies no filename heuristic whatsoever — in an inspected
|
|
22
|
+
* directory, presence is the whole test (requirement §9, 2026-09-27; design §9, C5).
|
|
23
|
+
*
|
|
24
|
+
* **The bound on cost (R6).** Only the parent directories of assets already enumerated from the
|
|
25
|
+
* ledger are read — one level, never recursed into. A managed tree that nests (Kiro's steering tree
|
|
26
|
+
* mirrors `core/shared/guidance/`, `rules/` and `mcp/` included) is still covered completely
|
|
27
|
+
* without a walk, because every subdirectory holding a managed file is itself the parent of a
|
|
28
|
+
* managed resource and so is inspected on its own account. `mcp-builder/` and `skill-creator/`
|
|
29
|
+
* sitting beside a managed skills directory are never enumerated: neither is the parent of a
|
|
30
|
+
* managed asset (requirement §5, "Out of Scope").
|
|
31
|
+
*
|
|
32
|
+
* Two hard rules follow directly from the design:
|
|
33
|
+
* - No currency verdict is ever attached to a candidate (data-model `UNMANAGED_CANDIDATE`): there
|
|
34
|
+
* is no recorded fingerprint for a file DoFlow does not manage, so it is reported as present,
|
|
35
|
+
* never as diverged.
|
|
36
|
+
* - Read-only throughout (NFR-001). Nothing here writes, moves, or deletes.
|
|
37
|
+
*
|
|
38
|
+
* Follows this repo's directory-reading idiom (`src/runtime/knowledge/index-store.js`): a bare
|
|
39
|
+
* `readdirSync(dir, { withFileTypes: true })` inside a try/catch that skips on failure. A managed
|
|
40
|
+
* asset's parent directory may legitimately not exist — a recorded resource whose file was since
|
|
41
|
+
* removed by hand — and that is not an error this read-only module raises.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The asset/harness pairs whose native directory the harness loads wholesale.
|
|
46
|
+
*
|
|
47
|
+
* **Nothing in the registry declares load semantics.** This set is derived from adapter behaviour,
|
|
48
|
+
* read off two locators in this repository:
|
|
49
|
+
*
|
|
50
|
+
* - `src/adapters/copy-tree.js` — the `copilot-rule-instructions` transform (line ~80) renders
|
|
51
|
+
* *every* file it copies under an `applyTo: '**'` front-matter header, so every file landing in
|
|
52
|
+
* `.github/instructions/` is always applied. Asset `instructions.copilot`, native dir
|
|
53
|
+
* `instructions`.
|
|
54
|
+
* - `src/adapters/kiro/index.js` (header comment, lines 8-12) — "Kiro receives DoFlow's *full*
|
|
55
|
+
* guidance tree as steering files", with no single-file instruction surface at all. Asset
|
|
56
|
+
* `guidance.context-layer` for the `kiro` harness only, native dir `steering`; the same asset's
|
|
57
|
+
* `../.doflow/guidance` projection for claude/codex/gemini/antigravity is name-resolved by
|
|
58
|
+
* explicit import and is not in this set.
|
|
59
|
+
*
|
|
60
|
+
* This is the single place to change. If the registry later declares load semantics per projection,
|
|
61
|
+
* replace this constant's contents with that lookup — do not add a second source of the fact, and
|
|
62
|
+
* do not infer it from a directory's name. An asset/harness pair absent here is name-resolved and
|
|
63
|
+
* its directory is skipped entirely.
|
|
64
|
+
*
|
|
65
|
+
* @type {Readonly<Object<string, ReadonlyArray<string>>>} harness id -> registry asset ids
|
|
66
|
+
*/
|
|
67
|
+
const WHOLESALE_ASSETS = Object.freeze({
|
|
68
|
+
copilot: Object.freeze(['instructions.copilot']),
|
|
69
|
+
kiro: Object.freeze(['guidance.context-layer']),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @typedef {Object} UnmanagedCandidate The `UNMANAGED_CANDIDATE` entity of the feature's data model.
|
|
74
|
+
* @property {string} name The entry's own filename, exactly as read from the directory.
|
|
75
|
+
* @property {string} path The entry's absolute path.
|
|
76
|
+
* @property {string} parentDir The absolute directory it was found in — a managed asset's own
|
|
77
|
+
* parent, and one the harness loads wholesale.
|
|
78
|
+
* @property {'file'|'directory'} kind What the entry is. A `directory` is an unmanaged directory
|
|
79
|
+
* inside a wholesale tree: its contents are loaded too, and were not enumerated because this
|
|
80
|
+
* module does not recurse. A directory that itself holds a managed asset is not a candidate at
|
|
81
|
+
* all — it is the managed tree's own structure.
|
|
82
|
+
* @property {string} harness The harness whose wholesale load makes this file operative.
|
|
83
|
+
* @property {string} assetId The registry asset whose directory it shares.
|
|
84
|
+
*
|
|
85
|
+
* No `currency` field exists on this shape, deliberately: an unmanaged file carries no recorded
|
|
86
|
+
* fingerprint, so it can never be judged to match or diverge from anything.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/** Whether this harness loads this asset's native directory wholesale. `Array.isArray` also guards
|
|
90
|
+
* a harness id that collides with an `Object.prototype` key. */
|
|
91
|
+
function loadsWholesale(harness , assetId ) {
|
|
92
|
+
const assets = (WHOLESALE_ASSETS )[harness];
|
|
93
|
+
return Array.isArray(assets) && assets.includes(assetId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Groups managed resources by parent directory. Every well-formed resource contributes its leaf
|
|
97
|
+
* filename (so a managed file is never mistaken for an unmanaged one) and its directory (so a
|
|
98
|
+
* subdirectory holding managed assets is recognised as structure, not as a foreign entry); only
|
|
99
|
+
* resources whose harness loads them wholesale contribute a `wholesale` target, and only a
|
|
100
|
+
* directory with at least one of those is ever read. */
|
|
101
|
+
function groupByParentDir(resources ) {
|
|
102
|
+
const byDir = new Map();
|
|
103
|
+
const managedDirs = new Set();
|
|
104
|
+
for (const resource of resources ?? []) {
|
|
105
|
+
const target = resource?.target;
|
|
106
|
+
if (typeof target !== 'string' || target === '') continue;
|
|
107
|
+
const dir = path.dirname(target);
|
|
108
|
+
managedDirs.add(dir);
|
|
109
|
+
if (!byDir.has(dir)) byDir.set(dir, { basenames: new Set(), wholesale: [] });
|
|
110
|
+
const entry = byDir.get(dir);
|
|
111
|
+
entry.basenames.add(path.basename(target));
|
|
112
|
+
if (loadsWholesale(resource.harness, resource.assetId)) {
|
|
113
|
+
entry.wholesale.push({ target, harness: resource.harness, assetId: resource.assetId });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { byDir, managedDirs };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Whether a directory entry is part of the managed tree's own structure — it is, or contains at
|
|
120
|
+
* some depth, the parent directory of a managed resource. Scans `managedDirs`, which is bounded by
|
|
121
|
+
* the number of managed resources rather than by directory size (R6). */
|
|
122
|
+
function holdsManagedAsset(candidatePath , managedDirs ) {
|
|
123
|
+
const prefix = candidatePath + path.sep;
|
|
124
|
+
for (const dir of managedDirs) {
|
|
125
|
+
if (dir === candidatePath || (dir ).startsWith(prefix)) return true;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Inspects the wholesale-loaded parent directories of already-enumerated managed resources for
|
|
132
|
+
* files DoFlow does not manage.
|
|
133
|
+
*
|
|
134
|
+
* @param {Object} options
|
|
135
|
+
* @param {Array<{harness: string, assetId: string, target: string}>} options.resources Managed
|
|
136
|
+
* resources exactly as recorded — from one scope or both concatenated; this module does not care
|
|
137
|
+
* which scope a resource came from, only where it lives on disk and how its harness loads it.
|
|
138
|
+
* @param {Object} [options.fsImpl] Injectable `node:fs`-shaped module, for hermetic tests.
|
|
139
|
+
* @returns {{
|
|
140
|
+
* candidatesByTarget: Object<string, UnmanagedCandidate[]>,
|
|
141
|
+
* directoriesInspected: number,
|
|
142
|
+
* }} `candidatesByTarget` maps a managed resource's absolute `target` to the unmanaged entries
|
|
143
|
+
* sharing its directory (present only for targets whose directory held at least one);
|
|
144
|
+
* `directoriesInspected` is how many wholesale directories actually existed and were read.
|
|
145
|
+
*
|
|
146
|
+
* **Nothing inspected is ever withheld, so no count of withheld entries is returned.** IC-002 names
|
|
147
|
+
* such a count, and this module used to return it as a literal 0 to satisfy that name. Under the
|
|
148
|
+
* narrowed FR-007 the two halves of this module's behaviour leave it no other value to hold: an
|
|
149
|
+
* inspected directory is read in full and every entry that is neither a managed basename nor
|
|
150
|
+
* managed structure is listed, and a directory that is not inspected is never read, so its entries
|
|
151
|
+
* are not inspected either. A field with one reachable value states nothing — and reporting 0
|
|
152
|
+
* beside eight reported siblings invited the reading that eight more had been hidden. IC-002's
|
|
153
|
+
* final field is dropped with it (task F.3); see `index.js#buildInventoryReport`.
|
|
154
|
+
*/
|
|
155
|
+
function inspectSiblings({ resources, fsImpl = fs } = {}) {
|
|
156
|
+
const { byDir, managedDirs } = groupByParentDir(resources);
|
|
157
|
+
const candidatesByTarget = {};
|
|
158
|
+
let directoriesInspected = 0;
|
|
159
|
+
|
|
160
|
+
for (const [dir, { basenames, wholesale }] of byDir) {
|
|
161
|
+
if ((wholesale ).length === 0) continue; // name-resolved: never read, not even to count.
|
|
162
|
+
|
|
163
|
+
let entries;
|
|
164
|
+
try {
|
|
165
|
+
entries = fsImpl.readdirSync(dir, { withFileTypes: true });
|
|
166
|
+
} catch {
|
|
167
|
+
continue; // the managed asset's parent legitimately may not exist; not an error.
|
|
168
|
+
}
|
|
169
|
+
directoriesInspected += 1;
|
|
170
|
+
|
|
171
|
+
const unmanaged = [];
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
const name = (entry ).name;
|
|
174
|
+
if ((basenames ).has(name)) continue; // the managed file itself.
|
|
175
|
+
const entryPath = path.join(dir , name);
|
|
176
|
+
const isDirectory = (entry ).isDirectory?.() === true;
|
|
177
|
+
if (isDirectory && holdsManagedAsset(entryPath, managedDirs)) continue; // managed structure.
|
|
178
|
+
unmanaged.push({ name, path: entryPath, parentDir: dir, kind: isDirectory ? 'directory' : 'file' });
|
|
179
|
+
}
|
|
180
|
+
if (unmanaged.length === 0) continue;
|
|
181
|
+
|
|
182
|
+
// Attributed per managed target rather than shared, so each candidate names the harness and
|
|
183
|
+
// asset whose wholesale load is the reason it is operative at all.
|
|
184
|
+
for (const { target, harness, assetId } of (wholesale )) {
|
|
185
|
+
candidatesByTarget[target] = unmanaged.map((found ) => ({ ...found, harness, assetId }));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { candidatesByTarget, directoriesInspected };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = { inspectSiblings, WHOLESALE_ASSETS };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
// Structure-aware markdown chunker. Chunks follow document
|
|
6
|
+
// structure — frontmatter becomes metadata, headings bound sections, fenced code blocks never
|
|
7
|
+
// split mid-block — because every downstream stage (indexing, ranking, prompting) inherits the
|
|
8
|
+
// coherence this stage preserves. Character budgets approximate common token targets (~150-token
|
|
9
|
+
// children ≈ 600 chars) without a tokenizer dependency: the corpus is
|
|
10
|
+
// prose-dominant English/markdown, where chars/4 tracks tokens closely enough to rank with.
|
|
11
|
+
|
|
12
|
+
const TARGET_CHUNK_CHARS = 600;
|
|
13
|
+
const HARD_MAX_CHUNK_CHARS = 1200;
|
|
14
|
+
|
|
15
|
+
/** Split raw markdown into chunks: {id, path, section, text}. Ids are stable path+ordinal pairs,
|
|
16
|
+
* not content hashes — the index manifest owns content addressing, and a chunk whose text changed
|
|
17
|
+
* should keep its identity so diffs are legible. */
|
|
18
|
+
function chunkMarkdown(rawText , { path: filePath } = {}) {
|
|
19
|
+
const { body, title } = stripFrontmatter(String(rawText ?? ''));
|
|
20
|
+
const sections = splitSections(body);
|
|
21
|
+
const chunks = [];
|
|
22
|
+
let ordinal = 0;
|
|
23
|
+
for (const section of sections) {
|
|
24
|
+
for (const piece of packSection(section.text)) {
|
|
25
|
+
chunks.push({
|
|
26
|
+
id: `${filePath ?? 'memory'}#${ordinal}`,
|
|
27
|
+
path: filePath ?? null,
|
|
28
|
+
title: section.heading ? `${title ? `${title} › ` : ''}${section.heading}` : (title ?? null),
|
|
29
|
+
ordinal,
|
|
30
|
+
text: piece,
|
|
31
|
+
});
|
|
32
|
+
ordinal += 1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return chunks;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stripFrontmatter(text ) {
|
|
39
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
40
|
+
if (!match) return { body: text, title: null };
|
|
41
|
+
const meta = match[1];
|
|
42
|
+
const titleMatch = /^title:\s*(.+)$/m.exec(meta) ?? /^#\s+(.+)$/m.exec(text.slice(match[0].length));
|
|
43
|
+
return { body: text.slice(match[0].length), title: titleMatch ? titleMatch[1].trim().replace(/^["']|["']$/g, '') : null };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Heading-bounded sections; a leading H1 is metadata (the title), not a section boundary. */
|
|
47
|
+
function splitSections(body ) {
|
|
48
|
+
const lines = body.split(/\r?\n/);
|
|
49
|
+
const sections = [];
|
|
50
|
+
let current = { heading: null, lines: [] };
|
|
51
|
+
const flush = () => {
|
|
52
|
+
if (current.lines.some((l) => l.trim() !== '')) {
|
|
53
|
+
sections.push({ heading: current.heading, text: current.lines.join('\n') });
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
const heading = /^(#{2,6})\s+(.*)$/.exec(line);
|
|
58
|
+
if (heading) {
|
|
59
|
+
flush();
|
|
60
|
+
current = { heading: heading[2].trim(), lines: [] };
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
current.lines.push(line);
|
|
64
|
+
}
|
|
65
|
+
flush();
|
|
66
|
+
return sections.length ? sections : [{ heading: null, text: body }];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Group a section's blocks (paragraphs, lists, fenced code) into chunks near the target size.
|
|
70
|
+
* A block larger than the hard max (a long code fence) stands alone rather than being cut. */
|
|
71
|
+
function packSection(text ) {
|
|
72
|
+
const blocks = splitBlocks(text).filter((b) => b.trim() !== '');
|
|
73
|
+
const packed = [];
|
|
74
|
+
let buffer = '';
|
|
75
|
+
for (const block of blocks) {
|
|
76
|
+
if (block.length > HARD_MAX_CHUNK_CHARS) {
|
|
77
|
+
if (buffer.trim()) packed.push(buffer.trim());
|
|
78
|
+
packed.push(block.trim());
|
|
79
|
+
buffer = '';
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (buffer && buffer.length + block.length + 2 > TARGET_CHUNK_CHARS) {
|
|
83
|
+
packed.push(buffer.trim());
|
|
84
|
+
buffer = block;
|
|
85
|
+
} else {
|
|
86
|
+
buffer = buffer ? `${buffer}\n\n${block}` : block;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (buffer.trim()) packed.push(buffer.trim());
|
|
90
|
+
return packed;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Split into blocks at blank lines, keeping fenced code blocks intact across their blank lines. */
|
|
94
|
+
function splitBlocks(text ) {
|
|
95
|
+
const lines = text.split(/\r?\n/);
|
|
96
|
+
const blocks = [];
|
|
97
|
+
let buffer = [];
|
|
98
|
+
let fence = null;
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
const opening = /^\s*(```|~~~)/.exec(line);
|
|
101
|
+
if (fence) {
|
|
102
|
+
buffer.push(line);
|
|
103
|
+
if (opening && opening[1] === fence) fence = null;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (opening) {
|
|
107
|
+
if (buffer.some((l) => l.trim() !== '')) blocks.push(buffer.join('\n'));
|
|
108
|
+
buffer = [line];
|
|
109
|
+
fence = opening[1];
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (line.trim() === '' && !buffer.some((l) => l.trim() !== '')) continue;
|
|
113
|
+
if (line.trim() === '') {
|
|
114
|
+
blocks.push(buffer.join('\n'));
|
|
115
|
+
buffer = [];
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
buffer.push(line);
|
|
119
|
+
}
|
|
120
|
+
if (buffer.some((l) => l.trim() !== '')) blocks.push(buffer.join('\n'));
|
|
121
|
+
return blocks;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { chunkMarkdown, TARGET_CHUNK_CHARS, HARD_MAX_CHUNK_CHARS };
|