claude-code-session-manager 0.86.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -53
- package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
- package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
- package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
- package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
- package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
- package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
- package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
- package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
- package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
- package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
- package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
- package/dist/assets/Permissions-m0KAnccF.js +3 -0
- package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
- package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
- package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
- package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
- package/dist/assets/Settings-BHSGGFEc.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
- package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
- package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
- package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
- package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
- package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
- package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
- package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
- package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
- package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
- package/dist/index.html +2 -2
- package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
- package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
- package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
- package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
- package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
- package/package.json +14 -17
- package/plugins/CLAUDE.md +61 -0
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
- package/scripts/README.md +59 -0
- package/scripts/audit-ops-hygiene.cjs +350 -0
- package/scripts/hooks/guard-destructive-git.cjs +10 -34
- package/scripts/hooks/guard-inline-implementation.cjs +14 -8
- package/scripts/hooks/guard-prd-writes.cjs +7 -36
- package/scripts/hooks/guard-self-schedule.cjs +175 -0
- package/scripts/ops-sweep.cjs +355 -0
- package/scripts/scheduler-mcp-server.cjs +35 -71
- package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
- package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
- package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
- package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
- package/src/main/__tests__/chat-queue.test.cjs +2 -2
- package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
- package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
- package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
- package/src/main/__tests__/dod-report.test.cjs +2 -2
- package/src/main/__tests__/dod-reverify.test.cjs +2 -2
- package/src/main/__tests__/epicMint.test.cjs +4 -4
- package/src/main/__tests__/exchanges.test.cjs +2 -2
- package/src/main/__tests__/extractJson.test.cjs +2 -2
- package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
- package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
- package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
- package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
- package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
- package/src/main/__tests__/historyRollup.test.cjs +2 -2
- package/src/main/__tests__/kg-augment.test.cjs +2 -2
- package/src/main/__tests__/mcpStatus.test.cjs +2 -2
- package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
- package/src/main/__tests__/memoryStale.test.cjs +1 -1
- package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
- package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
- package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
- package/src/main/__tests__/prdLocations.test.cjs +100 -2
- package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
- package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
- package/src/main/__tests__/proc-role-env.test.cjs +125 -0
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
- package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
- package/src/main/__tests__/projectPages.test.cjs +63 -149
- package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
- package/src/main/__tests__/queueHistory.test.cjs +65 -2
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
- package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
- package/src/main/__tests__/runVerify.test.cjs +5 -5
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
- package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
- package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
- package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
- package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
- package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
- package/src/main/bilkoHost.cjs +16 -7
- package/src/main/build-info.json +8 -0
- package/src/main/chatRunner.cjs +12 -7
- package/src/main/config.cjs +2 -3
- package/src/main/docEdit.cjs +4 -2
- package/src/main/health.cjs +268 -52
- package/src/main/heapSnapshot.cjs +2 -2
- package/src/main/historyAggregator.cjs +6 -1
- package/src/main/index.cjs +152 -50
- package/src/main/ipcSchemas.cjs +53 -17
- package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
- package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
- package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
- package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
- package/src/main/lib/__tests__/gateFixtures.json +20 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
- package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
- package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
- package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
- package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
- package/src/main/lib/__tests__/procName.test.cjs +92 -0
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
- package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
- package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
- package/src/main/lib/activeIndexRebuild.cjs +1 -3
- package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
- package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
- package/src/main/lib/agentModelResolve.cjs +94 -24
- package/src/main/lib/agentPersonaSchema.cjs +2 -2
- package/src/main/lib/auditLog.cjs +30 -5
- package/src/main/lib/bootSelfHeal.cjs +88 -0
- package/src/main/lib/buildIdentity.cjs +113 -0
- package/src/main/lib/classifyPromptTicket.cjs +3 -2
- package/src/main/lib/claudeBin.cjs +37 -2
- package/src/main/lib/cleanEnv.cjs +27 -1
- package/src/main/lib/credentials.cjs +4 -2
- package/src/main/lib/cwdClassify.cjs +185 -0
- package/src/main/lib/definitionOfDone.cjs +296 -52
- package/src/main/lib/delegationReadiness.cjs +290 -225
- package/src/main/lib/dispatchLoop.cjs +40 -0
- package/src/main/lib/effectiveModelInfo.cjs +332 -0
- package/src/main/lib/ephemeralCwd.cjs +7 -28
- package/src/main/lib/epicMint.cjs +3 -3
- package/src/main/lib/gitWorktree.cjs +136 -19
- package/src/main/lib/guardShims.cjs +156 -0
- package/src/main/lib/historyRollup.cjs +6 -7
- package/src/main/lib/instanceLock.cjs +32 -5
- package/src/main/lib/jobDirtFilter.cjs +7 -2
- package/src/main/lib/jobSupervisorRecord.cjs +147 -0
- package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
- package/src/main/lib/launchFailure.cjs +2 -1
- package/src/main/lib/localAdminHttp.cjs +9 -27
- package/src/main/lib/mcpToolCatalog.cjs +33 -78
- package/src/main/lib/needsReviewLedger.cjs +205 -0
- package/src/main/lib/opsErrorLog.cjs +1 -1
- package/src/main/lib/opsOwnership.cjs +27 -19
- package/src/main/lib/prdAuthoringSeed.cjs +36 -0
- package/src/main/lib/prdCreate.cjs +56 -1
- package/src/main/lib/prdDisposition.cjs +199 -0
- package/src/main/lib/prdFrontmatter.cjs +8 -2
- package/src/main/lib/prdLocations.cjs +214 -45
- package/src/main/lib/procIdentity.cjs +126 -0
- package/src/main/lib/procName.cjs +98 -0
- package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
- package/src/main/lib/projectRootResolve.cjs +1 -1
- package/src/main/lib/queueHistory.cjs +27 -8
- package/src/main/lib/queueStore.cjs +63 -38
- package/src/main/lib/quietMachineLease.cjs +19 -2
- package/src/main/lib/reaperHelpers.cjs +181 -15
- package/src/main/lib/reservationExpiry.cjs +30 -0
- package/src/main/lib/runClaudeP.cjs +4 -2
- package/src/main/lib/runLogRetention.cjs +3 -2
- package/src/main/lib/scheduleJobSchema.cjs +10 -2
- package/src/main/lib/scheduleJobTransitions.cjs +60 -0
- package/src/main/lib/schedulerBatch.cjs +27 -2
- package/src/main/lib/schedulerConfig.cjs +24 -0
- package/src/main/lib/schedulerPaths.cjs +175 -0
- package/src/main/lib/schedulerRuntimeState.cjs +59 -0
- package/src/main/lib/sessionSlots.cjs +47 -9
- package/src/main/lib/smProcNames.cjs +51 -0
- package/src/main/lib/upgradeDrain.cjs +188 -0
- package/src/main/lib/usageCircuit.cjs +206 -0
- package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
- package/src/main/lib/withTimeout.cjs +33 -0
- package/src/main/mcpStatus.cjs +4 -2
- package/src/main/pluginInstall.cjs +6 -2
- package/src/main/projectPages.cjs +41 -145
- package/src/main/pty.cjs +8 -1
- package/src/main/queueOps.cjs +10 -10
- package/src/main/runVerify.cjs +69 -3
- package/src/main/scheduler/prdParser.cjs +13 -0
- package/src/main/scheduler.cjs +2841 -482
- package/src/main/seedAgentPersonas.cjs +1 -1
- package/src/main/seedDevPlugin.cjs +1 -1
- package/src/main/seedSchedulerMcp.cjs +7 -6
- package/src/main/seedStatus.cjs +1 -1
- package/src/main/supervisor.cjs +5 -3
- package/src/main/templates/PRD_AUTHORING.md +50 -0
- package/src/main/usage.cjs +140 -58
- package/src/preload/api.d.ts +110 -65
- package/src/preload/index.cjs +12 -4
- package/src/seed/agents/project-home-builder.md +31 -48
- package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
- package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
- package/dist/assets/Permissions-BiYZNGYW.js +0 -3
- package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
- package/dist/assets/Settings-Cv-pRyms.js +0 -3
- package/dist/assets/settingsSchema-BJVciriw.js +0 -3
- package/screenshots/.gitkeep +0 -0
- package/screenshots/README-screenshots.md +0 -13
- package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
- package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
- package/scripts/render-project-pages.cjs +0 -70
- package/scripts/validate-project-pages-summary.cjs +0 -62
- package/src/main/lib/projectPageSummarySchema.cjs +0 -181
- package/src/main/teams.cjs +0 -95
- package/src/main/templates/project-pages-catalog.json +0 -741
- package/src/main/templates/project-pages-default-home.html +0 -123
- package/src/main/templates/project-pages-pipeline.md +0 -417
- package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
- package/src/seed/prompts/code-review/correctness-only.md +0 -8
- package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
- package/src/seed/prompts/code-review/hallucination-check.md +0 -8
- package/src/seed/prompts/code-review/public-api-compat.md +0 -8
- package/src/seed/prompts/code-review/readability-naming.md +0 -8
- package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
- package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
- package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
- package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
- package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
- package/src/seed/prompts/documentation/adr-from-change.md +0 -8
- package/src/seed/prompts/documentation/module-readme.md +0 -8
- package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
- package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
- package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
- package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
- package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
- package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
- package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
- package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
- package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
- package/src/seed/prompts/performance/complexity-audit.md +0 -8
- package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
- package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
- package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
- package/src/seed/prompts/qa/api-contract-tests.md +0 -8
- package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
- package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
- package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
- package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
- package/src/seed/prompts/qa/tdd-red-first.md +0 -8
- package/src/seed/prompts/qa/visual-regression-review.md +0 -8
- package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
- package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
- package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
- package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
- package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
- package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
- package/src/seed/prompts/security/authz-audit.md +0 -8
- package/src/seed/prompts/security/crypto-correctness.md +0 -8
- package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
- package/src/seed/prompts/security/dependency-audit.md +0 -8
- package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
- package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
- package/src/seed/prompts/security/secret-credential-scan.md +0 -8
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
import { test, expect, vi, afterEach } from 'vitest';
|
|
19
|
+
const fs = require('node:fs');
|
|
19
20
|
const scheduler = require('../scheduler.cjs');
|
|
20
21
|
const { evaluateSharedTreeGuard, checkSharedTreeGuard } = scheduler;
|
|
21
22
|
|
|
@@ -210,3 +211,170 @@ test('checkSharedTreeGuard never throws when the underlying git calls reject', a
|
|
|
210
211
|
});
|
|
211
212
|
expect(result).toBeNull();
|
|
212
213
|
});
|
|
214
|
+
|
|
215
|
+
// --- 2026-09-12 false-positive: an untracked path that merely became
|
|
216
|
+
// IGNORED during the run (a .gitignore edit) is not a revert. See the
|
|
217
|
+
// evaluateSharedTreeGuard doc comment for the full incident writeup. Each
|
|
218
|
+
// test below is written to FAIL against the pre-fix implementation (which
|
|
219
|
+
// computed `reverted = dirtyBefore - dirtyAfter - committed` with no
|
|
220
|
+
// existence/tracked-status distinction at all) and PASS after it.
|
|
221
|
+
|
|
222
|
+
test('evaluateSharedTreeGuard: an untracked path that disappears from git status but STILL EXISTS on disk is nowIgnored, not reverted', () => {
|
|
223
|
+
// Pre-fix behavior: this path would land in `reverted` (dirtyBefore minus
|
|
224
|
+
// dirtyAfter minus committed, no existence check at all) — FAILS pre-fix.
|
|
225
|
+
const result = evaluateSharedTreeGuard({
|
|
226
|
+
stashBefore: [],
|
|
227
|
+
stashAfter: [],
|
|
228
|
+
dirtyBefore: [{ code: '??', path: 'session-manager-operations/logs/errors-2026-09-12.jsonl' }],
|
|
229
|
+
dirtyAfter: [],
|
|
230
|
+
pathsCommittedDuringRun: ['.gitignore'],
|
|
231
|
+
existsAfter: ['session-manager-operations/logs/errors-2026-09-12.jsonl'],
|
|
232
|
+
});
|
|
233
|
+
expect(result.reverted).toEqual([]);
|
|
234
|
+
expect(result.nowIgnored).toEqual(['session-manager-operations/logs/errors-2026-09-12.jsonl']);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('evaluateSharedTreeGuard: an untracked path that disappears from git status AND no longer exists on disk is still reverted', () => {
|
|
238
|
+
// Proves the fix is not a blanket "stop reporting reverted": a genuinely
|
|
239
|
+
// deleted untracked file (the guard's real purpose — catching a job that
|
|
240
|
+
// discarded uncommitted work) must still surface. If the fix had simply
|
|
241
|
+
// stopped reporting any untracked-and-missing path as reverted, this test
|
|
242
|
+
// would fail (reverted would come back empty instead of containing the
|
|
243
|
+
// path).
|
|
244
|
+
const result = evaluateSharedTreeGuard({
|
|
245
|
+
stashBefore: [],
|
|
246
|
+
stashAfter: [],
|
|
247
|
+
dirtyBefore: [{ code: '??', path: 'data/scratch.txt' }],
|
|
248
|
+
dirtyAfter: [],
|
|
249
|
+
pathsCommittedDuringRun: [],
|
|
250
|
+
existsAfter: [], // file no longer exists on disk
|
|
251
|
+
});
|
|
252
|
+
expect(result.reverted).toEqual(['data/scratch.txt']);
|
|
253
|
+
expect(result.nowIgnored).toEqual([]);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('evaluateSharedTreeGuard: a TRACKED path clean after the run is still reverted even though it exists on disk', () => {
|
|
257
|
+
// A tracked file leaving the dirty set means its content was restored to
|
|
258
|
+
// HEAD — exactly the discard this guard exists to catch — regardless of
|
|
259
|
+
// whether the file is still present. Pre-fix this already worked by
|
|
260
|
+
// accident (no existence check existed at all); this test pins the
|
|
261
|
+
// behavior now that existence checks exist for the untracked branch, so a
|
|
262
|
+
// future change can't accidentally start trusting `existsAfter` for
|
|
263
|
+
// tracked paths too.
|
|
264
|
+
const result = evaluateSharedTreeGuard({
|
|
265
|
+
stashBefore: [],
|
|
266
|
+
stashAfter: [],
|
|
267
|
+
dirtyBefore: [{ code: ' M', path: 'src/config.js' }],
|
|
268
|
+
dirtyAfter: [],
|
|
269
|
+
pathsCommittedDuringRun: [],
|
|
270
|
+
existsAfter: ['src/config.js'], // file exists, but content was reset to HEAD
|
|
271
|
+
});
|
|
272
|
+
expect(result.reverted).toEqual(['src/config.js']);
|
|
273
|
+
expect(result.nowIgnored).toEqual([]);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('checkSharedTreeGuard end-to-end: the real 2026-09-12 incident shape — untracked logs path ignored by a committed .gitignore change, file still on disk — must NOT verdict shared_tree_reverted', async () => {
|
|
277
|
+
// PRD 1181's exact shape: a bare `logs/` pattern lands in .gitignore during
|
|
278
|
+
// the run (committed), eleven untracked log paths vanish from `git status`,
|
|
279
|
+
// and the files are untouched on disk. Pre-fix, checkSharedTreeGuard's
|
|
280
|
+
// `reverted` would contain this path (no existsAfter computation existed),
|
|
281
|
+
// driving the caller's `verdict: 'shared_tree_reverted'` downgrade — FAILS
|
|
282
|
+
// pre-fix.
|
|
283
|
+
const logPath = 'session-manager-operations/logs/errors-2026-09-12.jsonl';
|
|
284
|
+
vi.spyOn(scheduler, 'stashList').mockResolvedValue([]);
|
|
285
|
+
vi.spyOn(scheduler, 'gitHead').mockResolvedValue('sha-after');
|
|
286
|
+
vi.spyOn(scheduler, 'pathsChangedSince').mockResolvedValue(['.gitignore']);
|
|
287
|
+
vi.spyOn(scheduler, 'uncommittedChanges').mockResolvedValue([]); // the log path no longer shows up at all
|
|
288
|
+
// checkSharedTreeGuard's existence check runs concurrently via
|
|
289
|
+
// fs.promises.access (fsp === fs.promises — same object) rather than
|
|
290
|
+
// fs.existsSync, so a large untracked baseline doesn't block the event
|
|
291
|
+
// loop with hundreds of synchronous stats back-to-back.
|
|
292
|
+
vi.spyOn(fs.promises, 'access').mockImplementation((p) => (
|
|
293
|
+
String(p).includes('errors-2026-09-12.jsonl') ? Promise.resolve() : Promise.reject(new Error('ENOENT'))
|
|
294
|
+
));
|
|
295
|
+
|
|
296
|
+
const result = await checkSharedTreeGuard({
|
|
297
|
+
cwd: '/repo',
|
|
298
|
+
stashBaseline: [],
|
|
299
|
+
dirtyBaseline: [{ code: '??', path: logPath }],
|
|
300
|
+
headBefore: 'sha-before',
|
|
301
|
+
slug: 'job-1181',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(result.reverted).toBeUndefined();
|
|
305
|
+
expect(result.nowIgnored).toEqual([logPath]);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// --- landedCommit ground truth (2026-09-18, 1229-fo-03 false positive) ---
|
|
309
|
+
// Real temp git fixture, never the live repo. Baseline-dirty path `cfg.txt`
|
|
310
|
+
// goes clean with no commit covering it (the shape that makes the baseline
|
|
311
|
+
// diff say "reverted"); only the landedCommit ancestry decides the verdict.
|
|
312
|
+
const { execFileSync } = require('node:child_process');
|
|
313
|
+
const os = require('node:os');
|
|
314
|
+
const nodePath = require('node:path');
|
|
315
|
+
|
|
316
|
+
function gitIn(dir, ...args) {
|
|
317
|
+
return execFileSync('git', args, { cwd: dir, encoding: 'utf8' }).trim();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function fixtureRepo() {
|
|
321
|
+
const dir = fs.mkdtempSync(nodePath.join(os.tmpdir(), 'shared-tree-landed-'));
|
|
322
|
+
gitIn(dir, 'init', '-q');
|
|
323
|
+
gitIn(dir, 'config', 'user.email', 't@example.com');
|
|
324
|
+
gitIn(dir, 'config', 'user.name', 'T');
|
|
325
|
+
fs.writeFileSync(nodePath.join(dir, 'cfg.txt'), 'base\n');
|
|
326
|
+
gitIn(dir, 'add', '-A');
|
|
327
|
+
gitIn(dir, 'commit', '-q', '-m', 'base');
|
|
328
|
+
const base = gitIn(dir, 'rev-parse', 'HEAD');
|
|
329
|
+
fs.writeFileSync(nodePath.join(dir, 'cfg.txt'), 'human wip\n'); // baseline-dirty
|
|
330
|
+
return { dir, base };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function commitPath(dir, rel, msg) {
|
|
334
|
+
fs.writeFileSync(nodePath.join(dir, rel), `${msg}\n`);
|
|
335
|
+
gitIn(dir, 'add', rel);
|
|
336
|
+
gitIn(dir, 'commit', '-q', '-m', msg);
|
|
337
|
+
return gitIn(dir, 'rev-parse', 'HEAD');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
test('checkSharedTreeGuard: landedCommit still an ancestor of HEAD plus an unrelated later commit is NOT reverted', async () => {
|
|
341
|
+
const { dir, base } = fixtureRepo();
|
|
342
|
+
const landed = commitPath(dir, 'job.txt', 'job work');
|
|
343
|
+
gitIn(dir, 'checkout', '--', 'cfg.txt'); // baseline-dirty path goes clean, no commit covers it
|
|
344
|
+
commitPath(dir, 'later.txt', 'unrelated human commit');
|
|
345
|
+
|
|
346
|
+
const result = await checkSharedTreeGuard({
|
|
347
|
+
cwd: dir, stashBaseline: [], dirtyBaseline: [{ code: ' M', path: 'cfg.txt' }],
|
|
348
|
+
headBefore: base, slug: 'job-a', landedCommit: landed,
|
|
349
|
+
});
|
|
350
|
+
expect(result).toBeNull();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('checkSharedTreeGuard: landedCommit absent from history still parks as reverted', async () => {
|
|
354
|
+
const { dir, base } = fixtureRepo();
|
|
355
|
+
const landed = commitPath(dir, 'job.txt', 'job work');
|
|
356
|
+
gitIn(dir, 'reset', '-q', '--hard', base); // job commit discarded
|
|
357
|
+
commitPath(dir, 'later.txt', 'unrelated human commit');
|
|
358
|
+
|
|
359
|
+
const result = await checkSharedTreeGuard({
|
|
360
|
+
cwd: dir, stashBaseline: [], dirtyBaseline: [{ code: ' M', path: 'cfg.txt' }],
|
|
361
|
+
headBefore: base, slug: 'job-b', landedCommit: landed,
|
|
362
|
+
});
|
|
363
|
+
expect(result).toEqual({ reverted: ['cfg.txt'] });
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('checkSharedTreeGuard: no landedCommit at all still parks as reverted', async () => {
|
|
367
|
+
const { dir, base } = fixtureRepo();
|
|
368
|
+
gitIn(dir, 'checkout', '--', 'cfg.txt');
|
|
369
|
+
const result = await checkSharedTreeGuard({
|
|
370
|
+
cwd: dir, stashBaseline: [], dirtyBaseline: [{ code: ' M', path: 'cfg.txt' }],
|
|
371
|
+
headBefore: base, slug: 'job-c', landedCommit: null,
|
|
372
|
+
});
|
|
373
|
+
expect(result).toEqual({ reverted: ['cfg.txt'] });
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('landedCommitIsAncestorOfHead: empty/unknown sha is false, never throws', async () => {
|
|
377
|
+
const { dir } = fixtureRepo();
|
|
378
|
+
expect(await scheduler.landedCommitIsAncestorOfHead(dir, '')).toBe(false);
|
|
379
|
+
expect(await scheduler.landedCommitIsAncestorOfHead(dir, 'deadbeefdeadbeef')).toBe(false);
|
|
380
|
+
});
|
|
@@ -12,21 +12,26 @@
|
|
|
12
12
|
* Exercises selectStarveEscalations() (pure) directly, and
|
|
13
13
|
* runStarveEscalationSweep() (the side-effecting wrapper) against a real,
|
|
14
14
|
* scratch audit-log.jsonl and a fake attached window — matching
|
|
15
|
-
*
|
|
16
|
-
* require()
|
|
15
|
+
* pollLoop-dispatch-on-failure.test.cjs's pattern of setting HOME before
|
|
16
|
+
* require() (beforeAll runs AFTER module evaluation) since every state path
|
|
17
|
+
* is baked from os.homedir() at load, and restoring it in afterAll.
|
|
17
18
|
*
|
|
18
19
|
* Run: timeout 180 npx vitest run src/main/__tests__/scheduler-starve-escalation.test.cjs
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
'use strict';
|
|
22
23
|
|
|
23
|
-
import { test, beforeEach } from 'vitest';
|
|
24
|
+
import { test, beforeEach, afterAll } from 'vitest';
|
|
24
25
|
const assert = require('node:assert/strict');
|
|
25
26
|
const fs = require('node:fs');
|
|
26
27
|
const os = require('node:os');
|
|
27
28
|
const path = require('node:path');
|
|
28
29
|
|
|
29
|
-
process.env.HOME
|
|
30
|
+
const originalHome = process.env.HOME;
|
|
31
|
+
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'starve-escalation-test-'));
|
|
32
|
+
process.env.HOME = tmpHome;
|
|
33
|
+
fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
|
|
34
|
+
fs.mkdirSync(path.join(tmpHome, '.claude', 'projects'), { recursive: true });
|
|
30
35
|
|
|
31
36
|
const {
|
|
32
37
|
selectStarveEscalations,
|
|
@@ -34,7 +39,12 @@ const {
|
|
|
34
39
|
STARVE_ESCALATION_MS,
|
|
35
40
|
attachWindow,
|
|
36
41
|
} = require('../scheduler.cjs');
|
|
37
|
-
const {
|
|
42
|
+
const { auditLogPath } = require('../lib/auditLog.cjs');
|
|
43
|
+
|
|
44
|
+
afterAll(() => {
|
|
45
|
+
process.env.HOME = originalHome;
|
|
46
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
47
|
+
});
|
|
38
48
|
|
|
39
49
|
const CWD_A = '/home/bilko/Projects/Bilko';
|
|
40
50
|
const CWD_B = '/home/bilko/Projects/session-manager';
|
|
@@ -42,7 +52,7 @@ const CWD_B = '/home/bilko/Projects/session-manager';
|
|
|
42
52
|
function readAuditKind(kind) {
|
|
43
53
|
let lines;
|
|
44
54
|
try {
|
|
45
|
-
lines = fs.readFileSync(
|
|
55
|
+
lines = fs.readFileSync(auditLogPath(), 'utf8').split('\n').filter(Boolean);
|
|
46
56
|
} catch {
|
|
47
57
|
return [];
|
|
48
58
|
}
|
|
@@ -65,8 +75,8 @@ function fakeWindow() {
|
|
|
65
75
|
beforeEach(() => {
|
|
66
76
|
// Fresh scratch audit log per test so escalation counts never bleed across
|
|
67
77
|
// tests in this file.
|
|
68
|
-
fs.mkdirSync(path.dirname(
|
|
69
|
-
fs.writeFileSync(
|
|
78
|
+
fs.mkdirSync(path.dirname(auditLogPath()), { recursive: true });
|
|
79
|
+
fs.writeFileSync(auditLogPath(), '');
|
|
70
80
|
attachWindow(null);
|
|
71
81
|
});
|
|
72
82
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scheduler-stranded-autofix-park.test.cjs — PRD "1218-fo-01 stranded
|
|
3
|
+
* auto-fix park" (2026-09-13, findings filed at session-manager-operations/
|
|
4
|
+
* reviews/2026-09-13-scheduler-stability-investigation.md, "post-run
|
|
5
|
+
* adjudication" section).
|
|
6
|
+
*
|
|
7
|
+
* spawnInvestigation restores a job's status from 'investigating' back to
|
|
8
|
+
* needs_review in ONE mutate() call (scheduler.cjs, source
|
|
9
|
+
* 'spawnInvestigation:onExit') and stamps autoFixOutcome in a SEPARATE,
|
|
10
|
+
* later mutate() call. A crash/restart between the two leaves a row with
|
|
11
|
+
* autoFixAttempted: true and autoFixOutcome permanently undefined —
|
|
12
|
+
* isExhaustedAutoFix wants autoFixRetries >= 1 (never incremented here) and
|
|
13
|
+
* isGuardParkedWithoutAutoFix wants autoFixAttempted === false, so the row
|
|
14
|
+
* falls through every resolving door and the periodic reverify pass just
|
|
15
|
+
* re-scans the same frozen transcript forever. Job 1218-fo-01 sat exactly
|
|
16
|
+
* like this with a landed commit while every dependent PRD waited.
|
|
17
|
+
*
|
|
18
|
+
* This file covers the new isStrandedAutoFixPark predicate and its wiring
|
|
19
|
+
* into isEligibleForNeedsReviewAutoResolve / selectExhaustedNeedsReviewTargets
|
|
20
|
+
* / applyNeedsReviewAutoResolve — the SAME bounded ladder the exhausted-
|
|
21
|
+
* auto-fix and guard-parked doors already use, unchanged.
|
|
22
|
+
*
|
|
23
|
+
* HOME is overridden to a tmp dir BEFORE requiring scheduler.cjs — same
|
|
24
|
+
* reason as scheduler-looks-done.test.cjs (appendAuditEvent writes under
|
|
25
|
+
* $HOME/.claude/session-manager/audit-log.jsonl).
|
|
26
|
+
*
|
|
27
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/scheduler-stranded-autofix-park.test.cjs
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const assert = require('node:assert/strict');
|
|
33
|
+
const fs = require('node:fs');
|
|
34
|
+
const os = require('node:os');
|
|
35
|
+
const path = require('node:path');
|
|
36
|
+
const { execFileSync } = require('node:child_process');
|
|
37
|
+
|
|
38
|
+
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'stranded-autofix-park-test-'));
|
|
39
|
+
process.env.HOME = tmpHome;
|
|
40
|
+
|
|
41
|
+
const {
|
|
42
|
+
isStrandedAutoFixPark,
|
|
43
|
+
isExhaustedAutoFix,
|
|
44
|
+
isGuardParkedWithoutAutoFix,
|
|
45
|
+
isEligibleForNeedsReviewAutoResolve,
|
|
46
|
+
selectExhaustedNeedsReviewTargets,
|
|
47
|
+
applyNeedsReviewAutoResolve,
|
|
48
|
+
computeLooksDone,
|
|
49
|
+
fixSlugFor,
|
|
50
|
+
NEEDS_REVIEW_RESOLVE_CAP,
|
|
51
|
+
} = require('../scheduler.cjs');
|
|
52
|
+
const { resolvePrdWriteDir } = require('../lib/prdLocations.cjs');
|
|
53
|
+
const { bustCwdCache } = require('../lib/queueStore.cjs');
|
|
54
|
+
|
|
55
|
+
const MIN_MS = 60_000;
|
|
56
|
+
const THRESHOLD_MS = 30 * MIN_MS;
|
|
57
|
+
|
|
58
|
+
// findPrdDir (via resolvePrdsDirs -> allProjectCwds) only discovers a PRD
|
|
59
|
+
// dir under a cwd it already knows about — same registration computeLooksDone
|
|
60
|
+
// needs in scheduler-looks-done.test.cjs / scheduler-guard-verdict-autoresolve.test.cjs.
|
|
61
|
+
function registerActiveProject(cwd, slug) {
|
|
62
|
+
const slugDir = path.join(tmpHome, '.claude', 'projects', slug);
|
|
63
|
+
fs.mkdirSync(slugDir, { recursive: true });
|
|
64
|
+
fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
|
|
65
|
+
bustCwdCache();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Replays the live 1218-fo-01 tuple: needs_review, verifierVerdict
|
|
69
|
+
// transcript_errors, exitCode 0, a landed commit, autoFixAttempted true,
|
|
70
|
+
// autoFixOutcome undefined, statusHistory ending in the exact
|
|
71
|
+
// spawnInvestigation:onExit restore reason.
|
|
72
|
+
function strandedJob(overrides = {}) {
|
|
73
|
+
return {
|
|
74
|
+
slug: '1218-fo-01-move-scripts-lib-into-src-main-lib',
|
|
75
|
+
cwd: '/home/user/project',
|
|
76
|
+
status: 'needs_review',
|
|
77
|
+
exitCode: 0,
|
|
78
|
+
verifierVerdict: 'transcript_errors',
|
|
79
|
+
landedCommit: 'd1edf15babc1234',
|
|
80
|
+
autoFixAttempted: true,
|
|
81
|
+
statusHistory: [
|
|
82
|
+
{ to: 'needs_review', at: new Date(Date.now() - 45 * MIN_MS).toISOString() },
|
|
83
|
+
{ to: 'investigating', at: new Date(Date.now() - 40 * MIN_MS).toISOString(), reason: 'spawning investigation probe' },
|
|
84
|
+
{
|
|
85
|
+
to: 'needs_review',
|
|
86
|
+
at: new Date(Date.now() - 35 * MIN_MS).toISOString(),
|
|
87
|
+
reason: 'investigation probe exited — restoring prior status',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
...overrides,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- isStrandedAutoFixPark ---
|
|
95
|
+
|
|
96
|
+
test('the 1218 tuple (autoFixAttempted true, autoFixOutcome undefined, no child row) is a stranded auto-fix park', () => {
|
|
97
|
+
const job = strandedJob();
|
|
98
|
+
assert.equal(isStrandedAutoFixPark(job, [job]), true);
|
|
99
|
+
assert.equal(isExhaustedAutoFix(job), false, 'never accumulated a retry — must not double-count as exhausted');
|
|
100
|
+
assert.equal(isGuardParkedWithoutAutoFix(job), false, 'autoFixAttempted is true — the guard-parked door requires false');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('autoFixOutcome "error" or "no-plan" with an unspent retry is NOT stranded — selectAutoFixTargets still owns it', () => {
|
|
104
|
+
// Durably stamped (unlike the 1218 tuple's permanently-unset outcome), so
|
|
105
|
+
// its one bounded retry (autoFixRetries < 1) is still selectAutoFixTargets's
|
|
106
|
+
// to spend — pulling it into this ladder instead would race it away from
|
|
107
|
+
// that retry (see scheduler-needs-review-autoresolve.test.cjs's "a
|
|
108
|
+
// non-exhausted needs_review row … is left alone").
|
|
109
|
+
assert.equal(isStrandedAutoFixPark(strandedJob({ autoFixOutcome: 'error' }), []), false);
|
|
110
|
+
assert.equal(isStrandedAutoFixPark(strandedJob({ autoFixOutcome: 'no-plan' }), []), false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('autoFixOutcome "plan" is never a stranded park — that is isPlanUnqueued/isFixPlanDead territory', () => {
|
|
114
|
+
assert.equal(isStrandedAutoFixPark(strandedJob({ autoFixOutcome: 'plan' }), []), false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('a row that never attempted auto-fix at all is not a stranded park', () => {
|
|
118
|
+
assert.equal(isStrandedAutoFixPark(strandedJob({ autoFixAttempted: undefined }), []), false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('a row not in needs_review is never a stranded park, whatever its autoFix* fields say', () => {
|
|
122
|
+
assert.equal(isStrandedAutoFixPark(strandedJob({ status: 'failed' }), []), false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('a live fix-plan child (running) means there IS something in flight — not stranded', () => {
|
|
126
|
+
const job = strandedJob();
|
|
127
|
+
const child = { slug: fixSlugFor(job), status: 'running' };
|
|
128
|
+
assert.equal(isStrandedAutoFixPark(job, [job, child]), false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('a row with a live investigating status (the fix-plan child mid-probe) is NOT selected', () => {
|
|
132
|
+
const job = strandedJob();
|
|
133
|
+
const child = { slug: fixSlugFor(job), status: 'investigating' };
|
|
134
|
+
assert.equal(isStrandedAutoFixPark(job, [job, child]), false);
|
|
135
|
+
assert.equal(selectExhaustedNeedsReviewTargets([job, child], Date.now(), THRESHOLD_MS).length, 0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('a queued (pending) fix-plan child also blocks the stranded-park door', () => {
|
|
139
|
+
const job = strandedJob();
|
|
140
|
+
const child = { slug: fixSlugFor(job), status: 'pending' };
|
|
141
|
+
assert.equal(isStrandedAutoFixPark(job, [job, child]), false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('a DEAD fix-plan child (needs_review/failed/quarantined/skipped) does not block it — nothing left to wait on', () => {
|
|
145
|
+
const job = strandedJob();
|
|
146
|
+
for (const deadStatus of ['needs_review', 'failed', 'quarantined', 'skipped']) {
|
|
147
|
+
const child = { slug: fixSlugFor(job), status: deadStatus };
|
|
148
|
+
assert.equal(isStrandedAutoFixPark(job, [job, child]), true, `child status ${deadStatus} must not block`);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('a COMPLETED fix-plan child does not block it either', () => {
|
|
153
|
+
const job = strandedJob();
|
|
154
|
+
const child = { slug: fixSlugFor(job), status: 'completed' };
|
|
155
|
+
assert.equal(isStrandedAutoFixPark(job, [job, child]), true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('isEligibleForNeedsReviewAutoResolve admits the stranded-park door alongside the other two', () => {
|
|
159
|
+
const job = strandedJob();
|
|
160
|
+
assert.equal(isEligibleForNeedsReviewAutoResolve(job, [job]), true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// --- selectExhaustedNeedsReviewTargets: age-gated selection ---
|
|
164
|
+
|
|
165
|
+
test('a fresh stranded park (under threshold) is not selected', () => {
|
|
166
|
+
const job = strandedJob({
|
|
167
|
+
statusHistory: [
|
|
168
|
+
{ to: 'needs_review', at: new Date(Date.now() - 2 * MIN_MS).toISOString(), reason: 'investigation probe exited — restoring prior status' },
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
assert.equal(selectExhaustedNeedsReviewTargets([job], Date.now(), THRESHOLD_MS).length, 0);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('a stranded park past NEEDS_REVIEW_RESOLVE_MS, under the cap, IS selected', () => {
|
|
175
|
+
const job = strandedJob();
|
|
176
|
+
const found = selectExhaustedNeedsReviewTargets([job], Date.now(), THRESHOLD_MS);
|
|
177
|
+
assert.equal(found.length, 1);
|
|
178
|
+
assert.equal(found[0].slug, job.slug);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// --- applyNeedsReviewAutoResolve: same bounded ladder, entered via the new door ---
|
|
182
|
+
|
|
183
|
+
test('computeLooksDone evidence completes the stranded row via the existing looksDone branch', async () => {
|
|
184
|
+
const projectCwd = fs.mkdtempSync(path.join(tmpHome, 'proj-stranded-'));
|
|
185
|
+
fs.mkdirSync(projectCwd, { recursive: true });
|
|
186
|
+
const git = (args) => execFileSync('git', args, { cwd: projectCwd, encoding: 'utf8' });
|
|
187
|
+
git(['init', '-q']);
|
|
188
|
+
git(['config', 'user.email', 'test@example.com']);
|
|
189
|
+
git(['config', 'user.name', 'Test']);
|
|
190
|
+
fs.writeFileSync(path.join(projectCwd, 'README.md'), 'hello\n');
|
|
191
|
+
git(['add', '-A']);
|
|
192
|
+
git(['commit', '-q', '-m', 'initial']);
|
|
193
|
+
registerActiveProject(projectCwd, path.basename(projectCwd));
|
|
194
|
+
|
|
195
|
+
const dir = resolvePrdWriteDir(projectCwd);
|
|
196
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
197
|
+
const slug = '1218-fo-01-move-scripts-lib-into-src-main-lib';
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
path.join(dir, `${slug}.md`),
|
|
200
|
+
`---\ntitle: Test PRD\ncwd: ${projectCwd}\nestimateMinutes: 30\n---\n# Implementation notes\nEdit \`src/thing.js\`.\n`,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const startedAt = new Date(Date.now() - 60 * MIN_MS).toISOString();
|
|
204
|
+
await new Promise((r) => setTimeout(r, 1100)); // git --since has 1s resolution
|
|
205
|
+
fs.mkdirSync(path.join(projectCwd, 'src'), { recursive: true });
|
|
206
|
+
fs.writeFileSync(path.join(projectCwd, 'src', 'thing.js'), 'module.exports = {};\n');
|
|
207
|
+
git(['add', '-A']);
|
|
208
|
+
git(['commit', '-q', '-m', `the actual fix for ${slug} landed`]);
|
|
209
|
+
|
|
210
|
+
const job = strandedJob({ slug, cwd: projectCwd, startedAt });
|
|
211
|
+
assert.equal(isStrandedAutoFixPark(job, [job]), true);
|
|
212
|
+
|
|
213
|
+
const looksDone = await computeLooksDone(job);
|
|
214
|
+
assert.ok(looksDone, 'expected computeLooksDone to find the attributable commit');
|
|
215
|
+
job.looksDone = looksDone;
|
|
216
|
+
|
|
217
|
+
const outcome = applyNeedsReviewAutoResolve(job, [job]);
|
|
218
|
+
assert.equal(outcome, 'completed');
|
|
219
|
+
assert.equal(job.status, 'completed');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('with no completion evidence yet, the stranded row gets one bounded requeue, same cap/logic as the other doors', () => {
|
|
223
|
+
const job = strandedJob({ exhaustedResolveAttempts: 0 });
|
|
224
|
+
assert.equal(job.looksDone, undefined);
|
|
225
|
+
const outcome = applyNeedsReviewAutoResolve(job, [job]);
|
|
226
|
+
assert.equal(outcome, 'requeued');
|
|
227
|
+
assert.equal(job.status, 'pending');
|
|
228
|
+
assert.equal(job.exhaustedResolveAttempts, 1);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('once the cap is spent, the stranded row auto-skips like every other door', () => {
|
|
232
|
+
const job = strandedJob({ exhaustedResolveAttempts: NEEDS_REVIEW_RESOLVE_CAP });
|
|
233
|
+
const outcome = applyNeedsReviewAutoResolve(job, [job]);
|
|
234
|
+
assert.equal(outcome, 'skipped');
|
|
235
|
+
assert.equal(job.status, 'skipped');
|
|
236
|
+
assert.equal(job.needsReviewAutoResolvedSkip, true);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('a live fix-plan child also blocks applyNeedsReviewAutoResolve, not just selection', () => {
|
|
240
|
+
const job = strandedJob();
|
|
241
|
+
const child = { slug: fixSlugFor(job), status: 'running' };
|
|
242
|
+
const outcome = applyNeedsReviewAutoResolve(job, [job, child]);
|
|
243
|
+
assert.equal(outcome, null, 'nothing to resolve while the fix-plan child is still live');
|
|
244
|
+
assert.equal(job.status, 'needs_review', 'must stay parked while the child is in flight');
|
|
245
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scheduler-supervisor-record.test.cjs — executeJob writes
|
|
3
|
+
* <runDir>/<slug>.supervisor.json synchronously at the `spawned pid=` point,
|
|
4
|
+
* i.e. before the child's first output and before onPid's mutate runs.
|
|
5
|
+
*
|
|
6
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/scheduler-supervisor-record.test.cjs
|
|
7
|
+
*/
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const os = require('node:os');
|
|
13
|
+
const path = require('node:path');
|
|
14
|
+
|
|
15
|
+
let tmpHome;
|
|
16
|
+
let originalHome;
|
|
17
|
+
let executeJob;
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
originalHome = process.env.HOME;
|
|
21
|
+
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-supervisor-rec-'));
|
|
22
|
+
process.env.HOME = tmpHome;
|
|
23
|
+
({ executeJob } = require('../scheduler.cjs'));
|
|
24
|
+
});
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
process.env.HOME = originalHome;
|
|
27
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => { delete process.env.SM_CLAUDE_BIN; });
|
|
30
|
+
|
|
31
|
+
function writeSlowStub() {
|
|
32
|
+
const stubPath = path.join(os.tmpdir(), `sm-sup-stub-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
|
|
33
|
+
const body = `
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok' }) + '\\n');
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}, 500);
|
|
38
|
+
`;
|
|
39
|
+
fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
|
|
40
|
+
return stubPath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test('the supervisor record exists by the time onPid fires, before the child prints anything', async () => {
|
|
44
|
+
const mainCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-sup-main-'));
|
|
45
|
+
const worktreeCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-sup-wt-'));
|
|
46
|
+
fs.mkdirSync(path.join(tmpHome, '.claude'), { recursive: true });
|
|
47
|
+
const runDir = fs.mkdtempSync(path.join(tmpHome, '.claude', 'sm-sup-run-'));
|
|
48
|
+
try {
|
|
49
|
+
const slug = `sup-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
|
|
50
|
+
const prdsDir = path.join(mainCwd, 'session-manager-operations', 'scheduler', 'prds');
|
|
51
|
+
fs.mkdirSync(prdsDir, { recursive: true });
|
|
52
|
+
fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'body', 'utf8');
|
|
53
|
+
process.env.SM_CLAUDE_BIN = writeSlowStub();
|
|
54
|
+
|
|
55
|
+
const seen = {};
|
|
56
|
+
const result = await executeJob({ slug, cwd: mainCwd }, runDir, mainCwd, (pid) => {
|
|
57
|
+
const file = path.join(runDir, `${slug}.supervisor.json`);
|
|
58
|
+
seen.exists = fs.existsSync(file);
|
|
59
|
+
seen.record = seen.exists ? JSON.parse(fs.readFileSync(file, 'utf8')) : null;
|
|
60
|
+
seen.pid = pid;
|
|
61
|
+
seen.logHasOutput = fs.readFileSync(path.join(runDir, `${slug}.log`), 'utf8').includes('"type":"result"');
|
|
62
|
+
seen.metaExists = fs.existsSync(path.join(runDir, `${slug}.meta.json`));
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
}, worktreeCwd);
|
|
65
|
+
|
|
66
|
+
expect(result.exitCode).toBe(0);
|
|
67
|
+
expect(seen.exists).toBe(true);
|
|
68
|
+
expect(seen.logHasOutput).toBe(false);
|
|
69
|
+
expect(seen.metaExists).toBe(false);
|
|
70
|
+
expect(seen.record).toMatchObject({
|
|
71
|
+
kind: 'job', slug, cwd: mainCwd, runId: path.basename(runDir), pid: seen.pid, pgid: seen.pid,
|
|
72
|
+
execCwd: worktreeCwd, worktreeDir: worktreeCwd, worktreeBranch: `sm-job/${slug}`, schedulerPid: process.pid,
|
|
73
|
+
});
|
|
74
|
+
expect(typeof seen.record.startedAt).toBe('number');
|
|
75
|
+
expect(seen.record.idleKillMs).toBeGreaterThan(0);
|
|
76
|
+
} finally {
|
|
77
|
+
fs.rmSync(mainCwd, { recursive: true, force: true });
|
|
78
|
+
fs.rmSync(worktreeCwd, { recursive: true, force: true });
|
|
79
|
+
fs.rmSync(runDir, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* subsequent tickQueue() call short-circuited on the stale flag forever,
|
|
9
9
|
* even though queue.json's `paused` field was correctly null.
|
|
10
10
|
*
|
|
11
|
-
* Run: timeout
|
|
11
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/scheduler-tick-cancel-token.test.cjs
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import { test } from 'vitest';
|
|
17
17
|
const assert = require('node:assert/strict');
|
|
18
18
|
const { applyPauseCleared, pickNextBatch } = require('../scheduler.cjs');
|
|
19
19
|
|