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,92 @@
|
|
|
1
|
+
// PRD pn-01: procName primitive. HOME is redirected to a temp dir for the whole file
|
|
2
|
+
// (procnamesRoot() resolves lazily from HOME) — never the real ~/.claude.
|
|
3
|
+
import { describe, it, expect, afterAll } from 'vitest';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'procname-'));
|
|
9
|
+
const realHome = process.env.HOME;
|
|
10
|
+
process.env.HOME = tmpHome; // stays redirected: procnamesRoot() resolves lazily
|
|
11
|
+
const { aliasBinFor, smArgv0, procnamesRoot, pruneStaleAliases } = require('../procName.cjs');
|
|
12
|
+
|
|
13
|
+
afterAll(() => {
|
|
14
|
+
process.env.HOME = realHome;
|
|
15
|
+
try { fs.chmodSync(procnamesRoot(), 0o700); } catch { /* absent */ }
|
|
16
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function fakeBin(name) {
|
|
20
|
+
const p = path.join(tmpHome, name);
|
|
21
|
+
fs.writeFileSync(p, '#!/bin/sh\nexit 0\n', { mode: 0o755 });
|
|
22
|
+
return p;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('procName', () => {
|
|
26
|
+
it('procnamesRoot() lives under the temp HOME machine-state root', () => {
|
|
27
|
+
expect(procnamesRoot()).toBe(path.join(tmpHome, '.claude', 'session-manager', 'procnames'));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('creates a symlink and returns the alias path', () => {
|
|
31
|
+
const bin = fakeBin('bin-a');
|
|
32
|
+
const out = aliasBinFor(bin, 'sm-claude-job');
|
|
33
|
+
expect(out).toBe(path.join(procnamesRoot(), 'sm-claude-job'));
|
|
34
|
+
expect(fs.readlinkSync(out)).toBe(bin);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('self-repairs a drifted link', () => {
|
|
38
|
+
const a = fakeBin('bin-b1');
|
|
39
|
+
const b = fakeBin('bin-b2');
|
|
40
|
+
aliasBinFor(a, 'sm-claude-chat');
|
|
41
|
+
const out = aliasBinFor(b, 'sm-claude-chat');
|
|
42
|
+
expect(fs.readlinkSync(out)).toBe(b);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('throws on an alias longer than 15 chars', () => {
|
|
46
|
+
expect(() => aliasBinFor(fakeBin('bin-c'), 'sm-claude-toolong-x')).toThrow(/TASK_COMM_LEN/);
|
|
47
|
+
expect(() => aliasBinFor(fakeBin('bin-c'), 'session-manager')).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('returns the original bin for an unresolvable bare name', () => {
|
|
51
|
+
expect(aliasBinFor('definitely-not-a-real-cmd-xyz', 'sm-claude-aux')).toBe('definitely-not-a-real-cmd-xyz');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it.skipIf(process.getuid && process.getuid() === 0)('fails open when procnamesRoot() is read-only', () => {
|
|
55
|
+
const bin = fakeBin('bin-d');
|
|
56
|
+
fs.mkdirSync(procnamesRoot(), { recursive: true });
|
|
57
|
+
fs.chmodSync(procnamesRoot(), 0o500);
|
|
58
|
+
try {
|
|
59
|
+
expect(aliasBinFor(bin, 'sm-shell')).toBe(bin);
|
|
60
|
+
} finally {
|
|
61
|
+
fs.chmodSync(procnamesRoot(), 0o700);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('smArgv0 shape, sanitisation, and length bound', () => {
|
|
66
|
+
expect(smArgv0('job', 'prd 12/x!')).toBe('sm-claude-job:prd12x');
|
|
67
|
+
expect(smArgv0('job', 'a'.repeat(500)).length).toBeLessThanOrEqual(120);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('smArgv0 always matches the live /\\bclaude\\b/ gates for every role', () => {
|
|
71
|
+
// Replicated verbatim from scheduler.cjs killOrphanClaudePid & reaperHelpers.cjs claudePidAlive.
|
|
72
|
+
const gate = /\bclaude\b/;
|
|
73
|
+
for (const role of ['job', 'chat', 'aux', 'helper', '', undefined, '!!!', 'x'.repeat(300)]) {
|
|
74
|
+
for (const detail of ['abc', '', undefined, null, '///', 'y'.repeat(300)]) {
|
|
75
|
+
const label = smArgv0(role, detail);
|
|
76
|
+
expect(gate.test(label)).toBe(true);
|
|
77
|
+
expect(label.length).toBeLessThanOrEqual(120);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('pruneStaleAliases removes dead links and keeps live ones', () => {
|
|
83
|
+
const live = fakeBin('bin-live');
|
|
84
|
+
const dead = fakeBin('bin-dead');
|
|
85
|
+
const liveAlias = aliasBinFor(live, 'sm-live');
|
|
86
|
+
const deadAlias = aliasBinFor(dead, 'sm-dead');
|
|
87
|
+
fs.rmSync(dead);
|
|
88
|
+
expect(() => pruneStaleAliases()).not.toThrow();
|
|
89
|
+
expect(fs.existsSync(liveAlias)).toBe(true);
|
|
90
|
+
expect(fs.lstatSync(deadAlias, { throwIfNoEntry: false })).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -121,3 +121,70 @@ test('readMerged (async) recovers the same real fixture', async () => {
|
|
|
121
121
|
expect(state.machineStateRecoveryMode).toBe('prefix');
|
|
122
122
|
expect(state.lastRunAt).toBe('2026-09-11T10:00:14.441Z');
|
|
123
123
|
});
|
|
124
|
+
|
|
125
|
+
// ---------- per-cwd shard quarantine (never recover, never write) ----------
|
|
126
|
+
|
|
127
|
+
function makeProject(name, jobs) {
|
|
128
|
+
const cwd = path.join(tmpHome, 'Projects', name);
|
|
129
|
+
const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
|
|
130
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
131
|
+
const file = path.join(stateDir, 'queue.json');
|
|
132
|
+
if (jobs) fs.writeFileSync(file, JSON.stringify({ jobs }));
|
|
133
|
+
const slugDir = path.join(tmpHome, '.claude', 'projects', `slug-${name}`);
|
|
134
|
+
fs.mkdirSync(slugDir, { recursive: true });
|
|
135
|
+
fs.writeFileSync(path.join(slugDir, 't.jsonl'), JSON.stringify({ cwd }) + '\n');
|
|
136
|
+
return { cwd, file };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const TORN = '{"jobs":[{"slug":"a","status":"running"}]} {"jobs":[{"slug":"a","stat';
|
|
140
|
+
const goodJob = (slug, cwd) => ({
|
|
141
|
+
slug, status: 'pending', prdPath: `/x/${slug}.md`, cwd, scheduledFor: null, createdAt: new Date().toISOString(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('a torn shard is quarantined per cwd; the machine-level `unreadable` stays unset and the sibling shard still loads', () => {
|
|
145
|
+
const good = makeProject('good', [goodJob('g1', path.join(tmpHome, 'Projects', 'good'))]);
|
|
146
|
+
const torn = makeProject('torn');
|
|
147
|
+
fs.writeFileSync(torn.file, TORN);
|
|
148
|
+
const opts = { projectsDir: path.join(tmpHome, '.claude', 'projects') };
|
|
149
|
+
|
|
150
|
+
const state = queueStore.readMergedSync(opts);
|
|
151
|
+
|
|
152
|
+
expect(state.unreadable).toBeUndefined();
|
|
153
|
+
expect(state.unreadableCwds).toHaveLength(1);
|
|
154
|
+
expect(state.unreadableCwds[0]).toMatchObject({ cwd: torn.cwd, file: torn.file });
|
|
155
|
+
expect(typeof state.unreadableCwds[0].error).toBe('string');
|
|
156
|
+
expect(state.jobs.map((j) => j.slug)).toEqual(['g1']);
|
|
157
|
+
expect(state.sourceCwds).not.toContain(torn.cwd);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('readMerged (async) quarantines the same way', async () => {
|
|
161
|
+
const torn = makeProject('torn2');
|
|
162
|
+
fs.writeFileSync(torn.file, TORN);
|
|
163
|
+
const state = await queueStore.readMerged({ projectsDir: path.join(tmpHome, '.claude', 'projects') });
|
|
164
|
+
expect(state.unreadable).toBeUndefined();
|
|
165
|
+
expect(state.unreadableCwds.map((u) => u.cwd)).toEqual([torn.cwd]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('writeSplit leaves a torn shard byte-identical even when merged rows carry its cwd, and still writes healthy shards', async () => {
|
|
169
|
+
const good = makeProject('good3', [goodJob('g1', path.join(tmpHome, 'Projects', 'good3'))]);
|
|
170
|
+
const torn = makeProject('torn3');
|
|
171
|
+
fs.writeFileSync(torn.file, TORN);
|
|
172
|
+
const before = fs.readFileSync(torn.file);
|
|
173
|
+
const opts = { projectsDir: path.join(tmpHome, '.claude', 'projects') };
|
|
174
|
+
|
|
175
|
+
const state = await queueStore.readMerged(opts);
|
|
176
|
+
state.jobs.push(goodJob('intruder', torn.cwd)); // a row that claims the torn cwd
|
|
177
|
+
state.jobs[0].status = 'running';
|
|
178
|
+
await queueStore.writeSplit(state, good.cwd);
|
|
179
|
+
|
|
180
|
+
expect(fs.readFileSync(torn.file).equals(before)).toBe(true);
|
|
181
|
+
const written = JSON.parse(fs.readFileSync(good.file, 'utf8'));
|
|
182
|
+
expect(written.jobs.map((j) => j.status)).toEqual(['running']);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('writeSplit never re-creates a quarantined shard that has no file at all', async () => {
|
|
186
|
+
const ghost = makeProject('ghost');
|
|
187
|
+
const state = { config: {}, jobs: [goodJob('x', ghost.cwd)], sourceCwds: [], unreadableCwds: [{ cwd: ghost.cwd, file: ghost.file, error: 'x' }] };
|
|
188
|
+
await queueStore.writeSplit(state, ghost.cwd);
|
|
189
|
+
expect(fs.existsSync(ghost.file)).toBe(false);
|
|
190
|
+
});
|
|
@@ -23,7 +23,8 @@ const { spawn } = require('node:child_process');
|
|
|
23
23
|
const {
|
|
24
24
|
selectReapableJobs, mapOutcomeToGateOutcome, classifyRunOutcome,
|
|
25
25
|
findLiveProcessForJob, logHasOutput, resolvePidlessGateOutcome,
|
|
26
|
-
isAlreadySatisfiedOnMain,
|
|
26
|
+
isAlreadySatisfiedOnMain, resolvePidlessFailureOverride,
|
|
27
|
+
readSpawnedPidFromLog, isLogFresh,
|
|
27
28
|
} = require('../reaperHelpers.cjs');
|
|
28
29
|
const { detectRateLimitInLog } = require('../rateLimitDetect.cjs');
|
|
29
30
|
|
|
@@ -339,6 +340,28 @@ test('findLiveProcessForJob: no live process anywhere → null', () => {
|
|
|
339
340
|
assert.strictEqual(pid, null);
|
|
340
341
|
});
|
|
341
342
|
|
|
343
|
+
// findLiveProcessForJob: runCwd — the 2026-09-13 in-place/shared-tree fix.
|
|
344
|
+
// A run with no dedicated worktree (no worktreeDir) and no slug in argv
|
|
345
|
+
// (dispatch never puts it there) is invisible to both existing matches; the
|
|
346
|
+
// runCwd+cmdline-claude combo match exists for exactly this shape. `sleep`
|
|
347
|
+
// is used as the /proc-visible process here (not a real `claude` binary), so
|
|
348
|
+
// this exercises the cwd-plus-cmdline-substring code path directly rather
|
|
349
|
+
// than depending on a real claude binary being installed.
|
|
350
|
+
test('findLiveProcessForJob: runCwd match requires BOTH cwd AND a claude-looking cmdline — bare cwd match is never sufficient', async () => {
|
|
351
|
+
if (process.platform !== 'linux' || !fs.existsSync('/proc')) return;
|
|
352
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-reaper-runcwd-'));
|
|
353
|
+
// Runs in `dir` but its cmdline has no "claude" in it — must NOT match.
|
|
354
|
+
const child = spawn('sleep', ['5'], { cwd: dir, stdio: 'ignore' });
|
|
355
|
+
try {
|
|
356
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
357
|
+
const pid = findLiveProcessForJob({ slug: 'unrelated-slug' }, { runCwd: dir });
|
|
358
|
+
assert.strictEqual(pid, null, 'a bare cwd match without a claude-looking cmdline must never match — shared-tree cross-match hazard');
|
|
359
|
+
} finally {
|
|
360
|
+
child.kill('SIGKILL');
|
|
361
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
342
365
|
// logHasOutput — the literal "did the run dir produce any log output" check
|
|
343
366
|
// that gates whether a pidless reap may assert gateOutcome: 'never_ran'.
|
|
344
367
|
|
|
@@ -397,3 +420,158 @@ test('isAlreadySatisfiedOnMain: no satisfying commits → null — the gate must
|
|
|
397
420
|
assert.strictEqual(isAlreadySatisfiedOnMain(null), null);
|
|
398
421
|
assert.strictEqual(isAlreadySatisfiedOnMain(undefined), null);
|
|
399
422
|
});
|
|
423
|
+
|
|
424
|
+
// resolvePidlessFailureOverride — PRD 1173. A pidless reap must never stamp
|
|
425
|
+
// 'failed' on a row that already carries proof (landedCommit) that its work
|
|
426
|
+
// shipped, purely because reproting bookkeeping (runtime.pid) was lost.
|
|
427
|
+
|
|
428
|
+
test('resolvePidlessFailureOverride: row with a landedCommit → override naming the recorded commit sha', () => {
|
|
429
|
+
const override = resolvePidlessFailureOverride({ slug: 'shipped-but-pidless', landedCommit: 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f' });
|
|
430
|
+
assert.ok(override, 'a row with landedCommit must produce a failure override');
|
|
431
|
+
assert.strictEqual(override.verdict, 'pidless_reap_with_landed_commit');
|
|
432
|
+
assert.strictEqual(override.landedCommit, 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f');
|
|
433
|
+
assert.match(override.reason, /f3e35a7f9e49f13a9eb8c338b0d086160e32a87f/, 'the reason string must name the recorded commit sha so a human can go straight to `git show <sha>`');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test('resolvePidlessFailureOverride: row with no landedCommit → null, existing failed transition untouched', () => {
|
|
437
|
+
assert.strictEqual(resolvePidlessFailureOverride({ slug: 'genuinely-never-ran' }), null);
|
|
438
|
+
assert.strictEqual(resolvePidlessFailureOverride({ slug: 'genuinely-never-ran', landedCommit: null }), null);
|
|
439
|
+
assert.strictEqual(resolvePidlessFailureOverride({ slug: 'genuinely-never-ran', landedCommit: '' }), null);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// readSpawnedPidFromLog — recovers the pid a pidless row's spawn actually
|
|
443
|
+
// produced from the run's own log (2026-09-13 incident: runId
|
|
444
|
+
// 2026-09-13T16-15-56-818Z spawned pid=3138458, logged it, but the
|
|
445
|
+
// runtime.pid persistence mutate failed silently and the pidless-grace
|
|
446
|
+
// reaper terminalized a live job it could have found here).
|
|
447
|
+
|
|
448
|
+
test('readSpawnedPidFromLog: parses the scheduler\'s own spawned-pid line', () => {
|
|
449
|
+
const p = writeTmpLog('some preamble\n[scheduler] spawned pid=3138458 sessionId=345ca5b1-c7a7-4167-9b55-f4905e3afe01 (process group)\n\nmore output\n');
|
|
450
|
+
assert.strictEqual(readSpawnedPidFromLog(p), 3138458);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test('readSpawnedPidFromLog: missing file → null', () => {
|
|
454
|
+
assert.strictEqual(readSpawnedPidFromLog('/nonexistent/path/does-not-exist.log'), null);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('readSpawnedPidFromLog: empty file → null', () => {
|
|
458
|
+
const p = writeTmpLog('');
|
|
459
|
+
assert.strictEqual(readSpawnedPidFromLog(p), null);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('readSpawnedPidFromLog: log with no spawned-pid line → null', () => {
|
|
463
|
+
const p = writeTmpLog('{"type":"result","subtype":"success","is_error":false,"result":"done"}\n');
|
|
464
|
+
assert.strictEqual(readSpawnedPidFromLog(p), null);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test('readSpawnedPidFromLog: null path → null', () => {
|
|
468
|
+
assert.strictEqual(readSpawnedPidFromLog(null), null);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// isLogFresh — pure predicate: a log modified within the window is positive
|
|
472
|
+
// proof of life, independent of runtime.pid bookkeeping.
|
|
473
|
+
|
|
474
|
+
test('isLogFresh: mtime within window → true', () => {
|
|
475
|
+
assert.strictEqual(isLogFresh(NOW - 60_000, NOW, 20 * 60_000), true);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test('isLogFresh: mtime outside window → false', () => {
|
|
479
|
+
assert.strictEqual(isLogFresh(NOW - 30 * 60_000, NOW, 20 * 60_000), false);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test('isLogFresh: missing/invalid mtime or window → false', () => {
|
|
483
|
+
assert.strictEqual(isLogFresh(null, NOW, 20 * 60_000), false);
|
|
484
|
+
assert.strictEqual(isLogFresh(NaN, NOW, 20 * 60_000), false);
|
|
485
|
+
assert.strictEqual(isLogFresh(NOW - 60_000, NOW, null), false);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// selectReapableJobs — Part B wiring: a pidless row past grace now consults
|
|
489
|
+
// the run log for a pid, and a fresh log skips the row entirely, BEFORE
|
|
490
|
+
// falling back to findLiveProcess or the existing terminal reap.
|
|
491
|
+
|
|
492
|
+
test('selectReapableJobs: pidless + past grace + log yields a LIVE pid → recovered, never reapable, row stays running', () => {
|
|
493
|
+
const jobs = [{ slug: 'zombie-log-alive', status: 'running', startedAt: agoMin(464) }];
|
|
494
|
+
const pidAlive = (pid) => pid === 3138458;
|
|
495
|
+
const getLogPid = () => 3138458;
|
|
496
|
+
const { reapable, recovered, warnings } = selectReapableJobs(jobs, NOW, {
|
|
497
|
+
pidAlive, grace: GRACE, getLogPid,
|
|
498
|
+
});
|
|
499
|
+
assert.deepStrictEqual(reapable, []);
|
|
500
|
+
assert.deepStrictEqual(warnings, []);
|
|
501
|
+
assert.strictEqual(recovered.length, 1);
|
|
502
|
+
assert.strictEqual(recovered[0].slug, 'zombie-log-alive');
|
|
503
|
+
assert.strictEqual(recovered[0].pid, 3138458);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test('selectReapableJobs: pidless + past grace + log yields a DEAD pid → still reaps (no regression)', () => {
|
|
507
|
+
const jobs = [{ slug: 'zombie-log-dead', status: 'running', startedAt: agoMin(464) }];
|
|
508
|
+
const getLogPid = () => 3138458;
|
|
509
|
+
const { reapable, recovered } = selectReapableJobs(jobs, NOW, {
|
|
510
|
+
pidAlive: alwaysDead, grace: GRACE, getLogPid,
|
|
511
|
+
});
|
|
512
|
+
assert.deepStrictEqual(recovered, []);
|
|
513
|
+
assert.strictEqual(reapable.length, 1);
|
|
514
|
+
assert.strictEqual(reapable[0].pidless, true);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('selectReapableJobs: pidless + past grace + FRESH log mtime → skipped entirely (neither reapable nor recovered)', () => {
|
|
518
|
+
const jobs = [{ slug: 'chatty-pidless', status: 'running', startedAt: agoMin(464) }];
|
|
519
|
+
const getLogMtimeMs = () => NOW - 60_000; // modified 1 minute ago
|
|
520
|
+
const { reapable, recovered, warnings } = selectReapableJobs(jobs, NOW, {
|
|
521
|
+
pidAlive: alwaysAlive, grace: GRACE, getLogMtimeMs, logFreshWindowMs: 20 * 60_000,
|
|
522
|
+
});
|
|
523
|
+
assert.deepStrictEqual(reapable, []);
|
|
524
|
+
assert.deepStrictEqual(recovered, []);
|
|
525
|
+
assert.strictEqual(warnings.length, 1);
|
|
526
|
+
assert.strictEqual(warnings[0].slug, 'chatty-pidless');
|
|
527
|
+
assert.match(warnings[0].reason, /log evidence/);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test('selectReapableJobs: pidless + past grace + STALE log mtime + no recoverable pid → reaps with the existing reason string, byte-identical', () => {
|
|
531
|
+
const jobs = [{ slug: 'zombie', status: 'running', startedAt: agoMin(464) }];
|
|
532
|
+
const getLogMtimeMs = () => NOW - 25 * 60_000; // stale relative to a 20m window
|
|
533
|
+
const { reapable } = selectReapableJobs(jobs, NOW, {
|
|
534
|
+
pidAlive: alwaysAlive, grace: GRACE, getLogMtimeMs, logFreshWindowMs: 20 * 60_000,
|
|
535
|
+
});
|
|
536
|
+
assert.strictEqual(reapable.length, 1);
|
|
537
|
+
assert.strictEqual(reapable[0].reason, 'reaped: no runtime.pid recorded after 10m — spawn never completed');
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('selectReapableJobs: a row WITH a live runtime.pid is untouched by every new code path', () => {
|
|
541
|
+
const jobs = [{ slug: 'live', status: 'running', runtime: { pid: 1 }, startedAt: agoMin(500) }];
|
|
542
|
+
const getLogPid = () => { throw new Error('must never be called for a live-pid row'); };
|
|
543
|
+
const getLogMtimeMs = () => { throw new Error('must never be called for a live-pid row'); };
|
|
544
|
+
const { reapable, recovered, warnings } = selectReapableJobs(jobs, NOW, {
|
|
545
|
+
pidAlive: alwaysAlive, grace: GRACE, getLogPid, getLogMtimeMs, logFreshWindowMs: 20 * 60_000,
|
|
546
|
+
});
|
|
547
|
+
assert.deepStrictEqual(reapable, []);
|
|
548
|
+
assert.deepStrictEqual(recovered, []);
|
|
549
|
+
assert.deepStrictEqual(warnings, []);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('selectReapableJobs: pidless + past grace + landedCommit on the row → reapable entry carries a failureOverride', () => {
|
|
553
|
+
const jobs = [{
|
|
554
|
+
slug: 'shipped-but-pidless',
|
|
555
|
+
status: 'running',
|
|
556
|
+
startedAt: agoMin(464),
|
|
557
|
+
landedCommit: 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f',
|
|
558
|
+
}];
|
|
559
|
+
const { reapable } = selectReapableJobs(jobs, NOW, { pidAlive: alwaysAlive, grace: GRACE });
|
|
560
|
+
assert.strictEqual(reapable.length, 1);
|
|
561
|
+
assert.strictEqual(reapable[0].pidless, true);
|
|
562
|
+
assert.ok(reapable[0].failureOverride, 'the reapable entry must carry the evidence-based override');
|
|
563
|
+
assert.strictEqual(reapable[0].failureOverride.landedCommit, 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f');
|
|
564
|
+
// The existing byte-for-byte reason string is untouched by this PRD — the
|
|
565
|
+
// override is carried as a separate field, not folded into `reason`.
|
|
566
|
+
assert.strictEqual(
|
|
567
|
+
reapable[0].reason,
|
|
568
|
+
'reaped: no runtime.pid recorded after 10m — spawn never completed',
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test('selectReapableJobs: pidless + past grace + NO landedCommit on the row → failureOverride is null, unchanged from today', () => {
|
|
573
|
+
const jobs = [{ slug: 'genuinely-never-ran', status: 'running', startedAt: agoMin(464) }];
|
|
574
|
+
const { reapable } = selectReapableJobs(jobs, NOW, { pidAlive: alwaysAlive, grace: GRACE });
|
|
575
|
+
assert.strictEqual(reapable.length, 1);
|
|
576
|
+
assert.strictEqual(reapable[0].failureOverride, null);
|
|
577
|
+
});
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* when the admin API is unreachable — only the readiness section may report
|
|
7
7
|
* unavailable.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
9
|
+
* The admin token path (schedulerPaths.adminTokenPath) resolves lazily from
|
|
10
|
+
* os.homedir(). Since os.homedir() reads process.env.HOME on POSIX, each test
|
|
11
|
+
* repoints HOME at a fresh tmp dir for its duration (restored in afterEach)
|
|
12
|
+
* so the token path resolves under that tmp dir, never the real
|
|
13
|
+
* ~/.claude/session-manager/admin-api.json.
|
|
14
14
|
*
|
|
15
15
|
* Run: timeout 120 npx vitest run src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs
|
|
16
16
|
*/
|
|
@@ -72,8 +72,7 @@ function requireServerWithHome(homeDir) {
|
|
|
72
72
|
process.env.HOME = homeDir;
|
|
73
73
|
delete require.cache[require.resolve(SERVER_PATH)];
|
|
74
74
|
const mod = require(SERVER_PATH);
|
|
75
|
-
|
|
76
|
-
originalHome = undefined;
|
|
75
|
+
// HOME stays redirected until afterEach: admin-token path resolves lazily.
|
|
77
76
|
return mod;
|
|
78
77
|
}
|
|
79
78
|
|