claude-code-session-manager 0.86.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -53
- package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
- package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
- package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
- package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
- package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
- package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
- package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
- package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
- package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
- package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
- package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
- package/dist/assets/Permissions-m0KAnccF.js +3 -0
- package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
- package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
- package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
- package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
- package/dist/assets/Settings-BHSGGFEc.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
- package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
- package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
- package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
- package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
- package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
- package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
- package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
- package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
- package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
- package/dist/index.html +2 -2
- package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
- package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
- package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
- package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
- package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
- package/package.json +14 -17
- package/plugins/CLAUDE.md +61 -0
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
- package/scripts/README.md +59 -0
- package/scripts/audit-ops-hygiene.cjs +350 -0
- package/scripts/hooks/guard-destructive-git.cjs +10 -34
- package/scripts/hooks/guard-inline-implementation.cjs +14 -8
- package/scripts/hooks/guard-prd-writes.cjs +7 -36
- package/scripts/hooks/guard-self-schedule.cjs +175 -0
- package/scripts/ops-sweep.cjs +355 -0
- package/scripts/scheduler-mcp-server.cjs +35 -71
- package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
- package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
- package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
- package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
- package/src/main/__tests__/chat-queue.test.cjs +2 -2
- package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
- package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
- package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
- package/src/main/__tests__/dod-report.test.cjs +2 -2
- package/src/main/__tests__/dod-reverify.test.cjs +2 -2
- package/src/main/__tests__/epicMint.test.cjs +4 -4
- package/src/main/__tests__/exchanges.test.cjs +2 -2
- package/src/main/__tests__/extractJson.test.cjs +2 -2
- package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
- package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
- package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
- package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
- package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
- package/src/main/__tests__/historyRollup.test.cjs +2 -2
- package/src/main/__tests__/kg-augment.test.cjs +2 -2
- package/src/main/__tests__/mcpStatus.test.cjs +2 -2
- package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
- package/src/main/__tests__/memoryStale.test.cjs +1 -1
- package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
- package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
- package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
- package/src/main/__tests__/prdLocations.test.cjs +100 -2
- package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
- package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
- package/src/main/__tests__/proc-role-env.test.cjs +125 -0
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
- package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
- package/src/main/__tests__/projectPages.test.cjs +63 -149
- package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
- package/src/main/__tests__/queueHistory.test.cjs +65 -2
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
- package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
- package/src/main/__tests__/runVerify.test.cjs +5 -5
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
- package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
- package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
- package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
- package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
- package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
- package/src/main/bilkoHost.cjs +16 -7
- package/src/main/build-info.json +8 -0
- package/src/main/chatRunner.cjs +12 -7
- package/src/main/config.cjs +2 -3
- package/src/main/docEdit.cjs +4 -2
- package/src/main/health.cjs +268 -52
- package/src/main/heapSnapshot.cjs +2 -2
- package/src/main/historyAggregator.cjs +6 -1
- package/src/main/index.cjs +152 -50
- package/src/main/ipcSchemas.cjs +53 -17
- package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
- package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
- package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
- package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
- package/src/main/lib/__tests__/gateFixtures.json +20 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
- package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
- package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
- package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
- package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
- package/src/main/lib/__tests__/procName.test.cjs +92 -0
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
- package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
- package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
- package/src/main/lib/activeIndexRebuild.cjs +1 -3
- package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
- package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
- package/src/main/lib/agentModelResolve.cjs +94 -24
- package/src/main/lib/agentPersonaSchema.cjs +2 -2
- package/src/main/lib/auditLog.cjs +30 -5
- package/src/main/lib/bootSelfHeal.cjs +88 -0
- package/src/main/lib/buildIdentity.cjs +113 -0
- package/src/main/lib/classifyPromptTicket.cjs +3 -2
- package/src/main/lib/claudeBin.cjs +37 -2
- package/src/main/lib/cleanEnv.cjs +27 -1
- package/src/main/lib/credentials.cjs +4 -2
- package/src/main/lib/cwdClassify.cjs +185 -0
- package/src/main/lib/definitionOfDone.cjs +296 -52
- package/src/main/lib/delegationReadiness.cjs +290 -225
- package/src/main/lib/dispatchLoop.cjs +40 -0
- package/src/main/lib/effectiveModelInfo.cjs +332 -0
- package/src/main/lib/ephemeralCwd.cjs +7 -28
- package/src/main/lib/epicMint.cjs +3 -3
- package/src/main/lib/gitWorktree.cjs +136 -19
- package/src/main/lib/guardShims.cjs +156 -0
- package/src/main/lib/historyRollup.cjs +6 -7
- package/src/main/lib/instanceLock.cjs +32 -5
- package/src/main/lib/jobDirtFilter.cjs +7 -2
- package/src/main/lib/jobSupervisorRecord.cjs +147 -0
- package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
- package/src/main/lib/launchFailure.cjs +2 -1
- package/src/main/lib/localAdminHttp.cjs +9 -27
- package/src/main/lib/mcpToolCatalog.cjs +33 -78
- package/src/main/lib/needsReviewLedger.cjs +205 -0
- package/src/main/lib/opsErrorLog.cjs +1 -1
- package/src/main/lib/opsOwnership.cjs +27 -19
- package/src/main/lib/prdAuthoringSeed.cjs +36 -0
- package/src/main/lib/prdCreate.cjs +56 -1
- package/src/main/lib/prdDisposition.cjs +199 -0
- package/src/main/lib/prdFrontmatter.cjs +8 -2
- package/src/main/lib/prdLocations.cjs +214 -45
- package/src/main/lib/procIdentity.cjs +126 -0
- package/src/main/lib/procName.cjs +98 -0
- package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
- package/src/main/lib/projectRootResolve.cjs +1 -1
- package/src/main/lib/queueHistory.cjs +27 -8
- package/src/main/lib/queueStore.cjs +63 -38
- package/src/main/lib/quietMachineLease.cjs +19 -2
- package/src/main/lib/reaperHelpers.cjs +181 -15
- package/src/main/lib/reservationExpiry.cjs +30 -0
- package/src/main/lib/runClaudeP.cjs +4 -2
- package/src/main/lib/runLogRetention.cjs +3 -2
- package/src/main/lib/scheduleJobSchema.cjs +10 -2
- package/src/main/lib/scheduleJobTransitions.cjs +60 -0
- package/src/main/lib/schedulerBatch.cjs +27 -2
- package/src/main/lib/schedulerConfig.cjs +24 -0
- package/src/main/lib/schedulerPaths.cjs +175 -0
- package/src/main/lib/schedulerRuntimeState.cjs +59 -0
- package/src/main/lib/sessionSlots.cjs +47 -9
- package/src/main/lib/smProcNames.cjs +51 -0
- package/src/main/lib/upgradeDrain.cjs +188 -0
- package/src/main/lib/usageCircuit.cjs +206 -0
- package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
- package/src/main/lib/withTimeout.cjs +33 -0
- package/src/main/mcpStatus.cjs +4 -2
- package/src/main/pluginInstall.cjs +6 -2
- package/src/main/projectPages.cjs +41 -145
- package/src/main/pty.cjs +8 -1
- package/src/main/queueOps.cjs +10 -10
- package/src/main/runVerify.cjs +69 -3
- package/src/main/scheduler/prdParser.cjs +13 -0
- package/src/main/scheduler.cjs +2841 -482
- package/src/main/seedAgentPersonas.cjs +1 -1
- package/src/main/seedDevPlugin.cjs +1 -1
- package/src/main/seedSchedulerMcp.cjs +7 -6
- package/src/main/seedStatus.cjs +1 -1
- package/src/main/supervisor.cjs +5 -3
- package/src/main/templates/PRD_AUTHORING.md +50 -0
- package/src/main/usage.cjs +140 -58
- package/src/preload/api.d.ts +110 -65
- package/src/preload/index.cjs +12 -4
- package/src/seed/agents/project-home-builder.md +31 -48
- package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
- package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
- package/dist/assets/Permissions-BiYZNGYW.js +0 -3
- package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
- package/dist/assets/Settings-Cv-pRyms.js +0 -3
- package/dist/assets/settingsSchema-BJVciriw.js +0 -3
- package/screenshots/.gitkeep +0 -0
- package/screenshots/README-screenshots.md +0 -13
- package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
- package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
- package/scripts/render-project-pages.cjs +0 -70
- package/scripts/validate-project-pages-summary.cjs +0 -62
- package/src/main/lib/projectPageSummarySchema.cjs +0 -181
- package/src/main/teams.cjs +0 -95
- package/src/main/templates/project-pages-catalog.json +0 -741
- package/src/main/templates/project-pages-default-home.html +0 -123
- package/src/main/templates/project-pages-pipeline.md +0 -417
- package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
- package/src/seed/prompts/code-review/correctness-only.md +0 -8
- package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
- package/src/seed/prompts/code-review/hallucination-check.md +0 -8
- package/src/seed/prompts/code-review/public-api-compat.md +0 -8
- package/src/seed/prompts/code-review/readability-naming.md +0 -8
- package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
- package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
- package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
- package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
- package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
- package/src/seed/prompts/documentation/adr-from-change.md +0 -8
- package/src/seed/prompts/documentation/module-readme.md +0 -8
- package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
- package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
- package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
- package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
- package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
- package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
- package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
- package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
- package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
- package/src/seed/prompts/performance/complexity-audit.md +0 -8
- package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
- package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
- package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
- package/src/seed/prompts/qa/api-contract-tests.md +0 -8
- package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
- package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
- package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
- package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
- package/src/seed/prompts/qa/tdd-red-first.md +0 -8
- package/src/seed/prompts/qa/visual-regression-review.md +0 -8
- package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
- package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
- package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
- package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
- package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
- package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
- package/src/seed/prompts/security/authz-audit.md +0 -8
- package/src/seed/prompts/security/crypto-correctness.md +0 -8
- package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
- package/src/seed/prompts/security/dependency-audit.md +0 -8
- package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
- package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
- package/src/seed/prompts/security/secret-credential-scan.md +0 -8
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scheduler-porcelain-rename.test.cjs — PRD 1187/1186-follow-on: a staged
|
|
3
|
+
* rename ("R old -> new") was parsed by `parsePorcelainEntries` as ONE fused
|
|
4
|
+
* path string `"old -> new"`. That string can never match a real path in
|
|
5
|
+
* `dirtyAfter`, `pathsCommittedDuringRun`, or an `fs.existsSync` check, so
|
|
6
|
+
* every staged rename in a shared-tree-guard baseline was structurally
|
|
7
|
+
* guaranteed to be reported reverted — even when it was cleanly committed
|
|
8
|
+
* during the run. On 2026-09-12 this flagged 45 paths and parked a clean,
|
|
9
|
+
* committed run (job `1187-autoresolve-guard-verdict-parks`):
|
|
10
|
+
*
|
|
11
|
+
* "job discarded pre-existing state in the shared tree: 45 path(s)
|
|
12
|
+
* reverted with no commit to explain it
|
|
13
|
+
* (scripts/__tests__/manual-chapter-links.test.cjs ->
|
|
14
|
+
* web/manual/__tests__/chapter-links.test.cjs, ...)" — exit 0, landedCommit
|
|
15
|
+
* recorded, nothing actually lost.
|
|
16
|
+
*
|
|
17
|
+
* Every test below is written to FAIL against the pre-fix implementation
|
|
18
|
+
* (`path: l.slice(3)`, no rename/quote parsing at all).
|
|
19
|
+
*
|
|
20
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/scheduler-porcelain-rename.test.cjs
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
import { test, expect } from 'vitest';
|
|
26
|
+
const scheduler = require('../scheduler.cjs');
|
|
27
|
+
const { parsePorcelainEntries, evaluateSharedTreeGuard } = scheduler;
|
|
28
|
+
|
|
29
|
+
// --- parsePorcelainEntries: rename/copy lines split into real components ---
|
|
30
|
+
|
|
31
|
+
test('parsePorcelainEntries: a plain staged rename splits into new path + oldPath, not one fused string', () => {
|
|
32
|
+
const entries = parsePorcelainEntries('R old.txt -> new.txt\n');
|
|
33
|
+
expect(entries).toEqual([{ code: 'R ', path: 'new.txt', oldPath: 'old.txt' }]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('parsePorcelainEntries: a rename-with-worktree-modification (RM) line still splits correctly', () => {
|
|
37
|
+
const entries = parsePorcelainEntries('RM old.txt -> new.txt\n');
|
|
38
|
+
expect(entries).toEqual([{ code: 'RM', path: 'new.txt', oldPath: 'old.txt' }]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('parsePorcelainEntries: a copy line (C) splits the same way as a rename', () => {
|
|
42
|
+
const entries = parsePorcelainEntries('C old.txt -> copy.txt\n');
|
|
43
|
+
expect(entries).toEqual([{ code: 'C ', path: 'copy.txt', oldPath: 'old.txt' }]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('parsePorcelainEntries: a NEW path containing a space is quoted by git and must be unquoted', () => {
|
|
47
|
+
// Real `git status --porcelain` output for `git mv new.txt "renamed with space.txt"`.
|
|
48
|
+
const entries = parsePorcelainEntries('R new.txt -> "renamed with space.txt"\n');
|
|
49
|
+
expect(entries).toEqual([{ code: 'R ', path: 'renamed with space.txt', oldPath: 'new.txt' }]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('parsePorcelainEntries: an OLD path containing a space is quoted independently of the new path', () => {
|
|
53
|
+
const entries = parsePorcelainEntries('R "old with space.txt" -> new.txt\n');
|
|
54
|
+
expect(entries).toEqual([{ code: 'R ', path: 'new.txt', oldPath: 'old with space.txt' }]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('parsePorcelainEntries: a non-ASCII path is quoted with per-byte octal escapes and must decode back to UTF-8', () => {
|
|
58
|
+
// Real recorded `git status --porcelain` output for a rename onto
|
|
59
|
+
// "résumé file.txt" — git's default core.quotepath escapes each raw UTF-8
|
|
60
|
+
// byte >= 0x80 as \NNN octal: 'é' is UTF-8 bytes 0xC3 0xA9 -> \303\251.
|
|
61
|
+
const line = 'R new.txt -> "r\\303\\251sum\\303\\251 file.txt"\n';
|
|
62
|
+
const entries = parsePorcelainEntries(line);
|
|
63
|
+
expect(entries).toEqual([{ code: 'R ', path: 'résumé file.txt', oldPath: 'new.txt' }]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('parsePorcelainEntries: non-rename entries are unaffected (no oldPath, path as before)', () => {
|
|
67
|
+
const entries = parsePorcelainEntries(' M src/foo.js\n?? untracked.txt\n');
|
|
68
|
+
expect(entries).toEqual([
|
|
69
|
+
{ code: ' M', path: 'src/foo.js' },
|
|
70
|
+
{ code: '??', path: 'untracked.txt' },
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('parsePorcelainEntries: a quoted plain (non-rename) path is also unquoted, not left with literal quotes', () => {
|
|
75
|
+
const entries = parsePorcelainEntries('?? "plain space.txt"\n');
|
|
76
|
+
expect(entries).toEqual([{ code: '??', path: 'plain space.txt' }]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// --- The exact 1187 shape: committed rename must not be flagged reverted ---
|
|
80
|
+
|
|
81
|
+
test('evaluateSharedTreeGuard: a staged rename that is committed during the run is NOT reported reverted (real 1187 case)', () => {
|
|
82
|
+
const oldPath = 'scripts/__tests__/manual-chapter-links.test.cjs';
|
|
83
|
+
const newPath = 'web/manual/__tests__/chapter-links.test.cjs';
|
|
84
|
+
const dirtyBefore = parsePorcelainEntries(`R ${oldPath} -> ${newPath}\n`);
|
|
85
|
+
// git diff --name-only <headBefore>..<headAfter> reports only the NEW path
|
|
86
|
+
// for a committed rename (verified against real git 2.43 behavior).
|
|
87
|
+
const result = evaluateSharedTreeGuard({
|
|
88
|
+
stashBefore: [],
|
|
89
|
+
stashAfter: [],
|
|
90
|
+
dirtyBefore,
|
|
91
|
+
dirtyAfter: [], // fully committed, nothing left dirty
|
|
92
|
+
pathsCommittedDuringRun: [newPath],
|
|
93
|
+
});
|
|
94
|
+
expect(result.reverted).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('evaluateSharedTreeGuard: a batch of committed renames (the real 45-path shape) are all cleared, none reverted', () => {
|
|
98
|
+
const pairs = [
|
|
99
|
+
['scripts/__tests__/manual-chapter-links.test.cjs', 'web/manual/__tests__/chapter-links.test.cjs'],
|
|
100
|
+
['scripts/foo-old.js', 'src/foo-new.js'],
|
|
101
|
+
['docs/old-name.md', 'docs/new-name.md'],
|
|
102
|
+
];
|
|
103
|
+
const porcelain = pairs.map(([o, n]) => `R ${o} -> ${n}`).join('\n') + '\n';
|
|
104
|
+
const dirtyBefore = parsePorcelainEntries(porcelain);
|
|
105
|
+
const result = evaluateSharedTreeGuard({
|
|
106
|
+
stashBefore: [],
|
|
107
|
+
stashAfter: [],
|
|
108
|
+
dirtyBefore,
|
|
109
|
+
dirtyAfter: [],
|
|
110
|
+
pathsCommittedDuringRun: pairs.map(([, n]) => n),
|
|
111
|
+
});
|
|
112
|
+
expect(result.reverted).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// --- The guard's real purpose survives: an UNDONE rename still parks ---
|
|
116
|
+
|
|
117
|
+
test('evaluateSharedTreeGuard: a staged rename that is UNDONE (old path restored, new path gone, nothing committed) IS still reported reverted', () => {
|
|
118
|
+
// This test must FAIL if the fix is implemented as "blanket skip rename
|
|
119
|
+
// entries" (dropping every rename-coded entry from consideration would
|
|
120
|
+
// make `reverted` come back empty here, exactly like the committed case
|
|
121
|
+
// above — but here nothing was committed to explain the disappearance).
|
|
122
|
+
const dirtyBefore = parsePorcelainEntries('R old.txt -> new.txt\n');
|
|
123
|
+
const result = evaluateSharedTreeGuard({
|
|
124
|
+
stashBefore: [],
|
|
125
|
+
stashAfter: [],
|
|
126
|
+
dirtyBefore,
|
|
127
|
+
dirtyAfter: [], // clean again — reverted to HEAD, old.txt restored, new.txt gone
|
|
128
|
+
pathsCommittedDuringRun: [], // nothing committed
|
|
129
|
+
});
|
|
130
|
+
expect(result.reverted).toEqual(['new.txt']);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// --- The two already-fixed false-positive classes stay fixed (pinned here
|
|
134
|
+
// too, using parsePorcelainEntries as the real entry point rather than
|
|
135
|
+
// hand-built fixtures, so a future change to the parser can't silently
|
|
136
|
+
// regress them without failing a test right next to the rename fix). ---
|
|
137
|
+
|
|
138
|
+
test('evaluateSharedTreeGuard: nowIgnored still works for a real parsePorcelainEntries untracked baseline entry', () => {
|
|
139
|
+
const dirtyBefore = parsePorcelainEntries('?? session-manager-operations/logs/errors-2026-09-12.jsonl\n');
|
|
140
|
+
const result = evaluateSharedTreeGuard({
|
|
141
|
+
stashBefore: [],
|
|
142
|
+
stashAfter: [],
|
|
143
|
+
dirtyBefore,
|
|
144
|
+
dirtyAfter: [],
|
|
145
|
+
pathsCommittedDuringRun: ['.gitignore'],
|
|
146
|
+
existsAfter: ['session-manager-operations/logs/errors-2026-09-12.jsonl'],
|
|
147
|
+
});
|
|
148
|
+
expect(result.reverted).toEqual([]);
|
|
149
|
+
expect(result.nowIgnored).toEqual(['session-manager-operations/logs/errors-2026-09-12.jsonl']);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('evaluateSharedTreeGuard: a genuine deletion of an untracked path (from a real parsePorcelainEntries entry) is still reverted', () => {
|
|
153
|
+
const dirtyBefore = parsePorcelainEntries('?? data/scratch.txt\n');
|
|
154
|
+
const result = evaluateSharedTreeGuard({
|
|
155
|
+
stashBefore: [],
|
|
156
|
+
stashAfter: [],
|
|
157
|
+
dirtyBefore,
|
|
158
|
+
dirtyAfter: [],
|
|
159
|
+
pathsCommittedDuringRun: [],
|
|
160
|
+
existsAfter: [],
|
|
161
|
+
});
|
|
162
|
+
expect(result.reverted).toEqual(['data/scratch.txt']);
|
|
163
|
+
expect(result.nowIgnored).toEqual([]);
|
|
164
|
+
});
|
|
@@ -103,6 +103,22 @@ function writeProjectQueue(cwd, jobs) {
|
|
|
103
103
|
return path.join(stateDir, 'queue.json');
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// This test's whole premise is a PRD source missing EVERYWHERE — so the row
|
|
107
|
+
// this produces is exactly what reconcile()'s auto-archive-drop path treats
|
|
108
|
+
// as "archived on purpose": backfilled to history.jsonl and dropped from
|
|
109
|
+
// queue.json in the very same reconcile pass that spawnJob's own
|
|
110
|
+
// broadcast({flush:true}) triggers on completion (see
|
|
111
|
+
// scheduler-reconcile-history-backfill.test.cjs). Look in both places.
|
|
112
|
+
function readTerminalRow(queuePath, projectCwd, slug) {
|
|
113
|
+
const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
|
|
114
|
+
const row = jobs.find((j) => j.slug === slug);
|
|
115
|
+
if (row) return row;
|
|
116
|
+
const historyPath = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'state', 'history.jsonl');
|
|
117
|
+
if (!fs.existsSync(historyPath)) return undefined;
|
|
118
|
+
const lines = fs.readFileSync(historyPath, 'utf8').trim().split('\n').filter(Boolean);
|
|
119
|
+
return lines.map((l) => JSON.parse(l)).reverse().find((j) => j.slug === slug);
|
|
120
|
+
}
|
|
121
|
+
|
|
106
122
|
// This is the actual regression case: PRD 812's fix only covered executeJob's
|
|
107
123
|
// own return value. The bug lived one layer up, in spawnJob's post-run
|
|
108
124
|
// handler, which only short-circuited on skipped === 'prd-archived' and let
|
|
@@ -123,8 +139,7 @@ test("spawnJob's post-run handler retires a prd-missing skip as a plain completi
|
|
|
123
139
|
const job = { slug, cwd: projectCwd };
|
|
124
140
|
await spawnJob(job, 'run-spawnjob-missing', runDir, projectCwd);
|
|
125
141
|
|
|
126
|
-
const
|
|
127
|
-
const row = state.jobs.find((j) => j.slug === slug);
|
|
142
|
+
const row = readTerminalRow(queuePath, projectCwd, slug);
|
|
128
143
|
expect(row).toBeDefined();
|
|
129
144
|
// A 'prd-missing' skip means no executor ever ran — it must land on the
|
|
130
145
|
// distinct 'skipped' status, never 'completed' (the status a genuine
|
|
@@ -17,6 +17,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
|
17
17
|
const fs = require('node:fs');
|
|
18
18
|
const os = require('node:os');
|
|
19
19
|
const path = require('node:path');
|
|
20
|
+
const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
|
|
20
21
|
|
|
21
22
|
let tmpHome;
|
|
22
23
|
let originalHome;
|
|
@@ -66,16 +67,13 @@ test('buildClaudeSpawnArgs: model is never omitted even without a systemPrompt',
|
|
|
66
67
|
// stream-json success result and exits 0 — same technique as
|
|
67
68
|
// scheduler-bash-timeout-env.test.cjs's env-var proof, applied to argv.
|
|
68
69
|
function writeClaudeStub() {
|
|
69
|
-
|
|
70
|
-
const body = `
|
|
70
|
+
return claudeStub.writeClaudeStub({ body: `
|
|
71
71
|
const fs = require('fs');
|
|
72
72
|
const path = require('path');
|
|
73
73
|
fs.writeFileSync(path.join(process.cwd(), 'argv.marker'), JSON.stringify(process.argv.slice(2)), 'utf8');
|
|
74
74
|
process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok' }) + '\\n');
|
|
75
75
|
process.exit(0);
|
|
76
|
-
|
|
77
|
-
fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
|
|
78
|
-
return stubPath;
|
|
76
|
+
` });
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
function writePersona(dir, name, frontmatterLines, bodyText) {
|
|
@@ -19,6 +19,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
|
19
19
|
const fs = require('node:fs');
|
|
20
20
|
const os = require('node:os');
|
|
21
21
|
const path = require('node:path');
|
|
22
|
+
const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
|
|
22
23
|
const { execFileSync } = require('node:child_process');
|
|
23
24
|
|
|
24
25
|
let tmpHome;
|
|
@@ -52,10 +53,41 @@ function registerActiveProject(cwd) {
|
|
|
52
53
|
fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
|
|
53
54
|
}
|
|
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
|
+
|
|
55
84
|
function writeProjectQueue(cwd, jobs) {
|
|
56
85
|
const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
|
|
57
86
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
58
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
|
+
}
|
|
59
91
|
return path.join(stateDir, 'queue.json');
|
|
60
92
|
}
|
|
61
93
|
|
|
@@ -66,15 +98,12 @@ function writeProjectQueue(cwd, jobs) {
|
|
|
66
98
|
// as a genuine kill-with-partial-work, not an auto-retryable transient blip
|
|
67
99
|
// with no evidence of real execution.
|
|
68
100
|
function writeKilledClaudeStub() {
|
|
69
|
-
|
|
70
|
-
const body = `
|
|
101
|
+
return claudeStub.writeClaudeStub({ body: `
|
|
71
102
|
const fs = require('fs');
|
|
72
103
|
const path = require('path');
|
|
73
104
|
fs.writeFileSync(path.join(process.cwd(), 'job-output.txt'), 'work in progress when killed\\n', 'utf8');
|
|
74
105
|
process.exit(137);
|
|
75
|
-
|
|
76
|
-
fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
|
|
77
|
-
return stubPath;
|
|
106
|
+
` });
|
|
78
107
|
}
|
|
79
108
|
|
|
80
109
|
let reapDeadRunningJobs;
|
|
@@ -109,7 +138,13 @@ test('a killed quietMachine job releases the exclusive lease, and an ordinary jo
|
|
|
109
138
|
|
|
110
139
|
const quietSlug = `1107-test-quiet-killed-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
|
|
111
140
|
const ordinarySlug = `1107-test-ordinary-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
|
|
112
|
-
|
|
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');
|
|
113
148
|
fs.mkdirSync(prdsDir, { recursive: true });
|
|
114
149
|
fs.writeFileSync(path.join(prdsDir, `${quietSlug}.md`), '---\nquietMachine: true\n---\nMeasure timing without contention, then get killed.', 'utf8');
|
|
115
150
|
fs.writeFileSync(path.join(prdsDir, `${ordinarySlug}.md`), 'An ordinary PRD queued behind the quiet job.', 'utf8');
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
* resume timer when both the log and the billing endpoint come up empty
|
|
6
6
|
* (the live failure mode: the usage endpoint itself 429s).
|
|
7
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
|
+
*
|
|
8
15
|
* These test the PURE decision functions scheduler.cjs's setPaused() and the
|
|
9
16
|
* spawnJob() rateLimited branch delegate to — never the real setPaused/mutate
|
|
10
17
|
* path, which writes the machine's live scheduler queue.json at a fixed
|
|
@@ -21,9 +28,11 @@ const assert = require('node:assert/strict');
|
|
|
21
28
|
const path = require('node:path');
|
|
22
29
|
const {
|
|
23
30
|
resolveRateLimitPauseReset,
|
|
31
|
+
billingResetForPause,
|
|
24
32
|
computeEffectiveResumeAt,
|
|
25
33
|
computeResumeDelay,
|
|
26
34
|
} = require('../scheduler.cjs');
|
|
35
|
+
const billing = require('../usage.cjs');
|
|
27
36
|
|
|
28
37
|
// Repo-tracked, byte-for-byte copy of the real 2026-09-05 starry-night-ships
|
|
29
38
|
// incident log (see src/main/lib/__tests__/rateLimitWindow.test.cjs for the
|
|
@@ -32,37 +41,84 @@ const {
|
|
|
32
41
|
const FIXTURE = path.join(__dirname, '..', 'lib', '__tests__', 'fixtures', '204-mercury-steam-horse.log.txt');
|
|
33
42
|
const FIVE_HOUR_ISO = new Date(1788643800 * 1000).toISOString(); // what refreshNextReset() would have returned
|
|
34
43
|
const SEVEN_DAY_ISO = new Date(1789059600 * 1000).toISOString(); // the actually-binding window
|
|
44
|
+
// Fixed "now" contemporaneous with the fixture's own timestamps (BEFORE both
|
|
45
|
+
// resets above), so freshness checks read the fixture the way it looked at
|
|
46
|
+
// incident time — not relative to whenever this suite happens to run.
|
|
47
|
+
const FIXTURE_NOW = Date.parse('2026-09-05T16:32:00.000Z');
|
|
35
48
|
|
|
36
49
|
test('resolveRateLimitPauseReset: prefers the log-derived seven-day reset over the billing five_hour reset', () => {
|
|
37
|
-
const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO);
|
|
50
|
+
const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO, FIXTURE_NOW);
|
|
38
51
|
assert.strictEqual(resetIso, SEVEN_DAY_ISO);
|
|
39
52
|
assert.notStrictEqual(resetIso, FIVE_HOUR_ISO);
|
|
40
53
|
});
|
|
41
54
|
|
|
42
55
|
test('resolveRateLimitPauseReset: falls back to the billing reset when the log yields none', () => {
|
|
43
|
-
const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO);
|
|
56
|
+
const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO, FIXTURE_NOW);
|
|
44
57
|
assert.strictEqual(resetIso, FIVE_HOUR_ISO);
|
|
45
58
|
});
|
|
46
59
|
|
|
47
60
|
test('resolveRateLimitPauseReset: null when BOTH the log and the billing endpoint are unavailable', () => {
|
|
48
|
-
assert.strictEqual(resolveRateLimitPauseReset('/nonexistent/path/job.log', null), null);
|
|
61
|
+
assert.strictEqual(resolveRateLimitPauseReset('/nonexistent/path/job.log', null, FIXTURE_NOW), null);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('resolveRateLimitPauseReset: a PAST log-derived reset is rejected, not returned stale', () => {
|
|
65
|
+
// "now" is AFTER both fixture resets have already elapsed.
|
|
66
|
+
const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
|
|
67
|
+
const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO, longAfter);
|
|
68
|
+
assert.strictEqual(resetIso, null);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('resolveRateLimitPauseReset: log yields nothing and the billing reset has already passed -> null, not the stale value', () => {
|
|
72
|
+
const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
|
|
73
|
+
const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO, longAfter);
|
|
74
|
+
assert.strictEqual(resetIso, null);
|
|
49
75
|
});
|
|
50
76
|
|
|
51
77
|
test('computeEffectiveResumeAt: rate_limit with no resumeAt gets the same bounded 30-minute fallback as network', () => {
|
|
52
|
-
const now =
|
|
78
|
+
const now = FIXTURE_NOW;
|
|
53
79
|
const resumeAt = computeEffectiveResumeAt('rate_limit', null, now);
|
|
54
80
|
assert.strictEqual(resumeAt, new Date(now + 30 * 60_000).toISOString());
|
|
55
81
|
});
|
|
56
82
|
|
|
57
|
-
test('computeEffectiveResumeAt:
|
|
58
|
-
const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO,
|
|
83
|
+
test('computeEffectiveResumeAt: a FRESH explicit resumeAt (the log-derived reset) is never overridden', () => {
|
|
84
|
+
const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, FIXTURE_NOW);
|
|
59
85
|
assert.strictEqual(resumeAt, SEVEN_DAY_ISO);
|
|
60
86
|
});
|
|
61
87
|
|
|
88
|
+
test('computeEffectiveResumeAt: a PAST resumeAt gets the 30-minute fallback, not a ~30-second nap', () => {
|
|
89
|
+
// now is well AFTER SEVEN_DAY_ISO has already elapsed — the exact shape of
|
|
90
|
+
// the bug: Math.max(30_000, <negative>) used to arm a 30-second resume.
|
|
91
|
+
const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
|
|
92
|
+
const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, longAfter);
|
|
93
|
+
assert.strictEqual(resumeAt, new Date(longAfter + 30 * 60_000).toISOString());
|
|
94
|
+
assert.notStrictEqual(resumeAt, SEVEN_DAY_ISO);
|
|
95
|
+
const { delayMs } = computeResumeDelay(resumeAt, longAfter);
|
|
96
|
+
assert.ok(delayMs >= 30 * 60_000, `expected >= 30 minutes, got ${delayMs}ms`);
|
|
97
|
+
});
|
|
98
|
+
|
|
62
99
|
test('computeEffectiveResumeAt: other reasons (e.g. auth) get no fallback — stays indefinite', () => {
|
|
63
100
|
assert.strictEqual(computeEffectiveResumeAt('auth', null, Date.now()), null);
|
|
64
101
|
});
|
|
65
102
|
|
|
103
|
+
test('billingResetForPause: skips refreshNextReset() (no billing.fetchUsage call) while the shared circuit is OPEN', async () => {
|
|
104
|
+
const originalFetchUsage = billing.fetchUsage;
|
|
105
|
+
let fetchCalls = 0;
|
|
106
|
+
billing.fetchUsage = async () => { fetchCalls++; return { kind: 'ok', data: { usage: {} } }; };
|
|
107
|
+
try {
|
|
108
|
+
// Force the shared circuit open (3 consecutive failures), same threshold usageCircuit.cjs uses.
|
|
109
|
+
billing.usageCircuit.recordFailure('a');
|
|
110
|
+
billing.usageCircuit.recordFailure('a');
|
|
111
|
+
billing.usageCircuit.recordFailure('a');
|
|
112
|
+
assert.strictEqual(billing.usageCircuit.state(), 'open');
|
|
113
|
+
|
|
114
|
+
await billingResetForPause();
|
|
115
|
+
assert.strictEqual(fetchCalls, 0, 'refreshNextReset() must not call billing.fetchUsage() while the circuit is open');
|
|
116
|
+
} finally {
|
|
117
|
+
billing.usageCircuit.recordSuccess({}); // close the shared circuit back up for later tests/files
|
|
118
|
+
billing.fetchUsage = originalFetchUsage;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
66
122
|
test('computeResumeDelay: a seven-day-out reset (~5 days from now) is armed, not treated as too-far', () => {
|
|
67
123
|
const now = Date.parse('2026-09-05T16:32:00.000Z');
|
|
68
124
|
// ~5 days out — comfortably under the 0x7fffffff (~24.8 day) setTimeout cap.
|
|
@@ -27,6 +27,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
|
27
27
|
const fs = require('node:fs');
|
|
28
28
|
const os = require('node:os');
|
|
29
29
|
const path = require('node:path');
|
|
30
|
+
const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
|
|
30
31
|
const { execFileSync } = require('node:child_process');
|
|
31
32
|
|
|
32
33
|
let tmpHome;
|
|
@@ -92,15 +93,12 @@ function writeProjectQueue(cwd, jobs) {
|
|
|
92
93
|
// non-zero almost instantly, and bumps a counter file each invocation so the
|
|
93
94
|
// test can assert exactly how many times it was actually spawned.
|
|
94
95
|
function writeAlways429ClaudeStub(counterPath) {
|
|
95
|
-
|
|
96
|
-
const body = `
|
|
96
|
+
return claudeStub.writeClaudeStub({ body: `
|
|
97
97
|
const fs = require('fs');
|
|
98
98
|
fs.appendFileSync(${JSON.stringify(counterPath)}, 'x');
|
|
99
99
|
process.stdout.write(JSON.stringify({ type: 'result', subtype: 'error', is_error: true, api_error_status: 429 }) + '\\n');
|
|
100
100
|
process.exit(1);
|
|
101
|
-
|
|
102
|
-
fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
|
|
103
|
-
return stubPath;
|
|
101
|
+
` });
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
test('a manual Resume immediately followed by a permanently-429ing job never produces a runaway dispatch loop', async () => {
|