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,1192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* walker.mjs — Sunset-review surface walker (issue #444).
|
|
4
|
+
*
|
|
5
|
+
* READ-ONLY analysis of the plugin's skill/agent/command surface. Combines
|
|
6
|
+
* agent-dispatch telemetry (subagents.jsonl) with static reference scanning to
|
|
7
|
+
* classify each surface item into a 4-tier verdict:
|
|
8
|
+
*
|
|
9
|
+
* Active — clearly in use (dispatch, command-linkage, or prose refs)
|
|
10
|
+
* Investigate — default-safe; conflicting signals OR low telemetry confidence
|
|
11
|
+
* Demote — near-zero use (single dispatch, single cross-ref)
|
|
12
|
+
* Retire — provably cold (zero dispatch AND zero non-boilerplate refs)
|
|
13
|
+
*
|
|
14
|
+
* IMPORTANT GUARDRAIL (Discovery, grep-verified):
|
|
15
|
+
* Telemetry only spans ~18 days, but the default window is 90. When
|
|
16
|
+
* coverageDays < windowDays, EVERY Retire verdict is downgraded to
|
|
17
|
+
* Investigate and meta.lowConfidence is set. Retiring on <window data is
|
|
18
|
+
* unsafe.
|
|
19
|
+
*
|
|
20
|
+
* Telemetry facts the walker is built to respect:
|
|
21
|
+
* - subagents.jsonl agent_type is reliable ONLY on event==="start"; all nulls
|
|
22
|
+
* are on "stop" events. The walker counts START events only.
|
|
23
|
+
* - Skill-invocation telemetry NOW EXISTS in skill-invocations.jsonl (L1
|
|
24
|
+
* telemetry, epic #645). Skills are assessed by real selection counts from
|
|
25
|
+
* that file. Static reference scanning remains a SUPPLEMENTARY signal.
|
|
26
|
+
* - NO command-invocation telemetry exists. Commands are assessed via static
|
|
27
|
+
* reference scanning only.
|
|
28
|
+
*
|
|
29
|
+
* This module is pure-ish and side-effect free: it reads files but writes
|
|
30
|
+
* none, holds no mutable runtime state, and exposes no concurrency surface.
|
|
31
|
+
* The consuming SKILL writes the last-run timestamp — NOT the walker.
|
|
32
|
+
*
|
|
33
|
+
* JSON-first CLI per .claude/rules/cli-design.md:
|
|
34
|
+
* node scripts/lib/sunset/walker.mjs [--json] [--window-days N] [--kind K]
|
|
35
|
+
* exit 0 = walk done (cold findings are exit 0, NOT an error)
|
|
36
|
+
* exit 1 = bad args / surface dir missing
|
|
37
|
+
* exit 2 = system error
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
41
|
+
import path from 'node:path';
|
|
42
|
+
import { pathToFileURL } from 'node:url';
|
|
43
|
+
import { normalizeSkillInvocation } from '../skill-invocations-schema.mjs';
|
|
44
|
+
import { normalizeSkillJudgment } from '../skill-judgments-schema.mjs';
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Named threshold constants
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/** Default sunset-review window in days. */
|
|
51
|
+
export const DEFAULT_WINDOW_DAYS = 90;
|
|
52
|
+
|
|
53
|
+
/** Agent dispatch count strictly greater than this floor ⇒ Active. */
|
|
54
|
+
export const ACTIVE_DISPATCH_FLOOR = 1;
|
|
55
|
+
|
|
56
|
+
/** Agent dispatch count in (0, this ceiling] ⇒ Demote (near-zero). */
|
|
57
|
+
export const DEMOTE_DISPATCH_CEILING = 1;
|
|
58
|
+
|
|
59
|
+
/** Non-boilerplate prose/strict refs at or above this floor ⇒ Active. */
|
|
60
|
+
export const ACTIVE_REF_FLOOR = 2;
|
|
61
|
+
|
|
62
|
+
/** Default agent-type prefix stripped from telemetry. */
|
|
63
|
+
const AGENT_TYPE_PREFIX = 'session-orchestrator:';
|
|
64
|
+
|
|
65
|
+
/** Surface directories excluded from enumeration. */
|
|
66
|
+
const EXCLUDED_SKILL_DIRS = new Set(['_shared']);
|
|
67
|
+
const EXCLUDED_AGENT_FILES = new Set(['AGENTS.md']);
|
|
68
|
+
|
|
69
|
+
/** Directories scanned for static references. */
|
|
70
|
+
const SCAN_DIRS = ['skills', 'agents', 'commands', 'hooks', 'scripts'];
|
|
71
|
+
|
|
72
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Surface enumeration
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Enumerate the plugin's skill/agent/command surface.
|
|
80
|
+
* Excludes `skills/_shared` (shared helpers, not a skill) and
|
|
81
|
+
* `agents/AGENTS.md` (authoring spec, not an agent).
|
|
82
|
+
*
|
|
83
|
+
* @param {string} repoRoot
|
|
84
|
+
* @returns {{skills: string[], agents: string[], commands: string[]}}
|
|
85
|
+
*/
|
|
86
|
+
export function enumerateSurface(repoRoot) {
|
|
87
|
+
const skills = [];
|
|
88
|
+
const skillsDir = path.join(repoRoot, 'skills');
|
|
89
|
+
if (existsSync(skillsDir)) {
|
|
90
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
91
|
+
if (!entry.isDirectory()) continue;
|
|
92
|
+
if (EXCLUDED_SKILL_DIRS.has(entry.name)) continue;
|
|
93
|
+
// A skill dir must contain a SKILL.md to count as a skill.
|
|
94
|
+
if (existsSync(path.join(skillsDir, entry.name, 'SKILL.md'))) {
|
|
95
|
+
skills.push(entry.name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const agents = [];
|
|
101
|
+
const agentsDir = path.join(repoRoot, 'agents');
|
|
102
|
+
if (existsSync(agentsDir)) {
|
|
103
|
+
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
104
|
+
if (!entry.isFile()) continue;
|
|
105
|
+
if (!entry.name.endsWith('.md')) continue;
|
|
106
|
+
if (EXCLUDED_AGENT_FILES.has(entry.name)) continue;
|
|
107
|
+
agents.push(entry.name.replace(/\.md$/, ''));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const commands = [];
|
|
112
|
+
const commandsDir = path.join(repoRoot, 'commands');
|
|
113
|
+
if (existsSync(commandsDir)) {
|
|
114
|
+
for (const entry of readdirSync(commandsDir, { withFileTypes: true })) {
|
|
115
|
+
if (!entry.isFile()) continue;
|
|
116
|
+
if (!entry.name.endsWith('.md')) continue;
|
|
117
|
+
commands.push(entry.name.replace(/\.md$/, ''));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
skills: skills.sort(),
|
|
123
|
+
agents: agents.sort(),
|
|
124
|
+
commands: commands.sort(),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Dispatch telemetry
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Read agent-dispatch counts from subagents.jsonl.
|
|
134
|
+
*
|
|
135
|
+
* Only `event === "start"` records are counted — `agent_type` is null on
|
|
136
|
+
* "stop" events, so a stop event must never mark an agent cold.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} subagentsJsonlPath
|
|
139
|
+
* @param {{windowDays: number, now?: number}} opts
|
|
140
|
+
* @returns {{
|
|
141
|
+
* byAgent: Map<string,{count:number,lastTs:string|null}>,
|
|
142
|
+
* earliestTs: string|null,
|
|
143
|
+
* latestTs: string|null,
|
|
144
|
+
* coverageDays: number
|
|
145
|
+
* }}
|
|
146
|
+
*/
|
|
147
|
+
export function readDispatchCounts(subagentsJsonlPath, { windowDays, now } = {}) {
|
|
148
|
+
const nowMs = typeof now === 'number' ? now : Date.now();
|
|
149
|
+
const windowStartMs = nowMs - (windowDays ?? DEFAULT_WINDOW_DAYS) * MS_PER_DAY;
|
|
150
|
+
|
|
151
|
+
/** @type {Map<string,{count:number,lastTs:string|null}>} */
|
|
152
|
+
const byAgent = new Map();
|
|
153
|
+
let earliestMs = null;
|
|
154
|
+
let latestMs = null;
|
|
155
|
+
|
|
156
|
+
if (!existsSync(subagentsJsonlPath)) {
|
|
157
|
+
return { byAgent, earliestTs: null, latestTs: null, coverageDays: 0 };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let raw;
|
|
161
|
+
try {
|
|
162
|
+
raw = readFileSync(subagentsJsonlPath, 'utf8');
|
|
163
|
+
} catch {
|
|
164
|
+
return { byAgent, earliestTs: null, latestTs: null, coverageDays: 0 };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const line of raw.split('\n')) {
|
|
168
|
+
const trimmed = line.trim();
|
|
169
|
+
if (trimmed === '') continue;
|
|
170
|
+
let rec;
|
|
171
|
+
try {
|
|
172
|
+
rec = JSON.parse(trimmed);
|
|
173
|
+
} catch {
|
|
174
|
+
// Malformed line — skip, never throw.
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!rec || typeof rec !== 'object') continue;
|
|
178
|
+
if (rec.event !== 'start') continue;
|
|
179
|
+
const ts = typeof rec.timestamp === 'string' ? rec.timestamp : null;
|
|
180
|
+
const tsMs = ts ? Date.parse(ts) : NaN;
|
|
181
|
+
|
|
182
|
+
// Track coverage envelope from ALL start events (regardless of window).
|
|
183
|
+
if (!Number.isNaN(tsMs)) {
|
|
184
|
+
if (earliestMs === null || tsMs < earliestMs) earliestMs = tsMs;
|
|
185
|
+
if (latestMs === null || tsMs > latestMs) latestMs = tsMs;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const rawType = typeof rec.agent_type === 'string' ? rec.agent_type : null;
|
|
189
|
+
if (!rawType) continue;
|
|
190
|
+
const name = rawType.startsWith(AGENT_TYPE_PREFIX)
|
|
191
|
+
? rawType.slice(AGENT_TYPE_PREFIX.length)
|
|
192
|
+
: rawType;
|
|
193
|
+
|
|
194
|
+
// Only count dispatches inside the window for the per-agent tally.
|
|
195
|
+
if (!Number.isNaN(tsMs) && tsMs < windowStartMs) continue;
|
|
196
|
+
|
|
197
|
+
const cur = byAgent.get(name) ?? { count: 0, lastTs: null };
|
|
198
|
+
cur.count += 1;
|
|
199
|
+
if (ts && (cur.lastTs === null || Date.parse(ts) > Date.parse(cur.lastTs))) {
|
|
200
|
+
cur.lastTs = ts;
|
|
201
|
+
}
|
|
202
|
+
byAgent.set(name, cur);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const coverageDays =
|
|
206
|
+
earliestMs === null ? 0 : Math.max(0, (nowMs - earliestMs) / MS_PER_DAY);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
byAgent,
|
|
210
|
+
earliestTs: earliestMs === null ? null : new Date(earliestMs).toISOString(),
|
|
211
|
+
latestTs: latestMs === null ? null : new Date(latestMs).toISOString(),
|
|
212
|
+
coverageDays,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Skill-invocation telemetry (L1, epic #645)
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Read skill-selection counts from skill-invocations.jsonl.
|
|
222
|
+
*
|
|
223
|
+
* Mirrors `readDispatchCounts` style: synchronous, window-aware, never throws.
|
|
224
|
+
* Counts only `event === "selected"` records inside the window.
|
|
225
|
+
*
|
|
226
|
+
* Uses `normalizeSkillInvocation` from skill-invocations-schema.mjs for
|
|
227
|
+
* consistent field defaulting, keeping parity with the async reader the schema
|
|
228
|
+
* module also provides.
|
|
229
|
+
*
|
|
230
|
+
* @param {string} invocationsPath
|
|
231
|
+
* @param {{windowDays: number, now?: number}} opts
|
|
232
|
+
* @returns {{
|
|
233
|
+
* bySkill: Map<string,{count:number,lastTs:string|null}>,
|
|
234
|
+
* earliestTs: string|null,
|
|
235
|
+
* latestTs: string|null,
|
|
236
|
+
* coverageDays: number
|
|
237
|
+
* }}
|
|
238
|
+
*/
|
|
239
|
+
export function readSkillInvocationCounts(invocationsPath, { windowDays, now } = {}) {
|
|
240
|
+
const nowMs = typeof now === 'number' ? now : Date.now();
|
|
241
|
+
const windowStartMs = nowMs - (windowDays ?? DEFAULT_WINDOW_DAYS) * MS_PER_DAY;
|
|
242
|
+
|
|
243
|
+
/** @type {Map<string,{count:number,lastTs:string|null}>} */
|
|
244
|
+
const bySkill = new Map();
|
|
245
|
+
let earliestMs = null;
|
|
246
|
+
let latestMs = null;
|
|
247
|
+
|
|
248
|
+
if (!existsSync(invocationsPath)) {
|
|
249
|
+
return { bySkill, earliestTs: null, latestTs: null, coverageDays: 0 };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let raw;
|
|
253
|
+
try {
|
|
254
|
+
raw = readFileSync(invocationsPath, 'utf8');
|
|
255
|
+
} catch {
|
|
256
|
+
return { bySkill, earliestTs: null, latestTs: null, coverageDays: 0 };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const line of raw.split('\n')) {
|
|
260
|
+
const trimmed = line.trim();
|
|
261
|
+
if (trimmed === '') continue;
|
|
262
|
+
let rec;
|
|
263
|
+
try {
|
|
264
|
+
rec = normalizeSkillInvocation(JSON.parse(trimmed));
|
|
265
|
+
} catch {
|
|
266
|
+
// Malformed line — skip, never throw.
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (!rec || typeof rec !== 'object') continue;
|
|
270
|
+
if (rec.event !== 'selected') continue;
|
|
271
|
+
|
|
272
|
+
const ts = typeof rec.timestamp === 'string' ? rec.timestamp : null;
|
|
273
|
+
const tsMs = ts ? Date.parse(ts) : NaN;
|
|
274
|
+
|
|
275
|
+
// Track coverage envelope from ALL selected events (regardless of window).
|
|
276
|
+
if (!Number.isNaN(tsMs)) {
|
|
277
|
+
if (earliestMs === null || tsMs < earliestMs) earliestMs = tsMs;
|
|
278
|
+
if (latestMs === null || tsMs > latestMs) latestMs = tsMs;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const skillName = typeof rec.skill === 'string' && rec.skill.trim() ? rec.skill.trim() : null;
|
|
282
|
+
if (!skillName) continue;
|
|
283
|
+
|
|
284
|
+
// Only count invocations inside the window for the per-skill tally.
|
|
285
|
+
if (!Number.isNaN(tsMs) && tsMs < windowStartMs) continue;
|
|
286
|
+
|
|
287
|
+
const cur = bySkill.get(skillName) ?? { count: 0, lastTs: null };
|
|
288
|
+
cur.count += 1;
|
|
289
|
+
if (ts && (cur.lastTs === null || Date.parse(ts) > Date.parse(cur.lastTs))) {
|
|
290
|
+
cur.lastTs = ts;
|
|
291
|
+
}
|
|
292
|
+
bySkill.set(skillName, cur);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const coverageDays =
|
|
296
|
+
earliestMs === null ? 0 : Math.max(0, (nowMs - earliestMs) / MS_PER_DAY);
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
bySkill,
|
|
300
|
+
earliestTs: earliestMs === null ? null : new Date(earliestMs).toISOString(),
|
|
301
|
+
latestTs: latestMs === null ? null : new Date(latestMs).toISOString(),
|
|
302
|
+
coverageDays,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
// Skill-judgment telemetry (L3, epic #645 — ADVISORY ONLY)
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Read skill-applied JUDGMENT counts from skill-judgments.jsonl (L3).
|
|
312
|
+
*
|
|
313
|
+
* Mirrors `readSkillInvocationCounts` style: synchronous, window-aware, never
|
|
314
|
+
* throws, existsSync-guarded. Counts only `event === "judged"` records inside
|
|
315
|
+
* the window and aggregates the tri-state applied/completed tallies per skill.
|
|
316
|
+
*
|
|
317
|
+
* Uses `normalizeSkillJudgment` from skill-judgments-schema.mjs for consistent
|
|
318
|
+
* field defaulting (advisory:true, session_id:null, schema_version).
|
|
319
|
+
*
|
|
320
|
+
* IMPORTANT (firewall): the data returned here is ADVISORY ONLY. The walker
|
|
321
|
+
* uses it to ANNOTATE a diagnosis or DOWNGRADE a verdict — never to escalate a
|
|
322
|
+
* skill toward Retire (see classifyItem #645 R9 advisory-only).
|
|
323
|
+
*
|
|
324
|
+
* @param {string} judgmentsPath
|
|
325
|
+
* @param {{windowDays: number, now?: number}} opts
|
|
326
|
+
* @returns {{
|
|
327
|
+
* bySkill: Map<string,{
|
|
328
|
+
* appliedYes:number, appliedNo:number, appliedUnknown:number,
|
|
329
|
+
* completedYes:number, completedNo:number, completedUnknown:number,
|
|
330
|
+
* total:number, lastTs:string|null
|
|
331
|
+
* }>,
|
|
332
|
+
* earliestTs: string|null,
|
|
333
|
+
* latestTs: string|null,
|
|
334
|
+
* coverageDays: number
|
|
335
|
+
* }}
|
|
336
|
+
*/
|
|
337
|
+
export function readSkillJudgmentCounts(judgmentsPath, { windowDays, now } = {}) {
|
|
338
|
+
const nowMs = typeof now === 'number' ? now : Date.now();
|
|
339
|
+
const windowStartMs = nowMs - (windowDays ?? DEFAULT_WINDOW_DAYS) * MS_PER_DAY;
|
|
340
|
+
|
|
341
|
+
/** @type {Map<string,{
|
|
342
|
+
* appliedYes:number, appliedNo:number, appliedUnknown:number,
|
|
343
|
+
* completedYes:number, completedNo:number, completedUnknown:number,
|
|
344
|
+
* total:number, lastTs:string|null
|
|
345
|
+
* }>} */
|
|
346
|
+
const bySkill = new Map();
|
|
347
|
+
let earliestMs = null;
|
|
348
|
+
let latestMs = null;
|
|
349
|
+
|
|
350
|
+
if (!existsSync(judgmentsPath)) {
|
|
351
|
+
return { bySkill, earliestTs: null, latestTs: null, coverageDays: 0 };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let raw;
|
|
355
|
+
try {
|
|
356
|
+
raw = readFileSync(judgmentsPath, 'utf8');
|
|
357
|
+
} catch {
|
|
358
|
+
return { bySkill, earliestTs: null, latestTs: null, coverageDays: 0 };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const line of raw.split('\n')) {
|
|
362
|
+
const trimmed = line.trim();
|
|
363
|
+
if (trimmed === '') continue;
|
|
364
|
+
let rec;
|
|
365
|
+
try {
|
|
366
|
+
rec = normalizeSkillJudgment(JSON.parse(trimmed));
|
|
367
|
+
} catch {
|
|
368
|
+
// Malformed line — skip, never throw.
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (!rec || typeof rec !== 'object') continue;
|
|
372
|
+
if (rec.event !== 'judged') continue;
|
|
373
|
+
|
|
374
|
+
const ts = typeof rec.timestamp === 'string' ? rec.timestamp : null;
|
|
375
|
+
const tsMs = ts ? Date.parse(ts) : NaN;
|
|
376
|
+
|
|
377
|
+
// Track coverage envelope from ALL judged events (regardless of window).
|
|
378
|
+
if (!Number.isNaN(tsMs)) {
|
|
379
|
+
if (earliestMs === null || tsMs < earliestMs) earliestMs = tsMs;
|
|
380
|
+
if (latestMs === null || tsMs > latestMs) latestMs = tsMs;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const skillName = typeof rec.skill === 'string' && rec.skill.trim() ? rec.skill.trim() : null;
|
|
384
|
+
if (!skillName) continue;
|
|
385
|
+
|
|
386
|
+
// Only count judgments inside the window for the per-skill tally.
|
|
387
|
+
if (!Number.isNaN(tsMs) && tsMs < windowStartMs) continue;
|
|
388
|
+
|
|
389
|
+
const cur = bySkill.get(skillName) ?? {
|
|
390
|
+
appliedYes: 0,
|
|
391
|
+
appliedNo: 0,
|
|
392
|
+
appliedUnknown: 0,
|
|
393
|
+
completedYes: 0,
|
|
394
|
+
completedNo: 0,
|
|
395
|
+
completedUnknown: 0,
|
|
396
|
+
total: 0,
|
|
397
|
+
lastTs: null,
|
|
398
|
+
};
|
|
399
|
+
cur.total += 1;
|
|
400
|
+
if (rec.applied === 'yes') cur.appliedYes += 1;
|
|
401
|
+
else if (rec.applied === 'no') cur.appliedNo += 1;
|
|
402
|
+
else cur.appliedUnknown += 1;
|
|
403
|
+
if (rec.completed === 'yes') cur.completedYes += 1;
|
|
404
|
+
else if (rec.completed === 'no') cur.completedNo += 1;
|
|
405
|
+
else cur.completedUnknown += 1;
|
|
406
|
+
if (ts && (cur.lastTs === null || Date.parse(ts) > Date.parse(cur.lastTs))) {
|
|
407
|
+
cur.lastTs = ts;
|
|
408
|
+
}
|
|
409
|
+
bySkill.set(skillName, cur);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const coverageDays =
|
|
413
|
+
earliestMs === null ? 0 : Math.max(0, (nowMs - earliestMs) / MS_PER_DAY);
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
bySkill,
|
|
417
|
+
earliestTs: earliestMs === null ? null : new Date(earliestMs).toISOString(),
|
|
418
|
+
latestTs: latestMs === null ? null : new Date(latestMs).toISOString(),
|
|
419
|
+
coverageDays,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
// Static reference scanning
|
|
425
|
+
// ---------------------------------------------------------------------------
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Recursively collect file paths under a directory (skipping node_modules and
|
|
429
|
+
* dot-directories). Returns absolute paths.
|
|
430
|
+
*
|
|
431
|
+
* @param {string} dir
|
|
432
|
+
* @returns {string[]}
|
|
433
|
+
*/
|
|
434
|
+
function collectFiles(dir) {
|
|
435
|
+
/** @type {string[]} */
|
|
436
|
+
const out = [];
|
|
437
|
+
if (!existsSync(dir)) return out;
|
|
438
|
+
const stack = [dir];
|
|
439
|
+
while (stack.length > 0) {
|
|
440
|
+
const cur = stack.pop();
|
|
441
|
+
let entries;
|
|
442
|
+
try {
|
|
443
|
+
entries = readdirSync(cur, { withFileTypes: true });
|
|
444
|
+
} catch {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
for (const entry of entries) {
|
|
448
|
+
const full = path.join(cur, entry.name);
|
|
449
|
+
if (entry.isDirectory()) {
|
|
450
|
+
if (entry.name === 'node_modules') continue;
|
|
451
|
+
if (entry.name.startsWith('.')) continue;
|
|
452
|
+
stack.push(full);
|
|
453
|
+
} else if (entry.isFile()) {
|
|
454
|
+
out.push(full);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return out;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Determine whether a file path is a boilerplate (self-definition or
|
|
463
|
+
* authoring/registry) site for a given surface item, and therefore must be
|
|
464
|
+
* excluded from the "is anyone else using this?" reference count.
|
|
465
|
+
*
|
|
466
|
+
* @param {string} relPath - repo-root-relative POSIX path
|
|
467
|
+
* @param {'skill'|'agent'|'command'} kind
|
|
468
|
+
* @param {string} name
|
|
469
|
+
* @returns {boolean}
|
|
470
|
+
*/
|
|
471
|
+
function isBoilerplateSite(relPath, kind, name) {
|
|
472
|
+
if (kind === 'agent') {
|
|
473
|
+
if (relPath === `agents/${name}.md`) return true;
|
|
474
|
+
if (relPath === 'agents/AGENTS.md') return true;
|
|
475
|
+
if (relPath === `agents/schemas/${name}.schema.json`) return true;
|
|
476
|
+
// Routing-table / validator boilerplate.
|
|
477
|
+
if (relPath === 'agents/schemas/routing-table.json') return true;
|
|
478
|
+
// The dead-bridge validator (subsumed check-subagent-types per #671) carries
|
|
479
|
+
// agent names in its corpus/detector fixtures — exempt it as a validator site.
|
|
480
|
+
if (relPath.startsWith('scripts/lib/validate/dead-bridge')) return true;
|
|
481
|
+
if (relPath.startsWith('scripts/lib/validate/check-dead-bridge')) return true;
|
|
482
|
+
if (relPath.startsWith('scripts/lib/validate/check-agents')) return true;
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
if (kind === 'skill') {
|
|
486
|
+
// The skill's own directory is boilerplate.
|
|
487
|
+
if (relPath.startsWith(`skills/${name}/`)) return true;
|
|
488
|
+
// README index / top-level docs index entries are boilerplate.
|
|
489
|
+
if (relPath === 'README.md') return true;
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
// command
|
|
493
|
+
if (relPath === `commands/${name}.md`) return true;
|
|
494
|
+
if (relPath === 'README.md') return true;
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Build the strict-token and prose-invocation matchers for a surface item.
|
|
500
|
+
* Bare-word matching is deliberately NOT used (too noisy per Discovery: e.g.
|
|
501
|
+
* "daily" yields 0 strict refs but 16 name-anywhere refs).
|
|
502
|
+
*
|
|
503
|
+
* @param {'skill'|'agent'|'command'} kind
|
|
504
|
+
* @param {string} name
|
|
505
|
+
* @returns {{strict: RegExp[], prose: RegExp[]}}
|
|
506
|
+
*/
|
|
507
|
+
function buildMatchers(kind, name) {
|
|
508
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
509
|
+
if (kind === 'agent') {
|
|
510
|
+
return {
|
|
511
|
+
strict: [
|
|
512
|
+
new RegExp(`session-orchestrator:${esc}\\b`),
|
|
513
|
+
new RegExp(`subagent_type[^\\n]*["']${esc}["']`),
|
|
514
|
+
],
|
|
515
|
+
prose: [
|
|
516
|
+
new RegExp(`\\bdispatch(?:es|ed|ing)?\\b[^\\n]*\\b${esc}\\b`, 'i'),
|
|
517
|
+
new RegExp(`\\b${esc}\\b[^\\n]*\\bagent\\b`, 'i'),
|
|
518
|
+
],
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
if (kind === 'skill') {
|
|
522
|
+
return {
|
|
523
|
+
strict: [
|
|
524
|
+
new RegExp(`skills/${esc}/`),
|
|
525
|
+
new RegExp(`session-orchestrator:${esc}\\b`),
|
|
526
|
+
],
|
|
527
|
+
prose: [
|
|
528
|
+
new RegExp(`\\b[Ii]nvoke[^\\n]*\\b${esc}\\b[^\\n]*\\bskill\\b`),
|
|
529
|
+
new RegExp(`\\b${esc}\\b[^\\n]*\\bskill\\b`, 'i'),
|
|
530
|
+
],
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
// command
|
|
534
|
+
return {
|
|
535
|
+
strict: [new RegExp(`/${esc}\\b`)],
|
|
536
|
+
prose: [new RegExp(`\\b${esc}\\b[^\\n]*\\bcommand\\b`, 'i')],
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Scan skills/agents/commands/hooks/scripts for references to a surface item,
|
|
542
|
+
* excluding boilerplate (self-definition + authoring/registry) sites.
|
|
543
|
+
*
|
|
544
|
+
* @param {string} repoRoot
|
|
545
|
+
* @param {{kind:'skill'|'agent'|'command', name:string}} opts
|
|
546
|
+
* @returns {{strictRefs:number, proseRefs:number, nonBoilerplateRefs:number, refFiles:string[]}}
|
|
547
|
+
*/
|
|
548
|
+
export function staticReferenceScan(repoRoot, { kind, name }) {
|
|
549
|
+
const { strict, prose } = buildMatchers(kind, name);
|
|
550
|
+
let strictRefs = 0;
|
|
551
|
+
let proseRefs = 0;
|
|
552
|
+
/** @type {Set<string>} */
|
|
553
|
+
const refFiles = new Set();
|
|
554
|
+
|
|
555
|
+
for (const dir of SCAN_DIRS) {
|
|
556
|
+
const absDir = path.join(repoRoot, dir);
|
|
557
|
+
for (const file of collectFiles(absDir)) {
|
|
558
|
+
const relPath = path.relative(repoRoot, file).split(path.sep).join('/');
|
|
559
|
+
if (isBoilerplateSite(relPath, kind, name)) continue;
|
|
560
|
+
let content;
|
|
561
|
+
try {
|
|
562
|
+
content = readFileSync(file, 'utf8');
|
|
563
|
+
} catch {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
let hit = false;
|
|
567
|
+
for (const re of strict) {
|
|
568
|
+
if (re.test(content)) {
|
|
569
|
+
strictRefs += 1;
|
|
570
|
+
hit = true;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
for (const re of prose) {
|
|
574
|
+
if (re.test(content)) {
|
|
575
|
+
proseRefs += 1;
|
|
576
|
+
hit = true;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (hit) refFiles.add(relPath);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
strictRefs,
|
|
585
|
+
proseRefs,
|
|
586
|
+
nonBoilerplateRefs: refFiles.size,
|
|
587
|
+
refFiles: [...refFiles].sort(),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
// Command ↔ skill linkage
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Parse commands/*.md for the skill each command invokes. Recognises the
|
|
597
|
+
* conventional `skills/<name>/SKILL.md` reference inside a command file.
|
|
598
|
+
*
|
|
599
|
+
* @param {string} repoRoot
|
|
600
|
+
* @returns {{commandToSkill: Map<string,string|null>, skillToCommands: Map<string,string[]>}}
|
|
601
|
+
*/
|
|
602
|
+
export function commandSkillLinkage(repoRoot) {
|
|
603
|
+
/** @type {Map<string,string|null>} */
|
|
604
|
+
const commandToSkill = new Map();
|
|
605
|
+
/** @type {Map<string,string[]>} */
|
|
606
|
+
const skillToCommands = new Map();
|
|
607
|
+
|
|
608
|
+
const commandsDir = path.join(repoRoot, 'commands');
|
|
609
|
+
if (!existsSync(commandsDir)) {
|
|
610
|
+
return { commandToSkill, skillToCommands };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const skillRefRe = /skills\/([a-z0-9-]+)\/SKILL\.md/i;
|
|
614
|
+
for (const entry of readdirSync(commandsDir, { withFileTypes: true })) {
|
|
615
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
616
|
+
const command = entry.name.replace(/\.md$/, '');
|
|
617
|
+
let content;
|
|
618
|
+
try {
|
|
619
|
+
content = readFileSync(path.join(commandsDir, entry.name), 'utf8');
|
|
620
|
+
} catch {
|
|
621
|
+
commandToSkill.set(command, null);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
const m = skillRefRe.exec(content);
|
|
625
|
+
const skill = m ? m[1] : null;
|
|
626
|
+
commandToSkill.set(command, skill);
|
|
627
|
+
if (skill) {
|
|
628
|
+
const list = skillToCommands.get(skill) ?? [];
|
|
629
|
+
list.push(command);
|
|
630
|
+
skillToCommands.set(skill, list);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return { commandToSkill, skillToCommands };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ---------------------------------------------------------------------------
|
|
638
|
+
// Classification
|
|
639
|
+
// ---------------------------------------------------------------------------
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Strong-applied-no threshold: a skill selected but judged applied=no for at
|
|
643
|
+
* least this many judgments (and no applied=yes) is a strong "selected but not
|
|
644
|
+
* applied" advisory signal — enough to DOWNGRADE Active → Investigate.
|
|
645
|
+
*/
|
|
646
|
+
const STRONG_APPLIED_NO_FLOOR = 2;
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Apply the L3 judge advisory layer to an already-computed deterministic
|
|
650
|
+
* verdict. ADVISORY ONLY (#645 R9):
|
|
651
|
+
* - ANNOTATE: append a diagnosis axis derived from applied/completed tallies.
|
|
652
|
+
* - DOWNGRADE: Active → Investigate on a STRONG applied=no signal.
|
|
653
|
+
* - It MUST NEVER escalate a verdict toward Retire/Demote, and MUST NEVER
|
|
654
|
+
* promote a verdict upward (no Retire→Active). Judge data only softens.
|
|
655
|
+
*
|
|
656
|
+
* Clean-degrade contract: when `judge` is null/absent OR carries no judgments,
|
|
657
|
+
* the result is returned byte-for-byte unchanged.
|
|
658
|
+
*
|
|
659
|
+
* @param {{name:string,kind:string,verdict:string,score:number,reasons:string[],signals:object}} result
|
|
660
|
+
* the deterministic verdict (mutated in place: reasons/signals only)
|
|
661
|
+
* @param {{appliedYes:number,appliedNo:number,appliedUnknown:number,completedYes:number,completedNo:number,completedUnknown:number,total:number,lastTs:string|null}|null} judge
|
|
662
|
+
* @returns {{name:string,kind:string,verdict:string,score:number,reasons:string[],signals:object}}
|
|
663
|
+
*/
|
|
664
|
+
function applyJudgeAdvisory(result, judge) {
|
|
665
|
+
// Disabled-path guarantee: null judge, or judge with zero recorded judgments,
|
|
666
|
+
// leaves the deterministic result completely untouched.
|
|
667
|
+
if (!judge || typeof judge !== 'object' || (judge.total ?? 0) <= 0) {
|
|
668
|
+
return result;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const appliedYes = judge.appliedYes ?? 0;
|
|
672
|
+
const appliedNo = judge.appliedNo ?? 0;
|
|
673
|
+
const completedNo = judge.completedNo ?? 0;
|
|
674
|
+
const total = judge.total ?? 0;
|
|
675
|
+
|
|
676
|
+
// Surface the raw judge tallies on signals for downstream consumers (always
|
|
677
|
+
// advisory — never read by the deterministic verdict gate above).
|
|
678
|
+
result.signals.judge = {
|
|
679
|
+
appliedYes,
|
|
680
|
+
appliedNo,
|
|
681
|
+
appliedUnknown: judge.appliedUnknown ?? 0,
|
|
682
|
+
completedYes: judge.completedYes ?? 0,
|
|
683
|
+
completedNo,
|
|
684
|
+
completedUnknown: judge.completedUnknown ?? 0,
|
|
685
|
+
total,
|
|
686
|
+
lastTs: judge.lastTs ?? null,
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// Annotation axis 1: selected but applied=no → trigger description may be unclear.
|
|
690
|
+
const strongAppliedNo = appliedNo >= STRONG_APPLIED_NO_FLOOR && appliedYes === 0;
|
|
691
|
+
if (appliedNo > 0) {
|
|
692
|
+
result.reasons.push(
|
|
693
|
+
`[advisory] judged applied=no in ${appliedNo}/${total} judgment(s)` +
|
|
694
|
+
(appliedYes === 0
|
|
695
|
+
? ' (selected but never applied → trigger description may be unclear)'
|
|
696
|
+
: ''),
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Annotation axis 2: applied but completed=no → instructions may be wrong.
|
|
701
|
+
if (appliedYes > 0 && completedNo > 0) {
|
|
702
|
+
result.reasons.push(
|
|
703
|
+
`[advisory] judged applied=yes but completed=no in ${completedNo}/${total} judgment(s) (instructions may be wrong)`,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// DOWNGRADE (never escalate): a strong "selected but not applied" signal
|
|
708
|
+
// softens an Active verdict to Investigate so an operator reviews the trigger.
|
|
709
|
+
// Firewall: we ONLY move Active → Investigate. We never touch Demote/Retire
|
|
710
|
+
// (those are deterministic-cold), and we never promote upward.
|
|
711
|
+
if (strongAppliedNo && result.verdict === 'Active') {
|
|
712
|
+
result.reasons.push(
|
|
713
|
+
`[advisory] strong applied=no signal (${appliedNo}/${total}, 0 applied=yes) → Active downgraded to Investigate for operator review`,
|
|
714
|
+
);
|
|
715
|
+
result.verdict = 'Investigate';
|
|
716
|
+
result.score = 2;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return result;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Classify a single surface item into a 4-tier verdict.
|
|
724
|
+
*
|
|
725
|
+
* Verdict precedence (default-safe):
|
|
726
|
+
* - Active — agent dispatch>floor, OR skill invoked-by-command /
|
|
727
|
+
* command invokes-a-live-skill, OR proseRefs>=ACTIVE_REF_FLOOR
|
|
728
|
+
* - Investigate — coverage<window (low-confidence), OR conflicting signals
|
|
729
|
+
* (DEFAULT-SAFE bucket)
|
|
730
|
+
* - Demote — 0<dispatch<=DEMOTE_DISPATCH_CEILING, OR a skill with only a
|
|
731
|
+
* single cross-ref and no command linkage
|
|
732
|
+
* - Retire — dispatch===0 AND nonBoilerplateRefs===0 AND coverage>=window
|
|
733
|
+
*
|
|
734
|
+
* The optional `judge` (L3, epic #645) is ADVISORY ONLY. It is consumed AFTER
|
|
735
|
+
* the deterministic verdict is computed, and may only ANNOTATE the diagnosis or
|
|
736
|
+
* DOWNGRADE Active → Investigate on a strong applied=no signal. It can NEVER
|
|
737
|
+
* escalate a skill toward Retire/Demote nor promote it upward. When `judge` is
|
|
738
|
+
* null/absent the classification is byte-for-byte identical to the L1-only path
|
|
739
|
+
* (disabled-path guarantee).
|
|
740
|
+
*
|
|
741
|
+
* @param {{
|
|
742
|
+
* kind:'skill'|'agent'|'command',
|
|
743
|
+
* name:string,
|
|
744
|
+
* dispatch?:{count:number,lastTs:string|null}|null,
|
|
745
|
+
* static:{strictRefs:number,proseRefs:number,nonBoilerplateRefs:number,refFiles:string[]},
|
|
746
|
+
* linkage?:{invokedByCommands?:string[], invokesSkill?:string|null, invokedSkillExists?:boolean},
|
|
747
|
+
* windowDays:number,
|
|
748
|
+
* coverageDays:number,
|
|
749
|
+
* judge?:{appliedYes:number,appliedNo:number,appliedUnknown:number,completedYes:number,completedNo:number,completedUnknown:number,total:number,lastTs:string|null}|null
|
|
750
|
+
* }} args
|
|
751
|
+
* @returns {{name:string,kind:string,verdict:string,score:number,reasons:string[],signals:object}}
|
|
752
|
+
*/
|
|
753
|
+
export function classifyItem({
|
|
754
|
+
kind,
|
|
755
|
+
name,
|
|
756
|
+
dispatch,
|
|
757
|
+
static: staticRefs,
|
|
758
|
+
linkage,
|
|
759
|
+
windowDays,
|
|
760
|
+
coverageDays,
|
|
761
|
+
judge = null,
|
|
762
|
+
}) {
|
|
763
|
+
const reasons = [];
|
|
764
|
+
const dispatchCount = dispatch?.count ?? 0;
|
|
765
|
+
const proseRefs = staticRefs.proseRefs;
|
|
766
|
+
const strictRefs = staticRefs.strictRefs;
|
|
767
|
+
const nonBoilerplateRefs = staticRefs.nonBoilerplateRefs;
|
|
768
|
+
const invokedByCommands = linkage?.invokedByCommands ?? [];
|
|
769
|
+
const invokesSkill = linkage?.invokesSkill ?? null;
|
|
770
|
+
// When a command invokes a skill, the caller passes whether that skill is
|
|
771
|
+
// present on disk. Default-true so callers that don't thread the surface set
|
|
772
|
+
// (older callers/tests) preserve the historical "Active on linkage" behavior.
|
|
773
|
+
const invokedSkillExists = linkage?.invokedSkillExists ?? true;
|
|
774
|
+
const lowConfidence = coverageDays < windowDays;
|
|
775
|
+
|
|
776
|
+
const signals = {
|
|
777
|
+
dispatchCount,
|
|
778
|
+
lastDispatch: dispatch?.lastTs ?? null,
|
|
779
|
+
strictRefs,
|
|
780
|
+
proseRefs,
|
|
781
|
+
nonBoilerplateRefs,
|
|
782
|
+
invokedByCommands,
|
|
783
|
+
invokesSkill,
|
|
784
|
+
invokedSkillExists,
|
|
785
|
+
lowConfidence,
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// --- Agent dispatch tier takes precedence over ref-based Active -----------
|
|
789
|
+
// For agents, telemetry is authoritative. An agent with exactly one dispatch
|
|
790
|
+
// is near-zero (Demote-tier) even though its definition is referenced across
|
|
791
|
+
// many docs — those references are largely boilerplate-adjacent and do NOT
|
|
792
|
+
// prove the agent is actually being dispatched. Discovery: db-specialist /
|
|
793
|
+
// ui-developer are n=1 (Demote), distinct from never-dispatched (Retire).
|
|
794
|
+
if (kind === 'agent') {
|
|
795
|
+
if (dispatchCount > ACTIVE_DISPATCH_FLOOR) {
|
|
796
|
+
reasons.push(
|
|
797
|
+
`agent dispatched ${dispatchCount}× in window (>floor ${ACTIVE_DISPATCH_FLOOR})`,
|
|
798
|
+
);
|
|
799
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Active', score: 0, reasons, signals }, judge);
|
|
800
|
+
}
|
|
801
|
+
if (dispatchCount > 0 && dispatchCount <= DEMOTE_DISPATCH_CEILING) {
|
|
802
|
+
reasons.push(
|
|
803
|
+
`near-zero dispatch (${dispatchCount} in (0, ${DEMOTE_DISPATCH_CEILING}] ceiling), last ${dispatch?.lastTs ?? 'n/a'}`,
|
|
804
|
+
);
|
|
805
|
+
if (lowConfidence) {
|
|
806
|
+
reasons.push(
|
|
807
|
+
`low-confidence telemetry (coverage ${coverageDays.toFixed(1)}d < window ${windowDays}d) → Investigate`,
|
|
808
|
+
);
|
|
809
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
|
|
810
|
+
}
|
|
811
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Demote', score: 1, reasons, signals }, judge);
|
|
812
|
+
}
|
|
813
|
+
// dispatchCount === 0 falls through to the cold/Retire + ref checks below.
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// --- Active checks (linkage + ref-floor) -----------------------------------
|
|
817
|
+
// NOTE: the ref-floor Active promotion applies to skills & commands ONLY.
|
|
818
|
+
// For agents, dispatch telemetry is authoritative — a zero-dispatch agent
|
|
819
|
+
// must NOT be promoted to Active by mere documentation mentions (every agent
|
|
820
|
+
// is referenced across docs; that proves nothing about dispatch). A
|
|
821
|
+
// zero-dispatch agent with some non-boilerplate refs lands in Investigate via
|
|
822
|
+
// the fallthrough below, never Active. This is the Discovery "single-grep is
|
|
823
|
+
// noisy" guard realised at the verdict layer.
|
|
824
|
+
// --- Command → removed-skill staleness guard -------------------------------
|
|
825
|
+
// A command that invokes a skill which is no longer present on disk is the
|
|
826
|
+
// exact staleness a sunset tool must surface. Such a command is NOT Active —
|
|
827
|
+
// the linkage points at a deleted skill. Route it to Investigate so an
|
|
828
|
+
// operator confirms whether the command (or its target skill) should go.
|
|
829
|
+
if (kind === 'command' && invokesSkill && !invokedSkillExists) {
|
|
830
|
+
reasons.push(`command invokes a skill not present on disk: ${invokesSkill}`);
|
|
831
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
let active = false;
|
|
835
|
+
if (kind === 'skill' && invokedByCommands.length > 0) {
|
|
836
|
+
active = true;
|
|
837
|
+
reasons.push(`skill invoked by command(s): ${invokedByCommands.join(', ')}`);
|
|
838
|
+
}
|
|
839
|
+
if (kind === 'command' && invokesSkill && invokedSkillExists) {
|
|
840
|
+
active = true;
|
|
841
|
+
reasons.push(`command invokes live skill: ${invokesSkill}`);
|
|
842
|
+
}
|
|
843
|
+
if (
|
|
844
|
+
kind !== 'agent' &&
|
|
845
|
+
(nonBoilerplateRefs >= ACTIVE_REF_FLOOR || proseRefs >= ACTIVE_REF_FLOOR)
|
|
846
|
+
) {
|
|
847
|
+
active = true;
|
|
848
|
+
reasons.push(
|
|
849
|
+
`${nonBoilerplateRefs} non-boilerplate ref file(s), ${proseRefs} prose ref(s) (>=floor ${ACTIVE_REF_FLOOR})`,
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
if (active) {
|
|
853
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Active', score: 0, reasons, signals }, judge);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// --- Retire (provably cold) — gated on full coverage -----------------------
|
|
857
|
+
const provablyCold =
|
|
858
|
+
dispatchCount === 0 && nonBoilerplateRefs === 0;
|
|
859
|
+
if (provablyCold) {
|
|
860
|
+
if (lowConfidence) {
|
|
861
|
+
// GUARDRAIL: never Retire on < window of telemetry. Downgrade.
|
|
862
|
+
reasons.push(
|
|
863
|
+
`cold (0 dispatch, 0 non-boilerplate refs) BUT coverage ${coverageDays.toFixed(1)}d < window ${windowDays}d → downgraded to Investigate`,
|
|
864
|
+
);
|
|
865
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
|
|
866
|
+
}
|
|
867
|
+
reasons.push(
|
|
868
|
+
`0 dispatch AND 0 non-boilerplate refs AND coverage ${coverageDays.toFixed(1)}d >= window ${windowDays}d`,
|
|
869
|
+
);
|
|
870
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Retire', score: 3, reasons, signals }, judge);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// --- Demote (near-zero) — skills & commands (agents handled above) ---------
|
|
874
|
+
const lonelySkill =
|
|
875
|
+
kind === 'skill' && nonBoilerplateRefs <= 1 && invokedByCommands.length === 0;
|
|
876
|
+
const lonelyCommand =
|
|
877
|
+
kind === 'command' && !invokesSkill && nonBoilerplateRefs <= 1;
|
|
878
|
+
if (lonelySkill || lonelyCommand) {
|
|
879
|
+
if (lonelySkill) {
|
|
880
|
+
reasons.push(`skill has only ${nonBoilerplateRefs} cross-ref and no command linkage`);
|
|
881
|
+
}
|
|
882
|
+
if (lonelyCommand) {
|
|
883
|
+
reasons.push(`command invokes no skill and has only ${nonBoilerplateRefs} cross-ref`);
|
|
884
|
+
}
|
|
885
|
+
if (lowConfidence) {
|
|
886
|
+
reasons.push(
|
|
887
|
+
`low-confidence telemetry (coverage ${coverageDays.toFixed(1)}d < window ${windowDays}d) → Investigate`,
|
|
888
|
+
);
|
|
889
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
|
|
890
|
+
}
|
|
891
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Demote', score: 1, reasons, signals }, judge);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// --- Investigate (default-safe fallthrough) --------------------------------
|
|
895
|
+
reasons.push('conflicting or inconclusive signals → default-safe Investigate');
|
|
896
|
+
return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// ---------------------------------------------------------------------------
|
|
900
|
+
// Top-level walk
|
|
901
|
+
// ---------------------------------------------------------------------------
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Run the full sunset walk across the plugin surface.
|
|
905
|
+
*
|
|
906
|
+
* @param {string} repoRoot
|
|
907
|
+
* @param {{windowDays?:number, kind?:'skill'|'agent'|'command', now?:number, subagentsPath?:string, invocationsPath?:string, judgmentsPath?:string}} [opts]
|
|
908
|
+
* @returns {{
|
|
909
|
+
* meta:{generatedAt:string,windowDays:number,coverageDays:number,lowConfidence:boolean,telemetrySources:object},
|
|
910
|
+
* summary:{active:number,investigate:number,demote:number,retire:number},
|
|
911
|
+
* items:Array<object>
|
|
912
|
+
* }}
|
|
913
|
+
*/
|
|
914
|
+
export function runSunsetWalk(repoRoot, opts = {}) {
|
|
915
|
+
const windowDays = opts.windowDays ?? DEFAULT_WINDOW_DAYS;
|
|
916
|
+
const nowMs = typeof opts.now === 'number' ? opts.now : Date.now();
|
|
917
|
+
const kindFilter = opts.kind ?? null;
|
|
918
|
+
|
|
919
|
+
const subagentsPath =
|
|
920
|
+
opts.subagentsPath ??
|
|
921
|
+
path.join(repoRoot, '.orchestrator', 'metrics', 'subagents.jsonl');
|
|
922
|
+
|
|
923
|
+
const invocationsPath =
|
|
924
|
+
opts.invocationsPath ??
|
|
925
|
+
path.join(repoRoot, '.orchestrator', 'metrics', 'skill-invocations.jsonl');
|
|
926
|
+
|
|
927
|
+
const judgmentsPath =
|
|
928
|
+
opts.judgmentsPath ??
|
|
929
|
+
path.join(repoRoot, '.orchestrator', 'metrics', 'skill-judgments.jsonl');
|
|
930
|
+
|
|
931
|
+
const surface = enumerateSurface(repoRoot);
|
|
932
|
+
const dispatch = readDispatchCounts(subagentsPath, { windowDays, now: nowMs });
|
|
933
|
+
const skillInvocations = readSkillInvocationCounts(invocationsPath, { windowDays, now: nowMs });
|
|
934
|
+
// L3 advisory judge telemetry (#645). Clean-degrade: when the sidecar is
|
|
935
|
+
// absent (judge disabled) this returns an empty bySkill map and the skill
|
|
936
|
+
// classification below behaves byte-for-byte identically to the L1-only path.
|
|
937
|
+
const skillJudgments = readSkillJudgmentCounts(judgmentsPath, { windowDays, now: nowMs });
|
|
938
|
+
const linkage = commandSkillLinkage(repoRoot);
|
|
939
|
+
const coverageDays = dispatch.coverageDays;
|
|
940
|
+
const lowConfidence = coverageDays < windowDays;
|
|
941
|
+
|
|
942
|
+
/** @type {Array<object>} */
|
|
943
|
+
const items = [];
|
|
944
|
+
|
|
945
|
+
const wantKind = (k) => !kindFilter || kindFilter === k;
|
|
946
|
+
|
|
947
|
+
if (wantKind('skill')) {
|
|
948
|
+
for (const name of surface.skills) {
|
|
949
|
+
const staticRefs = staticReferenceScan(repoRoot, { kind: 'skill', name });
|
|
950
|
+
const invokedByCommands = linkage.skillToCommands.get(name) ?? [];
|
|
951
|
+
// Use real L1 selection counts (#645). Fall back to zero-count (NOT null)
|
|
952
|
+
// so the coverage guard treats skills with no invocations as "cold but
|
|
953
|
+
// possibly low-coverage" rather than "no telemetry at all".
|
|
954
|
+
const skillDispatch = skillInvocations.bySkill.get(name) ?? { count: 0, lastTs: null };
|
|
955
|
+
// L3 advisory judge data (#645). null when no in-window judgment exists
|
|
956
|
+
// for this skill → classifyItem ignores it entirely (disabled-path parity).
|
|
957
|
+
const judge = skillJudgments.bySkill.get(name) ?? null;
|
|
958
|
+
items.push(
|
|
959
|
+
classifyItem({
|
|
960
|
+
kind: 'skill',
|
|
961
|
+
name,
|
|
962
|
+
dispatch: skillDispatch,
|
|
963
|
+
static: staticRefs,
|
|
964
|
+
linkage: { invokedByCommands },
|
|
965
|
+
windowDays,
|
|
966
|
+
coverageDays,
|
|
967
|
+
judge,
|
|
968
|
+
}),
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (wantKind('agent')) {
|
|
974
|
+
for (const name of surface.agents) {
|
|
975
|
+
const staticRefs = staticReferenceScan(repoRoot, { kind: 'agent', name });
|
|
976
|
+
items.push(
|
|
977
|
+
classifyItem({
|
|
978
|
+
kind: 'agent',
|
|
979
|
+
name,
|
|
980
|
+
dispatch: dispatch.byAgent.get(name) ?? { count: 0, lastTs: null },
|
|
981
|
+
static: staticRefs,
|
|
982
|
+
windowDays,
|
|
983
|
+
coverageDays,
|
|
984
|
+
}),
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (wantKind('command')) {
|
|
990
|
+
const skillSet = new Set(surface.skills);
|
|
991
|
+
for (const name of surface.commands) {
|
|
992
|
+
const staticRefs = staticReferenceScan(repoRoot, { kind: 'command', name });
|
|
993
|
+
const invokesSkill = linkage.commandToSkill.get(name) ?? null;
|
|
994
|
+
// Cross-check the linkage target against the enumerated skill surface so a
|
|
995
|
+
// command pointing at a deleted skill is flagged Investigate, not Active.
|
|
996
|
+
const invokedSkillExists = invokesSkill ? skillSet.has(invokesSkill) : true;
|
|
997
|
+
items.push(
|
|
998
|
+
classifyItem({
|
|
999
|
+
kind: 'command',
|
|
1000
|
+
name,
|
|
1001
|
+
dispatch: null,
|
|
1002
|
+
static: staticRefs,
|
|
1003
|
+
linkage: { invokesSkill, invokedSkillExists },
|
|
1004
|
+
windowDays,
|
|
1005
|
+
coverageDays,
|
|
1006
|
+
}),
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const summary = { active: 0, investigate: 0, demote: 0, retire: 0 };
|
|
1012
|
+
for (const item of items) {
|
|
1013
|
+
const key = item.verdict.toLowerCase();
|
|
1014
|
+
if (key in summary) summary[key] += 1;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
return {
|
|
1018
|
+
meta: {
|
|
1019
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
1020
|
+
windowDays,
|
|
1021
|
+
coverageDays: Number(coverageDays.toFixed(2)),
|
|
1022
|
+
lowConfidence,
|
|
1023
|
+
telemetrySources: {
|
|
1024
|
+
subagentsJsonl: subagentsPath,
|
|
1025
|
+
earliestTs: dispatch.earliestTs,
|
|
1026
|
+
latestTs: dispatch.latestTs,
|
|
1027
|
+
skillInvocationsJsonl: invocationsPath,
|
|
1028
|
+
skillInvocationsEarliestTs: skillInvocations.earliestTs,
|
|
1029
|
+
skillInvocationsLatestTs: skillInvocations.latestTs,
|
|
1030
|
+
skillJudgmentsJsonl: judgmentsPath,
|
|
1031
|
+
skillJudgmentsEarliestTs: skillJudgments.earliestTs,
|
|
1032
|
+
skillJudgmentsLatestTs: skillJudgments.latestTs,
|
|
1033
|
+
skillJudgmentsAssessment: 'L3 advisory only — annotates/downgrades, never escalates toward Retire',
|
|
1034
|
+
commandsAssessment: 'static-analysis only (no command-invocation telemetry)',
|
|
1035
|
+
},
|
|
1036
|
+
},
|
|
1037
|
+
summary,
|
|
1038
|
+
items,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// ---------------------------------------------------------------------------
|
|
1043
|
+
// CLI
|
|
1044
|
+
// ---------------------------------------------------------------------------
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Parse argv into options. Returns { error } on bad args.
|
|
1048
|
+
* @param {string[]} argv
|
|
1049
|
+
*/
|
|
1050
|
+
function parseArgs(argv) {
|
|
1051
|
+
const opts = { json: false, windowDays: DEFAULT_WINDOW_DAYS, kind: null };
|
|
1052
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1053
|
+
const arg = argv[i];
|
|
1054
|
+
if (arg === '--json') {
|
|
1055
|
+
opts.json = true;
|
|
1056
|
+
} else if (arg === '--window-days') {
|
|
1057
|
+
const val = argv[i + 1];
|
|
1058
|
+
const n = Number(val);
|
|
1059
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
1060
|
+
return { error: `--window-days requires a positive number, got: ${val}` };
|
|
1061
|
+
}
|
|
1062
|
+
opts.windowDays = n;
|
|
1063
|
+
i += 1;
|
|
1064
|
+
} else if (arg.startsWith('--window-days=')) {
|
|
1065
|
+
const n = Number(arg.split('=')[1]);
|
|
1066
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
1067
|
+
return { error: `--window-days requires a positive number` };
|
|
1068
|
+
}
|
|
1069
|
+
opts.windowDays = n;
|
|
1070
|
+
} else if (arg === '--kind') {
|
|
1071
|
+
const val = argv[i + 1];
|
|
1072
|
+
if (!['skill', 'agent', 'command'].includes(val)) {
|
|
1073
|
+
return { error: `--kind must be one of skill|agent|command, got: ${val}` };
|
|
1074
|
+
}
|
|
1075
|
+
opts.kind = val;
|
|
1076
|
+
i += 1;
|
|
1077
|
+
} else if (arg.startsWith('--kind=')) {
|
|
1078
|
+
const val = arg.split('=')[1];
|
|
1079
|
+
if (!['skill', 'agent', 'command'].includes(val)) {
|
|
1080
|
+
return { error: `--kind must be one of skill|agent|command` };
|
|
1081
|
+
}
|
|
1082
|
+
opts.kind = val;
|
|
1083
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
1084
|
+
opts.help = true;
|
|
1085
|
+
} else {
|
|
1086
|
+
return { error: `unknown argument: ${arg}` };
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return opts;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const HELP = `sunset-review walker — READ-ONLY surface usage analysis
|
|
1093
|
+
|
|
1094
|
+
Usage:
|
|
1095
|
+
node scripts/lib/sunset/walker.mjs [--json] [--window-days N] [--kind K]
|
|
1096
|
+
|
|
1097
|
+
Options:
|
|
1098
|
+
--json Emit only JSON to stdout (machine-parseable).
|
|
1099
|
+
--window-days N Sunset window in days (default ${DEFAULT_WINDOW_DAYS}).
|
|
1100
|
+
--kind K Limit to one of: skill | agent | command.
|
|
1101
|
+
--help, -h Show this help.
|
|
1102
|
+
|
|
1103
|
+
Exit codes:
|
|
1104
|
+
0 walk done (cold findings are exit 0, not an error)
|
|
1105
|
+
1 bad args / surface directory missing
|
|
1106
|
+
2 system error
|
|
1107
|
+
`;
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Render a human-readable table grouped by verdict.
|
|
1111
|
+
* @param {ReturnType<typeof runSunsetWalk>} result
|
|
1112
|
+
* @returns {string}
|
|
1113
|
+
*/
|
|
1114
|
+
function renderHuman(result) {
|
|
1115
|
+
const lines = [];
|
|
1116
|
+
lines.push('Sunset Review — surface usage walk');
|
|
1117
|
+
lines.push(` generated: ${result.meta.generatedAt}`);
|
|
1118
|
+
lines.push(
|
|
1119
|
+
` window: ${result.meta.windowDays}d coverage: ${result.meta.coverageDays}d lowConfidence: ${result.meta.lowConfidence}`,
|
|
1120
|
+
);
|
|
1121
|
+
lines.push(
|
|
1122
|
+
` summary: Active=${result.summary.active} Investigate=${result.summary.investigate} Demote=${result.summary.demote} Retire=${result.summary.retire}`,
|
|
1123
|
+
);
|
|
1124
|
+
if (result.meta.lowConfidence) {
|
|
1125
|
+
lines.push(
|
|
1126
|
+
' NOTE: coverage < window — all Retire verdicts downgraded to Investigate.',
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
for (const verdict of ['Retire', 'Demote', 'Investigate', 'Active']) {
|
|
1130
|
+
const group = result.items.filter((i) => i.verdict === verdict);
|
|
1131
|
+
if (group.length === 0) continue;
|
|
1132
|
+
lines.push('');
|
|
1133
|
+
lines.push(`== ${verdict} (${group.length}) ==`);
|
|
1134
|
+
for (const item of group) {
|
|
1135
|
+
lines.push(` [${item.kind}] ${item.name}`);
|
|
1136
|
+
for (const reason of item.reasons) {
|
|
1137
|
+
lines.push(` - ${reason}`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return lines.join('\n');
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function main(argv) {
|
|
1145
|
+
const opts = parseArgs(argv);
|
|
1146
|
+
if (opts.error) {
|
|
1147
|
+
process.stderr.write(`Error: ${opts.error}\n\n${HELP}`);
|
|
1148
|
+
return 1;
|
|
1149
|
+
}
|
|
1150
|
+
if (opts.help) {
|
|
1151
|
+
process.stdout.write(HELP);
|
|
1152
|
+
return 0;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
let repoRoot;
|
|
1156
|
+
try {
|
|
1157
|
+
repoRoot = process.cwd();
|
|
1158
|
+
// Prefer git root if available, but cwd is acceptable.
|
|
1159
|
+
const skillsDir = path.join(repoRoot, 'skills');
|
|
1160
|
+
if (!existsSync(skillsDir)) {
|
|
1161
|
+
process.stderr.write(`Error: surface directory not found: ${skillsDir}\n`);
|
|
1162
|
+
return 1;
|
|
1163
|
+
}
|
|
1164
|
+
} catch (err) {
|
|
1165
|
+
process.stderr.write(`System error resolving repo root: ${err.message}\n`);
|
|
1166
|
+
return 2;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
let result;
|
|
1170
|
+
try {
|
|
1171
|
+
result = runSunsetWalk(repoRoot, {
|
|
1172
|
+
windowDays: opts.windowDays,
|
|
1173
|
+
kind: opts.kind,
|
|
1174
|
+
});
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
process.stderr.write(`System error during walk: ${err.message}\n`);
|
|
1177
|
+
return 2;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
if (opts.json) {
|
|
1181
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
1182
|
+
} else {
|
|
1183
|
+
process.stdout.write(renderHuman(result) + '\n');
|
|
1184
|
+
}
|
|
1185
|
+
return 0;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// Import-safe main-guard.
|
|
1189
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
1190
|
+
const code = main(process.argv.slice(2));
|
|
1191
|
+
process.exit(code);
|
|
1192
|
+
}
|