claude-code-session-manager 0.87.0 → 0.88.1
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-DyLWzZDf.js → AgentLibrary-kQF8_wAw.js} +1 -1
- package/dist/assets/{DataModel--mISIJ6h.js → DataModel-CY_mMnsr.js} +1 -1
- package/dist/assets/{History-C2ahUXTg.js → History-Sji2brb0.js} +1 -1
- package/dist/assets/{Hooks-BiC6oyR2.js → Hooks-CtqpO-p5.js} +1 -1
- package/dist/assets/{HostBilko-BPleEOld.js → HostBilko-DN0ydOCr.js} +1 -1
- package/dist/assets/{Library-Dc8Qst1R.js → Library-CoiLZTKh.js} +1 -1
- package/dist/assets/{ListDetail-DIXh-OLX.js → ListDetail-CmqiQ_io.js} +1 -1
- package/dist/assets/{MarkdownEditor-C90bkLXK.js → MarkdownEditor-HPoNeKhT.js} +1 -1
- package/dist/assets/{McpServers-DqcbLOLZ.js → McpServers-Bj1X3_NT.js} +1 -1
- package/dist/assets/{Memory-CW62MXlh.js → Memory-1PKjsM3b.js} +1 -1
- package/dist/assets/{Panel-Bw1FhRuF.js → Panel-I1s6ghPo.js} +1 -1
- package/dist/assets/{Permissions-BcUC-5y8.js → Permissions-BhiiZjHQ.js} +1 -1
- package/dist/assets/{Plugins-BnKx9flD.js → Plugins-gv3_fkPX.js} +2 -2
- package/dist/assets/{ProvenanceBadge-Bw5vNVPT.js → ProvenanceBadge-BGcgBd8V.js} +1 -1
- package/dist/assets/{SaveBar-CWr0O_w-.js → SaveBar-BGr9eZ4g.js} +1 -1
- package/dist/assets/{Scheduler-DYdLuUqq.js → Scheduler-Bdo_awPq.js} +7 -7
- package/dist/assets/{ScopeSwitcher-CrBLbg8s.js → ScopeSwitcher-ukl0qWaA.js} +1 -1
- package/dist/assets/{Settings-DluB-vN1.js → Settings-DJkFj22G.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-CHLSseay.js → SkillReferenceGraph-0t1Qblp6.js} +1 -1
- package/dist/assets/{Skills-gNdo_HNK.js → Skills-CeVdBGE-.js} +1 -1
- package/dist/assets/{SystemPrompt-Cru05-Ia.js → SystemPrompt-CfKvNHi3.js} +1 -1
- package/dist/assets/{TagLibrary-DNHY0xou.js → TagLibrary-DAzNaUPd.js} +1 -1
- package/dist/assets/{TiptapBody-I4lmbCgP.js → TiptapBody--S_rGXoA.js} +1 -1
- package/dist/assets/{Toggle-bWMHjmRh.js → Toggle-Dszm0xnp.js} +1 -1
- package/dist/assets/{index-DV3PorRY.css → index-BHTX4OTc.css} +1 -1
- package/dist/assets/{index-fc_JjdxL.js → index-Dp4Rc--q.js} +356 -356
- package/dist/assets/{settingsSchema-BfhtZnGD.js → settingsSchema-BLbssMYo.js} +1 -1
- 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 +10 -13
- 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 +28 -71
- package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
- 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 +2 -2
- 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 +15 -1
- package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
- package/src/main/__tests__/health-tick-liveness.test.cjs +27 -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__/opsErrorLogTelemetryTap.test.cjs +25 -1
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +33 -3
- 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__/prdLocationsArchived.test.cjs +20 -13
- 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 +9 -9
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
- package/src/main/__tests__/queueHistory.test.cjs +2 -2
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
- 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 +2 -2
- 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 +78 -97
- 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 +46 -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-launch-failure.test.cjs +3 -5
- package/src/main/__tests__/scheduler-looks-done.test.cjs +26 -7
- 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-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 +23 -4
- 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 +81 -1
- package/src/main/__tests__/scheduler-stall-per-project.test.cjs +14 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +4 -4
- 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 +37 -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 +4 -2
- package/src/main/config.cjs +2 -3
- package/src/main/docEdit.cjs +4 -2
- package/src/main/health.cjs +276 -52
- package/src/main/heapSnapshot.cjs +2 -2
- package/src/main/historyAggregator.cjs +1 -1
- package/src/main/index.cjs +61 -10
- package/src/main/ipcSchemas.cjs +6 -17
- package/src/main/lib/__tests__/auditLog.test.cjs +38 -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 +1 -1
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
- package/src/main/lib/__tests__/gateFixtures.json +20 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +8 -1
- 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__/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__/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 +61 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +63 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +73 -0
- package/src/main/lib/activeIndexRebuild.cjs +1 -3
- package/src/main/lib/activeSessions.cjs +20 -88
- package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
- package/src/main/lib/agentModelResolve.cjs +33 -1
- package/src/main/lib/agentPersonaSchema.cjs +2 -2
- package/src/main/lib/auditLog.cjs +30 -5
- 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/dispatchLoop.cjs +40 -0
- package/src/main/lib/effectiveModelInfo.cjs +9 -10
- package/src/main/lib/ephemeralCwd.cjs +7 -28
- package/src/main/lib/gitWorktree.cjs +95 -8
- package/src/main/lib/guardShims.cjs +3 -3
- package/src/main/lib/historyRollup.cjs +6 -7
- package/src/main/lib/instanceLock.cjs +32 -5
- package/src/main/lib/jobSupervisorRecord.cjs +147 -0
- package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
- package/src/main/lib/localAdminHttp.cjs +9 -27
- package/src/main/lib/mcpToolCatalog.cjs +29 -77
- package/src/main/lib/opsOwnership.cjs +27 -19
- package/src/main/lib/prdAuthoringSeed.cjs +36 -0
- package/src/main/lib/prdLocations.cjs +48 -1
- 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/queueHistory.cjs +8 -7
- package/src/main/lib/queueStore.cjs +57 -37
- package/src/main/lib/quietMachineLease.cjs +19 -2
- 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 +2 -2
- package/src/main/lib/scheduleJobTransitions.cjs +29 -2
- package/src/main/lib/schedulerBatch.cjs +27 -2
- 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 +53 -6
- package/src/main/lib/watchdogHelpers.cjs +70 -37
- 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.cjs +1607 -383
- 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/usage.cjs +126 -62
- package/src/preload/api.d.ts +18 -64
- package/src/preload/index.cjs +2 -4
- package/src/seed/agents/project-home-builder.md +31 -48
- package/screenshots/.gitkeep +0 -0
- package/screenshots/README-screenshots.md +0 -13
- 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
- package/web/README.md +0 -41
- package/web/project-pages/logic/dist/logic.cjs +0 -4709
- package/web/project-pages/render.cjs +0 -70
- package/web/project-pages/renderer/dist/renderer.cjs +0 -18900
- package/web/project-pages/validate-summary.cjs +0 -62
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runVerify-landed-commit-outranks.test.cjs — ground truth (exit 0 + a commit
|
|
3
|
+
* landed THIS run + no explicit FAIL sentinel) outranks the transcript-
|
|
4
|
+
* pattern heuristics (final-20% is_error, FAIL/Traceback,
|
|
5
|
+
* ModuleNotFoundError), so a self-recovered tool error no longer parks a
|
|
6
|
+
* green, committed run.
|
|
7
|
+
*
|
|
8
|
+
* Fixture: the REAL run log for 1218-fo-01-move-scripts-lib-into-src-main-lib
|
|
9
|
+
* (session-manager-operations/reviews/2026-09-13-scheduler-stability-
|
|
10
|
+
* investigation.md), copied verbatim from
|
|
11
|
+
* ~/.claude/session-manager/scheduled-plans/runs/2026-09-13T21-52-51-192Z/ —
|
|
12
|
+
* a `git apply` "patch does not apply" tool error at event 179/209 (inside
|
|
13
|
+
* the final 20% of the transcript) that the agent retried and resolved 3
|
|
14
|
+
* events later, in a run that landed commit d1edf15 and was STILL parked
|
|
15
|
+
* needs_review (verdicts.json: verdict transcript_errors, reason
|
|
16
|
+
* "is_error=true in final 20% of transcript (event 179/209)"), spawning a
|
|
17
|
+
* $3.74 fix-plan investigation into work that needed no fix.
|
|
18
|
+
*
|
|
19
|
+
* The real run's own SCHEDULER_VERDICT sentinel is an explicit FAIL — an
|
|
20
|
+
* honest, UNRELATED report that test:unit was red for proven pre-existing/
|
|
21
|
+
* environmental reasons (shared /tmp contention), not a regression from this
|
|
22
|
+
* PRD's move. Per this PRD's own AC, an explicit FAIL sentinel must keep
|
|
23
|
+
* today's behavior byte-for-byte, so scenarios (a)/(b)/(d) below use a
|
|
24
|
+
* variant of the fixture with that trailing SCHEDULER_VERDICT line stripped
|
|
25
|
+
* (sentinel === null) — the majority real-world park shape this rule targets
|
|
26
|
+
* (the findings report measured 77 of 212 parks as transcript_errors with NO
|
|
27
|
+
* sentinel at all, vs. this one specific row's own additional, unrelated
|
|
28
|
+
* FAIL claim). Scenario (c) reuses the pristine, byte-for-byte-unmodified
|
|
29
|
+
* fixture to prove the real, still-present FAIL sentinel is honored exactly
|
|
30
|
+
* as before this change.
|
|
31
|
+
*
|
|
32
|
+
* Run standalone: timeout 120 npx vitest run src/main/__tests__/runVerify-landed-commit-outranks.test.cjs
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { test } from 'vitest';
|
|
36
|
+
const assert = require('node:assert/strict');
|
|
37
|
+
const os = require('node:os');
|
|
38
|
+
const fs = require('node:fs');
|
|
39
|
+
const path = require('node:path');
|
|
40
|
+
const { execFileSync } = require('node:child_process');
|
|
41
|
+
const { verifyRun } = require('../runVerify.cjs');
|
|
42
|
+
|
|
43
|
+
const SLUG = '1218-fo-01-move-scripts-lib-into-src-main-lib';
|
|
44
|
+
const FIXTURE_PATH = path.join(__dirname, 'fixtures', `${SLUG}.log`);
|
|
45
|
+
const LANDED_COMMIT = 'd1edf15be50a783f08a55e6de328780f0feb3cb0';
|
|
46
|
+
|
|
47
|
+
function makeTmpDir() {
|
|
48
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'run-verify-landed-commit-outranks-test-'));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function rmdir(dir) {
|
|
52
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* */ }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function makeTmpGitRepo() {
|
|
56
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'run-verify-landed-commit-outranks-repo-'));
|
|
57
|
+
execFileSync('git', ['init', '-q'], { cwd: dir });
|
|
58
|
+
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir });
|
|
59
|
+
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: dir });
|
|
60
|
+
fs.writeFileSync(path.join(dir, 'README.md'), 'init\n');
|
|
61
|
+
execFileSync('git', ['add', 'README.md'], { cwd: dir });
|
|
62
|
+
execFileSync('git', ['commit', '-q', '-m', 'init'], { cwd: dir });
|
|
63
|
+
return dir;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writePrd(dir, slug) {
|
|
67
|
+
const text = '---\ntitle: Test PRD\ncwd: /tmp\nestimateMinutes: 30\n---\n# Move scripts/lib into src/main/lib\n';
|
|
68
|
+
fs.writeFileSync(path.join(dir, `${slug}.md`), text);
|
|
69
|
+
return path.join(dir, `${slug}.md`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Load the real fixture log, optionally stripping the trailing
|
|
74
|
+
* `SCHEDULER_VERDICT: FAIL ...` line from the final `{"type":"result",...}`
|
|
75
|
+
* event's `result` field so the sentinel scan sees no sentinel at all.
|
|
76
|
+
*/
|
|
77
|
+
function loadFixture({ stripFailSentinel }) {
|
|
78
|
+
const raw = fs.readFileSync(FIXTURE_PATH, 'utf8');
|
|
79
|
+
if (!stripFailSentinel) return raw;
|
|
80
|
+
const lines = raw.split('\n');
|
|
81
|
+
const idx = lines.findIndex((l) => l.includes('"type":"result"'));
|
|
82
|
+
assert.ok(idx !== -1, 'fixture must contain a type:result line');
|
|
83
|
+
const obj = JSON.parse(lines[idx]);
|
|
84
|
+
assert.ok(/SCHEDULER_VERDICT:\s*FAIL/.test(obj.result), 'fixture result must carry the real FAIL sentinel before stripping');
|
|
85
|
+
obj.result = obj.result.replace(/\n?SCHEDULER_VERDICT:\s*FAIL[^\n]*$/, '');
|
|
86
|
+
assert.ok(!/SCHEDULER_VERDICT/.test(obj.result), 'sentinel must be fully removed from the stripped variant');
|
|
87
|
+
lines[idx] = JSON.stringify(obj);
|
|
88
|
+
return lines.join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function writeLog(dir, slug, text) {
|
|
92
|
+
fs.writeFileSync(path.join(dir, `${slug}.log`), text);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// (a) exit 0 + commit landed THIS run + no sentinel at all → clean, demoted to annotation.
|
|
96
|
+
test('exit 0 + landed commit this run + no FAIL sentinel → clean with annotation (1218-fo-01)', async () => {
|
|
97
|
+
const tmp = makeTmpDir();
|
|
98
|
+
const repo = makeTmpGitRepo();
|
|
99
|
+
try {
|
|
100
|
+
writeLog(tmp, SLUG, loadFixture({ stripFailSentinel: true }));
|
|
101
|
+
const prdPath = writePrd(tmp, SLUG);
|
|
102
|
+
const verdict = await verifyRun({
|
|
103
|
+
runDir: tmp,
|
|
104
|
+
prdPath,
|
|
105
|
+
queueEntry: { slug: SLUG, status: 'running', cwd: repo },
|
|
106
|
+
allJobs: [],
|
|
107
|
+
committedDuringRun: false,
|
|
108
|
+
jobLandedCommitThisRun: LANDED_COMMIT,
|
|
109
|
+
exitCode: 0,
|
|
110
|
+
});
|
|
111
|
+
assert.equal(verdict.verdict, 'clean', `expected clean, got ${verdict.verdict}: ${verdict.reason}`);
|
|
112
|
+
assert.equal(verdict.downgradeTo, null);
|
|
113
|
+
const sidecar = JSON.parse(fs.readFileSync(path.join(tmp, `${SLUG}.verdicts.json`), 'utf8'));
|
|
114
|
+
assert.ok(Array.isArray(sidecar.annotations) && sidecar.annotations.length > 0, 'the demoted is_error hit must be recorded as an annotation');
|
|
115
|
+
assert.ok(sidecar.annotations.some((a) => a.verdict === 'transcript_errors'), 'annotation must carry the original transcript_errors verdict');
|
|
116
|
+
} finally { rmdir(tmp); rmdir(repo); }
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// (b) the same (stripped-sentinel) log, but exitCode !== 0 → keeps today's behavior.
|
|
120
|
+
test('non-zero exit → transcript_errors (evidence never applies to a failing run)', async () => {
|
|
121
|
+
const tmp = makeTmpDir();
|
|
122
|
+
const repo = makeTmpGitRepo();
|
|
123
|
+
try {
|
|
124
|
+
writeLog(tmp, SLUG, loadFixture({ stripFailSentinel: true }));
|
|
125
|
+
const prdPath = writePrd(tmp, SLUG);
|
|
126
|
+
const verdict = await verifyRun({
|
|
127
|
+
runDir: tmp,
|
|
128
|
+
prdPath,
|
|
129
|
+
queueEntry: { slug: SLUG, status: 'running', cwd: repo },
|
|
130
|
+
allJobs: [],
|
|
131
|
+
committedDuringRun: false,
|
|
132
|
+
jobLandedCommitThisRun: LANDED_COMMIT,
|
|
133
|
+
exitCode: 1,
|
|
134
|
+
});
|
|
135
|
+
assert.equal(verdict.verdict, 'transcript_errors', `expected transcript_errors, got ${verdict.verdict}: ${verdict.reason}`);
|
|
136
|
+
assert.equal(verdict.downgradeTo, 'needs_review');
|
|
137
|
+
} finally { rmdir(tmp); rmdir(repo); }
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// (c) the pristine, byte-for-byte-unmodified fixture — its own real FAIL sentinel — keeps today's behavior.
|
|
141
|
+
test('explicit FAIL sentinel (the real, unmodified fixture) → transcript_errors byte-for-byte', async () => {
|
|
142
|
+
const tmp = makeTmpDir();
|
|
143
|
+
const repo = makeTmpGitRepo();
|
|
144
|
+
try {
|
|
145
|
+
writeLog(tmp, SLUG, loadFixture({ stripFailSentinel: false }));
|
|
146
|
+
const prdPath = writePrd(tmp, SLUG);
|
|
147
|
+
const verdict = await verifyRun({
|
|
148
|
+
runDir: tmp,
|
|
149
|
+
prdPath,
|
|
150
|
+
queueEntry: { slug: SLUG, status: 'running', cwd: repo },
|
|
151
|
+
allJobs: [],
|
|
152
|
+
committedDuringRun: false,
|
|
153
|
+
jobLandedCommitThisRun: LANDED_COMMIT,
|
|
154
|
+
exitCode: 0,
|
|
155
|
+
});
|
|
156
|
+
assert.equal(verdict.verdict, 'transcript_errors', `expected transcript_errors, got ${verdict.verdict}: ${verdict.reason}`);
|
|
157
|
+
assert.equal(verdict.downgradeTo, 'needs_review');
|
|
158
|
+
assert.equal(verdict.sentinel, 'fail');
|
|
159
|
+
} finally { rmdir(tmp); rmdir(repo); }
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// (d) no landed-commit evidence at all → transcript_errors (evidence gate requires attribution).
|
|
163
|
+
test('no landed commit evidence → transcript_errors (evidence gate requires attribution)', async () => {
|
|
164
|
+
const tmp = makeTmpDir();
|
|
165
|
+
const repo = makeTmpGitRepo();
|
|
166
|
+
try {
|
|
167
|
+
writeLog(tmp, SLUG, loadFixture({ stripFailSentinel: true }));
|
|
168
|
+
const prdPath = writePrd(tmp, SLUG);
|
|
169
|
+
const verdict = await verifyRun({
|
|
170
|
+
runDir: tmp,
|
|
171
|
+
prdPath,
|
|
172
|
+
queueEntry: { slug: SLUG, status: 'running', cwd: repo },
|
|
173
|
+
allJobs: [],
|
|
174
|
+
committedDuringRun: false,
|
|
175
|
+
jobLandedCommitThisRun: null,
|
|
176
|
+
exitCode: 0,
|
|
177
|
+
});
|
|
178
|
+
assert.equal(verdict.verdict, 'transcript_errors', `expected transcript_errors, got ${verdict.verdict}: ${verdict.reason}`);
|
|
179
|
+
assert.equal(verdict.downgradeTo, 'needs_review');
|
|
180
|
+
} finally { rmdir(tmp); rmdir(repo); }
|
|
181
|
+
});
|
|
@@ -1140,7 +1140,7 @@ function noOpRunEvents(resultText) {
|
|
|
1140
1140
|
test('no_verdict_sentinel: no tool_result issues, no sentinel, no commit → needs_review', async () => {
|
|
1141
1141
|
const tmp = makeTmpDir();
|
|
1142
1142
|
try {
|
|
1143
|
-
const slug = '406-
|
|
1143
|
+
const slug = '406-capture-panel-ui';
|
|
1144
1144
|
writeLog(tmp, slug, noOpRunEvents('I need clarification on which capture API to target before proceeding.'));
|
|
1145
1145
|
const prdPath = writePrd(tmp, slug, '# Browser capture panel UI');
|
|
1146
1146
|
const verdict = await verifyRun({
|
|
@@ -1160,7 +1160,7 @@ test('no_verdict_sentinel: no tool_result issues, no sentinel, no commit → nee
|
|
|
1160
1160
|
test('no_verdict_sentinel guard: same run but committedDuringRun:true → stays clean', async () => {
|
|
1161
1161
|
const tmp = makeTmpDir();
|
|
1162
1162
|
try {
|
|
1163
|
-
const slug = '406-
|
|
1163
|
+
const slug = '406-capture-panel-ui-committed';
|
|
1164
1164
|
writeLog(tmp, slug, noOpRunEvents('I need clarification on which capture API to target before proceeding.'));
|
|
1165
1165
|
const prdPath = writePrd(tmp, slug, '# Browser capture panel UI');
|
|
1166
1166
|
const verdict = await verifyRun({
|
|
@@ -1180,7 +1180,7 @@ test('no_verdict_sentinel guard: same run but committedDuringRun:true → stays
|
|
|
1180
1180
|
test('no_verdict_sentinel guard: same run but with SCHEDULER_VERDICT: PASS sentinel + a commit → stays clean', async () => {
|
|
1181
1181
|
const tmp = makeTmpDir();
|
|
1182
1182
|
try {
|
|
1183
|
-
const slug = '406-
|
|
1183
|
+
const slug = '406-capture-panel-ui-sentinel';
|
|
1184
1184
|
writeLog(tmp, slug, noOpRunEvents('All acceptance criteria verified.\nSCHEDULER_VERDICT: PASS'));
|
|
1185
1185
|
const prdPath = writePrd(tmp, slug, '# Browser capture panel UI');
|
|
1186
1186
|
const verdict = await verifyRun({
|
|
@@ -1634,7 +1634,7 @@ test('isMergeMainSlug matches the NN-prXXX-merge-main and NN-fix-prXXX-merge-mai
|
|
|
1634
1634
|
assert.equal(isMergeMainSlug('565-pr188-merge-main'), true);
|
|
1635
1635
|
assert.equal(isMergeMainSlug('571-fix-pr141-merge-main'), true);
|
|
1636
1636
|
assert.equal(isMergeMainSlug('523-fix-bounded-fix-plan-retry'), false);
|
|
1637
|
-
assert.equal(isMergeMainSlug('406-
|
|
1637
|
+
assert.equal(isMergeMainSlug('406-capture-panel-ui'), false);
|
|
1638
1638
|
assert.equal(isMergeMainSlug(''), false);
|
|
1639
1639
|
assert.equal(isMergeMainSlug(undefined), false);
|
|
1640
1640
|
});
|
|
@@ -1727,7 +1727,7 @@ test('merge-main exemption: gh call throws → falls through to pass_no_commit,
|
|
|
1727
1727
|
test('merge-main exemption: non-merge-main slug never attempts a gh call', async () => {
|
|
1728
1728
|
const tmp = makeTmpDir();
|
|
1729
1729
|
try {
|
|
1730
|
-
const slug = '406-
|
|
1730
|
+
const slug = '406-capture-panel-ui';
|
|
1731
1731
|
writeLog(tmp, slug, noOpRunEvents('All acceptance criteria verified.\nSCHEDULER_VERDICT: PASS'));
|
|
1732
1732
|
const prdPath = writePrd(tmp, slug, '# Browser capture panel UI');
|
|
1733
1733
|
let ghCalls = 0;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* is types-only (erased at build time, nothing to import at test runtime), so
|
|
7
7
|
* each consumer keeps its own hand-written mirror of the status enum:
|
|
8
8
|
* - src/preload/api.d.ts's `ScheduleJobStatus` union
|
|
9
|
-
* - src/renderer/
|
|
9
|
+
* - src/renderer/lib/jobStatus.ts's `JobStatus` union
|
|
10
10
|
* - src/renderer/components/SchedulePanel.tsx's `FilterStatus` union
|
|
11
11
|
* (which also carries the UI-only 'all' meta-value)
|
|
12
12
|
*
|
|
@@ -52,8 +52,8 @@ test('src/preload/api.d.ts ScheduleJobStatus matches JOB_STATUSES', () => {
|
|
|
52
52
|
expect(sorted(values)).toEqual(sorted(JOB_STATUSES));
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
test('src/renderer/
|
|
56
|
-
const source = readSource('src/renderer/
|
|
55
|
+
test('src/renderer/lib/jobStatus.ts JobStatus matches JOB_STATUSES', () => {
|
|
56
|
+
const source = readSource('src/renderer/lib/jobStatus.ts');
|
|
57
57
|
const values = extractQuotedList(source, /export type JobStatus = ([^\n]+)/);
|
|
58
58
|
expect(sorted(values)).toEqual(sorted(JOB_STATUSES));
|
|
59
59
|
});
|
|
@@ -24,8 +24,8 @@ function uniqueSlug(label) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function readAuditRecordsForSlug(slug) {
|
|
27
|
-
if (!fs.existsSync(auditLog.
|
|
28
|
-
return fs.readFileSync(auditLog.
|
|
27
|
+
if (!fs.existsSync(auditLog.auditLogPath())) return [];
|
|
28
|
+
return fs.readFileSync(auditLog.auditLogPath(), 'utf8')
|
|
29
29
|
.split('\n')
|
|
30
30
|
.filter(Boolean)
|
|
31
31
|
.map((line) => {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scheduler-adopted-run-supervision.test.cjs — an executor the boot spared
|
|
3
|
+
* (`adoptedAtBoot`) is supervised like a freshly spawned one: budget, idle-tail
|
|
4
|
+
* and deadman re-armed from the durable record, quietMachine lease re-acquired,
|
|
5
|
+
* and a budget kill parks needs_review/budget_exceeded via classifyBudgetKill.
|
|
6
|
+
*
|
|
7
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/scheduler-adopted-run-supervision.test.cjs
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
import { test, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
13
|
+
const { superviseAdoptedRuns, evaluateAdoptedRun } = require('../lib/adoptedRunSupervisor.cjs');
|
|
14
|
+
const quietMachineLease = require('../lib/quietMachineLease.cjs');
|
|
15
|
+
const { classifyBudgetKill } = require('../scheduler.cjs');
|
|
16
|
+
|
|
17
|
+
const MIN = 60_000;
|
|
18
|
+
const T0 = Date.parse('2026-09-18T12:00:00Z');
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.useFakeTimers();
|
|
22
|
+
vi.setSystemTime(T0);
|
|
23
|
+
quietMachineLease.__resetForTests();
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
vi.useRealTimers();
|
|
27
|
+
quietMachineLease.__resetForTests();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function harness({ rows, records, logMtimeMs = () => Date.now(), alive = () => true }) {
|
|
31
|
+
const kills = [];
|
|
32
|
+
const stamps = [];
|
|
33
|
+
const supervised = [];
|
|
34
|
+
const registry = new Map();
|
|
35
|
+
const ctx = {
|
|
36
|
+
registry,
|
|
37
|
+
runDir: (j) => `/runs/${j.runId}`,
|
|
38
|
+
readRecord: (_dir, slug) => records[slug] ?? null,
|
|
39
|
+
lease: quietMachineLease,
|
|
40
|
+
markSupervised: async (j) => { supervised.push(j.slug); },
|
|
41
|
+
makeDeps: (j, record) => ({
|
|
42
|
+
logPath: `/runs/${j.runId}/${j.slug}.log`,
|
|
43
|
+
statLogMtimeMs: () => logMtimeMs(j.slug),
|
|
44
|
+
pidAlive: alive,
|
|
45
|
+
identityOf: () => record.identity,
|
|
46
|
+
isDifferentProcess: () => false,
|
|
47
|
+
killGroup: (pgid, signal) => { kills.push({ slug: j.slug, pgid, signal }); },
|
|
48
|
+
stampKill: async (kind, reason) => { stamps.push({ slug: j.slug, kind, reason }); },
|
|
49
|
+
checkIntervalMs: MIN,
|
|
50
|
+
sigkillAfterMs: 30_000,
|
|
51
|
+
defaultMaxDurationMs: 4 * 60 * MIN,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
return { ctx, kills, stamps, supervised, registry, run: () => superviseAdoptedRuns(rows, ctx) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const row = (slug, extra = {}) => ({ slug, status: 'running', runId: 'r1', adoptedAtBoot: new Date(T0).toISOString(), ...extra });
|
|
58
|
+
const record = (slug, extra = {}) => ({
|
|
59
|
+
slug, pid: 4242, pgid: 4242, identity: { startTicks: 1 }, startedAt: T0, budgetMs: 45 * MIN, maxDurationMs: null, idleKillMs: 20 * MIN, ...extra,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('an adopted run past its budget is killed and its budget kill classifies needs_review/budget_exceeded', async () => {
|
|
63
|
+
const h = harness({ rows: [row('over')], records: { over: record('over', { startedAt: T0 - 50 * MIN }) } });
|
|
64
|
+
expect(await h.run()).toEqual(['over']);
|
|
65
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
66
|
+
expect(h.stamps).toHaveLength(1);
|
|
67
|
+
expect(h.stamps[0].kind).toBe('budget');
|
|
68
|
+
expect(h.kills).toEqual([{ slug: 'over', pgid: 4242, signal: 'SIGTERM' }]);
|
|
69
|
+
// SIGKILL follows if the group survives SIGTERM.
|
|
70
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
71
|
+
expect(h.kills.map((k) => k.signal)).toEqual(['SIGTERM', 'SIGKILL']);
|
|
72
|
+
// Same classifier as a native budget kill.
|
|
73
|
+
const outcome = classifyBudgetKill({ killedByWatchdog: h.stamps[0].kind, budgetKillReason: h.stamps[0].reason, exitCode: null }, null);
|
|
74
|
+
expect(outcome.status).toBe('needs_review');
|
|
75
|
+
expect(outcome.reason).toMatch(/wall-clock budget exceeded/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('a run within budget is untouched until the interval crosses the budget', async () => {
|
|
79
|
+
const h = harness({ rows: [row('ok')], records: { ok: record('ok', { startedAt: T0 - 10 * MIN }) } });
|
|
80
|
+
await h.run();
|
|
81
|
+
await vi.advanceTimersByTimeAsync(5 * MIN);
|
|
82
|
+
expect(h.kills).toEqual([]);
|
|
83
|
+
expect(h.stamps).toEqual([]);
|
|
84
|
+
// 10m + 36m elapsed > 45m budget: the re-armed interval now fires.
|
|
85
|
+
await vi.advanceTimersByTimeAsync(31 * MIN);
|
|
86
|
+
expect(h.stamps.map((s) => s.kind)).toEqual(['budget']);
|
|
87
|
+
expect(h.kills[0].signal).toBe('SIGTERM');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('idle-tail: a stalled log past idleKillMs is SIGTERMed; deadman SIGKILLs at maxDurationMs', async () => {
|
|
91
|
+
const idle = harness({
|
|
92
|
+
rows: [row('idle')], records: { idle: record('idle', { startedAt: T0 - 5 * MIN }) },
|
|
93
|
+
logMtimeMs: () => T0 - 25 * MIN,
|
|
94
|
+
});
|
|
95
|
+
await idle.run();
|
|
96
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
97
|
+
expect(idle.stamps.map((s) => s.kind)).toEqual(['idle-tail']);
|
|
98
|
+
expect(idle.kills[0].signal).toBe('SIGTERM');
|
|
99
|
+
|
|
100
|
+
const dead = harness({
|
|
101
|
+
rows: [row('dm')], records: { dm: record('dm', { startedAt: T0 - 5 * MIN, budgetMs: null, maxDurationMs: 4 * 60 * MIN }) },
|
|
102
|
+
});
|
|
103
|
+
await dead.run();
|
|
104
|
+
await vi.advanceTimersByTimeAsync(4 * 60 * MIN);
|
|
105
|
+
expect(dead.stamps.map((s) => s.kind)).toEqual(['deadman']);
|
|
106
|
+
expect(dead.kills.map((k) => k.signal)).toEqual(['SIGKILL']);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('a null record.maxDurationMs falls back to the native 4h deadman', () => {
|
|
110
|
+
const rec = record('x', { budgetMs: null, startedAt: T0 - 5 * 60 * MIN });
|
|
111
|
+
expect(evaluateAdoptedRun(rec, { now: T0, logMtimeMs: T0, defaultMaxDurationMs: 4 * 60 * MIN })?.kind).toBe('deadman');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('the quietMachine lease is re-acquired, supervisedAt stamped once, and an exempt (null budget) run is not budget-killed', async () => {
|
|
115
|
+
const h = harness({
|
|
116
|
+
rows: [row('quiet', { quietMachine: true })],
|
|
117
|
+
records: { quiet: record('quiet', { budgetMs: null, startedAt: T0 - 90 * MIN }) },
|
|
118
|
+
});
|
|
119
|
+
expect(await h.run()).toEqual(['quiet']);
|
|
120
|
+
expect(quietMachineLease.holder()).toBe('quiet');
|
|
121
|
+
expect(h.supervised).toEqual(['quiet']);
|
|
122
|
+
await vi.advanceTimersByTimeAsync(10 * MIN);
|
|
123
|
+
expect(h.kills).toEqual([]);
|
|
124
|
+
// A second dispatch-loop pass does not re-arm or re-stamp.
|
|
125
|
+
expect(await h.run()).toEqual([]);
|
|
126
|
+
expect(h.supervised).toEqual(['quiet']);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('a pid that is gone is never signalled; rows that stop running drop their supervisor', async () => {
|
|
130
|
+
const h = harness({
|
|
131
|
+
rows: [row('gone')], records: { gone: record('gone', { startedAt: T0 - 90 * MIN }) }, alive: () => false,
|
|
132
|
+
});
|
|
133
|
+
await h.run();
|
|
134
|
+
await vi.advanceTimersByTimeAsync(2 * MIN);
|
|
135
|
+
expect(h.kills).toEqual([]);
|
|
136
|
+
expect(h.stamps).toEqual([]);
|
|
137
|
+
|
|
138
|
+
const live = harness({ rows: [row('fin')], records: { fin: record('fin') } });
|
|
139
|
+
await live.run();
|
|
140
|
+
expect(live.registry.size).toBe(1);
|
|
141
|
+
await superviseAdoptedRuns([{ slug: 'fin', status: 'completed', runId: 'r1' }], live.ctx);
|
|
142
|
+
expect(live.registry.size).toBe(0);
|
|
143
|
+
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* scheduler-autofix-select.test.cjs — unit tests for selectAutoFixTargets.
|
|
3
3
|
*
|
|
4
|
-
* Run: timeout
|
|
4
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/scheduler-autofix-select.test.cjs
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import { test } from 'vitest';
|
|
10
10
|
const assert = require('node:assert/strict');
|
|
11
11
|
const {
|
|
12
12
|
selectAutoFixTargets, isUnresolvableNeedsReview, isRescanCandidate,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* scheduler-autopromote.test.cjs — unit tests for isPromotableOriginal.
|
|
3
3
|
*
|
|
4
|
-
* Run: timeout
|
|
4
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/scheduler-autopromote.test.cjs
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import { test } from 'vitest';
|
|
10
10
|
const assert = require('node:assert/strict');
|
|
11
11
|
const { isPromotableOriginal, healTargetForFix } = require('../scheduler.cjs');
|
|
12
12
|
|
|
@@ -26,6 +26,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
|
26
26
|
const fs = require('node:fs');
|
|
27
27
|
const os = require('node:os');
|
|
28
28
|
const path = require('node:path');
|
|
29
|
+
const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
|
|
29
30
|
|
|
30
31
|
let tmpHome;
|
|
31
32
|
let originalHome;
|
|
@@ -53,8 +54,7 @@ afterEach(() => {
|
|
|
53
54
|
// Stub `claude` binary: dumps the two env vars it received into a marker
|
|
54
55
|
// file, then emits a stream-json success result and exits 0.
|
|
55
56
|
function writeClaudeStub() {
|
|
56
|
-
|
|
57
|
-
const body = `
|
|
57
|
+
return claudeStub.writeClaudeStub({ body: `
|
|
58
58
|
const fs = require('fs');
|
|
59
59
|
const path = require('path');
|
|
60
60
|
fs.writeFileSync(path.join(process.cwd(), 'env.marker'), JSON.stringify({
|
|
@@ -63,9 +63,7 @@ function writeClaudeStub() {
|
|
|
63
63
|
}), 'utf8');
|
|
64
64
|
process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok' }) + '\\n');
|
|
65
65
|
process.exit(0);
|
|
66
|
-
|
|
67
|
-
fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
|
|
68
|
-
return stubPath;
|
|
66
|
+
` });
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
test('BASH_MAX_TIMEOUT_MS stays strictly below IDLE_OUTPUT_KILL_MS with real margin', () => {
|