@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +132 -2
- package/bin/doflow.js +44 -0
- package/core/.antigravity-plugin/hooks.json +39 -0
- package/core/.antigravity-plugin/mcp_config.json +18 -0
- package/core/.antigravity-plugin/plugin.json +31 -0
- package/core/.claude-plugin/marketplace.json +20 -0
- package/core/.claude-plugin/plugin.json +21 -0
- package/core/.codex-plugin/marketplace.json +20 -0
- package/core/.codex-plugin/plugin.json +35 -0
- package/core/.plugin/plugin.json +19 -0
- package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
- package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
- package/core/harnesses/claude/hooks/lib.sh +304 -0
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
- package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
- package/core/harnesses/claude/hooks/post-compact.sh +74 -0
- package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
- package/core/harnesses/claude/hooks/session-end.sh +89 -0
- package/core/harnesses/claude/hooks/session-start.sh +5 -0
- package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -0
- package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
- package/core/harnesses/claude/settings/keybindings.json +20 -0
- package/core/harnesses/claude/settings/settings.json +201 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
- package/core/harnesses/codex/agents/core-implementer.toml +7 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
- package/core/harnesses/codex/agents/research-writer.toml +8 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
- package/core/harnesses/codex/agents/system-architect.toml +9 -0
- package/core/harnesses/codex/config/config.toml +10 -0
- package/core/harnesses/codex/hooks/hooks.json +122 -0
- package/core/harnesses/codex/hooks/lib.sh +335 -0
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
- package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
- package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
- package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
- package/core/harnesses/codex/hooks/session-end.sh +5 -0
- package/core/harnesses/codex/hooks/session-start.sh +5 -0
- package/core/harnesses/codex/hooks/stop-check.sh +8 -0
- package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
- package/core/harnesses/gemini/hooks/hooks.json +77 -0
- package/core/harnesses/gemini/hooks/lib.sh +302 -0
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
- package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
- package/core/harnesses/gemini/hooks/session-end.sh +5 -0
- package/core/harnesses/gemini/hooks/session-start.sh +5 -0
- package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
- package/core/harnesses/kiro/agents/doflow.json +58 -0
- package/core/harnesses/kiro/hooks/doflow.json +53 -0
- package/core/harnesses/kiro/hooks/lib.sh +324 -0
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
- package/core/harnesses/kiro/hooks/session-start.sh +9 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
- package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
- package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
- package/core/harnesses/shared/hooks/hooks.json +47 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
- package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
- package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
- package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
- package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
- package/core/harnesses/shared/locator/doflow-run +87 -0
- package/core/registry/advice.json +101 -0
- package/core/registry/assets.json +792 -0
- package/core/registry/capabilities.json +120 -0
- package/core/registry/contracts.json +189 -0
- package/core/registry/external-tools.json +191 -0
- package/core/registry/harnesses.json +2062 -0
- package/core/registry/lifecycle.json +231 -0
- package/core/registry/mcp.json +18 -0
- package/core/registry/models.json +79 -0
- package/core/registry/readiness-templates.json +118 -0
- package/core/registry/routes.json +47 -0
- package/core/registry/storage.json +1057 -0
- package/core/registry/verification.json +258 -0
- package/core/registry/workflows.json +790 -0
- package/core/shared/agent-specs/core-implementer.md +73 -0
- package/core/shared/agent-specs/quality-guardian.md +28 -0
- package/core/shared/agent-specs/research-writer.md +31 -0
- package/core/shared/agent-specs/spec-analyst.md +33 -0
- package/core/shared/agent-specs/system-architect.md +29 -0
- package/core/shared/guidance/DOFLOW_CORE.md +8 -0
- package/core/shared/guidance/FLAGS.md +25 -0
- package/core/shared/guidance/PRINCIPLES.md +61 -0
- package/core/shared/guidance/VERSION +1 -0
- package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
- package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
- package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
- package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
- package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
- package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
- package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
- package/core/shared/guidance/pointers/claude-gemini.md +2 -0
- package/core/shared/guidance/pointers/codex.md +6 -0
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
- package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
- package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
- package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
- package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
- package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
- package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
- package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
- package/core/shared/scripts/doflow/bin/doflow-run +592 -0
- package/core/shared/skills/do/SKILL.md +128 -0
- package/core/shared/skills/do/references/estimation.md +13 -0
- package/core/shared/skills/do/references/pm_routing.md +25 -0
- package/core/shared/skills/do/references/tool_matrix.md +52 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
- package/core/shared/skills/do-code-review/README.md +49 -0
- package/core/shared/skills/do-code-review/SKILL.md +333 -0
- package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
- package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
- package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
- package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
- package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
- package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
- package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
- package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
- package/core/shared/skills/do-code-review/content-types/config.md +83 -0
- package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
- package/core/shared/skills/do-code-review/languages/c.md +103 -0
- package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
- package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
- package/core/shared/skills/do-code-review/languages/dart.md +108 -0
- package/core/shared/skills/do-code-review/languages/go.md +92 -0
- package/core/shared/skills/do-code-review/languages/java.md +103 -0
- package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
- package/core/shared/skills/do-code-review/languages/php.md +109 -0
- package/core/shared/skills/do-code-review/languages/python.md +94 -0
- package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
- package/core/shared/skills/do-code-review/languages/rust.md +99 -0
- package/core/shared/skills/do-code-review/languages/shell.md +90 -0
- package/core/shared/skills/do-code-review/languages/swift.md +88 -0
- package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
- package/core/shared/skills/do-code-review/review-policy.json +47 -0
- package/core/shared/skills/do-code-review/rules/universal.md +49 -0
- package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
- package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
- package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
- package/core/shared/skills/do-constitution/SKILL.md +72 -0
- package/core/shared/skills/do-design/SKILL.md +212 -0
- package/core/shared/skills/do-diagnose/SKILL.md +156 -0
- package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
- package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
- package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
- package/core/shared/skills/do-document/SKILL.md +81 -0
- package/core/shared/skills/do-document/references/api-reference.md +42 -0
- package/core/shared/skills/do-document/references/deep_research.md +18 -0
- package/core/shared/skills/do-document/references/embedded-research.md +11 -0
- package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
- package/core/shared/skills/do-document/references/index_generator.md +9 -0
- package/core/shared/skills/do-document/references/user-guide.md +22 -0
- package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
- package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
- package/core/shared/skills/do-flow/SKILL.md +186 -0
- package/core/shared/skills/do-flow/references/task_classes.md +134 -0
- package/core/shared/skills/do-git/SKILL.md +115 -0
- package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
- package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
- package/core/shared/skills/do-git/references/release-ritual.md +64 -0
- package/core/shared/skills/do-implement/SKILL.md +222 -0
- package/core/shared/skills/do-plan/SKILL.md +219 -0
- package/core/shared/skills/do-test/SKILL.md +113 -0
- package/core/shared/templates/doflow/constitution-template.md +28 -0
- package/core/shared/templates/doflow/data-model-template.md +169 -0
- package/core/shared/templates/doflow/design-template.md +193 -0
- package/core/shared/templates/doflow/external-contract-template.md +35 -0
- package/core/shared/templates/doflow/intent-template.md +45 -0
- package/core/shared/templates/doflow/plan-template.md +151 -0
- package/core/shared/templates/doflow/question-log-template.md +51 -0
- package/core/shared/templates/doflow/requirement-template.md +126 -0
- package/core/shared/templates/doflow/specs-template.md +55 -0
- package/core/shared/templates/doflow/state-template.md +59 -0
- package/core/shared/workflows/do-flow-chain.md +17 -0
- package/package.json +46 -4
- package/src/adapters/antigravity/index.ts +631 -0
- package/src/adapters/claude/index.ts +548 -0
- package/src/adapters/claude/mcp.ts +94 -0
- package/src/adapters/codex/agents.ts +208 -0
- package/src/adapters/codex/config.ts +272 -0
- package/src/adapters/codex/hooks.ts +203 -0
- package/src/adapters/codex/index.ts +707 -0
- package/src/adapters/codex/mcp.ts +271 -0
- package/src/adapters/codex/native-projection.ts +34 -0
- package/src/adapters/copilot/index.ts +413 -0
- package/src/adapters/copy-tree.ts +620 -0
- package/src/adapters/gemini/hooks.ts +210 -0
- package/src/adapters/gemini/index.ts +390 -0
- package/src/adapters/hook-commands.ts +60 -0
- package/src/adapters/index.ts +106 -0
- package/src/adapters/kiro/index.ts +310 -0
- package/src/adapters/mcp-entries.ts +331 -0
- package/src/adapters/opencode/index.ts +466 -0
- package/src/adapters/pi/index.ts +279 -0
- package/src/adapters/pi/json-members.ts +185 -0
- package/src/adapters/pi/mcp.ts +373 -0
- package/src/adapters/resolve-source.ts +19 -0
- package/src/cli/commands/install.ts +218 -0
- package/src/cli/commands/list-backups.ts +21 -0
- package/src/cli/commands/reconcile.ts +120 -0
- package/src/cli/commands/remove.ts +82 -0
- package/src/cli/commands/rollback.ts +172 -0
- package/src/cli/commands/self-update.ts +33 -0
- package/src/cli/commands/status.ts +138 -0
- package/src/cli/commands/tools.ts +92 -0
- package/src/cli/commands/update.ts +162 -0
- package/src/cli/index.ts +568 -0
- package/src/cli/runtime-commands.ts +281 -0
- package/src/cli/shared.ts +218 -0
- package/src/helper/bash-helper.ts +34 -0
- package/src/helper/git.ts +24 -0
- package/src/helper/guardrails.ts +49 -0
- package/src/helper/harness-paths.ts +140 -0
- package/src/helper/marker-merge.ts +116 -0
- package/src/helper/prompt.ts +61 -0
- package/src/helper/repo-root.ts +29 -0
- package/src/helper/settings-merge.ts +151 -0
- package/src/helper/settings-scope.ts +33 -0
- package/src/helper/toml.ts +244 -0
- package/src/install/backup-set.ts +149 -0
- package/src/install/backup.ts +724 -0
- package/src/install/context.ts +58 -0
- package/src/install/manifest.ts +145 -0
- package/src/install/mcp.ts +254 -0
- package/src/install/paths.ts +84 -0
- package/src/install/targets.ts +90 -0
- package/src/install/tool-lifecycle.ts +151 -0
- package/src/lifecycle/bash-availability.ts +25 -0
- package/src/lifecycle/index.ts +812 -0
- package/src/lifecycle/mcp-index.ts +28 -0
- package/src/lifecycle/policies.ts +73 -0
- package/src/lifecycle/view.ts +175 -0
- package/src/registry/index.ts +611 -0
- package/src/release/sync-plugin-versions.ts +66 -0
- package/src/runtime/advice/cli.ts +283 -0
- package/src/runtime/advice/client.ts +324 -0
- package/src/runtime/advice/config.ts +189 -0
- package/src/runtime/advice/doctor.ts +169 -0
- package/src/runtime/advice/endpoint-guard.ts +134 -0
- package/src/runtime/advice/kinds.ts +154 -0
- package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
- package/src/runtime/advice/record.ts +166 -0
- package/src/runtime/advice/registry.ts +170 -0
- package/src/runtime/advice/strength.ts +66 -0
- package/src/runtime/brief-tools.ts +342 -0
- package/src/runtime/c4-project.ts +371 -0
- package/src/runtime/capability-router.ts +484 -0
- package/src/runtime/checkouts.ts +170 -0
- package/src/runtime/claims.ts +468 -0
- package/src/runtime/cli-result.ts +55 -0
- package/src/runtime/cli.ts +678 -0
- package/src/runtime/command-detect.ts +516 -0
- package/src/runtime/context-pack.ts +492 -0
- package/src/runtime/decision-register.ts +493 -0
- package/src/runtime/evidence-ledger.ts +338 -0
- package/src/runtime/failure/capture.ts +272 -0
- package/src/runtime/failure/classifier.ts +68 -0
- package/src/runtime/failure/cli.ts +178 -0
- package/src/runtime/failure/home.ts +64 -0
- package/src/runtime/failure/store.ts +226 -0
- package/src/runtime/feature-resolve.ts +79 -0
- package/src/runtime/freshness.ts +177 -0
- package/src/runtime/health.ts +863 -0
- package/src/runtime/history-compactor.ts +237 -0
- package/src/runtime/implementation-gate.ts +321 -0
- package/src/runtime/inventory/coverage.ts +218 -0
- package/src/runtime/inventory/identity.ts +65 -0
- package/src/runtime/inventory/index.ts +705 -0
- package/src/runtime/inventory/precedence.ts +134 -0
- package/src/runtime/inventory/read-scopes.ts +131 -0
- package/src/runtime/inventory/siblings.ts +192 -0
- package/src/runtime/knowledge/chunker.ts +124 -0
- package/src/runtime/knowledge/hybrid.ts +149 -0
- package/src/runtime/knowledge/index-store.ts +145 -0
- package/src/runtime/knowledge/retrieval.ts +194 -0
- package/src/runtime/leak-scan.ts +185 -0
- package/src/runtime/lifecycle/cli.ts +276 -0
- package/src/runtime/lifecycle/event-store.ts +340 -0
- package/src/runtime/lifecycle/fold.ts +353 -0
- package/src/runtime/lifecycle/followup.ts +432 -0
- package/src/runtime/lifecycle/goal.ts +249 -0
- package/src/runtime/lifecycle/intent-writer.ts +86 -0
- package/src/runtime/lifecycle/overview.ts +292 -0
- package/src/runtime/lifecycle/release.ts +241 -0
- package/src/runtime/lifecycle/report-store.ts +221 -0
- package/src/runtime/lifecycle/retention.ts +212 -0
- package/src/runtime/lifecycle/root.ts +71 -0
- package/src/runtime/lifecycle/status.ts +353 -0
- package/src/runtime/lifecycle/store-upkeep.ts +393 -0
- package/src/runtime/locator-resolve.ts +158 -0
- package/src/runtime/mask.ts +210 -0
- package/src/runtime/mcp-consultations.ts +412 -0
- package/src/runtime/mcp-reach.ts +157 -0
- package/src/runtime/model-router.ts +153 -0
- package/src/runtime/outcome.ts +467 -0
- package/src/runtime/reach.ts +148 -0
- package/src/runtime/readiness-record.ts +150 -0
- package/src/runtime/readiness.ts +435 -0
- package/src/runtime/recovery.ts +282 -0
- package/src/runtime/research-request.ts +231 -0
- package/src/runtime/retrieval-plan.ts +578 -0
- package/src/runtime/scaffold/artifacts.ts +217 -0
- package/src/runtime/scaffold/fingerprint.ts +56 -0
- package/src/runtime/scaffold/generate.ts +911 -0
- package/src/runtime/scaffold/languages.ts +245 -0
- package/src/runtime/storage/cli.ts +64 -0
- package/src/runtime/storage/engine.ts +249 -0
- package/src/runtime/storage/registry.ts +130 -0
- package/src/runtime/storage/roots.ts +63 -0
- package/src/runtime/storage/rules.ts +282 -0
- package/src/runtime/storage/upkeep.ts +243 -0
- package/src/runtime/storage/work-status.ts +326 -0
- package/src/runtime/task-attempts.ts +415 -0
- package/src/runtime/task-classifier.ts +550 -0
- package/src/runtime/task-scope.ts +130 -0
- package/src/runtime/task-state.ts +176 -0
- package/src/runtime/trace/indicators.ts +235 -0
- package/src/runtime/trace/ledger.ts +626 -0
- package/src/runtime/trace/render.ts +76 -0
- package/src/runtime/trace/views.ts +459 -0
- package/src/runtime/verification/contract-runner.ts +341 -0
- package/src/runtime/verification/engine.ts +1461 -0
- package/src/runtime/verification/registry.ts +130 -0
- package/src/runtime/verification/scope-bound.ts +418 -0
- package/src/runtime/verification/test-selection.ts +172 -0
- package/src/runtime/workflow-engine.ts +588 -0
- package/src/runtime/workflow-orchestrator.ts +953 -0
- package/src/runtime/worktree.ts +340 -0
- package/src/state/index.ts +193 -0
- package/src/state/lockfile.ts +136 -0
- package/src/state/run-lock.ts +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Masking and normalisation (IC-012). Best-effort, never a guarantee: NFR-003 rests on local-only
|
|
7
|
+
* storage, on keeping a report body on the machine that produced it and on DoFlow never committing
|
|
8
|
+
* the project store. Masking only narrows what a statement or an excerpt can carry.
|
|
9
|
+
*
|
|
10
|
+
* Two profiles. The line profile is for one-line statements and failure messages; the body profile
|
|
11
|
+
* is for report bodies and excerpts and runs every line rule plus the body-only rules 7 to 9.
|
|
12
|
+
* Rules run in the table's order. Normalisation (rules 10 to 13) is a separate step applied to
|
|
13
|
+
* failure messages only, after masking.
|
|
14
|
+
*
|
|
15
|
+
* The module has no I/O and no state; the home directory is read from `HOME` unless the caller
|
|
16
|
+
* passes one.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const MASKED = '<masked>';
|
|
20
|
+
const EMAIL = '<email>';
|
|
21
|
+
const IP = '<ip>';
|
|
22
|
+
|
|
23
|
+
/** Key names whose value is a secret. One list for rules 4 and 7. */
|
|
24
|
+
const KEY_WORDS = 'password|passwd|secret|token|apikey|api_key|api-key|access_key|client_secret|private_key|credential|cookie|session_id';
|
|
25
|
+
/** A key: a whole identifier of at most about 130 characters with the key list inside it. Both runs
|
|
26
|
+
* are bounded, so no input makes the match cost more than that per word start (a long word is tried once). */
|
|
27
|
+
const KEY_NAME = `(?<![A-Za-z0-9_.-])[A-Za-z0-9_.-]{0,64}?(?:${KEY_WORDS})[A-Za-z0-9_.-]{0,64}(?![A-Za-z0-9_.-])`;
|
|
28
|
+
|
|
29
|
+
const TOKEN_SHAPES = [
|
|
30
|
+
/(?:AKIA|ASIA)[0-9A-Z]{16}/g,
|
|
31
|
+
/gh[pousr]_[A-Za-z0-9]{20,}/g,
|
|
32
|
+
/github_pat_[A-Za-z0-9_]{20,}/g,
|
|
33
|
+
/glpat-[A-Za-z0-9_-]{20,}/g,
|
|
34
|
+
// A lookbehind keeps `sk-` from matching inside ordinary hyphenated words such as "risk-...".
|
|
35
|
+
/(?<![A-Za-z0-9])sk-[A-Za-z0-9_-]{16,}/g,
|
|
36
|
+
/xox[abprs]-[A-Za-z0-9-]{10,}/g,
|
|
37
|
+
/npm_[A-Za-z0-9]{24,}/g,
|
|
38
|
+
/AIza[0-9A-Za-z_-]{35}/g,
|
|
39
|
+
/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const PEM = /-----BEGIN [^\n-]*-----(?:[\s\S]*?-----END [^\n-]*-----|[\s\S]*$)/g;
|
|
43
|
+
const BEARER = /\b(bearer)([ \t]+)(?!<masked>)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
44
|
+
const URL_CREDENTIALS = /:\/\/[^/\s:@]+:[^/\s@]+@/g;
|
|
45
|
+
// Rule 4: unquoted, directly after the separator, and only when the value is not plain letters.
|
|
46
|
+
const KEY_VALUE_LINE = new RegExp(`(${KEY_NAME})(=|:[ ]?)([^\\s,;&"'\`)\\]}]+)`, 'gi');
|
|
47
|
+
const LONG_RUN = /(?<![A-Za-z0-9+/=_-])[A-Za-z0-9+/=_-]{32,}(?![A-Za-z0-9+/=_-])/g;
|
|
48
|
+
const EMAIL_ADDRESS = /(?<![A-Za-z0-9._%+-])[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}/g;
|
|
49
|
+
const IPV4 = /(?<![0-9.])(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])(?![0-9]|\.[0-9])/g;
|
|
50
|
+
// Rule 7: any spacing around the separator, a quoted or an unquoted value, whatever its shape.
|
|
51
|
+
const KEY_VALUE_BODY = new RegExp(`(${KEY_NAME}["']?[ \\t]*[=:][ \\t]*)("[^"\\r\\n]*"|'[^'\\r\\n]*'|[^\\s,;&"'\`)\\]}]+)`, 'gi');
|
|
52
|
+
const AUTH_HEADER = /((?:Proxy-)?Authorization:)[ \t]*([^\r\n]*)/gi;
|
|
53
|
+
const SECRET_FLAG = /(--(?:password|passwd|token|secret|api-key|client-secret)(?:=|[ \t]+))("[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+)/gi;
|
|
54
|
+
|
|
55
|
+
function escapeRegExp(text: string): string { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
56
|
+
|
|
57
|
+
/** The home directory to abbreviate: an absolute path other than the file system root. */
|
|
58
|
+
function homeOf(options?: any): string | null {
|
|
59
|
+
const raw = options && 'home' in options ? options.home : process.env.HOME;
|
|
60
|
+
if (typeof raw !== 'string' || !raw.startsWith('/')) return null;
|
|
61
|
+
const home = raw.replace(/\/+$/, '');
|
|
62
|
+
return home.length > 1 ? home : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A run of 32 or more characters that is a path: starts with `/`, `~` or `.` and has no part of 32+. */
|
|
66
|
+
function isPathRun(run: string, before: string | undefined): boolean {
|
|
67
|
+
if (!(run.startsWith('/') || before === '~' || before === '.')) return false;
|
|
68
|
+
return run.split('/').every((part: string) => part.length < 32);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function looksLikeSecretRun(run: string, minDigits: number): boolean {
|
|
72
|
+
const digits = (run.match(/[0-9]/g) || []).length;
|
|
73
|
+
const upper = (run.match(/[A-Z]/g) || []).length;
|
|
74
|
+
const lower = (run.match(/[a-z]/g) || []).length;
|
|
75
|
+
return digits >= minDigits && upper >= 2 && lower >= 2;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Deviation from IC-012 rule 5 as written ("at least two digits"): a cloud secret access key is
|
|
79
|
+
// exactly 40 base64 characters and the spec's own fixture `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`
|
|
80
|
+
// holds one digit, so a 40-character value needs one. The value is measured on its own, after any
|
|
81
|
+
// `=`, `:` or `_` it is glued to, so `KEY=<40 characters>` is masked like the bare key. A git SHA is
|
|
82
|
+
// lower-case hex and still survives. Known limit (DEC-047): the path exemption below lets a key that
|
|
83
|
+
// starts with `/` through.
|
|
84
|
+
const KEY_VALUE_40 = /(?<![A-Za-z0-9+/])[A-Za-z0-9+/]{40}(?![A-Za-z0-9+/])/g;
|
|
85
|
+
|
|
86
|
+
/** Masks the 40-character values inside a run that did not qualify as a whole. */
|
|
87
|
+
function mask40(run: string, before: string | undefined): string {
|
|
88
|
+
return run.replace(KEY_VALUE_40, (value: string, index: number) => (
|
|
89
|
+
looksLikeSecretRun(value, 1) && !isPathRun(value, index === 0 ? before : run[index - 1]) ? MASKED : value));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isMasked(value: string): boolean { return value.replace(/^["']|["']$/g, '') === MASKED; }
|
|
93
|
+
|
|
94
|
+
function count(text: string, token: string): number { return text.split(token).length - 1; }
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Masks `text` with a profile.
|
|
98
|
+
*
|
|
99
|
+
* @param {*} text coerced to a string
|
|
100
|
+
* @param {'line'|'body'} profile
|
|
101
|
+
* @param {{home?: string|null}} [options] `home` overrides `HOME`; `null` turns rule 0 off
|
|
102
|
+
* @returns {{text: string, masked: number}} `masked` counts the values replaced: the net growth in
|
|
103
|
+
* the number of `<masked>`, `<email>` and `<ip>` markers, so a value two rules both reach counts once
|
|
104
|
+
*/
|
|
105
|
+
function mask(text: any, profile: 'line' | 'body', options: any = {}): { text: string; masked: number } {
|
|
106
|
+
if (profile !== 'line' && profile !== 'body') throw new TypeError(`unknown mask profile '${profile}'`);
|
|
107
|
+
const input = text == null ? '' : String(text);
|
|
108
|
+
let out = input;
|
|
109
|
+
|
|
110
|
+
// 0. The home directory prefix.
|
|
111
|
+
const home = homeOf(options);
|
|
112
|
+
// The prefix starts the text or follows a character that cannot be part of a path component, so
|
|
113
|
+
// `/private/var/x` is not cut at a home of `/var/x`.
|
|
114
|
+
if (home) out = out.replace(new RegExp(`(?<![^\\s"'\`=:(\\[])${escapeRegExp(home)}(?=$|[/\\s"'\`:;,)\\]}])`, 'g'), '~');
|
|
115
|
+
// 1. PEM blocks, to the end of the text when the END line is missing.
|
|
116
|
+
out = out.replace(PEM, MASKED);
|
|
117
|
+
// 2. Token shapes.
|
|
118
|
+
for (const shape of TOKEN_SHAPES) out = out.replace(shape, MASKED);
|
|
119
|
+
// 3. Bearer tokens and URL credentials.
|
|
120
|
+
out = out.replace(BEARER, `$1$2${MASKED}`).replace(URL_CREDENTIALS, `://${MASKED}@`);
|
|
121
|
+
// 4. Key=value, unquoted, when the value is not plain letters.
|
|
122
|
+
out = out.replace(KEY_VALUE_LINE, (match: string, key: string, separator: string, value: string) => (
|
|
123
|
+
isMasked(value) || value.startsWith(MASKED) || !/[^A-Za-z]/.test(value) ? match : `${key}${separator}${MASKED}`));
|
|
124
|
+
// 5. Long secret-looking runs that are not paths.
|
|
125
|
+
out = out.replace(LONG_RUN, (run: string, offset: number) => (
|
|
126
|
+
looksLikeSecretRun(run, 2) && !isPathRun(run, out[offset - 1]) ? MASKED : mask40(run, out[offset - 1])));
|
|
127
|
+
// 6. Email addresses and IPv4 addresses.
|
|
128
|
+
out = out.replace(EMAIL_ADDRESS, EMAIL).replace(IPV4, IP);
|
|
129
|
+
|
|
130
|
+
if (profile === 'body') {
|
|
131
|
+
// 7. Key and value, quoted or not, whatever the value looks like.
|
|
132
|
+
out = out.replace(KEY_VALUE_BODY, (match: string, head: string, value: string) => {
|
|
133
|
+
if (isMasked(value) || value.startsWith(MASKED)) return match;
|
|
134
|
+
const quote = value[0] === '"' || value[0] === "'" ? value[0] : '';
|
|
135
|
+
return `${head}${quote}${MASKED}${quote}`;
|
|
136
|
+
});
|
|
137
|
+
// 8. Authorization headers: everything after the colon.
|
|
138
|
+
out = out.replace(AUTH_HEADER, (match: string, name: string, rest: string) => (rest === '' || rest === MASKED ? match : `${name} ${MASKED}`));
|
|
139
|
+
// 9. Secret-bearing command-line flags.
|
|
140
|
+
out = out.replace(SECRET_FLAG, (match: string, flag: string, value: string) => (isMasked(value) ? match : `${flag}${MASKED}`));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const masked = Math.max(0, [MASKED, EMAIL, IP].reduce((sum, token) => sum + count(out, token) - count(input, token), 0));
|
|
144
|
+
return { text: out, masked };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const maskLine = (text: any, options?: any) => mask(text, 'line', options);
|
|
148
|
+
const maskBody = (text: any, options?: any) => mask(text, 'body', options);
|
|
149
|
+
|
|
150
|
+
const ABSOLUTE_PATH = /(?<![A-Za-z0-9_.:/~-])(?:~(?![A-Za-z0-9_])\/?|\/)[^\s"'`<>:;,()[\]{}]*|[A-Za-z]:\\[^\s"'`<>:;,()[\]{}]*/g;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Normalisation rules 10 to 13 (DEC-020), for failure messages. Run it on already-masked text.
|
|
154
|
+
* @param {*} text
|
|
155
|
+
* @returns {string}
|
|
156
|
+
*/
|
|
157
|
+
function normalise(text: any): string {
|
|
158
|
+
let out = text == null ? '' : String(text);
|
|
159
|
+
out = out.replace(ABSOLUTE_PATH, '<path>'); // 10
|
|
160
|
+
out = out.replace(/"[^"]*"|(?<![A-Za-z0-9])'[^']*'|`[^`]*`/g, '"..."'); // 11
|
|
161
|
+
out = out.replace(/[0-9]+/g, 'N'); // 12
|
|
162
|
+
return out.replace(/\s+/g, ' ').trim().slice(0, 200); // 13
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* A failure message as stored: paths first (rule 10), then line-profile masking, then the rest of
|
|
167
|
+
* normalisation. Paths go first so the same bug at differently shaped paths gives one fingerprint:
|
|
168
|
+
* a path with a long mixed-case segment would otherwise be masked by rule 5 as `<masked>` while a
|
|
169
|
+
* short one becomes `<path>`.
|
|
170
|
+
*/
|
|
171
|
+
function normaliseMessage(text: any, options?: any): string {
|
|
172
|
+
const paths = (text == null ? '' : String(text)).replace(ABSOLUTE_PATH, '<path>');
|
|
173
|
+
return normalise(maskLine(paths, options).text);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Line breaks, which print-safe text turns into a space. */
|
|
177
|
+
const PRINT_LINE_BREAK = /[\r\n\u2028\u2029]/g;
|
|
178
|
+
/** C0 and C1 controls except tab (the line breaks are replaced first), and the bidirectional marks and overrides. */
|
|
179
|
+
const PRINT_CONTROL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g;
|
|
180
|
+
const REPLACEMENT = '\uFFFD';
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Makes stored text safe to show a terminal (DEC-046): a control character (ESC, BEL, C1 included) or a
|
|
184
|
+
* bidirectional mark or override becomes U+FFFD, and a line break becomes a space; tab is kept. A
|
|
185
|
+
* string is cleaned, an array or a plain object is cleaned member by member (keys are left alone),
|
|
186
|
+
* and any other value comes back unchanged. It never alters what is stored: call it on the way out.
|
|
187
|
+
*
|
|
188
|
+
* `keepLineBreaks` leaves CR, LF, U+2028 and U+2029 as they are, for data that is multi-line by design
|
|
189
|
+
* (a report excerpt). That data must still go through the plain form before it is printed as text; as a
|
|
190
|
+
* JSON string a line break is escaped, so it is safe there.
|
|
191
|
+
* @template T
|
|
192
|
+
* @param {T} value
|
|
193
|
+
* @param {{keepLineBreaks?: boolean}} [options]
|
|
194
|
+
* @returns {T}
|
|
195
|
+
*/
|
|
196
|
+
function printSafe(value: any, options: { keepLineBreaks?: boolean } = {}): any {
|
|
197
|
+
if (typeof value === 'string') {
|
|
198
|
+
const spaced = options.keepLineBreaks ? value : value.replace(PRINT_LINE_BREAK, ' ');
|
|
199
|
+
return spaced.replace(PRINT_CONTROL, REPLACEMENT);
|
|
200
|
+
}
|
|
201
|
+
if (Array.isArray(value)) return value.map((item: any) => printSafe(item, options));
|
|
202
|
+
if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
|
|
203
|
+
const out: Record<string, any> = {};
|
|
204
|
+
for (const key of Object.keys(value)) out[key] = printSafe(value[key], options);
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = { mask, maskLine, maskBody, normalise, normaliseMessage, printSafe, MASKED };
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MCP consultation resolver (062 CH4, IC-005 and IC-007 to IC-011).
|
|
7
|
+
*
|
|
8
|
+
* At a stage handoff this decides which MCP servers the stage run names, resolves one outcome per
|
|
9
|
+
* server in a fixed order, reads `Tools used:` lines out of the reports briefs named, and returns
|
|
10
|
+
* the record to store, the warnings and notices to print, and the evidence items to add.
|
|
11
|
+
*
|
|
12
|
+
* Pure: it reads only what the caller injects (ledger, research-request store, brief records,
|
|
13
|
+
* report reader, reach object) and writes nothing. No model call, no network, no server or
|
|
14
|
+
* provider name in code: every id comes from the registry argument (NFR-001, NFR-008). Text an MCP
|
|
15
|
+
* server returned never reaches here as anything but a flag token, a claim attribution or an
|
|
16
|
+
* install fact, so it cannot decide a state or a warning (NFR-006).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const OUTCOME_STATES = Object.freeze(['consulted-used', 'consulted-nothing-usable', 'skipped', 'unavailable']);
|
|
20
|
+
|
|
21
|
+
/** Five distinct labels (NFR-007). `no-outcome` is derived, never stored. A skipped outcome's
|
|
22
|
+
* text surfaces append its reason: see `outcomeLabel`. */
|
|
23
|
+
const OUTCOME_LABELS: Readonly<Record<string, string>> = Object.freeze({
|
|
24
|
+
'consulted-used': 'consulted, used',
|
|
25
|
+
'consulted-nothing-usable': 'consulted, nothing usable',
|
|
26
|
+
skipped: 'skipped',
|
|
27
|
+
unavailable: 'unavailable',
|
|
28
|
+
'no-outcome': 'no outcome recorded',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const FLAG_TOKEN_STATE: Readonly<Record<string, string>> = Object.freeze({
|
|
32
|
+
used: 'consulted-used',
|
|
33
|
+
'nothing-usable': 'consulted-nothing-usable',
|
|
34
|
+
unavailable: 'unavailable',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const SERVER_FLAG_ID = /^[a-z][a-z0-9-]*$/;
|
|
38
|
+
const TOOL_NAME = /^[A-Za-z0-9_.:/-]+$/;
|
|
39
|
+
const REASON = /^[^,\r\n]{1,120}$/;
|
|
40
|
+
const CONTENT_LIMIT = 500;
|
|
41
|
+
const SCHEMA = 1;
|
|
42
|
+
|
|
43
|
+
// used > nothing usable > unavailable > skipped (IC-008 rule 2).
|
|
44
|
+
const PRECEDENCE: Readonly<Record<string, number>> = Object.freeze({
|
|
45
|
+
'consulted-used': 4, 'consulted-nothing-usable': 3, unavailable: 2, skipped: 1,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function outcomeLabel(outcome: any): string {
|
|
49
|
+
const label = OUTCOME_LABELS[outcome.state];
|
|
50
|
+
return outcome.state === 'skipped' && outcome.reason ? `${label}: ${outcome.reason}` : label;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse one outcome token (IC-005 flag token). Returns `{ state, reason? }` or `{ error }`.
|
|
55
|
+
* @param {string} token
|
|
56
|
+
*/
|
|
57
|
+
function parseOutcomeToken(token: any): any {
|
|
58
|
+
const text = String(token ?? '').trim();
|
|
59
|
+
if (Object.hasOwn(FLAG_TOKEN_STATE, text)) return { state: FLAG_TOKEN_STATE[text] };
|
|
60
|
+
if (text === 'skipped' || text === 'skipped:') return { error: 'skipped needs a reason: skipped:<reason>' };
|
|
61
|
+
if (text.startsWith('skipped:')) {
|
|
62
|
+
const reason = text.slice('skipped:'.length).trim();
|
|
63
|
+
if (!REASON.test(reason)) return { error: 'skipped reason must be 1 to 120 characters with no comma or newline' };
|
|
64
|
+
return { state: 'skipped', reason };
|
|
65
|
+
}
|
|
66
|
+
return { error: `unknown outcome '${text}'; use used, nothing-usable, unavailable or skipped:<reason>` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parse the repeatable `--mcp-outcome` values (IC-006). A bad value is returned in `ignored`
|
|
71
|
+
* with its reason and never in `outcomes`; the first value for a server wins.
|
|
72
|
+
* @param {string[]|string|undefined} values
|
|
73
|
+
* @returns {{ outcomes: Map<string, {state: string, reason?: string}>, ignored: Array<{value: string, reason: string}> }}
|
|
74
|
+
*/
|
|
75
|
+
function parseMcpOutcomeFlags(values?: string[] | string | null): { outcomes: Map<string, any>; ignored: Array<{ value: string; reason: string }> } {
|
|
76
|
+
const list: any[] = values === undefined || values === null ? [] : ([] as any[]).concat(values);
|
|
77
|
+
const outcomes = new Map<string, any>();
|
|
78
|
+
const ignored: Array<{ value: string; reason: string }> = [];
|
|
79
|
+
for (const raw of list) {
|
|
80
|
+
const value = String(raw);
|
|
81
|
+
const at = value.indexOf('=');
|
|
82
|
+
const server = at === -1 ? '' : value.slice(0, at).trim();
|
|
83
|
+
if (at === -1 || !SERVER_FLAG_ID.test(server)) {
|
|
84
|
+
ignored.push({ value, reason: 'expected <server>=<outcome> with a lowercase server id' });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const parsed = parseOutcomeToken(value.slice(at + 1));
|
|
88
|
+
if (parsed.error) { ignored.push({ value, reason: parsed.error }); continue; }
|
|
89
|
+
if (outcomes.has(server)) { ignored.push({ value, reason: `outcome for '${server}' was already given` }); continue; }
|
|
90
|
+
outcomes.set(server, parsed);
|
|
91
|
+
}
|
|
92
|
+
return { outcomes, ignored };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Strip a leading `- `, `**...**` emphasis and a line-wide pair of backticks, so
|
|
96
|
+
* `- **Tools used:** a` and `` `Tools used: a` `` both read as `Tools used: a`. */
|
|
97
|
+
function plainLine(line: string): string {
|
|
98
|
+
let text = line.trim().replace(/^-\s+/, '');
|
|
99
|
+
if (text.startsWith('**')) text = text.slice(2).replace('**', '');
|
|
100
|
+
if (text.startsWith('`Tools used:') && text.endsWith('`')) text = text.slice(1, -1);
|
|
101
|
+
return text;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Split on commas outside parentheses, so a note like `(a, b)` stays with its entry. */
|
|
105
|
+
function splitEntries(body: string): string[] {
|
|
106
|
+
const pieces: string[] = [];
|
|
107
|
+
let depth = 0;
|
|
108
|
+
let current = '';
|
|
109
|
+
for (const char of body) {
|
|
110
|
+
if (char === '(') depth += 1;
|
|
111
|
+
else if (char === ')' && depth > 0) depth -= 1;
|
|
112
|
+
if (char === ',' && depth === 0) { pieces.push(current); current = ''; } else current += char;
|
|
113
|
+
}
|
|
114
|
+
pieces.push(current);
|
|
115
|
+
return pieces;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** One entry with a trailing ` (note)` and a surrounding pair of backticks removed. */
|
|
119
|
+
function plainEntry(piece: string): string {
|
|
120
|
+
let text = piece.trim().replace(/\s+\([^()]*\)$/, '').trim();
|
|
121
|
+
if (text.length > 1 && text.startsWith('`') && text.endsWith('`')) text = text.slice(1, -1).trim();
|
|
122
|
+
return text;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Parse the last `Tools used:` line of a report (IC-009).
|
|
127
|
+
* @param {string} text report text
|
|
128
|
+
* @returns {null | { line: number, raw: string, entries: Array<{name: string, state?: string, reason?: string, raw: string, valid: boolean}> }}
|
|
129
|
+
*/
|
|
130
|
+
function parseToolsUsed(text: any): any {
|
|
131
|
+
const lines = String(text ?? '').split('\n');
|
|
132
|
+
let found = -1;
|
|
133
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
134
|
+
if (plainLine(lines[i]).startsWith('Tools used:')) found = i;
|
|
135
|
+
}
|
|
136
|
+
if (found === -1) return null;
|
|
137
|
+
const raw = lines[found].replace(/\s+$/, '').trim();
|
|
138
|
+
const body = plainLine(lines[found]).slice('Tools used:'.length).trim();
|
|
139
|
+
const entries: any[] = [];
|
|
140
|
+
if (body !== '' && body !== 'none') {
|
|
141
|
+
for (const piece of splitEntries(body)) {
|
|
142
|
+
const entry = plainEntry(piece);
|
|
143
|
+
if (entry === '') continue;
|
|
144
|
+
const at = entry.indexOf('=');
|
|
145
|
+
const name = at === -1 ? entry : entry.slice(0, at).trim();
|
|
146
|
+
if (!TOOL_NAME.test(name)) { entries.push({ name: entry, raw: entry, valid: false }); continue; }
|
|
147
|
+
if (at === -1) { entries.push({ name, state: 'consulted-used', raw: entry, valid: true }); continue; }
|
|
148
|
+
const parsed = parseOutcomeToken(entry.slice(at + 1));
|
|
149
|
+
if (parsed.error) { entries.push({ name, raw: entry, valid: false }); continue; }
|
|
150
|
+
entries.push({ name, ...parsed, raw: entry, valid: true });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { line: found + 1, raw, entries };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Server ids the registry knows: `mcp` servers plus every provider's `mcp.server`. */
|
|
157
|
+
function knownServerIds(registry: any): Set<string> {
|
|
158
|
+
const ids = new Set<string>((registry?.mcp || []).map((server: any) => server.id));
|
|
159
|
+
for (const capability of Object.values(registry?.capabilities || {}) as any[]) {
|
|
160
|
+
for (const provider of capability?.providers || []) {
|
|
161
|
+
if (provider?.mcp?.server) ids.add(provider.mcp.server);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return ids;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Servers a stage run names (IC-001 meaning). Sorted.
|
|
169
|
+
* @param {{registry: object, skill: string, stageId?: string, taskId?: string, requests?: {list: Function}}} input
|
|
170
|
+
*/
|
|
171
|
+
function namedServers({ registry, skill, stageId, taskId, requests }: any): string[] {
|
|
172
|
+
let researchRequested: boolean | undefined;
|
|
173
|
+
const hasRequest = (): boolean => {
|
|
174
|
+
if (researchRequested === undefined) {
|
|
175
|
+
let list: any = [];
|
|
176
|
+
try { list = requests && taskId ? requests.list(taskId) : []; } catch { list = []; }
|
|
177
|
+
researchRequested = Array.isArray(list) && list.some((request: any) => request && request.stageId === stageId);
|
|
178
|
+
}
|
|
179
|
+
return researchRequested;
|
|
180
|
+
};
|
|
181
|
+
const named = new Set<string>();
|
|
182
|
+
for (const server of registry?.mcp || []) {
|
|
183
|
+
for (const step of server.steps || []) {
|
|
184
|
+
if (step.expectedWhen === 'research-request') {
|
|
185
|
+
if (hasRequest()) named.add(server.id);
|
|
186
|
+
} else if (step.skill === skill) {
|
|
187
|
+
named.add(server.id);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return [...named].sort();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const bySortKey = (a: any, b: any): number => (a < b ? -1 : a > b ? 1 : 0);
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Resolve a stage run's MCP consultations (IC-008).
|
|
198
|
+
*
|
|
199
|
+
* Injected readers (all optional; a missing one contributes nothing):
|
|
200
|
+
* - `ledger.queryEvidence({taskId})` -> evidence items (active ones count)
|
|
201
|
+
* - `requests.list(taskId)` -> research requests
|
|
202
|
+
* - `briefRecords` -> IC-018 records already parsed, each `{ runTaskId, stage, tasks, expected }`
|
|
203
|
+
* - `readReport(path)` -> report text, or null when the file is missing
|
|
204
|
+
* - `reach.anyHarnessHas(server)` -> true | false | null (unknown); never decides an outcome
|
|
205
|
+
* - `reach.harnessWith(server)` -> the display name of a harness that has the server, or null
|
|
206
|
+
* - `harnessName(id)` -> a harness id's display name (for a brief record's `harness`)
|
|
207
|
+
* - `addCommand(server)` -> the text after "add it:" in the unavailable notice
|
|
208
|
+
* - `since` -> ISO time of the previous stage's completion (or the run start); rule 3 counts only
|
|
209
|
+
* evidence observed after it
|
|
210
|
+
*
|
|
211
|
+
* @returns {{ record: object, warnings: object[], notices: object[], evidenceToAdd: object[] }}
|
|
212
|
+
*/
|
|
213
|
+
function resolveConsultations({
|
|
214
|
+
taskId = null, stageId = null, skill, flags, registry, ledger, requests,
|
|
215
|
+
briefRecords = [], readReport, reach, addCommand, harnessName, since = null,
|
|
216
|
+
}: any = {}): { record: any; warnings: any[]; notices: any[]; evidenceToAdd: any[] } {
|
|
217
|
+
const warnings: any[] = [];
|
|
218
|
+
const notices: any[] = [];
|
|
219
|
+
const evidenceToAdd: any[] = [];
|
|
220
|
+
const registered = new Set((registry?.mcp || []).map((server: any) => server.id));
|
|
221
|
+
const known = knownServerIds(registry);
|
|
222
|
+
const named = namedServers({ registry, skill, stageId, taskId, requests });
|
|
223
|
+
|
|
224
|
+
// Rule 1: flags.
|
|
225
|
+
const parsedFlags = parseMcpOutcomeFlags(flags);
|
|
226
|
+
for (const bad of parsedFlags.ignored) {
|
|
227
|
+
warnings.push({
|
|
228
|
+
kind: 'ignored-flag',
|
|
229
|
+
text: `[WARN] mcp: ignored --mcp-outcome '${bad.value}': ${bad.reason}`,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Rule 2: Tools used lines in the reports of this run's briefs. Records with no stage are never
|
|
234
|
+
// ingested (design A6).
|
|
235
|
+
const toolsUsed: any[] = [];
|
|
236
|
+
const fromReports = new Map<string, any[]>();
|
|
237
|
+
const records = (Array.isArray(briefRecords) ? briefRecords : [])
|
|
238
|
+
.filter((record: any) => record && stageId && taskId && record.stage === stageId && record.runTaskId === taskId);
|
|
239
|
+
const reports: Array<{ id: string; report: string }> = [];
|
|
240
|
+
for (const record of records) {
|
|
241
|
+
for (const task of record.tasks || []) {
|
|
242
|
+
if (task && typeof task.id === 'string' && typeof task.report === 'string') reports.push({ id: task.id, report: task.report });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
reports.sort((a, b) => bySortKey(a.id, b.id) || bySortKey(a.report, b.report));
|
|
246
|
+
const existing = new Map<string, any[]>();
|
|
247
|
+
const existingFor = (id: string): any[] => {
|
|
248
|
+
if (!existing.has(id)) {
|
|
249
|
+
let items: any[] = [];
|
|
250
|
+
try { items = ledger ? ledger.queryEvidence({ taskId: id }) : []; } catch { items = []; }
|
|
251
|
+
existing.set(id, items.filter((item: any) => item && item.status === 'active'));
|
|
252
|
+
}
|
|
253
|
+
return existing.get(id) as any[];
|
|
254
|
+
};
|
|
255
|
+
for (const { id, report } of reports) {
|
|
256
|
+
let text: any = null;
|
|
257
|
+
try { text = typeof readReport === 'function' ? readReport(report) : null; } catch { text = null; }
|
|
258
|
+
if (typeof text !== 'string') {
|
|
259
|
+
toolsUsed.push({ taskId: id, status: 'no-report', report });
|
|
260
|
+
warnings.push({
|
|
261
|
+
kind: 'tools-used-no-report', taskId: id,
|
|
262
|
+
text: `[WARN] tools-used: task ${id} has no report at ${report}`,
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const parsed = parseToolsUsed(text);
|
|
267
|
+
if (!parsed) {
|
|
268
|
+
toolsUsed.push({ taskId: id, status: 'no-line', report });
|
|
269
|
+
warnings.push({
|
|
270
|
+
kind: 'tools-used-missing', taskId: id,
|
|
271
|
+
text: `[WARN] tools-used: task ${id} report ${report} has no "Tools used:" line`,
|
|
272
|
+
});
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const source = { provider: 'dispatched-agent', capability: 'tools-used' };
|
|
276
|
+
const content = parsed.raw.slice(0, CONTENT_LIMIT);
|
|
277
|
+
const duplicate = existingFor(id).find((item: any) => item.source?.provider === source.provider
|
|
278
|
+
&& item.source?.capability === source.capability && item.content === content);
|
|
279
|
+
const inBatch = evidenceToAdd.some((item: any) => item.taskId === id && item.content === content);
|
|
280
|
+
if (duplicate) {
|
|
281
|
+
toolsUsed.push({ taskId: id, status: 'already-recorded', evidenceId: duplicate.id });
|
|
282
|
+
} else if (inBatch) {
|
|
283
|
+
toolsUsed.push({ taskId: id, status: 'already-recorded' });
|
|
284
|
+
} else {
|
|
285
|
+
toolsUsed.push({ taskId: id, status: 'recorded' });
|
|
286
|
+
evidenceToAdd.push({
|
|
287
|
+
taskId: id, kind: 'generated-analysis', provenance: 'asserted', source, content,
|
|
288
|
+
locator: { file: report, line: parsed.line }, establishes: [],
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
for (const entry of parsed.entries) {
|
|
292
|
+
if (!entry.valid && known.has(entry.name)) {
|
|
293
|
+
warnings.push({ kind: 'tools-used-unparsed', taskId: id, text: `[WARN] tools-used: unparsed entry '${entry.raw}'` });
|
|
294
|
+
}
|
|
295
|
+
if (!entry.valid || !known.has(entry.name)) continue;
|
|
296
|
+
const list = fromReports.get(entry.name) || [];
|
|
297
|
+
list.push({ state: entry.state, reason: entry.reason, taskId: id });
|
|
298
|
+
fromReports.set(entry.name, list);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Rule 3: evidence attributed to a server by `source.provider`, observed during this stage run:
|
|
303
|
+
// after `since` (the previous stage's completion or the run start) when the caller knows it.
|
|
304
|
+
const evidenceServers = new Set<string>();
|
|
305
|
+
const inThisStage = (item: any): boolean => since === null || (typeof item.freshness?.observedAt === 'string' && item.freshness.observedAt > since);
|
|
306
|
+
if (ledger && taskId) {
|
|
307
|
+
let items: any[] = [];
|
|
308
|
+
try { items = ledger.queryEvidence({ taskId }); } catch { items = []; }
|
|
309
|
+
for (const item of items) {
|
|
310
|
+
if (item && item.status === 'active' && known.has(item.source?.provider) && inThisStage(item)) evidenceServers.add(item.source.provider);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Rule 4: a brief record for this stage that expected the server and found it unreachable. The
|
|
315
|
+
// brief writes `unreachable` only where every config the harness merges was read (IC-017).
|
|
316
|
+
const unreachable = new Map<string, any>();
|
|
317
|
+
const reachableOn = new Map<string, any>();
|
|
318
|
+
for (const record of records) {
|
|
319
|
+
for (const expected of record.expected || []) {
|
|
320
|
+
if (!expected || typeof expected.server !== 'string') continue;
|
|
321
|
+
if (expected.reach === 'unreachable' && !unreachable.has(expected.server)) unreachable.set(expected.server, record.tasks?.[0]?.id);
|
|
322
|
+
if (expected.reach === 'reachable' && !reachableOn.has(expected.server)) reachableOn.set(expected.server, record.harness ?? null);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Who has a server the run asserted unavailable: the brief's harness, else an installed one. */
|
|
327
|
+
const nameOf = (id: any): string => (typeof harnessName === 'function' && harnessName(id)) || id;
|
|
328
|
+
const contradiction = (server: string): string | null => {
|
|
329
|
+
if (reachableOn.has(server)) {
|
|
330
|
+
const id = reachableOn.get(server);
|
|
331
|
+
return id ? nameOf(id) : 'an installed harness';
|
|
332
|
+
}
|
|
333
|
+
let has: any = null;
|
|
334
|
+
try { has = reach && typeof reach.anyHarnessHas === 'function' ? reach.anyHarnessHas(server) : null; } catch { has = null; }
|
|
335
|
+
if (has !== true) return null;
|
|
336
|
+
let name: any = null;
|
|
337
|
+
try { name = typeof reach.harnessWith === 'function' ? reach.harnessWith(server) : null; } catch { name = null; }
|
|
338
|
+
return name || 'an installed harness';
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const candidates = new Set([
|
|
342
|
+
...named, ...parsedFlags.outcomes.keys(), ...fromReports.keys(), ...evidenceServers, ...unreachable.keys(),
|
|
343
|
+
]);
|
|
344
|
+
const outcomes: any[] = [];
|
|
345
|
+
for (const server of [...candidates].sort()) {
|
|
346
|
+
let outcome: any = null;
|
|
347
|
+
const flag = parsedFlags.outcomes.get(server);
|
|
348
|
+
if (flag) {
|
|
349
|
+
outcome = { server, state: flag.state, ...(flag.reason ? { reason: flag.reason } : {}), source: 'flag' };
|
|
350
|
+
} else if (fromReports.has(server)) {
|
|
351
|
+
const best = (fromReports.get(server) as any[]).reduce((a: any, b: any) => (PRECEDENCE[b.state] > PRECEDENCE[a.state] ? b : a));
|
|
352
|
+
outcome = {
|
|
353
|
+
server, state: best.state, ...(best.reason ? { reason: best.reason } : {}), source: 'tools-used', taskId: best.taskId,
|
|
354
|
+
};
|
|
355
|
+
} else if (evidenceServers.has(server)) {
|
|
356
|
+
outcome = { server, state: 'consulted-used', source: 'evidence' };
|
|
357
|
+
} else if (unreachable.has(server)) {
|
|
358
|
+
const firstTask = unreachable.get(server);
|
|
359
|
+
outcome = { server, state: 'unavailable', source: 'brief', ...(firstTask ? { taskId: firstTask } : {}) };
|
|
360
|
+
}
|
|
361
|
+
// Install facts alone never decide an outcome (rule 5): a server DoFlow does not see may still
|
|
362
|
+
// be loaded from a config it cannot read, so the no-outcome warning asks instead.
|
|
363
|
+
|
|
364
|
+
if (flag && !registered.has(server)) {
|
|
365
|
+
notices.push({
|
|
366
|
+
kind: 'unregistered-server', server,
|
|
367
|
+
text: `[INFO] mcp: ${server} is not in DoFlow's MCP registry; recorded as given`,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (!outcome) {
|
|
371
|
+
warnings.push({
|
|
372
|
+
kind: 'no-outcome', server,
|
|
373
|
+
text: `[WARN] mcp: ${skill} recorded no outcome for ${server}; pass --mcp-outcome ${server}=used|nothing-usable|skipped:<reason>|unavailable`,
|
|
374
|
+
});
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
outcomes.push(outcome);
|
|
378
|
+
if (outcome.state === 'unavailable') {
|
|
379
|
+
const has = outcome.source === 'flag' || outcome.source === 'tools-used' ? contradiction(server) : null;
|
|
380
|
+
if (has) {
|
|
381
|
+
warnings.push({
|
|
382
|
+
kind: 'unavailable-contradicted', server,
|
|
383
|
+
text: `[WARN] mcp: ${server} reported unavailable but ${has} has it configured`,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
const add = !has && typeof addCommand === 'function' ? addCommand(server) : null;
|
|
387
|
+
notices.push({
|
|
388
|
+
kind: 'unavailable', server,
|
|
389
|
+
text: `[INFO] mcp: ${server} unavailable to ${skill}${add ? `; add it: ${add}` : ''}`,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
toolsUsed.sort((a, b) => bySortKey(a.taskId, b.taskId));
|
|
395
|
+
return {
|
|
396
|
+
record: { schema: SCHEMA, skill, named, outcomes, toolsUsed },
|
|
397
|
+
warnings,
|
|
398
|
+
notices,
|
|
399
|
+
evidenceToAdd,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
module.exports = {
|
|
404
|
+
OUTCOME_STATES,
|
|
405
|
+
OUTCOME_LABELS,
|
|
406
|
+
outcomeLabel,
|
|
407
|
+
parseOutcomeToken,
|
|
408
|
+
parseMcpOutcomeFlags,
|
|
409
|
+
parseToolsUsed,
|
|
410
|
+
namedServers,
|
|
411
|
+
resolveConsultations,
|
|
412
|
+
};
|