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,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enumerate.mjs — Candidate-repo enumeration + free/busy resolution.
|
|
3
|
+
*
|
|
4
|
+
* Epic #673 Phase 2 (issue #676, PRD §2 P2.1+P2.2, §4). Enumerates candidate
|
|
5
|
+
* repos one level below a confinement root and resolves each as free or busy
|
|
6
|
+
* via its per-repo `session.lock` v2 lease (heartbeat-based liveness).
|
|
7
|
+
*
|
|
8
|
+
* Source of truth for free/busy: the same lease semantics as
|
|
9
|
+
* `scripts/lib/vault-status/board-writer.mjs` collectRows —
|
|
10
|
+
* - live lock → in-progress (busy)
|
|
11
|
+
* - dead lock → force-closed (busy)
|
|
12
|
+
* - no lock → frei (free)
|
|
13
|
+
* Busy repos are LISTED, never dropped (PRD: "busy repos listed as such, not
|
|
14
|
+
* selected"). Downstream selection happens in rank.mjs (#677), which consumes
|
|
15
|
+
* the {@link Candidate} contract defined here.
|
|
16
|
+
*
|
|
17
|
+
* Exports:
|
|
18
|
+
* enumerateCandidates — scan immediate children of a startDir, resolve each
|
|
19
|
+
* repo's free/busy status from its lease.
|
|
20
|
+
* freeCandidates — filter helper: keep only `free === true` candidates.
|
|
21
|
+
*
|
|
22
|
+
* No top-level side effects. All filesystem + lock access is dependency-injected
|
|
23
|
+
* (the `deps` arg) so Wave-4 tests can stub a tmpdir fs + fake locks.
|
|
24
|
+
*
|
|
25
|
+
* Plain Node ESM. Named exports. No external deps.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readdirSync, existsSync } from 'node:fs';
|
|
29
|
+
import os from 'node:os';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
|
|
32
|
+
import { getConfinementRoot, getCrossRepoProjects } from '../config/cross-repo.mjs';
|
|
33
|
+
import { validatePathInsideProject } from '../path-utils.mjs';
|
|
34
|
+
import { readLock, isLockLive } from '../session-lock.mjs';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} Candidate
|
|
38
|
+
* @property {string} repoRoot absolute path
|
|
39
|
+
* @property {string} repoName path.basename(repoRoot)
|
|
40
|
+
* @property {boolean} free true iff no live lease
|
|
41
|
+
* @property {'frei'|'in-progress'|'force-closed'} status
|
|
42
|
+
* @property {string|null} heartbeat lock.last_heartbeat ?? null
|
|
43
|
+
* @property {string|null} sessionId lock.semantic_session_id ?? lock.session_id ?? null
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
const STATUS_FREI = 'frei';
|
|
47
|
+
const STATUS_IN_PROGRESS = 'in-progress';
|
|
48
|
+
const STATUS_FORCE_CLOSED = 'force-closed';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Expand a leading `~` to the current user's home directory. Mirrors the helper
|
|
52
|
+
* in board-writer.mjs (a shared extraction is deferred to a later epic). Used to
|
|
53
|
+
* normalise config-declared cross-repo paths that may begin with `~/`.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} p
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function expandHome(p) {
|
|
59
|
+
if (typeof p !== 'string' || p.length === 0) return p;
|
|
60
|
+
if (p === '~') return os.homedir();
|
|
61
|
+
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
|
|
62
|
+
return p;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Derive free/busy status for a single repo from its session.lock lease.
|
|
67
|
+
* Mirrors board-writer.mjs collectRows semantics exactly:
|
|
68
|
+
* - live lock → in-progress (busy)
|
|
69
|
+
* - dead lock → force-closed (busy)
|
|
70
|
+
* - no lock → frei (free)
|
|
71
|
+
*
|
|
72
|
+
* @param {string} repoRoot — absolute repo path.
|
|
73
|
+
* @param {number} nowMs — clock seam (ms).
|
|
74
|
+
* @param {object} resolveDeps
|
|
75
|
+
* @param {Function} resolveDeps.readLock — readLock({repoRoot}) → lock|null.
|
|
76
|
+
* @param {Function} resolveDeps.isLockLive — isLockLive(lock, nowMs) → boolean.
|
|
77
|
+
* @returns {Candidate}
|
|
78
|
+
*/
|
|
79
|
+
function resolveCandidate(repoRoot, nowMs, { readLock: readLockFn, isLockLive: isLockLiveFn }) {
|
|
80
|
+
const lock = readLockFn({ repoRoot });
|
|
81
|
+
const live = !!(lock && isLockLiveFn(lock, nowMs));
|
|
82
|
+
|
|
83
|
+
let status;
|
|
84
|
+
let free;
|
|
85
|
+
if (live) {
|
|
86
|
+
status = STATUS_IN_PROGRESS;
|
|
87
|
+
free = false;
|
|
88
|
+
} else if (lock) {
|
|
89
|
+
// Lock present but dead lease (heartbeat older than ttl) — busy, not free.
|
|
90
|
+
status = STATUS_FORCE_CLOSED;
|
|
91
|
+
free = false;
|
|
92
|
+
} else {
|
|
93
|
+
status = STATUS_FREI;
|
|
94
|
+
free = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Fields read straight off the raw lock (null when there is no lock).
|
|
98
|
+
const heartbeat = lock ? (lock.last_heartbeat ?? null) : null;
|
|
99
|
+
const sessionId = lock ? (lock.semantic_session_id ?? lock.session_id ?? null) : null;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
repoRoot,
|
|
103
|
+
repoName: path.basename(repoRoot),
|
|
104
|
+
free,
|
|
105
|
+
status,
|
|
106
|
+
heartbeat,
|
|
107
|
+
sessionId,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Test whether a child path is a git repo. A child is a repo iff `<child>/.git`
|
|
113
|
+
* exists as a directory OR a file (the file form covers worktrees + submodules,
|
|
114
|
+
* whose `.git` is a `gitdir:` pointer file rather than a directory).
|
|
115
|
+
*
|
|
116
|
+
* @param {string} childAbs — absolute child path.
|
|
117
|
+
* @param {Function} existsSyncFn — injected existsSync.
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
function isGitRepo(childAbs, existsSyncFn) {
|
|
121
|
+
return existsSyncFn(path.join(childAbs, '.git'));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Enumerate candidate repos from a starting directory and resolve each as
|
|
126
|
+
* free or busy via its local lease.
|
|
127
|
+
*
|
|
128
|
+
* Algorithm:
|
|
129
|
+
* 1. Scan the IMMEDIATE children (one level deep) of `startDir` that are git
|
|
130
|
+
* repos (a child is a repo iff `<child>/.git` exists — dir or file).
|
|
131
|
+
* 2. Drop any child failing the confinement guard
|
|
132
|
+
* (`validatePathInsideProject(childAbs, startDir)`).
|
|
133
|
+
* 3. OPTIONAL secondary source: union with `getCrossRepoProjects()`
|
|
134
|
+
* config-declared paths (leading `~/` expanded, then confinement-filtered),
|
|
135
|
+
* deduped by `path.resolve()`. Additive, applied AFTER the FS scan.
|
|
136
|
+
* 4. Resolve free/busy per repo from its `session.lock` lease.
|
|
137
|
+
*
|
|
138
|
+
* ALL repos are returned (busy ones LISTED, not dropped — downstream rank.mjs
|
|
139
|
+
* filters). Returns a plain serialisable {@link Candidate}[].
|
|
140
|
+
*
|
|
141
|
+
* @param {object} [opts]
|
|
142
|
+
* @param {string} [opts.startDir] — scan root; defaults to {@link getConfinementRoot}().
|
|
143
|
+
* @param {number} [opts.now] — clock seam in ms; defaults to Date.now().
|
|
144
|
+
* @param {object} [opts.deps] — dependency-injection seam (Wave-4 testability).
|
|
145
|
+
* @param {Function} [opts.deps.readdirSync] — node:fs readdirSync.
|
|
146
|
+
* @param {Function} [opts.deps.existsSync] — node:fs existsSync.
|
|
147
|
+
* @param {Function} [opts.deps.readLock] — session-lock readLock.
|
|
148
|
+
* @param {Function} [opts.deps.isLockLive] — session-lock isLockLive.
|
|
149
|
+
* @param {Function} [opts.deps.getCrossRepoProjects] — config-declared paths accessor.
|
|
150
|
+
* @param {Function} [opts.deps.validatePathInsideProject] — confinement guard.
|
|
151
|
+
* @param {Function} [opts.deps.now] — () => ms (overridden by opts.now when set).
|
|
152
|
+
* @returns {Promise<Candidate[]>}
|
|
153
|
+
*/
|
|
154
|
+
export async function enumerateCandidates({ startDir, now, deps } = {}) {
|
|
155
|
+
const d = deps ?? {};
|
|
156
|
+
const readdirSyncFn = d.readdirSync ?? readdirSync;
|
|
157
|
+
const existsSyncFn = d.existsSync ?? existsSync;
|
|
158
|
+
const readLockFn = d.readLock ?? readLock;
|
|
159
|
+
const isLockLiveFn = d.isLockLive ?? isLockLive;
|
|
160
|
+
const getCrossRepoProjectsFn = d.getCrossRepoProjects ?? getCrossRepoProjects;
|
|
161
|
+
const validatePathInsideProjectFn = d.validatePathInsideProject ?? validatePathInsideProject;
|
|
162
|
+
const nowFn = d.now ?? Date.now;
|
|
163
|
+
|
|
164
|
+
const root = (typeof startDir === 'string' && startDir.length > 0)
|
|
165
|
+
? startDir
|
|
166
|
+
: getConfinementRoot();
|
|
167
|
+
const nowMs = typeof now === 'number' ? now : nowFn();
|
|
168
|
+
|
|
169
|
+
// Dedup set keyed by resolved absolute path; preserves first-seen ordering
|
|
170
|
+
// (FS-scan repos first, config-declared additions after).
|
|
171
|
+
const seen = new Set();
|
|
172
|
+
/** @type {string[]} */
|
|
173
|
+
const repoPaths = [];
|
|
174
|
+
|
|
175
|
+
const addRepo = (absPath) => {
|
|
176
|
+
const resolved = path.resolve(absPath);
|
|
177
|
+
if (seen.has(resolved)) return;
|
|
178
|
+
seen.add(resolved);
|
|
179
|
+
repoPaths.push(resolved);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// ── 1+2. FS scan of immediate children, confinement-guarded. ──
|
|
183
|
+
let entries;
|
|
184
|
+
try {
|
|
185
|
+
entries = readdirSyncFn(root, { withFileTypes: true });
|
|
186
|
+
} catch {
|
|
187
|
+
// Unreadable/absent startDir → no FS-scanned repos. The config-declared
|
|
188
|
+
// secondary source below may still contribute.
|
|
189
|
+
entries = [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const entry of entries) {
|
|
193
|
+
// Only directories can be repos. Dirent.isDirectory() guards against files,
|
|
194
|
+
// sockets, etc. A stubbed entry may not implement isDirectory — fall back
|
|
195
|
+
// to treating it as a directory candidate (existsSync('.git') gates anyway).
|
|
196
|
+
const isDir = typeof entry?.isDirectory === 'function' ? entry.isDirectory() : true;
|
|
197
|
+
if (!isDir) continue;
|
|
198
|
+
|
|
199
|
+
const childAbs = path.join(root, entry.name);
|
|
200
|
+
if (!isGitRepo(childAbs, existsSyncFn)) continue;
|
|
201
|
+
|
|
202
|
+
// Confinement guard: drop anything not strictly inside startDir.
|
|
203
|
+
const guard = validatePathInsideProjectFn(childAbs, root);
|
|
204
|
+
if (!guard || guard.ok !== true) continue;
|
|
205
|
+
|
|
206
|
+
addRepo(childAbs);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── 3. Optional secondary source: config-declared cross-repo projects. ──
|
|
210
|
+
let declared;
|
|
211
|
+
try {
|
|
212
|
+
declared = await getCrossRepoProjectsFn();
|
|
213
|
+
} catch {
|
|
214
|
+
declared = [];
|
|
215
|
+
}
|
|
216
|
+
if (Array.isArray(declared)) {
|
|
217
|
+
for (const raw of declared) {
|
|
218
|
+
if (typeof raw !== 'string' || raw.length === 0) continue;
|
|
219
|
+
const expanded = expandHome(raw);
|
|
220
|
+
const abs = path.resolve(expanded);
|
|
221
|
+
// Confinement-filter against the same root as the FS scan.
|
|
222
|
+
const guard = validatePathInsideProjectFn(abs, root);
|
|
223
|
+
if (!guard || guard.ok !== true) continue;
|
|
224
|
+
addRepo(abs);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── 4. Resolve free/busy per repo. ──
|
|
229
|
+
return repoPaths.map((repoRoot) =>
|
|
230
|
+
resolveCandidate(repoRoot, nowMs, { readLock: readLockFn, isLockLive: isLockLiveFn }),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Filter a candidate list down to the free ones (no live lease).
|
|
236
|
+
*
|
|
237
|
+
* @param {Candidate[]} candidates
|
|
238
|
+
* @returns {Candidate[]}
|
|
239
|
+
*/
|
|
240
|
+
export function freeCandidates(candidates) {
|
|
241
|
+
if (!Array.isArray(candidates)) return [];
|
|
242
|
+
return candidates.filter((c) => c && c.free === true);
|
|
243
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rank.mjs — autopilot dispatcher ranking scorer (Epic #673 P2, issue #677).
|
|
3
|
+
*
|
|
4
|
+
* Ranks FREE candidate repos so the cross-repo dispatcher can recommend the
|
|
5
|
+
* single most worthwhile one to work on next. Combines three signals into a
|
|
6
|
+
* multiplicative score:
|
|
7
|
+
*
|
|
8
|
+
* priority × staleness × readiness
|
|
9
|
+
*
|
|
10
|
+
* - PRIORITY — open-backlog severity (critical/high counts) from
|
|
11
|
+
* `scanBacklog` (scripts/lib/backlog-scan.mjs). null (glab/gh missing or
|
|
12
|
+
* timeout) ⇒ neutral fallback (1) + a warning. NEVER blocks.
|
|
13
|
+
* - STALENESS — days since the candidate's last completed session. Older =
|
|
14
|
+
* more worthwhile (you have not touched it in a while). No prior sessions ⇒
|
|
15
|
+
* maximally stale.
|
|
16
|
+
* - READINESS — CI status (red repos are worth less right now) × host
|
|
17
|
+
* resource verdict (a degraded/critical host de-prioritises everything
|
|
18
|
+
* equally; it is a host-level signal, fetched once and shared).
|
|
19
|
+
*
|
|
20
|
+
* Design contract (PRD §2 P2.3, §3 FA-2, §4):
|
|
21
|
+
* - `scoreCandidate` is a PURE, deterministic, documented formula — no I/O.
|
|
22
|
+
* - `rankCandidates` does all I/O through injectable `deps` so Wave-4 tests
|
|
23
|
+
* are deterministic. Default `deps` wires the real signal sources.
|
|
24
|
+
* - GLAB/GH FALLBACK is mandatory: a null priority is ranked on
|
|
25
|
+
* staleness × readiness only, with a human-readable warning. Never throws,
|
|
26
|
+
* never blocks.
|
|
27
|
+
* - All returns are plain serialisable objects (CLI wrapper lands in #678).
|
|
28
|
+
*
|
|
29
|
+
* @typedef {{ repoRoot: string, repoName: string, free: boolean, status: 'frei'|'in-progress'|'force-closed', heartbeat: string|null, sessionId: string|null }} Candidate
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { readFileSync } from 'node:fs';
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
|
|
35
|
+
import { scanBacklog } from '../backlog-scan.mjs';
|
|
36
|
+
import { checkCiStatus as realCheckCiStatus } from '../ci-status-banner.mjs';
|
|
37
|
+
import { probe as realProbe, evaluate as realEvaluate } from '../resource-probe.mjs';
|
|
38
|
+
|
|
39
|
+
/** Staleness cap (days). Beyond this, additional age does not raise the score. */
|
|
40
|
+
export const STALENESS_CAP_DAYS = 90;
|
|
41
|
+
|
|
42
|
+
/** Day length in ms — shared age-in-days idiom (mirrors backlog-scan ageDays). */
|
|
43
|
+
const MS_PER_DAY = 86_400_000;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Canonical resource-threshold defaults (mirrors
|
|
47
|
+
* scripts/lib/config/vault-integration.mjs `_parseResourceThresholds`).
|
|
48
|
+
* Used by the default `resourceVerdict` dep so ranking works standalone
|
|
49
|
+
* without a parsed Session Config in hand.
|
|
50
|
+
*/
|
|
51
|
+
const DEFAULT_RESOURCE_THRESHOLDS = {
|
|
52
|
+
'ram-free-min-gb': 4,
|
|
53
|
+
'ram-free-critical-gb': 2,
|
|
54
|
+
'cpu-load-max-pct': 80,
|
|
55
|
+
'concurrent-sessions-warn': 5,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// PURE core
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Score a single candidate from its gathered signals. Pure + deterministic.
|
|
64
|
+
*
|
|
65
|
+
* FORMULA (multiplicative — higher = more worth working on):
|
|
66
|
+
*
|
|
67
|
+
* priorityScore = priority
|
|
68
|
+
* ? 1 + 2·criticalCount + 1·highCount
|
|
69
|
+
* : 1 // null ⇒ neutral fallback
|
|
70
|
+
* stalenessScore = 1 + min(staleDays, 90) / 30 // older = higher, capped
|
|
71
|
+
* readinessScore = ciFactor · resourceFactor
|
|
72
|
+
* ciFactor = ciStatus === 'red' ? 0.25 : 1
|
|
73
|
+
* // null / 'unknown' / 'green' are all non-blocking (1)
|
|
74
|
+
* resourceFactor = critical ? 0.25 : degraded ? 0.6 : 1
|
|
75
|
+
* // 'green' / 'warn' ⇒ 1
|
|
76
|
+
*
|
|
77
|
+
* score = priorityScore · stalenessScore · readinessScore
|
|
78
|
+
*
|
|
79
|
+
* Rationale:
|
|
80
|
+
* - A candidate with no backlog signal is neither rewarded nor punished — it
|
|
81
|
+
* competes on staleness × readiness alone (the glab/gh fallback).
|
|
82
|
+
* - Staleness is bounded so an ancient repo cannot dominate purely on age.
|
|
83
|
+
* - Readiness only ever DAMPENS (factors ≤ 1): a red CI or a strained host
|
|
84
|
+
* makes a repo less attractive right now, but never negative.
|
|
85
|
+
*
|
|
86
|
+
* @param {{
|
|
87
|
+
* priority: { criticalCount: number, highCount: number } | null,
|
|
88
|
+
* staleDays: number,
|
|
89
|
+
* readiness: {
|
|
90
|
+
* ciStatus: 'green'|'red'|'unknown'|null,
|
|
91
|
+
* resourceVerdict: 'green'|'warn'|'degraded'|'critical',
|
|
92
|
+
* },
|
|
93
|
+
* }} signals
|
|
94
|
+
* @returns {number} score (≥ 0; higher = more worthwhile)
|
|
95
|
+
*/
|
|
96
|
+
export function scoreCandidate(signals) {
|
|
97
|
+
const { priority, staleDays, readiness } = signals;
|
|
98
|
+
|
|
99
|
+
const priorityScore = priority
|
|
100
|
+
? 1 + 2 * (priority.criticalCount || 0) + 1 * (priority.highCount || 0)
|
|
101
|
+
: 1;
|
|
102
|
+
|
|
103
|
+
// Guard against NaN/negative staleDays — clamp to [0, cap].
|
|
104
|
+
const safeStaleDays =
|
|
105
|
+
typeof staleDays === 'number' && Number.isFinite(staleDays) && staleDays > 0
|
|
106
|
+
? staleDays
|
|
107
|
+
: 0;
|
|
108
|
+
const stalenessScore = 1 + Math.min(safeStaleDays, STALENESS_CAP_DAYS) / 30;
|
|
109
|
+
|
|
110
|
+
const ciStatus = readiness?.ciStatus ?? null;
|
|
111
|
+
const resourceVerdict = readiness?.resourceVerdict ?? 'green';
|
|
112
|
+
|
|
113
|
+
const ciFactor = ciStatus === 'red' ? 0.25 : 1;
|
|
114
|
+
const resourceFactor =
|
|
115
|
+
resourceVerdict === 'critical'
|
|
116
|
+
? 0.25
|
|
117
|
+
: resourceVerdict === 'degraded'
|
|
118
|
+
? 0.6
|
|
119
|
+
: 1;
|
|
120
|
+
const readinessScore = ciFactor * resourceFactor;
|
|
121
|
+
|
|
122
|
+
return priorityScore * stalenessScore * readinessScore;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Default signal-source implementations (the real wiring)
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Default PRIORITY source: run `scanBacklog` against the candidate repo.
|
|
131
|
+
*
|
|
132
|
+
* `scanBacklog` keys off the CURRENT process git remote, so to score a
|
|
133
|
+
* DIFFERENT repo we run it with that repo as the working directory and restore
|
|
134
|
+
* cwd afterwards. Returns the scanBacklog summary or null (CLI missing /
|
|
135
|
+
* timeout / parse-fail). Never throws.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} repoRoot
|
|
138
|
+
* @param {number} nowMs
|
|
139
|
+
* @returns {Promise<null | {criticalCount: number, highCount: number}>}
|
|
140
|
+
*/
|
|
141
|
+
async function defaultFetchPriority(repoRoot, nowMs) {
|
|
142
|
+
const prevCwd = process.cwd();
|
|
143
|
+
try {
|
|
144
|
+
process.chdir(repoRoot);
|
|
145
|
+
return await scanBacklog({ nowMs });
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
} finally {
|
|
149
|
+
try {
|
|
150
|
+
process.chdir(prevCwd);
|
|
151
|
+
} catch {
|
|
152
|
+
/* best-effort cwd restore */
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Default STALENESS source: read `<repoRoot>/.orchestrator/metrics/sessions.jsonl`,
|
|
159
|
+
* take the LAST record, and compute days since `completed_at` (fallback
|
|
160
|
+
* `started_at`). No file / no parsable record / no timestamp ⇒
|
|
161
|
+
* `STALENESS_CAP_DAYS` (treat as maximally stale = most worthwhile).
|
|
162
|
+
*
|
|
163
|
+
* @param {string} repoRoot
|
|
164
|
+
* @param {number} nowMs
|
|
165
|
+
* @returns {Promise<number>} days since last session (≥ 0)
|
|
166
|
+
*/
|
|
167
|
+
async function defaultStaleDaysFor(repoRoot, nowMs) {
|
|
168
|
+
try {
|
|
169
|
+
const file = path.join(repoRoot, '.orchestrator', 'metrics', 'sessions.jsonl');
|
|
170
|
+
const raw = readFileSync(file, 'utf8');
|
|
171
|
+
const lines = raw.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
172
|
+
if (lines.length === 0) return STALENESS_CAP_DAYS;
|
|
173
|
+
|
|
174
|
+
// Last non-empty line = most recent session record.
|
|
175
|
+
let last = null;
|
|
176
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
177
|
+
try {
|
|
178
|
+
last = JSON.parse(lines[i]);
|
|
179
|
+
break;
|
|
180
|
+
} catch {
|
|
181
|
+
// Skip a corrupt trailing line and try the previous one.
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!last || typeof last !== 'object') return STALENESS_CAP_DAYS;
|
|
185
|
+
|
|
186
|
+
const iso = last.completed_at || last.started_at || null;
|
|
187
|
+
if (!iso || typeof iso !== 'string') return STALENESS_CAP_DAYS;
|
|
188
|
+
const t = Date.parse(iso);
|
|
189
|
+
if (Number.isNaN(t)) return STALENESS_CAP_DAYS;
|
|
190
|
+
|
|
191
|
+
const days = (nowMs - t) / MS_PER_DAY;
|
|
192
|
+
return days > 0 ? days : 0;
|
|
193
|
+
} catch {
|
|
194
|
+
// No sessions file (or unreadable) ⇒ never worked on ⇒ maximally stale.
|
|
195
|
+
return STALENESS_CAP_DAYS;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Default READINESS (CI) source: thin wrapper over `checkCiStatus`.
|
|
201
|
+
* Returns the 'green'|'red'|'unknown' status string, or null (no-op).
|
|
202
|
+
* null / 'unknown' are treated as non-blocking by `scoreCandidate`.
|
|
203
|
+
*
|
|
204
|
+
* @param {{ repoRoot: string }} args
|
|
205
|
+
* @returns {Promise<'green'|'red'|'unknown'|null>}
|
|
206
|
+
*/
|
|
207
|
+
async function defaultCheckCiStatus({ repoRoot }) {
|
|
208
|
+
try {
|
|
209
|
+
const result = await realCheckCiStatus({ repoRoot });
|
|
210
|
+
return result && typeof result.status === 'string' ? result.status : null;
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Default READINESS (resource) source: probe the host ONCE and evaluate against
|
|
218
|
+
* the canonical default thresholds. This is a HOST-level signal (identical for
|
|
219
|
+
* every candidate), so `rankCandidates` calls it exactly once per ranking run.
|
|
220
|
+
* Falls back to 'green' (non-dampening) on any failure.
|
|
221
|
+
*
|
|
222
|
+
* @returns {Promise<'green'|'warn'|'degraded'|'critical'>}
|
|
223
|
+
*/
|
|
224
|
+
async function defaultResourceVerdict() {
|
|
225
|
+
try {
|
|
226
|
+
const snapshot = await realProbe();
|
|
227
|
+
const { verdict } = realEvaluate(snapshot, DEFAULT_RESOURCE_THRESHOLDS);
|
|
228
|
+
return verdict || 'green';
|
|
229
|
+
} catch {
|
|
230
|
+
return 'green';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Build the default `deps` object. Exposed for tests that want to override a
|
|
236
|
+
* single dep while keeping the real ones for the rest.
|
|
237
|
+
*
|
|
238
|
+
* @returns {{
|
|
239
|
+
* fetchPriority: (repoRoot: string, nowMs: number) => Promise<null | {criticalCount: number, highCount: number}>,
|
|
240
|
+
* staleDaysFor: (repoRoot: string, nowMs: number) => Promise<number>,
|
|
241
|
+
* checkCiStatus: (args: { repoRoot: string }) => Promise<'green'|'red'|'unknown'|null>,
|
|
242
|
+
* resourceVerdict: () => Promise<'green'|'warn'|'degraded'|'critical'>,
|
|
243
|
+
* }}
|
|
244
|
+
*/
|
|
245
|
+
export function defaultDeps() {
|
|
246
|
+
return {
|
|
247
|
+
fetchPriority: defaultFetchPriority,
|
|
248
|
+
staleDaysFor: defaultStaleDaysFor,
|
|
249
|
+
checkCiStatus: defaultCheckCiStatus,
|
|
250
|
+
resourceVerdict: defaultResourceVerdict,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Orchestration
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Rank a list of FREE candidate repos (the dispatcher pre-filters
|
|
260
|
+
* `free === true`). Gathers signals via injectable `deps`, scores each
|
|
261
|
+
* candidate with the pure `scoreCandidate` formula, and sorts DESC by score.
|
|
262
|
+
*
|
|
263
|
+
* Sort is deterministic: score DESC, then staleDays DESC, then repoName ASC.
|
|
264
|
+
* `ranked[0]` is the top recommendation.
|
|
265
|
+
*
|
|
266
|
+
* GLAB/GH FALLBACK: when `deps.fetchPriority` returns null (CLI missing /
|
|
267
|
+
* timeout), the candidate is scored with `priority: null` (neutral 1) AND a
|
|
268
|
+
* warning is pushed. Never throws, never blocks.
|
|
269
|
+
*
|
|
270
|
+
* The host resource verdict is fetched ONCE (host-level signal) and shared
|
|
271
|
+
* across all candidates.
|
|
272
|
+
*
|
|
273
|
+
* @param {Candidate[]} freeCandidates
|
|
274
|
+
* @param {{
|
|
275
|
+
* now?: number,
|
|
276
|
+
* deps?: Partial<ReturnType<typeof defaultDeps>>,
|
|
277
|
+
* }} [opts]
|
|
278
|
+
* @returns {Promise<{
|
|
279
|
+
* ranked: Array<{ candidate: Candidate, score: number, signals: {
|
|
280
|
+
* priority: { criticalCount: number, highCount: number } | null,
|
|
281
|
+
* staleDays: number,
|
|
282
|
+
* readiness: { ciStatus: 'green'|'red'|'unknown'|null, resourceVerdict: 'green'|'warn'|'degraded'|'critical' },
|
|
283
|
+
* } }>,
|
|
284
|
+
* warnings: string[],
|
|
285
|
+
* }>}
|
|
286
|
+
*/
|
|
287
|
+
export async function rankCandidates(freeCandidates, opts = {}) {
|
|
288
|
+
const candidates = Array.isArray(freeCandidates) ? freeCandidates : [];
|
|
289
|
+
const nowMs = typeof opts.now === 'number' ? opts.now : Date.now();
|
|
290
|
+
const deps = { ...defaultDeps(), ...(opts.deps || {}) };
|
|
291
|
+
const warnings = [];
|
|
292
|
+
|
|
293
|
+
// Host-level resource verdict — fetched ONCE, shared across candidates.
|
|
294
|
+
let resourceVerdict;
|
|
295
|
+
try {
|
|
296
|
+
resourceVerdict = (await deps.resourceVerdict()) || 'green';
|
|
297
|
+
} catch {
|
|
298
|
+
// Non-blocking: a failed host probe defaults to non-dampening 'green'.
|
|
299
|
+
resourceVerdict = 'green';
|
|
300
|
+
warnings.push('resource verdict unavailable (host probe failed) — readiness scored without resource dampening');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const rows = [];
|
|
304
|
+
for (const candidate of candidates) {
|
|
305
|
+
const repoRoot = candidate?.repoRoot;
|
|
306
|
+
const repoName = candidate?.repoName ?? repoRoot ?? '<unknown>';
|
|
307
|
+
|
|
308
|
+
// PRIORITY (with glab/gh fallback).
|
|
309
|
+
let priority;
|
|
310
|
+
try {
|
|
311
|
+
const raw = await deps.fetchPriority(repoRoot, nowMs);
|
|
312
|
+
priority =
|
|
313
|
+
raw && typeof raw === 'object'
|
|
314
|
+
? { criticalCount: raw.criticalCount || 0, highCount: raw.highCount || 0 }
|
|
315
|
+
: null;
|
|
316
|
+
} catch {
|
|
317
|
+
priority = null;
|
|
318
|
+
}
|
|
319
|
+
if (priority === null) {
|
|
320
|
+
warnings.push(
|
|
321
|
+
`priority unavailable for ${repoName} (glab/gh missing or timeout) — ranked on staleness×readiness only`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// STALENESS.
|
|
326
|
+
let staleDays;
|
|
327
|
+
try {
|
|
328
|
+
const d = await deps.staleDaysFor(repoRoot, nowMs);
|
|
329
|
+
staleDays = typeof d === 'number' && Number.isFinite(d) && d >= 0 ? d : STALENESS_CAP_DAYS;
|
|
330
|
+
} catch {
|
|
331
|
+
staleDays = STALENESS_CAP_DAYS;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// READINESS — CI status.
|
|
335
|
+
let ciStatus;
|
|
336
|
+
try {
|
|
337
|
+
ciStatus = await deps.checkCiStatus({ repoRoot });
|
|
338
|
+
} catch {
|
|
339
|
+
ciStatus = null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const signals = {
|
|
343
|
+
priority,
|
|
344
|
+
staleDays,
|
|
345
|
+
readiness: { ciStatus, resourceVerdict },
|
|
346
|
+
};
|
|
347
|
+
const score = scoreCandidate(signals);
|
|
348
|
+
|
|
349
|
+
rows.push({ candidate, score, signals });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
rows.sort((a, b) => {
|
|
353
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
354
|
+
if (b.signals.staleDays !== a.signals.staleDays) {
|
|
355
|
+
return b.signals.staleDays - a.signals.staleDays;
|
|
356
|
+
}
|
|
357
|
+
const an = a.candidate?.repoName ?? '';
|
|
358
|
+
const bn = b.candidate?.repoName ?? '';
|
|
359
|
+
return an < bn ? -1 : an > bn ? 1 : 0;
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
return { ranked: rows, warnings };
|
|
363
|
+
}
|