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,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resource-probe.mjs — live RAM/CPU/process snapshot for adaptive session planning.
|
|
3
|
+
*
|
|
4
|
+
* Probes current host load at session-start (and optionally inside wave planning)
|
|
5
|
+
* so `agents-per-wave` and Docker-usage decisions can adapt to real resource
|
|
6
|
+
* pressure rather than static config defaults.
|
|
7
|
+
*
|
|
8
|
+
* Part of v3.1.0 Epic #157, Sub-Epic #158 (A+B). Issue #163.
|
|
9
|
+
* Extended in v3.2 Phase C-2 (#296): swap_used_mb + memory_pressure_pct_free signals.
|
|
10
|
+
*
|
|
11
|
+
* Snapshot shape:
|
|
12
|
+
* {
|
|
13
|
+
* timestamp: '2026-04-19T11:00:00Z',
|
|
14
|
+
* ram_free_gb: 3.2,
|
|
15
|
+
* ram_used_pct: 78,
|
|
16
|
+
* cpu_load_1m: 2.4,
|
|
17
|
+
* cpu_load_pct: 65,
|
|
18
|
+
* claude_processes_count: 3 | null,
|
|
19
|
+
* codex_processes_count: 0 | null,
|
|
20
|
+
* other_node_processes: 12 | null,
|
|
21
|
+
* zombie_processes_count: 1 | null,
|
|
22
|
+
* swap_used_mb: 512 | null,
|
|
23
|
+
* memory_pressure_pct_free: 42 | null,
|
|
24
|
+
* ram_available_gb: 82.5 | null, // macOS: free + reclaimable (vm_stat); null elsewhere (#667)
|
|
25
|
+
* probe_duration_ms: 45,
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* process-count fields fall back to `null` when the process-list command fails
|
|
29
|
+
* (e.g. sandboxed environments). Consumers treat `null` as "unknown".
|
|
30
|
+
*
|
|
31
|
+
* swap_used_mb: null on Windows/unknown or on spawn/parse failure.
|
|
32
|
+
* memory_pressure_pct_free: null on Linux/Windows/unknown or on failure.
|
|
33
|
+
*
|
|
34
|
+
* Implementation split in #287 (hotspot 2/2):
|
|
35
|
+
* - scripts/lib/resource-probe/parsers.mjs — pure output parsers
|
|
36
|
+
* - scripts/lib/resource-probe/probe-platform.mjs — platform sampling I/O
|
|
37
|
+
* - scripts/lib/resource-probe/evaluate.mjs — verdict + threshold logic
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { ramSnapshot, cpuSnapshot, processCounts, swapUsedMb, memoryPressurePctFree, ramAvailableGb } from './resource-probe/probe-platform.mjs';
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Re-exports — preserve public API for all existing callers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
export { evaluate } from './resource-probe/evaluate.mjs';
|
|
47
|
+
export { parseEtimeToMinutes, countZombieProcesses, countProcessMatches, parseSwapUsageOutput, parseMemoryPressureOutput, parseVmStatAvailableGb } from './resource-probe/parsers.mjs';
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Public API — probe()
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Capture a live resource snapshot of the current host.
|
|
55
|
+
* @param {object} [opts]
|
|
56
|
+
* @param {boolean} [opts.skipProcessCounts] — skip process listing (faster in tests)
|
|
57
|
+
* @param {boolean} [opts.skipExtendedSignals] — skip swap + memory_pressure calls (faster in tests)
|
|
58
|
+
* @param {number|null} [opts.zombieThresholdMin] — when non-null, detect zombie Claude/Node
|
|
59
|
+
* processes older than this many minutes with low CPU. Default null (feature disabled).
|
|
60
|
+
* @returns {Promise<object>}
|
|
61
|
+
*/
|
|
62
|
+
export async function probe(opts = {}) {
|
|
63
|
+
const start = Date.now();
|
|
64
|
+
const ram = ramSnapshot();
|
|
65
|
+
const cpu = cpuSnapshot();
|
|
66
|
+
const zombieThresholdMin = opts.zombieThresholdMin ?? null;
|
|
67
|
+
const procs = opts.skipProcessCounts
|
|
68
|
+
? { claude_processes_count: null, codex_processes_count: null, other_node_processes: null, zombie_processes_count: null }
|
|
69
|
+
: await processCounts(zombieThresholdMin);
|
|
70
|
+
|
|
71
|
+
let swap_used_mb = null;
|
|
72
|
+
let memory_pressure_pct_free = null;
|
|
73
|
+
let ram_available_gb = null;
|
|
74
|
+
if (!opts.skipExtendedSignals) {
|
|
75
|
+
[swap_used_mb, memory_pressure_pct_free, ram_available_gb] = await Promise.all([
|
|
76
|
+
swapUsedMb(),
|
|
77
|
+
memoryPressurePctFree(),
|
|
78
|
+
ramAvailableGb(),
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const duration = Date.now() - start;
|
|
83
|
+
return {
|
|
84
|
+
timestamp: new Date().toISOString(),
|
|
85
|
+
...ram,
|
|
86
|
+
...cpu,
|
|
87
|
+
...procs,
|
|
88
|
+
swap_used_mb,
|
|
89
|
+
memory_pressure_pct_free,
|
|
90
|
+
// macOS-only numeric available-RAM (free + reclaimable) from vm_stat; null
|
|
91
|
+
// on Linux/Windows/unknown where os.freemem() is already accurate (#667).
|
|
92
|
+
ram_available_gb,
|
|
93
|
+
probe_duration_ms: duration,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Loader — issue #336 glob-scoped rules + #694 rule-activation foundation
|
|
3
|
+
* + #795 `paths:` alias.
|
|
4
|
+
*
|
|
5
|
+
* Reads `.claude/rules/*.md` files, parses optional YAML frontmatter, and
|
|
6
|
+
* returns the subset of rules applicable to a given set of file paths and
|
|
7
|
+
* activation axes.
|
|
8
|
+
*
|
|
9
|
+
* Rules with no `globs:`/`paths:` frontmatter are always-on (loaded for every
|
|
10
|
+
* wave). Rules with `globs:` (or the `paths:` alias — issue #795) load only
|
|
11
|
+
* when at least one `scopePath` matches at least one glob pattern.
|
|
12
|
+
*
|
|
13
|
+
* `paths:` is a same-shape alias for `globs:` (issue #795) — some repos use a
|
|
14
|
+
* `paths:` frontmatter convention instead of `globs:` (e.g. projects-baseline:
|
|
15
|
+
* 26 rule files, all `paths:`, 0 `globs:`). Before #795 these repos'
|
|
16
|
+
* path-scoped rules were silently misclassified as always-on, inflating the
|
|
17
|
+
* `instruction-budget-guard.mjs` (#687) always-on count with a false positive.
|
|
18
|
+
* `paths:` supports the identical inline-array and block-list forms as
|
|
19
|
+
* `globs:`. Precedence when BOTH keys are present on the same rule: `globs:`
|
|
20
|
+
* wins (silently — no merge, no warning) and `paths:` is ignored entirely.
|
|
21
|
+
*
|
|
22
|
+
* A rule file MAY carry a leading single-line provenance header before its
|
|
23
|
+
* frontmatter block — the vendoring pipeline (`scripts/rules-sync.mjs`)
|
|
24
|
+
* requires a first-line `<!-- source: session-orchestrator plugin
|
|
25
|
+
* (canonical: ...) -->` comment on every vendored rule. The frontmatter
|
|
26
|
+
* parser tolerates any leading run of blank lines and/or single-line HTML
|
|
27
|
+
* comments before the opening `---`, so a vendored rule keeps its `globs:`
|
|
28
|
+
* scoping and scalar meta instead of silently falling back to always-on.
|
|
29
|
+
* Only single-line comments are tolerated (no multi-line comment blocks);
|
|
30
|
+
* files with no header, or starting directly with `---`, parse exactly as
|
|
31
|
+
* before.
|
|
32
|
+
*
|
|
33
|
+
* Beyond the `globs:` key (issue #336), the frontmatter parser also captures
|
|
34
|
+
* these scalar activation keys (issue #694), surfaced on each RuleEntry:
|
|
35
|
+
* - `description` (string)
|
|
36
|
+
* - `mode` (string) — see mode-gating below
|
|
37
|
+
* - `host-class` (string) — see host-class-gating below; surfaced as `hostClass`
|
|
38
|
+
* - `alwaysApply` (boolean) — DISTINCT from the `alwaysOn` "no globs" flag
|
|
39
|
+
* - `expires-at` (string) — ISO date; see expiry gating below; surfaced as `expiresAt`
|
|
40
|
+
* - `learning-key` (string) — surfaced as `learningKey`
|
|
41
|
+
* - `auto-generated`(boolean) — surfaced as `autoGenerated`
|
|
42
|
+
* - `confidence` (number)
|
|
43
|
+
* Unknown keys are still ignored without error, and malformed frontmatter still
|
|
44
|
+
* falls back to always-on (a rule is never silently dropped).
|
|
45
|
+
*
|
|
46
|
+
* Deterministic gating (issue #694) runs after a successful frontmatter parse
|
|
47
|
+
* and applies to BOTH always-on and glob-matched candidate rules — a rule must
|
|
48
|
+
* pass ALL active gates to be included:
|
|
49
|
+
* - Expiry: a rule with a parseable `expires-at` strictly before `now` is
|
|
50
|
+
* EXCLUDED (with a mandatory stderr WARN). A malformed `expires-at` never
|
|
51
|
+
* excludes (fail-open).
|
|
52
|
+
* - Mode-gating: when the `mode` param is non-null and the rule declares a
|
|
53
|
+
* `mode` that differs, the rule is EXCLUDED. A null `mode` param disables
|
|
54
|
+
* mode filtering; a rule without a `mode` key always passes.
|
|
55
|
+
* - Host-class-gating: identical logic against the `hostClass` param vs the
|
|
56
|
+
* rule's `host-class` value.
|
|
57
|
+
*
|
|
58
|
+
* Parse errors fall back to always-on — a rule is never silently dropped.
|
|
59
|
+
*
|
|
60
|
+
* @module rule-loader
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
64
|
+
import { join, extname } from 'node:path';
|
|
65
|
+
import { createRequire } from 'node:module';
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Picomatch integration (available in the project's node_modules).
|
|
69
|
+
// We resolve it dynamically so the module stays loadable in environments where
|
|
70
|
+
// picomatch is absent — the inline fallback glob-to-RegExp takes over then.
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
let _picomatch = null;
|
|
74
|
+
|
|
75
|
+
function getPicomatch() {
|
|
76
|
+
if (_picomatch !== null) return _picomatch;
|
|
77
|
+
try {
|
|
78
|
+
const require = createRequire(import.meta.url);
|
|
79
|
+
_picomatch = require('picomatch');
|
|
80
|
+
} catch {
|
|
81
|
+
// picomatch not available — will use inline fallback
|
|
82
|
+
_picomatch = false;
|
|
83
|
+
}
|
|
84
|
+
return _picomatch;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Minimal glob-to-RegExp fallback used only when picomatch is absent.
|
|
89
|
+
* Handles `**`, `*`, and literal character matching.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} pattern
|
|
92
|
+
* @returns {RegExp}
|
|
93
|
+
*/
|
|
94
|
+
function globToRegExp(pattern) {
|
|
95
|
+
// Normalize Windows separators
|
|
96
|
+
const p = pattern.replace(/\\/g, '/');
|
|
97
|
+
let re = '';
|
|
98
|
+
let i = 0;
|
|
99
|
+
while (i < p.length) {
|
|
100
|
+
const c = p[i];
|
|
101
|
+
if (c === '*' && p[i + 1] === '*') {
|
|
102
|
+
// `**` — match zero or more path segments
|
|
103
|
+
re += '.*';
|
|
104
|
+
i += 2;
|
|
105
|
+
// Consume optional trailing separator
|
|
106
|
+
if (p[i] === '/') i++;
|
|
107
|
+
} else if (c === '*') {
|
|
108
|
+
// `*` — match any characters except separator
|
|
109
|
+
re += '[^/]*';
|
|
110
|
+
i++;
|
|
111
|
+
} else if (c === '?') {
|
|
112
|
+
re += '[^/]';
|
|
113
|
+
i++;
|
|
114
|
+
} else if (c === '.') {
|
|
115
|
+
re += '\\.';
|
|
116
|
+
i++;
|
|
117
|
+
} else {
|
|
118
|
+
// Escape regex-special characters
|
|
119
|
+
re += c.replace(/[$()+[\]^{|}]/g, '\\$&');
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return new RegExp(`^${re}$`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Returns true when `filePath` matches `globPattern` using picomatch (or the
|
|
128
|
+
* inline fallback when picomatch is absent).
|
|
129
|
+
*
|
|
130
|
+
* @param {string} filePath - path relative to repo root, forward-slash separated
|
|
131
|
+
* @param {string} globPattern
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
134
|
+
function matchGlob(filePath, globPattern) {
|
|
135
|
+
const pm = getPicomatch();
|
|
136
|
+
if (pm) {
|
|
137
|
+
return pm.isMatch(filePath, globPattern, { dot: true });
|
|
138
|
+
}
|
|
139
|
+
return globToRegExp(globPattern).test(filePath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Minimal YAML frontmatter parser (matches state-md.mjs style)
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
147
|
+
|
|
148
|
+
// A single header line is either blank or a complete single-line HTML
|
|
149
|
+
// comment (`<!-- ... -->` on one line). Multi-line comment blocks are
|
|
150
|
+
// deliberately NOT supported — the vendoring convention is a single first
|
|
151
|
+
// line, and tolerating a few stacked comment lines (each matching this RE)
|
|
152
|
+
// is sufficient.
|
|
153
|
+
const HEADER_LINE_RE = /^[ \t]*(?:<!--.*-->)?[ \t]*$/;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Skips a leading run of blank lines and/or single-line HTML comments
|
|
157
|
+
* (`<!-- ... -->`) at the very start of a rule file's contents, so the
|
|
158
|
+
* frontmatter opener (`---`) can be found even when the vendoring pipeline's
|
|
159
|
+
* mandatory provenance header precedes it. Stops at the first line that is
|
|
160
|
+
* neither blank nor a complete single-line comment.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} contents - raw file contents
|
|
163
|
+
* @returns {string} contents starting at the first non-header line (empty
|
|
164
|
+
* string when the entire content is header lines)
|
|
165
|
+
*/
|
|
166
|
+
function stripLeadingProvenanceHeader(contents) {
|
|
167
|
+
const lines = contents.split(/\r?\n/);
|
|
168
|
+
let idx = 0;
|
|
169
|
+
while (idx < lines.length && HEADER_LINE_RE.test(lines[idx])) {
|
|
170
|
+
idx++;
|
|
171
|
+
}
|
|
172
|
+
return lines.slice(idx).join('\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Scalar activation keys captured (issue #694 + #692). The `globs` key keeps
|
|
176
|
+
// its dedicated sequence/flow handling and is NOT routed through this table.
|
|
177
|
+
const SCALAR_META_KEYS = new Set([
|
|
178
|
+
'description',
|
|
179
|
+
'mode',
|
|
180
|
+
'host-class',
|
|
181
|
+
'alwaysApply',
|
|
182
|
+
'expires-at',
|
|
183
|
+
'learning-key',
|
|
184
|
+
'auto-generated',
|
|
185
|
+
'confidence',
|
|
186
|
+
'tier',
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Parses the YAML frontmatter block for the `globs:` field (issue #336),
|
|
191
|
+
* its `paths:` alias (issue #795), and the scalar activation keys (issue #694).
|
|
192
|
+
*
|
|
193
|
+
* Returns:
|
|
194
|
+
* - `{ globs: string[] | null, meta: object }` on success
|
|
195
|
+
* - throws `Error` on malformed frontmatter so the caller can fall back
|
|
196
|
+
*
|
|
197
|
+
* `globs` is `null` when no frontmatter, and neither `globs:` nor `paths:` is
|
|
198
|
+
* present (always-on). `paths:` is parsed with the identical inline-array and
|
|
199
|
+
* block-list forms as `globs:`; when BOTH keys are present on the same rule,
|
|
200
|
+
* `globs:` wins silently and `paths:` is discarded (no merge, no warning —
|
|
201
|
+
* see module doc). The returned shape is unchanged either way — callers never
|
|
202
|
+
* see which key produced the value. `meta` carries only the recognised scalar
|
|
203
|
+
* keys that were present, with type coercion applied:
|
|
204
|
+
* - `alwaysApply`, `auto-generated` → boolean ('true'/'false')
|
|
205
|
+
* - `confidence` → number (undefined when NaN)
|
|
206
|
+
* - all other recognised keys → quote-stripped strings
|
|
207
|
+
* Unknown keys are ignored without error.
|
|
208
|
+
*
|
|
209
|
+
* A leading run of blank lines and/or single-line HTML comments (e.g. the
|
|
210
|
+
* vendoring pipeline's provenance header) is tolerated before the opening
|
|
211
|
+
* `---` — see `stripLeadingProvenanceHeader`. Files with no such header parse
|
|
212
|
+
* identically to before this tolerance was added.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} contents - raw file contents
|
|
215
|
+
* @returns {{ globs: string[] | null, meta: Record<string, unknown> }}
|
|
216
|
+
*/
|
|
217
|
+
export function parseGlobsFrontmatter(contents) {
|
|
218
|
+
const match = FRONTMATTER_RE.exec(stripLeadingProvenanceHeader(contents));
|
|
219
|
+
if (!match) return { globs: null, meta: {} };
|
|
220
|
+
|
|
221
|
+
const fmText = match[1];
|
|
222
|
+
const lines = fmText.split(/\r?\n/);
|
|
223
|
+
|
|
224
|
+
let globsValue = null;
|
|
225
|
+
let pathsValue = null;
|
|
226
|
+
// Which sequence-value key ('globs' | 'paths') is currently accumulating
|
|
227
|
+
// block-style ` - value` lines, or null when not inside either block.
|
|
228
|
+
let activeSeqKey = null;
|
|
229
|
+
/** @type {Record<string, unknown>} */
|
|
230
|
+
const meta = {};
|
|
231
|
+
|
|
232
|
+
for (const line of lines) {
|
|
233
|
+
const rstripped = line.replace(/\s+$/, '');
|
|
234
|
+
|
|
235
|
+
// Skip blank lines and comments
|
|
236
|
+
if (rstripped === '' || /^\s*#/.test(rstripped)) {
|
|
237
|
+
// If inside a `globs:`/`paths:` block, a blank line ends the sequence
|
|
238
|
+
// only if the next non-blank line is at col 0 (a new key). We just keep
|
|
239
|
+
// activeSeqKey set until we hit a non-indented non-blank line.
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (activeSeqKey) {
|
|
244
|
+
// Inside a `globs:`/`paths:` block — expect ` - value` indented lines
|
|
245
|
+
const seqMatch = rstripped.match(/^(\s+)-\s+(.*)/);
|
|
246
|
+
if (seqMatch) {
|
|
247
|
+
const raw = seqMatch[2].trim().replace(/^["']|["']$/g, '');
|
|
248
|
+
if (activeSeqKey === 'globs') {
|
|
249
|
+
if (!Array.isArray(globsValue)) globsValue = [];
|
|
250
|
+
globsValue.push(raw);
|
|
251
|
+
} else {
|
|
252
|
+
if (!Array.isArray(pathsValue)) pathsValue = [];
|
|
253
|
+
pathsValue.push(raw);
|
|
254
|
+
}
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
// Non-indented line — end of the sequence block
|
|
258
|
+
activeSeqKey = null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Top-level key detection
|
|
262
|
+
if (/^\s/.test(rstripped)) {
|
|
263
|
+
// Indented but not in a known block — skip (another block's continuation)
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const colonIdx = rstripped.indexOf(':');
|
|
268
|
+
if (colonIdx === -1) {
|
|
269
|
+
throw new Error(`Malformed frontmatter line: ${JSON.stringify(rstripped)}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const key = rstripped.slice(0, colonIdx).trim();
|
|
273
|
+
const valuePart = rstripped.slice(colonIdx + 1).trim();
|
|
274
|
+
|
|
275
|
+
if (key === 'globs' || key === 'paths') {
|
|
276
|
+
let parsedValue;
|
|
277
|
+
if (valuePart === '') {
|
|
278
|
+
// Block-style list follows
|
|
279
|
+
activeSeqKey = key;
|
|
280
|
+
parsedValue = [];
|
|
281
|
+
} else if (valuePart.startsWith('[') && valuePart.endsWith(']')) {
|
|
282
|
+
// Flow-style: globs: ["src/**", "lib/**"] (or paths: [...])
|
|
283
|
+
const inner = valuePart.slice(1, -1).trim();
|
|
284
|
+
parsedValue = inner === ''
|
|
285
|
+
? []
|
|
286
|
+
: inner.split(',').map((s) => s.trim().replace(/^["']|["']$/g, ''));
|
|
287
|
+
} else {
|
|
288
|
+
// Single inline value (unusual but handle gracefully)
|
|
289
|
+
parsedValue = [valuePart.replace(/^["']|["']$/g, '')];
|
|
290
|
+
}
|
|
291
|
+
if (key === 'globs') globsValue = parsedValue;
|
|
292
|
+
else pathsValue = parsedValue;
|
|
293
|
+
} else if (SCALAR_META_KEYS.has(key)) {
|
|
294
|
+
// Scalar activation key (issue #694). Strip surrounding quotes, then
|
|
295
|
+
// apply per-key coercion. Empty values are skipped (key not present).
|
|
296
|
+
const stripped = valuePart.replace(/^["']|["']$/g, '');
|
|
297
|
+
if (stripped === '') continue;
|
|
298
|
+
if (key === 'alwaysApply' || key === 'auto-generated') {
|
|
299
|
+
if (stripped === 'true') meta[key] = true;
|
|
300
|
+
else if (stripped === 'false') meta[key] = false;
|
|
301
|
+
// Any other value: leave undefined (not a recognised boolean)
|
|
302
|
+
} else if (key === 'confidence') {
|
|
303
|
+
const n = Number(stripped);
|
|
304
|
+
if (!Number.isNaN(n)) meta[key] = n;
|
|
305
|
+
} else {
|
|
306
|
+
meta[key] = stripped;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Ignore all other keys
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Precedence (issue #795): `globs:` wins silently when both keys are present.
|
|
313
|
+
const globs = globsValue !== null ? globsValue : pathsValue;
|
|
314
|
+
|
|
315
|
+
return { globs, meta };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Maps the raw (hyphenated) frontmatter meta keys onto the camelCase RuleEntry
|
|
320
|
+
* field names, including only keys that were actually present.
|
|
321
|
+
*
|
|
322
|
+
* @param {Record<string, unknown>} meta
|
|
323
|
+
* @returns {Record<string, unknown>}
|
|
324
|
+
*/
|
|
325
|
+
function metaToEntryFields(meta) {
|
|
326
|
+
/** @type {Record<string, unknown>} */
|
|
327
|
+
const out = {};
|
|
328
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'description')) out.description = meta.description;
|
|
329
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'mode')) out.mode = meta.mode;
|
|
330
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'host-class')) out.hostClass = meta['host-class'];
|
|
331
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'alwaysApply')) out.alwaysApply = meta.alwaysApply;
|
|
332
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'expires-at')) out.expiresAt = meta['expires-at'];
|
|
333
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'learning-key')) out.learningKey = meta['learning-key'];
|
|
334
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'auto-generated')) out.autoGenerated = meta['auto-generated'];
|
|
335
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'confidence')) out.confidence = meta.confidence;
|
|
336
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'tier')) out.tier = meta.tier;
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Deterministic gating check (issue #694 + #692). Returns `{ excluded: boolean,
|
|
342
|
+
* reason?: string }`. Applied to BOTH always-on and glob-matched rules.
|
|
343
|
+
*
|
|
344
|
+
* @param {Record<string, unknown>} meta - raw parsed meta (hyphenated keys)
|
|
345
|
+
* @param {string} filePath - absolute path (for WARN messages)
|
|
346
|
+
* @param {string|null} mode
|
|
347
|
+
* @param {string|null} hostClass
|
|
348
|
+
* @param {number} now - epoch ms
|
|
349
|
+
* @param {string|null} [context] - 'wave' | 'coordinator' | null (null = no tier gating)
|
|
350
|
+
* @returns {{ excluded: boolean }}
|
|
351
|
+
*/
|
|
352
|
+
function applyGates(meta, filePath, mode, hostClass, now, context = null) {
|
|
353
|
+
// Expiry gate — fail-open on a malformed `expires-at`.
|
|
354
|
+
if (Object.prototype.hasOwnProperty.call(meta, 'expires-at')) {
|
|
355
|
+
const rawExpiry = meta['expires-at'];
|
|
356
|
+
const ts = Date.parse(String(rawExpiry));
|
|
357
|
+
if (Number.isNaN(ts)) {
|
|
358
|
+
process.stderr.write(
|
|
359
|
+
`[rule-loader] Rule ${filePath} has unparseable expires-at ${JSON.stringify(rawExpiry)} — ignoring expiry\n`,
|
|
360
|
+
);
|
|
361
|
+
} else if (ts < now) {
|
|
362
|
+
process.stderr.write(
|
|
363
|
+
`[rule-loader] Rule ${filePath} expired at ${rawExpiry} — excluded\n`,
|
|
364
|
+
);
|
|
365
|
+
return { excluded: true };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Mode gate — only active when a non-null `mode` param is supplied AND the
|
|
370
|
+
// rule declares a differing `mode`.
|
|
371
|
+
if (mode !== null && Object.prototype.hasOwnProperty.call(meta, 'mode') && meta.mode !== mode) {
|
|
372
|
+
return { excluded: true };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Host-class gate — symmetric to the mode gate.
|
|
376
|
+
if (
|
|
377
|
+
hostClass !== null &&
|
|
378
|
+
Object.prototype.hasOwnProperty.call(meta, 'host-class') &&
|
|
379
|
+
meta['host-class'] !== hostClass
|
|
380
|
+
) {
|
|
381
|
+
return { excluded: true };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Tier gate (issue #692) — only active when context is non-null.
|
|
385
|
+
// BACKWARD-COMPAT: context === null → no tier gating whatsoever.
|
|
386
|
+
if (context !== null && Object.prototype.hasOwnProperty.call(meta, 'tier')) {
|
|
387
|
+
const tier = meta.tier;
|
|
388
|
+
if (context === 'wave' && tier === 'coordinator-only') {
|
|
389
|
+
return { excluded: true };
|
|
390
|
+
}
|
|
391
|
+
if (context === 'coordinator' && tier === 'wave-only') {
|
|
392
|
+
return { excluded: true };
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return { excluded: false };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
// Public API
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* @typedef {object} RuleEntry
|
|
405
|
+
* @property {string} path - absolute path to the rule file
|
|
406
|
+
* @property {string} content - raw file contents (byte-identical to disk)
|
|
407
|
+
* @property {boolean} alwaysOn - true when neither `globs:` nor its `paths:`
|
|
408
|
+
* alias (issue #795) frontmatter is present (UNCHANGED semantics; DISTINCT
|
|
409
|
+
* from the `alwaysApply` frontmatter key)
|
|
410
|
+
* @property {string[]} matchedGlobs - globs that matched; empty when alwaysOn
|
|
411
|
+
* @property {true} [_parseError] - present only on frontmatter parse-error entries
|
|
412
|
+
* @property {string} [description] - frontmatter `description` (issue #694), when present
|
|
413
|
+
* @property {string} [mode] - frontmatter `mode` (issue #694), when present
|
|
414
|
+
* @property {string} [hostClass] - frontmatter `host-class` (issue #694), when present
|
|
415
|
+
* @property {boolean} [alwaysApply] - frontmatter `alwaysApply` (issue #694), when present
|
|
416
|
+
* @property {string} [expiresAt] - frontmatter `expires-at` ISO date (issue #694), when present
|
|
417
|
+
* @property {string} [learningKey] - frontmatter `learning-key` (issue #694), when present
|
|
418
|
+
* @property {boolean} [autoGenerated] - frontmatter `auto-generated` (issue #694), when present
|
|
419
|
+
* @property {number} [confidence] - frontmatter `confidence` (issue #694), when present
|
|
420
|
+
* @property {string} [tier] - frontmatter `tier` (issue #692): 'coordinator-only' | 'wave-only' | 'always', when present
|
|
421
|
+
*/
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Loads rule files from `rulesDir` and returns those applicable to the given
|
|
425
|
+
* `scopePaths` and activation axes.
|
|
426
|
+
*
|
|
427
|
+
* Rules without `globs:` (or its `paths:` alias — issue #795) frontmatter are
|
|
428
|
+
* always included (alwaysOn: true). Rules with `globs:`/`paths:` are included
|
|
429
|
+
* only when at least one scopePath matches at least one glob pattern.
|
|
430
|
+
*
|
|
431
|
+
* After a successful frontmatter parse, deterministic gates (issue #694) are
|
|
432
|
+
* applied to BOTH always-on and glob-matched rules — a rule must pass ALL
|
|
433
|
+
* active gates (expiry + mode + host-class) to be included. The new params are
|
|
434
|
+
* strictly optional and default to "no gating", so the issue #336 two-key call
|
|
435
|
+
* shape stays 100% backward-compatible.
|
|
436
|
+
*
|
|
437
|
+
* On frontmatter parse error, the rule is treated as always-on and a warning
|
|
438
|
+
* is emitted to stderr — rules are never silently dropped. (Parse-error rules
|
|
439
|
+
* carry no meta, so they pass every gate.)
|
|
440
|
+
*
|
|
441
|
+
* @param {object} opts
|
|
442
|
+
* @param {string} opts.rulesDir - absolute path to the directory containing rule *.md files
|
|
443
|
+
* @param {string[]} [opts.scopePaths] - paths (relative to repo root) to match against globs
|
|
444
|
+
* @param {string|null} [opts.mode] - when non-null, exclude rules whose `mode` differs
|
|
445
|
+
* @param {string|null} [opts.hostClass] - when non-null, exclude rules whose `host-class` differs
|
|
446
|
+
* @param {number} [opts.now] - epoch ms used for expiry gating (injectable for tests)
|
|
447
|
+
* @param {string|null} [opts.context] - 'wave' | 'coordinator' | null; when non-null, applies
|
|
448
|
+
* tier gating: 'wave' excludes coordinator-only rules, 'coordinator' excludes wave-only rules.
|
|
449
|
+
* null (default) disables tier gating entirely — backward-compatible with all existing callers.
|
|
450
|
+
* @returns {RuleEntry[]}
|
|
451
|
+
*/
|
|
452
|
+
export function loadApplicableRules({
|
|
453
|
+
rulesDir,
|
|
454
|
+
scopePaths = [],
|
|
455
|
+
mode = null,
|
|
456
|
+
hostClass = null,
|
|
457
|
+
now = Date.now(),
|
|
458
|
+
context = null,
|
|
459
|
+
}) {
|
|
460
|
+
/** @type {string[]} */
|
|
461
|
+
let entries;
|
|
462
|
+
try {
|
|
463
|
+
entries = readdirSync(rulesDir);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
process.stderr.write(`[rule-loader] Cannot read rulesDir ${rulesDir}: ${err.message}\n`);
|
|
466
|
+
return [];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Normalize scopePaths to forward slashes for consistent matching
|
|
470
|
+
const normalizedScope = scopePaths.map((p) => p.replace(/\\/g, '/'));
|
|
471
|
+
|
|
472
|
+
/** @type {RuleEntry[]} */
|
|
473
|
+
const results = [];
|
|
474
|
+
|
|
475
|
+
for (const entry of entries) {
|
|
476
|
+
if (extname(entry) !== '.md') continue;
|
|
477
|
+
|
|
478
|
+
const filePath = join(rulesDir, entry);
|
|
479
|
+
let content;
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
content = readFileSync(filePath, 'utf8');
|
|
483
|
+
} catch (err) {
|
|
484
|
+
process.stderr.write(`[rule-loader] Cannot read ${filePath}: ${err.message}\n`);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
let globs;
|
|
489
|
+
let meta;
|
|
490
|
+
let parseError = false;
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
({ globs, meta } = parseGlobsFrontmatter(content));
|
|
494
|
+
} catch (err) {
|
|
495
|
+
process.stderr.write(
|
|
496
|
+
`[rule-loader] Frontmatter parse error in ${filePath}: ${err.message} — treating as always-on\n`,
|
|
497
|
+
);
|
|
498
|
+
globs = null;
|
|
499
|
+
meta = {};
|
|
500
|
+
parseError = true;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Deterministic gating (issue #694 + #692) — applies to BOTH always-on and
|
|
504
|
+
// glob-matched candidates. Parse-error rules carry empty meta, so they pass
|
|
505
|
+
// every gate (preserving fail-open behavior).
|
|
506
|
+
if (!parseError && applyGates(meta, filePath, mode, hostClass, now, context).excluded) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Camel-cased meta fields surfaced on the entry (only present keys).
|
|
511
|
+
const metaFields = metaToEntryFields(meta);
|
|
512
|
+
|
|
513
|
+
if (globs === null) {
|
|
514
|
+
// No globs frontmatter (or parse error) → always-on
|
|
515
|
+
results.push({
|
|
516
|
+
path: filePath,
|
|
517
|
+
content,
|
|
518
|
+
alwaysOn: true,
|
|
519
|
+
matchedGlobs: [],
|
|
520
|
+
...metaFields,
|
|
521
|
+
...(parseError ? { _parseError: true } : {}),
|
|
522
|
+
});
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (globs.length === 0) {
|
|
527
|
+
// Empty globs array: matches nothing (intentionally scoped out)
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Check whether any scopePath matches any glob
|
|
532
|
+
const matchedGlobs = [];
|
|
533
|
+
for (const glob of globs) {
|
|
534
|
+
const matched = normalizedScope.some((sp) => matchGlob(sp, glob));
|
|
535
|
+
if (matched) {
|
|
536
|
+
matchedGlobs.push(glob);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (matchedGlobs.length > 0) {
|
|
541
|
+
results.push({
|
|
542
|
+
path: filePath,
|
|
543
|
+
content,
|
|
544
|
+
alwaysOn: false,
|
|
545
|
+
matchedGlobs,
|
|
546
|
+
...metaFields,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return results;
|
|
552
|
+
}
|