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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* health-build-freshness.test.cjs — evaluateBuildFreshness separates
|
|
3
|
+
* restart-needed (installed differs from running) from publish-needed
|
|
4
|
+
* (repo HEAD differs from installed); both reported, never critical.
|
|
5
|
+
*
|
|
6
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/health-build-freshness.test.cjs
|
|
7
|
+
*/
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
import { test, expect } from 'vitest';
|
|
11
|
+
const { evaluateBuildFreshness } = require('../health.cjs');
|
|
12
|
+
|
|
13
|
+
test('all shas equal → nothing needed', () => {
|
|
14
|
+
const r = evaluateBuildFreshness({ running: 'abc1234', installedOnDisk: 'abc1234', repoHead: 'abc1234' });
|
|
15
|
+
expect(r).toMatchObject({ ok: true, restartNeeded: false, publishNeeded: false });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('installed differs from running → restart needed only', () => {
|
|
19
|
+
const r = evaluateBuildFreshness({ running: 'aaa1111', installedOnDisk: 'bbb2222', repoHead: 'bbb2222' });
|
|
20
|
+
expect(r).toMatchObject({ ok: true, restartNeeded: true, publishNeeded: false });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('repo HEAD differs from installed → publish needed only', () => {
|
|
24
|
+
const r = evaluateBuildFreshness({ running: 'bbb2222', installedOnDisk: 'bbb2222', repoHead: 'ccc3333' });
|
|
25
|
+
expect(r).toMatchObject({ ok: true, restartNeeded: false, publishNeeded: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('both differ → both reported, still ok (non-critical)', () => {
|
|
29
|
+
const r = evaluateBuildFreshness({ running: 'aaa1111', installedOnDisk: 'bbb2222', repoHead: 'ccc3333' });
|
|
30
|
+
expect(r).toMatchObject({ ok: true, restartNeeded: true, publishNeeded: true });
|
|
31
|
+
expect(r.note).toMatch(/restart needed/);
|
|
32
|
+
expect(r.note).toMatch(/publish needed/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('unknown inputs never claim a difference; short/long sha prefixes match', () => {
|
|
36
|
+
expect(evaluateBuildFreshness({})).toMatchObject({ restartNeeded: false, publishNeeded: false });
|
|
37
|
+
expect(evaluateBuildFreshness({ running: null, installedOnDisk: 'abc1234', repoHead: 'abc1234567890' }))
|
|
38
|
+
.toMatchObject({ restartNeeded: false, publishNeeded: false });
|
|
39
|
+
});
|
|
@@ -84,9 +84,23 @@ test('failing check with no fix omits the "(fix: ...)" suffix', () => {
|
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
test('end-to-end: check() reports delegation_chain for this repo, expected to pass here', async () => {
|
|
87
|
-
|
|
87
|
+
// Probes THIS machine's real user-scope config (~/.claude.json MCP registration, guard
|
|
88
|
+
// shims), so it reads the real home — not the per-run sandbox HOME. Read-only: scheduler
|
|
89
|
+
// state stays redirected to the sandbox (SM_SCHEDULER_HOME re-pinned after the HOME swap).
|
|
90
|
+
const sandboxHome = process.env.HOME;
|
|
91
|
+
const sandboxSchedulerHome = process.env.SM_SCHEDULER_HOME;
|
|
92
|
+
process.env.HOME = require('node:os').userInfo().homedir;
|
|
93
|
+
if (sandboxSchedulerHome !== undefined) process.env.SM_SCHEDULER_HOME = sandboxSchedulerHome;
|
|
94
|
+
process.env.SM_ALLOW_LIVE_ROOT_READS = '1';
|
|
95
|
+
let status;
|
|
96
|
+
try {
|
|
97
|
+
status = await check();
|
|
98
|
+
} finally {
|
|
99
|
+
delete process.env.SM_ALLOW_LIVE_ROOT_READS;
|
|
100
|
+
process.env.HOME = sandboxHome;
|
|
101
|
+
}
|
|
88
102
|
expect(status.components.delegation_chain).toBeDefined();
|
|
89
|
-
expect(status.components.delegation_chain.checks).toHaveLength(
|
|
103
|
+
expect(status.components.delegation_chain.checks).toHaveLength(9);
|
|
90
104
|
const ids = status.components.delegation_chain.checks.map((c) => c.id);
|
|
91
105
|
expect(ids).toEqual([
|
|
92
106
|
'scheduler-mcp',
|
|
@@ -97,6 +111,7 @@ test('end-to-end: check() reports delegation_chain for this repo, expected to pa
|
|
|
97
111
|
'prd-write-guard',
|
|
98
112
|
'destructive-git-guard',
|
|
99
113
|
'inline-implementation-guard',
|
|
114
|
+
'self-schedule-guard',
|
|
100
115
|
]);
|
|
101
116
|
// This repo registers the scheduler MCP at user scope (~/.claude.json) and
|
|
102
117
|
// wires guard-prd-writes in .claude/settings.json, and no longer carries a
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* health-queue-dispatch.test.cjs — `npm run health` must report non-GREEN
|
|
3
3
|
* when there is at least one DISPATCHABLE pending job (reusing
|
|
4
4
|
* classifyQueueStarvation's own 'starved' verdict, not merely "pending"),
|
|
5
|
-
* nothing running, and no
|
|
5
|
+
* nothing running, and no launch in a long time — the exact shape
|
|
6
6
|
* of the 2026-09-11 incident (27 pending across two projects, 0 running, for
|
|
7
7
|
* days, every other health surface reporting green). A dependsOn-blocked
|
|
8
8
|
* queue must NOT trip this the same way: ticking can't fix a blocked chain,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
'use strict';
|
|
19
19
|
|
|
20
20
|
import { test, expect } from 'vitest';
|
|
21
|
-
const { evaluateQueueDispatchHealth, DISPATCH_STALL_THRESHOLD_MS } = require('../health.cjs');
|
|
21
|
+
const { evaluateQueueDispatchHealth, DISPATCH_STALL_THRESHOLD_MS, DISPATCH_WARN_THRESHOLD_MS } = require('../health.cjs');
|
|
22
22
|
|
|
23
23
|
const CWD = '/home/bilko/Projects/starry-night-ships';
|
|
24
24
|
const NOW = Date.parse('2026-09-11T12:00:00.000Z');
|
|
@@ -28,7 +28,7 @@ test('a dispatchable pending job, 0 running, stale past the threshold — non-GR
|
|
|
28
28
|
const state = {
|
|
29
29
|
jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
|
|
30
30
|
paused: null,
|
|
31
|
-
|
|
31
|
+
lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
|
|
32
32
|
};
|
|
33
33
|
const result = evaluateQueueDispatchHealth(state, 0, NOW);
|
|
34
34
|
expect(result.ok).toBe(false);
|
|
@@ -44,7 +44,7 @@ test('every pending row behind a blocked dependsOn chain stays GREEN, reported a
|
|
|
44
44
|
{ slug: 'blocked-child', cwd: CWD, status: 'pending', dependsOn: ['dead'] },
|
|
45
45
|
],
|
|
46
46
|
paused: null,
|
|
47
|
-
|
|
47
|
+
lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
|
|
48
48
|
};
|
|
49
49
|
const result = evaluateQueueDispatchHealth(state, 0, NOW);
|
|
50
50
|
expect(result.ok).toBe(true);
|
|
@@ -57,7 +57,7 @@ test('healthy queue (recent dispatch attempt) is GREEN with no verdict', () => {
|
|
|
57
57
|
const state = {
|
|
58
58
|
jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
|
|
59
59
|
paused: null,
|
|
60
|
-
|
|
60
|
+
lastRunAt: idleFor(60_000),
|
|
61
61
|
};
|
|
62
62
|
const result = evaluateQueueDispatchHealth(state, 0, NOW);
|
|
63
63
|
expect(result).toEqual({ ok: true });
|
|
@@ -67,7 +67,7 @@ test('nothing pending is GREEN regardless of how stale the dispatch timestamp is
|
|
|
67
67
|
const state = {
|
|
68
68
|
jobs: [{ slug: 'a', cwd: CWD, status: 'completed' }],
|
|
69
69
|
paused: null,
|
|
70
|
-
|
|
70
|
+
lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
|
|
71
71
|
};
|
|
72
72
|
const result = evaluateQueueDispatchHealth(state, 0, NOW);
|
|
73
73
|
expect(result).toEqual({ ok: true });
|
|
@@ -77,8 +77,59 @@ test('work already running is GREEN — the queue is flowing', () => {
|
|
|
77
77
|
const state = {
|
|
78
78
|
jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
|
|
79
79
|
paused: null,
|
|
80
|
-
|
|
80
|
+
lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
|
|
81
81
|
};
|
|
82
82
|
const result = evaluateQueueDispatchHealth(state, 1, NOW);
|
|
83
83
|
expect(result).toEqual({ ok: true });
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
test('the two tiers are exported: 15-minute warn, 2-hour cold report', () => {
|
|
87
|
+
expect(DISPATCH_WARN_THRESHOLD_MS).toBe(15 * 60_000);
|
|
88
|
+
expect(DISPATCH_STALL_THRESHOLD_MS).toBe(2 * 60 * 60_000);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('starved between the warn and cold thresholds — ok:true but flagged warn', () => {
|
|
92
|
+
const state = {
|
|
93
|
+
jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
|
|
94
|
+
paused: null,
|
|
95
|
+
lastRunAt: idleFor(DISPATCH_WARN_THRESHOLD_MS + 60_000),
|
|
96
|
+
};
|
|
97
|
+
const result = evaluateQueueDispatchHealth(state, 0, NOW);
|
|
98
|
+
expect(result.ok).toBe(true);
|
|
99
|
+
expect(result.warn).toBe(true);
|
|
100
|
+
expect(result.starved).toBeUndefined();
|
|
101
|
+
expect(result.dispatchable).toBe(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('below the warn threshold is plain GREEN', () => {
|
|
105
|
+
const state = {
|
|
106
|
+
jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
|
|
107
|
+
paused: null,
|
|
108
|
+
lastRunAt: idleFor(DISPATCH_WARN_THRESHOLD_MS - 60_000),
|
|
109
|
+
};
|
|
110
|
+
expect(evaluateQueueDispatchHealth(state, 0, NOW)).toEqual({ ok: true });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('ticks that launch nothing (fresh lastDispatchAttemptAt, stale lastRunAt) still go non-GREEN', () => {
|
|
114
|
+
const state = {
|
|
115
|
+
jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
|
|
116
|
+
paused: null,
|
|
117
|
+
lastDispatchAttemptAt: idleFor(5_000),
|
|
118
|
+
lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
|
|
119
|
+
};
|
|
120
|
+
const result = evaluateQueueDispatchHealth(state, 0, NOW);
|
|
121
|
+
expect(result.ok).toBe(false);
|
|
122
|
+
expect(result.starved).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('rows held by an open launch breaker read blocked, not starved', () => {
|
|
126
|
+
const state = {
|
|
127
|
+
jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
|
|
128
|
+
paused: null,
|
|
129
|
+
lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
|
|
130
|
+
launchBlocks: { default: { kind: 'auth', at: NOW - 1000 } },
|
|
131
|
+
};
|
|
132
|
+
const result = evaluateQueueDispatchHealth(state, 0, NOW);
|
|
133
|
+
expect(result.ok).toBe(true);
|
|
134
|
+
expect(result.blocked).toBe(true);
|
|
135
|
+
});
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* since they're pure/fs-isolated — no need to mock os.homedir() through the
|
|
7
7
|
* full check() to cover the liveness logic.
|
|
8
8
|
*
|
|
9
|
-
* Run: timeout
|
|
9
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/health-tick-liveness.test.cjs
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
import { test } from 'vitest';
|
|
15
15
|
const assert = require('node:assert/strict');
|
|
16
16
|
const fs = require('node:fs');
|
|
17
17
|
const os = require('node:os');
|
|
@@ -72,6 +72,7 @@ test('no false positive: running is already at concurrencyCap', () => {
|
|
|
72
72
|
{ slug: '544-stuck-job', status: 'pending' },
|
|
73
73
|
],
|
|
74
74
|
});
|
|
75
|
+
q.slotCap = 3; // machine-wide sessionSlots pool is the only cap now
|
|
75
76
|
const result = evaluateTickLiveness(q, null, NOW);
|
|
76
77
|
assert.strictEqual(result.stalled, false);
|
|
77
78
|
assert.strictEqual(result.reason, 'at-capacity');
|
|
@@ -105,9 +106,16 @@ test('degrades honestly: heartbeat missing/stale means utilization cannot be rul
|
|
|
105
106
|
assert.strictEqual(result.reason, 'cannot-verify-utilization');
|
|
106
107
|
});
|
|
107
108
|
|
|
108
|
-
test('
|
|
109
|
+
test('manual firePolicy: pending work with free capacity is configured behaviour, not a stall', () => {
|
|
109
110
|
const q = baseQueue({ config: { ...baseQueue().config, firePolicy: 'manual' } });
|
|
110
111
|
const result = evaluateTickLiveness(q, null, NOW);
|
|
112
|
+
assert.strictEqual(result.stalled, false);
|
|
113
|
+
assert.strictEqual(result.reason, 'manual-fire-policy');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('does not caveat when firePolicy is neither when-available nor manual (no heartbeat needed)', () => {
|
|
117
|
+
const q = baseQueue({ config: { ...baseQueue().config, firePolicy: 'always' } });
|
|
118
|
+
const result = evaluateTickLiveness(q, null, NOW);
|
|
111
119
|
assert.strictEqual(result.stalled, true);
|
|
112
120
|
});
|
|
113
121
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* health-usage-poller.test.cjs — `npm run health` must report
|
|
3
|
-
* the usage/rate-limit poller
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* health-usage-poller.test.cjs — `npm run health` must report GREEN / YELLOW
|
|
3
|
+
* / RED for the usage/rate-limit poller (scheduler.cjs pollLoop), driven by
|
|
4
|
+
* the shared usageCircuit's OWN open/closed state and how long it's been
|
|
5
|
+
* open (usageCircuitState/usageCircuitOpenedAt, persisted to
|
|
6
|
+
* ~/.claude/session-manager/scheduler-state.json by persistSchedulerState —
|
|
7
|
+
* this is a separate `npm run health` process, so it reads the PERSISTED
|
|
8
|
+
* circuit fields rather than holding the live in-memory breaker) — not the
|
|
9
|
+
* old binary consecutiveFailures<5 check, which read flatly non-GREEN
|
|
10
|
+
* forever after the fifth failure with no way to distinguish "just tripped"
|
|
11
|
+
* from "down for hours." Exercises evaluateUsagePollerHealth() and
|
|
12
|
+
* loadUsagePollerState() directly since both are pure/file-scoped, matching
|
|
10
13
|
* every other evaluate* helper's test pattern in this file — deliberately NOT
|
|
11
14
|
* driving the full (slow, machine-state-coupled) check() for this.
|
|
12
15
|
*
|
|
@@ -20,44 +23,88 @@ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
|
20
23
|
import { tmpdir } from 'node:os';
|
|
21
24
|
import { join } from 'node:path';
|
|
22
25
|
const { evaluateUsagePollerHealth, loadUsagePollerState } = require('../health.cjs');
|
|
23
|
-
const {
|
|
26
|
+
const { FAILURE_STREAK_ESCALATION_MS } = require('../scheduler.cjs');
|
|
24
27
|
|
|
25
28
|
test('missing state (fresh install / poller never ran) is ok, not applicable', () => {
|
|
26
29
|
expect(evaluateUsagePollerHealth(null)).toEqual({ ok: true, applicable: false });
|
|
27
30
|
expect(evaluateUsagePollerHealth(undefined)).toEqual({ ok: true, applicable: false });
|
|
28
31
|
});
|
|
29
32
|
|
|
30
|
-
test('
|
|
31
|
-
const result = evaluateUsagePollerHealth({
|
|
33
|
+
test('circuit closed is GREEN, regardless of a nonzero consecutiveFailures count', () => {
|
|
34
|
+
const result = evaluateUsagePollerHealth({ usageCircuitState: 'closed', consecutiveFailures: 2, backoffMs: 60_000, lastPollAt: Date.now() });
|
|
32
35
|
expect(result.ok).toBe(true);
|
|
33
36
|
expect(result.applicable).toBe(true);
|
|
37
|
+
expect(result.color).toBe('GREEN');
|
|
34
38
|
expect(result.message).toBeUndefined();
|
|
35
39
|
});
|
|
36
40
|
|
|
37
|
-
test('
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
expect(result.ok).toBe(false);
|
|
42
|
-
expect(result.message).toMatch(new RegExp(`${FAILURE_STREAK_WARN_THRESHOLD} consecutive failures`));
|
|
41
|
+
test('no usageCircuitState field at all (pre-upgrade sidecar) defaults to closed -> GREEN', () => {
|
|
42
|
+
const result = evaluateUsagePollerHealth({ consecutiveFailures: 0, backoffMs: null, lastPollAt: Date.now() });
|
|
43
|
+
expect(result.ok).toBe(true);
|
|
44
|
+
expect(result.color).toBe('GREEN');
|
|
43
45
|
});
|
|
44
46
|
|
|
45
|
-
test('
|
|
46
|
-
const
|
|
47
|
+
test('circuit open for under FAILURE_STREAK_ESCALATION_MS (30 min) is YELLOW, not GREEN — but still ok:true, not yet critical', () => {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const result = evaluateUsagePollerHealth({
|
|
50
|
+
usageCircuitState: 'open',
|
|
51
|
+
usageCircuitOpenedAt: now - 5 * 60_000, // 5 minutes ago
|
|
52
|
+
consecutiveFailures: 6,
|
|
53
|
+
backoffMs: 480_000,
|
|
54
|
+
lastPollAt: now,
|
|
55
|
+
});
|
|
56
|
+
// ok:true is deliberate — the circuit's own open threshold (3 consecutive
|
|
57
|
+
// failures) is LOWER than the old 5-failure WARN, so an ok:false YELLOW
|
|
58
|
+
// would silently tighten the overall `npm run health` rollup's failure bar
|
|
59
|
+
// from 5 down to 3. Graceful degradation must not itself read as critical.
|
|
60
|
+
expect(result.ok).toBe(true);
|
|
61
|
+
expect(result.color).toBe('YELLOW');
|
|
62
|
+
expect(result.message).toMatch(/YELLOW/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('circuit open for AT LEAST FAILURE_STREAK_ESCALATION_MS (30 min) is RED — ok:false, this is the critical state', () => {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const result = evaluateUsagePollerHealth({
|
|
68
|
+
usageCircuitState: 'open',
|
|
69
|
+
usageCircuitOpenedAt: now - FAILURE_STREAK_ESCALATION_MS,
|
|
70
|
+
consecutiveFailures: 57,
|
|
71
|
+
backoffMs: 480_000,
|
|
72
|
+
lastPollAt: now,
|
|
73
|
+
});
|
|
47
74
|
expect(result.ok).toBe(false);
|
|
75
|
+
expect(result.color).toBe('RED');
|
|
48
76
|
expect(result.consecutiveFailures).toBe(57);
|
|
49
77
|
expect(result.backoffMs).toBe(480_000);
|
|
50
|
-
expect(result.message).toMatch(/
|
|
78
|
+
expect(result.message).toMatch(/RED/);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('half_open (mid-probe) is treated the same as open — not GREEN just because a probe is in flight', () => {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
const result = evaluateUsagePollerHealth({
|
|
84
|
+
usageCircuitState: 'half_open',
|
|
85
|
+
usageCircuitOpenedAt: now - 60_000,
|
|
86
|
+
consecutiveFailures: 5,
|
|
87
|
+
lastPollAt: now,
|
|
88
|
+
});
|
|
89
|
+
expect(result.ok).toBe(true); // still YELLOW-shaped (under the red threshold), so still ok:true
|
|
90
|
+
expect(result.color).toBe('YELLOW');
|
|
51
91
|
});
|
|
52
92
|
|
|
53
|
-
test('respects a custom
|
|
54
|
-
|
|
55
|
-
|
|
93
|
+
test('respects a custom redThresholdMs argument', () => {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const openedAt = now - 10_000;
|
|
96
|
+
const red = evaluateUsagePollerHealth({ usageCircuitState: 'open', usageCircuitOpenedAt: openedAt, consecutiveFailures: 5 }, 5_000);
|
|
97
|
+
expect(red.color).toBe('RED');
|
|
98
|
+
expect(red.ok).toBe(false);
|
|
99
|
+
const yellow = evaluateUsagePollerHealth({ usageCircuitState: 'open', usageCircuitOpenedAt: openedAt, consecutiveFailures: 5 }, 60_000);
|
|
100
|
+
expect(yellow.color).toBe('YELLOW');
|
|
101
|
+
expect(yellow.ok).toBe(true);
|
|
56
102
|
});
|
|
57
103
|
|
|
58
104
|
test('tolerates a malformed/partial state object without throwing', () => {
|
|
59
105
|
const result = evaluateUsagePollerHealth({});
|
|
60
106
|
expect(result.ok).toBe(true);
|
|
107
|
+
expect(result.color).toBe('GREEN');
|
|
61
108
|
expect(result.consecutiveFailures).toBe(0);
|
|
62
109
|
});
|
|
63
110
|
|
|
@@ -135,7 +135,7 @@ test('appendRollupDays: compacts (dedupes) once file exceeds threshold', async (
|
|
|
135
135
|
{ date: '2026-07-01', projectDir: 'p', modelId: 'sonnet', inputTokens: 2 },
|
|
136
136
|
]);
|
|
137
137
|
await historyRollup.compact();
|
|
138
|
-
const raw = fs.readFileSync(historyRollup.
|
|
138
|
+
const raw = fs.readFileSync(historyRollup.historyRollupPath(), 'utf8');
|
|
139
139
|
const nonEmptyLines = raw.split('\n').filter((l) => l.trim());
|
|
140
140
|
expect(nonEmptyLines.length).toBe(1);
|
|
141
141
|
expect(JSON.parse(nonEmptyLines[0]).inputTokens).toBe(2);
|
|
@@ -305,7 +305,7 @@ test('refreshIntradayToday: upserts a provisional (non-finalized) line for today
|
|
|
305
305
|
// duplicates — today's line for the same key once the file is compacted.
|
|
306
306
|
await historyAggregator.refreshIntradayToday();
|
|
307
307
|
await historyRollup.compact();
|
|
308
|
-
const raw = fs.readFileSync(historyRollup.
|
|
308
|
+
const raw = fs.readFileSync(historyRollup.historyRollupPath(), 'utf8');
|
|
309
309
|
const matchingLines = raw.split('\n').filter((l) => {
|
|
310
310
|
if (!l.trim()) return false;
|
|
311
311
|
const obj = JSON.parse(l);
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* differences between log sources don't break the match.
|
|
15
15
|
*
|
|
16
16
|
* Does NOT spawn claude -p. Run:
|
|
17
|
-
* timeout
|
|
17
|
+
* timeout 300 npx vitest run src/main/__tests__/kg-augment.test.cjs
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
import { test, describe, beforeAll as before, afterAll as after } from 'vitest';
|
|
21
21
|
const assert = require('node:assert/strict');
|
|
22
22
|
const fs = require('node:fs');
|
|
23
23
|
const fsp = require('node:fs/promises');
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* mcpStatus.test.cjs — unit tests for the `claude mcp list` output parser.
|
|
3
3
|
*
|
|
4
|
-
* Run: timeout
|
|
4
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/mcpStatus.test.cjs
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import { test } from 'vitest';
|
|
10
10
|
const assert = require('node:assert/strict');
|
|
11
11
|
const { parseMcpList } = require('../mcpStatus.cjs');
|
|
12
12
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* needsReviewLedger.test.cjs — unit tests for src/main/lib/needsReviewLedger.cjs's
|
|
3
|
+
* pure ladder-rung classification, line builders, and rollup reduction.
|
|
4
|
+
*
|
|
5
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/needsReviewLedger.test.cjs
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
import { test, expect } from 'vitest';
|
|
10
|
+
const {
|
|
11
|
+
LADDER_RUNGS,
|
|
12
|
+
classifyLadderRung,
|
|
13
|
+
buildNeedsReviewEntryLine,
|
|
14
|
+
buildNeedsReviewResolutionLine,
|
|
15
|
+
reduceNeedsReviewLedger,
|
|
16
|
+
} = require('../lib/needsReviewLedger.cjs');
|
|
17
|
+
|
|
18
|
+
// ---------- classifyLadderRung ----------
|
|
19
|
+
|
|
20
|
+
test('classifyLadderRung maps every known automated source to its named rung', () => {
|
|
21
|
+
expect(classifyLadderRung({ source: 'scheduler:mechanicalRecovery' })).toBe('mechanical-recovery');
|
|
22
|
+
expect(classifyLadderRung({ source: 'spawnJob:dispatch', reason: 'dispatched for resume-recovery' })).toBe('resume-recovery');
|
|
23
|
+
expect(classifyLadderRung({ source: 'spawnInvestigation:start' })).toBe('auto-fix');
|
|
24
|
+
expect(classifyLadderRung({ source: 'spawnJob:auto-promote' })).toBe('auto-fix');
|
|
25
|
+
expect(classifyLadderRung({ source: 'reverifyNeedsReview:auto-promote' })).toBe('auto-fix');
|
|
26
|
+
expect(classifyLadderRung({ source: 'reverifyNeedsReview:heal' })).toBe('reverify');
|
|
27
|
+
expect(classifyLadderRung({ source: 'needsReviewAutoResolve' })).toBe('reverify');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('classifyLadderRung: ordinary spawnJob:dispatch (no resume-recovery reason) is NOT resume-recovery', () => {
|
|
31
|
+
// 'spawnJob:dispatch' is shared with the everyday pending->running dispatch
|
|
32
|
+
// — only the resume-recovery REASON text distinguishes the two.
|
|
33
|
+
expect(classifyLadderRung({ source: 'spawnJob:dispatch', reason: 'dispatched for execution' })).toBe('manual-reset');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('classifyLadderRung: unrecognized/explicit-reset sources default to manual-reset', () => {
|
|
37
|
+
expect(classifyLadderRung({ source: 'ipc:schedule:reset-job' })).toBe('manual-reset');
|
|
38
|
+
expect(classifyLadderRung({ source: 'remote:resetJob' })).toBe('manual-reset');
|
|
39
|
+
expect(classifyLadderRung({ source: 'clear-queue' })).toBe('manual-reset');
|
|
40
|
+
expect(classifyLadderRung({})).toBe('manual-reset');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('LADDER_RUNGS names exactly the 6 documented rungs', () => {
|
|
44
|
+
expect(LADDER_RUNGS).toEqual(['mechanical-recovery', 'resume-recovery', 'quarantine', 'auto-fix', 'reverify', 'manual-reset']);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ---------- buildNeedsReviewEntryLine / buildNeedsReviewResolutionLine ----------
|
|
48
|
+
|
|
49
|
+
test('buildNeedsReviewEntryLine: park reason prefers verifierVerdict, then heldReason, then error', () => {
|
|
50
|
+
const entry = { to: 'needs_review', source: 'spawnJob:finalize', at: '2026-09-13T00:00:00.000Z' };
|
|
51
|
+
expect(buildNeedsReviewEntryLine({ slug: 's1', verifierVerdict: 'uncommitted_changes', heldReason: 'held', error: 'err' }, entry).reason).toBe('uncommitted_changes');
|
|
52
|
+
expect(buildNeedsReviewEntryLine({ slug: 's1', heldReason: 'held', error: 'err' }, entry).reason).toBe('held');
|
|
53
|
+
expect(buildNeedsReviewEntryLine({ slug: 's1', error: 'err' }, entry).reason).toBe('err');
|
|
54
|
+
expect(buildNeedsReviewEntryLine({ slug: 's1' }, entry).reason).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('buildNeedsReviewEntryLine: carries slug/cwd/epicId/runId/at/source verbatim', () => {
|
|
58
|
+
const job = { slug: '42-x', cwd: '/proj', epicId: 'epic-1', runId: 'run-9' };
|
|
59
|
+
const entry = { to: 'needs_review', source: 'spawnJob:finalize', at: '2026-09-13T00:00:00.000Z' };
|
|
60
|
+
const line = buildNeedsReviewEntryLine(job, entry);
|
|
61
|
+
expect(line).toEqual({
|
|
62
|
+
kind: 'needs_review_entry',
|
|
63
|
+
slug: '42-x',
|
|
64
|
+
cwd: '/proj',
|
|
65
|
+
epicId: 'epic-1',
|
|
66
|
+
runId: 'run-9',
|
|
67
|
+
at: '2026-09-13T00:00:00.000Z',
|
|
68
|
+
reason: null,
|
|
69
|
+
source: 'spawnJob:finalize',
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('buildNeedsReviewResolutionLine: references the EPISODE runId, not job.runId, and computes dwellMs', () => {
|
|
74
|
+
const job = { slug: '42-x', cwd: '/proj', epicId: 'epic-1', runId: 'run-LATER' };
|
|
75
|
+
const entry = { to: 'completed', source: 'reverifyNeedsReview:heal', at: '2026-09-13T00:10:00.000Z' };
|
|
76
|
+
const episode = { runId: 'run-ORIGINAL', enteredAt: '2026-09-13T00:00:00.000Z' };
|
|
77
|
+
const line = buildNeedsReviewResolutionLine(job, entry, episode);
|
|
78
|
+
expect(line.runId).toBe('run-ORIGINAL');
|
|
79
|
+
expect(line.resolvedTo).toBe('completed');
|
|
80
|
+
expect(line.ladderRung).toBe('reverify');
|
|
81
|
+
expect(line.dwellMs).toBe(10 * 60_000);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('buildNeedsReviewResolutionLine: dwellMs is null when the episode has no enteredAt (defensive — should not happen in practice)', () => {
|
|
85
|
+
const job = { slug: '42-x', runId: 'run-1' };
|
|
86
|
+
const entry = { to: 'failed', source: 'ipc:schedule:reset-job', at: '2026-09-13T00:00:00.000Z' };
|
|
87
|
+
const line = buildNeedsReviewResolutionLine(job, entry, { runId: 'run-1', enteredAt: null });
|
|
88
|
+
expect(line.dwellMs).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ---------- reduceNeedsReviewLedger ----------
|
|
92
|
+
|
|
93
|
+
function entryLine({ slug, runId, at, reason = 'uncommitted_changes', source = 'spawnJob:finalize' }) {
|
|
94
|
+
return { kind: 'needs_review_entry', slug, cwd: null, epicId: null, runId, at, reason, source };
|
|
95
|
+
}
|
|
96
|
+
function resolutionLine({ slug, runId, at, resolvedTo = 'completed', ladderRung = 'reverify', source = 'reverifyNeedsReview:heal', dwellMs = 1000 }) {
|
|
97
|
+
return { kind: 'needs_review_resolution', slug, cwd: null, epicId: null, runId, at, resolvedTo, ladderRung, source, dwellMs };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
test('reduceNeedsReviewLedger: entry-only (unresolved) episode counts toward byReason and unresolvedCount, not byLadderRung', () => {
|
|
101
|
+
const lines = [entryLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:00.000Z', reason: 'silent_no_op' })];
|
|
102
|
+
const rollup = reduceNeedsReviewLedger(lines);
|
|
103
|
+
expect(rollup.byReason).toEqual({ silent_no_op: 1 });
|
|
104
|
+
expect(rollup.byLadderRung).toEqual({});
|
|
105
|
+
expect(rollup.unresolvedCount).toBe(1);
|
|
106
|
+
expect(rollup.dwellMsP50).toBeNull();
|
|
107
|
+
expect(rollup.dwellMsP90).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('reduceNeedsReviewLedger: entry+resolution pair resolves and contributes dwell', () => {
|
|
111
|
+
const lines = [
|
|
112
|
+
entryLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:00.000Z' }),
|
|
113
|
+
resolutionLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:05.000Z', dwellMs: 5000, ladderRung: 'mechanical-recovery' }),
|
|
114
|
+
];
|
|
115
|
+
const rollup = reduceNeedsReviewLedger(lines);
|
|
116
|
+
expect(rollup.unresolvedCount).toBe(0);
|
|
117
|
+
expect(rollup.byLadderRung).toEqual({ 'mechanical-recovery': 1 });
|
|
118
|
+
expect(rollup.dwellMsP50).toBe(5000);
|
|
119
|
+
expect(rollup.dwellMsP90).toBe(5000);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('reduceNeedsReviewLedger: repeated entries for the same slug (different runIds) count as independent episodes', () => {
|
|
123
|
+
const lines = [
|
|
124
|
+
entryLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:00.000Z', reason: 'uncommitted_changes' }),
|
|
125
|
+
resolutionLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:10.000Z', dwellMs: 10_000, ladderRung: 'manual-reset' }),
|
|
126
|
+
entryLine({ slug: 'a', runId: 'r2', at: '2026-09-13T01:00:00.000Z', reason: 'uncommitted_changes' }),
|
|
127
|
+
// r2 never resolves.
|
|
128
|
+
];
|
|
129
|
+
const rollup = reduceNeedsReviewLedger(lines);
|
|
130
|
+
expect(rollup.byReason).toEqual({ uncommitted_changes: 2 });
|
|
131
|
+
expect(rollup.unresolvedCount).toBe(1);
|
|
132
|
+
expect(rollup.byLadderRung).toEqual({ 'manual-reset': 1 });
|
|
133
|
+
expect(rollup.dwellMsP50).toBe(10_000);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('reduceNeedsReviewLedger: p50/p90 over several resolved episodes', () => {
|
|
137
|
+
const dwells = [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10_000];
|
|
138
|
+
const lines = dwells.flatMap((d, i) => [
|
|
139
|
+
entryLine({ slug: `s${i}`, runId: `r${i}`, at: '2026-09-13T00:00:00.000Z' }),
|
|
140
|
+
resolutionLine({ slug: `s${i}`, runId: `r${i}`, at: '2026-09-13T00:00:00.000Z', dwellMs: d }),
|
|
141
|
+
]);
|
|
142
|
+
const rollup = reduceNeedsReviewLedger(lines);
|
|
143
|
+
expect(rollup.dwellMsP50).toBe(5000);
|
|
144
|
+
expect(rollup.dwellMsP90).toBe(9000);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('reduceNeedsReviewLedger: ignores unrelated (terminal) rows mixed into the same lines array', () => {
|
|
148
|
+
const lines = [
|
|
149
|
+
{ kind: 'terminal', slug: 'a', status: 'completed', runId: 'r1' },
|
|
150
|
+
{ slug: 'b', status: 'failed', runId: 'r2' }, // legacy row, no kind at all
|
|
151
|
+
entryLine({ slug: 'c', runId: 'r3', at: '2026-09-13T00:00:00.000Z' }),
|
|
152
|
+
];
|
|
153
|
+
const rollup = reduceNeedsReviewLedger(lines);
|
|
154
|
+
expect(rollup.unresolvedCount).toBe(1);
|
|
155
|
+
expect(Object.keys(rollup.byReason)).toEqual(['uncommitted_changes']);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('reduceNeedsReviewLedger: empty/non-array input returns a zeroed rollup, never throws', () => {
|
|
159
|
+
expect(reduceNeedsReviewLedger([])).toEqual({ byReason: {}, byLadderRung: {}, unresolvedCount: 0, dwellMsP50: null, dwellMsP90: null });
|
|
160
|
+
expect(reduceNeedsReviewLedger(null)).toEqual({ byReason: {}, byLadderRung: {}, unresolvedCount: 0, dwellMsP50: null, dwellMsP90: null });
|
|
161
|
+
expect(reduceNeedsReviewLedger(undefined)).toEqual({ byReason: {}, byLadderRung: {}, unresolvedCount: 0, dwellMsP50: null, dwellMsP90: null });
|
|
162
|
+
});
|