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,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scheduler-rate-limit-cooldown-freshness.test.cjs — PRD 1119.
|
|
3
|
-
*
|
|
4
|
-
* setPaused()'s manual-override cooldown used to suppress WRITING any pause
|
|
5
|
-
* for 5 minutes after a human Resume/Run now, unconditionally — even when
|
|
6
|
-
* the triggering rate-limit observation came from a run that started AFTER
|
|
7
|
-
* the manual clear (i.e. brand-new evidence, not the stale auto-detection
|
|
8
|
-
* the cooldown exists to ignore). Combined with the halt-reset that resets a
|
|
9
|
-
* rate-limited job straight back to 'pending', this produced a hot dispatch
|
|
10
|
-
* loop: dispatch -> 429 in seconds -> reset to pending -> pause suppressed
|
|
11
|
-
* -> dispatch again, on a ~12s cadence, until the cooldown itself expired.
|
|
12
|
-
*
|
|
13
|
-
* isCooldownSuppressed() is the pure predicate this PRD extracts so both
|
|
14
|
-
* directions of the fix (fresh observation bypasses the cooldown; stale one
|
|
15
|
-
* stays suppressed) are directly unit-testable without touching queue.json,
|
|
16
|
-
* mutate(), or any fs state. nextRapidRateLimitCount() is the pure reducer
|
|
17
|
-
* behind the independent rapid-repeat hard-pause cap (a second circuit
|
|
18
|
-
* breaker for when the computed resumeAt is itself stale/wrong and would
|
|
19
|
-
* otherwise let the resume timer keep re-clearing the pause every ~30s,
|
|
20
|
-
* with every subsequent dispatch genuinely "fresh").
|
|
21
|
-
*
|
|
22
|
-
* Run: timeout 60 npx vitest run src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
'use strict';
|
|
26
|
-
|
|
27
|
-
import { test, expect } from 'vitest';
|
|
28
|
-
const {
|
|
29
|
-
isCooldownSuppressed,
|
|
30
|
-
nextRapidRateLimitCount,
|
|
31
|
-
CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD,
|
|
32
|
-
RAPID_RATE_LIMIT_WINDOW_MS,
|
|
33
|
-
MANUAL_PAUSE_COOLDOWN_MS,
|
|
34
|
-
} = require('../scheduler.cjs');
|
|
35
|
-
|
|
36
|
-
const clearedAt = 1_000_000;
|
|
37
|
-
|
|
38
|
-
test('a rate_limit pause triggered by a run that STARTED AFTER the manual clear engages regardless of the cooldown', () => {
|
|
39
|
-
const now = clearedAt + 60_000; // well inside the 5-minute cooldown window
|
|
40
|
-
const observedAt = clearedAt + 5_000; // the triggering run started after the clear
|
|
41
|
-
const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt, force: false });
|
|
42
|
-
expect(suppressed).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('a rate_limit pause triggered by an observation OLDER than the manual clear stays suppressed', () => {
|
|
46
|
-
const now = clearedAt + 60_000;
|
|
47
|
-
const observedAt = clearedAt - 5_000; // stale: this run started before the human cleared the pause
|
|
48
|
-
const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt, force: false });
|
|
49
|
-
expect(suppressed).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('no observedAt at all (unrelated pause reasons, e.g. auth/network) is treated as stale — unchanged prior behavior', () => {
|
|
53
|
-
const now = clearedAt + 60_000;
|
|
54
|
-
const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt: null, force: false });
|
|
55
|
-
expect(suppressed).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('once the cooldown window itself has elapsed, suppression lifts regardless of freshness', () => {
|
|
59
|
-
const now = clearedAt + MANUAL_PAUSE_COOLDOWN_MS + 1;
|
|
60
|
-
const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt: null, force: false });
|
|
61
|
-
expect(suppressed).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('no manual clear on record never suppresses', () => {
|
|
65
|
-
const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: null, now: Date.now(), observedAt: null, force: false });
|
|
66
|
-
expect(suppressed).toBe(false);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('force always bypasses the cooldown, fresh or not', () => {
|
|
70
|
-
const now = clearedAt + 1_000;
|
|
71
|
-
const staleObservedAt = clearedAt - 5_000;
|
|
72
|
-
const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt: staleObservedAt, force: true });
|
|
73
|
-
expect(suppressed).toBe(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// ---------- rapid-repeat consecutive counter ----------
|
|
77
|
-
|
|
78
|
-
test('a rate-limited run under the rapid window increments the counter', () => {
|
|
79
|
-
expect(nextRapidRateLimitCount(0, { rateLimited: true, durationMs: 4_000 })).toBe(1);
|
|
80
|
-
expect(nextRapidRateLimitCount(1, { rateLimited: true, durationMs: 4_000 })).toBe(2);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('a rate-limited run that ran for >= the rapid window does not increment the counter', () => {
|
|
84
|
-
expect(nextRapidRateLimitCount(1, { rateLimited: true, durationMs: RAPID_RATE_LIMIT_WINDOW_MS })).toBe(1);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('any non-rate-limited outcome resets the counter to 0', () => {
|
|
88
|
-
expect(nextRapidRateLimitCount(2, { rateLimited: false, durationMs: 999_999 })).toBe(0);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('driving the counter to the documented threshold engages the hard-pause condition', () => {
|
|
92
|
-
let count = 0;
|
|
93
|
-
for (let i = 0; i < CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD; i += 1) {
|
|
94
|
-
count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
|
|
95
|
-
}
|
|
96
|
-
expect(count).toBe(CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD);
|
|
97
|
-
expect(count >= CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('reaching the threshold forces the pause through even against a STALE observation still inside the cooldown window', () => {
|
|
101
|
-
// This is the scenario the hard cap exists for: freshness alone is not
|
|
102
|
-
// enough when the computed resumeAt is itself stale/wrong and the resume
|
|
103
|
-
// timer keeps re-clearing the pause, so a LATER dispatch's observation can
|
|
104
|
-
// legitimately look "stale" relative to the most recent clear. The
|
|
105
|
-
// rapid-repeat counter is an independent, unconditional circuit breaker.
|
|
106
|
-
let count = 0;
|
|
107
|
-
for (let i = 0; i < CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD; i += 1) {
|
|
108
|
-
count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
|
|
109
|
-
}
|
|
110
|
-
const force = count >= CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD;
|
|
111
|
-
const staleObservedAt = clearedAt - 999_999;
|
|
112
|
-
const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now: clearedAt + 1_000, observedAt: staleObservedAt, force });
|
|
113
|
-
expect(suppressed).toBe(false);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test('one non-rate-limited run in between resets the streak so the hard pause never engages', () => {
|
|
117
|
-
let count = 0;
|
|
118
|
-
count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
|
|
119
|
-
count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
|
|
120
|
-
count = nextRapidRateLimitCount(count, { rateLimited: false, durationMs: 60_000 }); // job actually ran fine
|
|
121
|
-
count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
|
|
122
|
-
expect(count).toBeLessThan(CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD);
|
|
123
|
-
});
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scheduler-rate-limit-pause.test.cjs — PRD 1118: pause against the BINDING
|
|
3
|
-
* rate-limit window read off a run's own log, not refreshNextReset()'s
|
|
4
|
-
* five_hour-only assumption, and never leave an indefinite pause with no
|
|
5
|
-
* resume timer when both the log and the billing endpoint come up empty
|
|
6
|
-
* (the live failure mode: the usage endpoint itself 429s).
|
|
7
|
-
*
|
|
8
|
-
* Also (usage-meter circuit PRD): neither function may arm a PAST reset —
|
|
9
|
-
* resolveRateLimitPauseReset/computeEffectiveResumeAt now reject a stale ISO
|
|
10
|
-
* via usageCircuit.isResetFresh and fall back to the bounded 30-MINUTE pause
|
|
11
|
-
* instead. A stale reset used to survive straight into computeResumeDelay's
|
|
12
|
-
* `Math.max(30_000, <negative>)`, arming a 30-SECOND nap that spun the queue
|
|
13
|
-
* right back into the same still-active rate limit.
|
|
14
|
-
*
|
|
15
|
-
* These test the PURE decision functions scheduler.cjs's setPaused() and the
|
|
16
|
-
* spawnJob() rateLimited branch delegate to — never the real setPaused/mutate
|
|
17
|
-
* path, which writes the machine's live scheduler queue.json at a fixed
|
|
18
|
-
* homedir path (~/.claude/session-manager/scheduled-plans) and must not be
|
|
19
|
-
* touched by a test run.
|
|
20
|
-
*
|
|
21
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/scheduler-rate-limit-pause.test.cjs
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
'use strict';
|
|
25
|
-
|
|
26
|
-
import { test } from 'vitest';
|
|
27
|
-
const assert = require('node:assert/strict');
|
|
28
|
-
const path = require('node:path');
|
|
29
|
-
const {
|
|
30
|
-
resolveRateLimitPauseReset,
|
|
31
|
-
billingResetForPause,
|
|
32
|
-
computeEffectiveResumeAt,
|
|
33
|
-
computeResumeDelay,
|
|
34
|
-
computeDegradedBudget,
|
|
35
|
-
} = require('../scheduler.cjs');
|
|
36
|
-
const billing = require('../usage.cjs');
|
|
37
|
-
|
|
38
|
-
// Repo-tracked, byte-for-byte copy of the real 2026-09-05 starry-night-ships
|
|
39
|
-
// incident log (see src/main/lib/__tests__/rateLimitWindow.test.cjs for the
|
|
40
|
-
// live-path parity check) — kept hermetic rather than pointed at the
|
|
41
|
-
// machine-local live path directly.
|
|
42
|
-
const FIXTURE = path.join(__dirname, '..', 'lib', '__tests__', 'fixtures', '204-mercury-steam-horse.log.txt');
|
|
43
|
-
const FIVE_HOUR_ISO = new Date(1788643800 * 1000).toISOString(); // what refreshNextReset() would have returned
|
|
44
|
-
const SEVEN_DAY_ISO = new Date(1789059600 * 1000).toISOString(); // the actually-binding window
|
|
45
|
-
// Fixed "now" contemporaneous with the fixture's own timestamps (BEFORE both
|
|
46
|
-
// resets above), so freshness checks read the fixture the way it looked at
|
|
47
|
-
// incident time — not relative to whenever this suite happens to run.
|
|
48
|
-
const FIXTURE_NOW = Date.parse('2026-09-05T16:32:00.000Z');
|
|
49
|
-
|
|
50
|
-
test('resolveRateLimitPauseReset: prefers the log-derived seven-day reset over the billing five_hour reset', () => {
|
|
51
|
-
const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO, FIXTURE_NOW);
|
|
52
|
-
assert.strictEqual(resetIso, SEVEN_DAY_ISO);
|
|
53
|
-
assert.notStrictEqual(resetIso, FIVE_HOUR_ISO);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('resolveRateLimitPauseReset: falls back to the billing reset when the log yields none', () => {
|
|
57
|
-
const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO, FIXTURE_NOW);
|
|
58
|
-
assert.strictEqual(resetIso, FIVE_HOUR_ISO);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('resolveRateLimitPauseReset: null when BOTH the log and the billing endpoint are unavailable', () => {
|
|
62
|
-
assert.strictEqual(resolveRateLimitPauseReset('/nonexistent/path/job.log', null, FIXTURE_NOW), null);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('resolveRateLimitPauseReset: a PAST log-derived reset is rejected, not returned stale', () => {
|
|
66
|
-
// "now" is AFTER both fixture resets have already elapsed.
|
|
67
|
-
const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
|
|
68
|
-
const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO, longAfter);
|
|
69
|
-
assert.strictEqual(resetIso, null);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('resolveRateLimitPauseReset: log yields nothing and the billing reset has already passed -> null, not the stale value', () => {
|
|
73
|
-
const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
|
|
74
|
-
const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO, longAfter);
|
|
75
|
-
assert.strictEqual(resetIso, null);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('computeEffectiveResumeAt: rate_limit with no resumeAt gets the same bounded 30-minute fallback as network', () => {
|
|
79
|
-
const now = FIXTURE_NOW;
|
|
80
|
-
const resumeAt = computeEffectiveResumeAt('rate_limit', null, now);
|
|
81
|
-
assert.strictEqual(resumeAt, new Date(now + 30 * 60_000).toISOString());
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('computeEffectiveResumeAt: a FRESH explicit resumeAt (the log-derived reset) is never overridden', () => {
|
|
85
|
-
const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, FIXTURE_NOW);
|
|
86
|
-
assert.strictEqual(resumeAt, SEVEN_DAY_ISO);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('computeEffectiveResumeAt: a PAST resumeAt gets the 30-minute fallback, not a ~30-second nap', () => {
|
|
90
|
-
// now is well AFTER SEVEN_DAY_ISO has already elapsed — the exact shape of
|
|
91
|
-
// the bug: Math.max(30_000, <negative>) used to arm a 30-second resume.
|
|
92
|
-
const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
|
|
93
|
-
const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, longAfter);
|
|
94
|
-
assert.strictEqual(resumeAt, new Date(longAfter + 30 * 60_000).toISOString());
|
|
95
|
-
assert.notStrictEqual(resumeAt, SEVEN_DAY_ISO);
|
|
96
|
-
const { delayMs } = computeResumeDelay(resumeAt, longAfter);
|
|
97
|
-
assert.ok(delayMs >= 30 * 60_000, `expected >= 30 minutes, got ${delayMs}ms`);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('computeEffectiveResumeAt: other reasons (e.g. auth) get no fallback — stays indefinite', () => {
|
|
101
|
-
assert.strictEqual(computeEffectiveResumeAt('auth', null, Date.now()), null);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('billingResetForPause: skips refreshNextReset() (no billing.fetchUsage call) while the shared circuit is OPEN', async () => {
|
|
105
|
-
const originalFetchUsage = billing.fetchUsage;
|
|
106
|
-
let fetchCalls = 0;
|
|
107
|
-
billing.fetchUsage = async () => { fetchCalls++; return { kind: 'ok', data: { usage: {} } }; };
|
|
108
|
-
try {
|
|
109
|
-
// Force the shared circuit open (3 consecutive failures), same threshold usageCircuit.cjs uses.
|
|
110
|
-
billing.usageCircuit.recordFailure('a');
|
|
111
|
-
billing.usageCircuit.recordFailure('a');
|
|
112
|
-
billing.usageCircuit.recordFailure('a');
|
|
113
|
-
assert.strictEqual(billing.usageCircuit.state(), 'open');
|
|
114
|
-
|
|
115
|
-
await billingResetForPause();
|
|
116
|
-
assert.strictEqual(fetchCalls, 0, 'refreshNextReset() must not call billing.fetchUsage() while the circuit is open');
|
|
117
|
-
} finally {
|
|
118
|
-
billing.usageCircuit.recordSuccess({}); // close the shared circuit back up for later tests/files
|
|
119
|
-
billing.fetchUsage = originalFetchUsage;
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('computeResumeDelay: a seven-day-out reset (~5 days from now) is armed, not treated as too-far', () => {
|
|
124
|
-
const now = Date.parse('2026-09-05T16:32:00.000Z');
|
|
125
|
-
// ~5 days out — comfortably under the 0x7fffffff (~24.8 day) setTimeout cap.
|
|
126
|
-
const fiveDaysOut = new Date(now + 5 * 24 * 60 * 60_000).toISOString();
|
|
127
|
-
const { delayMs, tooFar } = computeResumeDelay(fiveDaysOut, now);
|
|
128
|
-
assert.strictEqual(tooFar, false);
|
|
129
|
-
assert.ok(delayMs < 0x7fffffff, `expected delay under setTimeout cap, got ${delayMs}`);
|
|
130
|
-
assert.ok(delayMs > 400_000_000, `expected ~5 days in ms, got ${delayMs}`);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('computeResumeDelay: a reset far enough out DOES trip the overflow guard', () => {
|
|
134
|
-
const now = Date.parse('2026-09-05T16:32:00.000Z');
|
|
135
|
-
const thirtyDaysOut = new Date(now + 30 * 24 * 60 * 60_000).toISOString();
|
|
136
|
-
const { tooFar } = computeResumeDelay(thirtyDaysOut, now);
|
|
137
|
-
assert.strictEqual(tooFar, true);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('refreshNextReset (via billingResetForPause) seeds lastGoodUsagePayload: a following degraded cycle yields the fetched percent, not 100', async () => {
|
|
141
|
-
const originalFetchUsage = billing.fetchUsage;
|
|
142
|
-
billing.fetchUsage = async () => ({
|
|
143
|
-
kind: 'ok',
|
|
144
|
-
data: { usage: { limits: [{ kind: 'weekly_all', percent: 64, resets_at: '2026-09-24T17:00:00Z', scope: null, is_active: true }] } },
|
|
145
|
-
});
|
|
146
|
-
try {
|
|
147
|
-
await billingResetForPause();
|
|
148
|
-
assert.strictEqual(computeDegradedBudget().utilization, 64);
|
|
149
|
-
} finally {
|
|
150
|
-
billing.fetchUsage = originalFetchUsage;
|
|
151
|
-
}
|
|
152
|
-
});
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scheduler-rate-limit-spin-guard.test.cjs — PRD 1119 end-to-end repro of the
|
|
3
|
-
* 2026-09-05 incident: a human pressed Resume/Run now on job 204, and for the
|
|
4
|
-
* next hour the scheduler dispatched it, watched it 429 in seconds, reset it
|
|
5
|
-
* to pending (halt-reset), and dispatched it again — 291 times, one every
|
|
6
|
-
* ~12s — because setPaused()'s manual-override cooldown suppressed WRITING
|
|
7
|
-
* the rate_limit pause for the whole 5-minute window, even though every one
|
|
8
|
-
* of those 429s was a brand-new observation from a run that started AFTER
|
|
9
|
-
* the human's clear.
|
|
10
|
-
*
|
|
11
|
-
* This drives the REAL dispatch path (spawnJob, via a stub `claude` binary
|
|
12
|
-
* that always 429s in well under a second) repeatedly, immediately after a
|
|
13
|
-
* simulated manual Resume, and asserts the scheduler engages a genuine pause
|
|
14
|
-
* almost immediately — never anywhere near the 291-dispatch runaway, and
|
|
15
|
-
* never past the documented CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD hard cap
|
|
16
|
-
* even in the worst case. Compressed in wall-clock time (a stub exits in
|
|
17
|
-
* milliseconds, not the ~12s a real `claude -p` round trip took) — the
|
|
18
|
-
* assertion is on DISPATCH COUNT, which is what the fix actually bounds,
|
|
19
|
-
* not on wall-clock duration.
|
|
20
|
-
*
|
|
21
|
-
* Run: timeout 60 npx vitest run src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
'use strict';
|
|
25
|
-
|
|
26
|
-
import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
27
|
-
const fs = require('node:fs');
|
|
28
|
-
const os = require('node:os');
|
|
29
|
-
const path = require('node:path');
|
|
30
|
-
const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
|
|
31
|
-
const { execFileSync } = require('node:child_process');
|
|
32
|
-
|
|
33
|
-
let tmpHome;
|
|
34
|
-
let originalHome;
|
|
35
|
-
let scheduler;
|
|
36
|
-
let queueStore;
|
|
37
|
-
|
|
38
|
-
beforeAll(() => {
|
|
39
|
-
originalHome = process.env.HOME;
|
|
40
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-rate-limit-spin-guard-'));
|
|
41
|
-
process.env.HOME = tmpHome;
|
|
42
|
-
// Worktree isolation is irrelevant to this repro (it's about pause
|
|
43
|
-
// suppression, not tree isolation) and each real `git worktree add` adds
|
|
44
|
-
// real wall-clock overhead this test does not need to pay 40 times over.
|
|
45
|
-
process.env.SM_JOB_WORKTREE_DISABLE = '1';
|
|
46
|
-
scheduler = require('../scheduler.cjs');
|
|
47
|
-
queueStore = require('../lib/queueStore.cjs');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
afterAll(() => {
|
|
51
|
-
process.env.HOME = originalHome;
|
|
52
|
-
delete process.env.SM_JOB_WORKTREE_DISABLE;
|
|
53
|
-
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
afterEach(() => {
|
|
57
|
-
delete process.env.SM_CLAUDE_BIN;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
function git(args, cwd) {
|
|
61
|
-
return execFileSync('git', args, { cwd, encoding: 'utf8' });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function initRepo(dir) {
|
|
65
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
66
|
-
git(['init', '-q'], dir);
|
|
67
|
-
git(['config', 'user.email', 'test@example.com'], dir);
|
|
68
|
-
git(['config', 'user.name', 'Test'], dir);
|
|
69
|
-
fs.writeFileSync(path.join(dir, 'README.md'), 'hello\n', 'utf8');
|
|
70
|
-
git(['add', '-A'], dir);
|
|
71
|
-
git(['commit', '-q', '-m', 'initial'], dir);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function registerActiveProject(cwd) {
|
|
75
|
-
const projectsDir = path.join(tmpHome, '.claude', 'projects');
|
|
76
|
-
const slugDir = path.join(projectsDir, 'spin-guard-project-slug');
|
|
77
|
-
fs.mkdirSync(slugDir, { recursive: true });
|
|
78
|
-
fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
|
|
79
|
-
queueStore.bustCwdCache();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function writeProjectQueue(cwd, jobs) {
|
|
83
|
-
const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
|
|
84
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
85
|
-
const queuePath = path.join(stateDir, 'queue.json');
|
|
86
|
-
fs.writeFileSync(queuePath, JSON.stringify({ jobs }, null, 2));
|
|
87
|
-
return queuePath;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Stub `claude` binary: always dies as a rate limit, in well under 30s (same
|
|
91
|
-
// technique as scheduler-prd-persona-spawn.test.cjs's argv-marker stub) —
|
|
92
|
-
// emits the canonical 429 signal detectRateLimitInLog looks for, then exits
|
|
93
|
-
// non-zero almost instantly, and bumps a counter file each invocation so the
|
|
94
|
-
// test can assert exactly how many times it was actually spawned.
|
|
95
|
-
function writeAlways429ClaudeStub(counterPath) {
|
|
96
|
-
return claudeStub.writeClaudeStub({ body: `
|
|
97
|
-
const fs = require('fs');
|
|
98
|
-
fs.appendFileSync(${JSON.stringify(counterPath)}, 'x');
|
|
99
|
-
process.stdout.write(JSON.stringify({ type: 'result', subtype: 'error', is_error: true, api_error_status: 429 }) + '\\n');
|
|
100
|
-
process.exit(1);
|
|
101
|
-
` });
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
test('a manual Resume immediately followed by a permanently-429ing job never produces a runaway dispatch loop', async () => {
|
|
105
|
-
const projectCwd = fs.mkdtempSync(path.join(tmpHome, 'spin-guard-project-'));
|
|
106
|
-
initRepo(projectCwd);
|
|
107
|
-
registerActiveProject(projectCwd);
|
|
108
|
-
const slug = `1119-spin-guard-${process.pid}`;
|
|
109
|
-
const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'prds');
|
|
110
|
-
fs.mkdirSync(prdsDir, { recursive: true });
|
|
111
|
-
fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'Do the thing.', 'utf8');
|
|
112
|
-
|
|
113
|
-
const counterPath = path.join(tmpHome, 'dispatch-count.txt');
|
|
114
|
-
fs.writeFileSync(counterPath, '');
|
|
115
|
-
process.env.SM_CLAUDE_BIN = writeAlways429ClaudeStub(counterPath);
|
|
116
|
-
|
|
117
|
-
writeProjectQueue(projectCwd, [
|
|
118
|
-
{ slug, status: 'pending', cwd: projectCwd },
|
|
119
|
-
]);
|
|
120
|
-
|
|
121
|
-
// The human just pressed Resume/Run now — this is the exact call clearPause
|
|
122
|
-
// makes from the IPC handlers, and it starts the 5-minute cooldown window.
|
|
123
|
-
await scheduler.clearPause('manual');
|
|
124
|
-
|
|
125
|
-
const job = { slug, cwd: projectCwd };
|
|
126
|
-
|
|
127
|
-
// Simulate the tick loop's dispatch decision being driven repeatedly "over
|
|
128
|
-
// ten minutes" — compressed here to however many cycles a permanently-429
|
|
129
|
-
// job can actually complete, since each cycle is a real (fast) subprocess
|
|
130
|
-
// spawn rather than a real 12s `claude -p` round trip. tickQueue()'s own
|
|
131
|
-
// paused early-return (unchanged by this PRD) is exactly what this loop
|
|
132
|
-
// reproduces by hand: stop dispatching the moment the queue is paused.
|
|
133
|
-
const MAX_TICKS = 40;
|
|
134
|
-
for (let i = 0; i < MAX_TICKS; i += 1) {
|
|
135
|
-
// eslint-disable-next-line no-await-in-loop
|
|
136
|
-
const state = await queueStore.readMerged();
|
|
137
|
-
if (state.paused) break;
|
|
138
|
-
const row = state.jobs.find((j) => j.slug === slug);
|
|
139
|
-
if (!row || row.status !== 'pending') break;
|
|
140
|
-
const runId = `run-${i}`;
|
|
141
|
-
const runDir = path.join(scheduler.RUNS_DIR, runId);
|
|
142
|
-
fs.mkdirSync(runDir, { recursive: true });
|
|
143
|
-
// eslint-disable-next-line no-await-in-loop
|
|
144
|
-
await scheduler.spawnJob(job, runId, runDir, projectCwd);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const dispatchCount = fs.readFileSync(counterPath, 'utf8').length;
|
|
148
|
-
expect(dispatchCount).toBeLessThanOrEqual(scheduler.CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD);
|
|
149
|
-
expect(dispatchCount).toBeGreaterThan(0);
|
|
150
|
-
expect(dispatchCount).toBeLessThan(10); // nowhere near the 291-dispatch incident
|
|
151
|
-
|
|
152
|
-
const finalState = await queueStore.readMerged();
|
|
153
|
-
expect(finalState.paused).toBeTruthy();
|
|
154
|
-
expect(finalState.paused.reason).toBe('rate_limit');
|
|
155
|
-
expect(finalState.jobs.find((j) => j.slug === slug).status).toBe('pending');
|
|
156
|
-
});
|