claude-code-session-manager 0.98.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-DCEgMTjH.js → DataModel-HdR0AxFq.js} +1 -1
- package/dist/assets/{History-Bfdoywlh.js → History-Dr4Jr_5z.js} +2 -2
- package/dist/assets/{Hooks-CKDJjTJV.js → Hooks-NhrvH3JP.js} +3 -3
- package/dist/assets/{Library-7akfgy67.js → Library-Dv_EtIMr.js} +1 -1
- package/dist/assets/{MarkdownEditor-DDesiqet.js → MarkdownEditor-DTKDWV_n.js} +1 -1
- package/dist/assets/{McpServers-D4lZtXof.js → McpServers-M034G9B3.js} +2 -2
- package/dist/assets/{Memory-CeI8G1w5.js → Memory-DzF1B4gx.js} +6 -6
- package/dist/assets/{Permissions-BGleLja4.js → Permissions-DrgbSGWl.js} +3 -3
- package/dist/assets/{Plugins-P4eCXy_S.js → Plugins-Bd3ef35w.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BFczzI1B.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
- package/dist/assets/{SaveBar-l9jnEMES.js → SaveBar-rimJem-Y.js} +1 -1
- package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
- package/dist/assets/{ScopeSwitcher-CjJi8ST5.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
- package/dist/assets/Settings-Dc1F_sSg.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CjbO01Sg.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
- package/dist/assets/{Skills-BxZ9_fCR.js → Skills-DRXd18gu.js} +2 -2
- package/dist/assets/{SystemPrompt-D8vfFb8b.js → SystemPrompt-ahy5-Fwe.js} +1 -1
- package/dist/assets/{TagLibrary-2vzUo7YL.js → TagLibrary-C_tToDeL.js} +1 -1
- package/dist/assets/{TiptapBody-B8ZfbK90.js → TiptapBody-DvdefS6K.js} +1 -1
- package/dist/assets/{Toggle-BbW4nGGf.js → Toggle-6Jl5Njc6.js} +1 -1
- package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
- package/dist/assets/{index-Cd98Q1lD.js → index-vx8O73l8.js} +497 -502
- package/dist/assets/{settingsSchema-IhUfucCF.js → settingsSchema-D6iRguNV.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +25 -29
- package/plugins/CLAUDE.md +6 -6
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +21 -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 +121 -12
- 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 +12 -25
- package/src/main/lib/workTypeLibrary.cjs +7 -1
- package/src/main/promptSessionEvents.cjs +37 -13
- package/src/main/scheduler.cjs +910 -198
- 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-CDVHE0hT.js +0 -1
- package/dist/assets/Scheduler-C_phpeUS.js +0 -16
- package/dist/assets/Settings-BIwZ3FGQ.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__/prdCreateAdoption.test.cjs +0 -205
- 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 -211
- 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-mutate-reentrancy.test.cjs +0 -117
- 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 -441
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
- package/src/main/lib/kgExchangePairing.cjs +0 -75
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* queueOpsAutoArchive.test.cjs — unit tests for queueOps.cjs's
|
|
3
|
-
* selectAutoArchivable predicate + autoArchiveCompleted orchestration
|
|
4
|
-
* (auto-archiving completed PRDs' .md files out of the live prds/ dir).
|
|
5
|
-
*
|
|
6
|
-
* selectAutoArchivable is pure (no fs) so it's fully covered here without
|
|
7
|
-
* touching disk. autoArchiveCompleted's disk-mutating path (archiveMany) is
|
|
8
|
-
* mostly exercised only via its kill-switch / no-op branches, since
|
|
9
|
-
* archiveMany's default destination is not test-injectable — except for the
|
|
10
|
-
* two retire-flag tests below, which write into PRDS_DIR (a lazy getter
|
|
11
|
-
* resolved under this run's SM_SCHEDULER_HOME sandbox — see
|
|
12
|
-
* tests/setup/schedulerSandbox.cjs — so they never touch a real
|
|
13
|
-
* ~/.claude/session-manager tree) and stub 'electron' via require.cache so
|
|
14
|
-
* registerQueueOpsHandlers' ipcMain.handle calls are no-ops outside a real
|
|
15
|
-
* Electron process. 'electron' is an externalized node_modules dependency
|
|
16
|
-
* under vitest, so vi.mock('electron', ...) never intercepts queueOps.cjs's
|
|
17
|
-
* own require('electron') — pre-seeding require.cache for its resolved path
|
|
18
|
-
* is the one hook that reaches it.
|
|
19
|
-
*
|
|
20
|
-
* Run: timeout 300 npx vitest run src/main/__tests__/queueOpsAutoArchive.test.cjs
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
'use strict';
|
|
24
|
-
|
|
25
|
-
import { test, expect, afterEach, vi } from 'vitest';
|
|
26
|
-
const fs = require('node:fs');
|
|
27
|
-
const path = require('node:path');
|
|
28
|
-
|
|
29
|
-
const electronPath = require.resolve('electron');
|
|
30
|
-
require.cache[electronPath] = {
|
|
31
|
-
id: electronPath,
|
|
32
|
-
filename: electronPath,
|
|
33
|
-
loaded: true,
|
|
34
|
-
exports: { ipcMain: { handle: () => {} } },
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const RETENTION_MS = 7 * 24 * 60 * 60_000;
|
|
38
|
-
const NOW = Date.parse('2026-07-24T12:00:00.000Z');
|
|
39
|
-
|
|
40
|
-
const { selectAutoArchivable, autoArchiveCompleted, archiveMany, registerQueueOpsHandlers, PRDS_DIR } = require('../queueOps.cjs');
|
|
41
|
-
|
|
42
|
-
function fresh(overrides = {}) {
|
|
43
|
-
return {
|
|
44
|
-
slug: '01-fresh',
|
|
45
|
-
status: 'completed',
|
|
46
|
-
finishedAt: new Date(NOW - 1 * 60 * 60_000).toISOString(), // 1h ago
|
|
47
|
-
...overrides,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function old(overrides = {}) {
|
|
52
|
-
return {
|
|
53
|
-
slug: '02-old',
|
|
54
|
-
status: 'completed',
|
|
55
|
-
finishedAt: new Date(NOW - 10 * 24 * 60 * 60_000).toISOString(), // 10d ago
|
|
56
|
-
...overrides,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
afterEach(() => {
|
|
61
|
-
delete process.env.SM_PRD_AUTOARCHIVE_DISABLE;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// ---------- selectAutoArchivable ----------
|
|
65
|
-
|
|
66
|
-
test('selectAutoArchivable: fresh completed job stays (not old enough)', () => {
|
|
67
|
-
const slugs = selectAutoArchivable([fresh()], { nowMs: NOW });
|
|
68
|
-
expect(slugs).toEqual([]);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('selectAutoArchivable: old completed job archives', () => {
|
|
72
|
-
const slugs = selectAutoArchivable([old()], { nowMs: NOW });
|
|
73
|
-
expect(slugs).toEqual(['02-old']);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('selectAutoArchivable: needs_review jobs never archive, regardless of age', () => {
|
|
77
|
-
const j = old({ slug: '03-review', status: 'needs_review' });
|
|
78
|
-
const slugs = selectAutoArchivable([j], { nowMs: NOW });
|
|
79
|
-
expect(slugs).toEqual([]);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('selectAutoArchivable: failed jobs never archive, regardless of age', () => {
|
|
83
|
-
const j = old({ slug: '04-failed', status: 'failed' });
|
|
84
|
-
const slugs = selectAutoArchivable([j], { nowMs: NOW });
|
|
85
|
-
expect(slugs).toEqual([]);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('selectAutoArchivable: pending jobs never archive', () => {
|
|
89
|
-
const j = old({ slug: '05-pending', status: 'pending', finishedAt: null });
|
|
90
|
-
const slugs = selectAutoArchivable([j], { nowMs: NOW });
|
|
91
|
-
expect(slugs).toEqual([]);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('selectAutoArchivable: running jobs never archive', () => {
|
|
95
|
-
const j = old({ slug: '06-running', status: 'running', finishedAt: null });
|
|
96
|
-
const slugs = selectAutoArchivable([j], { nowMs: NOW });
|
|
97
|
-
expect(slugs).toEqual([]);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('selectAutoArchivable: a completed job with a PENDING NN-fix-<slug> sibling is protected', () => {
|
|
101
|
-
const original = old({ slug: '07-thing', status: 'completed' });
|
|
102
|
-
const fixPlan = { slug: '07-fix-thing', status: 'pending', finishedAt: null };
|
|
103
|
-
const slugs = selectAutoArchivable([original, fixPlan], { nowMs: NOW });
|
|
104
|
-
expect(slugs).toEqual([]);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('selectAutoArchivable: a completed job with a RUNNING NN-fix-<slug> sibling is protected', () => {
|
|
108
|
-
const original = old({ slug: '08-thing', status: 'completed' });
|
|
109
|
-
const fixPlan = { slug: '08-fix-thing', status: 'running', finishedAt: null };
|
|
110
|
-
const slugs = selectAutoArchivable([original, fixPlan], { nowMs: NOW });
|
|
111
|
-
expect(slugs).toEqual([]);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('selectAutoArchivable: a completed job with a COMPLETED fix-plan sibling is free to archive (no longer protected)', () => {
|
|
115
|
-
const original = old({ slug: '09-thing', status: 'completed' });
|
|
116
|
-
const fixPlan = old({ slug: '09-fix-thing', status: 'completed' });
|
|
117
|
-
const slugs = selectAutoArchivable([original, fixPlan], { nowMs: NOW }).sort();
|
|
118
|
-
expect(slugs).toEqual(['09-fix-thing', '09-thing']);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('selectAutoArchivable: job with no/invalid finishedAt never archives even if status completed', () => {
|
|
122
|
-
const j = { slug: '10-nofinish', status: 'completed', finishedAt: null };
|
|
123
|
-
const slugs = selectAutoArchivable([j], { nowMs: NOW });
|
|
124
|
-
expect(slugs).toEqual([]);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('selectAutoArchivable: respects retentionMs override', () => {
|
|
128
|
-
const j = { slug: '11-recent', status: 'completed', finishedAt: new Date(NOW - 2 * 60 * 60_000).toISOString() };
|
|
129
|
-
const slugs = selectAutoArchivable([j], { nowMs: NOW, retentionMs: 60 * 60_000 }); // 1h retention
|
|
130
|
-
expect(slugs).toEqual(['11-recent']);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('selectAutoArchivable: defaults retentionMs to the shared HISTORY_RETENTION_MS constant', () => {
|
|
134
|
-
const j = { slug: '12-boundary', status: 'completed', finishedAt: new Date(NOW - RETENTION_MS - 1).toISOString() };
|
|
135
|
-
const slugs = selectAutoArchivable([j], { nowMs: NOW });
|
|
136
|
-
expect(slugs).toEqual(['12-boundary']);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// ---------- autoArchiveCompleted ----------
|
|
140
|
-
|
|
141
|
-
test('autoArchiveCompleted: kill switch SM_PRD_AUTOARCHIVE_DISABLE=1 skips entirely', async () => {
|
|
142
|
-
process.env.SM_PRD_AUTOARCHIVE_DISABLE = '1';
|
|
143
|
-
const result = await autoArchiveCompleted({ jobs: [old()] }, { nowMs: NOW });
|
|
144
|
-
expect(result.archived).toBe(0);
|
|
145
|
-
expect(result.skipped).toBe('disabled');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('autoArchiveCompleted: no eligible slugs returns a no-op without touching disk', async () => {
|
|
149
|
-
const result = await autoArchiveCompleted({ jobs: [fresh()] }, { nowMs: NOW });
|
|
150
|
-
expect(result.ok).toBe(true);
|
|
151
|
-
expect(result.archived).toBe(0);
|
|
152
|
-
expect(result.archivedTo).toBe(null);
|
|
153
|
-
expect(result.results).toEqual([]);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test('autoArchiveCompleted: tolerates a missing/malformed state.jobs', async () => {
|
|
157
|
-
const result = await autoArchiveCompleted({}, { nowMs: NOW });
|
|
158
|
-
expect(result.archived).toBe(0);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// ---------- archiving never strands a runnable queue job (scheduler PRD:
|
|
162
|
-
// archived-prd-skip — job 822-epics-nav-rename fired ENOENT after its PRD
|
|
163
|
-
// was archived out from under a still-queued job) ----------
|
|
164
|
-
|
|
165
|
-
test('selectAutoArchivable never selects a slug whose job is still pending or running, so auto-archive can never leave a runnable job pointing at an archived PRD', () => {
|
|
166
|
-
const pending = old({ slug: '13-pending', status: 'pending', finishedAt: null });
|
|
167
|
-
const running = old({ slug: '14-running', status: 'running', finishedAt: null });
|
|
168
|
-
const completed = old({ slug: '15-done', status: 'completed' });
|
|
169
|
-
const slugs = selectAutoArchivable([pending, running, completed], { nowMs: NOW });
|
|
170
|
-
expect(slugs).toEqual(['15-done']);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// ---------- retire-flag deadlock guard (PRD 1442: reconcile() -> mutate()
|
|
174
|
-
// body -> autoArchiveCompleted -> archiveMany -> retireCompletedSlugsFn ->
|
|
175
|
-
// mutate() re-entered every tick, so mutateTail never settled) ----------
|
|
176
|
-
|
|
177
|
-
test('autoArchiveCompleted never invokes retireCompletedSlugs', async () => {
|
|
178
|
-
const slug = 'test-auto-archive-no-retire';
|
|
179
|
-
fs.mkdirSync(PRDS_DIR, { recursive: true });
|
|
180
|
-
const src = path.join(PRDS_DIR, `${slug}.md`);
|
|
181
|
-
fs.writeFileSync(src, '# Goal\n\ntest\n', 'utf8');
|
|
182
|
-
|
|
183
|
-
const spy = vi.fn(async () => {});
|
|
184
|
-
registerQueueOpsHandlers({ retireCompletedSlugs: spy });
|
|
185
|
-
|
|
186
|
-
const job = old({ slug, status: 'completed' });
|
|
187
|
-
const result = await autoArchiveCompleted({ jobs: [job] }, { nowMs: NOW });
|
|
188
|
-
|
|
189
|
-
expect(result.archived).toBe(1);
|
|
190
|
-
expect(fs.existsSync(src)).toBe(false);
|
|
191
|
-
expect(result.results[0].ok).toBe(true);
|
|
192
|
-
expect(fs.existsSync(result.results[0].archivedTo)).toBe(true);
|
|
193
|
-
expect(spy).not.toHaveBeenCalled();
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test('manual archiveMany still retires', async () => {
|
|
197
|
-
const slug = 'test-manual-archive-still-retires';
|
|
198
|
-
fs.mkdirSync(PRDS_DIR, { recursive: true });
|
|
199
|
-
const src = path.join(PRDS_DIR, `${slug}.md`);
|
|
200
|
-
fs.writeFileSync(src, '# Goal\n\ntest\n', 'utf8');
|
|
201
|
-
|
|
202
|
-
const spy = vi.fn(async () => {});
|
|
203
|
-
registerQueueOpsHandlers({ retireCompletedSlugs: spy });
|
|
204
|
-
|
|
205
|
-
const result = await archiveMany([slug]);
|
|
206
|
-
|
|
207
|
-
expect(result.archived).toBe(1);
|
|
208
|
-
expect(fs.existsSync(src)).toBe(false);
|
|
209
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
210
|
-
expect(spy).toHaveBeenCalledWith([slug]);
|
|
211
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* rateLimitPollerStreak.test.cjs — covers the fix for the silent
|
|
3
|
-
* consecutiveFailures streak on the usage/rate-limit poller (scheduler.cjs
|
|
4
|
-
* pollLoop): the 'meter_rate_limited' branch used to never back off and
|
|
5
|
-
* never call persistSchedulerState(), letting scheduler-state.json freeze
|
|
6
|
-
* stale while the loop kept failing every POLL_INTERVAL_MS underneath it.
|
|
7
|
-
* This suite covers the pure pieces of the fix: the shared backoff
|
|
8
|
-
* curve/cap (nextBackoffMs), the once-per-streak WARN gate
|
|
9
|
-
* (shouldWarnFailureStreak), and the periodic escalation ladder that fires
|
|
10
|
-
* every FAILURE_STREAK_ESCALATION_MS while a streak PERSISTS past the
|
|
11
|
-
* initial WARN, re-arming once the streak clears (shouldEscalateFailureStreak).
|
|
12
|
-
*
|
|
13
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/rateLimitPollerStreak.test.cjs
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
'use strict';
|
|
17
|
-
|
|
18
|
-
import { test, expect } from 'vitest';
|
|
19
|
-
const {
|
|
20
|
-
nextBackoffMs,
|
|
21
|
-
shouldWarnFailureStreak,
|
|
22
|
-
shouldEscalateFailureStreak,
|
|
23
|
-
restoreFailureStreakWarnedAt,
|
|
24
|
-
persistedStreakMinutes,
|
|
25
|
-
BACKOFF_MAX_MS,
|
|
26
|
-
FAILURE_STREAK_WARN_THRESHOLD,
|
|
27
|
-
FAILURE_STREAK_ESCALATION_MS,
|
|
28
|
-
} = require('../scheduler.cjs');
|
|
29
|
-
|
|
30
|
-
test('nextBackoffMs starts at 30s from a zero/falsy backoff', () => {
|
|
31
|
-
expect(nextBackoffMs(0)).toBe(30_000);
|
|
32
|
-
expect(nextBackoffMs(null)).toBe(30_000);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('nextBackoffMs doubles each call', () => {
|
|
36
|
-
let ms = 0;
|
|
37
|
-
ms = nextBackoffMs(ms); // 30_000
|
|
38
|
-
ms = nextBackoffMs(ms); // 60_000
|
|
39
|
-
ms = nextBackoffMs(ms); // 120_000
|
|
40
|
-
expect(ms).toBe(120_000);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('nextBackoffMs caps at BACKOFF_MAX_MS (8 minutes) and never exceeds it', () => {
|
|
44
|
-
let ms = 0;
|
|
45
|
-
for (let i = 0; i < 20; i++) ms = nextBackoffMs(ms);
|
|
46
|
-
expect(ms).toBe(BACKOFF_MAX_MS);
|
|
47
|
-
expect(BACKOFF_MAX_MS).toBe(480_000);
|
|
48
|
-
// One more doubling from an already-capped value must still be capped, not climb further.
|
|
49
|
-
expect(nextBackoffMs(ms)).toBe(BACKOFF_MAX_MS);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('shouldWarnFailureStreak is false below the threshold', () => {
|
|
53
|
-
expect(shouldWarnFailureStreak(1, false)).toBe(false);
|
|
54
|
-
expect(shouldWarnFailureStreak(FAILURE_STREAK_WARN_THRESHOLD - 1, false)).toBe(false);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('shouldWarnFailureStreak fires exactly once when the threshold is crossed', () => {
|
|
58
|
-
expect(shouldWarnFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, false)).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('shouldWarnFailureStreak does not spam on every subsequent failure in the same streak', () => {
|
|
62
|
-
// Simulate a 57-failure streak: warn fires once, then `alreadyWarned` stays
|
|
63
|
-
// true for every remaining failure — this is the no-repeat property.
|
|
64
|
-
let warned = false;
|
|
65
|
-
let warnCount = 0;
|
|
66
|
-
for (let failures = 1; failures <= 57; failures++) {
|
|
67
|
-
if (shouldWarnFailureStreak(failures, warned)) {
|
|
68
|
-
warned = true;
|
|
69
|
-
warnCount++;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
expect(warnCount).toBe(1);
|
|
73
|
-
expect(warned).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('shouldWarnFailureStreak re-arms after a streak resets (alreadyWarned back to false)', () => {
|
|
77
|
-
expect(shouldWarnFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, false)).toBe(true);
|
|
78
|
-
// A later, independent streak crossing the threshold again must warn again.
|
|
79
|
-
expect(shouldWarnFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, false)).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('shouldWarnFailureStreak respects a custom threshold', () => {
|
|
83
|
-
expect(shouldWarnFailureStreak(3, false, 3)).toBe(true);
|
|
84
|
-
expect(shouldWarnFailureStreak(2, false, 3)).toBe(false);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('shouldEscalateFailureStreak: false below the WARN threshold, regardless of elapsed time', () => {
|
|
88
|
-
expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD - 1, null, Date.now())).toBe(false);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('shouldEscalateFailureStreak: fires immediately the first time (lastEscalatedAtMs null) once past the WARN threshold', () => {
|
|
92
|
-
expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, null, Date.now())).toBe(true);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('shouldEscalateFailureStreak: does not re-fire before FAILURE_STREAK_ESCALATION_MS has elapsed since the last one', () => {
|
|
96
|
-
const lastEscalatedAtMs = 1_000_000;
|
|
97
|
-
const tooSoon = lastEscalatedAtMs + FAILURE_STREAK_ESCALATION_MS - 1;
|
|
98
|
-
expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, lastEscalatedAtMs, tooSoon)).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('shouldEscalateFailureStreak: fires again once FAILURE_STREAK_ESCALATION_MS has elapsed — the persisting-streak ladder', () => {
|
|
102
|
-
const lastEscalatedAtMs = 1_000_000;
|
|
103
|
-
const dueNow = lastEscalatedAtMs + FAILURE_STREAK_ESCALATION_MS;
|
|
104
|
-
expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, lastEscalatedAtMs, dueNow)).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('shouldEscalateFailureStreak: re-arms after a streak clears (lastEscalatedAtMs reset to null) — a later independent streak escalates again', () => {
|
|
108
|
-
const firstStreakNow = 1_000_000;
|
|
109
|
-
expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, null, firstStreakNow)).toBe(true);
|
|
110
|
-
// Streak clears (a success resets both failureStreakWarned and the
|
|
111
|
-
// escalation timestamp) — a LATER, independent streak crossing the WARN
|
|
112
|
-
// threshold again must escalate again immediately, not wait out a stale
|
|
113
|
-
// 30-minute window from the previous incident.
|
|
114
|
-
const laterStreakNow = firstStreakNow + 5 * 60_000; // well under 30 min later
|
|
115
|
-
expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, null, laterStreakNow)).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('restoreFailureStreakWarnedAt backfills now when warned=true has no numeric timestamp', () => {
|
|
119
|
-
expect(restoreFailureStreakWarnedAt(true, null, 5000)).toBe(5000);
|
|
120
|
-
expect(restoreFailureStreakWarnedAt(true, 1000, 5000)).toBe(1000);
|
|
121
|
-
expect(restoreFailureStreakWarnedAt(false, null, 5000)).toBeNull();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('persistedStreakMinutes is always a number, never null', () => {
|
|
125
|
-
expect(persistedStreakMinutes(null, 5000)).toBe(0);
|
|
126
|
-
expect(persistedStreakMinutes(0, 120_000)).toBe(2);
|
|
127
|
-
expect(persistedStreakMinutes(restoreFailureStreakWarnedAt(true, undefined, 5000), 5000)).toBe(0);
|
|
128
|
-
});
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* rcaReport.test.cjs — unit tests for src/main/lib/rcaReport.cjs.
|
|
3
|
-
*
|
|
4
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/rcaReport.test.cjs
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
import { test, expect, beforeEach, afterEach } from 'vitest';
|
|
10
|
-
const fs = require('node:fs');
|
|
11
|
-
const os = require('node:os');
|
|
12
|
-
const path = require('node:path');
|
|
13
|
-
|
|
14
|
-
let tmpHome;
|
|
15
|
-
let realHome;
|
|
16
|
-
let rcaReport;
|
|
17
|
-
|
|
18
|
-
// config.cjs bakes os.homedir() into top-level consts at first require
|
|
19
|
-
// (allowedRoots/WRITE_PREFIXES). Node's require cache must be purged per test
|
|
20
|
-
// so each test's tmpHome takes effect — vi.resetModules() only resets vitest's
|
|
21
|
-
// ESM graph, not require().
|
|
22
|
-
const MODULES_TO_RELOAD = ['../lib/rcaReport.cjs', '../config.cjs'];
|
|
23
|
-
|
|
24
|
-
function purgeRequireCache() {
|
|
25
|
-
for (const m of MODULES_TO_RELOAD) {
|
|
26
|
-
try { delete require.cache[require.resolve(m)]; } catch { /* not loaded yet */ }
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
realHome = process.env.HOME;
|
|
32
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'rca-report-test-'));
|
|
33
|
-
process.env.HOME = tmpHome;
|
|
34
|
-
delete process.env.SM_RCA_DISABLE;
|
|
35
|
-
purgeRequireCache();
|
|
36
|
-
rcaReport = require('../lib/rcaReport.cjs');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
afterEach(() => {
|
|
40
|
-
process.env.HOME = realHome;
|
|
41
|
-
delete process.env.SM_RCA_DISABLE;
|
|
42
|
-
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
43
|
-
purgeRequireCache();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
function makeProject(name = 'proj') {
|
|
49
|
-
const cwd = path.join(tmpHome, name);
|
|
50
|
-
fs.mkdirSync(cwd, { recursive: true });
|
|
51
|
-
return cwd;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function writeRun(runDir, slug, { logLines = ['line1', 'line2'], exitCode = 1, durationMs = 1234 } = {}) {
|
|
55
|
-
fs.mkdirSync(runDir, { recursive: true });
|
|
56
|
-
fs.writeFileSync(path.join(runDir, `${slug}.log`), logLines.join('\n') + '\n');
|
|
57
|
-
fs.writeFileSync(path.join(runDir, `${slug}.meta.json`), JSON.stringify({ exitCode, durationMs }));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function writePrd(cwd, slug, { acBody = '- [ ] `timeout 10 echo ok` passes.' } = {}) {
|
|
61
|
-
const prdsDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'prds');
|
|
62
|
-
fs.mkdirSync(prdsDir, { recursive: true });
|
|
63
|
-
const body = ['---', `title: ${slug}`, '---', '', '# Goal', '', 'Do the thing.', '', '# Acceptance criteria', '', acBody, '', '# Out of scope', '', '- N/A'].join('\n');
|
|
64
|
-
fs.writeFileSync(path.join(prdsDir, `${slug}.md`), body);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function baseJob(overrides = {}) {
|
|
68
|
-
return { slug: 'testslug', runId: '2026-07-24T00-00-00-000Z', exitCode: 1, error: 'something broke', ...overrides };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function readSessions(cwd) {
|
|
72
|
-
const p = path.join(cwd, 'session-manager-operations', 'prompt-sessions', 'active-index.json');
|
|
73
|
-
if (!fs.existsSync(p)) return {};
|
|
74
|
-
return JSON.parse(fs.readFileSync(p, 'utf8')).sessions ?? {};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ─── report writing ──────────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
test('writeRcaReport: writes root-cause-<slug>.md into the run directory', async () => {
|
|
80
|
-
const cwd = makeProject();
|
|
81
|
-
const runDir = path.join(tmpHome, 'run1');
|
|
82
|
-
writeRun(runDir, 'testslug');
|
|
83
|
-
writePrd(cwd, 'testslug');
|
|
84
|
-
|
|
85
|
-
const res = await rcaReport.writeRcaReport({ job: baseJob({ cwd }), runDir, verdict: 'transcript_errors' });
|
|
86
|
-
|
|
87
|
-
expect(res.filed).toBe(true);
|
|
88
|
-
expect(res.path).toBe(path.join(runDir, 'root-cause-testslug.md'));
|
|
89
|
-
const body = fs.readFileSync(res.path, 'utf8');
|
|
90
|
-
expect(body).toContain('prdSlug: testslug');
|
|
91
|
-
expect(body).toContain('# What happened');
|
|
92
|
-
expect(body).toContain('# The PRD\'s acceptance criteria');
|
|
93
|
-
expect(body).toContain('timeout 10 echo ok');
|
|
94
|
-
expect(res.summary).toContain('testslug');
|
|
95
|
-
expect(res.summary.split('\n')).toHaveLength(1);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('writeRcaReport: creates no Epic — a parked job never opens work on its own', async () => {
|
|
99
|
-
const cwd = makeProject();
|
|
100
|
-
const runDir = path.join(tmpHome, 'run1');
|
|
101
|
-
writeRun(runDir, 'testslug');
|
|
102
|
-
writePrd(cwd, 'testslug');
|
|
103
|
-
|
|
104
|
-
await rcaReport.writeRcaReport({ job: baseJob({ cwd }), runDir, verdict: 'transcript_errors' });
|
|
105
|
-
|
|
106
|
-
expect(readSessions(cwd)).toEqual({});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('writeRcaReport: a second call for the same run overwrites in place, never duplicates', async () => {
|
|
110
|
-
const cwd = makeProject();
|
|
111
|
-
const runDir = path.join(tmpHome, 'run1');
|
|
112
|
-
writeRun(runDir, 'testslug');
|
|
113
|
-
writePrd(cwd, 'testslug');
|
|
114
|
-
const job = baseJob({ cwd });
|
|
115
|
-
|
|
116
|
-
const first = await rcaReport.writeRcaReport({ job, runDir, verdict: 'transcript_errors' });
|
|
117
|
-
const second = await rcaReport.writeRcaReport({ job, runDir, verdict: 'transcript_errors' });
|
|
118
|
-
|
|
119
|
-
expect(second.path).toBe(first.path);
|
|
120
|
-
expect(fs.readdirSync(runDir).filter((f) => f.startsWith('root-cause-'))).toHaveLength(1);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('writeRcaReport: investigationText enriches the report in place', async () => {
|
|
124
|
-
const cwd = makeProject();
|
|
125
|
-
const runDir = path.join(tmpHome, 'run1');
|
|
126
|
-
writeRun(runDir, 'testslug');
|
|
127
|
-
writePrd(cwd, 'testslug');
|
|
128
|
-
const job = baseJob({ cwd });
|
|
129
|
-
|
|
130
|
-
const first = await rcaReport.writeRcaReport({ job, runDir, verdict: 'transcript_errors' });
|
|
131
|
-
expect(fs.readFileSync(first.path, 'utf8')).not.toContain('## Investigation analysis');
|
|
132
|
-
|
|
133
|
-
const second = await rcaReport.writeRcaReport({
|
|
134
|
-
job, runDir, verdict: 'transcript_errors', investigationText: 'Root cause: forgot to bound the poll loop.',
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
expect(second.path).toBe(first.path);
|
|
138
|
-
const enriched = fs.readFileSync(second.path, 'utf8');
|
|
139
|
-
expect(enriched).toContain('## Investigation analysis');
|
|
140
|
-
expect(enriched).toContain('forgot to bound the poll loop');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('writeRcaReport: works for a job whose PRD cannot be read (no acceptance criteria on record)', async () => {
|
|
144
|
-
const cwd = makeProject('bare-proj');
|
|
145
|
-
const runDir = path.join(tmpHome, 'run1');
|
|
146
|
-
writeRun(runDir, 'testslug');
|
|
147
|
-
|
|
148
|
-
const res = await rcaReport.writeRcaReport({ job: baseJob({ cwd }), runDir, verdict: 'transcript_errors' });
|
|
149
|
-
|
|
150
|
-
expect(res.filed).toBe(true);
|
|
151
|
-
expect(fs.readFileSync(res.path, 'utf8')).toContain('acceptance criteria not found');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// ─── skip paths ──────────────────────────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
test('writeRcaReport: rejects a slug containing path-traversal segments', async () => {
|
|
157
|
-
const cwd = makeProject();
|
|
158
|
-
const runDir = path.join(tmpHome, 'run1');
|
|
159
|
-
fs.mkdirSync(runDir, { recursive: true });
|
|
160
|
-
|
|
161
|
-
const res = await rcaReport.writeRcaReport({ job: baseJob({ cwd, slug: '../../etc/passwd' }), runDir, verdict: 'transcript_errors' });
|
|
162
|
-
|
|
163
|
-
expect(res.filed).toBe(false);
|
|
164
|
-
expect(res.reason).toBe('unsafe-slug');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test('writeRcaReport: skips when there is no run directory to write into', async () => {
|
|
168
|
-
const cwd = makeProject();
|
|
169
|
-
|
|
170
|
-
const res = await rcaReport.writeRcaReport({ job: baseJob({ cwd }), verdict: 'transcript_errors' });
|
|
171
|
-
|
|
172
|
-
expect(res.filed).toBe(false);
|
|
173
|
-
expect(res.reason).toBe('no-run-dir');
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// ─── recovery actions ────────────────────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
test('RECOVERY_ACTIONS: every FAILURE_CLASSES member has an entry from the closed set', () => {
|
|
179
|
-
const CLOSED_SET = new Set(['archive', 'resume-and-commit', 'verify-and-close', 'investigate']);
|
|
180
|
-
for (const failureClass of Object.values(rcaReport.FAILURE_CLASSES)) {
|
|
181
|
-
expect(rcaReport.RECOVERY_ACTIONS).toHaveProperty(failureClass);
|
|
182
|
-
expect(CLOSED_SET.has(rcaReport.RECOVERY_ACTIONS[failureClass])).toBe(true);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('recoveryActionFor: maps each known failure class to its expected action', () => {
|
|
187
|
-
const { FAILURE_CLASSES, recoveryActionFor } = rcaReport;
|
|
188
|
-
expect(recoveryActionFor(FAILURE_CLASSES.ALREADY_SHIPPED)).toBe('archive');
|
|
189
|
-
expect(recoveryActionFor(FAILURE_CLASSES.UNCOMMITTED)).toBe('resume-and-commit');
|
|
190
|
-
expect(recoveryActionFor(FAILURE_CLASSES.NO_SENTINEL)).toBe('verify-and-close');
|
|
191
|
-
expect(recoveryActionFor(FAILURE_CLASSES.SELF_QUEUE)).toBe('investigate');
|
|
192
|
-
expect(recoveryActionFor(FAILURE_CLASSES.STUCK_LOOP)).toBe('investigate');
|
|
193
|
-
expect(recoveryActionFor(FAILURE_CLASSES.POST_AC_OVERRUN)).toBe('investigate');
|
|
194
|
-
expect(recoveryActionFor(FAILURE_CLASSES.TRANSCRIPT_ERRORS)).toBe('investigate');
|
|
195
|
-
expect(recoveryActionFor(FAILURE_CLASSES.UNKNOWN)).toBe('investigate');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test('recoveryActionFor: defaults to investigate for an unrecognised class', () => {
|
|
199
|
-
expect(rcaReport.recoveryActionFor('totally-made-up-class')).toBe('investigate');
|
|
200
|
-
expect(rcaReport.recoveryActionFor(undefined)).toBe('investigate');
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test('writeRcaReport: markdown carries a machine-readable recovery-action line matching recoveryActionFor, for each failure class', async () => {
|
|
204
|
-
const cwd = makeProject();
|
|
205
|
-
const runDir = path.join(tmpHome, 'run1');
|
|
206
|
-
writeRun(runDir, 'testslug');
|
|
207
|
-
writePrd(cwd, 'testslug');
|
|
208
|
-
|
|
209
|
-
const cases = [
|
|
210
|
-
{ verdict: 'pass_no_commit', logTail: 'The work was already implemented in a prior commit.', expectedClass: rcaReport.FAILURE_CLASSES.ALREADY_SHIPPED },
|
|
211
|
-
{ verdict: 'transcript_errors', logTail: 'Launching skill: session-manager-dev:develop', expectedClass: rcaReport.FAILURE_CLASSES.SELF_QUEUE },
|
|
212
|
-
{ verdict: 'transcript_errors', logTail: 'while true; do sleep 1; done', expectedClass: rcaReport.FAILURE_CLASSES.STUCK_LOOP },
|
|
213
|
-
{ verdict: 'uncommitted_changes', logTail: 'plain log tail', expectedClass: rcaReport.FAILURE_CLASSES.UNCOMMITTED },
|
|
214
|
-
{ verdict: 'no_verdict_sentinel', logTail: 'plain log tail', expectedClass: rcaReport.FAILURE_CLASSES.NO_SENTINEL },
|
|
215
|
-
{ verdict: 'transcript_errors', logTail: 'plain log tail', expectedClass: rcaReport.FAILURE_CLASSES.TRANSCRIPT_ERRORS },
|
|
216
|
-
{ verdict: 'some_other_verdict', logTail: 'plain log tail', expectedClass: rcaReport.FAILURE_CLASSES.UNKNOWN },
|
|
217
|
-
];
|
|
218
|
-
|
|
219
|
-
for (const { verdict, logTail, expectedClass } of cases) {
|
|
220
|
-
writeRun(runDir, 'testslug', { logLines: logTail.split('\n') });
|
|
221
|
-
const res = await rcaReport.writeRcaReport({ job: baseJob({ cwd }), runDir, verdict });
|
|
222
|
-
expect(res.failureClass).toBe(expectedClass);
|
|
223
|
-
const expectedAction = rcaReport.recoveryActionFor(expectedClass);
|
|
224
|
-
expect(res.recoveryAction).toBe(expectedAction);
|
|
225
|
-
const body = fs.readFileSync(res.path, 'utf8');
|
|
226
|
-
expect(body).toContain(`recovery-action: ${expectedAction}`);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test('writeRcaReport: SM_RCA_DISABLE=1 skips without writing', async () => {
|
|
231
|
-
process.env.SM_RCA_DISABLE = '1';
|
|
232
|
-
const cwd = makeProject();
|
|
233
|
-
const runDir = path.join(tmpHome, 'run1');
|
|
234
|
-
writeRun(runDir, 'testslug');
|
|
235
|
-
writePrd(cwd, 'testslug');
|
|
236
|
-
|
|
237
|
-
const res = await rcaReport.writeRcaReport({ job: baseJob({ cwd }), runDir, verdict: 'transcript_errors' });
|
|
238
|
-
|
|
239
|
-
expect(res.filed).toBe(false);
|
|
240
|
-
expect(res.reason).toBe('disabled');
|
|
241
|
-
expect(fs.readdirSync(runDir).filter((f) => f.startsWith('root-cause-'))).toHaveLength(0);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test('classifyFailure: blocked_by_foreign_wip_streak verdict classifies as BLOCKED_BY_FOREIGN_WIP, never UNKNOWN', () => {
|
|
245
|
-
const failureClass = rcaReport.classifyFailure({ verdict: 'blocked_by_foreign_wip_streak', logTail: 'anything' });
|
|
246
|
-
expect(failureClass).toBe(rcaReport.FAILURE_CLASSES.BLOCKED_BY_FOREIGN_WIP);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test('writeRcaReport: a blocked_by_foreign_wip_streak verdict points at the sibling job, not "re-run the gate"', async () => {
|
|
250
|
-
const cwd = makeProject();
|
|
251
|
-
const runDir = path.join(tmpHome, 'run-blocked');
|
|
252
|
-
writeRun(runDir, 'blockedslug');
|
|
253
|
-
writePrd(cwd, 'blockedslug');
|
|
254
|
-
|
|
255
|
-
const res = await rcaReport.writeRcaReport({
|
|
256
|
-
job: baseJob({ cwd, slug: 'blockedslug' }),
|
|
257
|
-
runDir,
|
|
258
|
-
verdict: 'blocked_by_foreign_wip_streak',
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
expect(res.filed).toBe(true);
|
|
262
|
-
expect(res.failureClass).toBe(rcaReport.FAILURE_CLASSES.BLOCKED_BY_FOREIGN_WIP);
|
|
263
|
-
const body = fs.readFileSync(res.path, 'utf8');
|
|
264
|
-
expect(body).toMatch(/sibling job/i);
|
|
265
|
-
expect(body).not.toMatch(/Re-run the acceptance criteria gate/);
|
|
266
|
-
});
|