claude-code-session-manager 0.86.0 → 0.88.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/README.md +26 -53
- package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
- package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
- package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
- package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
- package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
- package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
- package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
- package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
- package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
- package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
- package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
- package/dist/assets/Permissions-m0KAnccF.js +3 -0
- package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
- package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
- package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
- package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
- package/dist/assets/Settings-BHSGGFEc.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
- package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
- package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
- package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
- package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
- package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
- package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
- package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
- package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
- package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
- package/dist/index.html +2 -2
- package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
- package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
- package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
- package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
- package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
- package/package.json +14 -17
- package/plugins/CLAUDE.md +61 -0
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
- package/scripts/README.md +59 -0
- package/scripts/audit-ops-hygiene.cjs +350 -0
- package/scripts/hooks/guard-destructive-git.cjs +10 -34
- package/scripts/hooks/guard-inline-implementation.cjs +14 -8
- package/scripts/hooks/guard-prd-writes.cjs +7 -36
- package/scripts/hooks/guard-self-schedule.cjs +175 -0
- package/scripts/ops-sweep.cjs +355 -0
- package/scripts/scheduler-mcp-server.cjs +35 -71
- package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
- package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
- package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
- package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
- package/src/main/__tests__/chat-queue.test.cjs +2 -2
- package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
- package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
- package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
- package/src/main/__tests__/dod-report.test.cjs +2 -2
- package/src/main/__tests__/dod-reverify.test.cjs +2 -2
- package/src/main/__tests__/epicMint.test.cjs +4 -4
- package/src/main/__tests__/exchanges.test.cjs +2 -2
- package/src/main/__tests__/extractJson.test.cjs +2 -2
- package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
- package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
- package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
- package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
- package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
- package/src/main/__tests__/historyRollup.test.cjs +2 -2
- package/src/main/__tests__/kg-augment.test.cjs +2 -2
- package/src/main/__tests__/mcpStatus.test.cjs +2 -2
- package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
- package/src/main/__tests__/memoryStale.test.cjs +1 -1
- package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
- package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
- package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
- package/src/main/__tests__/prdLocations.test.cjs +100 -2
- package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
- package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
- package/src/main/__tests__/proc-role-env.test.cjs +125 -0
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
- package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
- package/src/main/__tests__/projectPages.test.cjs +63 -149
- package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
- package/src/main/__tests__/queueHistory.test.cjs +65 -2
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
- package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
- package/src/main/__tests__/runVerify.test.cjs +5 -5
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
- package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
- package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
- package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
- package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
- package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
- package/src/main/bilkoHost.cjs +16 -7
- package/src/main/build-info.json +8 -0
- package/src/main/chatRunner.cjs +12 -7
- package/src/main/config.cjs +2 -3
- package/src/main/docEdit.cjs +4 -2
- package/src/main/health.cjs +268 -52
- package/src/main/heapSnapshot.cjs +2 -2
- package/src/main/historyAggregator.cjs +6 -1
- package/src/main/index.cjs +152 -50
- package/src/main/ipcSchemas.cjs +53 -17
- package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
- package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
- package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
- package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
- package/src/main/lib/__tests__/gateFixtures.json +20 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
- package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
- package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
- package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
- package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
- package/src/main/lib/__tests__/procName.test.cjs +92 -0
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
- package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
- package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
- package/src/main/lib/activeIndexRebuild.cjs +1 -3
- package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
- package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
- package/src/main/lib/agentModelResolve.cjs +94 -24
- package/src/main/lib/agentPersonaSchema.cjs +2 -2
- package/src/main/lib/auditLog.cjs +30 -5
- package/src/main/lib/bootSelfHeal.cjs +88 -0
- package/src/main/lib/buildIdentity.cjs +113 -0
- package/src/main/lib/classifyPromptTicket.cjs +3 -2
- package/src/main/lib/claudeBin.cjs +37 -2
- package/src/main/lib/cleanEnv.cjs +27 -1
- package/src/main/lib/credentials.cjs +4 -2
- package/src/main/lib/cwdClassify.cjs +185 -0
- package/src/main/lib/definitionOfDone.cjs +296 -52
- package/src/main/lib/delegationReadiness.cjs +290 -225
- package/src/main/lib/dispatchLoop.cjs +40 -0
- package/src/main/lib/effectiveModelInfo.cjs +332 -0
- package/src/main/lib/ephemeralCwd.cjs +7 -28
- package/src/main/lib/epicMint.cjs +3 -3
- package/src/main/lib/gitWorktree.cjs +136 -19
- package/src/main/lib/guardShims.cjs +156 -0
- package/src/main/lib/historyRollup.cjs +6 -7
- package/src/main/lib/instanceLock.cjs +32 -5
- package/src/main/lib/jobDirtFilter.cjs +7 -2
- package/src/main/lib/jobSupervisorRecord.cjs +147 -0
- package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
- package/src/main/lib/launchFailure.cjs +2 -1
- package/src/main/lib/localAdminHttp.cjs +9 -27
- package/src/main/lib/mcpToolCatalog.cjs +33 -78
- package/src/main/lib/needsReviewLedger.cjs +205 -0
- package/src/main/lib/opsErrorLog.cjs +1 -1
- package/src/main/lib/opsOwnership.cjs +27 -19
- package/src/main/lib/prdAuthoringSeed.cjs +36 -0
- package/src/main/lib/prdCreate.cjs +56 -1
- package/src/main/lib/prdDisposition.cjs +199 -0
- package/src/main/lib/prdFrontmatter.cjs +8 -2
- package/src/main/lib/prdLocations.cjs +214 -45
- package/src/main/lib/procIdentity.cjs +126 -0
- package/src/main/lib/procName.cjs +98 -0
- package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
- package/src/main/lib/projectRootResolve.cjs +1 -1
- package/src/main/lib/queueHistory.cjs +27 -8
- package/src/main/lib/queueStore.cjs +63 -38
- package/src/main/lib/quietMachineLease.cjs +19 -2
- package/src/main/lib/reaperHelpers.cjs +181 -15
- package/src/main/lib/reservationExpiry.cjs +30 -0
- package/src/main/lib/runClaudeP.cjs +4 -2
- package/src/main/lib/runLogRetention.cjs +3 -2
- package/src/main/lib/scheduleJobSchema.cjs +10 -2
- package/src/main/lib/scheduleJobTransitions.cjs +60 -0
- package/src/main/lib/schedulerBatch.cjs +27 -2
- package/src/main/lib/schedulerConfig.cjs +24 -0
- package/src/main/lib/schedulerPaths.cjs +175 -0
- package/src/main/lib/schedulerRuntimeState.cjs +59 -0
- package/src/main/lib/sessionSlots.cjs +47 -9
- package/src/main/lib/smProcNames.cjs +51 -0
- package/src/main/lib/upgradeDrain.cjs +188 -0
- package/src/main/lib/usageCircuit.cjs +206 -0
- package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
- package/src/main/lib/withTimeout.cjs +33 -0
- package/src/main/mcpStatus.cjs +4 -2
- package/src/main/pluginInstall.cjs +6 -2
- package/src/main/projectPages.cjs +41 -145
- package/src/main/pty.cjs +8 -1
- package/src/main/queueOps.cjs +10 -10
- package/src/main/runVerify.cjs +69 -3
- package/src/main/scheduler/prdParser.cjs +13 -0
- package/src/main/scheduler.cjs +2841 -482
- package/src/main/seedAgentPersonas.cjs +1 -1
- package/src/main/seedDevPlugin.cjs +1 -1
- package/src/main/seedSchedulerMcp.cjs +7 -6
- package/src/main/seedStatus.cjs +1 -1
- package/src/main/supervisor.cjs +5 -3
- package/src/main/templates/PRD_AUTHORING.md +50 -0
- package/src/main/usage.cjs +140 -58
- package/src/preload/api.d.ts +110 -65
- package/src/preload/index.cjs +12 -4
- package/src/seed/agents/project-home-builder.md +31 -48
- package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
- package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
- package/dist/assets/Permissions-BiYZNGYW.js +0 -3
- package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
- package/dist/assets/Settings-Cv-pRyms.js +0 -3
- package/dist/assets/settingsSchema-BJVciriw.js +0 -3
- package/screenshots/.gitkeep +0 -0
- package/screenshots/README-screenshots.md +0 -13
- package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
- package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
- package/scripts/render-project-pages.cjs +0 -70
- package/scripts/validate-project-pages-summary.cjs +0 -62
- package/src/main/lib/projectPageSummarySchema.cjs +0 -181
- package/src/main/teams.cjs +0 -95
- package/src/main/templates/project-pages-catalog.json +0 -741
- package/src/main/templates/project-pages-default-home.html +0 -123
- package/src/main/templates/project-pages-pipeline.md +0 -417
- package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
- package/src/seed/prompts/code-review/correctness-only.md +0 -8
- package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
- package/src/seed/prompts/code-review/hallucination-check.md +0 -8
- package/src/seed/prompts/code-review/public-api-compat.md +0 -8
- package/src/seed/prompts/code-review/readability-naming.md +0 -8
- package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
- package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
- package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
- package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
- package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
- package/src/seed/prompts/documentation/adr-from-change.md +0 -8
- package/src/seed/prompts/documentation/module-readme.md +0 -8
- package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
- package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
- package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
- package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
- package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
- package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
- package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
- package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
- package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
- package/src/seed/prompts/performance/complexity-audit.md +0 -8
- package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
- package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
- package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
- package/src/seed/prompts/qa/api-contract-tests.md +0 -8
- package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
- package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
- package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
- package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
- package/src/seed/prompts/qa/tdd-red-first.md +0 -8
- package/src/seed/prompts/qa/visual-regression-review.md +0 -8
- package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
- package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
- package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
- package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
- package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
- package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
- package/src/seed/prompts/security/authz-audit.md +0 -8
- package/src/seed/prompts/security/crypto-correctness.md +0 -8
- package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
- package/src/seed/prompts/security/dependency-audit.md +0 -8
- package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
- package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
- package/src/seed/prompts/security/secret-credential-scan.md +0 -8
package/src/main/health.cjs
CHANGED
|
@@ -11,16 +11,20 @@ const os = require('node:os');
|
|
|
11
11
|
const { execFileSync } = require('node:child_process');
|
|
12
12
|
const { POLL_INTERVAL_MS, loadGateThreshold } = require('./lib/schedulerConfig.cjs');
|
|
13
13
|
const { opsPath } = require('./lib/opsOwnership.cjs');
|
|
14
|
+
const { effectivePaused } = require('./lib/upgradeDrain.cjs');
|
|
14
15
|
const { checkPersonaImports } = require('./lib/personaImportHealth.cjs');
|
|
15
16
|
const { checkDelegationReadiness } = require('./lib/delegationReadiness.cjs');
|
|
16
17
|
const { resolvePrdsDirs } = require('./lib/prdLocations.cjs');
|
|
17
18
|
const { migratePrds } = require('./lib/prdMigration.cjs');
|
|
18
19
|
const queueStore = require('./lib/queueStore.cjs');
|
|
19
|
-
const
|
|
20
|
-
const {
|
|
21
|
-
const {
|
|
20
|
+
const schedulerPaths = require('./lib/schedulerPaths.cjs');
|
|
21
|
+
const { evaluateDispatchLiveness } = require('./lib/watchdogHelpers.cjs');
|
|
22
|
+
const { computeStallSummary, computeDepHistorySatisfaction, FAILURE_STREAK_ESCALATION_MS, classifyQueueStarvation, launchBlockedSlugs, STARVE_ESCALATION_MS, REVERIFY_INTERVAL_MS } = require('./scheduler.cjs');
|
|
23
|
+
const { findStarvedProjects, findUnresolvableDepRoots, DEFAULT_PROJECT_CWD, DEP_HISTORY_FAIL_OPEN } = require('./lib/schedulerBatch.cjs');
|
|
24
|
+
const { auditLogPath, readTail } = require('./lib/auditLog.cjs');
|
|
25
|
+
const { resolveBuildIdentity } = require('./lib/buildIdentity.cjs');
|
|
22
26
|
const { DEFAULT_RUNS_DIR, computeReport, isRetentionEnabled, liveKeysFromJobs } = require('./lib/runLogRetention.cjs');
|
|
23
|
-
const { allProjectCwds } = require('
|
|
27
|
+
const { allProjectCwds } = require('./lib/activeSessions.cjs');
|
|
24
28
|
|
|
25
29
|
const MAX_LOG_AGE_MS = 5 * 60_000; // 5 min — warn if no logs this old
|
|
26
30
|
const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
@@ -40,6 +44,7 @@ const TICK_STALL_THRESHOLD_MS = TICK_STALL_MULTIPLIER * POLL_INTERVAL_MS;
|
|
|
40
44
|
// scheduler-heartbeat.log is only appended to while Electron is running, so
|
|
41
45
|
// a stale line here is silent-on-purpose, not evidence of anything.
|
|
42
46
|
const HEARTBEAT_STALE_MS = 5 * 60_000;
|
|
47
|
+
const AUDIT_TAIL_BYTES = 1024 * 1024;
|
|
43
48
|
|
|
44
49
|
function runCheck(cmd, cwd = PROJECT_ROOT) {
|
|
45
50
|
try {
|
|
@@ -72,6 +77,7 @@ function readFreshHeartbeat(heartbeatPath) {
|
|
|
72
77
|
} catch {
|
|
73
78
|
return null;
|
|
74
79
|
}
|
|
80
|
+
if (entry.degraded === true) return null; // a subsystem threw — utilization was not read
|
|
75
81
|
if (typeof entry.ts !== 'number' || Date.now() - entry.ts > HEARTBEAT_STALE_MS) return null;
|
|
76
82
|
return entry;
|
|
77
83
|
}
|
|
@@ -92,7 +98,7 @@ function evaluateTickLiveness(queueState, heartbeat, now, runningCount) {
|
|
|
92
98
|
?? (() => { try { return require('./lib/sessionSlots.cjs').totalSlots(); } catch { return Infinity; } })();
|
|
93
99
|
|
|
94
100
|
if (pending.length === 0) return { stalled: false, reason: 'no-pending-jobs' };
|
|
95
|
-
if (queueState
|
|
101
|
+
if (effectivePaused(queueState)) return { stalled: false, reason: queueState.paused ? 'paused' : 'draining' };
|
|
96
102
|
if (config.enabled === false) return { stalled: false, reason: 'disabled' };
|
|
97
103
|
if (running >= concurrencyCap) return { stalled: false, reason: 'at-capacity' };
|
|
98
104
|
// 'manual' means the operator fires batches by hand — the scheduler is not
|
|
@@ -375,27 +381,50 @@ function evaluateEpicIndexHealth(cwds) {
|
|
|
375
381
|
// not a failure, matches evaluatePrdMigrationHealth's fail-open-on-absence
|
|
376
382
|
// spirit. Kept separate from the fs-touching check() call site, same pattern
|
|
377
383
|
// as every other evaluate* helper in this file.
|
|
378
|
-
|
|
384
|
+
//
|
|
385
|
+
// Ladder is driven by the shared usageCircuit's OWN open/closed state and
|
|
386
|
+
// how long it's been open (usageCircuitState/usageCircuitOpenedAt, persisted
|
|
387
|
+
// by scheduler.cjs's persistSchedulerState — this process never holds the
|
|
388
|
+
// live in-memory circuit itself) — not a binary consecutiveFailures<5 check,
|
|
389
|
+
// which used to read GREEN right up until the exact failure that also fired
|
|
390
|
+
// the one-time WARN, then stayed flatly non-GREEN forever after with no way
|
|
391
|
+
// to tell "just tripped" from "down for hours" apart:
|
|
392
|
+
// GREEN — circuit closed (or half_open probing after a fresh backoff).
|
|
393
|
+
// YELLOW — circuit open, less than FAILURE_STREAK_ESCALATION_MS (30 min).
|
|
394
|
+
// Still `ok: true` — the circuit's OWN open threshold (3
|
|
395
|
+
// consecutive failures, usageCircuit.cjs) is lower than the old
|
|
396
|
+
// 5-failure WARN, so treating YELLOW as `ok: false` here would
|
|
397
|
+
// silently tighten the overall `npm run health` rollup's failure
|
|
398
|
+
// bar from 5 down to 3. Graceful degradation (the whole point of
|
|
399
|
+
// the breaker) should not itself read as a critical failure.
|
|
400
|
+
// RED — circuit open for FAILURE_STREAK_ESCALATION_MS or longer —
|
|
401
|
+
// `ok: false`, matching the same 30-minute threshold the
|
|
402
|
+
// escalation ladder (warnFailureStreakIfNeeded) re-alerts on.
|
|
403
|
+
function evaluateUsagePollerHealth(state, redThresholdMs = FAILURE_STREAK_ESCALATION_MS) {
|
|
379
404
|
if (!state || typeof state !== 'object') return { ok: true, applicable: false };
|
|
380
405
|
const consecutiveFailures = typeof state.consecutiveFailures === 'number' ? state.consecutiveFailures : 0;
|
|
381
406
|
const backoffMs = typeof state.backoffMs === 'number' ? state.backoffMs : null;
|
|
382
407
|
const lastPollAt = typeof state.lastPollAt === 'number' ? state.lastPollAt : null;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
408
|
+
const circuitState = typeof state.usageCircuitState === 'string' ? state.usageCircuitState : 'closed';
|
|
409
|
+
const openedAt = typeof state.usageCircuitOpenedAt === 'number' ? state.usageCircuitOpenedAt : null;
|
|
410
|
+
|
|
411
|
+
if (circuitState !== 'open' && circuitState !== 'half_open') {
|
|
412
|
+
return { ok: true, applicable: true, color: 'GREEN', consecutiveFailures, backoffMs, lastPollAt };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const openMs = openedAt ? Math.max(0, Date.now() - openedAt) : 0;
|
|
416
|
+
const color = openMs >= redThresholdMs ? 'RED' : 'YELLOW';
|
|
388
417
|
return {
|
|
389
|
-
ok,
|
|
418
|
+
ok: color !== 'RED',
|
|
390
419
|
applicable: true,
|
|
420
|
+
color,
|
|
391
421
|
consecutiveFailures,
|
|
392
422
|
backoffMs,
|
|
393
423
|
lastPollAt,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}),
|
|
424
|
+
openedAt,
|
|
425
|
+
openMs,
|
|
426
|
+
message: `usage/rate-limit poller circuit is ${circuitState}, open for ${Math.round(openMs / 60_000)}m (${color}) — `
|
|
427
|
+
+ `${consecutiveFailures} consecutive failures, backoffMs=${backoffMs} — see logs scope=scheduler`,
|
|
399
428
|
};
|
|
400
429
|
}
|
|
401
430
|
|
|
@@ -416,6 +445,10 @@ function evaluateUsagePollerHealth(state, threshold = FAILURE_STREAK_WARN_THRESH
|
|
|
416
445
|
// OWN forcing threshold; this is the "even the watchdog isn't helping
|
|
417
446
|
// anymore" signal a human needs to see.
|
|
418
447
|
const DISPATCH_STALL_THRESHOLD_MS = 2 * 60 * 60_000;
|
|
448
|
+
// Early-warning tier off the SAME launch-keyed clock (lastRunAt — a batch
|
|
449
|
+
// actually launched): still ok:true, but flagged so a slow leak surfaces
|
|
450
|
+
// well before the 2-hour cold-report threshold.
|
|
451
|
+
const DISPATCH_WARN_THRESHOLD_MS = 15 * 60_000;
|
|
419
452
|
|
|
420
453
|
/**
|
|
421
454
|
* evaluateQueueDispatchHealth(queueState, runningCount, now, thresholdMs) →
|
|
@@ -430,14 +463,18 @@ const DISPATCH_STALL_THRESHOLD_MS = 2 * 60 * 60_000;
|
|
|
430
463
|
* `blocked` verdict therefore stays `ok: true` (never trips the health gate)
|
|
431
464
|
* but is still reported by name so it isn't silently invisible either.
|
|
432
465
|
*/
|
|
433
|
-
function evaluateQueueDispatchHealth(queueState, runningCount, now, thresholdMs = DISPATCH_STALL_THRESHOLD_MS) {
|
|
466
|
+
function evaluateQueueDispatchHealth(queueState, runningCount, now, thresholdMs = DISPATCH_STALL_THRESHOLD_MS, warnMs = DISPATCH_WARN_THRESHOLD_MS) {
|
|
467
|
+
// Clock = lastRunAt (launches), never lastDispatchAttemptAt (loop-alive
|
|
468
|
+
// heartbeat, refreshed by ticks that launch nothing). Cold process: no
|
|
469
|
+
// in-memory pause/boot stamps to fold in. Breaker-held rows count blocked.
|
|
434
470
|
const verdict = classifyQueueStarvation({
|
|
435
471
|
jobs: queueState?.jobs,
|
|
436
|
-
paused: queueState
|
|
472
|
+
paused: effectivePaused(queueState),
|
|
437
473
|
runningCount,
|
|
438
|
-
lastRunAtMs: Date.parse(queueState?.
|
|
474
|
+
lastRunAtMs: Date.parse(queueState?.lastRunAt ?? ''),
|
|
475
|
+
heldSlugs: launchBlockedSlugs(queueState?.jobs, queueState?.launchBlocks),
|
|
439
476
|
now,
|
|
440
|
-
thresholdMs,
|
|
477
|
+
thresholdMs: Math.min(warnMs, thresholdMs),
|
|
441
478
|
});
|
|
442
479
|
if (!verdict) return { ok: true };
|
|
443
480
|
if (verdict.kind === 'blocked') {
|
|
@@ -445,18 +482,79 @@ function evaluateQueueDispatchHealth(queueState, runningCount, now, thresholdMs
|
|
|
445
482
|
ok: true,
|
|
446
483
|
blocked: true,
|
|
447
484
|
pending: verdict.pending,
|
|
448
|
-
message: `${verdict.pending} pending job(s) behind a blocked dependsOn chain — dispatch cannot help, needs a human`,
|
|
485
|
+
message: `${verdict.pending} pending job(s) behind a blocked dependsOn chain or open launch breaker — dispatch cannot help, needs a human`,
|
|
449
486
|
};
|
|
450
487
|
}
|
|
451
488
|
const ageMin = Math.round(verdict.idleMs / 60_000);
|
|
489
|
+
const critical = verdict.idleMs >= thresholdMs;
|
|
452
490
|
return {
|
|
453
|
-
ok:
|
|
454
|
-
starved: true,
|
|
491
|
+
ok: !critical,
|
|
492
|
+
...(critical ? { starved: true } : { warn: true }),
|
|
455
493
|
pending: verdict.pending,
|
|
456
494
|
dispatchable: verdict.dispatchable,
|
|
457
495
|
idleMs: verdict.idleMs,
|
|
458
|
-
message: `${verdict.dispatchable} dispatchable pending job(s), 0 running, no
|
|
459
|
-
+
|
|
496
|
+
message: `${verdict.dispatchable} dispatchable pending job(s), 0 running, no launch in ~${ageMin}m `
|
|
497
|
+
+ (critical
|
|
498
|
+
? `(threshold ${Math.round(thresholdMs / 60_000)}m) — dispatch appears stuck`
|
|
499
|
+
: `(warn at ${Math.round(warnMs / 60_000)}m, fail at ${Math.round(thresholdMs / 60_000)}m) — dispatch may be stalling`),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* evaluateUnresolvableDepHealth(jobs, satisfiedSlugsByCwd) →
|
|
505
|
+
* { ok, projects?: [{ cwd, roots, pending }], message? }
|
|
506
|
+
*
|
|
507
|
+
* Pure. A project whose EVERY pending row is held (directly or transitively)
|
|
508
|
+
* behind a dependsOn slug that names no live row and no history/archive
|
|
509
|
+
* record is fully blocked with a healthy engine — the 2026-09-18 fo-01 stall
|
|
510
|
+
* (21 pending rows, 0 running, one archived root, every other component
|
|
511
|
+
* GREEN). classifyQueueStarvation's 'blocked' kind cannot see this shape (it
|
|
512
|
+
* only walks failed/skipped ROWS), and evaluateQueueDispatchHealth keeps
|
|
513
|
+
* 'blocked' ok:true by design, so this is the component that fails it, naming
|
|
514
|
+
* the root slug(s). Reuses the picker's own resolver (findUnresolvableDepRoots)
|
|
515
|
+
* so health can never disagree with what dispatch actually holds. A project
|
|
516
|
+
* with anything running, or any dispatchable pending row, is not fully blocked.
|
|
517
|
+
* O(jobs + deps) per project; a fail-open project (history unreadable) is skipped.
|
|
518
|
+
*/
|
|
519
|
+
function evaluateUnresolvableDepHealth(jobs, satisfiedSlugsByCwd) {
|
|
520
|
+
const byCwd = new Map();
|
|
521
|
+
for (const j of Array.isArray(jobs) ? jobs : []) {
|
|
522
|
+
if (!j) continue;
|
|
523
|
+
const cwd = j.cwd || DEFAULT_PROJECT_CWD;
|
|
524
|
+
if (!byCwd.has(cwd)) byCwd.set(cwd, []);
|
|
525
|
+
byCwd.get(cwd).push(j);
|
|
526
|
+
}
|
|
527
|
+
const projects = [];
|
|
528
|
+
for (const [cwd, rows] of byCwd) {
|
|
529
|
+
const satisfied = satisfiedSlugsByCwd?.get?.(cwd) ?? new Set();
|
|
530
|
+
if (satisfied === DEP_HISTORY_FAIL_OPEN) continue;
|
|
531
|
+
const pending = rows.filter((j) => j.status === 'pending');
|
|
532
|
+
if (pending.length === 0 || rows.some((j) => j.status === 'running')) continue;
|
|
533
|
+
const roots = findUnresolvableDepRoots(rows, satisfied);
|
|
534
|
+
if (roots.length === 0) continue;
|
|
535
|
+
const rootSet = new Set(roots);
|
|
536
|
+
const bare = (x) => String(x ?? '').replace(/^\d+-/, '');
|
|
537
|
+
const held = new Set();
|
|
538
|
+
let grew = true;
|
|
539
|
+
while (grew) {
|
|
540
|
+
grew = false;
|
|
541
|
+
for (const j of pending) {
|
|
542
|
+
if (held.has(j.slug)) continue;
|
|
543
|
+
const isHeld = (j.dependsOn ?? []).some((d) => rootSet.has(d)
|
|
544
|
+
|| pending.some((p) => held.has(p.slug) && (p.slug === d || bare(p.slug) === bare(d))));
|
|
545
|
+
if (isHeld) { held.add(j.slug); grew = true; }
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (held.size === pending.length) projects.push({ cwd, roots, pending: pending.length });
|
|
549
|
+
}
|
|
550
|
+
if (projects.length === 0) return { ok: true };
|
|
551
|
+
return {
|
|
552
|
+
ok: false,
|
|
553
|
+
projects,
|
|
554
|
+
message: projects
|
|
555
|
+
.map((p) => `${p.cwd}: all ${p.pending} pending job(s) held behind unresolvable dependsOn root ${p.roots.join(', ')} `
|
|
556
|
+
+ '(no live row, no history/archive record) — restore its completion record or fix the dependsOn')
|
|
557
|
+
.join('; '),
|
|
460
558
|
};
|
|
461
559
|
}
|
|
462
560
|
|
|
@@ -472,12 +570,9 @@ function evaluateQueueDispatchHealth(queueState, runningCount, now, thresholdMs
|
|
|
472
570
|
* Missing/unreadable log → {} (no reasons known yet), never a throw.
|
|
473
571
|
*/
|
|
474
572
|
function latestStarveEscalationReasons(auditLogPath) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
} catch {
|
|
479
|
-
return {};
|
|
480
|
-
}
|
|
573
|
+
// Bounded reverse tail — the log is never rotated (28 MB+), and the latest
|
|
574
|
+
// escalation per cwd is by definition recent.
|
|
575
|
+
const lines = readTail(AUDIT_TAIL_BYTES, auditLogPath);
|
|
481
576
|
const byCwd = {};
|
|
482
577
|
for (const line of lines) {
|
|
483
578
|
let rec;
|
|
@@ -491,6 +586,24 @@ function latestStarveEscalationReasons(auditLogPath) {
|
|
|
491
586
|
return byCwd;
|
|
492
587
|
}
|
|
493
588
|
|
|
589
|
+
/**
|
|
590
|
+
* evaluateBuildFreshness({running, installedOnDisk, repoHead}) → component
|
|
591
|
+
*
|
|
592
|
+
* Pure; each input is a short git sha or null. restartNeeded: the on-disk
|
|
593
|
+
* install differs from what the live heartbeat says is running. publishNeeded:
|
|
594
|
+
* repo HEAD differs from the installed build. Both are reported, neither is
|
|
595
|
+
* critical (never flips status.ok).
|
|
596
|
+
*/
|
|
597
|
+
function evaluateBuildFreshness({ running = null, installedOnDisk = null, repoHead = null } = {}) {
|
|
598
|
+
const differs = (a, b) => !!a && !!b && !(a.startsWith(b) || b.startsWith(a));
|
|
599
|
+
const restartNeeded = differs(running, installedOnDisk);
|
|
600
|
+
const publishNeeded = differs(repoHead, installedOnDisk);
|
|
601
|
+
const notes = [];
|
|
602
|
+
if (restartNeeded) notes.push(`restart needed: running ${running}, installed ${installedOnDisk}`);
|
|
603
|
+
if (publishNeeded) notes.push(`publish needed: repo HEAD ${repoHead}, installed ${installedOnDisk}`);
|
|
604
|
+
return { ok: true, running, installedOnDisk, repoHead, restartNeeded, publishNeeded, ...(notes.length ? { note: notes.join('; ') } : {}) };
|
|
605
|
+
}
|
|
606
|
+
|
|
494
607
|
/**
|
|
495
608
|
* evaluateStarveEscalationHealth(jobs, now, thresholdMs, escalationReasons)
|
|
496
609
|
* → { ok, projects?, message? }
|
|
@@ -532,6 +645,69 @@ function loadUsagePollerState(statePath) {
|
|
|
532
645
|
}
|
|
533
646
|
}
|
|
534
647
|
|
|
648
|
+
/**
|
|
649
|
+
* evaluateBlockingParkHealth(jobs, now, thresholdMs = REVERIFY_INTERVAL_MS) →
|
|
650
|
+
* { ok, parks?: [{ cwd, slug, verdict, ageMs, dependents }], message? }
|
|
651
|
+
*
|
|
652
|
+
* Pure. A `needs_review` row parked longer than one full periodic-reverify
|
|
653
|
+
* interval that STILL holds >= 1 pending dependent (transitively, through
|
|
654
|
+
* pending rows, via dependsOn — exact or bare slug, same matching as the
|
|
655
|
+
* picker) is a silently-blocking park: the self-heal ladder had a full pass
|
|
656
|
+
* and did not release it. 2026-09-18: 1229-fo-03 held 19 of 20 pending rows
|
|
657
|
+
* while every other component read GREEN. Parked-since is the row's last
|
|
658
|
+
* `to: needs_review` statusHistory entry (fallback finishedAt); a row with no
|
|
659
|
+
* recoverable timestamp is skipped rather than guessed at. O(jobs + deps).
|
|
660
|
+
*/
|
|
661
|
+
function evaluateBlockingParkHealth(jobs, now, thresholdMs = REVERIFY_INTERVAL_MS) {
|
|
662
|
+
const rows = (Array.isArray(jobs) ? jobs : []).filter(Boolean);
|
|
663
|
+
const bare = (x) => String(x ?? '').replace(/^\d+-/, '');
|
|
664
|
+
const byCwd = new Map();
|
|
665
|
+
for (const j of rows) {
|
|
666
|
+
const cwd = j.cwd || DEFAULT_PROJECT_CWD;
|
|
667
|
+
if (!byCwd.has(cwd)) byCwd.set(cwd, []);
|
|
668
|
+
byCwd.get(cwd).push(j);
|
|
669
|
+
}
|
|
670
|
+
const parks = [];
|
|
671
|
+
for (const [cwd, group] of byCwd) {
|
|
672
|
+
const pending = group.filter((j) => j.status === 'pending');
|
|
673
|
+
if (pending.length === 0) continue;
|
|
674
|
+
for (const park of group) {
|
|
675
|
+
if (park.status !== 'needs_review') continue;
|
|
676
|
+
const history = Array.isArray(park.statusHistory) ? park.statusHistory : [];
|
|
677
|
+
let at = null;
|
|
678
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
679
|
+
if (history[i]?.to === 'needs_review') { at = history[i].at; break; }
|
|
680
|
+
}
|
|
681
|
+
const since = Date.parse(at ?? park.finishedAt ?? '');
|
|
682
|
+
if (Number.isNaN(since) || now - since <= thresholdMs) continue;
|
|
683
|
+
const held = new Set();
|
|
684
|
+
const heldBy = (d, root) => root.slug === d || bare(root.slug) === bare(d);
|
|
685
|
+
let grew = true;
|
|
686
|
+
while (grew) {
|
|
687
|
+
grew = false;
|
|
688
|
+
for (const j of pending) {
|
|
689
|
+
if (held.has(j.slug)) continue;
|
|
690
|
+
if ((j.dependsOn ?? []).some((d) => heldBy(d, park) || pending.some((p) => held.has(p.slug) && heldBy(d, p)))) {
|
|
691
|
+
held.add(j.slug);
|
|
692
|
+
grew = true;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (held.size > 0) parks.push({ cwd, slug: park.slug, verdict: park.verifierVerdict ?? null, ageMs: now - since, dependents: held.size });
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if (parks.length === 0) return { ok: true };
|
|
700
|
+
return {
|
|
701
|
+
ok: false,
|
|
702
|
+
parks,
|
|
703
|
+
message: parks
|
|
704
|
+
.map((p) => `${p.slug} (${p.cwd}) parked needs_review${p.verdict ? `/${p.verdict}` : ''} for ${Math.round(p.ageMs / 60_000)}m `
|
|
705
|
+
+ `(> one ${Math.round(REVERIFY_INTERVAL_MS / 60_000)}m reverify interval) is blocking ${p.dependents} pending dependent(s) — `
|
|
706
|
+
+ 'the self-heal ladder did not release it; resolve it or scheduler_reset_job')
|
|
707
|
+
.join('; '),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
535
711
|
async function check() {
|
|
536
712
|
const start = Date.now();
|
|
537
713
|
const status = {
|
|
@@ -577,7 +753,7 @@ async function check() {
|
|
|
577
753
|
};
|
|
578
754
|
|
|
579
755
|
// 2. Check config directory exists and is writable.
|
|
580
|
-
const configDir =
|
|
756
|
+
const configDir = schedulerPaths.claudeHome();
|
|
581
757
|
try {
|
|
582
758
|
await fsp.access(configDir, fs.constants.R_OK | fs.constants.W_OK);
|
|
583
759
|
const stat = await fsp.stat(configDir);
|
|
@@ -599,11 +775,14 @@ async function check() {
|
|
|
599
775
|
// 3. Check scheduler state (federated, 2026-07-31): machine runtime file +
|
|
600
776
|
// per-project job shards merged via queueStore — the retired global
|
|
601
777
|
// queue.json is no longer consulted.
|
|
602
|
-
const queuePath =
|
|
778
|
+
const queuePath = schedulerPaths.machineStatePath();
|
|
603
779
|
let queueState = null;
|
|
604
780
|
try {
|
|
605
781
|
queueState = queueStore.readMergedSync();
|
|
606
782
|
if (queueState.unreadable) throw new Error(queueState.unreadable);
|
|
783
|
+
for (const u of queueState.unreadableCwds ?? []) {
|
|
784
|
+
status.issues.push(`Scheduler queue shard quarantined for ${u.cwd}: ${u.error} — that project is skipped until it reads clean`);
|
|
785
|
+
}
|
|
607
786
|
// A torn scheduler-machine.json is now recovered rather than halting
|
|
608
787
|
// dispatch (queueStore.cjs's findLongestValidJsonPrefix) — but recovery
|
|
609
788
|
// happening at all means the file failed to parse moments ago, which is
|
|
@@ -626,11 +805,7 @@ async function check() {
|
|
|
626
805
|
const quarantinedCount = Object.values(queueState.jobs || {}).filter(
|
|
627
806
|
(j) => j.status === 'quarantined'
|
|
628
807
|
).length;
|
|
629
|
-
const
|
|
630
|
-
os.homedir(),
|
|
631
|
-
'.claude/session-manager/scheduler-heartbeat.log'
|
|
632
|
-
);
|
|
633
|
-
const heartbeat = readFreshHeartbeat(heartbeatPath);
|
|
808
|
+
const heartbeat = readFreshHeartbeat(schedulerPaths.heartbeatPath());
|
|
634
809
|
const liveness = evaluateTickLiveness(queueState, heartbeat, Date.now(), runningCount);
|
|
635
810
|
|
|
636
811
|
// Per-project rollup (PRD: monitoring must not collapse per-project
|
|
@@ -647,10 +822,13 @@ async function check() {
|
|
|
647
822
|
.map(([cwd]) => cwd);
|
|
648
823
|
|
|
649
824
|
status.components.scheduler_queue = {
|
|
650
|
-
ok: !liveness.stalled && projectsPastThreshold.length === 0 && !queueState.machineStateRecovered
|
|
825
|
+
ok: !liveness.stalled && projectsPastThreshold.length === 0 && !queueState.machineStateRecovered
|
|
826
|
+
&& (queueState.unreadableCwds ?? []).length === 0,
|
|
651
827
|
path: queuePath,
|
|
652
828
|
jobs: Object.keys(queueState.jobs || {}).length,
|
|
653
829
|
running: runningCount,
|
|
830
|
+
// Deliberate restart drain (lib/upgradeDrain.cjs) — reported so it is never read as a stall.
|
|
831
|
+
drain: queueState.drain?.active ? { since: queueState.drain.since ?? null, requestedAt: queueState.drain.requestedAt ?? null } : null,
|
|
654
832
|
failed: failedCount,
|
|
655
833
|
needsReview: needsReviewCount,
|
|
656
834
|
quarantined: quarantinedCount,
|
|
@@ -658,6 +836,7 @@ async function check() {
|
|
|
658
836
|
perProjectStall,
|
|
659
837
|
tickLiveness: liveness.reason,
|
|
660
838
|
machineStateRecovered: queueState.machineStateRecovered ?? false,
|
|
839
|
+
quarantinedCwds: (queueState.unreadableCwds ?? []).map((u) => u.cwd),
|
|
661
840
|
machineStateRecoveryMode: queueState.machineStateRecoveryMode ?? null,
|
|
662
841
|
// Informational only (PRD 1085): current 1-min loadavg per core vs the
|
|
663
842
|
// launch-gate threshold, so a "nothing is launching" report can be
|
|
@@ -694,10 +873,31 @@ async function check() {
|
|
|
694
873
|
);
|
|
695
874
|
}
|
|
696
875
|
}
|
|
876
|
+
// Same classification the external watchdog logs (observability only).
|
|
877
|
+
const dispatchLiveness = evaluateDispatchLiveness(heartbeat, now);
|
|
878
|
+
status.components.scheduler_queue.dispatchLiveness = dispatchLiveness;
|
|
879
|
+
if (dispatchLiveness.dead) {
|
|
880
|
+
status.issues.push('Scheduler dispatch appears dead: heartbeat is fresh but nothing has launched in 30m+ with dispatchable work pending and nothing running');
|
|
881
|
+
}
|
|
697
882
|
status.components.queue_dispatch = evaluateQueueDispatchHealth(queueState, runningCount, now);
|
|
698
883
|
if (!status.components.queue_dispatch.ok || status.components.queue_dispatch.blocked) {
|
|
699
884
|
status.issues.push(`Queue dispatch: ${status.components.queue_dispatch.message}`);
|
|
700
885
|
}
|
|
886
|
+
try {
|
|
887
|
+
status.components.queue_dep_roots = evaluateUnresolvableDepHealth(
|
|
888
|
+
queueState.jobs, await computeDepHistorySatisfaction(queueState),
|
|
889
|
+
);
|
|
890
|
+
} catch (e) {
|
|
891
|
+
status.components.queue_dep_roots = { ok: true, error: `dep-root check failed: ${e?.message}` };
|
|
892
|
+
}
|
|
893
|
+
if (!status.components.queue_dep_roots.ok) {
|
|
894
|
+
status.issues.push(`Queue dependsOn: ${status.components.queue_dep_roots.message}`);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
status.components.blocking_parks = evaluateBlockingParkHealth(queueState.jobs, now);
|
|
898
|
+
if (!status.components.blocking_parks.ok) {
|
|
899
|
+
status.issues.push(`Blocking park: ${status.components.blocking_parks.message}`);
|
|
900
|
+
}
|
|
701
901
|
|
|
702
902
|
// Per-project starve escalation (bounded consequence for project_starved
|
|
703
903
|
// — see runStarveEscalationSweep in scheduler.cjs). Distinct from
|
|
@@ -705,7 +905,7 @@ async function check() {
|
|
|
705
905
|
// "has any ONE project been starved past the LATER escalation threshold",
|
|
706
906
|
// the exact condition the 2026-09-12 19h Bilko starve went unreported by.
|
|
707
907
|
status.components.project_starve_escalation = evaluateStarveEscalationHealth(
|
|
708
|
-
queueState.jobs, now, STARVE_ESCALATION_MS, latestStarveEscalationReasons(
|
|
908
|
+
queueState.jobs, now, STARVE_ESCALATION_MS, latestStarveEscalationReasons(auditLogPath()),
|
|
709
909
|
);
|
|
710
910
|
if (!status.components.project_starve_escalation.ok) {
|
|
711
911
|
status.issues.push(status.components.project_starve_escalation.message);
|
|
@@ -746,7 +946,7 @@ async function check() {
|
|
|
746
946
|
// failure. A CORRUPT file (the torn-write class fixed for the sibling
|
|
747
947
|
// scheduler-machine.json in f56bdc0) is NOT the same as missing — surfaced
|
|
748
948
|
// as non-GREEN rather than fail-open, via loadUsagePollerState below.
|
|
749
|
-
const loaded = loadUsagePollerState(
|
|
949
|
+
const loaded = loadUsagePollerState(schedulerPaths.schedulerStatePath());
|
|
750
950
|
if (loaded.missing) {
|
|
751
951
|
status.components.usage_poller = { ok: true, applicable: false };
|
|
752
952
|
} else if (loaded.errorMessage) {
|
|
@@ -790,10 +990,7 @@ async function check() {
|
|
|
790
990
|
// of a stale installed build vs. the git repo — it fires regardless of
|
|
791
991
|
// *why* the build is stale (the 2026-07-31 burrow-project ENOENT + the
|
|
792
992
|
// 223-file/189-resurrected-job incident both trace back to this).
|
|
793
|
-
const legacyPrdsDir =
|
|
794
|
-
os.homedir(),
|
|
795
|
-
'.claude/session-manager/scheduled-plans/prds'
|
|
796
|
-
);
|
|
993
|
+
const legacyPrdsDir = schedulerPaths.prdsRoot();
|
|
797
994
|
try {
|
|
798
995
|
const migrationResult = await migratePrds(legacyPrdsDir);
|
|
799
996
|
status.components.prd_migration = evaluatePrdMigrationHealth(migrationResult, legacyPrdsDir);
|
|
@@ -809,7 +1006,7 @@ async function check() {
|
|
|
809
1006
|
}
|
|
810
1007
|
|
|
811
1008
|
// 5. Check transcripts directory (where live session logs are tailed).
|
|
812
|
-
const projectsDir =
|
|
1009
|
+
const projectsDir = schedulerPaths.claudeProjectsDir();
|
|
813
1010
|
try {
|
|
814
1011
|
await fsp.access(projectsDir, fs.constants.R_OK);
|
|
815
1012
|
status.components.transcripts_dir = {
|
|
@@ -826,10 +1023,7 @@ async function check() {
|
|
|
826
1023
|
}
|
|
827
1024
|
|
|
828
1025
|
// 6. Check session-manager's own logs (informational, not blocking).
|
|
829
|
-
const smLogsDir =
|
|
830
|
-
os.homedir(),
|
|
831
|
-
'.claude/session-manager/logs'
|
|
832
|
-
);
|
|
1026
|
+
const smLogsDir = schedulerPaths.watchdogLogsDir();
|
|
833
1027
|
let logAge = null;
|
|
834
1028
|
try {
|
|
835
1029
|
const files = await fsp.readdir(smLogsDir);
|
|
@@ -944,11 +1138,29 @@ async function check() {
|
|
|
944
1138
|
status.issues.push(`Epic index health check failed: ${e.message}`);
|
|
945
1139
|
}
|
|
946
1140
|
|
|
1141
|
+
// 6.7. Build freshness (informational): running heartbeat vs installed
|
|
1142
|
+
// build-info vs repo HEAD.
|
|
1143
|
+
try {
|
|
1144
|
+
let repoHead = null;
|
|
1145
|
+
try {
|
|
1146
|
+
repoHead = execFileSync('git', ['-C', PROJECT_ROOT, 'rev-parse', '--short', 'HEAD'], {
|
|
1147
|
+
timeout: 5000, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
1148
|
+
}).trim() || null;
|
|
1149
|
+
} catch { /* not a git checkout */ }
|
|
1150
|
+
status.components.build = evaluateBuildFreshness({
|
|
1151
|
+
running: readFreshHeartbeat(schedulerPaths.heartbeatPath())?.build?.codeSha ?? null,
|
|
1152
|
+
installedOnDisk: resolveBuildIdentity().codeSha ?? null,
|
|
1153
|
+
repoHead,
|
|
1154
|
+
});
|
|
1155
|
+
} catch (e) {
|
|
1156
|
+
status.components.build = { ok: true, error: e.message };
|
|
1157
|
+
}
|
|
1158
|
+
|
|
947
1159
|
// 7. Summary scoring: ok if all critical components pass.
|
|
948
1160
|
// Critical: nodejs, config dir, typescript, build artifact, test infrastructure.
|
|
949
1161
|
// Non-fatal: scheduler/transcripts dirs may not exist on fresh install.
|
|
950
1162
|
// Informational: app log age (shows if app is running, but not blocking).
|
|
951
|
-
const criticalComponents = ['nodejs', 'config_dir', 'typescript', 'build_artifact', 'test_infrastructure', 'scheduler_queue', 'queue_dispatch', 'project_starve_escalation', 'usage_poller', 'prd_migration', 'claude_md_budget', 'delegation_chain'];
|
|
1163
|
+
const criticalComponents = ['nodejs', 'config_dir', 'typescript', 'build_artifact', 'test_infrastructure', 'scheduler_queue', 'queue_dispatch', 'queue_dep_roots', 'blocking_parks', 'project_starve_escalation', 'usage_poller', 'prd_migration', 'claude_md_budget', 'delegation_chain'];
|
|
952
1164
|
status.ok = criticalComponents.every((c) => status.components[c]?.ok !== false);
|
|
953
1165
|
|
|
954
1166
|
status.elapsedMs = Date.now() - start;
|
|
@@ -980,9 +1192,13 @@ module.exports = {
|
|
|
980
1192
|
evaluateUsagePollerHealth,
|
|
981
1193
|
loadUsagePollerState,
|
|
982
1194
|
evaluateQueueDispatchHealth,
|
|
1195
|
+
evaluateUnresolvableDepHealth,
|
|
1196
|
+
evaluateBlockingParkHealth,
|
|
983
1197
|
evaluateStarveEscalationHealth,
|
|
1198
|
+
evaluateBuildFreshness,
|
|
984
1199
|
latestStarveEscalationReasons,
|
|
985
1200
|
DISPATCH_STALL_THRESHOLD_MS,
|
|
1201
|
+
DISPATCH_WARN_THRESHOLD_MS,
|
|
986
1202
|
TICK_STALL_THRESHOLD_MS,
|
|
987
1203
|
HEARTBEAT_STALE_MS,
|
|
988
1204
|
};
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
'use strict';
|
|
24
24
|
|
|
25
25
|
const path = require('node:path');
|
|
26
|
-
const os = require('node:os');
|
|
27
26
|
const fsp = require('node:fs/promises');
|
|
27
|
+
const { heapSnapshotDir } = require('./lib/schedulerPaths.cjs');
|
|
28
28
|
|
|
29
29
|
const CHANNEL = 'diagnostics:heap-snapshot';
|
|
30
30
|
const DEFAULT_TIMEOUT_MS = 3 * 60 * 1000; // 3 min — snapshots can be multi-hundred-MB
|
|
@@ -34,7 +34,7 @@ function isEnabled() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function snapshotDir() {
|
|
37
|
-
return
|
|
37
|
+
return heapSnapshotDir();
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function timestampedFilename(now = new Date()) {
|
|
@@ -21,10 +21,14 @@ const CACHE_MAX = 50_000;
|
|
|
21
21
|
// tokens are priced far below input because they're served from
|
|
22
22
|
// Anthropic's prompt cache, not re-processed — this is what makes the
|
|
23
23
|
// cache-savings figure in the dashboard real money, not a vanity stat.
|
|
24
|
+
// fable: Claude Fable 5.1 (claude-fable-5-1) — $10/$50 per MTok input/output,
|
|
25
|
+
// cache reads at $0.25/MTok (the `claude-api` skill's model reference and its
|
|
26
|
+
// Claude Fable 5.1 migration notes, both cached 2026-06-24).
|
|
24
27
|
const MODEL_PRICING = {
|
|
25
28
|
opus: { i: 15, o: 75, c: 1.5 },
|
|
26
29
|
sonnet: { i: 3, o: 15, c: 0.3 },
|
|
27
30
|
haiku: { i: 0.8, o: 4, c: 0.08 },
|
|
31
|
+
fable: { i: 10, o: 50, c: 0.25 },
|
|
28
32
|
};
|
|
29
33
|
const DEFAULT_PRICING_KEY = 'sonnet'; // fallback for unrecognized model ids
|
|
30
34
|
|
|
@@ -40,6 +44,7 @@ function resolvePricingKey(modelId) {
|
|
|
40
44
|
if (id.includes('opus')) return { key: 'opus', estimated: false };
|
|
41
45
|
if (id.includes('sonnet')) return { key: 'sonnet', estimated: false };
|
|
42
46
|
if (id.includes('haiku')) return { key: 'haiku', estimated: false };
|
|
47
|
+
if (id.includes('fable')) return { key: 'fable', estimated: false };
|
|
43
48
|
return { key: DEFAULT_PRICING_KEY, estimated: true };
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -638,7 +643,7 @@ function resetIntradayRegistryForTests() {
|
|
|
638
643
|
/**
|
|
639
644
|
* Pure walk-and-parse step (no rollup write) — split out from
|
|
640
645
|
* refreshIntradayToday() so it can be benchmarked read-only against the real
|
|
641
|
-
* ~/.claude/projects
|
|
646
|
+
* ~/.claude/projects without touching
|
|
642
647
|
* the rollup file on disk.
|
|
643
648
|
*/
|
|
644
649
|
async function computeIntradayBuckets() {
|