claude-code-session-manager 0.97.0 → 0.100.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 +3 -3
- package/bin/cli.cjs +16 -4
- package/bin/node-floor.cjs +41 -0
- package/dist/assets/{DataModel-D93FoaOm.js → DataModel-HdR0AxFq.js} +1 -1
- package/dist/assets/{History-CXxxqNP1.js → History-Dr4Jr_5z.js} +2 -2
- package/dist/assets/{Hooks-CzeYFQjq.js → Hooks-NhrvH3JP.js} +3 -3
- package/dist/assets/{Library-D2zPkwOC.js → Library-Dv_EtIMr.js} +1 -1
- package/dist/assets/{MarkdownEditor-DbinP78A.js → MarkdownEditor-DTKDWV_n.js} +1 -1
- package/dist/assets/{McpServers-Bu_sVGLr.js → McpServers-M034G9B3.js} +2 -2
- package/dist/assets/{Memory-BgiI6LZ_.js → Memory-DzF1B4gx.js} +6 -6
- package/dist/assets/{Permissions-C1MMzZRm.js → Permissions-DrgbSGWl.js} +3 -3
- package/dist/assets/{Plugins-Dhu6wAZi.js → Plugins-Bd3ef35w.js} +2 -2
- package/dist/assets/{ProvenanceBadge-C3AcWKMG.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
- package/dist/assets/{SaveBar-plwYm0iO.js → SaveBar-rimJem-Y.js} +1 -1
- package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
- package/dist/assets/{ScopeSwitcher-DFy0vv_o.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
- package/dist/assets/Settings-Dc1F_sSg.js +3 -0
- package/dist/assets/{SkillReferenceGraph-DsI7hRPB.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
- package/dist/assets/{Skills-3E748UfB.js → Skills-DRXd18gu.js} +2 -2
- package/dist/assets/{SystemPrompt-BqmfUJOd.js → SystemPrompt-ahy5-Fwe.js} +1 -1
- package/dist/assets/{TagLibrary-CPz6fO8Q.js → TagLibrary-C_tToDeL.js} +1 -1
- package/dist/assets/{TiptapBody-D9EoBQ1P.js → TiptapBody-DvdefS6K.js} +1 -1
- package/dist/assets/{Toggle-BHAmZNDn.js → Toggle-6Jl5Njc6.js} +1 -1
- package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
- package/dist/assets/{index-tn5JIVPj.js → index-vx8O73l8.js} +497 -502
- package/dist/assets/{settingsSchema-Crqy_aNz.js → settingsSchema-D6iRguNV.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +26 -29
- package/plugins/CLAUDE.md +6 -6
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +23 -19
- package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
- package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
- package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
- package/scripts/README.md +3 -11
- package/scripts/audit-ops-hygiene.cjs +3 -3
- package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
- package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
- package/scripts/mint-epic.cjs +2 -3
- package/scripts/scheduler-mcp-server.cjs +36 -7
- package/src/main/agentLibrary.cjs +22 -2
- package/src/main/build-info.json +4 -4
- package/src/main/config.cjs +41 -38
- package/src/main/docEdit.cjs +4 -1
- package/src/main/files.cjs +2 -5
- package/src/main/health.cjs +1 -1
- package/src/main/index.cjs +23 -10
- package/src/main/ipcSchemas.cjs +28 -35
- package/src/main/lib/agentPersonaSchema.cjs +13 -2
- package/src/main/lib/atomicFs.cjs +116 -0
- package/src/main/lib/branchSweep.cjs +13 -12
- package/src/main/lib/buildTarget.cjs +3 -3
- package/src/main/lib/claudeCliCaps.cjs +129 -0
- package/src/main/lib/credentials.cjs +2 -4
- package/src/main/lib/crossProjectFeedback.cjs +3 -3
- package/src/main/lib/cwdClassify.cjs +15 -3
- package/src/main/lib/definitionOfDone.cjs +207 -125
- package/src/main/lib/dodDrainHook.cjs +11 -5
- package/src/main/lib/epicMint.cjs +2 -4
- package/src/main/lib/epicStatusMirror.cjs +8 -10
- package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
- package/src/main/lib/gateAuthority.cjs +96 -0
- package/src/main/lib/gitExec.cjs +69 -0
- package/src/main/lib/gitWorktree.cjs +656 -92
- package/src/main/lib/instanceLock.cjs +5 -8
- package/src/main/lib/launchFailure.cjs +2 -3
- package/src/main/lib/macroLibrary.cjs +386 -0
- package/src/main/lib/mcpToolCatalog.cjs +26 -17
- package/src/main/lib/needsReviewLedger.cjs +1 -1
- package/src/main/lib/opsErrorLog.cjs +11 -1
- package/src/main/lib/opsOwnership.cjs +0 -5
- package/src/main/lib/pidAlive.cjs +32 -0
- package/src/main/lib/prdCreate.cjs +153 -19
- package/src/main/lib/prdDisposition.cjs +2 -2
- package/src/main/lib/prdGateFiles.cjs +284 -0
- package/src/main/lib/prdLocations.cjs +61 -0
- package/src/main/lib/prdMigration.cjs +2 -3
- package/src/main/lib/prdSizing.cjs +2 -1
- package/src/main/lib/promptSessionSchema.cjs +10 -2
- package/src/main/lib/rcaReport.cjs +4 -6
- package/src/main/lib/reaperHelpers.cjs +8 -1
- package/src/main/lib/reservationExpiry.cjs +6 -1
- package/src/main/lib/reviewNotice.cjs +263 -0
- package/src/main/lib/runClaudeP.cjs +4 -1
- package/src/main/lib/schedulerPaths.cjs +22 -2
- package/src/main/lib/sessionSlots.cjs +2 -4
- package/src/main/lib/shippedPersonaSeeds.cjs +179 -0
- package/src/main/lib/timeoutShim.cjs +123 -0
- package/src/main/lib/timeoutShimScript.cjs +275 -0
- package/src/main/lib/upgradeDrain.cjs +4 -10
- package/src/main/lib/watchdogHelpers.cjs +33 -30
- package/src/main/lib/workTypeLibrary.cjs +7 -1
- package/src/main/promptSessionEvents.cjs +37 -13
- package/src/main/queueOps.cjs +17 -6
- package/src/main/scheduler.cjs +975 -203
- package/src/main/seedAgentPersonas.cjs +278 -23
- package/src/main/supervisor.cjs +4 -1
- package/src/main/templates/PRD_AUTHORING.md +126 -406
- package/src/preload/__tests__/preload-surface.test.cjs +68 -0
- package/src/preload/api.d.ts +41 -94
- package/src/preload/index.cjs +14 -9
- package/src/seed/agents/architect.md +1 -0
- package/src/seed/agents/dev-lead.md +17 -18
- package/src/seed/agents/project-home-builder.md +1 -0
- package/src/seed/agents/validator.md +10 -4
- package/dist/assets/HostBilko-nESrTRIg.js +0 -1
- package/dist/assets/Scheduler-BVYTeh39.js +0 -16
- package/dist/assets/Settings-DIhGgdfs.js +0 -3
- package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
- package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
- package/src/main/__tests__/agentLibrary.test.cjs +0 -276
- package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
- package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
- package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
- package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
- package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
- package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
- package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
- package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
- package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
- package/src/main/__tests__/chat-queue.test.cjs +0 -97
- package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
- package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
- package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
- package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
- package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
- package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
- package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
- package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
- package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
- package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
- package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
- package/src/main/__tests__/docEdit.test.cjs +0 -244
- package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
- package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
- package/src/main/__tests__/dod-report.test.cjs +0 -304
- package/src/main/__tests__/dod-reverify.test.cjs +0 -285
- package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
- package/src/main/__tests__/epicMint.test.cjs +0 -332
- package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
- package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
- package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
- package/src/main/__tests__/exchanges.test.cjs +0 -122
- package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
- package/src/main/__tests__/extractJson.test.cjs +0 -51
- package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
- package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
- package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
- package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
- package/src/main/__tests__/health-credentials.test.cjs +0 -81
- package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
- package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
- package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
- package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
- package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
- package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
- package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
- package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
- package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
- package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
- package/src/main/__tests__/historyDashboard.test.cjs +0 -163
- package/src/main/__tests__/historyRollup.test.cjs +0 -333
- package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
- package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
- package/src/main/__tests__/kg-augment.test.cjs +0 -195
- package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
- package/src/main/__tests__/machineProfile.test.cjs +0 -152
- package/src/main/__tests__/mcpStatus.test.cjs +0 -61
- package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
- package/src/main/__tests__/memoryStale.test.cjs +0 -88
- package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
- package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
- package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
- package/src/main/__tests__/personaMerge.test.cjs +0 -169
- package/src/main/__tests__/planValidator.test.cjs +0 -125
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
- package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
- package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
- package/src/main/__tests__/prdAgentType.test.cjs +0 -103
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
- package/src/main/__tests__/prdCreate.test.cjs +0 -979
- package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
- package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
- package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
- package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
- package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
- package/src/main/__tests__/prdLocations.test.cjs +0 -195
- package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
- package/src/main/__tests__/prdMigration.test.cjs +0 -349
- package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
- package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
- package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
- package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
- package/src/main/__tests__/prdSizing.test.cjs +0 -106
- package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
- package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
- package/src/main/__tests__/proc-role-env.test.cjs +0 -125
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
- package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
- package/src/main/__tests__/projectPages.test.cjs +0 -137
- package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
- package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
- package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
- package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
- package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
- package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
- package/src/main/__tests__/pty-write-result.test.cjs +0 -46
- package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
- package/src/main/__tests__/queueHistory.test.cjs +0 -355
- package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
- package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
- package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -154
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
- package/src/main/__tests__/rcaReport.test.cjs +0 -266
- package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
- package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
- package/src/main/__tests__/runLogRetention.test.cjs +0 -489
- package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
- package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
- package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
- package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
- package/src/main/__tests__/runVerify.test.cjs +0 -1784
- package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
- package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
- package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
- package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
- package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
- package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
- package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
- package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
- package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
- package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
- package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
- package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
- package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
- package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
- package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
- package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
- package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
- package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
- package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
- package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
- package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
- package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
- package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
- package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
- package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
- package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
- package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
- package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
- package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
- package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
- package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
- package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
- package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
- package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
- package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
- package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
- package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
- package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
- package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
- package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
- package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
- package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
- package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
- package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
- package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
- package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
- package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
- package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
- package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
- package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
- package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
- package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
- package/src/main/__tests__/seedStatus.test.cjs +0 -100
- package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
- package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
- package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
- package/src/main/__tests__/telemetryContract.test.cjs +0 -930
- package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
- package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
- package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
- package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
- package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
- package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
- package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
- package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
- package/src/main/__tests__/validationSentinels.test.cjs +0 -84
- package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
- package/src/main/bilkoHost.cjs +0 -314
- package/src/main/bilkoHostCore.cjs +0 -89
- package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
- package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
- package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
- package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
- package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
- package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
- package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
- package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
- package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
- package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
- package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
- package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
- package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
- package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
- package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
- package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
- package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
- package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
- package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
- package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
- package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
- package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
- package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
- package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
- package/src/main/lib/__tests__/gateFixtures.json +0 -20
- package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
- package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
- package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
- package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
- package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
- package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
- package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
- package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
- package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
- package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
- package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
- package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
- package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
- package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
- package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
- package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
- package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
- package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
- package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
- package/src/main/lib/__tests__/procName.test.cjs +0 -92
- package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
- package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
- package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
- package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
- package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
- package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
- package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
- package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
- package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
- package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
- package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
- package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
- package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
- package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
- package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
- package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
- package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
- package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
- package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
- package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
- package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -375
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
- package/src/main/lib/kgExchangePairing.cjs +0 -75
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scheduler-prd-persona-spawn.test.cjs — PRD 1115: a PRD's `agentType`
|
|
3
|
-
* frontmatter must actually drive the headless spawn (--append-system-prompt
|
|
4
|
-
* with the persona's frontmatter-stripped, capped body, and --model with the
|
|
5
|
-
* persona's own model), not just sit in the prompt as prose the executor may
|
|
6
|
-
* or may not obey. Covers buildClaudeSpawnArgs' pure argv shape plus a real
|
|
7
|
-
* executeJob spawn (stub `claude` binary) proving the resolved persona
|
|
8
|
-
* actually reaches the child process, and that a dangling agentType still
|
|
9
|
-
* runs the job to completion (never parks or fails it).
|
|
10
|
-
*
|
|
11
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/scheduler-prd-persona-spawn.test.cjs
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
'use strict';
|
|
15
|
-
|
|
16
|
-
import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
17
|
-
const fs = require('node:fs');
|
|
18
|
-
const os = require('node:os');
|
|
19
|
-
const path = require('node:path');
|
|
20
|
-
const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
|
|
21
|
-
|
|
22
|
-
let tmpHome;
|
|
23
|
-
let originalHome;
|
|
24
|
-
let executeJob;
|
|
25
|
-
let buildClaudeSpawnArgs;
|
|
26
|
-
|
|
27
|
-
beforeAll(() => {
|
|
28
|
-
originalHome = process.env.HOME;
|
|
29
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-prd-persona-spawn-'));
|
|
30
|
-
process.env.HOME = tmpHome;
|
|
31
|
-
({ executeJob, buildClaudeSpawnArgs } = require('../scheduler.cjs'));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
afterAll(() => {
|
|
35
|
-
process.env.HOME = originalHome;
|
|
36
|
-
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
afterEach(() => {
|
|
40
|
-
delete process.env.SM_CLAUDE_BIN;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// ---------- buildClaudeSpawnArgs: systemPrompt -> --append-system-prompt ----------
|
|
44
|
-
|
|
45
|
-
test('buildClaudeSpawnArgs: a given systemPrompt is passed via --append-system-prompt', () => {
|
|
46
|
-
const args = buildClaudeSpawnArgs({ prompt: 'prd body', model: 'opus', sessionId: 'sid', resume: false, systemPrompt: 'You are dev-lead.' });
|
|
47
|
-
const idx = args.indexOf('--append-system-prompt');
|
|
48
|
-
expect(idx).toBeGreaterThanOrEqual(0);
|
|
49
|
-
expect(args[idx + 1]).toBe('You are dev-lead.');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('buildClaudeSpawnArgs: omits --append-system-prompt entirely when systemPrompt is absent', () => {
|
|
53
|
-
const args = buildClaudeSpawnArgs({ prompt: 'prd body', model: 'sonnet', sessionId: 'sid', resume: false });
|
|
54
|
-
expect(args).not.toContain('--append-system-prompt');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('buildClaudeSpawnArgs: model is never omitted even without a systemPrompt', () => {
|
|
58
|
-
const args = buildClaudeSpawnArgs({ prompt: 'x', model: 'sonnet', sessionId: 'sid', resume: false });
|
|
59
|
-
const idx = args.indexOf('--model');
|
|
60
|
-
expect(idx).toBeGreaterThanOrEqual(0);
|
|
61
|
-
expect(args[idx + 1]).toBe('sonnet');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// ---------- real spawn: persona actually reaches the child process ----------
|
|
65
|
-
|
|
66
|
-
// Stub `claude` binary: dumps its own argv into a marker file, then emits a
|
|
67
|
-
// stream-json success result and exits 0 — same technique as
|
|
68
|
-
// scheduler-bash-timeout-env.test.cjs's env-var proof, applied to argv.
|
|
69
|
-
function writeClaudeStub() {
|
|
70
|
-
return claudeStub.writeClaudeStub({ body: `
|
|
71
|
-
const fs = require('fs');
|
|
72
|
-
const path = require('path');
|
|
73
|
-
fs.writeFileSync(path.join(process.cwd(), 'argv.marker'), JSON.stringify(process.argv.slice(2)), 'utf8');
|
|
74
|
-
process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok' }) + '\\n');
|
|
75
|
-
process.exit(0);
|
|
76
|
-
` });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function writePersona(dir, name, frontmatterLines, bodyText) {
|
|
80
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
81
|
-
const lines = ['---', `name: ${name}`, ...frontmatterLines, '---', bodyText];
|
|
82
|
-
fs.writeFileSync(path.join(dir, `${name}.md`), lines.join('\n'));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function runJobWithAgentType({ agentType, overlay }) {
|
|
86
|
-
// Under tmpHome so config.validatePath's home-dir boundary admits the project overlay.
|
|
87
|
-
const mainCwd = fs.mkdtempSync(path.join(tmpHome, 'sm-prd-persona-main-'));
|
|
88
|
-
if (overlay) writePersona(path.join(mainCwd, '.claude', 'agents'), overlay.name, overlay.frontmatter, overlay.body || '');
|
|
89
|
-
fs.mkdirSync(path.join(tmpHome, '.claude'), { recursive: true });
|
|
90
|
-
const runDir = fs.mkdtempSync(path.join(tmpHome, '.claude', 'sm-prd-persona-run-'));
|
|
91
|
-
const slug = `1115-test-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
|
|
92
|
-
const prdsDir = path.join(mainCwd, 'session-manager-operations', 'scheduler', 'prds');
|
|
93
|
-
fs.mkdirSync(prdsDir, { recursive: true });
|
|
94
|
-
fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'Do the thing.', 'utf8');
|
|
95
|
-
process.env.SM_CLAUDE_BIN = writeClaudeStub();
|
|
96
|
-
|
|
97
|
-
const job = { slug, cwd: mainCwd, agentType };
|
|
98
|
-
const result = await executeJob(job, runDir, mainCwd, () => Promise.resolve());
|
|
99
|
-
const argv = JSON.parse(fs.readFileSync(path.join(mainCwd, 'argv.marker'), 'utf8'));
|
|
100
|
-
fs.rmSync(mainCwd, { recursive: true, force: true });
|
|
101
|
-
fs.rmSync(runDir, { recursive: true, force: true });
|
|
102
|
-
return { result, argv };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
test('executeJob: a resolvable agentType reaches the spawned child as --append-system-prompt + --model', async () => {
|
|
106
|
-
const globalAgentsDir = path.join(tmpHome, '.claude', 'agents');
|
|
107
|
-
writePersona(globalAgentsDir, 'dev-lead', ['model: opus'], 'You are dev-lead. Ship it.');
|
|
108
|
-
|
|
109
|
-
const { result, argv } = await runJobWithAgentType({ agentType: 'dev-lead' });
|
|
110
|
-
|
|
111
|
-
expect(result.exitCode).toBe(0);
|
|
112
|
-
const spIdx = argv.indexOf('--append-system-prompt');
|
|
113
|
-
expect(spIdx).toBeGreaterThanOrEqual(0);
|
|
114
|
-
expect(argv[spIdx + 1]).toBe('You are dev-lead. Ship it.');
|
|
115
|
-
const modelIdx = argv.indexOf('--model');
|
|
116
|
-
expect(argv[modelIdx + 1]).toBe('opus');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('executeJob: a dangling agentType (no resolvable persona file) still runs the job to completion, falling back to sonnet with no --append-system-prompt', async () => {
|
|
120
|
-
const { result, argv } = await runJobWithAgentType({ agentType: 'renamed-or-deleted-persona' });
|
|
121
|
-
|
|
122
|
-
expect(result.exitCode).toBe(0);
|
|
123
|
-
expect(argv).not.toContain('--append-system-prompt');
|
|
124
|
-
const modelIdx = argv.indexOf('--model');
|
|
125
|
-
expect(argv[modelIdx + 1]).toBe('sonnet');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('executeJob: a job with no agentType at all runs unaffected (no persona, sonnet fallback)', async () => {
|
|
129
|
-
const { result, argv } = await runJobWithAgentType({ agentType: null });
|
|
130
|
-
|
|
131
|
-
expect(result.exitCode).toBe(0);
|
|
132
|
-
expect(argv).not.toContain('--append-system-prompt');
|
|
133
|
-
const modelIdx = argv.indexOf('--model');
|
|
134
|
-
expect(argv[modelIdx + 1]).toBe('sonnet');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// ---------- persona effort → --effort ----------
|
|
138
|
-
|
|
139
|
-
test('buildClaudeSpawnArgs: effort appends --effort <level>; null/inherit/absent add no token', () => {
|
|
140
|
-
const base = { prompt: 'x', model: 'sonnet', sessionId: 'sid', resume: false };
|
|
141
|
-
const args = buildClaudeSpawnArgs({ ...base, effort: 'high' });
|
|
142
|
-
expect(args[args.indexOf('--effort') + 1]).toBe('high');
|
|
143
|
-
expect(args.indexOf('--model')).toBeGreaterThanOrEqual(0);
|
|
144
|
-
for (const effort of [null, 'inherit', undefined]) {
|
|
145
|
-
expect(buildClaudeSpawnArgs({ ...base, effort })).not.toContain('--effort');
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('executeJob: persona effort reaches the child as --effort; inherit yields no --effort token', async () => {
|
|
150
|
-
const globalAgentsDir = path.join(tmpHome, '.claude', 'agents');
|
|
151
|
-
writePersona(globalAgentsDir, 'effort-high', ['model: opus', 'effort: high'], 'body');
|
|
152
|
-
writePersona(globalAgentsDir, 'effort-inherit', ['model: opus', 'effort: inherit'], 'body');
|
|
153
|
-
|
|
154
|
-
const high = await runJobWithAgentType({ agentType: 'effort-high' });
|
|
155
|
-
expect(high.argv[high.argv.indexOf('--effort') + 1]).toBe('high');
|
|
156
|
-
expect(high.argv[high.argv.indexOf('--model') + 1]).toBe('opus');
|
|
157
|
-
|
|
158
|
-
const inherit = await runJobWithAgentType({ agentType: 'effort-inherit' });
|
|
159
|
-
expect(inherit.argv).not.toContain('--effort');
|
|
160
|
-
expect(inherit.argv).not.toContain('inherit');
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// ---------- project overlay MERGE: frontmatter-only overlay patches the runtime fields ----------
|
|
164
|
-
|
|
165
|
-
test('executeJob: a frontmatter-only project overlay changes --model/--effort while the global persona body still reaches --append-system-prompt', async () => {
|
|
166
|
-
const globalAgentsDir = path.join(tmpHome, '.claude', 'agents');
|
|
167
|
-
writePersona(globalAgentsDir, 'dev-lead', ['model: sonnet'], 'You are dev-lead. Global body.');
|
|
168
|
-
|
|
169
|
-
const { result, argv } = await runJobWithAgentType({
|
|
170
|
-
agentType: 'dev-lead',
|
|
171
|
-
overlay: { name: 'dev-lead', frontmatter: ['model: claude-opus-4-6', 'effort: high'], body: '' },
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
expect(result.exitCode).toBe(0);
|
|
175
|
-
expect(argv[argv.indexOf('--model') + 1]).toBe('claude-opus-4-6');
|
|
176
|
-
expect(argv[argv.indexOf('--effort') + 1]).toBe('high');
|
|
177
|
-
expect(argv[argv.indexOf('--append-system-prompt') + 1]).toBe('You are dev-lead. Global body.');
|
|
178
|
-
});
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scheduler-quarantine-autoresolve.test.cjs
|
|
3
|
-
*
|
|
4
|
-
* Covers the bounded automatic exit for a quarantined row that never
|
|
5
|
-
* received a `createdVia` provenance stamp (this PRD): the pure selector
|
|
6
|
-
* `selectQuarantineAutoResolveTargets`, the async applier
|
|
7
|
-
* `autoResolveQuarantine` (which re-reads the PRD file's frontmatter fresh
|
|
8
|
-
* from disk immediately before transitioning, so it can never race
|
|
9
|
-
* reconcile()'s own adopt path), and the kill switch
|
|
10
|
-
* `quarantineAutoResolveDisabled`.
|
|
11
|
-
*
|
|
12
|
-
* HOME is overridden to a tmp dir BEFORE requiring scheduler.cjs — same
|
|
13
|
-
* reason as scheduler-failed-autoreset.test.cjs (appendAuditEvent writes
|
|
14
|
-
* under $HOME/.claude/session-manager/audit-log.jsonl).
|
|
15
|
-
*
|
|
16
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
'use strict';
|
|
20
|
-
|
|
21
|
-
const assert = require('node:assert/strict');
|
|
22
|
-
const fs = require('node:fs');
|
|
23
|
-
const os = require('node:os');
|
|
24
|
-
const path = require('node:path');
|
|
25
|
-
|
|
26
|
-
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'quarantine-autoresolve-test-'));
|
|
27
|
-
process.env.HOME = tmpHome;
|
|
28
|
-
|
|
29
|
-
const {
|
|
30
|
-
selectQuarantineAutoResolveTargets,
|
|
31
|
-
autoResolveQuarantine,
|
|
32
|
-
QUARANTINE_RESOLVE_CAP,
|
|
33
|
-
quarantineAutoResolveDisabled,
|
|
34
|
-
} = require('../scheduler.cjs');
|
|
35
|
-
|
|
36
|
-
const MIN_MS = 60_000;
|
|
37
|
-
const THRESHOLD_MS = 60 * MIN_MS;
|
|
38
|
-
|
|
39
|
-
function quarantinedJob(overrides = {}) {
|
|
40
|
-
return {
|
|
41
|
-
slug: 'quarantined-row',
|
|
42
|
-
cwd: '/home/user/project',
|
|
43
|
-
status: 'quarantined',
|
|
44
|
-
statusHistory: [{ to: 'quarantined', at: new Date(Date.now() - 90 * MIN_MS).toISOString() }],
|
|
45
|
-
...overrides,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function registerActiveProject(cwd) {
|
|
50
|
-
const projectsDir = path.join(tmpHome, '.claude', 'projects');
|
|
51
|
-
const slugDir = path.join(projectsDir, `fake-project-slug-${path.basename(cwd)}`);
|
|
52
|
-
fs.mkdirSync(slugDir, { recursive: true });
|
|
53
|
-
fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function makeFixtureProject(prefix) {
|
|
57
|
-
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
58
|
-
registerActiveProject(cwd);
|
|
59
|
-
const prdsDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', 'test-epic-1', 'prds');
|
|
60
|
-
fs.mkdirSync(prdsDir, { recursive: true });
|
|
61
|
-
return { cwd, prdsDir };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// --- selectQuarantineAutoResolveTargets ---
|
|
65
|
-
|
|
66
|
-
test('a fresh quarantined row (under threshold) is not selected', () => {
|
|
67
|
-
const job = quarantinedJob({
|
|
68
|
-
statusHistory: [{ to: 'quarantined', at: new Date(Date.now() - 2 * MIN_MS).toISOString() }],
|
|
69
|
-
});
|
|
70
|
-
assert.equal(selectQuarantineAutoResolveTargets([job], Date.now(), THRESHOLD_MS).length, 0);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('a non-quarantined status is never selected', () => {
|
|
74
|
-
const job = quarantinedJob({ status: 'failed' });
|
|
75
|
-
assert.equal(selectQuarantineAutoResolveTargets([job], Date.now(), THRESHOLD_MS).length, 0);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('a quarantined row with no statusHistory entry cannot have its age proven, and is skipped', () => {
|
|
79
|
-
const job = quarantinedJob({ statusHistory: [] });
|
|
80
|
-
assert.equal(selectQuarantineAutoResolveTargets([job], Date.now(), THRESHOLD_MS).length, 0);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('a quarantined row past threshold, under the cap, is selected', () => {
|
|
84
|
-
const job = quarantinedJob({ quarantineResolveAttempts: 0 });
|
|
85
|
-
const found = selectQuarantineAutoResolveTargets([job], Date.now(), THRESHOLD_MS);
|
|
86
|
-
assert.equal(found.length, 1);
|
|
87
|
-
assert.equal(found[0].slug, 'quarantined-row');
|
|
88
|
-
assert.ok(found[0].ageMs >= 90 * MIN_MS - 1000);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('a quarantined row that already spent its resolve cap is excluded', () => {
|
|
92
|
-
const job = quarantinedJob({ quarantineResolveAttempts: QUARANTINE_RESOLVE_CAP });
|
|
93
|
-
assert.equal(selectQuarantineAutoResolveTargets([job], Date.now(), THRESHOLD_MS).length, 0);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('quarantineAutoResolveDisabled reflects SM_QUARANTINE_AUTORESOLVE_DISABLE', () => {
|
|
97
|
-
const saved = process.env.SM_QUARANTINE_AUTORESOLVE_DISABLE;
|
|
98
|
-
try {
|
|
99
|
-
delete process.env.SM_QUARANTINE_AUTORESOLVE_DISABLE;
|
|
100
|
-
assert.equal(quarantineAutoResolveDisabled(), false);
|
|
101
|
-
process.env.SM_QUARANTINE_AUTORESOLVE_DISABLE = '1';
|
|
102
|
-
assert.equal(quarantineAutoResolveDisabled(), true);
|
|
103
|
-
} finally {
|
|
104
|
-
if (saved === undefined) delete process.env.SM_QUARANTINE_AUTORESOLVE_DISABLE;
|
|
105
|
-
else process.env.SM_QUARANTINE_AUTORESOLVE_DISABLE = saved;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// --- autoResolveQuarantine: the applier ---
|
|
110
|
-
|
|
111
|
-
test('a quarantined row whose PRD file still has no createdVia is auto-resolved to skipped', async () => {
|
|
112
|
-
const { cwd, prdsDir } = makeFixtureProject('sm-quarantine-autoresolve-noprovenance-');
|
|
113
|
-
try {
|
|
114
|
-
fs.writeFileSync(
|
|
115
|
-
path.join(prdsDir, 'quarantined-row.md'),
|
|
116
|
-
`---\ntitle: Unstamped PRD\ncwd: ${cwd}\nestimateMinutes: 15\n---\n\n# Goal\nDo the thing.\n`,
|
|
117
|
-
'utf8',
|
|
118
|
-
);
|
|
119
|
-
const job = quarantinedJob({ cwd, quarantineResolveAttempts: 0 });
|
|
120
|
-
const outcome = await autoResolveQuarantine(job, 90 * MIN_MS);
|
|
121
|
-
assert.equal(outcome, 'skipped');
|
|
122
|
-
assert.equal(job.status, 'skipped');
|
|
123
|
-
assert.equal(job.quarantineResolveAttempts, 1);
|
|
124
|
-
assert.ok(typeof job.error === 'string' && /createdVia/.test(job.error));
|
|
125
|
-
} finally {
|
|
126
|
-
fs.rmSync(cwd, { recursive: true, force: true });
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('a quarantined row whose PRD file HAS been stamped with createdVia is left alone — reconcile owns it now', async () => {
|
|
131
|
-
const { cwd, prdsDir } = makeFixtureProject('sm-quarantine-autoresolve-stamped-');
|
|
132
|
-
try {
|
|
133
|
-
fs.writeFileSync(
|
|
134
|
-
path.join(prdsDir, 'quarantined-row.md'),
|
|
135
|
-
`---\ntitle: Stamped PRD\ncwd: ${cwd}\nestimateMinutes: 15\ncreatedVia: scheduler-api\nissuedAt: 2026-08-07T00:00:00.000Z\n---\n\n# Goal\nDo the thing.\n`,
|
|
136
|
-
'utf8',
|
|
137
|
-
);
|
|
138
|
-
const job = quarantinedJob({ cwd, quarantineResolveAttempts: 0 });
|
|
139
|
-
const outcome = await autoResolveQuarantine(job, 90 * MIN_MS);
|
|
140
|
-
assert.equal(outcome, null);
|
|
141
|
-
assert.equal(job.status, 'quarantined'); // untouched — the adopt path owns the next pass
|
|
142
|
-
assert.equal(job.quarantineResolveAttempts, 0);
|
|
143
|
-
} finally {
|
|
144
|
-
fs.rmSync(cwd, { recursive: true, force: true });
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('autoResolveQuarantine race-guards against a row that moved off quarantined', async () => {
|
|
149
|
-
const job = quarantinedJob({ status: 'pending', quarantineResolveAttempts: 0 });
|
|
150
|
-
assert.equal(await autoResolveQuarantine(job, 90 * MIN_MS), null);
|
|
151
|
-
assert.equal(job.status, 'pending'); // untouched
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('autoResolveQuarantine race-guards against a row whose resolve cap is already spent', async () => {
|
|
155
|
-
const job = quarantinedJob({ quarantineResolveAttempts: QUARANTINE_RESOLVE_CAP });
|
|
156
|
-
assert.equal(await autoResolveQuarantine(job, 90 * MIN_MS), null);
|
|
157
|
-
assert.equal(job.status, 'quarantined'); // untouched
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test('a quarantined row whose PRD file is missing entirely is treated as still lacking provenance and auto-resolved', async () => {
|
|
161
|
-
const job = quarantinedJob({ cwd: '/nonexistent/project/path', quarantineResolveAttempts: 0 });
|
|
162
|
-
const outcome = await autoResolveQuarantine(job, 90 * MIN_MS);
|
|
163
|
-
assert.equal(outcome, 'skipped');
|
|
164
|
-
assert.equal(job.status, 'skipped');
|
|
165
|
-
});
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scheduler-quiet-machine-lease.test.cjs — PRD 1107: the exclusive
|
|
3
|
-
* quiet-machine lease must be released on EVERY spawnJob exit path,
|
|
4
|
-
* including a killed job (SIGKILL/exit 137), not just a clean exit — the
|
|
5
|
-
* same finally block that already releases the sessionSlots token. Drives
|
|
6
|
-
* spawnJob end-to-end against a real git fixture repo with a stub `claude`
|
|
7
|
-
* binary that exits 137 mid-run (same pattern as
|
|
8
|
-
* scheduler-inplace-salvage.test.cjs), then asserts:
|
|
9
|
-
* 1. the lease is released once the job settles, and
|
|
10
|
-
* 2. an ordinary pending job — held behind the lease while it was held —
|
|
11
|
-
* is now eligible to dispatch.
|
|
12
|
-
*
|
|
13
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/scheduler-quiet-machine-lease.test.cjs
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
'use strict';
|
|
17
|
-
|
|
18
|
-
import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
19
|
-
const fs = require('node:fs');
|
|
20
|
-
const os = require('node:os');
|
|
21
|
-
const path = require('node:path');
|
|
22
|
-
const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
|
|
23
|
-
const { execFileSync } = require('node:child_process');
|
|
24
|
-
|
|
25
|
-
let tmpHome;
|
|
26
|
-
let originalHome;
|
|
27
|
-
let originalDisable;
|
|
28
|
-
let spawnJob;
|
|
29
|
-
let quietMachineLease;
|
|
30
|
-
let pickNextBatch;
|
|
31
|
-
|
|
32
|
-
function git(args, cwd) {
|
|
33
|
-
return execFileSync('git', args, { cwd, encoding: 'utf8' });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function initRepo(dir) {
|
|
37
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
38
|
-
git(['init', '-q'], dir);
|
|
39
|
-
git(['config', 'user.email', 'test@example.com'], dir);
|
|
40
|
-
git(['config', 'user.name', 'Test'], dir);
|
|
41
|
-
fs.writeFileSync(path.join(dir, 'README.md'), 'hello\n', 'utf8');
|
|
42
|
-
git(['add', '-A'], dir);
|
|
43
|
-
git(['commit', '-q', '-m', 'initial'], dir);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// See scheduler-inplace-salvage.test.cjs's identical helper: readQueue()'s
|
|
47
|
-
// per-project discovery walks ~/.claude/projects/*/*.jsonl transcripts for a
|
|
48
|
-
// `cwd` field to find known project queue.json shards.
|
|
49
|
-
function registerActiveProject(cwd) {
|
|
50
|
-
const projectsDir = path.join(tmpHome, '.claude', 'projects');
|
|
51
|
-
const slugDir = path.join(projectsDir, `fake-project-slug-${path.basename(cwd)}`);
|
|
52
|
-
fs.mkdirSync(slugDir, { recursive: true });
|
|
53
|
-
fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Every real scheduler row is born from a real PRD .md file — reconcile()'s
|
|
57
|
-
// auto-archive-drop path (a terminal job whose slug has no on-disk PRD
|
|
58
|
-
// anywhere is treated as "archived on purpose": dropped from queue.json and
|
|
59
|
-
// backfilled to history.jsonl, see scheduler-reconcile-history-backfill.
|
|
60
|
-
// test.cjs) fires the moment a completion path reconciles with
|
|
61
|
-
// broadcast({flush:true}). Without a matching PRD source, a row this test
|
|
62
|
-
// drives to a terminal status vanishes from queue.json before the assertions
|
|
63
|
-
// below ever read it back. Epic-scoped (not the legacy flat prds/ dir) so
|
|
64
|
-
// it's never swept by consolidateFlatPrds either.
|
|
65
|
-
// Some tests author their own real PRD (e.g. with `quietMachine: true`
|
|
66
|
-
// frontmatter) before calling writeProjectQueue — never shadow that with a
|
|
67
|
-
// blank fixture, which would silently overwrite its parsed frontmatter on
|
|
68
|
-
// the next reconcile() refresh (onDisk.set(slug, ...) keeps whichever file a
|
|
69
|
-
// later scan pass parses).
|
|
70
|
-
function prdAlreadyExists(cwd, slug) {
|
|
71
|
-
const flatPath = path.join(cwd, 'session-manager-operations', 'scheduler', 'prds', `${slug}.md`);
|
|
72
|
-
if (fs.existsSync(flatPath)) return true;
|
|
73
|
-
const epicsRoot = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics');
|
|
74
|
-
if (!fs.existsSync(epicsRoot)) return false;
|
|
75
|
-
return fs.readdirSync(epicsRoot).some((epicId) => fs.existsSync(path.join(epicsRoot, epicId, 'prds', `${slug}.md`)));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function writeFixturePrd(cwd, slug) {
|
|
79
|
-
const prdsDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
|
|
80
|
-
fs.mkdirSync(prdsDir, { recursive: true });
|
|
81
|
-
fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'Test fixture PRD.', 'utf8');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function writeProjectQueue(cwd, jobs) {
|
|
85
|
-
const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
|
|
86
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
87
|
-
fs.writeFileSync(path.join(stateDir, 'queue.json'), JSON.stringify({ jobs }, null, 2));
|
|
88
|
-
for (const job of jobs) {
|
|
89
|
-
if (job && job.slug && !prdAlreadyExists(job.cwd || cwd, job.slug)) writeFixturePrd(job.cwd || cwd, job.slug);
|
|
90
|
-
}
|
|
91
|
-
return path.join(stateDir, 'queue.json');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Stub `claude` binary that exits 137 (SIGKILL) with no result:success event,
|
|
95
|
-
// simulating a leased job killed mid-run before it could finish and release
|
|
96
|
-
// naturally through its own clean-exit path. Dirties the tree first (like
|
|
97
|
-
// scheduler-inplace-salvage.test.cjs's stub) so the scheduler classifies this
|
|
98
|
-
// as a genuine kill-with-partial-work, not an auto-retryable transient blip
|
|
99
|
-
// with no evidence of real execution.
|
|
100
|
-
function writeKilledClaudeStub() {
|
|
101
|
-
return claudeStub.writeClaudeStub({ body: `
|
|
102
|
-
const fs = require('fs');
|
|
103
|
-
const path = require('path');
|
|
104
|
-
fs.writeFileSync(path.join(process.cwd(), 'job-output.txt'), 'work in progress when killed\\n', 'utf8');
|
|
105
|
-
process.exit(137);
|
|
106
|
-
` });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let reapDeadRunningJobs;
|
|
110
|
-
|
|
111
|
-
beforeAll(() => {
|
|
112
|
-
originalHome = process.env.HOME;
|
|
113
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-quiet-lease-'));
|
|
114
|
-
process.env.HOME = tmpHome;
|
|
115
|
-
originalDisable = process.env.SM_JOB_WORKTREE_DISABLE;
|
|
116
|
-
process.env.SM_JOB_WORKTREE_DISABLE = '1';
|
|
117
|
-
({ spawnJob, reapDeadRunningJobs } = require('../scheduler.cjs'));
|
|
118
|
-
({ pickNextBatch } = require('../lib/schedulerBatch.cjs'));
|
|
119
|
-
quietMachineLease = require('../lib/quietMachineLease.cjs');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
afterAll(() => {
|
|
123
|
-
process.env.HOME = originalHome;
|
|
124
|
-
if (originalDisable === undefined) delete process.env.SM_JOB_WORKTREE_DISABLE;
|
|
125
|
-
else process.env.SM_JOB_WORKTREE_DISABLE = originalDisable;
|
|
126
|
-
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
afterEach(() => {
|
|
130
|
-
delete process.env.SM_CLAUDE_BIN;
|
|
131
|
-
quietMachineLease.__resetForTests();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('a killed quietMachine job releases the exclusive lease, and an ordinary job dispatches on the next pick', async () => {
|
|
135
|
-
const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-quiet-lease-project-'));
|
|
136
|
-
initRepo(projectCwd);
|
|
137
|
-
registerActiveProject(projectCwd);
|
|
138
|
-
|
|
139
|
-
const quietSlug = `1107-test-quiet-killed-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
|
|
140
|
-
const ordinarySlug = `1107-test-ordinary-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
|
|
141
|
-
// Epic-scoped, not the legacy flat prds/ dir: the flat dir is retired and
|
|
142
|
-
// swept into prds-archived/ by consolidateFlatPrds the moment a job is no
|
|
143
|
-
// longer pending/running, which would silently strip this PRD's
|
|
144
|
-
// `quietMachine: true` frontmatter from the row on the very next
|
|
145
|
-
// reconcile() refresh (or drop the row entirely once terminal — see
|
|
146
|
-
// scheduler-reconcile-history-backfill.test.cjs).
|
|
147
|
-
const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
|
|
148
|
-
fs.mkdirSync(prdsDir, { recursive: true });
|
|
149
|
-
fs.writeFileSync(path.join(prdsDir, `${quietSlug}.md`), '---\nquietMachine: true\n---\nMeasure timing without contention, then get killed.', 'utf8');
|
|
150
|
-
fs.writeFileSync(path.join(prdsDir, `${ordinarySlug}.md`), 'An ordinary PRD queued behind the quiet job.', 'utf8');
|
|
151
|
-
|
|
152
|
-
const queuePath = writeProjectQueue(projectCwd, [
|
|
153
|
-
{ slug: quietSlug, status: 'pending', cwd: projectCwd, quietMachine: true },
|
|
154
|
-
{ slug: ordinarySlug, status: 'pending', cwd: projectCwd, createdAt: new Date().toISOString() },
|
|
155
|
-
]);
|
|
156
|
-
|
|
157
|
-
process.env.SM_CLAUDE_BIN = writeKilledClaudeStub();
|
|
158
|
-
|
|
159
|
-
const runId = `run-${quietSlug}`;
|
|
160
|
-
const runDir = path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', runId);
|
|
161
|
-
fs.mkdirSync(runDir, { recursive: true });
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
// Before spawnJob ever runs, demonstrate that a held lease (as it would
|
|
165
|
-
// be while quietSlug is genuinely running) blocks EVERY dispatch,
|
|
166
|
-
// including the ordinary sibling job sitting right behind it in the same
|
|
167
|
-
// project. Uses a standalone dummy holder so it doesn't interact with
|
|
168
|
-
// spawnJob's own acquire/release of the real lease below.
|
|
169
|
-
expect(quietMachineLease.acquire('dummy-holder-simulating-in-flight-run')).toBe(true);
|
|
170
|
-
const heldPick = pickNextBatch(
|
|
171
|
-
[
|
|
172
|
-
{ slug: quietSlug, status: 'running', cwd: projectCwd, quietMachine: true },
|
|
173
|
-
{ slug: ordinarySlug, status: 'pending', cwd: projectCwd, createdAt: new Date().toISOString() },
|
|
174
|
-
],
|
|
175
|
-
new Set([quietSlug]),
|
|
176
|
-
5,
|
|
177
|
-
{ leaseHeld: quietMachineLease.isHeld(), machineInUse: 1, now: Date.now() },
|
|
178
|
-
);
|
|
179
|
-
expect(heldPick.batch).toEqual([]);
|
|
180
|
-
quietMachineLease.release('dummy-holder-simulating-in-flight-run');
|
|
181
|
-
|
|
182
|
-
// Now the real thing: spawnJob acquires the lease itself for quietSlug,
|
|
183
|
-
// runs the killed stub, and must release it on this exit path.
|
|
184
|
-
await spawnJob({ slug: quietSlug, cwd: projectCwd, quietMachine: true }, runId, runDir, projectCwd);
|
|
185
|
-
|
|
186
|
-
const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
|
|
187
|
-
const quietRow = jobs.find((j) => j.slug === quietSlug);
|
|
188
|
-
expect(quietRow).toBeTruthy();
|
|
189
|
-
expect(quietRow.exitCode).toBe(137);
|
|
190
|
-
expect(quietRow.quietMachine).toBe(true);
|
|
191
|
-
|
|
192
|
-
// The lease was released on this killed-job exit path — spawnJob's own
|
|
193
|
-
// finally block, the same one that releases the sessionSlots token.
|
|
194
|
-
expect(quietMachineLease.isHeld()).toBe(false);
|
|
195
|
-
|
|
196
|
-
// With the lease free again, the ordinary sibling job is now eligible.
|
|
197
|
-
const nextPick = pickNextBatch(
|
|
198
|
-
[
|
|
199
|
-
{ slug: quietSlug, status: 'failed', cwd: projectCwd, quietMachine: true },
|
|
200
|
-
{ slug: ordinarySlug, status: 'pending', cwd: projectCwd, createdAt: new Date().toISOString() },
|
|
201
|
-
],
|
|
202
|
-
new Set(),
|
|
203
|
-
5,
|
|
204
|
-
{ leaseHeld: quietMachineLease.isHeld(), machineInUse: 0, now: Date.now() },
|
|
205
|
-
);
|
|
206
|
-
expect(nextPick.batch.map((j) => j.slug)).toEqual([ordinarySlug]);
|
|
207
|
-
} finally {
|
|
208
|
-
fs.rmSync(projectCwd, { recursive: true, force: true });
|
|
209
|
-
fs.rmSync(runDir, { recursive: true, force: true });
|
|
210
|
-
}
|
|
211
|
-
}, 30_000);
|
|
212
|
-
|
|
213
|
-
test('reapDeadRunningJobs releases a quietMachine job\'s lease too — the process vanished without spawnJob\'s own finally ever running', async () => {
|
|
214
|
-
// This reaper exists exactly for the case spawnJob's own finally never
|
|
215
|
-
// runs (the process dies without the completion mutate() finishing) — see
|
|
216
|
-
// reapDeadRunningJobs's header comment. A quietMachine row reaped through
|
|
217
|
-
// THIS path must release the lease itself, or it stays wedged forever.
|
|
218
|
-
const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-quiet-lease-reap-project-'));
|
|
219
|
-
registerActiveProject(projectCwd);
|
|
220
|
-
|
|
221
|
-
const slug = `1107-test-reaped-quiet-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
|
|
222
|
-
const queuePath = writeProjectQueue(projectCwd, [
|
|
223
|
-
{
|
|
224
|
-
slug,
|
|
225
|
-
status: 'running',
|
|
226
|
-
cwd: projectCwd,
|
|
227
|
-
quietMachine: true,
|
|
228
|
-
runId: `run-${slug}`,
|
|
229
|
-
runtime: { pid: 999999 }, // guaranteed-dead pid (see reaperHelpers tests)
|
|
230
|
-
},
|
|
231
|
-
]);
|
|
232
|
-
const runDir = path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', `run-${slug}`);
|
|
233
|
-
fs.mkdirSync(runDir, { recursive: true });
|
|
234
|
-
fs.writeFileSync(
|
|
235
|
-
path.join(runDir, `${slug}.log`),
|
|
236
|
-
'{"type":"result","subtype":"success","result":"done","is_error":false}\n',
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
// Simulate the lease as held by this row, exactly as it would be if
|
|
240
|
-
// spawnJob's own acquire ran but its finally never got the chance to.
|
|
241
|
-
expect(quietMachineLease.acquire(slug)).toBe(true);
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
await reapDeadRunningJobs();
|
|
245
|
-
|
|
246
|
-
const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
|
|
247
|
-
const row = jobs.find((j) => j.slug === slug);
|
|
248
|
-
expect(row).toBeTruthy();
|
|
249
|
-
expect(row.status).toBe('completed');
|
|
250
|
-
|
|
251
|
-
// The lease must be released here too, not just in spawnJob's finally.
|
|
252
|
-
expect(quietMachineLease.isHeld()).toBe(false);
|
|
253
|
-
} finally {
|
|
254
|
-
fs.rmSync(projectCwd, { recursive: true, force: true });
|
|
255
|
-
fs.rmSync(runDir, { recursive: true, force: true });
|
|
256
|
-
}
|
|
257
|
-
});
|