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
|
@@ -7,12 +7,17 @@
|
|
|
7
7
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
-
import { test, expect, afterEach } from 'vitest';
|
|
10
|
+
import { test, expect, afterEach, vi } from 'vitest';
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const fsp = require('node:fs/promises');
|
|
13
13
|
const os = require('node:os');
|
|
14
14
|
const path = require('node:path');
|
|
15
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
resolvePrdWriteDir,
|
|
17
|
+
resolvePrdsDirs,
|
|
18
|
+
resolveEpicPrdWriteDir,
|
|
19
|
+
listEpicPrdDirs,
|
|
20
|
+
} = require('../lib/prdLocations.cjs');
|
|
16
21
|
|
|
17
22
|
const tmpDirs = [];
|
|
18
23
|
afterEach(async () => {
|
|
@@ -95,3 +100,96 @@ test('resolvePrdsDirs returns [] when no project has a recent transcript', () =>
|
|
|
95
100
|
const dirs = resolvePrdsDirs(90, { projectsDir: '/nonexistent-projects-dir' });
|
|
96
101
|
expect(dirs).toEqual([]);
|
|
97
102
|
});
|
|
103
|
+
|
|
104
|
+
test('listEpicPrdDirs memoizes on the Epics-root mtime: no readdir on a repeat call, but a freshly minted Epic prds dir is picked up on the very next call', async () => {
|
|
105
|
+
const cwd = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-memo-cwd-'));
|
|
106
|
+
tmpDirs.push(cwd);
|
|
107
|
+
fs.mkdirSync(resolveEpicPrdWriteDir(cwd, 'epic-1'), { recursive: true });
|
|
108
|
+
|
|
109
|
+
const first = listEpicPrdDirs(cwd);
|
|
110
|
+
expect(first).toEqual([resolveEpicPrdWriteDir(cwd, 'epic-1')]);
|
|
111
|
+
|
|
112
|
+
const readdirSpy = vi.spyOn(fs, 'readdirSync');
|
|
113
|
+
try {
|
|
114
|
+
// Nothing under the Epics root changed — the cached result comes back
|
|
115
|
+
// without a fresh readdir of the Epics root.
|
|
116
|
+
const second = listEpicPrdDirs(cwd);
|
|
117
|
+
expect(second).toEqual(first);
|
|
118
|
+
expect(readdirSpy).not.toHaveBeenCalled();
|
|
119
|
+
} finally {
|
|
120
|
+
readdirSpy.mockRestore();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// A brand-new Epic's prds/ dir is a NEW entry under the Epics root, which
|
|
124
|
+
// bumps the root's own mtime — no TTL wait required to see it.
|
|
125
|
+
fs.mkdirSync(resolveEpicPrdWriteDir(cwd, 'epic-2'), { recursive: true });
|
|
126
|
+
const third = listEpicPrdDirs(cwd);
|
|
127
|
+
expect(third).toEqual(
|
|
128
|
+
expect.arrayContaining([resolveEpicPrdWriteDir(cwd, 'epic-1'), resolveEpicPrdWriteDir(cwd, 'epic-2')]),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('listEpicPrdDirs: an unreadable Epics root does not write a poisoned entry that survives the dir becoming readable', async () => {
|
|
133
|
+
const cwd = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-unreadable-cwd-'));
|
|
134
|
+
tmpDirs.push(cwd);
|
|
135
|
+
|
|
136
|
+
// No Epics root at all yet.
|
|
137
|
+
expect(listEpicPrdDirs(cwd)).toEqual([]);
|
|
138
|
+
|
|
139
|
+
// The root now exists, with a real Epic prds dir inside it.
|
|
140
|
+
fs.mkdirSync(resolveEpicPrdWriteDir(cwd, 'epic-1'), { recursive: true });
|
|
141
|
+
expect(listEpicPrdDirs(cwd)).toEqual([resolveEpicPrdWriteDir(cwd, 'epic-1')]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('resolvePrdsDirs EDGE (non-negotiable): a new epics/<id>/prds dir created after a prior call is visible on the VERY NEXT call, no TTL wait', async () => {
|
|
145
|
+
const projectsDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-freshness-projects-'));
|
|
146
|
+
tmpDirs.push(projectsDir);
|
|
147
|
+
const projectCwd = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-freshness-cwd-'));
|
|
148
|
+
tmpDirs.push(projectCwd);
|
|
149
|
+
|
|
150
|
+
const projDir = path.join(projectsDir, 'freshness-project');
|
|
151
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
152
|
+
fs.writeFileSync(path.join(projDir, 'session1.jsonl'), `${JSON.stringify({ cwd: projectCwd })}\n`);
|
|
153
|
+
|
|
154
|
+
const before = resolvePrdsDirs(90, { projectsDir });
|
|
155
|
+
const newEpicPrdDir = resolveEpicPrdWriteDir(projectCwd, 'freshly-minted-epic');
|
|
156
|
+
expect(before).not.toContain(newEpicPrdDir);
|
|
157
|
+
|
|
158
|
+
fs.mkdirSync(newEpicPrdDir, { recursive: true });
|
|
159
|
+
|
|
160
|
+
const after = resolvePrdsDirs(90, { projectsDir });
|
|
161
|
+
expect(after).toContain(newEpicPrdDir);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('resolvePrdsDirs: calls with an explicit { projectsDir } opts are keyed separately per projectsDir and never share a stale cache entry', async () => {
|
|
165
|
+
const projectsDirA = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-keyed-a-'));
|
|
166
|
+
tmpDirs.push(projectsDirA);
|
|
167
|
+
const projectsDirB = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-keyed-b-'));
|
|
168
|
+
tmpDirs.push(projectsDirB);
|
|
169
|
+
const cwdA = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-keyed-cwd-a-'));
|
|
170
|
+
tmpDirs.push(cwdA);
|
|
171
|
+
const cwdB = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-keyed-cwd-b-'));
|
|
172
|
+
tmpDirs.push(cwdB);
|
|
173
|
+
|
|
174
|
+
const projDirA = path.join(projectsDirA, 'proj-a');
|
|
175
|
+
fs.mkdirSync(projDirA, { recursive: true });
|
|
176
|
+
fs.writeFileSync(path.join(projDirA, 'session1.jsonl'), `${JSON.stringify({ cwd: cwdA })}\n`);
|
|
177
|
+
fs.mkdirSync(resolvePrdWriteDir(cwdA), { recursive: true });
|
|
178
|
+
|
|
179
|
+
const projDirB = path.join(projectsDirB, 'proj-b');
|
|
180
|
+
fs.mkdirSync(projDirB, { recursive: true });
|
|
181
|
+
fs.writeFileSync(path.join(projDirB, 'session1.jsonl'), `${JSON.stringify({ cwd: cwdB })}\n`);
|
|
182
|
+
fs.mkdirSync(resolvePrdWriteDir(cwdB), { recursive: true });
|
|
183
|
+
|
|
184
|
+
const dirsA = resolvePrdsDirs(90, { projectsDir: projectsDirA });
|
|
185
|
+
const dirsB = resolvePrdsDirs(90, { projectsDir: projectsDirB });
|
|
186
|
+
|
|
187
|
+
expect(dirsA).toContain(resolvePrdWriteDir(cwdA));
|
|
188
|
+
expect(dirsA).not.toContain(resolvePrdWriteDir(cwdB));
|
|
189
|
+
expect(dirsB).toContain(resolvePrdWriteDir(cwdB));
|
|
190
|
+
expect(dirsB).not.toContain(resolvePrdWriteDir(cwdA));
|
|
191
|
+
|
|
192
|
+
// Re-querying A after B was resolved must still reflect A's own state, not
|
|
193
|
+
// B's — proof the two opts objects never shared one cache key.
|
|
194
|
+
expect(resolvePrdsDirs(90, { projectsDir: projectsDirA })).toEqual(dirsA);
|
|
195
|
+
});
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
'use strict';
|
|
23
23
|
|
|
24
|
-
import { test, expect, afterEach } from 'vitest';
|
|
24
|
+
import { test, expect, afterEach, vi } from 'vitest';
|
|
25
25
|
const fs = require('node:fs');
|
|
26
26
|
const fsp = require('node:fs/promises');
|
|
27
27
|
const os = require('node:os');
|
|
@@ -150,3 +150,52 @@ test('scheduler.cjs exports candidateArchivedPrdsDirs alongside candidatePrdsDir
|
|
|
150
150
|
expect(Array.isArray(candidatePrdsDirs())).toBe(true);
|
|
151
151
|
expect(Array.isArray(candidateArchivedPrdsDirs())).toBe(true);
|
|
152
152
|
});
|
|
153
|
+
|
|
154
|
+
test('resolveArchivedPrdsDirs: a prds-archived/ dir created inside an ALREADY-EXISTING Epic (the archiveCompletedPrd shape — does not bump the Epics root mtime) is bounded-stale, not stale forever', async () => {
|
|
155
|
+
// archiveCompletedPrd (scheduler.cjs) creates `epics/<id>/prds-archived/`
|
|
156
|
+
// as a new entry INSIDE an already-existing `epics/<id>/` dir — unlike a
|
|
157
|
+
// brand-new Epic's `prds/` (created in the same mkdirSync call as
|
|
158
|
+
// `epics/<id>/` itself), this does NOT bump the Epics root's own mtime,
|
|
159
|
+
// so the mtime-keyed freshness key alone would never invalidate. The
|
|
160
|
+
// short TTL backstop (same idiom as activeSessions.cjs's cwdScanCache)
|
|
161
|
+
// bounds that blind spot instead of leaving it stale indefinitely.
|
|
162
|
+
//
|
|
163
|
+
// Why this test is sensitive to the process environment: the code under test
|
|
164
|
+
// keys its cache on REAL directory mtimes and expires it via Date.now(); the
|
|
165
|
+
// fixtures are mkdtemp'd under os.tmpdir(), which a TMPDIR override moves.
|
|
166
|
+
// Installing fake timers BEFORE the async fs fixture setup let the fake timer
|
|
167
|
+
// queue interleave with real fs completions. Build every fixture on the real
|
|
168
|
+
// clock first, then fake ONLY Date — the sole clock the TTL reads.
|
|
169
|
+
const projectsDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-prdloc-blindspot-projects-'));
|
|
170
|
+
tmpDirs.push(projectsDir);
|
|
171
|
+
const projectCwd = await mkEpicProject();
|
|
172
|
+
tmpDirs.push(projectCwd);
|
|
173
|
+
|
|
174
|
+
const epicId = `blindspot-epic-${process.pid}`;
|
|
175
|
+
const epicPrdsDir = resolveEpicPrdWriteDir(projectCwd, epicId);
|
|
176
|
+
fs.mkdirSync(epicPrdsDir, { recursive: true }); // Epic already exists, well before archiving.
|
|
177
|
+
|
|
178
|
+
const projDir = path.join(projectsDir, 'blindspot-project');
|
|
179
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
180
|
+
fs.writeFileSync(path.join(projDir, 'session1.jsonl'), `${JSON.stringify({ cwd: projectCwd })}\n`);
|
|
181
|
+
|
|
182
|
+
vi.useFakeTimers({ toFake: ['Date'] });
|
|
183
|
+
try {
|
|
184
|
+
// Prime the cache — no prds-archived/ dir exists yet.
|
|
185
|
+
const before = resolveArchivedPrdsDirs(90, { projectsDir });
|
|
186
|
+
const epicArchiveDir = path.join(epicPrdsDir, '..', 'prds-archived');
|
|
187
|
+
expect(before).not.toContain(path.resolve(epicArchiveDir));
|
|
188
|
+
|
|
189
|
+
// The archiveCompletedPrd shape: a NEW prds-archived/ dir appears inside
|
|
190
|
+
// the ALREADY-EXISTING epic dir. This does not change the Epics root's
|
|
191
|
+
// own mtime.
|
|
192
|
+
fs.mkdirSync(epicArchiveDir, { recursive: true });
|
|
193
|
+
|
|
194
|
+
// Bounded staleness is acceptable — but it must actually be bounded.
|
|
195
|
+
vi.advanceTimersByTime(121_000); // TTL is 120 s — above the 60 s dispatch-loop interval
|
|
196
|
+
const after = resolveArchivedPrdsDirs(90, { projectsDir });
|
|
197
|
+
expect(after.map((d) => path.resolve(d))).toContain(path.resolve(epicArchiveDir));
|
|
198
|
+
} finally {
|
|
199
|
+
vi.useRealTimers();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prdSetDisposition.test.cjs — scheduler wave-disposition PRD:
|
|
3
|
+
* scheduler.remote.setPrdDisposition (backing the renderer's
|
|
4
|
+
* `schedule:set-prd-disposition` IPC action) is how a human changes an
|
|
5
|
+
* already-written PRD's disposition from the Scheduler UI — promoting an
|
|
6
|
+
* appended wave to its own head, or re-attaching a head behind another
|
|
7
|
+
* chain. Validates via prdDisposition.cjs's computeDispositionRewrite
|
|
8
|
+
* (cycle-safety, running/completed rows untouched) before delegating the
|
|
9
|
+
* actual write to the SAME remote.updatePrd every other PRD edit path uses.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors prdUpdateDependsOn.test.cjs's HOME-isolation fixture.
|
|
12
|
+
*
|
|
13
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/prdSetDisposition.test.cjs
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
import { test, expect, beforeAll, afterAll } from 'vitest';
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const os = require('node:os');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
|
|
23
|
+
let tmpHome;
|
|
24
|
+
let originalHome;
|
|
25
|
+
let scheduler;
|
|
26
|
+
let queueStore;
|
|
27
|
+
let config;
|
|
28
|
+
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
originalHome = process.env.HOME;
|
|
31
|
+
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-set-disposition-home-'));
|
|
32
|
+
process.env.HOME = tmpHome;
|
|
33
|
+
|
|
34
|
+
scheduler = require('../scheduler.cjs');
|
|
35
|
+
queueStore = require('../lib/queueStore.cjs');
|
|
36
|
+
config = require('../config.cjs');
|
|
37
|
+
|
|
38
|
+
if (!scheduler.PRDS_DIR.startsWith(tmpHome)) {
|
|
39
|
+
throw new Error(`refusing to run: PRDS_DIR (${scheduler.PRDS_DIR}) is not under the temp HOME (${tmpHome})`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterAll(() => {
|
|
44
|
+
process.env.HOME = originalHome;
|
|
45
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function registerActiveProject(cwd) {
|
|
49
|
+
const projectsDir = path.join(tmpHome, '.claude', 'projects');
|
|
50
|
+
const slugDir = path.join(projectsDir, `fake-project-slug-${path.basename(cwd)}`);
|
|
51
|
+
fs.mkdirSync(slugDir, { recursive: true });
|
|
52
|
+
fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function makeFixtureProject(prefix) {
|
|
56
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
57
|
+
registerActiveProject(cwd);
|
|
58
|
+
config.addAllowedRoot(cwd);
|
|
59
|
+
const opsRoot = path.join(cwd, 'session-manager-operations');
|
|
60
|
+
const prdsDir = path.join(opsRoot, 'scheduler', 'epics', 'test-epic-1', 'prds');
|
|
61
|
+
const stateDir = path.join(opsRoot, 'scheduler', 'state');
|
|
62
|
+
fs.mkdirSync(prdsDir, { recursive: true });
|
|
63
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
64
|
+
fs.writeFileSync(path.join(stateDir, 'queue.json'), JSON.stringify({ jobs: [] }, null, 2), 'utf8');
|
|
65
|
+
queueStore.bustCwdCache();
|
|
66
|
+
return { cwd, prdsDir, stateDir };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writePrd(prdsDir, filename, cwd, extraFrontmatter = '') {
|
|
70
|
+
fs.writeFileSync(
|
|
71
|
+
path.join(prdsDir, filename),
|
|
72
|
+
`---\ntitle: ${filename}\ncwd: ${cwd}\nestimateMinutes: 10\ncreatedVia: scheduler-api\n${extraFrontmatter}---\n\n# Goal\nDo the thing.\n`,
|
|
73
|
+
'utf8',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Seeds a queue.json row for `slug` with the given status — setPrdDisposition
|
|
78
|
+
* (like updatePrd) reads job status off the live queue, not the PRD file. */
|
|
79
|
+
function seedQueueJob(stateDir, slug, status) {
|
|
80
|
+
fs.writeFileSync(
|
|
81
|
+
path.join(stateDir, 'queue.json'),
|
|
82
|
+
JSON.stringify({ jobs: [{ slug, status, title: slug, cwd: '' }] }, null, 2),
|
|
83
|
+
'utf8',
|
|
84
|
+
);
|
|
85
|
+
queueStore.bustCwdCache();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
test('append sets the expected edges', async () => {
|
|
89
|
+
const { cwd, prdsDir } = makeFixtureProject('sm-set-disposition-append-');
|
|
90
|
+
try {
|
|
91
|
+
writePrd(prdsDir, '3-wave1-tail.md', cwd);
|
|
92
|
+
writePrd(prdsDir, '4-wave2-root.md', cwd);
|
|
93
|
+
|
|
94
|
+
const result = await scheduler.remote.setPrdDisposition({
|
|
95
|
+
slug: '4-wave2-root',
|
|
96
|
+
cwd,
|
|
97
|
+
disposition: 'append',
|
|
98
|
+
dependsOn: ['3-wave1-tail'],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(result.ok).toBe(true);
|
|
102
|
+
const written = fs.readFileSync(path.join(prdsDir, '4-wave2-root.md'), 'utf8');
|
|
103
|
+
expect(written).toMatch(/^dependsOn: \[3-wave1-tail\]$/m);
|
|
104
|
+
expect(written).toMatch(/^disposition: append$/m);
|
|
105
|
+
} finally {
|
|
106
|
+
fs.rmSync(cwd, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('new-head sets none', async () => {
|
|
111
|
+
const { cwd, prdsDir } = makeFixtureProject('sm-set-disposition-new-head-');
|
|
112
|
+
try {
|
|
113
|
+
writePrd(prdsDir, '3-wave1-tail.md', cwd);
|
|
114
|
+
writePrd(prdsDir, '4-wave2-root.md', cwd, 'dependsOn: [wave1-tail]\ndisposition: append\n');
|
|
115
|
+
|
|
116
|
+
const result = await scheduler.remote.setPrdDisposition({
|
|
117
|
+
slug: '4-wave2-root',
|
|
118
|
+
cwd,
|
|
119
|
+
disposition: 'new-head',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(result.ok).toBe(true);
|
|
123
|
+
const written = fs.readFileSync(path.join(prdsDir, '4-wave2-root.md'), 'utf8');
|
|
124
|
+
expect(written).not.toContain('dependsOn');
|
|
125
|
+
expect(written).toMatch(/^disposition: new-head$/m);
|
|
126
|
+
} finally {
|
|
127
|
+
fs.rmSync(cwd, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('promoting a wave to a head removes exactly its root edges', async () => {
|
|
132
|
+
const { cwd, prdsDir } = makeFixtureProject('sm-set-disposition-promote-');
|
|
133
|
+
try {
|
|
134
|
+
writePrd(prdsDir, '3-wave1-tail.md', cwd);
|
|
135
|
+
writePrd(prdsDir, '5-other-head.md', cwd);
|
|
136
|
+
writePrd(prdsDir, '4-wave2-root.md', cwd, 'dependsOn: [wave1-tail]\ndisposition: append\n');
|
|
137
|
+
|
|
138
|
+
const result = await scheduler.remote.setPrdDisposition({
|
|
139
|
+
slug: '4-wave2-root',
|
|
140
|
+
cwd,
|
|
141
|
+
disposition: 'new-head',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(result.ok).toBe(true);
|
|
145
|
+
const promoted = fs.readFileSync(path.join(prdsDir, '4-wave2-root.md'), 'utf8');
|
|
146
|
+
expect(promoted).not.toContain('dependsOn');
|
|
147
|
+
// The unrelated sibling head is untouched by this call.
|
|
148
|
+
const other = fs.readFileSync(path.join(prdsDir, '5-other-head.md'), 'utf8');
|
|
149
|
+
expect(other).not.toContain('dependsOn');
|
|
150
|
+
} finally {
|
|
151
|
+
fs.rmSync(cwd, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('a change that would create a cycle is rejected, and nothing is written', async () => {
|
|
156
|
+
const { cwd, prdsDir } = makeFixtureProject('sm-set-disposition-cycle-');
|
|
157
|
+
try {
|
|
158
|
+
writePrd(prdsDir, '3-a.md', cwd, 'dependsOn: [b]\n');
|
|
159
|
+
writePrd(prdsDir, '4-b.md', cwd);
|
|
160
|
+
const before = fs.readFileSync(path.join(prdsDir, '4-b.md'), 'utf8');
|
|
161
|
+
|
|
162
|
+
const result = await scheduler.remote.setPrdDisposition({
|
|
163
|
+
slug: '4-b',
|
|
164
|
+
cwd,
|
|
165
|
+
disposition: 'append',
|
|
166
|
+
dependsOn: ['3-a'],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(result.ok).toBe(false);
|
|
170
|
+
expect(result.error).toMatch(/cycle/i);
|
|
171
|
+
const after = fs.readFileSync(path.join(prdsDir, '4-b.md'), 'utf8');
|
|
172
|
+
expect(after).toBe(before);
|
|
173
|
+
} finally {
|
|
174
|
+
fs.rmSync(cwd, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('running rows are untouched', async () => {
|
|
179
|
+
const { cwd, prdsDir, stateDir } = makeFixtureProject('sm-set-disposition-running-');
|
|
180
|
+
try {
|
|
181
|
+
writePrd(prdsDir, '3-wave1-tail.md', cwd);
|
|
182
|
+
writePrd(prdsDir, '4-wave2-root.md', cwd, 'dependsOn: [wave1-tail]\ndisposition: append\n');
|
|
183
|
+
seedQueueJob(stateDir, '4-wave2-root', 'running');
|
|
184
|
+
const before = fs.readFileSync(path.join(prdsDir, '4-wave2-root.md'), 'utf8');
|
|
185
|
+
|
|
186
|
+
const result = await scheduler.remote.setPrdDisposition({
|
|
187
|
+
slug: '4-wave2-root',
|
|
188
|
+
cwd,
|
|
189
|
+
disposition: 'new-head',
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(result.ok).toBe(false);
|
|
193
|
+
expect(result.error).toMatch(/running/i);
|
|
194
|
+
const after = fs.readFileSync(path.join(prdsDir, '4-wave2-root.md'), 'utf8');
|
|
195
|
+
expect(after).toBe(before);
|
|
196
|
+
} finally {
|
|
197
|
+
fs.rmSync(cwd, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('completed rows are untouched', async () => {
|
|
202
|
+
const { cwd, prdsDir, stateDir } = makeFixtureProject('sm-set-disposition-completed-');
|
|
203
|
+
try {
|
|
204
|
+
writePrd(prdsDir, '3-wave1-tail.md', cwd);
|
|
205
|
+
writePrd(prdsDir, '4-wave2-root.md', cwd, 'dependsOn: [wave1-tail]\ndisposition: append\n');
|
|
206
|
+
seedQueueJob(stateDir, '4-wave2-root', 'completed');
|
|
207
|
+
const before = fs.readFileSync(path.join(prdsDir, '4-wave2-root.md'), 'utf8');
|
|
208
|
+
|
|
209
|
+
const result = await scheduler.remote.setPrdDisposition({
|
|
210
|
+
slug: '4-wave2-root',
|
|
211
|
+
cwd,
|
|
212
|
+
disposition: 'new-head',
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(result.ok).toBe(false);
|
|
216
|
+
expect(result.error).toMatch(/completed/i);
|
|
217
|
+
const after = fs.readFileSync(path.join(prdsDir, '4-wave2-root.md'), 'utf8');
|
|
218
|
+
expect(after).toBe(before);
|
|
219
|
+
} finally {
|
|
220
|
+
fs.rmSync(cwd, { recursive: true, force: true });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* proc-role-env.test.cjs — SM_PROC_ROLE attribution env + scripts/sm-ps.cjs.
|
|
3
|
+
*
|
|
4
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/proc-role-env.test.cjs
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
import { test, expect, describe, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const os = require('node:os');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
const { spawn, spawnSync } = require('node:child_process');
|
|
14
|
+
|
|
15
|
+
const { cleanChildEnv, withProcRole, inferProcRole } = require('../lib/cleanEnv.cjs');
|
|
16
|
+
const { runClaudeP } = require('../lib/runClaudeP.cjs');
|
|
17
|
+
const { runDocEdit } = require('../docEdit.cjs');
|
|
18
|
+
|
|
19
|
+
const SM_PS = path.join(__dirname, '..', '..', '..', 'scripts', 'sm-ps.cjs');
|
|
20
|
+
|
|
21
|
+
let tmp;
|
|
22
|
+
let savedBin;
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-proc-role-'));
|
|
25
|
+
savedBin = process.env.SM_CLAUDE_BIN;
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
if (savedBin === undefined) delete process.env.SM_CLAUDE_BIN; else process.env.SM_CLAUDE_BIN = savedBin;
|
|
29
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/** Stub `claude` that prints {"after":"<SM_PROC_ROLE>"} — valid for both runClaudeP and docEdit. */
|
|
33
|
+
function writeStub() {
|
|
34
|
+
const bin = path.join(tmp, 'claude-stub.sh');
|
|
35
|
+
fs.writeFileSync(bin, '#!/bin/sh\nprintf \'{"after":"%s"}\' "$SM_PROC_ROLE"\n', { mode: 0o755 });
|
|
36
|
+
return bin;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('cleanChildEnv role stamping', () => {
|
|
40
|
+
test('explicit role wins; inference from existing attribution vars; default aux', () => {
|
|
41
|
+
expect(cleanChildEnv({ SM_PROC_ROLE: 'shell' }).SM_PROC_ROLE).toBe('shell');
|
|
42
|
+
expect(inferProcRole({ SM_SCHEDULER_JOB_SLUG: 's' })).toBe('job');
|
|
43
|
+
expect(inferProcRole({ SM_CHAT_SESSION_ID: 'c' })).toBe('chat');
|
|
44
|
+
expect(inferProcRole({ SM_CHAT_SESSION_ID: 'c', SESSION_MANAGER_TAB_ID: 'c' })).toBe('shell');
|
|
45
|
+
expect(cleanChildEnv().SM_PROC_ROLE).toBe('aux');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('is additive: existing attribution vars survive and SM_PROC_ROOT is untouched', () => {
|
|
49
|
+
const env = cleanChildEnv({ SM_SCHEDULER_JOB_SLUG: 'slug-1', SM_CHAT_SESSION_ID: 'chat-1', SM_PROJECT_ROOT: '/p' });
|
|
50
|
+
expect(env).toMatchObject({ SM_PROC_ROLE: 'job', SM_SCHEDULER_JOB_SLUG: 'slug-1', SM_CHAT_SESSION_ID: 'chat-1', SM_PROJECT_ROOT: '/p' });
|
|
51
|
+
expect('SM_PROC_ROOT' in env).toBe(process.env.SM_PROC_ROOT !== undefined);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('withProcRole returns a copy and does not mutate its input', () => {
|
|
55
|
+
const base = { A: '1' };
|
|
56
|
+
expect(withProcRole(base, 'aux')).toEqual({ A: '1', SM_PROC_ROLE: 'aux' });
|
|
57
|
+
expect(base).toEqual({ A: '1' });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('real spawn paths carry SM_PROC_ROLE', () => {
|
|
62
|
+
test('runClaudeP (bypasses cleanChildEnv)', async () => {
|
|
63
|
+
process.env.SM_CLAUDE_BIN = writeStub();
|
|
64
|
+
const r = await runClaudeP('hi');
|
|
65
|
+
expect(r.ok).toBe(true);
|
|
66
|
+
expect(JSON.parse(r.out).after).toBe('aux');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('docEdit runClaude (bypasses cleanChildEnv)', async () => {
|
|
70
|
+
process.env.SM_CLAUDE_BIN = writeStub();
|
|
71
|
+
const r = await runDocEdit({ path: path.join(os.homedir(), 'x.md'), before: 'a', instruction: 'b', documentText: '' });
|
|
72
|
+
expect(r).toEqual({ ok: true, after: 'aux' });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('cleanChildEnv-built env reaches a spawned child', async () => {
|
|
76
|
+
const out = await new Promise((resolve) => {
|
|
77
|
+
const c = spawn('/bin/sh', ['-c', 'printf %s "$SM_PROC_ROLE"'], { env: cleanChildEnv({ SM_SCHEDULER_JOB_SLUG: 'x' }) });
|
|
78
|
+
let s = '';
|
|
79
|
+
c.stdout.on('data', (d) => { s += d; });
|
|
80
|
+
c.on('close', () => resolve(s));
|
|
81
|
+
});
|
|
82
|
+
expect(out).toBe('job');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('scripts/sm-ps.cjs', () => {
|
|
87
|
+
test('--json lists a tagged stub child (and its descendant) with role, slug, project root', async () => {
|
|
88
|
+
const env = cleanChildEnv({ SM_SCHEDULER_JOB_SLUG: 'fixture-slug', SM_PROJECT_ROOT: '/fixture/root' });
|
|
89
|
+
// outer sh forks an inner sleep, so we also assert descendants are included
|
|
90
|
+
const child = spawn('/bin/sh', ['-c', 'sleep 30 & wait'], { env, stdio: 'ignore' });
|
|
91
|
+
try {
|
|
92
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
93
|
+
const res = spawnSync(process.execPath, [SM_PS, '--json'], { encoding: 'utf8', timeout: 20000 });
|
|
94
|
+
expect(res.status).toBe(0);
|
|
95
|
+
const { processes } = JSON.parse(res.stdout);
|
|
96
|
+
const hit = processes.find((p) => p.pid === child.pid);
|
|
97
|
+
expect(hit).toMatchObject({ role: 'job', slug: 'fixture-slug', projectRoot: '/fixture/root', attributed: true });
|
|
98
|
+
expect(processes.some((p) => p.ppid === child.pid)).toBe(true);
|
|
99
|
+
} finally {
|
|
100
|
+
try { process.kill(-child.pid); } catch { /* */ }
|
|
101
|
+
child.kill('SIGKILL');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('exits 0 with an empty result set when nothing is running (empty proc root)', () => {
|
|
106
|
+
const root = path.join(tmp, 'proc');
|
|
107
|
+
fs.mkdirSync(root);
|
|
108
|
+
const res = spawnSync(process.execPath, [SM_PS, '--json', '--proc-root', root], { encoding: 'utf8' });
|
|
109
|
+
expect(res.status).toBe(0);
|
|
110
|
+
expect(JSON.parse(res.stdout)).toEqual({ processes: [] });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('degrades without attribution on unreadable environ / mid-scan exit, never crashes', () => {
|
|
114
|
+
const root = path.join(tmp, 'proc');
|
|
115
|
+
fs.mkdirSync(path.join(root, '100'), { recursive: true }); // comm-named ours, environ absent
|
|
116
|
+
fs.writeFileSync(path.join(root, '100', 'stat'), '100 (sm-watchdog) S 1 0 0');
|
|
117
|
+
fs.writeFileSync(path.join(root, '100', 'cmdline'), 'node\0x.js\0');
|
|
118
|
+
fs.mkdirSync(path.join(root, '101')); // vanished: no stat at all
|
|
119
|
+
const res = spawnSync(process.execPath, [SM_PS, '--json', '--proc-root', root], { encoding: 'utf8' });
|
|
120
|
+
expect(res.status).toBe(0);
|
|
121
|
+
const { processes } = JSON.parse(res.stdout);
|
|
122
|
+
expect(processes).toHaveLength(1);
|
|
123
|
+
expect(processes[0]).toMatchObject({ pid: 100, comm: 'sm-watchdog', role: null, attributed: false });
|
|
124
|
+
});
|
|
125
|
+
});
|