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,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evaluate.mjs — verdict + threshold logic for resource snapshots.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates a snapshot (output of probe()) against `resource-thresholds`
|
|
5
|
+
* from Session Config (#166) and returns a verdict used by session-start Phase 4.5.
|
|
6
|
+
*
|
|
7
|
+
* Part of v3.1.0 Epic #157 / Issue #163.
|
|
8
|
+
* Extended in v3.2 Phase C-2 (#296): swap + memory_pressure verdict rules.
|
|
9
|
+
* Zombie rule wired end-to-end in #178.
|
|
10
|
+
* Split from resource-probe.mjs in #287 (hotspot 2/2).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Verdict precedence helper
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
const VERDICT_RANK = { green: 0, warn: 1, degraded: 2, critical: 3 };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Return the more restrictive of two verdicts.
|
|
21
|
+
* @param {string} current
|
|
22
|
+
* @param {string} target
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
function bumpVerdict(current, target) {
|
|
26
|
+
const currentRank = VERDICT_RANK[current] ?? 0;
|
|
27
|
+
const targetRank = VERDICT_RANK[target] ?? 0;
|
|
28
|
+
return targetRank > currentRank ? target : current;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* macOS healthy-pressure threshold. When `memory_pressure_pct_free` ≥ this value,
|
|
33
|
+
* the OS reports that compressor + caches are doing their job, and `ram_free_gb`
|
|
34
|
+
* (which on macOS reflects only `Pages free`, not `inactive`) is misleading
|
|
35
|
+
* — the system has plenty of reclaimable memory. We suppress free-RAM-only
|
|
36
|
+
* verdict-bumps in that regime.
|
|
37
|
+
*
|
|
38
|
+
* Source: Apple Activity Monitor uses memory_pressure as the canonical health
|
|
39
|
+
* indicator since 10.9 Mavericks. See OSXDaily (2026-04) + Apple developer docs
|
|
40
|
+
* "Viewing Virtual Memory Usage". Threshold 30% matches the green/yellow
|
|
41
|
+
* boundary rendered by Activity Monitor (yellow at 30..50%, red at <30%).
|
|
42
|
+
*/
|
|
43
|
+
const MACOS_HEALTHY_PRESSURE_PCT = 30;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Evaluate a snapshot against `resource-thresholds` (from Session Config #166)
|
|
47
|
+
* and return a verdict used by session-start Phase 4.5.
|
|
48
|
+
* @param {object} snapshot — output of probe()
|
|
49
|
+
* @param {object} thresholds — resource-thresholds block from parseSessionConfig
|
|
50
|
+
* @returns {{verdict: 'green'|'warn'|'degraded'|'critical', reasons: string[], recommended_agents_per_wave_cap: number|null}}
|
|
51
|
+
*/
|
|
52
|
+
export function evaluate(snapshot, thresholds) {
|
|
53
|
+
const reasons = [];
|
|
54
|
+
let verdict = 'green';
|
|
55
|
+
let cap = null;
|
|
56
|
+
|
|
57
|
+
const { ram_free_gb, cpu_load_pct, claude_processes_count, memory_pressure_pct_free, ram_available_gb } = snapshot;
|
|
58
|
+
const {
|
|
59
|
+
'ram-free-min-gb': ramMin,
|
|
60
|
+
'ram-free-critical-gb': ramCrit,
|
|
61
|
+
'cpu-load-max-pct': cpuMax,
|
|
62
|
+
'concurrent-sessions-warn': concWarn,
|
|
63
|
+
} = thresholds;
|
|
64
|
+
|
|
65
|
+
// On macOS, `os.freemem()` reports only `Pages free` (excludes `inactive`),
|
|
66
|
+
// so `ram_free_gb` regularly reads sub-1 GB even when the system has 80+ GB
|
|
67
|
+
// reclaimable. The robust fix (#667): when `ram_available_gb` (free +
|
|
68
|
+
// inactive + speculative + purgeable, derived from vm_stat) is present, judge
|
|
69
|
+
// RAM-criticality on AVAILABLE rather than FREE. The `memory_pressure`
|
|
70
|
+
// healthy-suppression below is retained as a secondary guard for the case
|
|
71
|
+
// where vm_stat is unavailable but memory_pressure still reports.
|
|
72
|
+
const hasAvailable =
|
|
73
|
+
ram_available_gb !== null && ram_available_gb !== undefined;
|
|
74
|
+
const effectiveRamGb = hasAvailable ? ram_available_gb : ram_free_gb;
|
|
75
|
+
const ramLabel = hasAvailable ? 'RAM available' : 'RAM free';
|
|
76
|
+
|
|
77
|
+
const macosPressureHealthy =
|
|
78
|
+
memory_pressure_pct_free !== null &&
|
|
79
|
+
memory_pressure_pct_free !== undefined &&
|
|
80
|
+
memory_pressure_pct_free >= MACOS_HEALTHY_PRESSURE_PCT;
|
|
81
|
+
|
|
82
|
+
if (!macosPressureHealthy) {
|
|
83
|
+
if (effectiveRamGb < ramCrit) {
|
|
84
|
+
verdict = 'critical';
|
|
85
|
+
cap = 0;
|
|
86
|
+
reasons.push(`${ramLabel} ${effectiveRamGb.toFixed(1)} GB below critical threshold ${ramCrit} GB — recommend coordinator-direct (0 agents).`);
|
|
87
|
+
} else if (effectiveRamGb < ramMin) {
|
|
88
|
+
if (verdict === 'green') verdict = 'warn';
|
|
89
|
+
cap = cap === null ? 2 : Math.min(cap, 2);
|
|
90
|
+
reasons.push(`${ramLabel} ${effectiveRamGb.toFixed(1)} GB below threshold ${ramMin} GB — capping agents-per-wave at 2.`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
reasons.push(`macOS memory_pressure healthy (${memory_pressure_pct_free}% free ≥ ${MACOS_HEALTHY_PRESSURE_PCT}%) — free-RAM signal suppressed (Pages-free underreports on Darwin).`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (cpu_load_pct > cpuMax) {
|
|
97
|
+
if (verdict === 'green') verdict = 'warn';
|
|
98
|
+
cap = cap === null ? 2 : Math.min(cap, 2);
|
|
99
|
+
reasons.push(`CPU load ${cpu_load_pct}% above threshold ${cpuMax}% — capping agents-per-wave at 2.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (claude_processes_count !== null && claude_processes_count !== undefined && claude_processes_count >= concWarn) {
|
|
103
|
+
if (verdict === 'green') verdict = 'warn';
|
|
104
|
+
reasons.push(`${claude_processes_count} Claude processes already running (threshold: ${concWarn}) — consider sequencing this session after others finish.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Zombie signal (#178): null = feature disabled (zombieThresholdMin absent) → no rule fires
|
|
109
|
+
// Escalates to warn only when BOTH zombie_processes_count >= 1 AND
|
|
110
|
+
// claude_processes_count is elevated (>= concWarn or > 0 with zombies).
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
const { zombie_processes_count } = snapshot;
|
|
113
|
+
if (zombie_processes_count !== null && zombie_processes_count !== undefined && zombie_processes_count >= 1) {
|
|
114
|
+
const claudeElevated =
|
|
115
|
+
claude_processes_count !== null &&
|
|
116
|
+
claude_processes_count !== undefined &&
|
|
117
|
+
claude_processes_count > 0;
|
|
118
|
+
if (claudeElevated) {
|
|
119
|
+
verdict = bumpVerdict(verdict, 'warn');
|
|
120
|
+
reasons.push(`${zombie_processes_count} zombie Claude/Node process(es) detected (age > ${thresholds['zombie-threshold-min'] ?? 30} min, idle CPU) with ${claude_processes_count} Claude process(es) running — consider sweeping stale sessions.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Swap signal (macOS / Linux only; null = signal unavailable → no rule fires)
|
|
126
|
+
// On macOS, swap is accumulated over time and is not a real-time pressure
|
|
127
|
+
// indicator — Activity Monitor reports it under "Used" but classifies system
|
|
128
|
+
// health via memory_pressure, not swap volume. So when pressure is healthy,
|
|
129
|
+
// swap signal is treated as informational only (downgrades critical→warn).
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
const { swap_used_mb } = snapshot;
|
|
132
|
+
if (swap_used_mb !== null && swap_used_mb !== undefined) {
|
|
133
|
+
if (swap_used_mb > 3072 && !macosPressureHealthy) {
|
|
134
|
+
verdict = bumpVerdict(verdict, 'critical');
|
|
135
|
+
cap = 0;
|
|
136
|
+
reasons.push(`Swap usage ${swap_used_mb} MB above critical threshold 3072 MB — recommend coordinator-direct (0 agents).`);
|
|
137
|
+
} else if (swap_used_mb > 2048 && !macosPressureHealthy) {
|
|
138
|
+
const newVerdict = bumpVerdict(verdict, 'degraded');
|
|
139
|
+
if (newVerdict !== verdict) {
|
|
140
|
+
verdict = newVerdict;
|
|
141
|
+
cap = cap === null ? 2 : Math.min(cap, 2);
|
|
142
|
+
}
|
|
143
|
+
reasons.push(`Swap usage ${swap_used_mb} MB in degraded range (2048..3072 MB) — capping agents-per-wave at 2.`);
|
|
144
|
+
} else if (swap_used_mb > 1024 && !macosPressureHealthy) {
|
|
145
|
+
const newVerdict = bumpVerdict(verdict, 'warn');
|
|
146
|
+
if (newVerdict !== verdict) {
|
|
147
|
+
verdict = newVerdict;
|
|
148
|
+
cap = cap === null ? 2 : Math.min(cap, 2);
|
|
149
|
+
}
|
|
150
|
+
reasons.push(`Swap usage ${swap_used_mb} MB in warn range (1024..2048 MB) — capping agents-per-wave at 2.`);
|
|
151
|
+
} else if (swap_used_mb > 1024 && macosPressureHealthy) {
|
|
152
|
+
// Informational only: pressure says system is healthy, swap is historical.
|
|
153
|
+
reasons.push(`Swap usage ${swap_used_mb} MB present but macOS memory_pressure healthy — treating as informational (no cap).`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// macOS memory_pressure signal (null = signal unavailable → no rule fires)
|
|
159
|
+
// (memory_pressure_pct_free already destructured at the top of this fn)
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
if (memory_pressure_pct_free !== null && memory_pressure_pct_free !== undefined) {
|
|
162
|
+
if (memory_pressure_pct_free < 5) {
|
|
163
|
+
verdict = bumpVerdict(verdict, 'critical');
|
|
164
|
+
cap = 0;
|
|
165
|
+
reasons.push(`macOS memory_pressure free ${memory_pressure_pct_free}% below critical threshold 5% — recommend coordinator-direct (0 agents).`);
|
|
166
|
+
} else if (memory_pressure_pct_free < 15) {
|
|
167
|
+
const newVerdict = bumpVerdict(verdict, 'degraded');
|
|
168
|
+
if (newVerdict !== verdict) {
|
|
169
|
+
verdict = newVerdict;
|
|
170
|
+
cap = cap === null ? 2 : Math.min(cap, 2);
|
|
171
|
+
}
|
|
172
|
+
reasons.push(`macOS memory_pressure free ${memory_pressure_pct_free}% in degraded range (5..15%) — capping agents-per-wave at 2.`);
|
|
173
|
+
} else if (memory_pressure_pct_free < 30) {
|
|
174
|
+
const newVerdict = bumpVerdict(verdict, 'warn');
|
|
175
|
+
if (newVerdict !== verdict) {
|
|
176
|
+
verdict = newVerdict;
|
|
177
|
+
cap = cap === null ? 2 : Math.min(cap, 2);
|
|
178
|
+
}
|
|
179
|
+
reasons.push(`macOS memory_pressure free ${memory_pressure_pct_free}% in warn range (15..30%) — capping agents-per-wave at 2.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Ensure cap aligns with final verdict when critical was triggered by swap/memory_pressure
|
|
184
|
+
// (the bumpVerdict path sets cap=0 inline for critical, but re-affirm for safety)
|
|
185
|
+
if (verdict === 'critical') {
|
|
186
|
+
cap = 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { verdict, reasons, recommended_agents_per_wave_cap: cap };
|
|
190
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parsers.mjs — pure output-parser helpers for resource-probe signals.
|
|
3
|
+
*
|
|
4
|
+
* All functions are pure (no I/O) and exported for unit testability.
|
|
5
|
+
* Consumed by probe-platform.mjs and re-exported from resource-probe.mjs.
|
|
6
|
+
*
|
|
7
|
+
* Part of v3.1.0 Epic #157 / Issue #163.
|
|
8
|
+
* Extended in v3.2 Phase C-2 (#296): swap_used_mb + memory_pressure_pct_free parsers.
|
|
9
|
+
* Split from resource-probe.mjs in #287 (hotspot 2/2).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse a `ps` etime field (`[[DD-]HH:]MM:SS`) into whole minutes.
|
|
14
|
+
* Returns null when the format is unrecognised.
|
|
15
|
+
* Pure function for unit testability.
|
|
16
|
+
* @param {string} etime
|
|
17
|
+
* @returns {number|null}
|
|
18
|
+
*/
|
|
19
|
+
export function parseEtimeToMinutes(etime) {
|
|
20
|
+
if (typeof etime !== 'string') return null;
|
|
21
|
+
const s = etime.trim();
|
|
22
|
+
// Regex: optional "DD-", optional "HH:", then "MM:SS"
|
|
23
|
+
const m = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(s);
|
|
24
|
+
if (!m) return null;
|
|
25
|
+
const days = parseInt(m[1] ?? '0', 10);
|
|
26
|
+
const hours = parseInt(m[2] ?? '0', 10);
|
|
27
|
+
const mins = parseInt(m[3], 10);
|
|
28
|
+
// seconds intentionally dropped (we only need minutes resolution)
|
|
29
|
+
if ([days, hours, mins].some(Number.isNaN)) return null;
|
|
30
|
+
return days * 24 * 60 + hours * 60 + mins;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse the detailed ps output and count Claude/Node zombie candidates.
|
|
35
|
+
* A zombie candidate is a process matching "claude" or "node" that:
|
|
36
|
+
* - has been running longer than `thresholdMin` minutes, AND
|
|
37
|
+
* - has CPU% at or below `maxCpuPct` (default 1.0 → idle).
|
|
38
|
+
* Pure function for unit testability.
|
|
39
|
+
* @param {string|null} psOutput — stdout from `ps -A -o pid,comm,etime,%cpu`
|
|
40
|
+
* @param {number} thresholdMin — age threshold in minutes
|
|
41
|
+
* @param {number} [maxCpuPct] — CPU% at-or-below which process is considered idle (default 1.0)
|
|
42
|
+
* @returns {number|null} count, or null when psOutput is null
|
|
43
|
+
*/
|
|
44
|
+
export function countZombieProcesses(psOutput, thresholdMin, maxCpuPct = 1.0) {
|
|
45
|
+
if (psOutput === null || psOutput === undefined) return null;
|
|
46
|
+
const lines = String(psOutput).split(/\r?\n/);
|
|
47
|
+
let count = 0;
|
|
48
|
+
for (const raw of lines) {
|
|
49
|
+
const trimmed = raw.trim();
|
|
50
|
+
if (!trimmed || /^\s*PID/i.test(trimmed)) continue; // skip header
|
|
51
|
+
// Fields: PID COMM ELAPSED %CPU (ELAPSED may contain '-' and ':')
|
|
52
|
+
// Split on whitespace but preserve etime which has no spaces.
|
|
53
|
+
const parts = trimmed.split(/\s+/);
|
|
54
|
+
if (parts.length < 4) continue;
|
|
55
|
+
// parts[0]=PID, parts[1]=COMM, parts[2]=ETIME, parts[3]=%CPU
|
|
56
|
+
const comm = (parts[1] ?? '').toLowerCase();
|
|
57
|
+
const etimeStr = parts[2] ?? '';
|
|
58
|
+
const cpuStr = parts[3] ?? '';
|
|
59
|
+
const isClaudeOrNode =
|
|
60
|
+
/(^|[\s/])claude([\s]|$)/.test(comm) ||
|
|
61
|
+
comm === 'claude' ||
|
|
62
|
+
/(^|[\s/])node([\s]|$)/.test(comm) ||
|
|
63
|
+
comm === 'node';
|
|
64
|
+
if (!isClaudeOrNode) continue;
|
|
65
|
+
const ageMin = parseEtimeToMinutes(etimeStr);
|
|
66
|
+
if (ageMin === null || ageMin < thresholdMin) continue;
|
|
67
|
+
const cpu = parseFloat(cpuStr);
|
|
68
|
+
if (Number.isNaN(cpu) || cpu > maxCpuPct) continue;
|
|
69
|
+
count++;
|
|
70
|
+
}
|
|
71
|
+
return count;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parse a raw process listing (ps or tasklist output) and count matches.
|
|
76
|
+
* Pure function for unit testability.
|
|
77
|
+
* @param {string|null} psOutput
|
|
78
|
+
* @param {Array<{key: string, match: (line: string) => boolean}>} patterns
|
|
79
|
+
* @returns {Record<string, number>|null}
|
|
80
|
+
*/
|
|
81
|
+
export function countProcessMatches(psOutput, patterns) {
|
|
82
|
+
if (psOutput === null || psOutput === undefined) return null;
|
|
83
|
+
const lines = String(psOutput).split(/\r?\n/);
|
|
84
|
+
const counts = Object.fromEntries(patterns.map((p) => [p.key, 0]));
|
|
85
|
+
for (const raw of lines) {
|
|
86
|
+
const line = raw.toLowerCase();
|
|
87
|
+
for (const p of patterns) {
|
|
88
|
+
if (p.match(line)) counts[p.key] += 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return counts;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parse `sysctl vm.swapusage` output (macOS) and return used MB as integer.
|
|
96
|
+
* Pure function for unit testability.
|
|
97
|
+
* @param {string} text
|
|
98
|
+
* @returns {number|null}
|
|
99
|
+
*/
|
|
100
|
+
export function parseSwapUsageOutput(text) {
|
|
101
|
+
if (text === null || text === undefined) return null;
|
|
102
|
+
// Sample: "vm.swapusage: total = 4096.00M used = 1234.50M free = 2861.50M"
|
|
103
|
+
const match = /used\s*=\s*([\d.]+)M/i.exec(String(text));
|
|
104
|
+
if (!match) return null;
|
|
105
|
+
const mb = parseFloat(match[1]);
|
|
106
|
+
if (Number.isNaN(mb)) return null;
|
|
107
|
+
return Math.round(mb);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parse `memory_pressure` output (macOS) and return the free percentage as integer.
|
|
112
|
+
* Pure function for unit testability.
|
|
113
|
+
* @param {string} text
|
|
114
|
+
* @returns {number|null}
|
|
115
|
+
*/
|
|
116
|
+
export function parseMemoryPressureOutput(text) {
|
|
117
|
+
if (text === null || text === undefined) return null;
|
|
118
|
+
// Sample: "System-wide memory free percentage: 42%"
|
|
119
|
+
const match = /System-wide memory free percentage:\s*(\d+)%/i.exec(String(text));
|
|
120
|
+
if (!match) return null;
|
|
121
|
+
const pct = parseInt(match[1], 10);
|
|
122
|
+
if (Number.isNaN(pct)) return null;
|
|
123
|
+
return pct;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parse `vm_stat` output (macOS) and compute AVAILABLE RAM in GB.
|
|
128
|
+
*
|
|
129
|
+
* macOS treats most physical RAM as reclaimable cache, so `os.freemem()` (which
|
|
130
|
+
* reflects only `Pages free`) regularly reads sub-1 GB on a machine with 100+ GB
|
|
131
|
+
* of genuinely reclaimable headroom. The correct "how much can a new process
|
|
132
|
+
* actually claim?" estimate is:
|
|
133
|
+
*
|
|
134
|
+
* available = (free + inactive + speculative + purgeable) × page_size
|
|
135
|
+
*
|
|
136
|
+
* - `Pages free` — immediately allocatable.
|
|
137
|
+
* - `Pages inactive` — clean/dirty pages on the inactive queue, reclaimable
|
|
138
|
+
* under pressure (the macOS file/anon cache).
|
|
139
|
+
* - `Pages speculative` — read-ahead pages, dropped first under pressure.
|
|
140
|
+
* - `Pages purgeable` — volatile pages the kernel may discard on demand.
|
|
141
|
+
*
|
|
142
|
+
* `File-backed pages` overlaps the inactive queue, so it is intentionally NOT
|
|
143
|
+
* added (would double-count). This mirrors how Activity Monitor derives "free"
|
|
144
|
+
* memory and matches the formula in the issue spec (#667).
|
|
145
|
+
*
|
|
146
|
+
* Pure function for unit testability.
|
|
147
|
+
* @param {string} text — stdout from `vm_stat`
|
|
148
|
+
* @returns {number|null} available GB rounded to 1 decimal, or null on parse failure
|
|
149
|
+
*/
|
|
150
|
+
export function parseVmStatAvailableGb(text) {
|
|
151
|
+
if (text === null || text === undefined) return null;
|
|
152
|
+
const str = String(text);
|
|
153
|
+
// Page size is reported in the header: "page size of 16384 bytes".
|
|
154
|
+
const psMatch = /page size of (\d+) bytes/i.exec(str);
|
|
155
|
+
if (!psMatch) return null;
|
|
156
|
+
const pageSize = parseInt(psMatch[1], 10);
|
|
157
|
+
if (Number.isNaN(pageSize) || pageSize <= 0) return null;
|
|
158
|
+
|
|
159
|
+
// Each line looks like "Pages free: 10794." — value followed by a period.
|
|
160
|
+
const pagesFor = (label) => {
|
|
161
|
+
const re = new RegExp(`${label}:\\s+(\\d+)\\.`, 'i');
|
|
162
|
+
const m = re.exec(str);
|
|
163
|
+
if (!m) return null;
|
|
164
|
+
const n = parseInt(m[1], 10);
|
|
165
|
+
return Number.isNaN(n) ? null : n;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const free = pagesFor('Pages free');
|
|
169
|
+
const inactive = pagesFor('Pages inactive');
|
|
170
|
+
// `Pages free` and `Pages inactive` are the load-bearing terms; if either is
|
|
171
|
+
// absent the output is not recognisable vm_stat and we bail.
|
|
172
|
+
if (free === null || inactive === null) return null;
|
|
173
|
+
|
|
174
|
+
// speculative + purgeable are smaller reclaimable buckets; treat absent as 0.
|
|
175
|
+
const speculative = pagesFor('Pages speculative') ?? 0;
|
|
176
|
+
const purgeable = pagesFor('Pages purgeable') ?? 0;
|
|
177
|
+
|
|
178
|
+
const availablePages = free + inactive + speculative + purgeable;
|
|
179
|
+
const availableGb = (availablePages * pageSize) / 1024 ** 3;
|
|
180
|
+
return Math.round(availableGb * 10) / 10;
|
|
181
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* probe-platform.mjs — platform-specific resource sampling (RAM, CPU, processes, swap).
|
|
3
|
+
*
|
|
4
|
+
* Handles all async I/O: spawning `ps`/`tasklist`, reading `/proc/meminfo`,
|
|
5
|
+
* calling `sysctl`, `memory_pressure`. Pure parsing is delegated to parsers.mjs.
|
|
6
|
+
*
|
|
7
|
+
* Part of v3.1.0 Epic #157 / Issue #163.
|
|
8
|
+
* Extended in v3.2 Phase C-2 (#296): swap_used_mb + memory_pressure_pct_free signals.
|
|
9
|
+
* Split from resource-probe.mjs in #287 (hotspot 2/2).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { SO_IS_WINDOWS } from '../platform.mjs';
|
|
15
|
+
import {
|
|
16
|
+
countProcessMatches,
|
|
17
|
+
countZombieProcesses,
|
|
18
|
+
parseSwapUsageOutput,
|
|
19
|
+
parseMemoryPressureOutput,
|
|
20
|
+
parseVmStatAvailableGb,
|
|
21
|
+
} from './parsers.mjs';
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// RAM / CPU primitives (synchronous, via Node os module)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @returns {{ ram_free_gb: number, ram_used_pct: number }}
|
|
29
|
+
*/
|
|
30
|
+
export function ramSnapshot() {
|
|
31
|
+
const total = os.totalmem();
|
|
32
|
+
const free = os.freemem();
|
|
33
|
+
const usedPct = Math.round(((total - free) / total) * 100);
|
|
34
|
+
const freeGb = Math.round((free / (1024 * 1024 * 1024)) * 10) / 10;
|
|
35
|
+
return { ram_free_gb: freeGb, ram_used_pct: usedPct };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Derive CPU usage percentage from per-core times (used on Windows and when
|
|
40
|
+
* load-average is 0).
|
|
41
|
+
* @returns {number} 0-100
|
|
42
|
+
*/
|
|
43
|
+
function cpuPctFromTimes() {
|
|
44
|
+
const cpus = os.cpus() || [];
|
|
45
|
+
if (cpus.length === 0) return 0;
|
|
46
|
+
let totalBusy = 0;
|
|
47
|
+
let total = 0;
|
|
48
|
+
for (const c of cpus) {
|
|
49
|
+
const t = c.times;
|
|
50
|
+
const busy = t.user + t.nice + t.sys + t.irq;
|
|
51
|
+
totalBusy += busy;
|
|
52
|
+
total += busy + t.idle;
|
|
53
|
+
}
|
|
54
|
+
if (total === 0) return 0;
|
|
55
|
+
return Math.min(100, Math.round((totalBusy / total) * 100));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @returns {{ cpu_load_1m: number, cpu_load_pct: number }}
|
|
60
|
+
*/
|
|
61
|
+
export function cpuSnapshot() {
|
|
62
|
+
const load1m = os.loadavg()[0];
|
|
63
|
+
const cores = (os.cpus() || []).length || 1;
|
|
64
|
+
// load-average is a Unix concept and reports 0 on Windows. When zero, we
|
|
65
|
+
// fall back to per-core user+sys % derived from cpus() times (still rough,
|
|
66
|
+
// but non-zero on Windows so the threshold logic has a signal).
|
|
67
|
+
let loadPct;
|
|
68
|
+
if (SO_IS_WINDOWS || load1m === 0) {
|
|
69
|
+
loadPct = cpuPctFromTimes();
|
|
70
|
+
} else {
|
|
71
|
+
loadPct = Math.min(100, Math.round((load1m / cores) * 100));
|
|
72
|
+
}
|
|
73
|
+
return { cpu_load_1m: Math.round(load1m * 10) / 10, cpu_load_pct: loadPct };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Process listing helpers (async, shell-based)
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Run a command and collect stdout, with a timeout. Returns null on failure.
|
|
82
|
+
* @param {string} cmd
|
|
83
|
+
* @param {string[]} args
|
|
84
|
+
* @param {number} timeoutMs
|
|
85
|
+
* @returns {Promise<string|null>}
|
|
86
|
+
*/
|
|
87
|
+
export function runCommand(cmd, args, timeoutMs = 1500) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
let settled = false;
|
|
90
|
+
const finish = (value) => {
|
|
91
|
+
if (settled) return;
|
|
92
|
+
settled = true;
|
|
93
|
+
resolve(value);
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
97
|
+
const chunks = [];
|
|
98
|
+
child.stdout.on('data', (c) => chunks.push(c));
|
|
99
|
+
child.on('error', () => finish(null));
|
|
100
|
+
child.on('close', (code) => {
|
|
101
|
+
if (code !== 0) return finish(null);
|
|
102
|
+
finish(Buffer.concat(chunks).toString('utf8'));
|
|
103
|
+
});
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
try { child.kill('SIGKILL'); } catch { /* ignore */ }
|
|
106
|
+
finish(null);
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
} catch {
|
|
109
|
+
finish(null);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Run `ps -A -o comm` (or `tasklist` on Windows) and return stdout, or null on failure.
|
|
116
|
+
* @param {number} [timeoutMs]
|
|
117
|
+
* @returns {Promise<string|null>}
|
|
118
|
+
*/
|
|
119
|
+
function runPs(timeoutMs = 2000) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
let settled = false;
|
|
122
|
+
const finish = (value) => {
|
|
123
|
+
if (settled) return;
|
|
124
|
+
settled = true;
|
|
125
|
+
resolve(value);
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
const cmd = SO_IS_WINDOWS ? 'tasklist' : 'ps';
|
|
129
|
+
const args = SO_IS_WINDOWS
|
|
130
|
+
? ['/FO', 'CSV', '/NH']
|
|
131
|
+
: ['-A', '-o', 'comm'];
|
|
132
|
+
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
133
|
+
const chunks = [];
|
|
134
|
+
child.stdout.on('data', (c) => chunks.push(c));
|
|
135
|
+
child.on('error', () => finish(null));
|
|
136
|
+
child.on('close', (code) => {
|
|
137
|
+
if (code !== 0) return finish(null);
|
|
138
|
+
finish(Buffer.concat(chunks).toString('utf8'));
|
|
139
|
+
});
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
try { child.kill('SIGKILL'); } catch { /* ignore */ }
|
|
142
|
+
finish(null);
|
|
143
|
+
}, timeoutMs);
|
|
144
|
+
} catch {
|
|
145
|
+
finish(null);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Run `ps` collecting PID, command name, elapsed time, and CPU% — used for zombie detection.
|
|
152
|
+
* Returns raw stdout string or null on failure.
|
|
153
|
+
* @param {number} [timeoutMs]
|
|
154
|
+
* @returns {Promise<string|null>}
|
|
155
|
+
*/
|
|
156
|
+
function runPsDetailed(timeoutMs = 2000) {
|
|
157
|
+
return new Promise((resolve) => {
|
|
158
|
+
if (SO_IS_WINDOWS) { resolve(null); return; }
|
|
159
|
+
let settled = false;
|
|
160
|
+
const finish = (value) => {
|
|
161
|
+
if (settled) return;
|
|
162
|
+
settled = true;
|
|
163
|
+
resolve(value);
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
// -A: all processes; output: pid, comm (basename), etime, %cpu
|
|
167
|
+
const child = spawn('ps', ['-A', '-o', 'pid,comm,etime,%cpu'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
168
|
+
const chunks = [];
|
|
169
|
+
child.stdout.on('data', (c) => chunks.push(c));
|
|
170
|
+
child.on('error', () => finish(null));
|
|
171
|
+
child.on('close', (code) => {
|
|
172
|
+
if (code !== 0) return finish(null);
|
|
173
|
+
finish(Buffer.concat(chunks).toString('utf8'));
|
|
174
|
+
});
|
|
175
|
+
setTimeout(() => {
|
|
176
|
+
try { child.kill('SIGKILL'); } catch { /* ignore */ }
|
|
177
|
+
finish(null);
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
} catch {
|
|
180
|
+
finish(null);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Process pattern matching defaults
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
export const DEFAULT_PATTERNS = [
|
|
190
|
+
{
|
|
191
|
+
key: 'claude',
|
|
192
|
+
// Matches the `claude` CLI process plus `Claude Code` macOS app names.
|
|
193
|
+
match: (l) => /(^|[\s/,"])claude([\s",]|$)/.test(l) || l.includes('claude code') || l.includes('claude.app'),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
key: 'codex',
|
|
197
|
+
match: (l) => /(^|[\s/,"])codex([\s",]|$)/.test(l) || l.includes('codex cli'),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
key: 'other_node',
|
|
201
|
+
// All node processes; caller subtracts claude/codex-related node procs.
|
|
202
|
+
match: (l) => /(^|[\s/,"])node([\s",]|$)/.test(l),
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Collect process counts (claude, codex, node) and optionally zombie count.
|
|
208
|
+
* @param {number|null} [zombieThresholdMin]
|
|
209
|
+
* @returns {Promise<{ claude_processes_count: number|null, codex_processes_count: number|null, other_node_processes: number|null, zombie_processes_count: number|null }>}
|
|
210
|
+
*/
|
|
211
|
+
export async function processCounts(zombieThresholdMin = null) {
|
|
212
|
+
// Run simple ps (for counts) and detailed ps (for zombie detection) in parallel.
|
|
213
|
+
const [output, detailedOutput] = await Promise.all([
|
|
214
|
+
runPs(),
|
|
215
|
+
zombieThresholdMin !== null ? runPsDetailed() : Promise.resolve(null),
|
|
216
|
+
]);
|
|
217
|
+
if (output === null || output === undefined) {
|
|
218
|
+
return {
|
|
219
|
+
claude_processes_count: null,
|
|
220
|
+
codex_processes_count: null,
|
|
221
|
+
other_node_processes: null,
|
|
222
|
+
zombie_processes_count: null,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const raw = countProcessMatches(output, DEFAULT_PATTERNS);
|
|
226
|
+
// Subtract self (the currently running probe process counts as 1 node).
|
|
227
|
+
const otherNode = Math.max(0, (raw.other_node ?? 0) - 1);
|
|
228
|
+
const zombie_processes_count =
|
|
229
|
+
zombieThresholdMin !== null
|
|
230
|
+
? countZombieProcesses(detailedOutput, zombieThresholdMin)
|
|
231
|
+
: null;
|
|
232
|
+
return {
|
|
233
|
+
claude_processes_count: raw.claude ?? 0,
|
|
234
|
+
codex_processes_count: raw.codex ?? 0,
|
|
235
|
+
other_node_processes: otherNode,
|
|
236
|
+
zombie_processes_count,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Swap usage (async, spawn-based) — macOS + Linux only
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Probe swap usage. Returns used MB as integer, or null on unsupported/failure.
|
|
246
|
+
* @returns {Promise<number|null>}
|
|
247
|
+
*/
|
|
248
|
+
export async function swapUsedMb() {
|
|
249
|
+
if (SO_IS_WINDOWS) return null;
|
|
250
|
+
|
|
251
|
+
if (process.platform === 'darwin') {
|
|
252
|
+
const output = await runCommand('sysctl', ['vm.swapusage']);
|
|
253
|
+
return parseSwapUsageOutput(output);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (process.platform === 'linux') {
|
|
257
|
+
// Read /proc/meminfo and compute SwapTotal - SwapFree (values in kB)
|
|
258
|
+
const output = await runCommand('cat', ['/proc/meminfo']);
|
|
259
|
+
if (output === null) return null;
|
|
260
|
+
const totalMatch = /SwapTotal:\s*(\d+)\s*kB/i.exec(output);
|
|
261
|
+
const freeMatch = /SwapFree:\s*(\d+)\s*kB/i.exec(output);
|
|
262
|
+
if (!totalMatch || !freeMatch) return null;
|
|
263
|
+
const totalKb = parseInt(totalMatch[1], 10);
|
|
264
|
+
const freeKb = parseInt(freeMatch[1], 10);
|
|
265
|
+
if (Number.isNaN(totalKb) || Number.isNaN(freeKb)) return null;
|
|
266
|
+
return Math.round((totalKb - freeKb) / 1024);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Probe macOS memory_pressure free percentage. Returns integer 0..100, or null.
|
|
274
|
+
* @returns {Promise<number|null>}
|
|
275
|
+
*/
|
|
276
|
+
export async function memoryPressurePctFree() {
|
|
277
|
+
if (process.platform !== 'darwin') return null;
|
|
278
|
+
|
|
279
|
+
const output = await runCommand('memory_pressure', []);
|
|
280
|
+
return parseMemoryPressureOutput(output);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Probe macOS AVAILABLE RAM (free + reclaimable) via `vm_stat`.
|
|
285
|
+
*
|
|
286
|
+
* On macOS, `os.freemem()` reflects only `Pages free` and routinely reads
|
|
287
|
+
* sub-1 GB even when 100+ GB is reclaimable cache. This derives the real
|
|
288
|
+
* headroom from vm_stat (see parseVmStatAvailableGb for the page-class formula).
|
|
289
|
+
*
|
|
290
|
+
* Darwin-only — returns null on Linux/Windows/unknown (those platforms already
|
|
291
|
+
* surface an accurate `os.freemem()`, so no override is needed) or on
|
|
292
|
+
* spawn/parse failure.
|
|
293
|
+
* @returns {Promise<number|null>} available GB rounded to 1 decimal, or null
|
|
294
|
+
*/
|
|
295
|
+
export async function ramAvailableGb() {
|
|
296
|
+
if (process.platform !== 'darwin') return null;
|
|
297
|
+
|
|
298
|
+
const output = await runCommand('vm_stat', []);
|
|
299
|
+
return parseVmStatAvailableGb(output);
|
|
300
|
+
}
|