claude-code-session-manager 0.97.0 → 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/cli.cjs +16 -4
- package/bin/node-floor.cjs +41 -0
- package/dist/assets/{DataModel-D93FoaOm.js → DataModel-HdR0AxFq.js} +1 -1
- package/dist/assets/{History-CXxxqNP1.js → History-Dr4Jr_5z.js} +2 -2
- package/dist/assets/{Hooks-CzeYFQjq.js → Hooks-NhrvH3JP.js} +3 -3
- package/dist/assets/{Library-D2zPkwOC.js → Library-Dv_EtIMr.js} +1 -1
- package/dist/assets/{MarkdownEditor-DbinP78A.js → MarkdownEditor-DTKDWV_n.js} +1 -1
- package/dist/assets/{McpServers-Bu_sVGLr.js → McpServers-M034G9B3.js} +2 -2
- package/dist/assets/{Memory-BgiI6LZ_.js → Memory-DzF1B4gx.js} +6 -6
- package/dist/assets/{Permissions-C1MMzZRm.js → Permissions-DrgbSGWl.js} +3 -3
- package/dist/assets/{Plugins-Dhu6wAZi.js → Plugins-Bd3ef35w.js} +2 -2
- package/dist/assets/{ProvenanceBadge-C3AcWKMG.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
- package/dist/assets/{SaveBar-plwYm0iO.js → SaveBar-rimJem-Y.js} +1 -1
- package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
- package/dist/assets/{ScopeSwitcher-DFy0vv_o.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
- package/dist/assets/Settings-Dc1F_sSg.js +3 -0
- package/dist/assets/{SkillReferenceGraph-DsI7hRPB.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
- package/dist/assets/{Skills-3E748UfB.js → Skills-DRXd18gu.js} +2 -2
- package/dist/assets/{SystemPrompt-BqmfUJOd.js → SystemPrompt-ahy5-Fwe.js} +1 -1
- package/dist/assets/{TagLibrary-CPz6fO8Q.js → TagLibrary-C_tToDeL.js} +1 -1
- package/dist/assets/{TiptapBody-D9EoBQ1P.js → TiptapBody-DvdefS6K.js} +1 -1
- package/dist/assets/{Toggle-BHAmZNDn.js → Toggle-6Jl5Njc6.js} +1 -1
- package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
- package/dist/assets/{index-tn5JIVPj.js → index-vx8O73l8.js} +497 -502
- package/dist/assets/{settingsSchema-Crqy_aNz.js → settingsSchema-D6iRguNV.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +26 -29
- package/plugins/CLAUDE.md +6 -6
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +23 -19
- package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
- package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
- package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
- package/scripts/README.md +3 -11
- package/scripts/audit-ops-hygiene.cjs +3 -3
- package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
- package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
- package/scripts/mint-epic.cjs +2 -3
- package/scripts/scheduler-mcp-server.cjs +36 -7
- package/src/main/agentLibrary.cjs +22 -2
- package/src/main/build-info.json +4 -4
- package/src/main/config.cjs +41 -38
- package/src/main/docEdit.cjs +4 -1
- package/src/main/files.cjs +2 -5
- package/src/main/health.cjs +1 -1
- package/src/main/index.cjs +23 -10
- package/src/main/ipcSchemas.cjs +28 -35
- package/src/main/lib/agentPersonaSchema.cjs +13 -2
- package/src/main/lib/atomicFs.cjs +116 -0
- package/src/main/lib/branchSweep.cjs +13 -12
- package/src/main/lib/buildTarget.cjs +3 -3
- package/src/main/lib/claudeCliCaps.cjs +129 -0
- package/src/main/lib/credentials.cjs +2 -4
- package/src/main/lib/crossProjectFeedback.cjs +3 -3
- package/src/main/lib/cwdClassify.cjs +15 -3
- package/src/main/lib/definitionOfDone.cjs +207 -125
- package/src/main/lib/dodDrainHook.cjs +11 -5
- package/src/main/lib/epicMint.cjs +2 -4
- package/src/main/lib/epicStatusMirror.cjs +8 -10
- package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
- package/src/main/lib/gateAuthority.cjs +96 -0
- package/src/main/lib/gitExec.cjs +69 -0
- package/src/main/lib/gitWorktree.cjs +656 -92
- package/src/main/lib/instanceLock.cjs +5 -8
- package/src/main/lib/launchFailure.cjs +2 -3
- package/src/main/lib/macroLibrary.cjs +386 -0
- package/src/main/lib/mcpToolCatalog.cjs +26 -17
- package/src/main/lib/needsReviewLedger.cjs +1 -1
- package/src/main/lib/opsErrorLog.cjs +11 -1
- package/src/main/lib/opsOwnership.cjs +0 -5
- package/src/main/lib/pidAlive.cjs +32 -0
- package/src/main/lib/prdCreate.cjs +153 -19
- package/src/main/lib/prdDisposition.cjs +2 -2
- package/src/main/lib/prdGateFiles.cjs +284 -0
- package/src/main/lib/prdLocations.cjs +61 -0
- package/src/main/lib/prdMigration.cjs +2 -3
- package/src/main/lib/prdSizing.cjs +2 -1
- package/src/main/lib/promptSessionSchema.cjs +10 -2
- package/src/main/lib/rcaReport.cjs +4 -6
- package/src/main/lib/reaperHelpers.cjs +8 -1
- package/src/main/lib/reservationExpiry.cjs +6 -1
- package/src/main/lib/reviewNotice.cjs +263 -0
- package/src/main/lib/runClaudeP.cjs +4 -1
- package/src/main/lib/schedulerPaths.cjs +22 -2
- package/src/main/lib/sessionSlots.cjs +2 -4
- package/src/main/lib/shippedPersonaSeeds.cjs +179 -0
- package/src/main/lib/timeoutShim.cjs +123 -0
- package/src/main/lib/timeoutShimScript.cjs +275 -0
- package/src/main/lib/upgradeDrain.cjs +4 -10
- package/src/main/lib/watchdogHelpers.cjs +33 -30
- package/src/main/lib/workTypeLibrary.cjs +7 -1
- package/src/main/promptSessionEvents.cjs +37 -13
- package/src/main/queueOps.cjs +17 -6
- package/src/main/scheduler.cjs +975 -203
- package/src/main/seedAgentPersonas.cjs +278 -23
- package/src/main/supervisor.cjs +4 -1
- package/src/main/templates/PRD_AUTHORING.md +126 -406
- package/src/preload/__tests__/preload-surface.test.cjs +68 -0
- package/src/preload/api.d.ts +41 -94
- package/src/preload/index.cjs +14 -9
- package/src/seed/agents/architect.md +1 -0
- package/src/seed/agents/dev-lead.md +17 -18
- package/src/seed/agents/project-home-builder.md +1 -0
- package/src/seed/agents/validator.md +10 -4
- package/dist/assets/HostBilko-nESrTRIg.js +0 -1
- package/dist/assets/Scheduler-BVYTeh39.js +0 -16
- package/dist/assets/Settings-DIhGgdfs.js +0 -3
- package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
- package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
- package/src/main/__tests__/agentLibrary.test.cjs +0 -276
- package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
- package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
- package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
- package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
- package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
- package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
- package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
- package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
- package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
- package/src/main/__tests__/chat-queue.test.cjs +0 -97
- package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
- package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
- package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
- package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
- package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
- package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
- package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
- package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
- package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
- package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
- package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
- package/src/main/__tests__/docEdit.test.cjs +0 -244
- package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
- package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
- package/src/main/__tests__/dod-report.test.cjs +0 -304
- package/src/main/__tests__/dod-reverify.test.cjs +0 -285
- package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
- package/src/main/__tests__/epicMint.test.cjs +0 -332
- package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
- package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
- package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
- package/src/main/__tests__/exchanges.test.cjs +0 -122
- package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
- package/src/main/__tests__/extractJson.test.cjs +0 -51
- package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
- package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
- package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
- package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
- package/src/main/__tests__/health-credentials.test.cjs +0 -81
- package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
- package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
- package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
- package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
- package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
- package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
- package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
- package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
- package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
- package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
- package/src/main/__tests__/historyDashboard.test.cjs +0 -163
- package/src/main/__tests__/historyRollup.test.cjs +0 -333
- package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
- package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
- package/src/main/__tests__/kg-augment.test.cjs +0 -195
- package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
- package/src/main/__tests__/machineProfile.test.cjs +0 -152
- package/src/main/__tests__/mcpStatus.test.cjs +0 -61
- package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
- package/src/main/__tests__/memoryStale.test.cjs +0 -88
- package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
- package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
- package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
- package/src/main/__tests__/personaMerge.test.cjs +0 -169
- package/src/main/__tests__/planValidator.test.cjs +0 -125
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
- package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
- package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
- package/src/main/__tests__/prdAgentType.test.cjs +0 -103
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
- package/src/main/__tests__/prdCreate.test.cjs +0 -979
- package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
- package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
- package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
- package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
- package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
- package/src/main/__tests__/prdLocations.test.cjs +0 -195
- package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
- package/src/main/__tests__/prdMigration.test.cjs +0 -349
- package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
- package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
- package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
- package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
- package/src/main/__tests__/prdSizing.test.cjs +0 -106
- package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
- package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
- package/src/main/__tests__/proc-role-env.test.cjs +0 -125
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
- package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
- package/src/main/__tests__/projectPages.test.cjs +0 -137
- package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
- package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
- package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
- package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
- package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
- package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
- package/src/main/__tests__/pty-write-result.test.cjs +0 -46
- package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
- package/src/main/__tests__/queueHistory.test.cjs +0 -355
- package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
- package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
- package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -154
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
- package/src/main/__tests__/rcaReport.test.cjs +0 -266
- package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
- package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
- package/src/main/__tests__/runLogRetention.test.cjs +0 -489
- package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
- package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
- package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
- package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
- package/src/main/__tests__/runVerify.test.cjs +0 -1784
- package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
- package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
- package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
- package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
- package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
- package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
- package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
- package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
- package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
- package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
- package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
- package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
- package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
- package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
- package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
- package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
- package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
- package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
- package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
- package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
- package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
- package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
- package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
- package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
- package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
- package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
- package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
- package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
- package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
- package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
- package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
- package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
- package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
- package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
- package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
- package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
- package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
- package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
- package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
- package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
- package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
- package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
- package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
- package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
- package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
- package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
- package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
- package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
- package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
- package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
- package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
- package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
- package/src/main/__tests__/seedStatus.test.cjs +0 -100
- package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
- package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
- package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
- package/src/main/__tests__/telemetryContract.test.cjs +0 -930
- package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
- package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
- package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
- package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
- package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
- package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
- package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
- package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
- package/src/main/__tests__/validationSentinels.test.cjs +0 -84
- package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
- package/src/main/bilkoHost.cjs +0 -314
- package/src/main/bilkoHostCore.cjs +0 -89
- package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
- package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
- package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
- package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
- package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
- package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
- package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
- package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
- package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
- package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
- package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
- package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
- package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
- package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
- package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
- package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
- package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
- package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
- package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
- package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
- package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
- package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
- package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
- package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
- package/src/main/lib/__tests__/gateFixtures.json +0 -20
- package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
- package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
- package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
- package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
- package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
- package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
- package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
- package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
- package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
- package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
- package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
- package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
- package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
- package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
- package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
- package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
- package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
- package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
- package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
- package/src/main/lib/__tests__/procName.test.cjs +0 -92
- package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
- package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
- package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
- package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
- package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
- package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
- package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
- package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
- package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
- package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
- package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
- package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
- package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
- package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
- package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
- package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
- package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
- package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
- package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
- package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
- package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -375
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
- package/src/main/lib/kgExchangePairing.cjs +0 -75
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* runLogRetention.test.cjs — unit tests for src/main/lib/runLogRetention.cjs.
|
|
3
|
-
*
|
|
4
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/runLogRetention.test.cjs
|
|
5
|
-
*
|
|
6
|
-
* Fixtures live under os.tmpdir() only — never the real
|
|
7
|
-
* ~/.claude/session-manager/scheduled-plans/runs.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
'use strict';
|
|
11
|
-
|
|
12
|
-
import { test, expect, beforeEach, afterEach } from 'vitest';
|
|
13
|
-
const fs = require('node:fs');
|
|
14
|
-
const os = require('node:os');
|
|
15
|
-
const path = require('node:path');
|
|
16
|
-
const {
|
|
17
|
-
scanRunEntries,
|
|
18
|
-
computeEligibility,
|
|
19
|
-
computeReport,
|
|
20
|
-
isRetentionEnabled,
|
|
21
|
-
applyRetention,
|
|
22
|
-
liveKeysFromJobs,
|
|
23
|
-
runningRunIdsFromJobs,
|
|
24
|
-
isLiveJob,
|
|
25
|
-
runBootSweep,
|
|
26
|
-
} = require('../lib/runLogRetention.cjs');
|
|
27
|
-
|
|
28
|
-
let runsDir;
|
|
29
|
-
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
runsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-retention-test-'));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
afterEach(() => {
|
|
35
|
-
fs.rmSync(runsDir, { recursive: true, force: true });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
39
|
-
|
|
40
|
-
function makeRun(runId, slug, { finishedAt, extraFiles = [] } = {}) {
|
|
41
|
-
const dir = path.join(runsDir, runId);
|
|
42
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
-
fs.writeFileSync(path.join(dir, `${slug}.log`), 'log output\n');
|
|
44
|
-
fs.writeFileSync(
|
|
45
|
-
path.join(dir, `${slug}.meta.json`),
|
|
46
|
-
JSON.stringify({ slug, exitCode: 0, finishedAt })
|
|
47
|
-
);
|
|
48
|
-
for (const name of extraFiles) {
|
|
49
|
-
fs.writeFileSync(path.join(dir, name), 'extra\n');
|
|
50
|
-
}
|
|
51
|
-
return dir;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ─── scanRunEntries ─────────────────────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
test('scanRunEntries: returns [] for a missing runsDir (never throws)', () => {
|
|
57
|
-
const missing = path.join(runsDir, 'does-not-exist');
|
|
58
|
-
expect(scanRunEntries(missing)).toEqual([]);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('scanRunEntries: finds one entry per slug, even when a dir holds many slugs', () => {
|
|
62
|
-
makeRun('2026-01-01T00-00-00-000Z', 'a-slug', { finishedAt: Date.now() });
|
|
63
|
-
const dir = path.join(runsDir, '2026-01-01T00-00-00-000Z');
|
|
64
|
-
fs.writeFileSync(path.join(dir, 'b-slug.log'), 'log\n');
|
|
65
|
-
fs.writeFileSync(path.join(dir, 'b-slug.meta.json'), JSON.stringify({ finishedAt: Date.now() }));
|
|
66
|
-
|
|
67
|
-
const entries = scanRunEntries(runsDir);
|
|
68
|
-
expect(entries.map((e) => e.slug).sort()).toEqual(['a-slug', 'b-slug']);
|
|
69
|
-
expect(entries.every((e) => e.files.length === 2)).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// ─── default is dry-run: nothing removed with no opt-in ────────────────────
|
|
73
|
-
|
|
74
|
-
test('applyRetention: DEFAULT (no settings) is dry-run — deletes nothing, regardless of how old the runs are', () => {
|
|
75
|
-
const now = Date.now();
|
|
76
|
-
makeRun('2020-01-01T00-00-00-000Z', 'old-slug', { finishedAt: now - 400 * DAY_MS });
|
|
77
|
-
makeRun('2020-06-01T00-00-00-000Z', 'old-slug', { finishedAt: now - 300 * DAY_MS });
|
|
78
|
-
|
|
79
|
-
const result = applyRetention(runsDir, undefined, { now });
|
|
80
|
-
expect(result.deleted).toBe(false);
|
|
81
|
-
expect(result.removedFiles).toBeUndefined();
|
|
82
|
-
expect(fs.readdirSync(runsDir).length).toBe(2);
|
|
83
|
-
|
|
84
|
-
// The same data WOULD flag one eligible run once a real policy is computed
|
|
85
|
-
// (this is what the human-facing report is built from) — proves the dry
|
|
86
|
-
// run above wasn't a no-op because nothing was ever eligible.
|
|
87
|
-
const withPolicy = computeReport(runsDir, { maxAgeDays: 30 }, { now });
|
|
88
|
-
expect(withPolicy.eligible.length).toBeGreaterThan(0);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('applyRetention: enabled=false is still dry-run regardless of policy', () => {
|
|
92
|
-
const now = Date.now();
|
|
93
|
-
makeRun('2020-01-01T00-00-00-000Z', 'old-slug', { finishedAt: now - 400 * DAY_MS });
|
|
94
|
-
makeRun('2020-06-01T00-00-00-000Z', 'old-slug', { finishedAt: now - 300 * DAY_MS });
|
|
95
|
-
|
|
96
|
-
const settings = { schedulerRunLogRetention: { enabled: false, policy: { maxAgeDays: 30 } } };
|
|
97
|
-
const result = applyRetention(runsDir, settings, { now });
|
|
98
|
-
expect(result.deleted).toBe(false);
|
|
99
|
-
expect(fs.readdirSync(runsDir).length).toBe(2);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('isRetentionEnabled: requires enabled===true AND a non-empty policy', () => {
|
|
103
|
-
expect(isRetentionEnabled(undefined)).toBe(false);
|
|
104
|
-
expect(isRetentionEnabled({})).toBe(false);
|
|
105
|
-
expect(isRetentionEnabled({ schedulerRunLogRetention: { enabled: true } })).toBe(false);
|
|
106
|
-
expect(isRetentionEnabled({ schedulerRunLogRetention: { enabled: true, policy: {} } })).toBe(false);
|
|
107
|
-
expect(
|
|
108
|
-
isRetentionEnabled({ schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 30 } } })
|
|
109
|
-
).toBe(true);
|
|
110
|
-
expect(
|
|
111
|
-
isRetentionEnabled({ schedulerRunLogRetention: { enabled: 'true', policy: { maxAgeDays: 30 } } })
|
|
112
|
-
).toBe(false); // must be boolean true, not truthy
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// ─── explicit opt-in actually deletes ───────────────────────────────────────
|
|
116
|
-
|
|
117
|
-
test('applyRetention: deletes ONLY when schedulerRunLogRetention.enabled===true plus a policy', () => {
|
|
118
|
-
const now = Date.now();
|
|
119
|
-
makeRun('2020-01-01T00-00-00-000Z', 'old-slug', { finishedAt: now - 400 * DAY_MS });
|
|
120
|
-
const recentDir = makeRun('2020-06-01T00-00-00-000Z', 'old-slug', { finishedAt: now - 1 * DAY_MS });
|
|
121
|
-
|
|
122
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 30 } } };
|
|
123
|
-
const result = applyRetention(runsDir, settings, { now });
|
|
124
|
-
|
|
125
|
-
expect(result.deleted).toBe(true);
|
|
126
|
-
expect(result.removedFiles).toBeGreaterThan(0);
|
|
127
|
-
// old, non-most-recent run's directory is gone entirely
|
|
128
|
-
expect(fs.existsSync(path.join(runsDir, '2020-01-01T00-00-00-000Z'))).toBe(false);
|
|
129
|
-
// most-recent run for the slug survives regardless of policy
|
|
130
|
-
expect(fs.existsSync(recentDir)).toBe(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// ─── live-job protection ────────────────────────────────────────────────────
|
|
134
|
-
|
|
135
|
-
test('isLiveJob: pending/running/needs_review/investigating are live; completed/failed are not', () => {
|
|
136
|
-
expect(isLiveJob({ status: 'pending' })).toBe(true);
|
|
137
|
-
expect(isLiveJob({ status: 'running' })).toBe(true);
|
|
138
|
-
expect(isLiveJob({ status: 'needs_review' })).toBe(true);
|
|
139
|
-
expect(isLiveJob({ status: 'investigating' })).toBe(true);
|
|
140
|
-
expect(isLiveJob({ status: 'completed' })).toBe(false);
|
|
141
|
-
expect(isLiveJob({ status: 'failed' })).toBe(false);
|
|
142
|
-
expect(isLiveJob(null)).toBe(false);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test('a run belonging to a needs_review job is NEVER eligible, regardless of age', () => {
|
|
146
|
-
const now = Date.now();
|
|
147
|
-
// Two runs of the same slug so the protected one is not simply "most recent".
|
|
148
|
-
makeRun('2020-01-01T00-00-00-000Z', 'watched-slug', { finishedAt: now - 500 * DAY_MS });
|
|
149
|
-
makeRun('2020-02-01T00-00-00-000Z', 'watched-slug', { finishedAt: now - 490 * DAY_MS }); // this one is live
|
|
150
|
-
makeRun('2026-01-01T00-00-00-000Z', 'watched-slug', { finishedAt: now - 1 * DAY_MS }); // most recent
|
|
151
|
-
|
|
152
|
-
const jobs = [{ slug: 'watched-slug', runId: '2020-02-01T00-00-00-000Z', status: 'needs_review' }];
|
|
153
|
-
const liveKeys = liveKeysFromJobs(jobs);
|
|
154
|
-
expect(liveKeys.has('watched-slug|2020-02-01T00-00-00-000Z')).toBe(true);
|
|
155
|
-
|
|
156
|
-
const report = computeReport(runsDir, { maxAgeDays: 30 }, { now, liveKeys });
|
|
157
|
-
const eligibleRunIds = report.eligible.map((e) => e.runId);
|
|
158
|
-
expect(eligibleRunIds).toContain('2020-01-01T00-00-00-000Z'); // old, not live, not most-recent — eligible
|
|
159
|
-
expect(eligibleRunIds).not.toContain('2020-02-01T00-00-00-000Z'); // live — protected
|
|
160
|
-
expect(eligibleRunIds).not.toContain('2026-01-01T00-00-00-000Z'); // most recent — protected
|
|
161
|
-
|
|
162
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 30 } } };
|
|
163
|
-
const result = applyRetention(runsDir, settings, { now, liveKeys });
|
|
164
|
-
expect(fs.existsSync(path.join(runsDir, '2020-02-01T00-00-00-000Z'))).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test('queued/running jobs are also protected, same as needs_review', () => {
|
|
168
|
-
const now = Date.now();
|
|
169
|
-
makeRun('2020-01-01T00-00-00-000Z', 'busy-slug', { finishedAt: now - 500 * DAY_MS });
|
|
170
|
-
makeRun('2020-02-01T00-00-00-000Z', 'busy-slug', { finishedAt: now - 1 * DAY_MS }); // most recent, also running
|
|
171
|
-
|
|
172
|
-
const jobs = [{ slug: 'busy-slug', runId: '2020-02-01T00-00-00-000Z', status: 'running' }];
|
|
173
|
-
const liveKeys = liveKeysFromJobs(jobs);
|
|
174
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 0 } } };
|
|
175
|
-
const result = applyRetention(runsDir, settings, { now, liveKeys });
|
|
176
|
-
|
|
177
|
-
// old one is eligible and removed (age 0 means "older than 0 days")
|
|
178
|
-
expect(fs.existsSync(path.join(runsDir, '2020-01-01T00-00-00-000Z'))).toBe(false);
|
|
179
|
-
// running one is protected even though its own age also clears the 0-day cap
|
|
180
|
-
expect(fs.existsSync(path.join(runsDir, '2020-02-01T00-00-00-000Z'))).toBe(true);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// ─── most-recent-per-slug protection ────────────────────────────────────────
|
|
184
|
-
|
|
185
|
-
test('the most recent run for a slug is never eligible, even past the age cap', () => {
|
|
186
|
-
const now = Date.now();
|
|
187
|
-
makeRun('2018-01-01T00-00-00-000Z', 'lonely-slug', { finishedAt: now - 3000 * DAY_MS });
|
|
188
|
-
|
|
189
|
-
const report = computeReport(runsDir, { maxAgeDays: 1 }, { now });
|
|
190
|
-
expect(report.eligible.length).toBe(0);
|
|
191
|
-
|
|
192
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 1 } } };
|
|
193
|
-
const result = applyRetention(runsDir, settings, { now });
|
|
194
|
-
expect(result.removedFiles).toBe(0);
|
|
195
|
-
expect(fs.existsSync(path.join(runsDir, '2018-01-01T00-00-00-000Z'))).toBe(true);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// ─── age + count combinability ──────────────────────────────────────────────
|
|
199
|
-
|
|
200
|
-
test('age-based policy: only entries older than maxAgeDays (and not most-recent) are eligible', () => {
|
|
201
|
-
const now = Date.now();
|
|
202
|
-
makeRun('2020-01-01T00-00-00-000Z', 's', { finishedAt: now - 90 * DAY_MS });
|
|
203
|
-
makeRun('2020-06-01T00-00-00-000Z', 's', { finishedAt: now - 10 * DAY_MS });
|
|
204
|
-
makeRun('2020-09-01T00-00-00-000Z', 's', { finishedAt: now - 1 * DAY_MS }); // most recent
|
|
205
|
-
|
|
206
|
-
const report = computeReport(runsDir, { maxAgeDays: 30 }, { now });
|
|
207
|
-
expect(report.eligible.map((e) => e.runId)).toEqual(['2020-01-01T00-00-00-000Z']);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test('count-based policy: keeps the N most recent runs per slug, rest eligible', () => {
|
|
211
|
-
const now = Date.now();
|
|
212
|
-
const ids = [];
|
|
213
|
-
for (let i = 0; i < 5; i++) {
|
|
214
|
-
const runId = `2020-0${i + 1}-01T00-00-00-000Z`;
|
|
215
|
-
ids.push(runId);
|
|
216
|
-
makeRun(runId, 'count-slug', { finishedAt: now - (5 - i) * DAY_MS });
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const report = computeReport(runsDir, { keepPerSlug: 2 }, { now });
|
|
220
|
-
// 5 runs, keep 2 most recent (ranks 0,1) -> 3 eligible (ranks 2,3,4)
|
|
221
|
-
expect(report.eligible.length).toBe(3);
|
|
222
|
-
const eligibleRunIds = new Set(report.eligible.map((e) => e.runId));
|
|
223
|
-
expect(eligibleRunIds.has(ids[4])).toBe(false); // most recent
|
|
224
|
-
expect(eligibleRunIds.has(ids[3])).toBe(false); // 2nd most recent, kept by count
|
|
225
|
-
expect(eligibleRunIds.has(ids[0])).toBe(true);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test('age + count policies combine conservatively (AND): an entry needs both to be eligible', () => {
|
|
229
|
-
const now = Date.now();
|
|
230
|
-
// Old by age, but within the keepPerSlug=3 window (rank 1 of 3).
|
|
231
|
-
makeRun('2020-01-01T00-00-00-000Z', 's', { finishedAt: now - 400 * DAY_MS });
|
|
232
|
-
makeRun('2020-02-01T00-00-00-000Z', 's', { finishedAt: now - 300 * DAY_MS });
|
|
233
|
-
makeRun('2020-03-01T00-00-00-000Z', 's', { finishedAt: now - 1 * DAY_MS }); // most recent
|
|
234
|
-
|
|
235
|
-
const policy = { maxAgeDays: 30, keepPerSlug: 3 };
|
|
236
|
-
const report = computeReport(runsDir, policy, { now });
|
|
237
|
-
// Both are old enough by age, but keepPerSlug=3 keeps all 3 ranks (0,1,2) — none eligible.
|
|
238
|
-
expect(report.eligible.length).toBe(0);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// ─── report surface: usage stats ────────────────────────────────────────────
|
|
242
|
-
|
|
243
|
-
test('computeReport: reports total bytes, dir count, and oldest run date', () => {
|
|
244
|
-
const now = Date.now();
|
|
245
|
-
makeRun('2020-01-01T00-00-00-000Z', 'a', { finishedAt: now - 100 * DAY_MS });
|
|
246
|
-
makeRun('2020-06-01T00-00-00-000Z', 'b', { finishedAt: now - 10 * DAY_MS });
|
|
247
|
-
|
|
248
|
-
const report = computeReport(runsDir, {}, { now });
|
|
249
|
-
expect(report.usage.dirCount).toBe(2);
|
|
250
|
-
expect(report.usage.runCount).toBe(2);
|
|
251
|
-
expect(report.usage.totalBytes).toBeGreaterThan(0);
|
|
252
|
-
expect(report.usage.oldestRunAt).toBe(now - 100 * DAY_MS);
|
|
253
|
-
// no policy set -> nothing eligible, purely informational
|
|
254
|
-
expect(report.eligible.length).toBe(0);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// ─── directory-level cleanup: partial dirs never fully removed ─────────────
|
|
258
|
-
|
|
259
|
-
test('a directory holding an unclaimed extra file is never in removableDirs, even if its own entry is eligible', () => {
|
|
260
|
-
const now = Date.now();
|
|
261
|
-
makeRun('2020-01-01T00-00-00-000Z', 'shared-dir-slug', {
|
|
262
|
-
finishedAt: now - 400 * DAY_MS,
|
|
263
|
-
extraFiles: ['definition-of-done-abc12345.md'],
|
|
264
|
-
});
|
|
265
|
-
makeRun('2026-01-01T00-00-00-000Z', 'shared-dir-slug', { finishedAt: now - 1 * DAY_MS });
|
|
266
|
-
|
|
267
|
-
const report = computeReport(runsDir, { maxAgeDays: 30 }, { now });
|
|
268
|
-
expect(report.eligible.length).toBe(1);
|
|
269
|
-
expect(report.removableDirs).toEqual([]);
|
|
270
|
-
|
|
271
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 30 } } };
|
|
272
|
-
const result = applyRetention(runsDir, settings, { now });
|
|
273
|
-
// the slug's own log+meta are removed...
|
|
274
|
-
expect(fs.existsSync(path.join(runsDir, '2020-01-01T00-00-00-000Z', 'shared-dir-slug.log'))).toBe(false);
|
|
275
|
-
// ...but the directory itself survives because of the unclaimed DoD report
|
|
276
|
-
expect(fs.existsSync(path.join(runsDir, '2020-01-01T00-00-00-000Z'))).toBe(true);
|
|
277
|
-
expect(fs.existsSync(path.join(runsDir, '2020-01-01T00-00-00-000Z', 'definition-of-done-abc12345.md'))).toBe(true);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test('a directory whose sole entry is eligible is fully removed (dir + files)', () => {
|
|
281
|
-
const now = Date.now();
|
|
282
|
-
makeRun('2020-01-01T00-00-00-000Z', 'solo-slug', { finishedAt: now - 400 * DAY_MS });
|
|
283
|
-
makeRun('2026-01-01T00-00-00-000Z', 'solo-slug', { finishedAt: now - 1 * DAY_MS });
|
|
284
|
-
|
|
285
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 30 } } };
|
|
286
|
-
applyRetention(runsDir, settings, { now });
|
|
287
|
-
expect(fs.existsSync(path.join(runsDir, '2020-01-01T00-00-00-000Z'))).toBe(false);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// ─── liveKeysFromJobs: null-runId backfill (needs_review can lose its runId) ─
|
|
291
|
-
|
|
292
|
-
test('liveKeysFromJobs: backfills protection via a runsDir scan when a live job has lost its runId', () => {
|
|
293
|
-
makeRun('2020-01-01T00-00-00-000Z', 'lost-slug', { finishedAt: Date.now() });
|
|
294
|
-
const jobs = [{ slug: 'lost-slug', runId: null, status: 'needs_review' }];
|
|
295
|
-
const keys = liveKeysFromJobs(jobs, { runsDir });
|
|
296
|
-
expect(keys.has('lost-slug|2020-01-01T00-00-00-000Z')).toBe(true);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
test('liveKeysFromJobs: without a runsDir hint, a live job with no runId yields no protection (documented limitation — callers should always pass runsDir)', () => {
|
|
300
|
-
const jobs = [{ slug: 'lost-slug', runId: null, status: 'needs_review' }];
|
|
301
|
-
expect(liveKeysFromJobs(jobs).size).toBe(0);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// ─── applyRetention: fail-safe fallback when the caller forgets liveKeys/jobs ─
|
|
305
|
-
|
|
306
|
-
test('applyRetention: enabled=true with no liveKeys/jobs falls back to reading the real scheduler queue (queueStore) instead of silently protecting nothing', () => {
|
|
307
|
-
const now = Date.now();
|
|
308
|
-
makeRun('2020-05-01T00-00-00-000Z', 'fallback-slug', { finishedAt: now - 400 * DAY_MS });
|
|
309
|
-
makeRun('2020-06-01T00-00-00-000Z', 'fallback-slug', { finishedAt: now - 1 * DAY_MS }); // most recent, unrelated
|
|
310
|
-
|
|
311
|
-
const queueStore = require('../lib/queueStore.cjs');
|
|
312
|
-
const original = queueStore.readMergedSync;
|
|
313
|
-
queueStore.readMergedSync = () => ({
|
|
314
|
-
jobs: [{ slug: 'fallback-slug', runId: '2020-05-01T00-00-00-000Z', status: 'running' }],
|
|
315
|
-
});
|
|
316
|
-
try {
|
|
317
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 0 } } };
|
|
318
|
-
applyRetention(runsDir, settings, { now }); // deliberately no liveKeys, no jobs
|
|
319
|
-
expect(fs.existsSync(path.join(runsDir, '2020-05-01T00-00-00-000Z'))).toBe(true);
|
|
320
|
-
} finally {
|
|
321
|
-
queueStore.readMergedSync = original;
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// ─── runBootSweep: the one production caller ────────────────────────────────
|
|
326
|
-
|
|
327
|
-
test('runBootSweep: reads queueStore ONCE and threads its jobs explicitly into applyRetention (never the internal fallback re-read)', () => {
|
|
328
|
-
const now = Date.now();
|
|
329
|
-
makeRun('2020-01-01T00-00-00-000Z', 'boot-slug', { finishedAt: now - 400 * DAY_MS });
|
|
330
|
-
makeRun('2020-02-01T00-00-00-000Z', 'boot-slug', { finishedAt: now - 390 * DAY_MS }); // live, protected
|
|
331
|
-
makeRun('2026-01-01T00-00-00-000Z', 'boot-slug', { finishedAt: now - 1 * DAY_MS }); // most recent
|
|
332
|
-
|
|
333
|
-
const queueStore = require('../lib/queueStore.cjs');
|
|
334
|
-
const original = queueStore.readMergedSync;
|
|
335
|
-
let calls = 0;
|
|
336
|
-
queueStore.readMergedSync = () => {
|
|
337
|
-
calls += 1;
|
|
338
|
-
if (calls === 1) {
|
|
339
|
-
return {
|
|
340
|
-
config: { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 7 } } },
|
|
341
|
-
jobs: [{ slug: 'boot-slug', runId: '2020-02-01T00-00-00-000Z', status: 'running' }],
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
// If applyRetention's internal fallback ever re-reads the store, this
|
|
345
|
-
// second shape (no live jobs) would let the protected run be deleted —
|
|
346
|
-
// proving runBootSweep did NOT thread jobs explicitly.
|
|
347
|
-
return { config: {}, jobs: [] };
|
|
348
|
-
};
|
|
349
|
-
try {
|
|
350
|
-
const result = runBootSweep({ now, runsDir });
|
|
351
|
-
expect(calls).toBe(1); // proves applyRetention never fell back to its own re-read
|
|
352
|
-
expect(result.deleted).toBe(true);
|
|
353
|
-
expect(fs.existsSync(path.join(runsDir, '2020-01-01T00-00-00-000Z'))).toBe(false); // eligible, removed
|
|
354
|
-
expect(fs.existsSync(path.join(runsDir, '2020-02-01T00-00-00-000Z'))).toBe(true); // live, protected
|
|
355
|
-
expect(fs.existsSync(path.join(runsDir, '2026-01-01T00-00-00-000Z'))).toBe(true); // most recent, protected
|
|
356
|
-
} finally {
|
|
357
|
-
queueStore.readMergedSync = original;
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test('runBootSweep: dry-run when config has no opt-in', () => {
|
|
362
|
-
const now = Date.now();
|
|
363
|
-
makeRun('2020-01-01T00-00-00-000Z', 'noop-slug', { finishedAt: now - 400 * DAY_MS });
|
|
364
|
-
makeRun('2026-01-01T00-00-00-000Z', 'noop-slug', { finishedAt: now - 1 * DAY_MS });
|
|
365
|
-
|
|
366
|
-
const queueStore = require('../lib/queueStore.cjs');
|
|
367
|
-
const original = queueStore.readMergedSync;
|
|
368
|
-
queueStore.readMergedSync = () => ({ config: {}, jobs: [] });
|
|
369
|
-
try {
|
|
370
|
-
const result = runBootSweep({ now, runsDir });
|
|
371
|
-
expect(result.deleted).toBe(false);
|
|
372
|
-
expect(fs.readdirSync(runsDir).length).toBe(2);
|
|
373
|
-
} finally {
|
|
374
|
-
queueStore.readMergedSync = original;
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test('runBootSweep: a deletion failure (permission error) is caught, logged in result.errors, and never thrown', () => {
|
|
379
|
-
const now = Date.now();
|
|
380
|
-
const dir = makeRun('2020-01-01T00-00-00-000Z', 'fail-slug', { finishedAt: now - 400 * DAY_MS });
|
|
381
|
-
makeRun('2026-01-01T00-00-00-000Z', 'fail-slug', { finishedAt: now - 1 * DAY_MS });
|
|
382
|
-
|
|
383
|
-
const queueStore = require('../lib/queueStore.cjs');
|
|
384
|
-
const original = queueStore.readMergedSync;
|
|
385
|
-
queueStore.readMergedSync = () => ({
|
|
386
|
-
config: { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 7 } } },
|
|
387
|
-
jobs: [],
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
const originalUnlink = fs.unlinkSync;
|
|
391
|
-
fs.unlinkSync = (p) => {
|
|
392
|
-
if (String(p).startsWith(dir)) {
|
|
393
|
-
const err = new Error('EACCES: permission denied');
|
|
394
|
-
err.code = 'EACCES';
|
|
395
|
-
throw err;
|
|
396
|
-
}
|
|
397
|
-
return originalUnlink(p);
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
try {
|
|
401
|
-
expect(() => runBootSweep({ now, runsDir })).not.toThrow();
|
|
402
|
-
const result = runBootSweep({ now, runsDir });
|
|
403
|
-
expect(result.deleted).toBe(true);
|
|
404
|
-
expect(result.errors.length).toBeGreaterThan(0);
|
|
405
|
-
} finally {
|
|
406
|
-
fs.unlinkSync = originalUnlink;
|
|
407
|
-
queueStore.readMergedSync = original;
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// ─── orphan pass: empty run directories (PRD 1120) ─────────────────────────
|
|
412
|
-
|
|
413
|
-
test('runningRunIdsFromJobs: only status===running jobs with a runId are protected', () => {
|
|
414
|
-
const ids = runningRunIdsFromJobs([
|
|
415
|
-
{ slug: 'a', status: 'running', runId: 'run-a' },
|
|
416
|
-
{ slug: 'b', status: 'pending', runId: 'run-b' },
|
|
417
|
-
{ slug: 'c', status: 'running', runId: null },
|
|
418
|
-
null,
|
|
419
|
-
]);
|
|
420
|
-
expect(ids.has('run-a')).toBe(true);
|
|
421
|
-
expect(ids.has('run-b')).toBe(false);
|
|
422
|
-
expect(ids.size).toBe(1);
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
test('applyRetention orphan pass: an empty run directory 1 minute old is removed while a directory with a .log is retained', () => {
|
|
426
|
-
const now = Date.now();
|
|
427
|
-
// Orphan: minted by pickRunDir but the dispatch aborted before executeJob
|
|
428
|
-
// ever wrote into it — no meta.json, so scanRunEntries never sees it.
|
|
429
|
-
const orphanDir = path.join(runsDir, '2026-09-05T14-00-00-000Z');
|
|
430
|
-
fs.mkdirSync(orphanDir, { recursive: true });
|
|
431
|
-
const orphanTime = (now - 60_000) / 1000;
|
|
432
|
-
fs.utimesSync(orphanDir, orphanTime, orphanTime);
|
|
433
|
-
|
|
434
|
-
// Real run: has a .log, must survive regardless of the orphan pass.
|
|
435
|
-
makeRun('2026-09-05T15-00-00-000Z', 'real-slug', { finishedAt: now - 60_000 });
|
|
436
|
-
|
|
437
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 7, keepPerSlug: 1 } } };
|
|
438
|
-
const result = applyRetention(runsDir, settings, { now, jobs: [] });
|
|
439
|
-
|
|
440
|
-
expect(result.orphanDirsRemoved).toBe(1);
|
|
441
|
-
expect(fs.existsSync(orphanDir)).toBe(false);
|
|
442
|
-
expect(fs.existsSync(path.join(runsDir, '2026-09-05T15-00-00-000Z', 'real-slug.log'))).toBe(true);
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
test('applyRetention orphan pass: skips an empty directory whose runId belongs to a currently-running job (spawn in flight)', () => {
|
|
446
|
-
const now = Date.now();
|
|
447
|
-
const inFlightRunId = '2026-09-05T16-00-00-000Z';
|
|
448
|
-
const inFlightDir = path.join(runsDir, inFlightRunId);
|
|
449
|
-
fs.mkdirSync(inFlightDir, { recursive: true });
|
|
450
|
-
|
|
451
|
-
const settings = { schedulerRunLogRetention: { enabled: true, policy: { maxAgeDays: 7, keepPerSlug: 1 } } };
|
|
452
|
-
const jobs = [{ slug: 'mid-dispatch', status: 'running', runId: inFlightRunId }];
|
|
453
|
-
const result = applyRetention(runsDir, settings, { now, jobs });
|
|
454
|
-
|
|
455
|
-
expect(result.orphanDirsRemoved).toBe(0);
|
|
456
|
-
expect(fs.existsSync(inFlightDir)).toBe(true);
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
test('applyRetention orphan pass: never runs when retention is not opted in (dry-run)', () => {
|
|
460
|
-
const now = Date.now();
|
|
461
|
-
const orphanDir = path.join(runsDir, '2020-01-01T00-00-00-000Z');
|
|
462
|
-
fs.mkdirSync(orphanDir, { recursive: true });
|
|
463
|
-
|
|
464
|
-
const result = applyRetention(runsDir, undefined, { now, jobs: [] });
|
|
465
|
-
expect(result.deleted).toBe(false);
|
|
466
|
-
expect(fs.existsSync(orphanDir)).toBe(true);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test('applyRetention: dry-run path (enabled=false) never reads queueStore for live protection', () => {
|
|
470
|
-
const now = Date.now();
|
|
471
|
-
makeRun('2020-05-01T00-00-00-000Z', 'no-touch-slug', { finishedAt: now - 400 * DAY_MS });
|
|
472
|
-
makeRun('2020-06-01T00-00-00-000Z', 'no-touch-slug', { finishedAt: now - 1 * DAY_MS });
|
|
473
|
-
|
|
474
|
-
const queueStore = require('../lib/queueStore.cjs');
|
|
475
|
-
const original = queueStore.readMergedSync;
|
|
476
|
-
let called = false;
|
|
477
|
-
queueStore.readMergedSync = () => {
|
|
478
|
-
called = true;
|
|
479
|
-
return { jobs: [] };
|
|
480
|
-
};
|
|
481
|
-
try {
|
|
482
|
-
const settings = { schedulerRunLogRetention: { enabled: false, policy: { maxAgeDays: 0 } } };
|
|
483
|
-
const result = applyRetention(runsDir, settings, { now });
|
|
484
|
-
expect(result.deleted).toBe(false);
|
|
485
|
-
expect(called).toBe(false);
|
|
486
|
-
} finally {
|
|
487
|
-
queueStore.readMergedSync = original;
|
|
488
|
-
}
|
|
489
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { test, expect, vi } from 'vitest';
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
const src = fs.readFileSync(path.join(__dirname, '..', 'runVerify.cjs'), 'utf8');
|
|
5
|
-
|
|
6
|
-
test('verdicts sidecar write goes through config.writeJsonSync, not bare writeFileSync', () => {
|
|
7
|
-
expect(src).not.toMatch(/writeFileSync\(verdictsPath/);
|
|
8
|
-
expect(src).toMatch(/config\.cjs'\)\.writeJsonSync\(verdictsPath/);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test('conclude() invokes the atomic helper at runtime', async () => {
|
|
12
|
-
const config = require('../config.cjs');
|
|
13
|
-
const spy = vi.spyOn(config, 'writeJsonSync').mockReturnValue({ ok: true, mtimeMs: 0 });
|
|
14
|
-
const { verifyRun } = require('../runVerify.cjs');
|
|
15
|
-
const os = require('node:os');
|
|
16
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'rv-atomic-'));
|
|
17
|
-
try {
|
|
18
|
-
try {
|
|
19
|
-
await verifyRun({ queueEntry: { slug: 's' }, runDir: dir, prdPath: path.join(dir, 'none.md'), exitCode: 0 });
|
|
20
|
-
} catch { /* signature drift is fine; assert only on the spy */ }
|
|
21
|
-
expect(spy.mock.calls.some(([p]) => p === path.join(dir, 's.verdicts.json'))).toBe(true);
|
|
22
|
-
} finally {
|
|
23
|
-
spy.mockRestore();
|
|
24
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
25
|
-
}
|
|
26
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* runVerify-blocked-by-foreign-wip.test.cjs — scanSentinel's new
|
|
3
|
-
* BLOCKED_BY_FOREIGN_WIP token and the scanForeignWipPathsClaim evidence-line
|
|
4
|
-
* scanner (the executor-facing half of "give the executor a first-class
|
|
5
|
-
* verdict for a sibling job's in-flight file, but validate the claim").
|
|
6
|
-
*
|
|
7
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
'use strict';
|
|
11
|
-
|
|
12
|
-
import { test, expect } from 'vitest';
|
|
13
|
-
const { scanSentinel, scanForeignWipPathsClaim } = require('../runVerify.cjs');
|
|
14
|
-
|
|
15
|
-
function resultEventFor(text) {
|
|
16
|
-
return { kind: 'result', seq: 0, subtype: 'success', resultText: text };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
test('scanSentinel: recognizes BLOCKED_BY_FOREIGN_WIP alongside PASS/FAIL', () => {
|
|
20
|
-
expect(scanSentinel(resultEventFor('SCHEDULER_VERDICT: PASS'), [])).toBe('pass');
|
|
21
|
-
expect(scanSentinel(resultEventFor('SCHEDULER_VERDICT: FAIL bad thing'), [])).toBe('fail');
|
|
22
|
-
expect(scanSentinel(resultEventFor('SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP'), [])).toBe('blocked_by_foreign_wip');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('scanSentinel: BLOCKED_BY_FOREIGN_WIP only matches at line start, like PASS/FAIL', () => {
|
|
26
|
-
expect(scanSentinel(resultEventFor('I saw a SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP mention mid-sentence'), [])).toBe(null);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('scanSentinel: falls back to the last tool_result content when no resultEvent matches', () => {
|
|
30
|
-
const events = [
|
|
31
|
-
{ kind: 'tool_result', seq: 1, toolUseId: 'a', content: 'nothing here' },
|
|
32
|
-
{ kind: 'tool_result', seq: 2, toolUseId: 'b', content: 'SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP\nFOREIGN_WIP_PATHS: src/foo.ts' },
|
|
33
|
-
];
|
|
34
|
-
expect(scanSentinel(null, events)).toBe('blocked_by_foreign_wip');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('scanForeignWipPathsClaim: extracts a comma-separated FOREIGN_WIP_PATHS line from resultEvent', () => {
|
|
38
|
-
const text = 'SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP\nFOREIGN_WIP_PATHS: src/foo.ts, src/bar.ts';
|
|
39
|
-
expect(scanForeignWipPathsClaim(resultEventFor(text), [])).toEqual(['src/foo.ts', 'src/bar.ts']);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('scanForeignWipPathsClaim: dedupes and trims whitespace', () => {
|
|
43
|
-
const text = 'FOREIGN_WIP_PATHS: src/foo.ts ,src/foo.ts, src/bar.ts ';
|
|
44
|
-
expect(scanForeignWipPathsClaim(resultEventFor(text), [])).toEqual(['src/foo.ts', 'src/bar.ts']);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('scanForeignWipPathsClaim: returns [] when no FOREIGN_WIP_PATHS line is present', () => {
|
|
48
|
-
expect(scanForeignWipPathsClaim(resultEventFor('SCHEDULER_VERDICT: FAIL nope'), [])).toEqual([]);
|
|
49
|
-
expect(scanForeignWipPathsClaim(null, [])).toEqual([]);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('scanForeignWipPathsClaim: falls back to the last tool_result content', () => {
|
|
53
|
-
const events = [
|
|
54
|
-
{ kind: 'tool_result', seq: 1, toolUseId: 'a', content: 'FOREIGN_WIP_PATHS: src/stale.ts' },
|
|
55
|
-
{ kind: 'tool_result', seq: 2, toolUseId: 'b', content: 'FOREIGN_WIP_PATHS: src/latest.ts' },
|
|
56
|
-
];
|
|
57
|
-
expect(scanForeignWipPathsClaim(null, events)).toEqual(['src/latest.ts']);
|
|
58
|
-
});
|