claude-code-session-manager 0.97.0 → 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/cli.cjs +16 -4
- package/bin/node-floor.cjs +41 -0
- package/dist/assets/{DataModel-D93FoaOm.js → DataModel-HdR0AxFq.js} +1 -1
- package/dist/assets/{History-CXxxqNP1.js → History-Dr4Jr_5z.js} +2 -2
- package/dist/assets/{Hooks-CzeYFQjq.js → Hooks-NhrvH3JP.js} +3 -3
- package/dist/assets/{Library-D2zPkwOC.js → Library-Dv_EtIMr.js} +1 -1
- package/dist/assets/{MarkdownEditor-DbinP78A.js → MarkdownEditor-DTKDWV_n.js} +1 -1
- package/dist/assets/{McpServers-Bu_sVGLr.js → McpServers-M034G9B3.js} +2 -2
- package/dist/assets/{Memory-BgiI6LZ_.js → Memory-DzF1B4gx.js} +6 -6
- package/dist/assets/{Permissions-C1MMzZRm.js → Permissions-DrgbSGWl.js} +3 -3
- package/dist/assets/{Plugins-Dhu6wAZi.js → Plugins-Bd3ef35w.js} +2 -2
- package/dist/assets/{ProvenanceBadge-C3AcWKMG.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
- package/dist/assets/{SaveBar-plwYm0iO.js → SaveBar-rimJem-Y.js} +1 -1
- package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
- package/dist/assets/{ScopeSwitcher-DFy0vv_o.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
- package/dist/assets/Settings-Dc1F_sSg.js +3 -0
- package/dist/assets/{SkillReferenceGraph-DsI7hRPB.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
- package/dist/assets/{Skills-3E748UfB.js → Skills-DRXd18gu.js} +2 -2
- package/dist/assets/{SystemPrompt-BqmfUJOd.js → SystemPrompt-ahy5-Fwe.js} +1 -1
- package/dist/assets/{TagLibrary-CPz6fO8Q.js → TagLibrary-C_tToDeL.js} +1 -1
- package/dist/assets/{TiptapBody-D9EoBQ1P.js → TiptapBody-DvdefS6K.js} +1 -1
- package/dist/assets/{Toggle-BHAmZNDn.js → Toggle-6Jl5Njc6.js} +1 -1
- package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
- package/dist/assets/{index-tn5JIVPj.js → index-vx8O73l8.js} +497 -502
- package/dist/assets/{settingsSchema-Crqy_aNz.js → settingsSchema-D6iRguNV.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +26 -29
- package/plugins/CLAUDE.md +6 -6
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +23 -19
- package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
- package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
- package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
- package/scripts/README.md +3 -11
- package/scripts/audit-ops-hygiene.cjs +3 -3
- package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
- package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
- package/scripts/mint-epic.cjs +2 -3
- package/scripts/scheduler-mcp-server.cjs +36 -7
- package/src/main/agentLibrary.cjs +22 -2
- package/src/main/build-info.json +4 -4
- package/src/main/config.cjs +41 -38
- package/src/main/docEdit.cjs +4 -1
- package/src/main/files.cjs +2 -5
- package/src/main/health.cjs +1 -1
- package/src/main/index.cjs +23 -10
- package/src/main/ipcSchemas.cjs +28 -35
- package/src/main/lib/agentPersonaSchema.cjs +13 -2
- package/src/main/lib/atomicFs.cjs +116 -0
- package/src/main/lib/branchSweep.cjs +13 -12
- package/src/main/lib/buildTarget.cjs +3 -3
- package/src/main/lib/claudeCliCaps.cjs +129 -0
- package/src/main/lib/credentials.cjs +2 -4
- package/src/main/lib/crossProjectFeedback.cjs +3 -3
- package/src/main/lib/cwdClassify.cjs +15 -3
- package/src/main/lib/definitionOfDone.cjs +207 -125
- package/src/main/lib/dodDrainHook.cjs +11 -5
- package/src/main/lib/epicMint.cjs +2 -4
- package/src/main/lib/epicStatusMirror.cjs +8 -10
- package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
- package/src/main/lib/gateAuthority.cjs +96 -0
- package/src/main/lib/gitExec.cjs +69 -0
- package/src/main/lib/gitWorktree.cjs +656 -92
- package/src/main/lib/instanceLock.cjs +5 -8
- package/src/main/lib/launchFailure.cjs +2 -3
- package/src/main/lib/macroLibrary.cjs +386 -0
- package/src/main/lib/mcpToolCatalog.cjs +26 -17
- package/src/main/lib/needsReviewLedger.cjs +1 -1
- package/src/main/lib/opsErrorLog.cjs +11 -1
- package/src/main/lib/opsOwnership.cjs +0 -5
- package/src/main/lib/pidAlive.cjs +32 -0
- package/src/main/lib/prdCreate.cjs +153 -19
- package/src/main/lib/prdDisposition.cjs +2 -2
- package/src/main/lib/prdGateFiles.cjs +284 -0
- package/src/main/lib/prdLocations.cjs +61 -0
- package/src/main/lib/prdMigration.cjs +2 -3
- package/src/main/lib/prdSizing.cjs +2 -1
- package/src/main/lib/promptSessionSchema.cjs +10 -2
- package/src/main/lib/rcaReport.cjs +4 -6
- package/src/main/lib/reaperHelpers.cjs +8 -1
- package/src/main/lib/reservationExpiry.cjs +6 -1
- package/src/main/lib/reviewNotice.cjs +263 -0
- package/src/main/lib/runClaudeP.cjs +4 -1
- package/src/main/lib/schedulerPaths.cjs +22 -2
- package/src/main/lib/sessionSlots.cjs +2 -4
- package/src/main/lib/shippedPersonaSeeds.cjs +179 -0
- package/src/main/lib/timeoutShim.cjs +123 -0
- package/src/main/lib/timeoutShimScript.cjs +275 -0
- package/src/main/lib/upgradeDrain.cjs +4 -10
- package/src/main/lib/watchdogHelpers.cjs +33 -30
- package/src/main/lib/workTypeLibrary.cjs +7 -1
- package/src/main/promptSessionEvents.cjs +37 -13
- package/src/main/queueOps.cjs +17 -6
- package/src/main/scheduler.cjs +975 -203
- package/src/main/seedAgentPersonas.cjs +278 -23
- package/src/main/supervisor.cjs +4 -1
- package/src/main/templates/PRD_AUTHORING.md +126 -406
- package/src/preload/__tests__/preload-surface.test.cjs +68 -0
- package/src/preload/api.d.ts +41 -94
- package/src/preload/index.cjs +14 -9
- package/src/seed/agents/architect.md +1 -0
- package/src/seed/agents/dev-lead.md +17 -18
- package/src/seed/agents/project-home-builder.md +1 -0
- package/src/seed/agents/validator.md +10 -4
- package/dist/assets/HostBilko-nESrTRIg.js +0 -1
- package/dist/assets/Scheduler-BVYTeh39.js +0 -16
- package/dist/assets/Settings-DIhGgdfs.js +0 -3
- package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
- package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
- package/src/main/__tests__/agentLibrary.test.cjs +0 -276
- package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
- package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
- package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
- package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
- package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
- package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
- package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
- package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
- package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
- package/src/main/__tests__/chat-queue.test.cjs +0 -97
- package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
- package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
- package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
- package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
- package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
- package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
- package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
- package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
- package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
- package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
- package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
- package/src/main/__tests__/docEdit.test.cjs +0 -244
- package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
- package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
- package/src/main/__tests__/dod-report.test.cjs +0 -304
- package/src/main/__tests__/dod-reverify.test.cjs +0 -285
- package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
- package/src/main/__tests__/epicMint.test.cjs +0 -332
- package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
- package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
- package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
- package/src/main/__tests__/exchanges.test.cjs +0 -122
- package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
- package/src/main/__tests__/extractJson.test.cjs +0 -51
- package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
- package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
- package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
- package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
- package/src/main/__tests__/health-credentials.test.cjs +0 -81
- package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
- package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
- package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
- package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
- package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
- package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
- package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
- package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
- package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
- package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
- package/src/main/__tests__/historyDashboard.test.cjs +0 -163
- package/src/main/__tests__/historyRollup.test.cjs +0 -333
- package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
- package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
- package/src/main/__tests__/kg-augment.test.cjs +0 -195
- package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
- package/src/main/__tests__/machineProfile.test.cjs +0 -152
- package/src/main/__tests__/mcpStatus.test.cjs +0 -61
- package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
- package/src/main/__tests__/memoryStale.test.cjs +0 -88
- package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
- package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
- package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
- package/src/main/__tests__/personaMerge.test.cjs +0 -169
- package/src/main/__tests__/planValidator.test.cjs +0 -125
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
- package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
- package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
- package/src/main/__tests__/prdAgentType.test.cjs +0 -103
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
- package/src/main/__tests__/prdCreate.test.cjs +0 -979
- package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
- package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
- package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
- package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
- package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
- package/src/main/__tests__/prdLocations.test.cjs +0 -195
- package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
- package/src/main/__tests__/prdMigration.test.cjs +0 -349
- package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
- package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
- package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
- package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
- package/src/main/__tests__/prdSizing.test.cjs +0 -106
- package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
- package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
- package/src/main/__tests__/proc-role-env.test.cjs +0 -125
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
- package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
- package/src/main/__tests__/projectPages.test.cjs +0 -137
- package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
- package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
- package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
- package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
- package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
- package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
- package/src/main/__tests__/pty-write-result.test.cjs +0 -46
- package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
- package/src/main/__tests__/queueHistory.test.cjs +0 -355
- package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
- package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
- package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -154
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
- package/src/main/__tests__/rcaReport.test.cjs +0 -266
- package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
- package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
- package/src/main/__tests__/runLogRetention.test.cjs +0 -489
- package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
- package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
- package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
- package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
- package/src/main/__tests__/runVerify.test.cjs +0 -1784
- package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
- package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
- package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
- package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
- package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
- package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
- package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
- package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
- package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
- package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
- package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
- package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
- package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
- package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
- package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
- package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
- package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
- package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
- package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
- package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
- package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
- package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
- package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
- package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
- package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
- package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
- package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
- package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
- package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
- package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
- package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
- package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
- package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
- package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
- package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
- package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
- package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
- package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
- package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
- package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
- package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
- package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
- package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
- package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
- package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
- package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
- package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
- package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
- package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
- package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
- package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
- package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
- package/src/main/__tests__/seedStatus.test.cjs +0 -100
- package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
- package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
- package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
- package/src/main/__tests__/telemetryContract.test.cjs +0 -930
- package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
- package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
- package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
- package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
- package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
- package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
- package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
- package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
- package/src/main/__tests__/validationSentinels.test.cjs +0 -84
- package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
- package/src/main/bilkoHost.cjs +0 -314
- package/src/main/bilkoHostCore.cjs +0 -89
- package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
- package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
- package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
- package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
- package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
- package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
- package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
- package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
- package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
- package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
- package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
- package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
- package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
- package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
- package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
- package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
- package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
- package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
- package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
- package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
- package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
- package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
- package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
- package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
- package/src/main/lib/__tests__/gateFixtures.json +0 -20
- package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
- package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
- package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
- package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
- package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
- package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
- package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
- package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
- package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
- package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
- package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
- package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
- package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
- package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
- package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
- package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
- package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
- package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
- package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
- package/src/main/lib/__tests__/procName.test.cjs +0 -92
- package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
- package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
- package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
- package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
- package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
- package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
- package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
- package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
- package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
- package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
- package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
- package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
- package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
- package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
- package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
- package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
- package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
- package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
- package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
- package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
- package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -375
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
- package/src/main/lib/kgExchangePairing.cjs +0 -75
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* epicSpawnCwd.test.cjs — unit tests for resolveEpicSpawnCwd, the shared
|
|
3
|
-
* spawn-cwd resolver both pty.cjs's Terminal spawn and chatRunner.cjs's
|
|
4
|
-
* headless spawn use to point an Epic's actual child process at its
|
|
5
|
-
* isolated git worktree (PRD 1033), while every other cwd usage stays the
|
|
6
|
-
* real project cwd (see the module's own "ops-root hazard" header comment).
|
|
7
|
-
*
|
|
8
|
-
* Run: timeout 120 npx vitest run src/main/lib/__tests__/epicSpawnCwd.test.cjs
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
'use strict';
|
|
12
|
-
|
|
13
|
-
import { test, expect, vi } from 'vitest';
|
|
14
|
-
const { resolveEpicSpawnCwd } = require('../epicSpawnCwd.cjs');
|
|
15
|
-
|
|
16
|
-
function stubReadActiveIndex(sessions) {
|
|
17
|
-
return () => ({ sessions });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// resolveEpicSpawnCwd stats worktree.dir before returning it (a persisted
|
|
21
|
-
// path outlives its checkout across a reboot/tmp sweep, a clone onto another
|
|
22
|
-
// machine, or a manual prune). These fixtures name paths that don't exist on
|
|
23
|
-
// the test host, so inject a stat stub saying they do; the "vanished dir"
|
|
24
|
-
// tests at the bottom exercise the real fallback.
|
|
25
|
-
const statOk = () => ({ isDirectory: () => true });
|
|
26
|
-
const statGone = () => { throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); };
|
|
27
|
-
|
|
28
|
-
test('returns worktree.dir when the matching Epic (by claudeSessionId) has one', () => {
|
|
29
|
-
const result = resolveEpicSpawnCwd({
|
|
30
|
-
cwd: '/projects/foo',
|
|
31
|
-
claudeSessionId: 'sess-1',
|
|
32
|
-
deps: {
|
|
33
|
-
readActiveIndex: stubReadActiveIndex({
|
|
34
|
-
'epic-1': {
|
|
35
|
-
id: 'epic-1',
|
|
36
|
-
claudeSessionId: 'sess-1',
|
|
37
|
-
worktree: { dir: '/tmp/sm-epic-worktrees/abc/epic-1', branch: 'sm-epic/epic-1', baseCwd: '/projects/foo', status: 'active' },
|
|
38
|
-
},
|
|
39
|
-
}),
|
|
40
|
-
statSync: statOk,
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
expect(result).toBe('/tmp/sm-epic-worktrees/abc/epic-1');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('falls back to cwd unchanged when the matching Epic has no worktree field', () => {
|
|
47
|
-
const result = resolveEpicSpawnCwd({
|
|
48
|
-
cwd: '/projects/foo',
|
|
49
|
-
claudeSessionId: 'sess-1',
|
|
50
|
-
deps: {
|
|
51
|
-
readActiveIndex: stubReadActiveIndex({
|
|
52
|
-
'epic-1': { id: 'epic-1', claudeSessionId: 'sess-1' },
|
|
53
|
-
}),
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
expect(result).toBe('/projects/foo');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('falls back to cwd unchanged when no session matches claudeSessionId (non-Epic tab)', () => {
|
|
60
|
-
const result = resolveEpicSpawnCwd({
|
|
61
|
-
cwd: '/projects/foo',
|
|
62
|
-
claudeSessionId: 'some-other-session',
|
|
63
|
-
deps: {
|
|
64
|
-
readActiveIndex: stubReadActiveIndex({
|
|
65
|
-
'epic-1': {
|
|
66
|
-
id: 'epic-1',
|
|
67
|
-
claudeSessionId: 'sess-1',
|
|
68
|
-
worktree: { dir: '/tmp/sm-epic-worktrees/abc/epic-1', branch: 'sm-epic/epic-1', baseCwd: '/projects/foo', status: 'active' },
|
|
69
|
-
},
|
|
70
|
-
}),
|
|
71
|
-
statSync: statOk,
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
expect(result).toBe('/projects/foo');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('falls back to cwd unchanged when cwd or claudeSessionId is missing', () => {
|
|
78
|
-
expect(resolveEpicSpawnCwd({ cwd: '/projects/foo', claudeSessionId: '' })).toBe('/projects/foo');
|
|
79
|
-
expect(resolveEpicSpawnCwd({ cwd: '', claudeSessionId: 'sess-1' })).toBe('');
|
|
80
|
-
expect(resolveEpicSpawnCwd({})).toBeUndefined();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('returns worktree.dir for a conflicted (needs_merge_resolution) Epic too — same PTY-into-worktree path a human uses to resolve it manually (PRD 1034)', () => {
|
|
84
|
-
const result = resolveEpicSpawnCwd({
|
|
85
|
-
cwd: '/projects/foo',
|
|
86
|
-
claudeSessionId: 'sess-1',
|
|
87
|
-
deps: {
|
|
88
|
-
readActiveIndex: stubReadActiveIndex({
|
|
89
|
-
'epic-1': {
|
|
90
|
-
id: 'epic-1',
|
|
91
|
-
claudeSessionId: 'sess-1',
|
|
92
|
-
worktree: {
|
|
93
|
-
dir: '/tmp/sm-epic-worktrees/abc/epic-1',
|
|
94
|
-
branch: 'sm-epic/epic-1',
|
|
95
|
-
baseCwd: '/projects/foo',
|
|
96
|
-
status: 'needs_merge_resolution',
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
}),
|
|
100
|
-
statSync: statOk,
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
expect(result).toBe('/tmp/sm-epic-worktrees/abc/epic-1');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('never throws when the active-index read itself throws — falls back to cwd', () => {
|
|
107
|
-
const result = resolveEpicSpawnCwd({
|
|
108
|
-
cwd: '/projects/foo',
|
|
109
|
-
claudeSessionId: 'sess-1',
|
|
110
|
-
deps: {
|
|
111
|
-
readActiveIndex: () => { throw new Error('disk read failed'); },
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
expect(result).toBe('/projects/foo');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// ─── persisted worktree.dir outliving its checkout (2026-08-13) ─────────────
|
|
118
|
-
//
|
|
119
|
-
// worktree.dir is an absolute os.tmpdir() path persisted onto the Epic record
|
|
120
|
-
// in active-index.json — a git-TRACKED file. It therefore routinely names a
|
|
121
|
-
// directory that no longer exists: /tmp is cleared on boot on most Linux
|
|
122
|
-
// distros and macOS (this host: `D /tmp 1777 root root 30d`), Epics are
|
|
123
|
-
// deliberately long-lived, and a clone on another machine carries a path built
|
|
124
|
-
// from the ORIGINAL machine's tmpdir and a sha1 of its project cwd. Handing
|
|
125
|
-
// that to node-pty/child_process as a spawn cwd fails the spawn outright, so
|
|
126
|
-
// the resolver falls back to the shared project tree instead.
|
|
127
|
-
|
|
128
|
-
const WORKTREE_SESSIONS = {
|
|
129
|
-
'epic-1': {
|
|
130
|
-
id: 'epic-1',
|
|
131
|
-
claudeSessionId: 'sess-1',
|
|
132
|
-
worktree: {
|
|
133
|
-
dir: '/tmp/session-manager-epic-worktrees/deadbeef/epic-1',
|
|
134
|
-
branch: 'sm-epic/epic-1',
|
|
135
|
-
baseCwd: '/projects/foo',
|
|
136
|
-
status: 'active',
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
test('falls back to the project cwd when the persisted worktree dir no longer exists (reboot / tmp sweep)', () => {
|
|
142
|
-
const result = resolveEpicSpawnCwd({
|
|
143
|
-
cwd: '/projects/foo',
|
|
144
|
-
claudeSessionId: 'sess-1',
|
|
145
|
-
deps: { readActiveIndex: stubReadActiveIndex(WORKTREE_SESSIONS), statSync: statGone },
|
|
146
|
-
});
|
|
147
|
-
expect(result).toBe('/projects/foo');
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test('falls back when worktree.dir exists but is a FILE, not a directory', () => {
|
|
151
|
-
const result = resolveEpicSpawnCwd({
|
|
152
|
-
cwd: '/projects/foo',
|
|
153
|
-
claudeSessionId: 'sess-1',
|
|
154
|
-
deps: {
|
|
155
|
-
readActiveIndex: stubReadActiveIndex(WORKTREE_SESSIONS),
|
|
156
|
-
statSync: () => ({ isDirectory: () => false }),
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
expect(result).toBe('/projects/foo');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('a stat that throws something other than ENOENT still falls back rather than propagating', () => {
|
|
163
|
-
const result = resolveEpicSpawnCwd({
|
|
164
|
-
cwd: '/projects/foo',
|
|
165
|
-
claudeSessionId: 'sess-1',
|
|
166
|
-
deps: {
|
|
167
|
-
readActiveIndex: stubReadActiveIndex(WORKTREE_SESSIONS),
|
|
168
|
-
statSync: () => { throw Object.assign(new Error('EACCES'), { code: 'EACCES' }); },
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
expect(result).toBe('/projects/foo');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('a real, existing directory IS returned — the check gates on disk state, not on the field being set', () => {
|
|
175
|
-
const os = require('node:os');
|
|
176
|
-
const fs = require('node:fs');
|
|
177
|
-
const realDir = fs.mkdtempSync(require('node:path').join(os.tmpdir(), 'sm-epicspawn-real-'));
|
|
178
|
-
try {
|
|
179
|
-
const result = resolveEpicSpawnCwd({
|
|
180
|
-
cwd: '/projects/foo',
|
|
181
|
-
claudeSessionId: 'sess-1',
|
|
182
|
-
deps: {
|
|
183
|
-
readActiveIndex: stubReadActiveIndex({
|
|
184
|
-
'epic-1': { id: 'epic-1', claudeSessionId: 'sess-1', worktree: { dir: realDir, branch: 'b', baseCwd: '/projects/foo', status: 'active' } },
|
|
185
|
-
}),
|
|
186
|
-
// no statSync stub — exercises the real fs.statSync default
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
expect(result).toBe(realDir);
|
|
190
|
-
} finally {
|
|
191
|
-
fs.rmSync(realDir, { recursive: true, force: true });
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// ─── spawn-side: a vanished worktree is restored at the SAME path, never swapped for cwd ───
|
|
196
|
-
|
|
197
|
-
test('restore:true re-attaches the worktree and returns the recorded dir (transcript encoding unchanged)', () => {
|
|
198
|
-
const calls = [];
|
|
199
|
-
const result = resolveEpicSpawnCwd({
|
|
200
|
-
cwd: '/projects/foo',
|
|
201
|
-
claudeSessionId: 'sess-1',
|
|
202
|
-
deps: {
|
|
203
|
-
readActiveIndex: stubReadActiveIndex(WORKTREE_SESSIONS),
|
|
204
|
-
statSync: statGone,
|
|
205
|
-
restore: true,
|
|
206
|
-
restoreWorktree: (a) => { calls.push(a); return true; },
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
expect(result).toBe('/tmp/session-manager-epic-worktrees/deadbeef/epic-1');
|
|
210
|
-
expect(calls).toEqual([{ dir: '/tmp/session-manager-epic-worktrees/deadbeef/epic-1', branch: 'sm-epic/epic-1', baseCwd: '/projects/foo' }]);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test('restore:true with a failed restore returns the project cwd — never a dead recorded dir', () => {
|
|
214
|
-
const result = resolveEpicSpawnCwd({
|
|
215
|
-
cwd: '/projects/foo',
|
|
216
|
-
claudeSessionId: 'sess-1',
|
|
217
|
-
deps: { readActiveIndex: stubReadActiveIndex(WORKTREE_SESSIONS), statSync: statGone, restore: true, restoreWorktree: () => false },
|
|
218
|
-
});
|
|
219
|
-
expect(result).toBe('/projects/foo');
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test('a merged Epic with a missing dir returns the project cwd and never attempts a restore', () => {
|
|
223
|
-
const spy = vi.fn(() => true);
|
|
224
|
-
for (const status of ['merged', 'disabled']) {
|
|
225
|
-
const result = resolveEpicSpawnCwd({
|
|
226
|
-
cwd: '/projects/foo',
|
|
227
|
-
claudeSessionId: 'sess-1',
|
|
228
|
-
deps: {
|
|
229
|
-
readActiveIndex: stubReadActiveIndex({
|
|
230
|
-
'epic-1': { id: 'epic-1', claudeSessionId: 'sess-1', worktree: { dir: '/tmp/session-manager-epic-worktrees/deadbeef/epic-1', branch: 'sm-epic/epic-1', status } },
|
|
231
|
-
}),
|
|
232
|
-
statSync: statGone,
|
|
233
|
-
restore: true,
|
|
234
|
-
restoreWorktree: spy,
|
|
235
|
-
},
|
|
236
|
-
});
|
|
237
|
-
expect(result).toBe('/projects/foo');
|
|
238
|
-
}
|
|
239
|
-
expect(spy).not.toHaveBeenCalled();
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test('an active Epic with a missing dir attempts a restore and returns the project cwd when it fails', () => {
|
|
243
|
-
const spy = vi.fn(() => false);
|
|
244
|
-
const result = resolveEpicSpawnCwd({
|
|
245
|
-
cwd: '/projects/foo',
|
|
246
|
-
claudeSessionId: 'sess-1',
|
|
247
|
-
deps: {
|
|
248
|
-
readActiveIndex: stubReadActiveIndex({
|
|
249
|
-
'epic-1': { id: 'epic-1', claudeSessionId: 'sess-1', worktree: { dir: '/tmp/session-manager-epic-worktrees/deadbeef/epic-1', branch: 'sm-epic/epic-1', status: 'active' } },
|
|
250
|
-
}),
|
|
251
|
-
statSync: statGone,
|
|
252
|
-
restore: true,
|
|
253
|
-
restoreWorktree: spy,
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
257
|
-
expect(result).toBe('/projects/foo');
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test('a worktree record with no status key is restore-eligible and does not throw', () => {
|
|
261
|
-
const spy = vi.fn(() => true);
|
|
262
|
-
const result = resolveEpicSpawnCwd({
|
|
263
|
-
cwd: '/projects/foo',
|
|
264
|
-
claudeSessionId: 'sess-1',
|
|
265
|
-
deps: {
|
|
266
|
-
readActiveIndex: stubReadActiveIndex({
|
|
267
|
-
'epic-1': { id: 'epic-1', claudeSessionId: 'sess-1', worktree: { dir: '/tmp/session-manager-epic-worktrees/deadbeef/epic-1', branch: 'sm-epic/epic-1' } },
|
|
268
|
-
}),
|
|
269
|
-
statSync: statGone,
|
|
270
|
-
restore: true,
|
|
271
|
-
restoreWorktree: spy,
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
275
|
-
expect(result).toBe('/tmp/session-manager-epic-worktrees/deadbeef/epic-1');
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
test('restoreEpicWorktree refuses dirs outside an epic-worktrees segment and non sm-epic branches', () => {
|
|
279
|
-
const { restoreEpicWorktree } = require('../epicSpawnCwd.cjs');
|
|
280
|
-
expect(restoreEpicWorktree({ dir: '/etc/evil', branch: 'sm-epic/x', baseCwd: '/projects/foo' })).toBe(false);
|
|
281
|
-
expect(restoreEpicWorktree({ dir: '/tmp/session-manager-epic-worktrees/a/b', branch: 'main; rm -rf', baseCwd: '/projects/foo' })).toBe(false);
|
|
282
|
-
expect(restoreEpicWorktree({ dir: 'relative/session-manager-epic-worktrees/a', branch: 'sm-epic/x', baseCwd: '/projects/foo' })).toBe(false);
|
|
283
|
-
});
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* epicSpawnPlan.test.cjs — PRD 1319: spawn cwd + --resume flag are decided together; a session
|
|
3
|
-
* whose transcript is stranded under a dead worktree encoding is refused BEFORE any CLI spawn
|
|
4
|
-
* with an actionable message, later requests are refused cheaply, closed Epics are refused.
|
|
5
|
-
*
|
|
6
|
-
* Run: timeout 120 npx vitest run src/main/lib/__tests__/epicSpawnPlan.test.cjs
|
|
7
|
-
*/
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
import { test, expect, beforeEach, afterEach } from 'vitest';
|
|
11
|
-
const fs = require('node:fs');
|
|
12
|
-
const os = require('node:os');
|
|
13
|
-
const path = require('node:path');
|
|
14
|
-
const { EventEmitter } = require('node:events');
|
|
15
|
-
const { encodeCwd } = require('../encodeCwd.cjs');
|
|
16
|
-
const { planEpicSpawn, __resetForTests } = require('../epicSpawnPlan.cjs');
|
|
17
|
-
const { __resetCacheForTests } = require('../epicTranscriptPath.cjs');
|
|
18
|
-
|
|
19
|
-
const SID = '00ff31cb-56b6-421c-829c-012ef95b15e2';
|
|
20
|
-
const PROJECT = '/projects/starry-night-ships';
|
|
21
|
-
const DEAD = '/tmp/session-manager-epic-worktrees/abc123/blender-epic-442d711d';
|
|
22
|
-
|
|
23
|
-
let home;
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
__resetForTests();
|
|
26
|
-
__resetCacheForTests();
|
|
27
|
-
home = fs.mkdtempSync(path.join(os.tmpdir(), 'plan-home-'));
|
|
28
|
-
});
|
|
29
|
-
afterEach(() => fs.rmSync(home, { recursive: true, force: true }));
|
|
30
|
-
|
|
31
|
-
function transcriptUnder(dir) {
|
|
32
|
-
const d = path.join(home, '.claude', 'projects', encodeCwd(dir));
|
|
33
|
-
fs.mkdirSync(d, { recursive: true });
|
|
34
|
-
const f = path.join(d, `${SID}.jsonl`);
|
|
35
|
-
fs.writeFileSync(f, '{"type":"user"}\n');
|
|
36
|
-
return f;
|
|
37
|
-
}
|
|
38
|
-
const epic = (extra = {}) => ({ e1: { id: 'e1', claudeSessionId: SID, status: 'active', worktree: { dir: DEAD, branch: 'sm-epic/e1', baseCwd: PROJECT, status: 'active' }, ...extra } });
|
|
39
|
-
|
|
40
|
-
test('transcript under a dead worktree encoding + restore failure => refused, names the worktree, no raw CLI text', () => {
|
|
41
|
-
transcriptUnder(DEAD);
|
|
42
|
-
const plan = planEpicSpawn({ cwd: PROJECT, claudeSessionId: SID, deps: { homeDir: home, readActiveIndex: () => ({ sessions: epic() }), restoreWorktree: () => false } });
|
|
43
|
-
expect(plan.ok).toBe(false);
|
|
44
|
-
expect(plan.code).toBe('session_unreachable');
|
|
45
|
-
expect(plan.message).toContain(DEAD);
|
|
46
|
-
expect(plan.message).not.toMatch(/already in use/i);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('circuit breaker: 2nd..Nth request refused without re-running restore', () => {
|
|
50
|
-
transcriptUnder(DEAD);
|
|
51
|
-
let restores = 0;
|
|
52
|
-
const deps = { homeDir: home, readActiveIndex: () => ({ sessions: epic() }), restoreWorktree: () => { restores++; return false; } };
|
|
53
|
-
const first = planEpicSpawn({ cwd: PROJECT, claudeSessionId: SID, deps });
|
|
54
|
-
for (let i = 0; i < 4; i++) expect(planEpicSpawn({ cwd: PROJECT, claudeSessionId: SID, deps })).toEqual(first);
|
|
55
|
-
expect(restores).toBe(1);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('worktree re-attached at the same path => ok, resume, spawn cwd is the worktree', () => {
|
|
59
|
-
transcriptUnder(DEAD);
|
|
60
|
-
const live = new Set();
|
|
61
|
-
const statSync = (p) => (live.has(p) ? { isDirectory: () => true } : fs.statSync(p));
|
|
62
|
-
const plan = planEpicSpawn({
|
|
63
|
-
cwd: PROJECT, claudeSessionId: SID,
|
|
64
|
-
deps: { homeDir: home, readActiveIndex: () => ({ sessions: epic() }), statSync, restoreWorktree: ({ dir }) => { live.add(dir); return true; } },
|
|
65
|
-
});
|
|
66
|
-
expect(plan.ok).toBe(true);
|
|
67
|
-
expect(plan.useResume).toBe(true);
|
|
68
|
-
expect(plan.execCwd).toBe(DEAD);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('transcript under the project encoding, no worktree => ok, resume from project cwd', () => {
|
|
72
|
-
transcriptUnder(PROJECT);
|
|
73
|
-
const plan = planEpicSpawn({ cwd: PROJECT, claudeSessionId: SID, deps: { homeDir: home, readActiveIndex: () => ({ sessions: epic({ worktree: undefined }) }), statSync: (p) => (p === PROJECT ? { isDirectory: () => true } : fs.statSync(p)) } });
|
|
74
|
-
expect(plan).toMatchObject({ ok: true, useResume: true, execCwd: PROJECT });
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('merged worktree (dir gone), transcript only under project encoding => ok, resume from project cwd (reported incident)', () => {
|
|
78
|
-
const proj = fs.mkdtempSync(path.join(os.tmpdir(), 'plan-realproj-'));
|
|
79
|
-
try {
|
|
80
|
-
const sid = SID;
|
|
81
|
-
const d = path.join(home, '.claude', 'projects', encodeCwd(proj));
|
|
82
|
-
fs.mkdirSync(d, { recursive: true });
|
|
83
|
-
fs.writeFileSync(path.join(d, `${sid}.jsonl`), '{"type":"user"}\n');
|
|
84
|
-
const merged = epic({ worktree: { dir: DEAD, branch: 'sm-epic/e1', status: 'merged' } });
|
|
85
|
-
const deps = { homeDir: home, readActiveIndex: () => ({ sessions: merged }) };
|
|
86
|
-
const first = planEpicSpawn({ cwd: proj, claudeSessionId: sid, deps });
|
|
87
|
-
expect(first).toMatchObject({ ok: true, execCwd: proj, useResume: true });
|
|
88
|
-
// No circuit-breaker entry from the fallback: a second call is also ok.
|
|
89
|
-
expect(planEpicSpawn({ cwd: proj, claudeSessionId: sid, deps })).toMatchObject({ ok: true, execCwd: proj, useResume: true });
|
|
90
|
-
} finally { fs.rmSync(proj, { recursive: true, force: true }); }
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('merged worktree, no transcript anywhere => ok, real directory, no resume', () => {
|
|
94
|
-
const proj = fs.mkdtempSync(path.join(os.tmpdir(), 'plan-realproj-'));
|
|
95
|
-
try {
|
|
96
|
-
const merged = epic({ worktree: { dir: DEAD, branch: 'sm-epic/e1', status: 'merged' } });
|
|
97
|
-
const plan = planEpicSpawn({ cwd: proj, claudeSessionId: SID, deps: { homeDir: home, readActiveIndex: () => ({ sessions: merged }) } });
|
|
98
|
-
expect(plan.ok).toBe(true);
|
|
99
|
-
expect(plan.useResume).toBe(false);
|
|
100
|
-
expect(fs.statSync(plan.execCwd).isDirectory()).toBe(true);
|
|
101
|
-
} finally { fs.rmSync(proj, { recursive: true, force: true }); }
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('merged worktree, transcript only under dead encoding => refused, no git worktree add command', () => {
|
|
105
|
-
const proj = fs.mkdtempSync(path.join(os.tmpdir(), 'plan-realproj-'));
|
|
106
|
-
try {
|
|
107
|
-
transcriptUnder(DEAD);
|
|
108
|
-
const merged = epic({ worktree: { dir: DEAD, branch: 'sm-epic/e1', status: 'merged' } });
|
|
109
|
-
const plan = planEpicSpawn({ cwd: proj, claudeSessionId: SID, deps: { homeDir: home, readActiveIndex: () => ({ sessions: merged }) } });
|
|
110
|
-
expect(plan).toMatchObject({ ok: false, code: 'session_unreachable' });
|
|
111
|
-
expect(plan.message).toMatch(/merged to main/);
|
|
112
|
-
expect(plan.message).not.toMatch(/git worktree add/);
|
|
113
|
-
expect(plan.message).not.toMatch(/swept from \/tmp/);
|
|
114
|
-
} finally { fs.rmSync(proj, { recursive: true, force: true }); }
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test('closed (completed) Epic is refused with its own message', () => {
|
|
118
|
-
const plan = planEpicSpawn({ cwd: PROJECT, claudeSessionId: SID, deps: { homeDir: home, readActiveIndex: () => ({ sessions: epic({ status: 'completed' }) }) } });
|
|
119
|
-
expect(plan).toMatchObject({ ok: false, code: 'epic_closed' });
|
|
120
|
-
expect(plan.message).toMatch(/closed/i);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('chatRunner: dead-encoding transcript => zero CLI spawns, exactly one clear error event', async () => {
|
|
124
|
-
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'plan-proj-'));
|
|
125
|
-
const prevHome = process.env.HOME;
|
|
126
|
-
process.env.HOME = home;
|
|
127
|
-
try {
|
|
128
|
-
const idxDir = path.join(project, 'session-manager-operations', 'prompt-sessions');
|
|
129
|
-
fs.mkdirSync(idxDir, { recursive: true });
|
|
130
|
-
fs.writeFileSync(path.join(idxDir, 'active-index.json'), JSON.stringify({ sessions: epic() }));
|
|
131
|
-
transcriptUnder(DEAD);
|
|
132
|
-
const cp = require('node:child_process');
|
|
133
|
-
const realSpawn = cp.spawn;
|
|
134
|
-
let spawns = 0;
|
|
135
|
-
cp.spawn = () => { spawns++; const c = new EventEmitter(); c.stdout = new EventEmitter(); c.stderr = new EventEmitter(); c.pid = 1; c.kill = () => {}; return c; };
|
|
136
|
-
const chatRunner = require('../../chatRunner.cjs');
|
|
137
|
-
chatRunner.__resetQueueForTests();
|
|
138
|
-
const sent = [];
|
|
139
|
-
chatRunner.attachWindow({ isDestroyed: () => false, webContents: { isDestroyed: () => false, send: (channel, payload) => sent.push({ channel, payload }) } });
|
|
140
|
-
try {
|
|
141
|
-
for (let i = 0; i < 3; i++) chatRunner.run({ tabId: `t${i}`, sessionId: SID, prompt: 'hi', cwd: project, resume: true });
|
|
142
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
143
|
-
} finally { cp.spawn = realSpawn; }
|
|
144
|
-
const errs = sent.filter((e) => e.channel === 'chat:run:error');
|
|
145
|
-
expect(spawns).toBe(0);
|
|
146
|
-
expect(errs.length).toBe(3);
|
|
147
|
-
for (const e of errs) expect(e.payload.message).toContain(DEAD);
|
|
148
|
-
// Single-writer law: the rescued session's ops write (error log) lands under the PROJECT root.
|
|
149
|
-
const logsDir = path.join(project, 'session-manager-operations', 'logs');
|
|
150
|
-
expect(fs.existsSync(logsDir)).toBe(true);
|
|
151
|
-
expect(fs.readdirSync(logsDir).some((n) => n.startsWith('errors-'))).toBe(true);
|
|
152
|
-
expect(fs.existsSync(path.join(DEAD, 'session-manager-operations'))).toBe(false);
|
|
153
|
-
} finally {
|
|
154
|
-
process.env.HOME = prevHome;
|
|
155
|
-
fs.rmSync(project, { recursive: true, force: true });
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test('chatRunner: silent run refused by planEpicSpawn => onSilentError with the refusal, no broadcasts, no spawn', async () => {
|
|
160
|
-
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'plan-proj-'));
|
|
161
|
-
const prevHome = process.env.HOME;
|
|
162
|
-
process.env.HOME = home;
|
|
163
|
-
try {
|
|
164
|
-
const idxDir = path.join(project, 'session-manager-operations', 'prompt-sessions');
|
|
165
|
-
fs.mkdirSync(idxDir, { recursive: true });
|
|
166
|
-
fs.writeFileSync(path.join(idxDir, 'active-index.json'), JSON.stringify({ sessions: epic({ status: 'completed' }) }));
|
|
167
|
-
const cp = require('node:child_process');
|
|
168
|
-
const realSpawn = cp.spawn;
|
|
169
|
-
let spawns = 0;
|
|
170
|
-
cp.spawn = () => { spawns++; throw new Error('must not spawn'); };
|
|
171
|
-
const chatRunner = require('../../chatRunner.cjs');
|
|
172
|
-
chatRunner.__resetQueueForTests();
|
|
173
|
-
const sent = [];
|
|
174
|
-
chatRunner.attachWindow({ isDestroyed: () => false, webContents: { isDestroyed: () => false, send: (channel, payload) => sent.push({ channel, payload }) } });
|
|
175
|
-
const errors = [];
|
|
176
|
-
const results = [];
|
|
177
|
-
try {
|
|
178
|
-
chatRunner.run({
|
|
179
|
-
tabId: 'silent1', sessionId: SID, prompt: 'hi', cwd: project, resume: true, silent: true,
|
|
180
|
-
onSilentResult: (t) => results.push(t),
|
|
181
|
-
onSilentError: (message, code) => errors.push({ message, code }),
|
|
182
|
-
});
|
|
183
|
-
// no timer: delivery is a few microtasks after run()
|
|
184
|
-
await Promise.resolve(); await Promise.resolve(); await Promise.resolve();
|
|
185
|
-
} finally { cp.spawn = realSpawn; }
|
|
186
|
-
expect(spawns).toBe(0);
|
|
187
|
-
expect(results).toEqual([]);
|
|
188
|
-
expect(errors.length).toBe(1);
|
|
189
|
-
expect(errors[0].code).toBe('epic_closed');
|
|
190
|
-
expect(errors[0].message).toMatch(/closed/i);
|
|
191
|
-
expect(sent.filter((e) => e.channel.startsWith('chat:run:'))).toEqual([]);
|
|
192
|
-
} finally {
|
|
193
|
-
process.env.HOME = prevHome;
|
|
194
|
-
fs.rmSync(project, { recursive: true, force: true });
|
|
195
|
-
}
|
|
196
|
-
});
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* epicTranscriptPath.test.cjs — resolver for where an Epic session's JSONL
|
|
3
|
-
* transcript actually lives (worktree encoding vs project encoding).
|
|
4
|
-
*
|
|
5
|
-
* Run: timeout 120 npx vitest run src/main/lib/__tests__/epicTranscriptPath.test.cjs
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
import { test, expect, beforeEach, afterEach } from 'vitest';
|
|
11
|
-
const fs = require('node:fs');
|
|
12
|
-
const os = require('node:os');
|
|
13
|
-
const path = require('node:path');
|
|
14
|
-
const { resolveEpicTranscriptPath, __resetCacheForTests } = require('../epicTranscriptPath.cjs');
|
|
15
|
-
const { encodeCwd } = require('../encodeCwd.cjs');
|
|
16
|
-
const { readActiveIndex } = require('../epicMint.cjs');
|
|
17
|
-
|
|
18
|
-
const SID = 'sess-1';
|
|
19
|
-
let root, home, cwd, wtDir;
|
|
20
|
-
|
|
21
|
-
const projFile = (dir) => path.join(home, '.claude', 'projects', encodeCwd(dir), `${SID}.jsonl`);
|
|
22
|
-
function put(p, body = '{"a":1}\n', mtimeSec) {
|
|
23
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
24
|
-
fs.writeFileSync(p, body);
|
|
25
|
-
if (mtimeSec) fs.utimesSync(p, mtimeSec, mtimeSec);
|
|
26
|
-
}
|
|
27
|
-
const promptDir = () => path.join(cwd, 'session-manager-operations', 'prompt-sessions');
|
|
28
|
-
function writeIndex(sessions) {
|
|
29
|
-
fs.mkdirSync(promptDir(), { recursive: true });
|
|
30
|
-
fs.writeFileSync(path.join(promptDir(), 'active-index.json'), JSON.stringify({ sessions, events: {} }));
|
|
31
|
-
}
|
|
32
|
-
const epic = (extra = {}) => ({ id: 'epic-1', claudeSessionId: SID, worktree: { dir: wtDir, branch: 'b', baseCwd: cwd, status: 'active' }, ...extra });
|
|
33
|
-
const resolve = (deps = {}) => resolveEpicTranscriptPath({ cwd, claudeSessionId: SID, deps: { homeDir: home, ...deps } });
|
|
34
|
-
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
__resetCacheForTests();
|
|
37
|
-
root = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-etp-'));
|
|
38
|
-
home = path.join(root, 'home');
|
|
39
|
-
cwd = path.join(root, 'project');
|
|
40
|
-
wtDir = path.join(root, 'wt', 'epic-1');
|
|
41
|
-
fs.mkdirSync(cwd, { recursive: true });
|
|
42
|
-
});
|
|
43
|
-
afterEach(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
44
|
-
|
|
45
|
-
test('worktree transcript is found when the worktree dir exists', () => {
|
|
46
|
-
fs.mkdirSync(wtDir, { recursive: true });
|
|
47
|
-
writeIndex({ e: epic() });
|
|
48
|
-
put(projFile(wtDir));
|
|
49
|
-
const r = resolve();
|
|
50
|
-
expect(r.path).toBe(projFile(wtDir));
|
|
51
|
-
expect(r.existsAnywhere).toBe(true);
|
|
52
|
-
expect(r.candidates).toEqual([projFile(wtDir), projFile(cwd)]);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('worktree swept from disk but transcript survives — still returned', () => {
|
|
56
|
-
writeIndex({ e: epic() });
|
|
57
|
-
put(projFile(wtDir));
|
|
58
|
-
const r = resolve();
|
|
59
|
-
expect(r.path).toBe(projFile(wtDir));
|
|
60
|
-
expect(r.candidates).toContain(projFile(wtDir));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('project-encoding-only transcript', () => {
|
|
64
|
-
fs.mkdirSync(wtDir, { recursive: true });
|
|
65
|
-
writeIndex({ e: epic() });
|
|
66
|
-
put(projFile(cwd));
|
|
67
|
-
const r = resolve();
|
|
68
|
-
expect(r.path).toBe(projFile(cwd));
|
|
69
|
-
expect(r.existingPaths).toEqual([projFile(cwd)]);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('nothing anywhere: path is the spawn-cwd candidate, existsAnywhere false', () => {
|
|
73
|
-
fs.mkdirSync(wtDir, { recursive: true });
|
|
74
|
-
writeIndex({ e: epic() });
|
|
75
|
-
const r = resolve();
|
|
76
|
-
expect(r.path).toBe(projFile(wtDir));
|
|
77
|
-
expect(r.existsAnywhere).toBe(false);
|
|
78
|
-
expect(r.existingPaths).toEqual([]);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('non-Epic tab returns exactly the project path with a single candidate', () => {
|
|
82
|
-
writeIndex({ e: epic({ claudeSessionId: 'other' }) });
|
|
83
|
-
put(projFile(cwd));
|
|
84
|
-
const r = resolve();
|
|
85
|
-
expect(r.candidates).toEqual([projFile(cwd)]);
|
|
86
|
-
expect(r.path).toBe(projFile(cwd));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('dir exists but holds only a tool-results sidecar — does not win', () => {
|
|
90
|
-
fs.mkdirSync(wtDir, { recursive: true });
|
|
91
|
-
writeIndex({ e: epic() });
|
|
92
|
-
fs.mkdirSync(path.join(path.dirname(projFile(wtDir)), SID, 'tool-results'), { recursive: true });
|
|
93
|
-
put(projFile(cwd));
|
|
94
|
-
const r = resolve();
|
|
95
|
-
expect(r.path).toBe(projFile(cwd));
|
|
96
|
-
expect(r.existingPaths).toEqual([projFile(cwd)]);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('a directory named <id>.jsonl is not a transcript', () => {
|
|
100
|
-
writeIndex({ e: epic() });
|
|
101
|
-
fs.mkdirSync(projFile(wtDir), { recursive: true });
|
|
102
|
-
expect(resolve().existsAnywhere).toBe(false);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('zero-byte file counts as existing but loses to a non-empty one', () => {
|
|
106
|
-
fs.mkdirSync(wtDir, { recursive: true });
|
|
107
|
-
writeIndex({ e: epic() });
|
|
108
|
-
put(projFile(wtDir), '');
|
|
109
|
-
let r = resolve();
|
|
110
|
-
expect(r.existsAnywhere).toBe(true);
|
|
111
|
-
expect(r.path).toBe(projFile(wtDir));
|
|
112
|
-
put(projFile(cwd), '{"a":1}\n', 1000);
|
|
113
|
-
r = resolve();
|
|
114
|
-
expect(r.path).toBe(projFile(cwd));
|
|
115
|
-
expect(r.existingPaths.length).toBe(2);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('duplicate under two encodings: newest mtime wins', () => {
|
|
119
|
-
fs.mkdirSync(wtDir, { recursive: true });
|
|
120
|
-
writeIndex({ e: epic() });
|
|
121
|
-
put(projFile(wtDir), 'x\n', 2000);
|
|
122
|
-
put(projFile(cwd), 'x\n', 3000);
|
|
123
|
-
let r = resolve();
|
|
124
|
-
expect(r.existingPaths.length).toBe(2);
|
|
125
|
-
expect(r.path).toBe(projFile(cwd));
|
|
126
|
-
put(projFile(wtDir), 'x\n', 4000);
|
|
127
|
-
r = resolve();
|
|
128
|
-
expect(r.path).toBe(projFile(wtDir));
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('completed Epic (absent from index) resolved from its archive', () => {
|
|
132
|
-
writeIndex({});
|
|
133
|
-
fs.writeFileSync(path.join(promptDir(), 'done-epic-abcd1234.json'), JSON.stringify({ session: epic(), events: [], transcript: '', archivedAt: 'x' }));
|
|
134
|
-
put(projFile(wtDir));
|
|
135
|
-
const r = resolve();
|
|
136
|
-
expect(r.path).toBe(projFile(wtDir));
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('malformed archive JSON and unreadable index never throw', () => {
|
|
140
|
-
writeIndex({});
|
|
141
|
-
fs.writeFileSync(path.join(promptDir(), 'bad.json'), '{not json');
|
|
142
|
-
expect(() => resolve()).not.toThrow();
|
|
143
|
-
const r = resolve({ readActiveIndex: () => { throw new Error('boom'); } });
|
|
144
|
-
expect(r.candidates).toEqual([projFile(cwd)]);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('blank/missing/unsafe inputs return a safe result', () => {
|
|
148
|
-
for (const args of [{}, { cwd: '', claudeSessionId: SID }, { cwd, claudeSessionId: '' }, { cwd, claudeSessionId: '../x' }]) {
|
|
149
|
-
const r = resolveEpicTranscriptPath({ ...args, deps: { homeDir: home } });
|
|
150
|
-
expect(r).toEqual({ path: null, candidates: [], existsAnywhere: false, existingPaths: [] });
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('index read once across N calls when unchanged; re-read after a touch', () => {
|
|
155
|
-
writeIndex({ e: epic() });
|
|
156
|
-
let reads = 0;
|
|
157
|
-
const deps = { readActiveIndex: (c) => { reads++; return readActiveIndex(c); } };
|
|
158
|
-
for (let i = 0; i < 20; i++) resolve(deps);
|
|
159
|
-
expect(reads).toBe(1);
|
|
160
|
-
writeIndex({ e: epic(), f: epic({ id: 'epic-2', claudeSessionId: 'zzz' }) });
|
|
161
|
-
resolve(deps);
|
|
162
|
-
expect(reads).toBe(2);
|
|
163
|
-
});
|