session-orchestrator 3.16.0
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/.claude-plugin/marketplace.json +29 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.codex-plugin/agents/explorer.toml +14 -0
- package/.codex-plugin/agents/session-reviewer.toml +23 -0
- package/.codex-plugin/agents/wave-worker.toml +15 -0
- package/.codex-plugin/config.toml +20 -0
- package/.codex-plugin/plugin.json +37 -0
- package/.cursor/rules/000-session-orchestrator.mdc +73 -0
- package/.cursor/rules/010-session-workflow.mdc +170 -0
- package/.cursor/rules/020-quality-gates.mdc +128 -0
- package/.cursor/rules/030-wave-execution.mdc +216 -0
- package/.cursor/rules/040-discovery.mdc +242 -0
- package/.cursor/rules/050-plan.mdc +235 -0
- package/.cursor/rules/060-evolve.mdc +232 -0
- package/.cursor/rules/070-gitlab-ops.mdc +246 -0
- package/.cursor/rules/080-ecosystem-health.mdc +145 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +1544 -0
- package/LICENSE +21 -0
- package/NOTICE +64 -0
- package/README.md +242 -0
- package/SECURITY.md +90 -0
- package/agents/AGENTS.md +136 -0
- package/agents/analyst.md +99 -0
- package/agents/architect-reviewer.md +93 -0
- package/agents/code-implementer.md +106 -0
- package/agents/db-specialist.md +104 -0
- package/agents/dialectic-deriver.md +139 -0
- package/agents/docs-writer.md +113 -0
- package/agents/eval-judge.md +146 -0
- package/agents/memory-proposal-collector.md +297 -0
- package/agents/qa-strategist.md +102 -0
- package/agents/schemas/analyst.schema.json +46 -0
- package/agents/schemas/architect-reviewer.schema.json +50 -0
- package/agents/schemas/code-implementer.schema.json +61 -0
- package/agents/schemas/db-specialist.schema.json +80 -0
- package/agents/schemas/docs-writer.schema.json +56 -0
- package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
- package/agents/schemas/qa-strategist.schema.json +46 -0
- package/agents/schemas/security-reviewer.schema.json +86 -0
- package/agents/schemas/session-reviewer.schema.json +69 -0
- package/agents/schemas/test-writer.schema.json +69 -0
- package/agents/schemas/ui-developer.schema.json +90 -0
- package/agents/schemas/ux-evaluator.schema.json +51 -0
- package/agents/security-reviewer.md +236 -0
- package/agents/session-reviewer.md +201 -0
- package/agents/skill-applied-judge.md +122 -0
- package/agents/test-writer.md +123 -0
- package/agents/ui-developer.md +109 -0
- package/agents/ux-evaluator.md +161 -0
- package/assets/icon.svg +11 -0
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +47 -0
- package/commands/autopilot-multi.md +74 -0
- package/commands/autopilot.md +80 -0
- package/commands/bootstrap.md +56 -0
- package/commands/brainstorm.md +48 -0
- package/commands/close.md +24 -0
- package/commands/debug.md +36 -0
- package/commands/discovery.md +32 -0
- package/commands/dispatcher.md +59 -0
- package/commands/eval.md +28 -0
- package/commands/evolve.md +10 -0
- package/commands/go.md +41 -0
- package/commands/grill.md +45 -0
- package/commands/harness-audit.md +26 -0
- package/commands/memory-cleanup.md +25 -0
- package/commands/persona-panel.md +121 -0
- package/commands/plan.md +15 -0
- package/commands/portfolio.md +97 -0
- package/commands/reconcile.md +23 -0
- package/commands/repo-audit.md +24 -0
- package/commands/session.md +30 -0
- package/commands/spinout.md +15 -0
- package/commands/sunset-review.md +27 -0
- package/commands/templates-ack.md +96 -0
- package/commands/test.md +97 -0
- package/docs/README.md +105 -0
- package/docs/USER-GUIDE.md +1403 -0
- package/docs/ci-setup.md +81 -0
- package/docs/codex-setup.md +142 -0
- package/docs/components.md +74 -0
- package/docs/cursor-setup.md +104 -0
- package/docs/events-schema.md +81 -0
- package/docs/migration-v3.md +148 -0
- package/docs/owner-config-schema.md +154 -0
- package/docs/persona-panel.md +433 -0
- package/docs/pi-setup.md +115 -0
- package/docs/plugin-architecture-v3.md +296 -0
- package/docs/pm-skills-marketplace.md +114 -0
- package/docs/policy-cache-validation-2026-04-28.md +118 -0
- package/docs/rule-authoring.md +316 -0
- package/docs/session-config-reference.md +1439 -0
- package/docs/session-config-template.md +961 -0
- package/docs/vault-docs-architecture.md +297 -0
- package/hooks/_lib/lock-bootstrap.mjs +272 -0
- package/hooks/_lib/lock-reconcile.mjs +93 -0
- package/hooks/_lib/profile-gate.mjs +95 -0
- package/hooks/_lib/transcript-history.mjs +211 -0
- package/hooks/agent-teams-h3-test.sh +362 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/cwd-change-restore.mjs +131 -0
- package/hooks/enforce-commands.mjs +179 -0
- package/hooks/enforce-scope.mjs +273 -0
- package/hooks/hooks-codex.json +60 -0
- package/hooks/hooks-cursor.json +15 -0
- package/hooks/hooks-pi.json +115 -0
- package/hooks/hooks.json +215 -0
- package/hooks/loop-guard.mjs +260 -0
- package/hooks/on-session-end.mjs +217 -0
- package/hooks/on-session-start.mjs +660 -0
- package/hooks/on-stop.mjs +294 -0
- package/hooks/operator-steer.mjs +64 -0
- package/hooks/post-edit-validate.mjs +225 -0
- package/hooks/post-subagent-discovery-validator.mjs +398 -0
- package/hooks/post-tool-batch-wave-signal.mjs +328 -0
- package/hooks/post-tool-failure-corrective-context.mjs +248 -0
- package/hooks/post-tooluse-frontend-slop.mjs +184 -0
- package/hooks/pre-bash-destructive-guard.mjs +515 -0
- package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
- package/hooks/pre-bash-staging-fence.mjs +223 -0
- package/hooks/pre-bash-templates-first.mjs +404 -0
- package/hooks/run-node.sh +72 -0
- package/hooks/skill-invocation-telemetry.mjs +99 -0
- package/hooks/subagent-telemetry.mjs +249 -0
- package/hooks/wave-scope-commit-guard.mjs +191 -0
- package/monitors/monitors.json +14 -0
- package/output-styles/finding-report.md +48 -0
- package/output-styles/session-report.md +53 -0
- package/output-styles/wave-summary.md +38 -0
- package/package.json +94 -0
- package/pi/extensions/session-orchestrator.ts +25 -0
- package/pi/prompts/autopilot-multi.md +12 -0
- package/pi/prompts/autopilot.md +12 -0
- package/pi/prompts/bootstrap.md +12 -0
- package/pi/prompts/brainstorm.md +12 -0
- package/pi/prompts/close.md +11 -0
- package/pi/prompts/debug.md +12 -0
- package/pi/prompts/discovery.md +12 -0
- package/pi/prompts/dispatcher.md +12 -0
- package/pi/prompts/eval.md +12 -0
- package/pi/prompts/evolve.md +12 -0
- package/pi/prompts/go.md +12 -0
- package/pi/prompts/grill.md +12 -0
- package/pi/prompts/harness-audit.md +12 -0
- package/pi/prompts/memory-cleanup.md +12 -0
- package/pi/prompts/persona-panel.md +12 -0
- package/pi/prompts/plan.md +12 -0
- package/pi/prompts/portfolio.md +12 -0
- package/pi/prompts/reconcile.md +12 -0
- package/pi/prompts/repo-audit.md +12 -0
- package/pi/prompts/session.md +12 -0
- package/pi/prompts/spinout.md +12 -0
- package/pi/prompts/sunset-review.md +12 -0
- package/pi/prompts/templates-ack.md +12 -0
- package/pi/prompts/test.md +12 -0
- package/rules/_index.md +51 -0
- package/rules/always-on/commit-discipline.md +26 -0
- package/rules/always-on/npm-quality-gates.md +26 -0
- package/rules/always-on/parallel-sessions.md +43 -0
- package/rules/opt-in-domain/prompt-caching.md +270 -0
- package/rules/opt-in-stack/backend-data.md +188 -0
- package/rules/opt-in-stack/backend.md +390 -0
- package/rules/opt-in-stack/frontend.md +98 -0
- package/rules/opt-in-stack/security-web.md +194 -0
- package/rules/opt-in-stack/swift.md +65 -0
- package/scripts/archive-closed-prds.mjs +416 -0
- package/scripts/autopilot-multi.mjs +802 -0
- package/scripts/autopilot.mjs +383 -0
- package/scripts/backfill-abandoned-sessions.mjs +265 -0
- package/scripts/backfill-learnings-expires.mjs +196 -0
- package/scripts/backfill-learnings.mjs +203 -0
- package/scripts/backfill-sessions.mjs +282 -0
- package/scripts/check-doc-consistency.sh +279 -0
- package/scripts/check-package-manager.mjs +445 -0
- package/scripts/ci/assert-vitest-green.mjs +267 -0
- package/scripts/codex-install.mjs +435 -0
- package/scripts/compute-grounding-injection.sh +186 -0
- package/scripts/cursor-install.mjs +113 -0
- package/scripts/dialectic-deriver.mjs +573 -0
- package/scripts/emit-event.mjs +160 -0
- package/scripts/emit-session.mjs +212 -0
- package/scripts/eval-session.mjs +262 -0
- package/scripts/export-hw-learnings.mjs +437 -0
- package/scripts/gc-stale-worktrees.mjs +666 -0
- package/scripts/generate-pi-prompts.mjs +127 -0
- package/scripts/harness-audit.mjs +287 -0
- package/scripts/lib/agent-frontmatter.mjs +266 -0
- package/scripts/lib/agent-output-schema.mjs +166 -0
- package/scripts/lib/agent-status.mjs +303 -0
- package/scripts/lib/ajv-loader.mjs +34 -0
- package/scripts/lib/auto-dialectic.mjs +382 -0
- package/scripts/lib/auto-dream.mjs +471 -0
- package/scripts/lib/autonomy/suitability.mjs +212 -0
- package/scripts/lib/autopilot/dep-graph.mjs +417 -0
- package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
- package/scripts/lib/autopilot/flags.mjs +104 -0
- package/scripts/lib/autopilot/kill-switches.mjs +174 -0
- package/scripts/lib/autopilot/loop.mjs +320 -0
- package/scripts/lib/autopilot/mr-draft.mjs +520 -0
- package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
- package/scripts/lib/autopilot/recent-runs.mjs +106 -0
- package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
- package/scripts/lib/autopilot/telemetry.mjs +224 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
- package/scripts/lib/autopilot-telemetry.mjs +11 -0
- package/scripts/lib/autopilot.mjs +39 -0
- package/scripts/lib/backlog-scan.mjs +179 -0
- package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
- package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
- package/scripts/lib/build-live-signals.mjs +150 -0
- package/scripts/lib/ci-status-banner.mjs +425 -0
- package/scripts/lib/claude-md-budget-lint.mjs +246 -0
- package/scripts/lib/cli-flags.mjs +158 -0
- package/scripts/lib/codex/plugin-contract.mjs +610 -0
- package/scripts/lib/cold-start-detector.mjs +240 -0
- package/scripts/lib/command-blocker.mjs +458 -0
- package/scripts/lib/common.mjs +333 -0
- package/scripts/lib/config/auto-dream.mjs +77 -0
- package/scripts/lib/config/block-header.mjs +94 -0
- package/scripts/lib/config/broken-window.mjs +114 -0
- package/scripts/lib/config/coercers.mjs +248 -0
- package/scripts/lib/config/cold-start.mjs +92 -0
- package/scripts/lib/config/config-protection.mjs +120 -0
- package/scripts/lib/config/cross-repo.mjs +104 -0
- package/scripts/lib/config/custom-phases.mjs +213 -0
- package/scripts/lib/config/dialectic.mjs +92 -0
- package/scripts/lib/config/discovery-validator.mjs +75 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
- package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
- package/scripts/lib/config/docs-orchestrator.mjs +90 -0
- package/scripts/lib/config/docs-staleness.mjs +96 -0
- package/scripts/lib/config/drift-check.mjs +155 -0
- package/scripts/lib/config/eval.mjs +130 -0
- package/scripts/lib/config/events-rotation.mjs +74 -0
- package/scripts/lib/config/evolve.mjs +308 -0
- package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
- package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
- package/scripts/lib/config/handover-gate.mjs +106 -0
- package/scripts/lib/config/host-paths.mjs +76 -0
- package/scripts/lib/config/io.mjs +54 -0
- package/scripts/lib/config/loop-guard.mjs +117 -0
- package/scripts/lib/config/memory.mjs +150 -0
- package/scripts/lib/config/persona-gate-wave.mjs +258 -0
- package/scripts/lib/config/reconcile.mjs +205 -0
- package/scripts/lib/config/section-extractor.mjs +100 -0
- package/scripts/lib/config/skill-evolution.mjs +112 -0
- package/scripts/lib/config/slopcheck.mjs +99 -0
- package/scripts/lib/config/state-md-lock.mjs +83 -0
- package/scripts/lib/config/templates-first.mjs +94 -0
- package/scripts/lib/config/test.mjs +113 -0
- package/scripts/lib/config/vault-integration.mjs +201 -0
- package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
- package/scripts/lib/config/vault-staleness.mjs +84 -0
- package/scripts/lib/config/vault-sync.mjs +96 -0
- package/scripts/lib/config/verification-auto-fix.mjs +84 -0
- package/scripts/lib/config/wave-reviewers.mjs +133 -0
- package/scripts/lib/config-schema.mjs +345 -0
- package/scripts/lib/config.mjs +474 -0
- package/scripts/lib/convergence-monitor.mjs +389 -0
- package/scripts/lib/coordinator-snapshot.mjs +371 -0
- package/scripts/lib/crypto-digest-utils.mjs +91 -0
- package/scripts/lib/discovery/helpers.mjs +127 -0
- package/scripts/lib/discovery/triage-state.mjs +279 -0
- package/scripts/lib/dispatcher/cli.mjs +257 -0
- package/scripts/lib/dispatcher/enumerate.mjs +243 -0
- package/scripts/lib/dispatcher/rank.mjs +363 -0
- package/scripts/lib/ecosystem-health.mjs +224 -0
- package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
- package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
- package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
- package/scripts/lib/ecosystem-wizard.mjs +48 -0
- package/scripts/lib/env-check.mjs +89 -0
- package/scripts/lib/eval/engine.mjs +605 -0
- package/scripts/lib/eval/judge.mjs +433 -0
- package/scripts/lib/eval/report.mjs +367 -0
- package/scripts/lib/eval/schema.mjs +618 -0
- package/scripts/lib/eval/session-resolve.mjs +137 -0
- package/scripts/lib/eval/sink.mjs +77 -0
- package/scripts/lib/events-rotation.mjs +86 -0
- package/scripts/lib/events-schema.mjs +81 -0
- package/scripts/lib/events.mjs +80 -0
- package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
- package/scripts/lib/exclusivity-matrix.mjs +68 -0
- package/scripts/lib/fetch-baseline.mjs +311 -0
- package/scripts/lib/file-lock.mjs +512 -0
- package/scripts/lib/frontend-detect/detect.mjs +138 -0
- package/scripts/lib/frontend-detect/rules.mjs +295 -0
- package/scripts/lib/frontmatter-guard.mjs +241 -0
- package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
- package/scripts/lib/gates/gate-baseline.mjs +42 -0
- package/scripts/lib/gates/gate-full.mjs +85 -0
- package/scripts/lib/gates/gate-helpers.mjs +231 -0
- package/scripts/lib/gates/gate-incremental.mjs +76 -0
- package/scripts/lib/gates/gate-per-file.mjs +55 -0
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
- package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
- package/scripts/lib/handover-gate.mjs +222 -0
- package/scripts/lib/hardening.mjs +43 -0
- package/scripts/lib/hardware-pattern-detector.mjs +238 -0
- package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
- package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
- package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
- package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
- package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
- package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
- package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
- package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
- package/scripts/lib/harness-audit/categories.mjs +19 -0
- package/scripts/lib/historical-guard.mjs +15 -0
- package/scripts/lib/host-identity.mjs +262 -0
- package/scripts/lib/instruction-budget-guard.mjs +332 -0
- package/scripts/lib/io.mjs +304 -0
- package/scripts/lib/issue-close-strip-labels.mjs +161 -0
- package/scripts/lib/language-mappers/README.md +57 -0
- package/scripts/lib/language-mappers/index.mjs +165 -0
- package/scripts/lib/language-mappers/markdown.mjs +149 -0
- package/scripts/lib/language-mappers/python.mjs +249 -0
- package/scripts/lib/language-mappers/swift.mjs +201 -0
- package/scripts/lib/language-mappers/typescript.mjs +433 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
- package/scripts/lib/learnings/filters.mjs +43 -0
- package/scripts/lib/learnings/io.mjs +255 -0
- package/scripts/lib/learnings/schema.mjs +518 -0
- package/scripts/lib/learnings/surface.mjs +207 -0
- package/scripts/lib/learnings.mjs +42 -0
- package/scripts/lib/lock-reaper.mjs +648 -0
- package/scripts/lib/locks/index.mjs +31 -0
- package/scripts/lib/locks/lock-body.mjs +62 -0
- package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
- package/scripts/lib/locks/state-md-lock.mjs +351 -0
- package/scripts/lib/loop-readiness-banner.mjs +144 -0
- package/scripts/lib/memory-banner.mjs +478 -0
- package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
- package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
- package/scripts/lib/memory-paths.mjs +31 -0
- package/scripts/lib/memory-proposals/collector.mjs +334 -0
- package/scripts/lib/memory-proposals/schema.mjs +289 -0
- package/scripts/lib/memory-proposals/sink.mjs +507 -0
- package/scripts/lib/memory-proposals/store.mjs +441 -0
- package/scripts/lib/mission-status-schema.mjs +114 -0
- package/scripts/lib/mode-selector/alternatives.mjs +64 -0
- package/scripts/lib/mode-selector/constants.mjs +29 -0
- package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
- package/scripts/lib/mode-selector/rationale.mjs +55 -0
- package/scripts/lib/mode-selector/scoring.mjs +221 -0
- package/scripts/lib/mode-selector-accuracy.mjs +121 -0
- package/scripts/lib/mode-selector.mjs +160 -0
- package/scripts/lib/multi-provider-build/providers.mjs +64 -0
- package/scripts/lib/multi-provider-build/templating.mjs +130 -0
- package/scripts/lib/named-baseline-resolver.mjs +233 -0
- package/scripts/lib/named-vault-resolver.mjs +433 -0
- package/scripts/lib/owner-config/coerce.mjs +29 -0
- package/scripts/lib/owner-config/constants.mjs +21 -0
- package/scripts/lib/owner-config/defaults.mjs +50 -0
- package/scripts/lib/owner-config/error.mjs +19 -0
- package/scripts/lib/owner-config/index.mjs +13 -0
- package/scripts/lib/owner-config/merge.mjs +52 -0
- package/scripts/lib/owner-config/validate.mjs +259 -0
- package/scripts/lib/owner-config-banner.mjs +126 -0
- package/scripts/lib/owner-config-loader.mjs +159 -0
- package/scripts/lib/owner-config.example.yaml +72 -0
- package/scripts/lib/owner-config.mjs +28 -0
- package/scripts/lib/owner-interview.mjs +243 -0
- package/scripts/lib/owner-yaml.mjs +571 -0
- package/scripts/lib/package-manager.mjs +160 -0
- package/scripts/lib/path-utils.mjs +217 -0
- package/scripts/lib/peer-cards/merger.mjs +310 -0
- package/scripts/lib/peer-cards/reader.mjs +125 -0
- package/scripts/lib/peer-cards/schema.mjs +230 -0
- package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
- package/scripts/lib/peer-cards/writer.mjs +138 -0
- package/scripts/lib/peer-discovery.mjs +200 -0
- package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
- package/scripts/lib/persona-panel/consolidator.mjs +370 -0
- package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
- package/scripts/lib/persona-panel/threshold.mjs +130 -0
- package/scripts/lib/pi-hook-bridge.mjs +328 -0
- package/scripts/lib/platform.mjs +266 -0
- package/scripts/lib/playwright-driver/runner.mjs +297 -0
- package/scripts/lib/plugin-root.mjs +210 -0
- package/scripts/lib/pre-dispatch-check.mjs +126 -0
- package/scripts/lib/product-repo-detect.mjs +121 -0
- package/scripts/lib/profiles/registry.mjs +176 -0
- package/scripts/lib/profiles/schema.mjs +209 -0
- package/scripts/lib/qg-command-drift-banner.mjs +88 -0
- package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
- package/scripts/lib/quality-gate.mjs +536 -0
- package/scripts/lib/quality-gates-cache.mjs +228 -0
- package/scripts/lib/quality-gates-policy.mjs +95 -0
- package/scripts/lib/recommendations-v0.mjs +156 -0
- package/scripts/lib/reconcile/eligibility.mjs +203 -0
- package/scripts/lib/reconcile/emitter.mjs +244 -0
- package/scripts/lib/reconcile/engine.mjs +412 -0
- package/scripts/lib/reconcile/idempotency.mjs +239 -0
- package/scripts/lib/reconcile/renderer.mjs +211 -0
- package/scripts/lib/reconcile/writer.mjs +293 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
- package/scripts/lib/resource-probe/evaluate.mjs +190 -0
- package/scripts/lib/resource-probe/parsers.mjs +181 -0
- package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
- package/scripts/lib/resource-probe.mjs +95 -0
- package/scripts/lib/rule-loader.mjs +552 -0
- package/scripts/lib/rules-sync.mjs +439 -0
- package/scripts/lib/scope-gate.mjs +496 -0
- package/scripts/lib/session-close-backfill.mjs +539 -0
- package/scripts/lib/session-discovery.mjs +256 -0
- package/scripts/lib/session-end/phase-skip.mjs +357 -0
- package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
- package/scripts/lib/session-id.mjs +362 -0
- package/scripts/lib/session-lock.mjs +703 -0
- package/scripts/lib/session-registry.mjs +355 -0
- package/scripts/lib/session-schema/aliases.mjs +71 -0
- package/scripts/lib/session-schema/constants.mjs +115 -0
- package/scripts/lib/session-schema/normalizer.mjs +66 -0
- package/scripts/lib/session-schema/timestamps.mjs +64 -0
- package/scripts/lib/session-schema/validator.mjs +453 -0
- package/scripts/lib/session-schema.mjs +71 -0
- package/scripts/lib/session-token-rollup.mjs +137 -0
- package/scripts/lib/sessions-staleness-banner.mjs +247 -0
- package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
- package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
- package/scripts/lib/skill-evolution/engine.mjs +719 -0
- package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
- package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
- package/scripts/lib/skill-health/join.mjs +181 -0
- package/scripts/lib/skill-health/score.mjs +123 -0
- package/scripts/lib/skill-invocations-schema.mjs +214 -0
- package/scripts/lib/skill-judge.mjs +348 -0
- package/scripts/lib/skill-judgments-schema.mjs +264 -0
- package/scripts/lib/slopcheck.mjs +501 -0
- package/scripts/lib/soul-resolve.mjs +118 -0
- package/scripts/lib/spiral-carryover.mjs +495 -0
- package/scripts/lib/state-md/body-sections.mjs +851 -0
- package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
- package/scripts/lib/state-md/mission-status.mjs +247 -0
- package/scripts/lib/state-md/recommendations.mjs +57 -0
- package/scripts/lib/state-md/yaml-parser.mjs +234 -0
- package/scripts/lib/state-md-peer-guard.mjs +232 -0
- package/scripts/lib/state-md.mjs +53 -0
- package/scripts/lib/subagents-schema.mjs +309 -0
- package/scripts/lib/sunset/walker.mjs +1192 -0
- package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
- package/scripts/lib/test-runner/fingerprint.mjs +33 -0
- package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
- package/scripts/lib/tmux-layout/layouts.mjs +224 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
- package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
- package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
- package/scripts/lib/validate/check-agents.mjs +457 -0
- package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
- package/scripts/lib/validate/check-commands.mjs +148 -0
- package/scripts/lib/validate/check-component-paths.mjs +112 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
- package/scripts/lib/validate/check-json-files.mjs +116 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
- package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
- package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
- package/scripts/lib/validate/check-pi-package.mjs +110 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
- package/scripts/lib/validate/check-plugin-json.mjs +96 -0
- package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
- package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
- package/scripts/lib/validate/check-rules.mjs +143 -0
- package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
- package/scripts/lib/validate/confidential-names.mjs +169 -0
- package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
- package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
- package/scripts/lib/validate/tier-inference.mjs +100 -0
- package/scripts/lib/validate-vendored-rules.mjs +519 -0
- package/scripts/lib/vault-archive.mjs +404 -0
- package/scripts/lib/vault-backfill/glab.mjs +164 -0
- package/scripts/lib/vault-backfill/manifest.mjs +75 -0
- package/scripts/lib/vault-backfill/template.mjs +130 -0
- package/scripts/lib/vault-consolidate-fs.mjs +331 -0
- package/scripts/lib/vault-migration-rules.mjs +155 -0
- package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
- package/scripts/lib/vault-mirror/namespace.mjs +152 -0
- package/scripts/lib/vault-mirror/process.mjs +567 -0
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
- package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
- package/scripts/lib/vault-mirror/render.mjs +8 -0
- package/scripts/lib/vault-mirror/utils.mjs +217 -0
- package/scripts/lib/vault-relocation-rules.mjs +555 -0
- package/scripts/lib/vault-repo-backfill.mjs +235 -0
- package/scripts/lib/vault-staleness-banner.mjs +142 -0
- package/scripts/lib/vault-status/board-writer.mjs +769 -0
- package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
- package/scripts/lib/vault-sync-baseline.mjs +152 -0
- package/scripts/lib/wave-context.mjs +29 -0
- package/scripts/lib/wave-executor/pool.mjs +248 -0
- package/scripts/lib/wave-resource-gate.mjs +204 -0
- package/scripts/lib/wave-sizing.mjs +75 -0
- package/scripts/lib/webhook-url.mjs +105 -0
- package/scripts/lib/workspace.mjs +198 -0
- package/scripts/lib/worktree/constants.mjs +35 -0
- package/scripts/lib/worktree/index.mjs +17 -0
- package/scripts/lib/worktree/lifecycle.mjs +287 -0
- package/scripts/lib/worktree/listing.mjs +118 -0
- package/scripts/lib/worktree/meta.mjs +64 -0
- package/scripts/lib/worktree-freshness.mjs +313 -0
- package/scripts/lib/worktree.mjs +15 -0
- package/scripts/lifecycle-sim-v6.mjs +347 -0
- package/scripts/lock-reaper.mjs +185 -0
- package/scripts/mcp-server.sh +241 -0
- package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
- package/scripts/memory-propose.mjs +464 -0
- package/scripts/migrate-cold-start-seed.mjs +404 -0
- package/scripts/migrate-learnings-jsonl.mjs +189 -0
- package/scripts/migrate-legacy-learnings.sh +61 -0
- package/scripts/migrate-sessions-jsonl.mjs +448 -0
- package/scripts/migrate-subagents-jsonl.mjs +196 -0
- package/scripts/migrate-vault-paths.mjs +796 -0
- package/scripts/parse-config.mjs +149 -0
- package/scripts/pi-install.mjs +117 -0
- package/scripts/print-applicable-rules.mjs +247 -0
- package/scripts/promote-vault-strict.mjs +496 -0
- package/scripts/relocate-vault-corpus.mjs +1178 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
- package/scripts/run-quality-gate.mjs +216 -0
- package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
- package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
- package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
- package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
- package/scripts/sweep-expired-learnings.mjs +135 -0
- package/scripts/sync-vault-schema.mjs +376 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
- package/scripts/tmux-layout.mjs +245 -0
- package/scripts/token-audit.sh +191 -0
- package/scripts/typecheck.mjs +42 -0
- package/scripts/upload-social-preview.mjs +316 -0
- package/scripts/validate-config.mjs +46 -0
- package/scripts/validate-plugin-manifests.mjs +163 -0
- package/scripts/validate-plugin.mjs +264 -0
- package/scripts/validate-wave-scope.mjs +289 -0
- package/scripts/vault-backfill.mjs +404 -0
- package/scripts/vault-consolidate.mjs +596 -0
- package/scripts/vault-integration-watcher.mjs +394 -0
- package/scripts/vault-mirror.mjs +430 -0
- package/skills/_shared/bootstrap-gate.md +111 -0
- package/skills/_shared/config-reading.md +226 -0
- package/skills/_shared/instruction-file-resolution.md +79 -0
- package/skills/_shared/model-selection.md +64 -0
- package/skills/_shared/monitor-patterns.md +300 -0
- package/skills/_shared/parallel-aware-auq.md +121 -0
- package/skills/_shared/parallel-aware-preamble.md +185 -0
- package/skills/_shared/platform-tools.md +96 -0
- package/skills/_shared/state-ownership.md +221 -0
- package/skills/architecture/DEEPENING.md +37 -0
- package/skills/architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/architecture/LANGUAGE.md +53 -0
- package/skills/architecture/SKILL.md +92 -0
- package/skills/autopilot/SKILL.md +419 -0
- package/skills/bootstrap/SKILL.md +592 -0
- package/skills/bootstrap/STATE.md.template +24 -0
- package/skills/bootstrap/_shared-template.md +243 -0
- package/skills/bootstrap/deep-template.md +659 -0
- package/skills/bootstrap/fast-template.md +251 -0
- package/skills/bootstrap/intensity-heuristic.md +80 -0
- package/skills/bootstrap/public-fallback.md +342 -0
- package/skills/bootstrap/standard-template.md +736 -0
- package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
- package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
- package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
- package/skills/brainstorm/SKILL.md +268 -0
- package/skills/brainstorm/soul.md +49 -0
- package/skills/claude-md-drift-check/SKILL.md +186 -0
- package/skills/claude-md-drift-check/checker.mjs +1380 -0
- package/skills/claude-md-drift-check/checker.sh +37 -0
- package/skills/claude-md-drift-check/package.json +16 -0
- package/skills/convergence-monitoring/README.md +39 -0
- package/skills/convergence-monitoring/SIGNALS.md +246 -0
- package/skills/convergence-monitoring/SKILL.md +285 -0
- package/skills/daily/SKILL.md +222 -0
- package/skills/daily/generate.sh +92 -0
- package/skills/daily/templates/daily.md.tpl +36 -0
- package/skills/debug/SKILL.md +188 -0
- package/skills/debug/soul.md +35 -0
- package/skills/discovery/SKILL.md +567 -0
- package/skills/discovery/issue-templates.md +237 -0
- package/skills/discovery/probes/docs-staleness.mjs +195 -0
- package/skills/discovery/probes/frontend-slop.mjs +186 -0
- package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
- package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
- package/skills/discovery/probes/vault-staleness.mjs +272 -0
- package/skills/discovery/probes-arch.md +252 -0
- package/skills/discovery/probes-audit.md +95 -0
- package/skills/discovery/probes-code.md +329 -0
- package/skills/discovery/probes-docs.md +76 -0
- package/skills/discovery/probes-feature.md +150 -0
- package/skills/discovery/probes-infra.md +138 -0
- package/skills/discovery/probes-intro.md +25 -0
- package/skills/discovery/probes-session.md +495 -0
- package/skills/discovery/probes-supply-chain.md +94 -0
- package/skills/discovery/probes-ui.md +147 -0
- package/skills/discovery/probes-vault.md +64 -0
- package/skills/discovery/slop-patterns.md +115 -0
- package/skills/dispatcher/SKILL.md +173 -0
- package/skills/docs-orchestrator/SKILL.md +362 -0
- package/skills/docs-orchestrator/audience-mapping.md +140 -0
- package/skills/domain-model/ADR-FORMAT.md +47 -0
- package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
- package/skills/domain-model/SKILL.md +85 -0
- package/skills/ecosystem-health/SKILL.md +119 -0
- package/skills/ecosystem-health/wizard.md +193 -0
- package/skills/eval/SKILL.md +293 -0
- package/skills/eval/rubric-v1.md +218 -0
- package/skills/evolve/SKILL.md +546 -0
- package/skills/frontmatter-guard/SKILL.md +126 -0
- package/skills/gitlab-ops/SKILL.md +368 -0
- package/skills/gitlab-portfolio/SKILL.md +196 -0
- package/skills/grill/SKILL.md +185 -0
- package/skills/grill/soul.md +55 -0
- package/skills/hook-development/SKILL.md +413 -0
- package/skills/mcp-builder/SKILL.md +260 -0
- package/skills/memory-cleanup/SKILL.md +310 -0
- package/skills/mode-selector/SKILL.md +226 -0
- package/skills/peekaboo-driver/SKILL.md +237 -0
- package/skills/peekaboo-driver/soul.md +32 -0
- package/skills/persona-panel/SKILL.md +365 -0
- package/skills/persona-panel/persona-format.md +205 -0
- package/skills/persona-panel/presets/designer-lens.md +87 -0
- package/skills/persona-panel/presets/engineer-lens.md +88 -0
- package/skills/persona-panel/presets/pm-lens.md +86 -0
- package/skills/plan/SKILL.md +496 -0
- package/skills/plan/mode-feature.md +141 -0
- package/skills/plan/mode-new.md +297 -0
- package/skills/plan/mode-retro.md +271 -0
- package/skills/plan/prd-feature-template.md +132 -0
- package/skills/plan/prd-full-template.md +151 -0
- package/skills/plan/prd-reviewer-prompt.md +103 -0
- package/skills/plan/retro-template.md +75 -0
- package/skills/plan/soul.md +62 -0
- package/skills/playwright-driver/SKILL.md +226 -0
- package/skills/playwright-driver/soul.md +30 -0
- package/skills/quality-gates/SKILL.md +212 -0
- package/skills/reconcile/SKILL.md +324 -0
- package/skills/repo-audit/SKILL.md +272 -0
- package/skills/session-end/SKILL.md +1044 -0
- package/skills/session-end/discovery-scan.md +37 -0
- package/skills/session-end/drift-operations.md +97 -0
- package/skills/session-end/learning-patterns.md +78 -0
- package/skills/session-end/metrics-collection.md +175 -0
- package/skills/session-end/phase-3-2-docs-verification.md +148 -0
- package/skills/session-end/phase-3-6-tail.md +344 -0
- package/skills/session-end/phase-3-7a-recommendations.md +86 -0
- package/skills/session-end/plan-verification.md +288 -0
- package/skills/session-end/session-metrics-write.md +223 -0
- package/skills/session-end/vault-operations.md +50 -0
- package/skills/session-end/verification-checklist.md +20 -0
- package/skills/session-plan/SKILL.md +554 -0
- package/skills/session-plan/wave-template.md +37 -0
- package/skills/session-start/SKILL.md +1043 -0
- package/skills/session-start/phase-2-5-docs-planning.md +119 -0
- package/skills/session-start/phase-4-5-resource-health.md +49 -0
- package/skills/session-start/phase-7-1-premise-check.md +47 -0
- package/skills/session-start/phase-7-5-mode-selector.md +237 -0
- package/skills/session-start/phase-8-5-express-path.md +61 -0
- package/skills/session-start/presentation-format.md +81 -0
- package/skills/session-start/soul.md +57 -0
- package/skills/skill-creator/SKILL.md +168 -0
- package/skills/spinout/SKILL.md +76 -0
- package/skills/sunset-review/SKILL.md +96 -0
- package/skills/test-runner/SKILL.md +362 -0
- package/skills/test-runner/rubric-v1.md +388 -0
- package/skills/test-runner/soul.md +46 -0
- package/skills/tmux-layout/SKILL.md +104 -0
- package/skills/ubiquitous-language/SKILL.md +97 -0
- package/skills/using-orchestrator/SKILL.md +144 -0
- package/skills/vault-mirror/SKILL.md +234 -0
- package/skills/vault-sync/SKILL.md +319 -0
- package/skills/vault-sync/package-lock.json +40 -0
- package/skills/vault-sync/package.json +11 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
- package/skills/vault-sync/validator.mjs +658 -0
- package/skills/vault-sync/validator.sh +55 -0
- package/skills/wave-executor/SKILL.md +496 -0
- package/skills/wave-executor/circuit-breaker.md +169 -0
- package/skills/wave-executor/wave-loop.md +1043 -0
- package/skills/write-executable-plan/SKILL.md +237 -0
- package/skills/write-executable-plan/plan-template.md +154 -0
- package/templates/_minimal/CLAUDE.md.tmpl +41 -0
- package/templates/_minimal/README.md.tmpl +15 -0
- package/templates/_minimal/gitignore.tmpl +47 -0
- package/templates/_shared/harte-regeln.md +16 -0
- package/templates/_shared/loop.md +90 -0
- package/templates/_shared/rules/parallel-sessions.md +77 -0
- package/templates/nextjs-minimal/README.md +30 -0
- package/templates/nextjs-minimal/app/layout.tsx +18 -0
- package/templates/nextjs-minimal/app/page.tsx +7 -0
- package/templates/nextjs-minimal/eslint.config.mjs +16 -0
- package/templates/nextjs-minimal/next.config.mjs +4 -0
- package/templates/nextjs-minimal/package.json +27 -0
- package/templates/nextjs-minimal/tsconfig.json +23 -0
- package/templates/node-minimal/README.md +33 -0
- package/templates/node-minimal/eslint.config.mjs +10 -0
- package/templates/node-minimal/package.json +21 -0
- package/templates/node-minimal/src/index.ts +1 -0
- package/templates/node-minimal/tests/sanity.test.ts +5 -0
- package/templates/node-minimal/tsconfig.json +17 -0
- package/templates/personas/README.md +150 -0
- package/templates/personas/accounting-compliance.v1.md +120 -0
- package/templates/personas/accounting-tax-advisor.v1.md +116 -0
- package/templates/personas/buyer-p1-cto.v1.md +125 -0
- package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
- package/templates/personas/buyer-p3-build.v1.md +130 -0
- package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
- package/templates/personas/buyer-p5-solo.v1.md +132 -0
- package/templates/personas/buyer-p6-ld.v1.md +130 -0
- package/templates/personas/klima-ai-expert.v1.md +114 -0
- package/templates/personas/klima-physicist.v1.md +117 -0
- package/templates/python-uv/README.md +28 -0
- package/templates/python-uv/pyproject.toml +32 -0
- package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
- package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
- package/templates/python-uv/tests/test_sanity.py +2 -0
- package/templates/static-html/README.md +19 -0
- package/templates/static-html/index.html +15 -0
- package/templates/static-html/script.js +1 -0
- package/templates/static-html/styles.css +26 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* triage-state.mjs — Persistent triage state for discovery findings.
|
|
3
|
+
*
|
|
4
|
+
* Implements append-only JSONL state tracking for discovery findings
|
|
5
|
+
* introduced in issue #419. State is keyed by a stable fingerprint derived
|
|
6
|
+
* from {probe, file, severity, ruleId} — line_number intentionally excluded
|
|
7
|
+
* because it drifts on refactoring without the underlying issue changing.
|
|
8
|
+
*
|
|
9
|
+
* Design decisions (from W1-A5 discovery):
|
|
10
|
+
* - Fingerprint: sha256(probe|file|severity|ruleId).slice(0, 16) hex
|
|
11
|
+
* - State enum: open | dismissed | promoted-to-#NNN | accepted-as-known | reopened
|
|
12
|
+
* - No TTL on dismissed state — recurrence requires a genuine fingerprint change
|
|
13
|
+
* - Last-writer-wins per fingerprint when loading state
|
|
14
|
+
* - Re-run flow: load state → filter new findings → present only open/missing
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
18
|
+
import { appendFile, readFile, mkdir } from 'node:fs/promises';
|
|
19
|
+
import { existsSync } from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { findProjectRoot } from '../common.mjs';
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Constants
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/** Valid triage state values. */
|
|
28
|
+
export const VALID_STATES = [
|
|
29
|
+
'open',
|
|
30
|
+
'dismissed',
|
|
31
|
+
'accepted-as-known',
|
|
32
|
+
'reopened',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// promoted-to-#NNN is a dynamic variant — validated via prefix check below
|
|
36
|
+
const PROMOTED_PREFIX = 'promoted-to-#';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default state file path relative to the repo root.
|
|
40
|
+
* Override by passing `stateFilePath` explicitly.
|
|
41
|
+
*/
|
|
42
|
+
export const DEFAULT_STATE_FILE = '.orchestrator/metrics/discovery-triage.jsonl';
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Helpers
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the absolute state file path.
|
|
50
|
+
* If `stateFilePath` is already absolute, use it as-is.
|
|
51
|
+
* Otherwise join with the project root.
|
|
52
|
+
*
|
|
53
|
+
* @param {string|undefined} stateFilePath
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
function resolveStatePath(stateFilePath) {
|
|
57
|
+
const p = stateFilePath ?? DEFAULT_STATE_FILE;
|
|
58
|
+
if (path.isAbsolute(p)) return p;
|
|
59
|
+
return path.join(findProjectRoot(), p);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Return true if `state` is a valid triage state value.
|
|
64
|
+
* Accepts fixed enum values AND the dynamic `promoted-to-#NNN` form.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} state
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
function isValidState(state) {
|
|
70
|
+
if (typeof state !== 'string') return false;
|
|
71
|
+
if (VALID_STATES.includes(state)) return true;
|
|
72
|
+
if (state.startsWith(PROMOTED_PREFIX)) {
|
|
73
|
+
const suffix = state.slice(PROMOTED_PREFIX.length);
|
|
74
|
+
return /^\d+$/.test(suffix);
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// computeFingerprint
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compute a deterministic 16-character hex fingerprint for a finding.
|
|
85
|
+
* Stable across runs as long as probe/file/severity/ruleId are unchanged.
|
|
86
|
+
* line_number is intentionally excluded — it drifts on refactoring.
|
|
87
|
+
*
|
|
88
|
+
* @param {{probe: string, file: string, severity: string, ruleId: string}} finding
|
|
89
|
+
* @returns {string} 16-char lowercase hex string
|
|
90
|
+
* @throws {TypeError} if any required field is missing or not a string
|
|
91
|
+
*/
|
|
92
|
+
export function computeFingerprint({ probe, file, severity, ruleId } = {}) {
|
|
93
|
+
const fields = { probe, file, severity, ruleId };
|
|
94
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
95
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
96
|
+
throw new TypeError(
|
|
97
|
+
`computeFingerprint: field '${key}' must be a non-empty string, got ${JSON.stringify(value)}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const input = [probe, file, severity, ruleId].join('|');
|
|
102
|
+
return createHash('sha256').update(input).digest('hex').slice(0, 16);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// loadTriageState
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Load discovery triage state from a JSONL file.
|
|
111
|
+
*
|
|
112
|
+
* Each line is a JSON object with at minimum `{fingerprint, state}`.
|
|
113
|
+
* Entries are accumulated by fingerprint — later entries overwrite earlier
|
|
114
|
+
* ones (last-writer-wins). Malformed lines are silently skipped.
|
|
115
|
+
*
|
|
116
|
+
* Returns an empty Map if the file does not exist.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} [stateFilePath] — path to the JSONL file (absolute or
|
|
119
|
+
* repo-relative). Defaults to DEFAULT_STATE_FILE.
|
|
120
|
+
* @returns {Promise<Map<string, {state: string, issue_id?: number, timestamp: string, session_id: string}>>}
|
|
121
|
+
*/
|
|
122
|
+
export async function loadTriageState(stateFilePath) {
|
|
123
|
+
const resolved = resolveStatePath(stateFilePath);
|
|
124
|
+
if (!existsSync(resolved)) return new Map();
|
|
125
|
+
|
|
126
|
+
const raw = await readFile(resolved, 'utf8');
|
|
127
|
+
const lines = raw.split('\n').filter((l) => l.trim().length > 0);
|
|
128
|
+
|
|
129
|
+
const map = new Map();
|
|
130
|
+
let malformedCount = 0;
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
try {
|
|
133
|
+
const entry = JSON.parse(line);
|
|
134
|
+
if (typeof entry.fingerprint !== 'string') continue;
|
|
135
|
+
map.set(entry.fingerprint, {
|
|
136
|
+
state: entry.state,
|
|
137
|
+
issue_id: entry.issue_id,
|
|
138
|
+
timestamp: entry.timestamp,
|
|
139
|
+
session_id: entry.session_id,
|
|
140
|
+
user_decision: entry.user_decision,
|
|
141
|
+
});
|
|
142
|
+
} catch {
|
|
143
|
+
// Skip malformed lines — the file is append-only and a partial write
|
|
144
|
+
// (e.g. from a crashed session) should not prevent loading valid state.
|
|
145
|
+
// Set DISCOVERY_DEBUG=1 to surface these at runtime for diagnosis.
|
|
146
|
+
malformedCount += 1;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (malformedCount > 0 && process.env.DISCOVERY_DEBUG) {
|
|
151
|
+
process.stderr.write(
|
|
152
|
+
`[triage-state] loadTriageState: skipped ${malformedCount} malformed JSONL line(s) in ${resolved}\n`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return map;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// appendTriageEntry
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Append a single triage entry to the JSONL state file.
|
|
165
|
+
*
|
|
166
|
+
* The entry shape is:
|
|
167
|
+
* ```json
|
|
168
|
+
* {
|
|
169
|
+
* "fingerprint": "...",
|
|
170
|
+
* "state": "open|dismissed|accepted-as-known|reopened|promoted-to-#NNN",
|
|
171
|
+
* "issue_id": 123, // optional — for promoted-to-#NNN entries
|
|
172
|
+
* "user_decision": "...", // optional — human-readable decision context
|
|
173
|
+
* "timestamp": "2026-01-01T00:00:00.000Z",
|
|
174
|
+
* "session_id": "deep-1"
|
|
175
|
+
* }
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
* Creates parent directories if missing.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} stateFilePath — absolute or repo-relative path
|
|
181
|
+
* @param {{fingerprint: string, state: string, issue_id?: number, user_decision?: string, timestamp: string, session_id: string}} entry
|
|
182
|
+
* @returns {Promise<void>}
|
|
183
|
+
* @throws {TypeError} if state is not a valid enum value
|
|
184
|
+
* @throws {TypeError} if fingerprint, timestamp, or session_id is missing
|
|
185
|
+
*/
|
|
186
|
+
export async function appendTriageEntry(stateFilePath, entry) {
|
|
187
|
+
if (typeof entry?.fingerprint !== 'string' || entry.fingerprint.length === 0) {
|
|
188
|
+
throw new TypeError('appendTriageEntry: entry.fingerprint must be a non-empty string');
|
|
189
|
+
}
|
|
190
|
+
if (typeof entry?.timestamp !== 'string' || entry.timestamp.length === 0) {
|
|
191
|
+
throw new TypeError('appendTriageEntry: entry.timestamp must be a non-empty string');
|
|
192
|
+
}
|
|
193
|
+
if (typeof entry?.session_id !== 'string' || entry.session_id.length === 0) {
|
|
194
|
+
throw new TypeError('appendTriageEntry: entry.session_id must be a non-empty string');
|
|
195
|
+
}
|
|
196
|
+
if (!isValidState(entry?.state)) {
|
|
197
|
+
throw new TypeError(
|
|
198
|
+
`appendTriageEntry: invalid state '${entry?.state}'. ` +
|
|
199
|
+
`Valid values: ${VALID_STATES.join(', ')}, or promoted-to-#<number>`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const resolved = resolveStatePath(stateFilePath);
|
|
204
|
+
await mkdir(path.dirname(resolved), { recursive: true });
|
|
205
|
+
|
|
206
|
+
const line = JSON.stringify(entry) + '\n';
|
|
207
|
+
await appendFile(resolved, line, 'utf8');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// filterFindings
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Partition a list of findings against the loaded triage state map.
|
|
216
|
+
*
|
|
217
|
+
* Returns three buckets:
|
|
218
|
+
* - `toShow` — findings with state=open or NO prior state entry (new findings)
|
|
219
|
+
* - `suppressed` — findings with state=dismissed or state=accepted-as-known
|
|
220
|
+
* - `tracked` — findings with state=promoted-to-#NNN (informational; issue_id attached)
|
|
221
|
+
*
|
|
222
|
+
* For `reopened` findings, they are included in `toShow` (re-presented for triage).
|
|
223
|
+
*
|
|
224
|
+
* Each finding must have enough fields to compute a fingerprint:
|
|
225
|
+
* {probe, file, severity, ruleId}. Findings that cannot be fingerprinted
|
|
226
|
+
* are included in `toShow` (safe default — show rather than suppress).
|
|
227
|
+
*
|
|
228
|
+
* @param {{
|
|
229
|
+
* findings: Array<{probe: string, file: string, severity: string, ruleId: string, [key: string]: unknown}>,
|
|
230
|
+
* stateMap: Map<string, {state: string, issue_id?: number}>
|
|
231
|
+
* }} params
|
|
232
|
+
* @returns {{
|
|
233
|
+
* toShow: typeof params.findings,
|
|
234
|
+
* suppressed: typeof params.findings,
|
|
235
|
+
* tracked: Array<typeof params.findings[number] & {issue_id?: number}>
|
|
236
|
+
* }}
|
|
237
|
+
*/
|
|
238
|
+
export function filterFindings({ findings, stateMap }) {
|
|
239
|
+
const toShow = [];
|
|
240
|
+
const suppressed = [];
|
|
241
|
+
const tracked = [];
|
|
242
|
+
|
|
243
|
+
for (const finding of findings) {
|
|
244
|
+
let fingerprint;
|
|
245
|
+
try {
|
|
246
|
+
fingerprint = computeFingerprint({
|
|
247
|
+
probe: finding.probe,
|
|
248
|
+
file: finding.file,
|
|
249
|
+
severity: finding.severity,
|
|
250
|
+
ruleId: finding.ruleId,
|
|
251
|
+
});
|
|
252
|
+
} catch {
|
|
253
|
+
// Cannot fingerprint → show by default
|
|
254
|
+
toShow.push(finding);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const entry = stateMap.get(fingerprint);
|
|
259
|
+
|
|
260
|
+
if (!entry) {
|
|
261
|
+
// New finding — no prior state
|
|
262
|
+
toShow.push(finding);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const { state } = entry;
|
|
267
|
+
|
|
268
|
+
if (state === 'dismissed' || state === 'accepted-as-known') {
|
|
269
|
+
suppressed.push(finding);
|
|
270
|
+
} else if (state.startsWith(PROMOTED_PREFIX)) {
|
|
271
|
+
tracked.push({ ...finding, issue_id: entry.issue_id });
|
|
272
|
+
} else {
|
|
273
|
+
// open, reopened, or any unknown future state → show
|
|
274
|
+
toShow.push(finding);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return { toShow, suppressed, tracked };
|
|
279
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli.mjs — autopilot dispatcher front-door (Epic #673 P3, issue #678).
|
|
3
|
+
*
|
|
4
|
+
* JSON-first orchestration that wires the Wave-2 dispatcher primitives into a
|
|
5
|
+
* single read path and a scriptable atomic-claim path:
|
|
6
|
+
*
|
|
7
|
+
* enumerateCandidates → freeCandidates → rankCandidates → recommendation
|
|
8
|
+
*
|
|
9
|
+
* This is the front-door for the `dispatcher` skill (coordinator prose lives in
|
|
10
|
+
* `skills/dispatcher/SKILL.md`). The default read path is NON-MUTATING: it scans
|
|
11
|
+
* the confinement root, resolves free/busy per repo, and ranks the free ones.
|
|
12
|
+
* The atomic claim (the only mutating operation) is exposed separately via
|
|
13
|
+
* {@link claimRepo} so it is explicit, scriptable, and Wave-4-testable.
|
|
14
|
+
*
|
|
15
|
+
* Design contract (PRD §2 P3, §4; .claude/rules/cli-design.md):
|
|
16
|
+
* - JSON-first: every invocation supports `--json` (machine-parseable stdout).
|
|
17
|
+
* - Data → stdout, diagnostics/warnings → stderr. Never mixed.
|
|
18
|
+
* - Exit codes: 0 success · 1 user/input error · 2 system error.
|
|
19
|
+
* - `--dry-run` = explicit non-mutating rank (the default read path is already
|
|
20
|
+
* non-mutating; the flag documents intent and is reserved for parity with the
|
|
21
|
+
* skill's read/claim split).
|
|
22
|
+
* - `runDispatch` / `claimRepo` are testable orchestration fns — all I/O flows
|
|
23
|
+
* through injectable `deps` so Wave-4 tests are deterministic.
|
|
24
|
+
* - Importing the module never executes `main` (import.meta.url guard).
|
|
25
|
+
*
|
|
26
|
+
* Plain Node ESM. Named exports. Stdlib + Wave-2 dispatcher modules only.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { parseArgs } from 'node:util';
|
|
30
|
+
|
|
31
|
+
import { warn } from '../common.mjs';
|
|
32
|
+
import { acquire } from '../session-lock.mjs';
|
|
33
|
+
import { enumerateCandidates, freeCandidates } from './enumerate.mjs';
|
|
34
|
+
import { rankCandidates } from './rank.mjs';
|
|
35
|
+
|
|
36
|
+
const VERSION = '1.0.0';
|
|
37
|
+
|
|
38
|
+
const USAGE = `dispatcher — cross-repo autopilot front-door (Epic #673, issue #678)
|
|
39
|
+
|
|
40
|
+
Enumerates candidate repos below the confinement root, resolves free/busy from
|
|
41
|
+
each repo's session.lock lease, and ranks the FREE ones so you can pick the most
|
|
42
|
+
worthwhile to work on next. The read path is non-mutating; the atomic claim is a
|
|
43
|
+
separate, explicit step (see the dispatcher skill).
|
|
44
|
+
|
|
45
|
+
USAGE
|
|
46
|
+
node scripts/lib/dispatcher/cli.mjs [options]
|
|
47
|
+
|
|
48
|
+
OPTIONS
|
|
49
|
+
--json Emit the full result as a single JSON object to stdout.
|
|
50
|
+
--dry-run Explicit non-mutating rank (the default read path is
|
|
51
|
+
already non-mutating; this documents intent).
|
|
52
|
+
--repo <name> Filter the human-readable output to a single repoName.
|
|
53
|
+
(Does not change ranking — informational only.)
|
|
54
|
+
--start-dir <path> Override the scan root (defaults to the confinement root).
|
|
55
|
+
--help Print this help and exit 0.
|
|
56
|
+
--version Print the version and exit 0.
|
|
57
|
+
|
|
58
|
+
OUTPUT
|
|
59
|
+
Default (human): a readable table of free candidates, the top recommendation,
|
|
60
|
+
and any warnings (warnings to stderr).
|
|
61
|
+
--json: JSON.stringify({ candidates, free, ranked, warnings, recommended }).
|
|
62
|
+
|
|
63
|
+
EXIT CODES
|
|
64
|
+
0 success
|
|
65
|
+
1 user/input error (e.g. bad --start-dir)
|
|
66
|
+
2 system error (unexpected failure)
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run the non-mutating dispatch read path: enumerate → filter free → rank.
|
|
71
|
+
*
|
|
72
|
+
* Wires the three Wave-2 primitives into one serialisable result. The only I/O
|
|
73
|
+
* is performed by the injected `deps` (forwarded verbatim to
|
|
74
|
+
* `enumerateCandidates` and `rankCandidates`), so this fn is deterministic under
|
|
75
|
+
* stubbed deps.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} [opts]
|
|
78
|
+
* @param {string} [opts.startDir] — scan root; defaults to the confinement root.
|
|
79
|
+
* @param {number} [opts.now] — clock seam in ms; forwarded to both stages.
|
|
80
|
+
* @param {object} [opts.deps] — DI seam forwarded to enumerate + rank.
|
|
81
|
+
* @returns {Promise<{
|
|
82
|
+
* candidates: import('./enumerate.mjs').Candidate[],
|
|
83
|
+
* free: import('./enumerate.mjs').Candidate[],
|
|
84
|
+
* ranked: Awaited<ReturnType<typeof rankCandidates>>['ranked'],
|
|
85
|
+
* warnings: string[],
|
|
86
|
+
* recommended: (Awaited<ReturnType<typeof rankCandidates>>['ranked'][number]) | null,
|
|
87
|
+
* }>}
|
|
88
|
+
*/
|
|
89
|
+
export async function runDispatch({ startDir, now, deps } = {}) {
|
|
90
|
+
const candidates = await enumerateCandidates({ startDir, now, deps });
|
|
91
|
+
const free = freeCandidates(candidates);
|
|
92
|
+
const { ranked, warnings } = await rankCandidates(free, { now, deps });
|
|
93
|
+
const recommended = ranked.length > 0 ? ranked[0] : null;
|
|
94
|
+
return { candidates, free, ranked, warnings, recommended };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Atomic, scriptable repo claim. Thin wrapper over `acquire(...)` from
|
|
99
|
+
* session-lock.mjs — returns its result VERBATIM so callers (the skill prose,
|
|
100
|
+
* Wave-4 tests) can branch on the exact `{ ok, reason, ... }` contract.
|
|
101
|
+
*
|
|
102
|
+
* On `ok:false` (race lost / busy — reasons: active, stale-pid-alive,
|
|
103
|
+
* stale-pid-dead, fs-error, ...) the dispatcher must exclude that repo and
|
|
104
|
+
* re-rank the remaining free candidates. On `ok:true` the claim is held and the
|
|
105
|
+
* coordinator may launch the chosen entry command.
|
|
106
|
+
*
|
|
107
|
+
* @param {object} [opts]
|
|
108
|
+
* @param {string} [opts.repoRoot] — absolute path to the repo to claim.
|
|
109
|
+
* @param {string} [opts.sessionId] — claiming session id.
|
|
110
|
+
* @param {string} [opts.mode] — session mode (drives exclusivity classification).
|
|
111
|
+
* @param {number} [opts.ttlHours] — lease TTL in hours (defaults inside acquire).
|
|
112
|
+
* @param {string} [opts.semanticSessionId] — optional semantic id for the lease.
|
|
113
|
+
* @param {object} [opts.deps] — DI seam: `deps.acquire` overrides the real acquire.
|
|
114
|
+
* @returns {{ ok: true, lock: object } | { ok: false, reason: string }}
|
|
115
|
+
*/
|
|
116
|
+
export function claimRepo({ repoRoot, sessionId, mode, ttlHours, semanticSessionId, deps } = {}) {
|
|
117
|
+
const acquireFn = (deps && typeof deps.acquire === 'function') ? deps.acquire : acquire;
|
|
118
|
+
return acquireFn({ repoRoot, sessionId, mode, ttlHours, semanticSessionId });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Render the human-readable (non-JSON) report for a dispatch result.
|
|
123
|
+
* Pure string builder — no I/O. The caller decides stdout vs stderr.
|
|
124
|
+
*
|
|
125
|
+
* @param {Awaited<ReturnType<typeof runDispatch>>} result
|
|
126
|
+
* @param {{ repo?: string }} [opts] — `repo` filters the free table to one name.
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
function renderHuman(result, { repo } = {}) {
|
|
130
|
+
const { free, ranked, recommended } = result;
|
|
131
|
+
const lines = [];
|
|
132
|
+
|
|
133
|
+
lines.push('Dispatcher — free candidate repos');
|
|
134
|
+
lines.push('');
|
|
135
|
+
|
|
136
|
+
const shown = repo
|
|
137
|
+
? ranked.filter((r) => r.candidate?.repoName === repo)
|
|
138
|
+
: ranked;
|
|
139
|
+
|
|
140
|
+
if (shown.length === 0) {
|
|
141
|
+
if (repo) {
|
|
142
|
+
lines.push(`(no free candidate named "${repo}")`);
|
|
143
|
+
} else if (free.length === 0) {
|
|
144
|
+
lines.push('(no free candidates — all repos busy)');
|
|
145
|
+
} else {
|
|
146
|
+
lines.push('(no ranked candidates)');
|
|
147
|
+
}
|
|
148
|
+
return lines.join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Compact table: rank · repoName · score · staleDays · ci · priority.
|
|
152
|
+
lines.push(' # repo score staleD ci priority');
|
|
153
|
+
lines.push(' - -------------------------------- ------ ------ ------- --------');
|
|
154
|
+
shown.forEach((row, i) => {
|
|
155
|
+
const name = (row.candidate?.repoName ?? '<unknown>').padEnd(32).slice(0, 32);
|
|
156
|
+
const score = row.score.toFixed(2).padStart(6);
|
|
157
|
+
const stale = Math.round(row.signals?.staleDays ?? 0).toString().padStart(6);
|
|
158
|
+
const ci = String(row.signals?.readiness?.ciStatus ?? 'n/a').padEnd(7).slice(0, 7);
|
|
159
|
+
const prio = row.signals?.priority
|
|
160
|
+
? `c${row.signals.priority.criticalCount}/h${row.signals.priority.highCount}`
|
|
161
|
+
: 'n/a';
|
|
162
|
+
lines.push(` ${String(i + 1).padStart(1)} ${name} ${score} ${stale} ${ci} ${prio}`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
lines.push('');
|
|
166
|
+
if (recommended) {
|
|
167
|
+
lines.push(
|
|
168
|
+
`Recommendation: ${recommended.candidate?.repoName ?? '<unknown>'} ` +
|
|
169
|
+
`(score ${recommended.score.toFixed(2)}) → ${recommended.candidate?.repoRoot ?? ''}`,
|
|
170
|
+
);
|
|
171
|
+
} else {
|
|
172
|
+
lines.push('Recommendation: (none — no free candidates)');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return lines.join('\n');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* CLI entrypoint. Parses flags, runs the read path, and writes either JSON or a
|
|
180
|
+
* human-readable report. Returns the process exit code (does NOT call exit so it
|
|
181
|
+
* is unit-testable; the import.meta guard below maps the return to process.exit).
|
|
182
|
+
*
|
|
183
|
+
* @param {string[]} argv — process.argv.slice(2).
|
|
184
|
+
* @returns {Promise<number>} exit code (0 success · 1 input error · 2 system error)
|
|
185
|
+
*/
|
|
186
|
+
export async function main(argv) {
|
|
187
|
+
let parsed;
|
|
188
|
+
try {
|
|
189
|
+
parsed = parseArgs({
|
|
190
|
+
args: argv,
|
|
191
|
+
options: {
|
|
192
|
+
json: { type: 'boolean', default: false },
|
|
193
|
+
'dry-run': { type: 'boolean', default: false },
|
|
194
|
+
repo: { type: 'string' },
|
|
195
|
+
'start-dir': { type: 'string' },
|
|
196
|
+
help: { type: 'boolean', default: false },
|
|
197
|
+
version: { type: 'boolean', default: false },
|
|
198
|
+
},
|
|
199
|
+
allowPositionals: false,
|
|
200
|
+
});
|
|
201
|
+
} catch (err) {
|
|
202
|
+
process.stderr.write(`ERROR: ${err.message}\n\n${USAGE}`);
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const { values } = parsed;
|
|
207
|
+
|
|
208
|
+
if (values.help) {
|
|
209
|
+
process.stdout.write(USAGE);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
if (values.version) {
|
|
213
|
+
process.stdout.write(`${VERSION}\n`);
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Validate --start-dir as a user/input concern (exit 1 on bad input).
|
|
218
|
+
let startDir;
|
|
219
|
+
if (values['start-dir'] !== undefined) {
|
|
220
|
+
if (typeof values['start-dir'] !== 'string' || values['start-dir'].length === 0) {
|
|
221
|
+
process.stderr.write('ERROR: --start-dir requires a non-empty path\n');
|
|
222
|
+
return 1;
|
|
223
|
+
}
|
|
224
|
+
startDir = values['start-dir'];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let result;
|
|
228
|
+
try {
|
|
229
|
+
result = await runDispatch({ startDir });
|
|
230
|
+
} catch (err) {
|
|
231
|
+
// Unexpected failure in the read path = system error (exit 2). The Wave-2
|
|
232
|
+
// primitives are no-throw by contract, so reaching here is exceptional.
|
|
233
|
+
process.stderr.write(`ERROR: dispatch failed: ${err?.message ?? err}\n`);
|
|
234
|
+
return 2;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Warnings always go to stderr (diagnostics, never mixed with data on stdout).
|
|
238
|
+
for (const w of result.warnings ?? []) warn(w);
|
|
239
|
+
|
|
240
|
+
if (values.json) {
|
|
241
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
process.stdout.write(`${renderHuman(result, { repo: values.repo })}\n`);
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Run main only when executed directly, never on import.
|
|
250
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
251
|
+
main(process.argv.slice(2))
|
|
252
|
+
.then((code) => process.exit(code))
|
|
253
|
+
.catch((err) => {
|
|
254
|
+
process.stderr.write(`ERROR: ${err?.stack ?? err}\n`);
|
|
255
|
+
process.exit(2);
|
|
256
|
+
});
|
|
257
|
+
}
|