claude-code-session-manager 0.86.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -53
- package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
- package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
- package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
- package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
- package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
- package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
- package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
- package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
- package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
- package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
- package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
- package/dist/assets/Permissions-m0KAnccF.js +3 -0
- package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
- package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
- package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
- package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
- package/dist/assets/Settings-BHSGGFEc.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
- package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
- package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
- package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
- package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
- package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
- package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
- package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
- package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
- package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
- package/dist/index.html +2 -2
- package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
- package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
- package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
- package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
- package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
- package/package.json +14 -17
- package/plugins/CLAUDE.md +61 -0
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
- package/scripts/README.md +59 -0
- package/scripts/audit-ops-hygiene.cjs +350 -0
- package/scripts/hooks/guard-destructive-git.cjs +10 -34
- package/scripts/hooks/guard-inline-implementation.cjs +14 -8
- package/scripts/hooks/guard-prd-writes.cjs +7 -36
- package/scripts/hooks/guard-self-schedule.cjs +175 -0
- package/scripts/ops-sweep.cjs +355 -0
- package/scripts/scheduler-mcp-server.cjs +35 -71
- package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
- package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
- package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
- package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
- package/src/main/__tests__/chat-queue.test.cjs +2 -2
- package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
- package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
- package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
- package/src/main/__tests__/dod-report.test.cjs +2 -2
- package/src/main/__tests__/dod-reverify.test.cjs +2 -2
- package/src/main/__tests__/epicMint.test.cjs +4 -4
- package/src/main/__tests__/exchanges.test.cjs +2 -2
- package/src/main/__tests__/extractJson.test.cjs +2 -2
- package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
- package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
- package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
- package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
- package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
- package/src/main/__tests__/historyRollup.test.cjs +2 -2
- package/src/main/__tests__/kg-augment.test.cjs +2 -2
- package/src/main/__tests__/mcpStatus.test.cjs +2 -2
- package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
- package/src/main/__tests__/memoryStale.test.cjs +1 -1
- package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
- package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
- package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
- package/src/main/__tests__/prdLocations.test.cjs +100 -2
- package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
- package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
- package/src/main/__tests__/proc-role-env.test.cjs +125 -0
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
- package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
- package/src/main/__tests__/projectPages.test.cjs +63 -149
- package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
- package/src/main/__tests__/queueHistory.test.cjs +65 -2
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
- package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
- package/src/main/__tests__/runVerify.test.cjs +5 -5
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
- package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
- package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
- package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
- package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
- package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
- package/src/main/bilkoHost.cjs +16 -7
- package/src/main/build-info.json +8 -0
- package/src/main/chatRunner.cjs +12 -7
- package/src/main/config.cjs +2 -3
- package/src/main/docEdit.cjs +4 -2
- package/src/main/health.cjs +268 -52
- package/src/main/heapSnapshot.cjs +2 -2
- package/src/main/historyAggregator.cjs +6 -1
- package/src/main/index.cjs +152 -50
- package/src/main/ipcSchemas.cjs +53 -17
- package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
- package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
- package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
- package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
- package/src/main/lib/__tests__/gateFixtures.json +20 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
- package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
- package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
- package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
- package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
- package/src/main/lib/__tests__/procName.test.cjs +92 -0
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
- package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
- package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
- package/src/main/lib/activeIndexRebuild.cjs +1 -3
- package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
- package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
- package/src/main/lib/agentModelResolve.cjs +94 -24
- package/src/main/lib/agentPersonaSchema.cjs +2 -2
- package/src/main/lib/auditLog.cjs +30 -5
- package/src/main/lib/bootSelfHeal.cjs +88 -0
- package/src/main/lib/buildIdentity.cjs +113 -0
- package/src/main/lib/classifyPromptTicket.cjs +3 -2
- package/src/main/lib/claudeBin.cjs +37 -2
- package/src/main/lib/cleanEnv.cjs +27 -1
- package/src/main/lib/credentials.cjs +4 -2
- package/src/main/lib/cwdClassify.cjs +185 -0
- package/src/main/lib/definitionOfDone.cjs +296 -52
- package/src/main/lib/delegationReadiness.cjs +290 -225
- package/src/main/lib/dispatchLoop.cjs +40 -0
- package/src/main/lib/effectiveModelInfo.cjs +332 -0
- package/src/main/lib/ephemeralCwd.cjs +7 -28
- package/src/main/lib/epicMint.cjs +3 -3
- package/src/main/lib/gitWorktree.cjs +136 -19
- package/src/main/lib/guardShims.cjs +156 -0
- package/src/main/lib/historyRollup.cjs +6 -7
- package/src/main/lib/instanceLock.cjs +32 -5
- package/src/main/lib/jobDirtFilter.cjs +7 -2
- package/src/main/lib/jobSupervisorRecord.cjs +147 -0
- package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
- package/src/main/lib/launchFailure.cjs +2 -1
- package/src/main/lib/localAdminHttp.cjs +9 -27
- package/src/main/lib/mcpToolCatalog.cjs +33 -78
- package/src/main/lib/needsReviewLedger.cjs +205 -0
- package/src/main/lib/opsErrorLog.cjs +1 -1
- package/src/main/lib/opsOwnership.cjs +27 -19
- package/src/main/lib/prdAuthoringSeed.cjs +36 -0
- package/src/main/lib/prdCreate.cjs +56 -1
- package/src/main/lib/prdDisposition.cjs +199 -0
- package/src/main/lib/prdFrontmatter.cjs +8 -2
- package/src/main/lib/prdLocations.cjs +214 -45
- package/src/main/lib/procIdentity.cjs +126 -0
- package/src/main/lib/procName.cjs +98 -0
- package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
- package/src/main/lib/projectRootResolve.cjs +1 -1
- package/src/main/lib/queueHistory.cjs +27 -8
- package/src/main/lib/queueStore.cjs +63 -38
- package/src/main/lib/quietMachineLease.cjs +19 -2
- package/src/main/lib/reaperHelpers.cjs +181 -15
- package/src/main/lib/reservationExpiry.cjs +30 -0
- package/src/main/lib/runClaudeP.cjs +4 -2
- package/src/main/lib/runLogRetention.cjs +3 -2
- package/src/main/lib/scheduleJobSchema.cjs +10 -2
- package/src/main/lib/scheduleJobTransitions.cjs +60 -0
- package/src/main/lib/schedulerBatch.cjs +27 -2
- package/src/main/lib/schedulerConfig.cjs +24 -0
- package/src/main/lib/schedulerPaths.cjs +175 -0
- package/src/main/lib/schedulerRuntimeState.cjs +59 -0
- package/src/main/lib/sessionSlots.cjs +47 -9
- package/src/main/lib/smProcNames.cjs +51 -0
- package/src/main/lib/upgradeDrain.cjs +188 -0
- package/src/main/lib/usageCircuit.cjs +206 -0
- package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
- package/src/main/lib/withTimeout.cjs +33 -0
- package/src/main/mcpStatus.cjs +4 -2
- package/src/main/pluginInstall.cjs +6 -2
- package/src/main/projectPages.cjs +41 -145
- package/src/main/pty.cjs +8 -1
- package/src/main/queueOps.cjs +10 -10
- package/src/main/runVerify.cjs +69 -3
- package/src/main/scheduler/prdParser.cjs +13 -0
- package/src/main/scheduler.cjs +2841 -482
- package/src/main/seedAgentPersonas.cjs +1 -1
- package/src/main/seedDevPlugin.cjs +1 -1
- package/src/main/seedSchedulerMcp.cjs +7 -6
- package/src/main/seedStatus.cjs +1 -1
- package/src/main/supervisor.cjs +5 -3
- package/src/main/templates/PRD_AUTHORING.md +50 -0
- package/src/main/usage.cjs +140 -58
- package/src/preload/api.d.ts +110 -65
- package/src/preload/index.cjs +12 -4
- package/src/seed/agents/project-home-builder.md +31 -48
- package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
- package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
- package/dist/assets/Permissions-BiYZNGYW.js +0 -3
- package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
- package/dist/assets/Settings-Cv-pRyms.js +0 -3
- package/dist/assets/settingsSchema-BJVciriw.js +0 -3
- package/screenshots/.gitkeep +0 -0
- package/screenshots/README-screenshots.md +0 -13
- package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
- package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
- package/scripts/render-project-pages.cjs +0 -70
- package/scripts/validate-project-pages-summary.cjs +0 -62
- package/src/main/lib/projectPageSummarySchema.cjs +0 -181
- package/src/main/teams.cjs +0 -95
- package/src/main/templates/project-pages-catalog.json +0 -741
- package/src/main/templates/project-pages-default-home.html +0 -123
- package/src/main/templates/project-pages-pipeline.md +0 -417
- package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
- package/src/seed/prompts/code-review/correctness-only.md +0 -8
- package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
- package/src/seed/prompts/code-review/hallucination-check.md +0 -8
- package/src/seed/prompts/code-review/public-api-compat.md +0 -8
- package/src/seed/prompts/code-review/readability-naming.md +0 -8
- package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
- package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
- package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
- package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
- package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
- package/src/seed/prompts/documentation/adr-from-change.md +0 -8
- package/src/seed/prompts/documentation/module-readme.md +0 -8
- package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
- package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
- package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
- package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
- package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
- package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
- package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
- package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
- package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
- package/src/seed/prompts/performance/complexity-audit.md +0 -8
- package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
- package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
- package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
- package/src/seed/prompts/qa/api-contract-tests.md +0 -8
- package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
- package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
- package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
- package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
- package/src/seed/prompts/qa/tdd-red-first.md +0 -8
- package/src/seed/prompts/qa/visual-regression-review.md +0 -8
- package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
- package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
- package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
- package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
- package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
- package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
- package/src/seed/prompts/security/authz-audit.md +0 -8
- package/src/seed/prompts/security/crypto-correctness.md +0 -8
- package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
- package/src/seed/prompts/security/dependency-audit.md +0 -8
- package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
- package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
- package/src/seed/prompts/security/secret-credential-scan.md +0 -8
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Run: timeout 120 npx vitest run src/main/lib/__tests__/active-sessions.test.cjs
|
|
4
|
+
|
|
5
|
+
import { vi } from 'vitest';
|
|
6
|
+
const assert = require('node:assert/strict');
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const os = require('node:os');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const { activeProjectCwds, allProjectCwds, bustProjectCwdCache } = require('../activeSessions.cjs');
|
|
11
|
+
|
|
12
|
+
function tmpDir() {
|
|
13
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'active-sessions-test-'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeTranscript(projectsDir, slug, fileName, cwd, mtimeMsAgo) {
|
|
17
|
+
const projDir = path.join(projectsDir, slug);
|
|
18
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
19
|
+
const fp = path.join(projDir, fileName);
|
|
20
|
+
fs.writeFileSync(fp, JSON.stringify({ cwd }) + '\n');
|
|
21
|
+
const mtime = new Date(Date.now() - mtimeMsAgo);
|
|
22
|
+
fs.utimesSync(fp, mtime, mtime);
|
|
23
|
+
return fp;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test('two recent + one stale + one nonexistent → returns exactly the two recent existing cwds', () => {
|
|
27
|
+
const base = tmpDir();
|
|
28
|
+
try {
|
|
29
|
+
const projectsDir = path.join(base, 'projects');
|
|
30
|
+
const cwdA = path.join(base, 'proj-a');
|
|
31
|
+
const cwdB = path.join(base, 'proj-b');
|
|
32
|
+
const cwdC = path.join(base, 'proj-c'); // stale — exists on disk
|
|
33
|
+
const cwdMissing = path.join(base, 'proj-missing'); // never created
|
|
34
|
+
fs.mkdirSync(cwdA);
|
|
35
|
+
fs.mkdirSync(cwdB);
|
|
36
|
+
fs.mkdirSync(cwdC);
|
|
37
|
+
|
|
38
|
+
const MAX_AGE_MIN = 90;
|
|
39
|
+
writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 10 * 60 * 1000);
|
|
40
|
+
writeTranscript(projectsDir, 'slug-b', 'b.jsonl', cwdB, 10 * 60 * 1000);
|
|
41
|
+
writeTranscript(projectsDir, 'slug-c', 'c.jsonl', cwdC, 120 * 60 * 1000);
|
|
42
|
+
writeTranscript(projectsDir, 'slug-missing', 'm.jsonl', cwdMissing, 10 * 60 * 1000);
|
|
43
|
+
|
|
44
|
+
const result = activeProjectCwds(MAX_AGE_MIN, { projectsDir });
|
|
45
|
+
|
|
46
|
+
assert.equal(
|
|
47
|
+
result.length, 2,
|
|
48
|
+
`expected 2 cwds, got ${result.length}: ${JSON.stringify(result)}`,
|
|
49
|
+
);
|
|
50
|
+
assert.ok(result.includes(cwdA), `cwdA must be in result`);
|
|
51
|
+
assert.ok(result.includes(cwdB), `cwdB must be in result`);
|
|
52
|
+
assert.ok(!result.includes(cwdC), 'stale cwdC must not appear');
|
|
53
|
+
assert.ok(!result.includes(cwdMissing), 'nonexistent path must not appear');
|
|
54
|
+
} finally {
|
|
55
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('duplicate cwd across two project dirs → deduped to one entry', () => {
|
|
60
|
+
const base = tmpDir();
|
|
61
|
+
try {
|
|
62
|
+
const projectsDir = path.join(base, 'projects');
|
|
63
|
+
const cwdA = path.join(base, 'proj-a');
|
|
64
|
+
fs.mkdirSync(cwdA);
|
|
65
|
+
|
|
66
|
+
writeTranscript(projectsDir, 'slug-1', 'a.jsonl', cwdA, 5 * 60 * 1000);
|
|
67
|
+
writeTranscript(projectsDir, 'slug-2', 'b.jsonl', cwdA, 5 * 60 * 1000);
|
|
68
|
+
|
|
69
|
+
const result = activeProjectCwds(90, { projectsDir });
|
|
70
|
+
assert.equal(result.length, 1, 'duplicate cwd must be deduped');
|
|
71
|
+
assert.equal(result[0], cwdA);
|
|
72
|
+
} finally {
|
|
73
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('missing projects dir → empty result', () => {
|
|
78
|
+
const base = tmpDir();
|
|
79
|
+
try {
|
|
80
|
+
const result = activeProjectCwds(90, { projectsDir: path.join(base, 'no-such-dir') });
|
|
81
|
+
assert.deepEqual(result, []);
|
|
82
|
+
} finally {
|
|
83
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('prompts.jsonl absent → transcripts still detected (sole detection path)', () => {
|
|
88
|
+
const base = tmpDir();
|
|
89
|
+
try {
|
|
90
|
+
// No knowledge-log / prompts.jsonl anywhere in this tmp tree — the
|
|
91
|
+
// function must not need it. logPath, if passed, is a harmless no-op.
|
|
92
|
+
const projectsDir = path.join(base, 'projects');
|
|
93
|
+
const cwdA = path.join(base, 'proj-a');
|
|
94
|
+
fs.mkdirSync(cwdA);
|
|
95
|
+
|
|
96
|
+
writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 5 * 60 * 1000);
|
|
97
|
+
|
|
98
|
+
const missingLogPath = path.join(base, 'knowledge-log', 'prompts.jsonl');
|
|
99
|
+
assert.ok(!fs.existsSync(missingLogPath), 'sanity: log path must not exist');
|
|
100
|
+
|
|
101
|
+
const result = activeProjectCwds(90, { projectsDir, logPath: missingLogPath });
|
|
102
|
+
assert.deepEqual(result, [cwdA]);
|
|
103
|
+
} finally {
|
|
104
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('fresh transcript mtime with no prompt log present → cwd is returned', () => {
|
|
109
|
+
const base = tmpDir();
|
|
110
|
+
try {
|
|
111
|
+
const projectsDir = path.join(base, 'projects');
|
|
112
|
+
const cwdA = path.join(base, 'proj-fresh');
|
|
113
|
+
fs.mkdirSync(cwdA);
|
|
114
|
+
|
|
115
|
+
writeTranscript(projectsDir, 'slug-fresh', 'fresh.jsonl', cwdA, 60 * 1000); // 1 min ago
|
|
116
|
+
|
|
117
|
+
const result = activeProjectCwds(90, { projectsDir });
|
|
118
|
+
assert.equal(result.length, 1);
|
|
119
|
+
assert.equal(result[0], cwdA);
|
|
120
|
+
} finally {
|
|
121
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('garbage lines in transcript are skipped, cwd read from last parseable line', () => {
|
|
126
|
+
const base = tmpDir();
|
|
127
|
+
try {
|
|
128
|
+
const projectsDir = path.join(base, 'projects');
|
|
129
|
+
const cwdA = path.join(base, 'proj-a');
|
|
130
|
+
fs.mkdirSync(cwdA);
|
|
131
|
+
|
|
132
|
+
const projDir = path.join(projectsDir, 'slug-a');
|
|
133
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
134
|
+
const fp = path.join(projDir, 'a.jsonl');
|
|
135
|
+
fs.writeFileSync(fp, [
|
|
136
|
+
JSON.stringify({ cwd: cwdA }),
|
|
137
|
+
'not json at all!!!',
|
|
138
|
+
'{ broken',
|
|
139
|
+
].join('\n') + '\n');
|
|
140
|
+
const mtime = new Date(Date.now() - 5 * 60 * 1000);
|
|
141
|
+
fs.utimesSync(fp, mtime, mtime);
|
|
142
|
+
|
|
143
|
+
const result = activeProjectCwds(90, { projectsDir });
|
|
144
|
+
assert.equal(result.length, 1);
|
|
145
|
+
assert.equal(result[0], cwdA);
|
|
146
|
+
} finally {
|
|
147
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('5 consecutive allProjectCwds() calls with the same args perform ONE directory scan', () => {
|
|
152
|
+
const base = tmpDir();
|
|
153
|
+
try {
|
|
154
|
+
const projectsDir = path.join(base, 'projects');
|
|
155
|
+
const cwdA = path.join(base, 'proj-a');
|
|
156
|
+
fs.mkdirSync(cwdA);
|
|
157
|
+
writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 5 * 60 * 1000);
|
|
158
|
+
|
|
159
|
+
bustProjectCwdCache();
|
|
160
|
+
const readdirSpy = vi.spyOn(fs, 'readdirSync');
|
|
161
|
+
|
|
162
|
+
const first = allProjectCwds({ projectsDir });
|
|
163
|
+
const callsAfterFirst = readdirSpy.mock.calls.length;
|
|
164
|
+
assert.ok(callsAfterFirst > 0, 'first call must actually scan');
|
|
165
|
+
|
|
166
|
+
for (let i = 0; i < 4; i++) {
|
|
167
|
+
const again = allProjectCwds({ projectsDir });
|
|
168
|
+
assert.deepEqual(again, first);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
assert.equal(
|
|
172
|
+
readdirSpy.mock.calls.length, callsAfterFirst,
|
|
173
|
+
'the 4 repeat calls must not perform any additional directory scan',
|
|
174
|
+
);
|
|
175
|
+
} finally {
|
|
176
|
+
vi.restoreAllMocks();
|
|
177
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('bustProjectCwdCache() forces the very next call to rescan', () => {
|
|
182
|
+
const base = tmpDir();
|
|
183
|
+
try {
|
|
184
|
+
const projectsDir = path.join(base, 'projects');
|
|
185
|
+
const cwdA = path.join(base, 'proj-a');
|
|
186
|
+
fs.mkdirSync(cwdA);
|
|
187
|
+
writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 5 * 60 * 1000);
|
|
188
|
+
|
|
189
|
+
bustProjectCwdCache();
|
|
190
|
+
allProjectCwds({ projectsDir }); // warm the cache
|
|
191
|
+
|
|
192
|
+
const readdirSpy = vi.spyOn(fs, 'readdirSync');
|
|
193
|
+
allProjectCwds({ projectsDir });
|
|
194
|
+
assert.equal(readdirSpy.mock.calls.length, 0, 'a cache hit must not scan');
|
|
195
|
+
|
|
196
|
+
bustProjectCwdCache();
|
|
197
|
+
allProjectCwds({ projectsDir });
|
|
198
|
+
assert.ok(readdirSpy.mock.calls.length > 0, 'a bust must force a rescan on the next call');
|
|
199
|
+
} finally {
|
|
200
|
+
vi.restoreAllMocks();
|
|
201
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('allProjectCwds() and activeProjectCwds(90) do not share a cache entry', () => {
|
|
206
|
+
const base = tmpDir();
|
|
207
|
+
try {
|
|
208
|
+
const projectsDir = path.join(base, 'projects');
|
|
209
|
+
const cwdFresh = path.join(base, 'proj-fresh');
|
|
210
|
+
const cwdStale = path.join(base, 'proj-stale');
|
|
211
|
+
fs.mkdirSync(cwdFresh);
|
|
212
|
+
fs.mkdirSync(cwdStale);
|
|
213
|
+
writeTranscript(projectsDir, 'slug-fresh', 'fresh.jsonl', cwdFresh, 5 * 60 * 1000);
|
|
214
|
+
writeTranscript(projectsDir, 'slug-stale', 'stale.jsonl', cwdStale, 120 * 60 * 1000);
|
|
215
|
+
|
|
216
|
+
bustProjectCwdCache();
|
|
217
|
+
// allProjectCwds (maxAgeMin Infinity, maxCwds 500) sees both — cached
|
|
218
|
+
// first under its own key.
|
|
219
|
+
const all = allProjectCwds({ projectsDir });
|
|
220
|
+
assert.ok(all.includes(cwdFresh) && all.includes(cwdStale));
|
|
221
|
+
|
|
222
|
+
// activeProjectCwds(90) must recompute under its OWN key, not return
|
|
223
|
+
// allProjectCwds' cached (Infinity, 500) result — proving the cache key
|
|
224
|
+
// includes maxAgeMin/maxCwds, not just projectsDir.
|
|
225
|
+
const active = activeProjectCwds(90, { projectsDir });
|
|
226
|
+
assert.ok(active.includes(cwdFresh), 'fresh cwd must be present');
|
|
227
|
+
assert.ok(!active.includes(cwdStale), 'stale cwd must be excluded by the 90-min filter');
|
|
228
|
+
} finally {
|
|
229
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('missing projectsDir returns [] and does not poison the cache once the dir appears', () => {
|
|
234
|
+
const base = tmpDir();
|
|
235
|
+
try {
|
|
236
|
+
const projectsDir = path.join(base, 'projects');
|
|
237
|
+
bustProjectCwdCache();
|
|
238
|
+
|
|
239
|
+
assert.deepEqual(activeProjectCwds(90, { projectsDir }), []);
|
|
240
|
+
assert.deepEqual(activeProjectCwds(90, { projectsDir }), []);
|
|
241
|
+
|
|
242
|
+
const cwdA = path.join(base, 'proj-a');
|
|
243
|
+
fs.mkdirSync(cwdA);
|
|
244
|
+
writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 5 * 60 * 1000);
|
|
245
|
+
|
|
246
|
+
const result = activeProjectCwds(90, { projectsDir });
|
|
247
|
+
assert.deepEqual(result, [cwdA], 'the dir appearing later must be visible, not stuck on a cached []');
|
|
248
|
+
} finally {
|
|
249
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auditLog.test.cjs — readTail returns a bounded reverse tail of complete lines.
|
|
3
|
+
*
|
|
4
|
+
* Run: timeout 120 npx vitest run src/main/lib/__tests__/auditLog.test.cjs
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
import { test, expect, beforeAll, afterAll } from 'vitest';
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const os = require('node:os');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const { readTail } = require('../auditLog.cjs');
|
|
13
|
+
|
|
14
|
+
let dir;
|
|
15
|
+
beforeAll(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'auditlog-tail-')); });
|
|
16
|
+
afterAll(() => { fs.rmSync(dir, { recursive: true, force: true }); });
|
|
17
|
+
|
|
18
|
+
test('missing file → []', () => {
|
|
19
|
+
expect(readTail(1024, path.join(dir, 'nope.jsonl'))).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('window larger than file → every line', () => {
|
|
23
|
+
const f = path.join(dir, 'small.jsonl');
|
|
24
|
+
fs.writeFileSync(f, '{"n":1}\n{"n":2}\n');
|
|
25
|
+
expect(readTail(1_000_000, f)).toEqual(['{"n":1}', '{"n":2}']);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('bounded window drops the partial leading line and keeps the newest', () => {
|
|
29
|
+
const f = path.join(dir, 'big.jsonl');
|
|
30
|
+
const lines = Array.from({ length: 1000 }, (_, i) => JSON.stringify({ n: i, pad: 'x'.repeat(50) }));
|
|
31
|
+
fs.writeFileSync(f, lines.join('\n') + '\n');
|
|
32
|
+
const tail = readTail(500, f);
|
|
33
|
+
expect(tail.length).toBeGreaterThan(0);
|
|
34
|
+
expect(tail.length).toBeLessThan(20);
|
|
35
|
+
expect(tail[tail.length - 1]).toBe(lines[999]);
|
|
36
|
+
for (const l of tail) expect(() => JSON.parse(l)).not.toThrow();
|
|
37
|
+
expect(readTail(0, f)).toEqual([]);
|
|
38
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bootSelfHeal.test.cjs — the epic-index self-heal scan must be SCHEDULED
|
|
3
|
+
* off app.whenReady()'s synchronous critical path, not executed inline, so
|
|
4
|
+
* the ~270ms allProjectCwds() scan can't compete with the renderer's first
|
|
5
|
+
* IPC round trips (schedule.state, billing.fetch, teams.list) for the event
|
|
6
|
+
* loop at boot.
|
|
7
|
+
*
|
|
8
|
+
* Run: timeout 300 npx vitest run src/main/lib/__tests__/bootSelfHeal.test.cjs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
import { test, expect, vi } from 'vitest';
|
|
14
|
+
const { EventEmitter } = require('node:events');
|
|
15
|
+
const { scheduleBootSelfHeal, runEpicIndexSelfHeal } = require('../bootSelfHeal.cjs');
|
|
16
|
+
|
|
17
|
+
function flush(times = 2) {
|
|
18
|
+
return (async () => {
|
|
19
|
+
for (let i = 0; i < times; i++) {
|
|
20
|
+
await new Promise((resolve) => { setImmediate(resolve); });
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeDeps() {
|
|
26
|
+
return {
|
|
27
|
+
fs: { existsSync: vi.fn(() => true), readFileSync: vi.fn(() => '{}') },
|
|
28
|
+
allProjectCwds: vi.fn(() => ['/proj-a']),
|
|
29
|
+
promptSessionsActiveIndexPath: (cwd) => `${cwd}/active-index.json`,
|
|
30
|
+
rebuildActiveIndex: vi.fn(() => ({ rows: [], skipped: [] })),
|
|
31
|
+
logs: { writeLine: vi.fn() },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function fakeWindow() {
|
|
36
|
+
const webContents = new EventEmitter();
|
|
37
|
+
webContents.isDestroyed = () => false;
|
|
38
|
+
return { isDestroyed: () => false, webContents };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test('scheduleBootSelfHeal does not run the scan synchronously — only after did-finish-load', async () => {
|
|
42
|
+
const deps = makeDeps();
|
|
43
|
+
const mainWindow = fakeWindow();
|
|
44
|
+
|
|
45
|
+
scheduleBootSelfHeal(mainWindow, deps);
|
|
46
|
+
expect(deps.allProjectCwds).not.toHaveBeenCalled();
|
|
47
|
+
|
|
48
|
+
mainWindow.webContents.emit('did-finish-load');
|
|
49
|
+
expect(deps.allProjectCwds).toHaveBeenCalledTimes(1);
|
|
50
|
+
await flush();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('a window destroyed before did-finish-load falls back to an unconditional setImmediate run', async () => {
|
|
54
|
+
const deps = makeDeps();
|
|
55
|
+
const mainWindow = fakeWindow();
|
|
56
|
+
|
|
57
|
+
scheduleBootSelfHeal(mainWindow, deps);
|
|
58
|
+
mainWindow.webContents.emit('destroyed');
|
|
59
|
+
expect(deps.allProjectCwds).not.toHaveBeenCalled();
|
|
60
|
+
|
|
61
|
+
await flush();
|
|
62
|
+
expect(deps.allProjectCwds).toHaveBeenCalledTimes(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('a headless / no-window boot still self-heals via setImmediate', async () => {
|
|
66
|
+
const deps = makeDeps();
|
|
67
|
+
|
|
68
|
+
scheduleBootSelfHeal(null, deps);
|
|
69
|
+
expect(deps.allProjectCwds).not.toHaveBeenCalled();
|
|
70
|
+
|
|
71
|
+
await flush();
|
|
72
|
+
expect(deps.allProjectCwds).toHaveBeenCalledTimes(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('runs exactly once per boot even if did-finish-load and destroyed both fire', async () => {
|
|
76
|
+
const deps = makeDeps();
|
|
77
|
+
const mainWindow = fakeWindow();
|
|
78
|
+
|
|
79
|
+
scheduleBootSelfHeal(mainWindow, deps);
|
|
80
|
+
mainWindow.webContents.emit('did-finish-load');
|
|
81
|
+
mainWindow.webContents.emit('destroyed');
|
|
82
|
+
await flush();
|
|
83
|
+
|
|
84
|
+
expect(deps.allProjectCwds).toHaveBeenCalledTimes(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('runEpicIndexSelfHeal rebuilds only a project whose index is missing or unparseable — a clean index is never rewritten', async () => {
|
|
88
|
+
const files = {
|
|
89
|
+
'/clean-proj/active-index.json': '{"sessions":{}}',
|
|
90
|
+
'/broken-proj/active-index.json': '{ not json',
|
|
91
|
+
};
|
|
92
|
+
const fs = {
|
|
93
|
+
existsSync: (p) => Object.prototype.hasOwnProperty.call(files, p),
|
|
94
|
+
readFileSync: (p) => files[p],
|
|
95
|
+
};
|
|
96
|
+
const rebuildActiveIndex = vi.fn(() => ({ rows: [], skipped: [] }));
|
|
97
|
+
const logs = { writeLine: vi.fn() };
|
|
98
|
+
const allProjectCwds = () => ['/clean-proj', '/broken-proj', '/missing-proj'];
|
|
99
|
+
const promptSessionsActiveIndexPath = (cwd) => `${cwd}/active-index.json`;
|
|
100
|
+
|
|
101
|
+
await runEpicIndexSelfHeal({ fs, allProjectCwds, promptSessionsActiveIndexPath, rebuildActiveIndex, logs });
|
|
102
|
+
|
|
103
|
+
expect(rebuildActiveIndex).toHaveBeenCalledTimes(2);
|
|
104
|
+
expect(rebuildActiveIndex).toHaveBeenCalledWith('/broken-proj');
|
|
105
|
+
expect(rebuildActiveIndex).toHaveBeenCalledWith('/missing-proj');
|
|
106
|
+
expect(rebuildActiveIndex).not.toHaveBeenCalledWith('/clean-proj');
|
|
107
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buildIdentity.test.cjs — resolveBuildIdentity() resolution order:
|
|
3
|
+
* build-info.json first, git fallback only when a real .git sits AT the
|
|
4
|
+
* package root (never walking up), else null.
|
|
5
|
+
*
|
|
6
|
+
* Each test injects a distinct fixture `packageRoot` (see buildIdentity.cjs's
|
|
7
|
+
* `opts.packageRoot`), which also keeps memoization scoped per-root so the
|
|
8
|
+
* three cases don't clobber each other's cached result in one process.
|
|
9
|
+
*
|
|
10
|
+
* Run: timeout 120 npx vitest run src/main/lib/__tests__/buildIdentity.test.cjs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
import { test, expect, afterEach } from 'vitest';
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const os = require('node:os');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { execFileSync } = require('node:child_process');
|
|
20
|
+
const { resolveBuildIdentity } = require('../buildIdentity.cjs');
|
|
21
|
+
|
|
22
|
+
const cleanupDirs = [];
|
|
23
|
+
|
|
24
|
+
function mkFixtureRoot(prefix) {
|
|
25
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
26
|
+
cleanupDirs.push(dir);
|
|
27
|
+
fs.mkdirSync(path.join(dir, 'src', 'main'), { recursive: true });
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function writePackageJson(root, version) {
|
|
32
|
+
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'fixture-pkg', version }));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function mkRealGitRepo(root) {
|
|
36
|
+
execFileSync('git', ['-C', root, 'init', '-q']);
|
|
37
|
+
execFileSync('git', ['-C', root, 'config', 'user.email', 'test@test.com']);
|
|
38
|
+
execFileSync('git', ['-C', root, 'config', 'user.name', 'Test']);
|
|
39
|
+
fs.writeFileSync(path.join(root, 'README.md'), 'fixture\n');
|
|
40
|
+
execFileSync('git', ['-C', root, 'add', 'README.md']);
|
|
41
|
+
execFileSync('git', ['-C', root, 'commit', '-q', '-m', 'init']);
|
|
42
|
+
return execFileSync('git', ['-C', root, 'rev-parse', '--short', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
while (cleanupDirs.length) {
|
|
47
|
+
const dir = cleanupDirs.pop();
|
|
48
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('build-info.json present: its fields win over both git and package.json', () => {
|
|
53
|
+
const root = mkFixtureRoot('sm-buildid-present-');
|
|
54
|
+
writePackageJson(root, '0.1.0');
|
|
55
|
+
fs.writeFileSync(path.join(root, 'src', 'main', 'build-info.json'), JSON.stringify({
|
|
56
|
+
version: '9.9.9',
|
|
57
|
+
gitSha: 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
|
|
58
|
+
gitShortSha: 'deadbee',
|
|
59
|
+
gitBranch: 'main',
|
|
60
|
+
builtAt: '2026-01-01T00:00:00.000Z',
|
|
61
|
+
dirty: false,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
const identity = resolveBuildIdentity({ packageRoot: root, bootedAt: '2026-02-02T00:00:00.000Z' });
|
|
65
|
+
|
|
66
|
+
expect(identity.version).toBe('9.9.9');
|
|
67
|
+
expect(identity.codeSha).toBe('deadbee');
|
|
68
|
+
expect(identity.builtAt).toBe('2026-01-01T00:00:00.000Z');
|
|
69
|
+
expect(identity.appPath).toBe(root);
|
|
70
|
+
expect(identity.bootedAt).toBe('2026-02-02T00:00:00.000Z');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('build-info.json absent, real .git at the package root: codeSha falls back to git, builtAt stays null', () => {
|
|
74
|
+
const root = mkFixtureRoot('sm-buildid-git-');
|
|
75
|
+
writePackageJson(root, '1.2.3');
|
|
76
|
+
const sha = mkRealGitRepo(root);
|
|
77
|
+
|
|
78
|
+
const identity = resolveBuildIdentity({ packageRoot: root });
|
|
79
|
+
|
|
80
|
+
expect(identity.version).toBe('1.2.3');
|
|
81
|
+
expect(identity.codeSha).toBe(sha);
|
|
82
|
+
expect(identity.builtAt).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('build-info.json absent, no .git at the package root: codeSha is null, never walks up to an ancestor .git', () => {
|
|
86
|
+
const ancestor = mkFixtureRoot('sm-buildid-ancestor-');
|
|
87
|
+
mkRealGitRepo(ancestor); // a real repo one level UP from the fixture root — must never be found
|
|
88
|
+
const root = path.join(ancestor, 'nested-pkg');
|
|
89
|
+
fs.mkdirSync(path.join(root, 'src', 'main'), { recursive: true });
|
|
90
|
+
writePackageJson(root, '2.0.0');
|
|
91
|
+
|
|
92
|
+
const identity = resolveBuildIdentity({ packageRoot: root });
|
|
93
|
+
|
|
94
|
+
expect(identity.version).toBe('2.0.0');
|
|
95
|
+
expect(identity.codeSha).toBeNull();
|
|
96
|
+
expect(identity.builtAt).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('bootedAt is echoed back verbatim, never re-captured, and defaults to null', () => {
|
|
100
|
+
const root = mkFixtureRoot('sm-buildid-bootedat-');
|
|
101
|
+
writePackageJson(root, '1.0.0');
|
|
102
|
+
|
|
103
|
+
expect(resolveBuildIdentity({ packageRoot: root }).bootedAt).toBeNull();
|
|
104
|
+
expect(resolveBuildIdentity({ packageRoot: root, bootedAt: 'fixed-value' }).bootedAt).toBe('fixed-value');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('installChannel is reused from machineProfile.resolveInstallChannel, keyed off appPath', () => {
|
|
108
|
+
const root = mkFixtureRoot('sm-buildid-channel-');
|
|
109
|
+
writePackageJson(root, '1.0.0');
|
|
110
|
+
const prevSmDev = process.env.SM_DEV;
|
|
111
|
+
delete process.env.SM_DEV;
|
|
112
|
+
try {
|
|
113
|
+
const identity = resolveBuildIdentity({ packageRoot: root });
|
|
114
|
+
// fixture root lives under os.tmpdir(), not an .npm/_npx cache path, and
|
|
115
|
+
// SM_DEV is unset here, so resolveInstallChannel falls through to 'unknown'.
|
|
116
|
+
expect(identity.installChannel).toBe('unknown');
|
|
117
|
+
} finally {
|
|
118
|
+
if (prevSmDev === undefined) delete process.env.SM_DEV;
|
|
119
|
+
else process.env.SM_DEV = prevSmDev;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// cwdClassify.cjs — one classifier for "what project is this path", failing
|
|
2
|
+
// CLOSED on the unprovable worktree case. Fixtures live under a per-run
|
|
3
|
+
// mkdtemp sandbox, never the live roots.
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { classifyCwd, nearestGitEntry, worktreeMainRootOf } = require('../cwdClassify.cjs');
|
|
9
|
+
const { assertOpsWrite, resolveOpsRoot } = require('../opsOwnership.cjs');
|
|
10
|
+
const { isEphemeralCwd } = require('../ephemeralCwd.cjs');
|
|
11
|
+
const { projectRootOf } = require('../activeSessions.cjs');
|
|
12
|
+
|
|
13
|
+
const OPS = 'session-manager-operations';
|
|
14
|
+
let sandbox;
|
|
15
|
+
const real = (p) => fs.realpathSync(p);
|
|
16
|
+
|
|
17
|
+
beforeEach(() => { sandbox = real(fs.mkdtempSync(path.join(os.tmpdir(), 'sm-cwdclassify-'))); });
|
|
18
|
+
afterEach(() => { fs.rmSync(sandbox, { recursive: true, force: true }); });
|
|
19
|
+
|
|
20
|
+
function mkWorktree({ backRef = true } = {}) {
|
|
21
|
+
const main = path.join(sandbox, 'main');
|
|
22
|
+
const wt = path.join(sandbox, 'wt');
|
|
23
|
+
const admin = path.join(main, '.git', 'worktrees', 'wt1');
|
|
24
|
+
fs.mkdirSync(admin, { recursive: true });
|
|
25
|
+
fs.mkdirSync(wt, { recursive: true });
|
|
26
|
+
fs.writeFileSync(path.join(wt, '.git'), `gitdir: ${admin}\n`);
|
|
27
|
+
if (backRef) fs.writeFileSync(path.join(admin, 'gitdir'), `${path.join(wt, '.git')}\n`);
|
|
28
|
+
return { main, wt };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test('healthy worktree → worktree, projectRoot is the main tree', () => {
|
|
32
|
+
const { main, wt } = mkWorktree();
|
|
33
|
+
const c = classifyCwd(wt);
|
|
34
|
+
assert.equal(c.kind, 'worktree');
|
|
35
|
+
assert.equal(c.projectRoot, main);
|
|
36
|
+
assert.equal(projectRootOf(wt), main);
|
|
37
|
+
assert.equal(isEphemeralCwd(wt), true);
|
|
38
|
+
assert.equal(worktreeMainRootOf(wt), main);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('broken back-reference → unknown; ops writes refused, resolution and dispatch stay open', () => {
|
|
42
|
+
const { wt } = mkWorktree({ backRef: false });
|
|
43
|
+
const c = classifyCwd(wt);
|
|
44
|
+
assert.equal(c.kind, 'unknown');
|
|
45
|
+
assert.equal(c.projectRoot, wt);
|
|
46
|
+
assert.match(c.reason, /back-reference/);
|
|
47
|
+
assert.equal(projectRootOf(wt), wt); // total — never throws
|
|
48
|
+
assert.equal(resolveOpsRoot(wt), path.join(wt, OPS)); // reads/dispatch unaffected
|
|
49
|
+
assert.throws(
|
|
50
|
+
() => assertOpsWrite(path.join(wt, OPS, 'scheduler', 'state', 'queue.json'), 'scheduler'),
|
|
51
|
+
(e) => e.unknownCwd === true && /back-reference/.test(e.message),
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('garbled .git file → unknown', () => {
|
|
56
|
+
const dir = path.join(sandbox, 'garbled');
|
|
57
|
+
fs.mkdirSync(dir);
|
|
58
|
+
fs.writeFileSync(path.join(dir, '.git'), 'not a pointer\n');
|
|
59
|
+
assert.equal(classifyCwd(dir).kind, 'unknown');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('separate-git-dir → project (gitdir not under worktrees/)', () => {
|
|
63
|
+
const dir = path.join(sandbox, 'sep');
|
|
64
|
+
fs.mkdirSync(dir);
|
|
65
|
+
fs.writeFileSync(path.join(dir, '.git'), `gitdir: ${path.join(sandbox, 'elsewhere.git')}\n`);
|
|
66
|
+
const c = classifyCwd(dir);
|
|
67
|
+
assert.equal(c.kind, 'project');
|
|
68
|
+
assert.equal(c.projectRoot, dir);
|
|
69
|
+
assert.doesNotThrow(() => assertOpsWrite(path.join(dir, OPS, 'scheduler', 'state', 'queue.json'), 'scheduler'));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('submodule (relative gitdir) → project rooted at the submodule', () => {
|
|
73
|
+
const sup = path.join(sandbox, 'super');
|
|
74
|
+
const sub = path.join(sup, 'libs', 'sub');
|
|
75
|
+
fs.mkdirSync(path.join(sup, '.git', 'modules', 'sub'), { recursive: true });
|
|
76
|
+
fs.mkdirSync(path.join(sub, 'src'), { recursive: true });
|
|
77
|
+
fs.writeFileSync(path.join(sub, '.git'), 'gitdir: ../../.git/modules/sub\n');
|
|
78
|
+
const c = classifyCwd(path.join(sub, 'src'));
|
|
79
|
+
assert.equal(c.kind, 'project');
|
|
80
|
+
assert.equal(c.projectRoot, sub);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('plain project (.git dir, and no git at all) → project', () => {
|
|
84
|
+
const proj = path.join(sandbox, 'proj');
|
|
85
|
+
fs.mkdirSync(path.join(proj, '.git'), { recursive: true });
|
|
86
|
+
fs.mkdirSync(path.join(proj, 'src'));
|
|
87
|
+
assert.deepEqual(
|
|
88
|
+
{ k: classifyCwd(path.join(proj, 'src')).kind, r: classifyCwd(path.join(proj, 'src')).projectRoot },
|
|
89
|
+
{ k: 'project', r: proj },
|
|
90
|
+
);
|
|
91
|
+
const bare = path.join(sandbox, 'nogit');
|
|
92
|
+
fs.mkdirSync(bare);
|
|
93
|
+
assert.equal(classifyCwd(bare).projectRoot, bare);
|
|
94
|
+
assert.equal(nearestGitEntry(bare), null);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('nested ops root: outermost (indexOf) and innermost (lastIndexOf) are both reported, neither unified', () => {
|
|
98
|
+
const proj = path.join(sandbox, 'proj');
|
|
99
|
+
fs.mkdirSync(path.join(proj, '.git'), { recursive: true });
|
|
100
|
+
const nested = path.join(proj, OPS, 'scheduler', 'prds', OPS, 'scheduler', 'state');
|
|
101
|
+
const c = classifyCwd(nested);
|
|
102
|
+
assert.equal(c.projectRoot, proj);
|
|
103
|
+
assert.equal(c.outermostOpsRoot, path.join(proj, OPS));
|
|
104
|
+
assert.equal(c.innermostOpsRoot, path.join(proj, OPS, 'scheduler', 'prds', OPS));
|
|
105
|
+
assert.equal(classifyCwd(proj).outermostOpsRoot, null);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('ephemeral: os.tmpdir() itself', () => {
|
|
109
|
+
assert.equal(classifyCwd(path.resolve(os.tmpdir())).kind, 'ephemeral');
|
|
110
|
+
assert.equal(isEphemeralCwd(os.tmpdir()), true);
|
|
111
|
+
});
|