@khoavu882/doflow 2.0.0-beta.5 → 2.0.1
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/README.md +16 -16
- package/bin/doflow.js +22 -12
- package/core/.antigravity-plugin/plugin.json +1 -2
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +26 -9
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/hooks.json +0 -10
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +51 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -11
- package/core/harnesses/kiro/hooks/doflow.json +0 -10
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
- package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +35 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1445 -366
- package/core/registry/intake.json +53 -0
- package/core/registry/lifecycle.json +599 -996
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/intake-category/files/category.json +8 -0
- package/core/registry/package-templates/intake-category/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +272 -0
- package/core/registry/storage.json +687 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -6
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
- package/core/shared/scripts/doflow/bin/doflow-run +152 -34
- package/core/shared/skills/do/SKILL.md +14 -12
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +48 -148
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +23 -20
- package/core/shared/skills/do-diagnose/SKILL.md +18 -6
- package/core/shared/skills/do-document/SKILL.md +12 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +19 -7
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +10 -11
- package/core/shared/skills/do-flow/references/task_classes.md +5 -5
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +13 -14
- package/core/shared/skills/do-plan/SKILL.md +34 -20
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/shared/workflows/do-flow-chain.md +2 -0
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +143 -66
- package/src/adapters/claude/index.js +46 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +39 -3
- package/src/adapters/codex/index.js +61 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +291 -46
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +22 -3
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +337 -32
- package/src/cli/runtime-commands.js +220 -60
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +4 -16
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +472 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +20 -11
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/pointer.js +67 -12
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +158 -79
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-ledger.js +52 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +624 -18
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +281 -26
- package/src/runtime/intake/engine.js +677 -0
- package/src/runtime/intake/index.js +136 -0
- package/src/runtime/intake/manifest.js +140 -0
- package/src/runtime/intake/taxonomy.js +206 -0
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +88 -10
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +38 -7
- package/src/runtime/lifecycle/overview.js +26 -2
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +2 -2
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +487 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/attendance.js +137 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +273 -0
- package/src/runtime/roadmap/loop.js +1719 -0
- package/src/runtime/roadmap/merge.js +390 -0
- package/src/runtime/roadmap/rows.js +1122 -0
- package/src/runtime/roadmap/seed.js +1063 -0
- package/src/runtime/roadmap/worktree-cleanup.js +147 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +1 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +85 -14
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +373 -64
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verb-view.js +267 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -54
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +256 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +610 -67
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -251
- package/src/adapters/gemini/index.js +0 -429
package/src/runtime/mask.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the project store. Masking only narrows what a statement or an excerpt can carry.
|
|
9
9
|
*
|
|
10
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.
|
|
11
|
+
* is for report bodies and excerpts and runs every line rule plus the body-only rules 7 to 9 and 14 to 17.
|
|
12
12
|
* Rules run in the table's order. Normalisation (rules 10 to 13) is a separate step applied to
|
|
13
13
|
* failure messages only, after masking.
|
|
14
14
|
*
|
|
@@ -36,7 +36,9 @@ const TOKEN_SHAPES = [
|
|
|
36
36
|
/xox[abprs]-[A-Za-z0-9-]{10,}/g,
|
|
37
37
|
/npm_[A-Za-z0-9]{24,}/g,
|
|
38
38
|
/AIza[0-9A-Za-z_-]{35}/g,
|
|
39
|
-
|
|
39
|
+
// Left-anchored (DEC-069): without it every `eyJ` inside a long dotless run starts a scan to the
|
|
40
|
+
// run's end, which makes masking one line quadratic in its length.
|
|
41
|
+
/(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
40
42
|
];
|
|
41
43
|
|
|
42
44
|
const PEM = /-----BEGIN [^\n-]*-----(?:[\s\S]*?-----END [^\n-]*-----|[\s\S]*$)/g;
|
|
@@ -52,13 +54,107 @@ const KEY_VALUE_BODY = new RegExp(`(${KEY_NAME}["']?[ \\t]*[=:][ \\t]*)("[^"\\r\
|
|
|
52
54
|
const AUTH_HEADER = /((?:Proxy-)?Authorization:)[ \t]*([^\r\n]*)/gi;
|
|
53
55
|
const SECRET_FLAG = /(--(?:password|passwd|token|secret|api-key|client-secret)(?:=|[ \t]+))("[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+)/gi;
|
|
54
56
|
|
|
57
|
+
// Rules 14 to 17 (body profile only, FU-ht8b3r). Command shapes that carry a secret without a key
|
|
58
|
+
// word: `curl -u user:pw` (also glued, `-uuser:pw`), `mysql -psecret` (value glued to the flag; a bare
|
|
59
|
+
// `-p` prompts and carries none), and the phrase `password hunter2`. The flags are masked by one
|
|
60
|
+
// left-to-right scan (rules 14 and 15, `maskCommandFlags`) that knows which command it is in, so every
|
|
61
|
+
// flag of a command is masked and `sort -u`, `git push -u` and `mysql -P 3306` stay as written.
|
|
62
|
+
// A command word counts only at command position (see `maskCommandFlags`), and quoted text is scanned,
|
|
63
|
+
// not skipped. Alternatives, in order: a quote, a command break (`|`, `;`, `&`, parentheses, a
|
|
64
|
+
// backtick, a line end), curl's `--user` and `-u` (the short one may be glued to its value), mysql's
|
|
65
|
+
// `-p` glued to its value, any other word.
|
|
66
|
+
const COMMAND_SCAN = /(?<quote>["'])|(?<brk>[|;&()`\r\n])|(?<=[ \t])(?:(?<long>--user)(?:=|[ \t]+)|(?<short>-u)(?:=|[ \t]*)(?=[^\s|;&]))|(?<=[ \t])(?<pass>-p)(?=[^\s|;&-])|(?<word>[^\s|;&()`"']+)/g;
|
|
67
|
+
// The closing quote of a quoted span, on the same line.
|
|
68
|
+
const QUOTE_CLOSE = { '"': /[^"\r\n]*"/y, "'": /[^'\r\n]*'/y };
|
|
69
|
+
// A word that is the command: bare or as a path (never a URL, which holds `:`).
|
|
70
|
+
const COMMAND_WORD = /^(?:(?:\.{0,2}|~)?\/(?:[^\s:]*\/)?)?(?:(curl)|(mysql(?:dump|admin|pump|import)?))$/;
|
|
71
|
+
// Words after which the next word is the command: wrappers, remote and container runners, shell keywords.
|
|
72
|
+
const WRAPPER_WORDS = new Set(['sudo', 'doas', 'su', 'env', 'exec', 'nohup', 'time', 'timeout', 'nice', 'ionice', 'xargs', 'watch',
|
|
73
|
+
'command', 'builtin', 'eval', 'setsid', 'stdbuf', 'flock', 'chroot', 'ssh', 'docker', 'podman', 'kubectl', 'sh', 'bash', 'zsh', 'dash', 'ksh',
|
|
74
|
+
'if', 'then', 'else', 'elif', 'do', 'while', 'until', '!', '{']);
|
|
75
|
+
// A first word that does not use up the command position: a prompt mark, a variable assignment or a
|
|
76
|
+
// label such as `Running:`.
|
|
77
|
+
const PROMPT_OR_ASSIGNMENT = /^(?:[$#%>+]+|[A-Za-z_][A-Za-z0-9_]*=[^=]*|[A-Za-z][A-Za-z0-9_-]*:)$/;
|
|
78
|
+
// A flag value: quoted, or unquoted up to a space, quote or command break.
|
|
79
|
+
const COMMAND_FLAG_VALUE = /"[^"\r\n]*"|'[^'\r\n]*'|[^\s"'|;&]+/y;
|
|
80
|
+
// The value after the phrase must hold a non-letter, as in rule 4, so ordinary prose ("the password is
|
|
81
|
+
// required") survives; a plain-letter value after a bare `password ` is a known gap, and so is a
|
|
82
|
+
// password of one to three digits (`reset password 2 times` is prose).
|
|
83
|
+
const PASSWORD_PHRASE = /(?<![A-Za-z0-9_.-])((?:password|passwd)[ \t]+)(?!<masked>|[=:])([^\s,;&"'`)\]}]+)/gi;
|
|
84
|
+
// Another user's home directory (the running user's is rule 0). `Shared` is not a person and `runner`
|
|
85
|
+
// is the CI runner account (`/Users/runner/work/...`), so neither is masked.
|
|
86
|
+
const OTHER_USER_HOME = /(?<![A-Za-z0-9_.-])\/Users\/(?!(?:Shared|runner)(?![^/\s"'`<>:;,()[\]{}])|<)[^/\s"'`<>:;,()[\]{}]+/g;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Rules 14 and 15. Scans left to right and masks the value of every `-u`/`--user` of a curl command
|
|
90
|
+
* and every `-p<value>` of a mysql command. A command word (`curl`, `mysql*`) is the command of its
|
|
91
|
+
* segment only at command position: the first word of the text, of a line or of a segment after a
|
|
92
|
+
* break (`|`, `;`, `&`, a parenthesis, a backtick), of a quoted span, or after a wrapper word
|
|
93
|
+
* (`sudo`, `env`, `ssh`, `docker`, `sh`, `bash`, ...) at the start of the segment. A word that holds `:`
|
|
94
|
+
* (a URL) or is followed by more of its token (`mysql-8`) is not the command. A segment has one
|
|
95
|
+
* command. Quoted text is scanned: its start is a command position, and a break inside it ends only
|
|
96
|
+
* the command inside it, so `curl -H "a|b" -u x:y` keeps its command. One pass; every step consumes
|
|
97
|
+
* at least one character and a closing quote is searched on its own line, so the cost is linear.
|
|
98
|
+
*/
|
|
99
|
+
function maskCommandFlags(text ) {
|
|
100
|
+
let out = '';
|
|
101
|
+
let copied = 0;
|
|
102
|
+
let command = null;
|
|
103
|
+
let atStart = true; // the next word is the first of its segment
|
|
104
|
+
let wrapped = false; // the segment began with a wrapper word
|
|
105
|
+
const spans = [];
|
|
106
|
+
COMMAND_SCAN.lastIndex = 0;
|
|
107
|
+
let found ;
|
|
108
|
+
while ((found = COMMAND_SCAN.exec(text)) !== null) {
|
|
109
|
+
const groups = found.groups || {};
|
|
110
|
+
const end = found.index + found[0].length;
|
|
111
|
+
if (groups.quote) {
|
|
112
|
+
const top = spans[spans.length - 1];
|
|
113
|
+
if (top && top.quote === groups.quote) {
|
|
114
|
+
spans.pop();
|
|
115
|
+
({ command, atStart, wrapped } = top);
|
|
116
|
+
atStart = false;
|
|
117
|
+
} else {
|
|
118
|
+
const close = QUOTE_CLOSE[groups.quote];
|
|
119
|
+
close.lastIndex = end;
|
|
120
|
+
if (close.test(text)) {
|
|
121
|
+
spans.push({ quote: groups.quote, command, atStart, wrapped });
|
|
122
|
+
command = null; atStart = true; wrapped = false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else if (groups.brk) {
|
|
126
|
+
if (!(groups.brk === '&' && /[<>]/.test(text[found.index - 1] || ''))) { command = null; atStart = true; wrapped = false; }
|
|
127
|
+
} else if (groups.word) {
|
|
128
|
+
const word = groups.word;
|
|
129
|
+
if (atStart && PROMPT_OR_ASSIGNMENT.test(word)) continue;
|
|
130
|
+
const named = COMMAND_WORD.exec(word);
|
|
131
|
+
if (command === null && (atStart || wrapped) && named) command = named[1] ? 'curl' : 'mysql';
|
|
132
|
+
else if (atStart) wrapped = WRAPPER_WORDS.has(word.slice(word.lastIndexOf('/') + 1));
|
|
133
|
+
atStart = false;
|
|
134
|
+
} else if (((groups.long || groups.short) && command === 'curl') || (groups.pass && command === 'mysql')) {
|
|
135
|
+
atStart = false;
|
|
136
|
+
COMMAND_FLAG_VALUE.lastIndex = end;
|
|
137
|
+
const value = COMMAND_FLAG_VALUE.exec(text);
|
|
138
|
+
if (value && !isMasked(value[0]) && !(command === 'mysql' && value[0].startsWith(MASKED))) {
|
|
139
|
+
out += text.slice(copied, end) + MASKED;
|
|
140
|
+
copied = end + value[0].length;
|
|
141
|
+
COMMAND_SCAN.lastIndex = copied;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
atStart = false; // another command's flag: rescan what follows it
|
|
145
|
+
COMMAND_SCAN.lastIndex = end;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out + text.slice(copied);
|
|
149
|
+
}
|
|
150
|
+
|
|
55
151
|
function escapeRegExp(text ) { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
56
152
|
|
|
57
153
|
/** The home directory to abbreviate: an absolute path other than the file system root. */
|
|
58
154
|
function homeOf(options ) {
|
|
59
155
|
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(
|
|
156
|
+
if (typeof raw !== 'string' || !(raw.startsWith('/') || /^[A-Za-z]:[\\/]/.test(raw))) return null;
|
|
157
|
+
const home = raw.replace(/[\\/]+$/, '');
|
|
62
158
|
return home.length > 1 ? home : null;
|
|
63
159
|
}
|
|
64
160
|
|
|
@@ -89,6 +185,30 @@ function mask40(run , before ) {
|
|
|
89
185
|
looksLikeSecretRun(value, 1) && !isPathRun(value, index === 0 ? before : run[index - 1]) ? MASKED : value));
|
|
90
186
|
}
|
|
91
187
|
|
|
188
|
+
const TS_TYPE_NAMES = new Set([
|
|
189
|
+
'string', 'number', 'boolean', 'any', 'unknown', 'void',
|
|
190
|
+
'never', 'null', 'undefined', 'symbol', 'bigint',
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
function isCodePreserved(head , value , offset , fullString ) {
|
|
194
|
+
if (value.startsWith('"') || value.startsWith("'")) return false;
|
|
195
|
+
const isColon = /:[ \t]*$/.test(head);
|
|
196
|
+
if (isColon) {
|
|
197
|
+
const baseType = value.replace(/\[\s*\]?$|\[$/, '');
|
|
198
|
+
if (TS_TYPE_NAMES.has(baseType)) return true;
|
|
199
|
+
if (/^[A-Z]|^\[/.test(value)) return true;
|
|
200
|
+
if (/^(?:new\s|[A-Za-z0-9_$]+\s*\(|[A-Za-z0-9_$]+\.[A-Za-z0-9_$]+)/.test(value)) return true;
|
|
201
|
+
}
|
|
202
|
+
const isEquals = /=[ \t]*$/.test(head);
|
|
203
|
+
if (isEquals) {
|
|
204
|
+
const before = fullString.slice(Math.max(0, offset - 30), offset);
|
|
205
|
+
if (/(?:const|let|var|readonly)\s+$/.test(before)) return true;
|
|
206
|
+
if (/\bnew\s|[A-Za-z0-9_$]+\.[A-Za-z0-9_$]+|\(/.test(value)) return true;
|
|
207
|
+
if (/^[\[{]/.test(value)) return true;
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
92
212
|
function isMasked(value ) { return value.replace(/^["']|["']$/g, '') === MASKED; }
|
|
93
213
|
|
|
94
214
|
function count(text , token ) { return text.split(token).length - 1; }
|
|
@@ -119,8 +239,8 @@ function mask(text , profile , options = {})
|
|
|
119
239
|
// 3. Bearer tokens and URL credentials.
|
|
120
240
|
out = out.replace(BEARER, `$1$2${MASKED}`).replace(URL_CREDENTIALS, `://${MASKED}@`);
|
|
121
241
|
// 4. Key=value, unquoted, when the value is not plain letters.
|
|
122
|
-
out = out.replace(KEY_VALUE_LINE, (match , key , separator , value ) => (
|
|
123
|
-
isMasked(value) || value.startsWith(MASKED) || !/[^A-Za-z]/.test(value) ? match : `${key}${separator}${MASKED}`));
|
|
242
|
+
out = out.replace(KEY_VALUE_LINE, (match , key , separator , value , offset , fullString ) => (
|
|
243
|
+
isMasked(value) || value.startsWith(MASKED) || !/[^A-Za-z]/.test(value) || isCodePreserved(key + separator, value, offset, fullString) ? match : `${key}${separator}${MASKED}`));
|
|
124
244
|
// 5. Long secret-looking runs that are not paths.
|
|
125
245
|
out = out.replace(LONG_RUN, (run , offset ) => (
|
|
126
246
|
looksLikeSecretRun(run, 2) && !isPathRun(run, out[offset - 1]) ? MASKED : mask40(run, out[offset - 1])));
|
|
@@ -129,8 +249,9 @@ function mask(text , profile , options = {})
|
|
|
129
249
|
|
|
130
250
|
if (profile === 'body') {
|
|
131
251
|
// 7. Key and value, quoted or not, whatever the value looks like.
|
|
132
|
-
out = out.replace(KEY_VALUE_BODY, (match , head , value ) => {
|
|
252
|
+
out = out.replace(KEY_VALUE_BODY, (match , head , value , offset , fullString ) => {
|
|
133
253
|
if (isMasked(value) || value.startsWith(MASKED)) return match;
|
|
254
|
+
if (isCodePreserved(head, value, offset, fullString)) return match;
|
|
134
255
|
const quote = value[0] === '"' || value[0] === "'" ? value[0] : '';
|
|
135
256
|
return `${head}${quote}${MASKED}${quote}`;
|
|
136
257
|
});
|
|
@@ -138,6 +259,12 @@ function mask(text , profile , options = {})
|
|
|
138
259
|
out = out.replace(AUTH_HEADER, (match , name , rest ) => (rest === '' || rest === MASKED ? match : `${name} ${MASKED}`));
|
|
139
260
|
// 9. Secret-bearing command-line flags.
|
|
140
261
|
out = out.replace(SECRET_FLAG, (match , flag , value ) => (isMasked(value) ? match : `${flag}${MASKED}`));
|
|
262
|
+
// 14 and 15. `curl -u user:pw`, `curl --user user:pw`, `curl -uuser:pw` and `mysql -psecret`, every flag of a command.
|
|
263
|
+
out = maskCommandFlags(out);
|
|
264
|
+
// 16. `password hunter2`: the key word, a space and a value with a non-letter in it.
|
|
265
|
+
out = out.replace(PASSWORD_PHRASE, (match , head , value ) => (/[^A-Za-z]/.test(value) && !/^[0-9]{1,3}$/.test(value) ? `${head}${MASKED}` : match));
|
|
266
|
+
// 17. Another user's home directory: `/Users/<name>` becomes `/Users/<masked>`.
|
|
267
|
+
out = out.replace(OTHER_USER_HOME, `/Users/${MASKED}`);
|
|
141
268
|
}
|
|
142
269
|
|
|
143
270
|
const masked = Math.max(0, [MASKED, EMAIL, IP].reduce((sum, token) => sum + count(out, token) - count(input, token), 0));
|
|
@@ -164,11 +164,33 @@ function knownServerIds(registry ) {
|
|
|
164
164
|
return ids;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
const MANIFEST_PATTERNS = Object.freeze([
|
|
168
|
+
/(?:^|[\\/])package(?:-lock)?\.json$/,
|
|
169
|
+
/(?:^|[\\/])pnpm-lock\.yaml$/,
|
|
170
|
+
/(?:^|[\\/])yarn\.lock$/,
|
|
171
|
+
/(?:^|[\\/])Cargo\.(?:toml|lock)$/,
|
|
172
|
+
/(?:^|[\\/])go\.(?:mod|sum)$/,
|
|
173
|
+
/(?:^|[\\/])requirements.*\.txt$/,
|
|
174
|
+
/(?:^|[\\/])pyproject\.toml$/,
|
|
175
|
+
/(?:^|[\\/])poetry\.lock$/,
|
|
176
|
+
/(?:^|[\\/])Gemfile(?:\.lock)?$/,
|
|
177
|
+
/(?:^|[\\/])composer\.(?:json|lock)$/,
|
|
178
|
+
/(?:^|[\\/])build\.gradle(?:\.kts)?$/,
|
|
179
|
+
/(?:^|[\\/])pom\.xml$/,
|
|
180
|
+
/(?:^|[\\/])Package\.swift$/,
|
|
181
|
+
/(?:^|[\\/])Podfile(?:\.lock)?$/,
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
function touchesManifest(files ) {
|
|
185
|
+
if (!Array.isArray(files) || files.length === 0) return false;
|
|
186
|
+
return files.some((f) => MANIFEST_PATTERNS.some((pattern) => pattern.test(f)));
|
|
187
|
+
}
|
|
188
|
+
|
|
167
189
|
/**
|
|
168
190
|
* Servers a stage run names (IC-001 meaning). Sorted.
|
|
169
|
-
* @param {{registry: object, skill: string, stageId?: string, taskId?: string, requests?: {list: Function}}} input
|
|
191
|
+
* @param {{registry: object, skill: string, stageId?: string, taskId?: string, requests?: {list: Function}, taskClass?: string, diffFiles?: string[]}} input
|
|
170
192
|
*/
|
|
171
|
-
function namedServers({ registry, skill, stageId, taskId, requests } ) {
|
|
193
|
+
function namedServers({ registry, skill, stageId, taskId, requests, taskClass, diffFiles } ) {
|
|
172
194
|
let researchRequested ;
|
|
173
195
|
const hasRequest = () => {
|
|
174
196
|
if (researchRequested === undefined) {
|
|
@@ -178,12 +200,21 @@ function namedServers({ registry, skill, stageId, taskId, requests } )
|
|
|
178
200
|
}
|
|
179
201
|
return researchRequested;
|
|
180
202
|
};
|
|
203
|
+
|
|
204
|
+
const isDependencyChange = taskClass === 'dependency-change';
|
|
205
|
+
const hasManifestDiff = touchesManifest(diffFiles);
|
|
206
|
+
|
|
181
207
|
const named = new Set ();
|
|
182
208
|
for (const server of registry?.mcp || []) {
|
|
183
209
|
for (const step of server.steps || []) {
|
|
184
|
-
if (step.
|
|
185
|
-
|
|
186
|
-
|
|
210
|
+
if (step.skill !== skill) continue;
|
|
211
|
+
if (!step.expectedWhen) {
|
|
212
|
+
named.add(server.id);
|
|
213
|
+
} else if (step.expectedWhen === 'research-request' && hasRequest()) {
|
|
214
|
+
named.add(server.id);
|
|
215
|
+
} else if (step.expectedWhen === 'dependency-change' && isDependencyChange) {
|
|
216
|
+
named.add(server.id);
|
|
217
|
+
} else if (step.expectedWhen === 'manifest-review' && hasManifestDiff) {
|
|
187
218
|
named.add(server.id);
|
|
188
219
|
}
|
|
189
220
|
}
|
|
@@ -213,13 +244,14 @@ const bySortKey = (a , b ) => (a < b ? -1 : a > b ? 1 : 0);
|
|
|
213
244
|
function resolveConsultations({
|
|
214
245
|
taskId = null, stageId = null, skill, flags, registry, ledger, requests,
|
|
215
246
|
briefRecords = [], readReport, reach, addCommand, harnessName, since = null,
|
|
247
|
+
taskClass = null, diffFiles = [],
|
|
216
248
|
} = {}) {
|
|
217
249
|
const warnings = [];
|
|
218
250
|
const notices = [];
|
|
219
251
|
const evidenceToAdd = [];
|
|
220
252
|
const registered = new Set((registry?.mcp || []).map((server ) => server.id));
|
|
221
253
|
const known = knownServerIds(registry);
|
|
222
|
-
const named = namedServers({ registry, skill, stageId, taskId, requests });
|
|
254
|
+
const named = namedServers({ registry, skill, stageId, taskId, requests, taskClass, diffFiles });
|
|
223
255
|
|
|
224
256
|
// Rule 1: flags.
|
|
225
257
|
const parsedFlags = parseMcpOutcomeFlags(flags);
|
|
@@ -408,5 +440,7 @@ module.exports = {
|
|
|
408
440
|
parseMcpOutcomeFlags,
|
|
409
441
|
parseToolsUsed,
|
|
410
442
|
namedServers,
|
|
443
|
+
touchesManifest,
|
|
444
|
+
MANIFEST_PATTERNS,
|
|
411
445
|
resolveConsultations,
|
|
412
446
|
};
|
|
@@ -39,22 +39,58 @@ function availableProviderIds({ fsImpl = fs } = {})
|
|
|
39
39
|
.map(([id]) => id);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function normalizeFamilyList(value ) {
|
|
43
|
+
const values = Array.isArray(value) ? value : [value];
|
|
44
|
+
return [...new Set(values.flatMap((entry) => String(entry ?? '').split(',').map((family) => family.trim().toLowerCase()).filter(Boolean)))];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// families is a hand-kept stopgap; the model-facts registry (roadmap row 105) supersedes it and this field is removed then.
|
|
48
|
+
/** An unknown family is not evidence that a provider can review outside the author's family. */
|
|
49
|
+
function familyInfo(families , excludedFamilies ) {
|
|
50
|
+
const declared = Array.isArray(families) ? families : [];
|
|
51
|
+
const hasUnknown = declared.length === 0
|
|
52
|
+
|| declared.some((family) => typeof family !== 'string' || !/^[a-z][a-z0-9-]*$/.test(family) || family === 'unknown');
|
|
53
|
+
const overlapsAuthor = declared.some((family) => typeof family === 'string'
|
|
54
|
+
&& family !== 'unknown' && /^[a-z][a-z0-9-]*$/.test(family) && excludedFamilies.has(family));
|
|
55
|
+
const familyRelation = overlapsAuthor ? 'overlaps-author' : hasUnknown ? 'unverified' : 'cross-family';
|
|
56
|
+
const sameFamily = declared.length > 0 && declared.every((family) => typeof family === 'string' && excludedFamilies.has(family));
|
|
57
|
+
return {
|
|
58
|
+
families: [...declared],
|
|
59
|
+
familyVerified: familyRelation === 'cross-family',
|
|
60
|
+
familyRelation,
|
|
61
|
+
sameFamily,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
/**
|
|
43
66
|
* Resolve one role to ranked candidates.
|
|
44
67
|
* Ordering: available first, then preferred-tier match, then fallback-tier match, registry order
|
|
45
|
-
* breaking ties. `exclude` drops provider ids entirely
|
|
46
|
-
*
|
|
68
|
+
* breaking ties. `exclude` drops provider ids entirely; `excludeFamilies` drops providers whose
|
|
69
|
+
* complete, known catalogue is within the excluded model families. Unknown catalogues are kept
|
|
70
|
+
* unverified rather than being presented as cross-family.
|
|
47
71
|
* Availability unknown stays null — absence of evidence is not reported as absent.
|
|
48
72
|
*/
|
|
49
|
-
function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] } = {}) {
|
|
73
|
+
function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [], excludeFamilies } = {}) {
|
|
50
74
|
const role = (registry.modelRoles || []).find((entry ) => entry.id === roleId);
|
|
51
75
|
if (!role) {
|
|
52
76
|
throw new Error(`Unknown model role '${roleId}'; valid: ${(registry.modelRoles || []).map((r ) => r.id).join(', ')}`);
|
|
53
77
|
}
|
|
54
78
|
const excluded = new Set(exclude);
|
|
79
|
+
const familyMode = excludeFamilies !== undefined && excludeFamilies !== null;
|
|
80
|
+
const normalizedFamilies = familyMode ? normalizeFamilyList(excludeFamilies) : [];
|
|
81
|
+
if (familyMode && normalizedFamilies.length === 0) {
|
|
82
|
+
throw new Error('--exclude-family requires at least one family name');
|
|
83
|
+
}
|
|
84
|
+
const excludedFamilies = new Set(normalizedFamilies);
|
|
55
85
|
const candidates = [];
|
|
86
|
+
const familyExcluded = [];
|
|
56
87
|
for (const [index, provider] of (registry.modelProviders || []).entries()) {
|
|
57
88
|
if (excluded.has(provider.id)) continue;
|
|
89
|
+
const family = familyInfo(provider.families, excludedFamilies);
|
|
90
|
+
if (familyMode && family.sameFamily) {
|
|
91
|
+
familyExcluded.push(provider.id);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
58
94
|
const tiers = provider.tiers ?? [];
|
|
59
95
|
candidates.push({
|
|
60
96
|
id: provider.id,
|
|
@@ -64,6 +100,7 @@ function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] }
|
|
|
64
100
|
available: isAvailable ? Boolean(isAvailable(provider.id)) : null,
|
|
65
101
|
servesPreferredTier: role.prefer ? tiers.includes(role.prefer) : null,
|
|
66
102
|
servesFallbackTier: role.fallback ? tiers.includes(role.fallback) : null,
|
|
103
|
+
...(familyMode ? { families: family.families, familyRelation: family.familyRelation, familyVerified: family.familyVerified } : {}),
|
|
67
104
|
_index: index,
|
|
68
105
|
});
|
|
69
106
|
}
|
|
@@ -79,12 +116,19 @@ function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] }
|
|
|
79
116
|
return 0;
|
|
80
117
|
});
|
|
81
118
|
for (const candidate of candidates) delete candidate._index;
|
|
82
|
-
|
|
119
|
+
const result = {
|
|
83
120
|
roleId,
|
|
84
121
|
policy: { prefer: role.prefer ?? null, fallback: role.fallback ?? null, require: role.require ?? null },
|
|
85
122
|
candidates,
|
|
86
123
|
excluded: [...excluded],
|
|
87
124
|
};
|
|
125
|
+
// Keep the established result shape byte-for-byte for callers that do not request family
|
|
126
|
+
// filtering. Family metadata is included whenever that comparison is requested.
|
|
127
|
+
if (familyMode) {
|
|
128
|
+
result.excludedFamilies = [...excludedFamilies];
|
|
129
|
+
result.familyExcluded = familyExcluded;
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
88
132
|
}
|
|
89
133
|
|
|
90
134
|
/**
|
|
@@ -118,7 +162,7 @@ function resolveRetrievalSlot({ registry, slotId, isAvailable = null } = {}
|
|
|
118
162
|
|
|
119
163
|
/** CLI handler for `doflow model-role`. Read-only advisory routing; exits 1 on an unknown role
|
|
120
164
|
* because a silently-empty candidate list would read as "no providers" rather than "bad input". */
|
|
121
|
-
function handleModelRoleCommand({ role, exclude, json = false, repoRoot } = {}) {
|
|
165
|
+
function handleModelRoleCommand({ role, exclude, excludeFamilies, json = false, repoRoot } = {}) {
|
|
122
166
|
if (!role) return usageError('model-role', '--role is required (one of the roles in core/registry/models.json)', json);
|
|
123
167
|
const registry = (() => {
|
|
124
168
|
try { return loadRegistry({ repoRoot: repoRoot || REPO_ROOT }); }
|
|
@@ -127,20 +171,48 @@ function handleModelRoleCommand({ role, exclude, json = false, repoRoot } =
|
|
|
127
171
|
if (!registry) return undefined;
|
|
128
172
|
const available = new Set(availableProviderIds());
|
|
129
173
|
try {
|
|
174
|
+
const excludedProviderIds = Array.isArray(exclude) ? exclude : String(exclude ?? '').split(',').map((s ) => s.trim()).filter(Boolean);
|
|
175
|
+
const requestedFamilies = excludeFamilies === undefined || excludeFamilies === null ? null : normalizeFamilyList(excludeFamilies);
|
|
176
|
+
if (requestedFamilies !== null) {
|
|
177
|
+
const knownFamilies = [...new Set((registry.modelProviders || []).flatMap((provider ) => provider.families || []))]
|
|
178
|
+
.filter((family) => typeof family === 'string' && family !== 'unknown').sort();
|
|
179
|
+
const knownSet = new Set(knownFamilies);
|
|
180
|
+
const unknownFamilies = requestedFamilies.filter((family) => !knownSet.has(family));
|
|
181
|
+
if (requestedFamilies.length === 0) {
|
|
182
|
+
return usageError('model-role', `--exclude-family requires at least one family name; known families: ${knownFamilies.join(', ') || '(none)'}`, json);
|
|
183
|
+
}
|
|
184
|
+
if (requestedFamilies.includes('unknown')) {
|
|
185
|
+
return usageError('model-role', `unknown cannot be excluded because it names missing knowledge; known families: ${knownFamilies.join(', ') || '(none)'}`, json);
|
|
186
|
+
}
|
|
187
|
+
if (unknownFamilies.length) {
|
|
188
|
+
return usageError('model-role', `unknown family name${unknownFamilies.length === 1 ? '' : 's'}: ${unknownFamilies.join(', ')}; known families: ${knownFamilies.join(', ') || '(none)'}`, json);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
130
191
|
const resolution = resolveModelRole({
|
|
131
192
|
registry,
|
|
132
193
|
roleId: role,
|
|
133
194
|
isAvailable: (providerId ) => available.has(providerId),
|
|
134
|
-
exclude:
|
|
195
|
+
exclude: excludedProviderIds,
|
|
196
|
+
...(requestedFamilies === null ? {} : { excludeFamilies: requestedFamilies }),
|
|
135
197
|
});
|
|
136
198
|
if (json) console.log(JSON.stringify(resolution, null, 2));
|
|
137
199
|
else {
|
|
138
200
|
console.log(`Role '${resolution.roleId}' — prefer:${resolution.policy.prefer ?? '—'} fallback:${resolution.policy.fallback ?? '—'} require:${resolution.policy.require ?? '—'}`);
|
|
139
201
|
for (const candidate of resolution.candidates) {
|
|
140
202
|
const availability = candidate.available === null ? '?' : candidate.available ? 'yes' : 'no';
|
|
141
|
-
|
|
203
|
+
const familyStatus = requestedFamilies === null ? ''
|
|
204
|
+
: ` ${candidate.familyRelation}`;
|
|
205
|
+
console.log(` ${candidate.available ? '*' : ' '} ${candidate.id} (${candidate.kind}, via ${candidate.backendCli ?? 'hosted'}) available=${availability}${resolution.policy.prefer && candidate.servesPreferredTier ? ` serves ${resolution.policy.prefer}` : ''}${familyStatus}`);
|
|
142
206
|
}
|
|
143
207
|
if (resolution.excluded.length) console.log(`Excluded: ${resolution.excluded.join(', ')}`);
|
|
208
|
+
if (requestedFamilies !== null) {
|
|
209
|
+
const familySet = new Set(requestedFamilies);
|
|
210
|
+
const providerExcluded = new Set(excludedProviderIds);
|
|
211
|
+
const sameFamily = (registry.modelProviders || []).filter((provider ) => !providerExcluded.has(provider.id)
|
|
212
|
+
&& familyInfo(provider.families, familySet).sameFamily);
|
|
213
|
+
for (const provider of sameFamily) console.log(` ${provider.id} same-family (excluded)`);
|
|
214
|
+
if (resolution.excludedFamilies.length) console.log(`Excluded families: ${resolution.excludedFamilies.join(', ')}`);
|
|
215
|
+
}
|
|
144
216
|
}
|
|
145
217
|
return finishRuntime(0);
|
|
146
218
|
} catch (error ) {
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const { PROBES, HARNESS_IDS } = require('../worker/probes/index.js');
|
|
8
|
+
const { configHome } = require('../config-home.js');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
function getProjectCataloguePath(projectRoot ) {
|
|
48
|
+
return path.join(projectRoot, '.doflow', 'state', 'models', 'catalogue.json');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getUserCataloguePath(homeDir , env = process.env) {
|
|
52
|
+
if (homeDir) {
|
|
53
|
+
return path.join(homeDir, '.config', 'doflow', 'state', 'models-observed.json');
|
|
54
|
+
}
|
|
55
|
+
const { dir } = configHome(env);
|
|
56
|
+
const baseDir = dir || path.join(os.homedir(), '.config', 'doflow');
|
|
57
|
+
return path.join(baseDir, 'state', 'models-observed.json');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveProjectRoot(projectRoot , fsImpl = fs) {
|
|
61
|
+
if (projectRoot) return path.resolve(projectRoot);
|
|
62
|
+
try {
|
|
63
|
+
const { projectRoot: findRoot } = require('../lifecycle/root.js');
|
|
64
|
+
const root = findRoot(process.cwd());
|
|
65
|
+
if (root && fsImpl.existsSync(path.join(root, '.doflow'))) return root;
|
|
66
|
+
} catch {
|
|
67
|
+
// fallback
|
|
68
|
+
}
|
|
69
|
+
if (fsImpl.existsSync(path.join(process.cwd(), '.doflow'))) {
|
|
70
|
+
return process.cwd();
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readCatalogue(
|
|
76
|
+
projectRoot ,
|
|
77
|
+
options = {}
|
|
78
|
+
) {
|
|
79
|
+
const fsImpl = options.fsImpl || fs;
|
|
80
|
+
const root = projectRoot ? path.resolve(projectRoot) : resolveProjectRoot(undefined, fsImpl);
|
|
81
|
+
|
|
82
|
+
if (root) {
|
|
83
|
+
const projPath = getProjectCataloguePath(root);
|
|
84
|
+
if (fsImpl.existsSync(projPath)) {
|
|
85
|
+
try {
|
|
86
|
+
const raw = fsImpl.readFileSync(projPath, 'utf8');
|
|
87
|
+
return JSON.parse(raw);
|
|
88
|
+
} catch (err ) {
|
|
89
|
+
if (err instanceof SyntaxError) {
|
|
90
|
+
const corrupt = `${projPath}.corrupt-${Date.now()}`;
|
|
91
|
+
try {
|
|
92
|
+
fsImpl.renameSync(projPath, corrupt);
|
|
93
|
+
} catch {
|
|
94
|
+
// best-effort
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Fallback to user cache
|
|
104
|
+
const userPath = getUserCataloguePath(options.homeDir, options.env);
|
|
105
|
+
if (fsImpl.existsSync(userPath)) {
|
|
106
|
+
try {
|
|
107
|
+
const raw = fsImpl.readFileSync(userPath, 'utf8');
|
|
108
|
+
return JSON.parse(raw);
|
|
109
|
+
} catch (err ) {
|
|
110
|
+
if (err instanceof SyntaxError) {
|
|
111
|
+
const corrupt = `${userPath}.corrupt-${Date.now()}`;
|
|
112
|
+
try {
|
|
113
|
+
fsImpl.renameSync(userPath, corrupt);
|
|
114
|
+
} catch {
|
|
115
|
+
// best-effort
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function writeCatalogue(
|
|
127
|
+
catalogue ,
|
|
128
|
+
projectRoot ,
|
|
129
|
+
options = {}
|
|
130
|
+
) {
|
|
131
|
+
const fsImpl = options.fsImpl || fs;
|
|
132
|
+
const root = projectRoot ? path.resolve(projectRoot) : resolveProjectRoot(undefined, fsImpl);
|
|
133
|
+
const targetPath = root ? getProjectCataloguePath(root) : getUserCataloguePath(options.homeDir, options.env);
|
|
134
|
+
|
|
135
|
+
const targetDir = path.dirname(targetPath);
|
|
136
|
+
fsImpl.mkdirSync(targetDir, { recursive: true });
|
|
137
|
+
|
|
138
|
+
const tmpPath = `${targetPath}.${process.pid}.${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`;
|
|
139
|
+
fsImpl.writeFileSync(tmpPath, JSON.stringify(catalogue, null, 2), 'utf8');
|
|
140
|
+
fsImpl.renameSync(tmpPath, targetPath);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function syncHarness(
|
|
144
|
+
harnessId ,
|
|
145
|
+
options = {}
|
|
146
|
+
) {
|
|
147
|
+
const probe = PROBES[harnessId];
|
|
148
|
+
if (!probe) {
|
|
149
|
+
return {
|
|
150
|
+
harnessId,
|
|
151
|
+
binary: harnessId,
|
|
152
|
+
installed: false,
|
|
153
|
+
version: null,
|
|
154
|
+
loginState: 'unsupported',
|
|
155
|
+
reloginHint: null,
|
|
156
|
+
detectedMode: 'unverified',
|
|
157
|
+
derivedBilling: 'unknown',
|
|
158
|
+
offeredModels: [],
|
|
159
|
+
lastSyncedAt: new Date().toISOString(),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return await probe(options);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function syncAllHarnesses(
|
|
167
|
+
options = {}
|
|
168
|
+
) {
|
|
169
|
+
const results = {};
|
|
170
|
+
for (const id of HARNESS_IDS) {
|
|
171
|
+
results[id] = await syncHarness(id, options);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const catalogue = {
|
|
175
|
+
version: 1,
|
|
176
|
+
syncedAt: new Date().toISOString(),
|
|
177
|
+
harnesses: results,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
writeCatalogue(catalogue, options.projectRoot, options);
|
|
181
|
+
return catalogue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
getProjectCataloguePath,
|
|
186
|
+
getUserCataloguePath,
|
|
187
|
+
readCatalogue,
|
|
188
|
+
writeCatalogue,
|
|
189
|
+
syncHarness,
|
|
190
|
+
syncAllHarnesses,
|
|
191
|
+
};
|