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
|
@@ -11,10 +11,7 @@
|
|
|
11
11
|
* feedback_list_projects() -> GET /admin/feedback/targets
|
|
12
12
|
* feedback_open_session({ ... }) -> POST /admin/feedback/open-session
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* project_home_validate_summary({ cwd?, summary }) -> POST /admin/project-home/validate-summary
|
|
16
|
-
* project_home_render({ cwd?, summary, picks }) -> POST /admin/project-home/render
|
|
17
|
-
* project_home_status({ cwd? }) -> GET /admin/project-home/status
|
|
14
|
+
* project_home_write({ cwd?, html }) -> POST /admin/project-home/write
|
|
18
15
|
*
|
|
19
16
|
* This is a separate process from the Electron app — it only ever reaches
|
|
20
17
|
* it over the token-authed loopback HTTP API in admin-api.json, never by
|
|
@@ -24,7 +21,9 @@
|
|
|
24
21
|
|
|
25
22
|
'use strict';
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
// Name this process first (pn-03): otherwise it shows as a bare `node`.
|
|
25
|
+
const { PROC_NAMES, setProcessTitle } = require('../src/main/lib/smProcNames.cjs');
|
|
26
|
+
setProcessTitle(PROC_NAMES.mcpServer);
|
|
28
27
|
const path = require('node:path');
|
|
29
28
|
const fsp = require('node:fs/promises');
|
|
30
29
|
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
@@ -44,7 +43,7 @@ function descriptionFor(toolName) {
|
|
|
44
43
|
return composeDescription(entry);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const
|
|
46
|
+
const { adminTokenPath } = require('../src/main/lib/schedulerPaths.cjs');
|
|
48
47
|
|
|
49
48
|
// Defined once so every failure path points to the same next call, verbatim
|
|
50
49
|
// — never paste this sentence at each return site (PRD: session_manager_help).
|
|
@@ -71,7 +70,7 @@ function resolveCwdArg(args) {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
async function readAdminConfig() {
|
|
74
|
-
const raw = await fsp.readFile(
|
|
73
|
+
const raw = await fsp.readFile(adminTokenPath(), 'utf8');
|
|
75
74
|
const parsed = JSON.parse(raw);
|
|
76
75
|
if (!parsed || typeof parsed.port !== 'number' || typeof parsed.token !== 'string') {
|
|
77
76
|
throw new Error(NOT_RUNNING_ERROR);
|
|
@@ -140,6 +139,16 @@ const TOOLS = [
|
|
|
140
139
|
required: ['slug'],
|
|
141
140
|
},
|
|
142
141
|
},
|
|
142
|
+
{
|
|
143
|
+
name: 'scheduler_pause',
|
|
144
|
+
description: descriptionFor('scheduler_pause'),
|
|
145
|
+
inputSchema: { type: 'object', properties: {} },
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'scheduler_resume',
|
|
149
|
+
description: descriptionFor('scheduler_resume'),
|
|
150
|
+
inputSchema: { type: 'object', properties: {} },
|
|
151
|
+
},
|
|
143
152
|
{
|
|
144
153
|
name: 'scheduler_list_jobs',
|
|
145
154
|
description: descriptionFor('scheduler_list_jobs'),
|
|
@@ -164,6 +173,11 @@ const TOOLS = [
|
|
|
164
173
|
slug: { type: 'string', description: 'Optional kebab-case slug; derived from title if omitted' },
|
|
165
174
|
parallelGroup: { type: 'number', description: 'DEPRECATED and ignored (PRD 832): numbers are strictly unique per project; use dependsOn for ordering' },
|
|
166
175
|
dependsOn: { type: 'array', items: { type: 'string' }, description: 'Optional: PRD slugs that must complete before this one becomes eligible (replaces the retired shared-NN-parallel convention)' },
|
|
176
|
+
disposition: {
|
|
177
|
+
type: 'string',
|
|
178
|
+
enum: ['append', 'new-head'],
|
|
179
|
+
description: 'Required when this PRD would land as a new root (no dependsOn given) in an Epic that already has incomplete PRDs: "append" depends this wave on the existing chain\'s current terminal PRD(s); "new-head" leaves it independent, eligible to run in parallel. Omit when the Epic has no incomplete PRDs yet, or when dependsOn already places this PRD explicitly.',
|
|
180
|
+
},
|
|
167
181
|
sourcePromptId: { type: 'string', description: "Effectively required: an EXISTING, already-human-approved Epic's promptSessionId (the id shown in the Epics list / active-index.json sessions key) to join — NOT a PromptTicket.id (those are distinct fields). The server never mints a new Epic; if this is omitted and no Epic can be resolved (via the SM_CHAT_SESSION_ID fallback below), the write is refused with an error telling the caller to create/approve an Epic first." },
|
|
168
182
|
sourceTabId: { type: 'string', description: 'Optional: tab id (claudeSessionId) this PRD was queued from, so the scheduler can route a completion status prompt back to it' },
|
|
169
183
|
tag: {
|
|
@@ -231,6 +245,8 @@ const TOOLS = [
|
|
|
231
245
|
tag: { type: 'string', enum: PRD_WORK_TYPES },
|
|
232
246
|
agentType: { type: 'string', description: 'WHO executes this PRD (persona name) — patchable after creation too, see scheduler_create_prd' },
|
|
233
247
|
quietMachine: { type: 'boolean', description: 'Optional: the exclusive machine-wide quiet lease flag (see scheduler_create_prd) — patchable after creation too' },
|
|
248
|
+
dependsOn: { type: 'array', items: { type: 'string' }, description: 'Optional: replaces the dependsOn list; an explicit empty array CLEARS it — see scheduler_create_prd' },
|
|
249
|
+
disposition: { type: 'string', enum: ['append', 'new-head'], description: 'Optional: patches the recorded wave-authoring decision — see scheduler_create_prd' },
|
|
234
250
|
},
|
|
235
251
|
},
|
|
236
252
|
body: { type: 'string', description: 'Optional: full replacement body (everything after the frontmatter)' },
|
|
@@ -340,48 +356,15 @@ const TOOLS = [
|
|
|
340
356
|
},
|
|
341
357
|
},
|
|
342
358
|
{
|
|
343
|
-
name: '
|
|
344
|
-
description: descriptionFor('
|
|
359
|
+
name: 'project_home_write',
|
|
360
|
+
description: descriptionFor('project_home_write'),
|
|
345
361
|
inputSchema: {
|
|
346
362
|
type: 'object',
|
|
347
363
|
properties: {
|
|
348
364
|
cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root (SM_PROJECT_ROOT or process.cwd()) when omitted." },
|
|
365
|
+
html: { type: 'string', description: 'The complete self-contained HTML document (<= 1MB; no <script src>, <link href=http*>, @import, or url(http*)).' },
|
|
349
366
|
},
|
|
350
|
-
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
name: 'project_home_validate_summary',
|
|
354
|
-
description: descriptionFor('project_home_validate_summary'),
|
|
355
|
-
inputSchema: {
|
|
356
|
-
type: 'object',
|
|
357
|
-
properties: {
|
|
358
|
-
cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
|
|
359
|
-
summary: { type: 'object', description: 'ProjectPageSummary object matching the summarySchema from project_home_get_contract' },
|
|
360
|
-
},
|
|
361
|
-
required: ['summary'],
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
name: 'project_home_render',
|
|
366
|
-
description: descriptionFor('project_home_render'),
|
|
367
|
-
inputSchema: {
|
|
368
|
-
type: 'object',
|
|
369
|
-
properties: {
|
|
370
|
-
cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
|
|
371
|
-
summary: { type: 'object', description: 'ProjectPageSummary object matching the summarySchema from project_home_get_contract' },
|
|
372
|
-
picks: { type: 'object', description: 'ProjectPagePicks object (lensId -> slotId -> variantId) matching the picksSchema from project_home_get_contract' },
|
|
373
|
-
},
|
|
374
|
-
required: ['summary', 'picks'],
|
|
375
|
-
},
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
name: 'project_home_status',
|
|
379
|
-
description: descriptionFor('project_home_status'),
|
|
380
|
-
inputSchema: {
|
|
381
|
-
type: 'object',
|
|
382
|
-
properties: {
|
|
383
|
-
cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
|
|
384
|
-
},
|
|
367
|
+
required: ['html'],
|
|
385
368
|
},
|
|
386
369
|
},
|
|
387
370
|
{
|
|
@@ -421,6 +404,10 @@ async function handleCallTool(request) {
|
|
|
421
404
|
const result = await adminRequest('POST', '/admin/scheduler/reset-job', { slug, force, cwd });
|
|
422
405
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
423
406
|
}
|
|
407
|
+
if (name === 'scheduler_pause' || name === 'scheduler_resume') {
|
|
408
|
+
const result = await adminRequest('POST', `/admin/scheduler/${name === 'scheduler_pause' ? 'pause' : 'resume'}`, {});
|
|
409
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
410
|
+
}
|
|
424
411
|
if (name === 'scheduler_list_jobs') {
|
|
425
412
|
const result = await adminRequest('GET', '/admin/scheduler/jobs');
|
|
426
413
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
@@ -568,35 +555,12 @@ async function handleCallTool(request) {
|
|
|
568
555
|
const result = await adminRequest('POST', '/admin/chat/send-prompt', { tabId, prompt });
|
|
569
556
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
570
557
|
}
|
|
571
|
-
if (name === '
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const result = await adminRequest('GET', `/admin/project-home/contract?${qs.toString()}`);
|
|
575
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
576
|
-
}
|
|
577
|
-
if (name === 'project_home_validate_summary') {
|
|
578
|
-
if (!args || typeof args.summary !== 'object' || args.summary === null) {
|
|
579
|
-
return errorResult('missing required argument: summary');
|
|
580
|
-
}
|
|
581
|
-
const cwd = resolveCwdArg(args);
|
|
582
|
-
const result = await adminRequest('POST', '/admin/project-home/validate-summary', { cwd, summary: args.summary });
|
|
583
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
584
|
-
}
|
|
585
|
-
if (name === 'project_home_render') {
|
|
586
|
-
if (!args || typeof args.summary !== 'object' || args.summary === null) {
|
|
587
|
-
return errorResult('missing required argument: summary');
|
|
588
|
-
}
|
|
589
|
-
if (typeof args.picks !== 'object' || args.picks === null) {
|
|
590
|
-
return errorResult('missing required argument: picks');
|
|
558
|
+
if (name === 'project_home_write') {
|
|
559
|
+
if (!args || typeof args.html !== 'string' || args.html.length === 0) {
|
|
560
|
+
return errorResult('missing required argument: html');
|
|
591
561
|
}
|
|
592
562
|
const cwd = resolveCwdArg(args);
|
|
593
|
-
const result = await adminRequest('POST', '/admin/project-home/
|
|
594
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
595
|
-
}
|
|
596
|
-
if (name === 'project_home_status') {
|
|
597
|
-
const cwd = resolveCwdArg(args);
|
|
598
|
-
const qs = new URLSearchParams({ cwd });
|
|
599
|
-
const result = await adminRequest('GET', `/admin/project-home/status?${qs.toString()}`);
|
|
563
|
+
const result = await adminRequest('POST', '/admin/project-home/write', { cwd, html: args.html });
|
|
600
564
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
601
565
|
}
|
|
602
566
|
if (name === 'session_manager_help') {
|
|
@@ -17,7 +17,7 @@ const {
|
|
|
17
17
|
FALLBACK_MODEL,
|
|
18
18
|
resolveEpicModel,
|
|
19
19
|
findAgentTypeByClaudeSessionId,
|
|
20
|
-
|
|
20
|
+
readOverlayAwarePersonaModel,
|
|
21
21
|
resolvePrdPersonaForSpawn,
|
|
22
22
|
} = require('../lib/agentModelResolve.cjs');
|
|
23
23
|
|
|
@@ -103,37 +103,39 @@ test('findAgentTypeByClaudeSessionId returns null for no match', async () => {
|
|
|
103
103
|
expect(findAgentTypeByClaudeSessionId(cwd, 'missing')).toBeNull();
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
test('
|
|
106
|
+
test('readOverlayAwarePersonaModel returns null for a persona with no model field', async () => {
|
|
107
|
+
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
107
108
|
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
108
109
|
writePersona(globalDir, 'no-model-persona', []);
|
|
109
|
-
expect(
|
|
110
|
+
expect(readOverlayAwarePersonaModel('no-model-persona', { cwd, globalDir, validatePath: noopValidatePath })).toBeNull();
|
|
110
111
|
});
|
|
111
112
|
|
|
112
|
-
test('
|
|
113
|
+
test('readOverlayAwarePersonaModel returns null (never throws) for a path-traversal agentType rejected by validatePath', async () => {
|
|
114
|
+
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
113
115
|
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
114
116
|
const realValidatePath = (p) => {
|
|
115
117
|
const real = path.resolve(p);
|
|
116
118
|
if (!real.startsWith(path.resolve(globalDir) + path.sep)) throw new Error('outside allowed boundaries');
|
|
117
119
|
return real;
|
|
118
120
|
};
|
|
119
|
-
expect(
|
|
121
|
+
expect(readOverlayAwarePersonaModel('../../../etc/passwd', { cwd, globalDir, validatePath: realValidatePath })).toBeNull();
|
|
120
122
|
});
|
|
121
123
|
|
|
122
124
|
// READ side of the agentType FK: a persona that was valid when the Epic was
|
|
123
125
|
// created but got deleted afterward (rename/deletion in Agent Library) must
|
|
124
126
|
// still resolve to null, not throw — the WRITE side (epicMint.cjs's
|
|
125
127
|
// ensureEpic) is what refuses a bad reference at creation time.
|
|
126
|
-
test('
|
|
128
|
+
test('readOverlayAwarePersonaModel returns null (does not throw) for a persona deleted after the Epic was created, and logs it once via opsErrorLog', async () => {
|
|
127
129
|
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
128
130
|
const globalDir = await mkTmpDir('sm-agentmodel-agents-'); // never written to — simulates a deleted persona
|
|
129
131
|
|
|
130
|
-
expect(() =>
|
|
131
|
-
expect(
|
|
132
|
+
expect(() => readOverlayAwarePersonaModel('deleted-persona', { globalDir, validatePath: noopValidatePath, cwd })).not.toThrow();
|
|
133
|
+
expect(readOverlayAwarePersonaModel('deleted-persona', { globalDir, validatePath: noopValidatePath, cwd })).toBeNull();
|
|
132
134
|
|
|
133
135
|
const { todayFile } = require('../lib/opsErrorLog.cjs');
|
|
134
136
|
const lines = fs.readFileSync(todayFile(cwd), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
135
137
|
const matches = lines.filter((l) => l.message.includes('deleted-persona'));
|
|
136
|
-
// Logged once despite two
|
|
138
|
+
// Logged once despite two readOverlayAwarePersonaModel calls above — dedup key is (cwd, agentType).
|
|
137
139
|
expect(matches).toHaveLength(1);
|
|
138
140
|
expect(matches[0].level).toBe('warn');
|
|
139
141
|
});
|
|
@@ -190,6 +192,95 @@ test('resolvePrdPersonaForSpawn returns the fallback (no persona applied) when a
|
|
|
190
192
|
expect(result).toEqual({ model: FALLBACK_MODEL, systemPrompt: null, personaPath: null });
|
|
191
193
|
});
|
|
192
194
|
|
|
195
|
+
// Overlay-aware resolution (unify-epic-model-resolution): resolveEpicModel
|
|
196
|
+
// must use the SAME project-overlay-then-global precedence
|
|
197
|
+
// resolvePrdPersonaForSpawn already uses — so a project's
|
|
198
|
+
// `.claude/agents/<name>.md` wins for a launched Chat/Terminal session
|
|
199
|
+
// exactly like it already does for a scheduled PRD.
|
|
200
|
+
|
|
201
|
+
test('resolveEpicModel prefers a project-overlay persona over the global one with the same name', async () => {
|
|
202
|
+
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
203
|
+
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
204
|
+
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-overlay', agentType: 'shared-persona' } });
|
|
205
|
+
writePersona(globalDir, 'shared-persona', ['model: opus']);
|
|
206
|
+
const projectAgentsDir = path.join(cwd, '.claude', 'agents');
|
|
207
|
+
writePersona(projectAgentsDir, 'shared-persona', ['model: haiku']);
|
|
208
|
+
|
|
209
|
+
const model = resolveEpicModel({
|
|
210
|
+
cwd,
|
|
211
|
+
claudeSessionId: 'sess-overlay',
|
|
212
|
+
deps: { globalDir, validatePath: noopValidatePath },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(model).toBe('haiku');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('resolveEpicModel falls through to the global persona when no project overlay exists', async () => {
|
|
219
|
+
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
220
|
+
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
221
|
+
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-global-only', agentType: 'global-only-persona' } });
|
|
222
|
+
writePersona(globalDir, 'global-only-persona', ['model: opus']);
|
|
223
|
+
|
|
224
|
+
const model = resolveEpicModel({
|
|
225
|
+
cwd,
|
|
226
|
+
claudeSessionId: 'sess-global-only',
|
|
227
|
+
deps: { globalDir, validatePath: noopValidatePath },
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(model).toBe('opus');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('resolveEpicModel falls back to FALLBACK_MODEL (never throws) for a dangling agentType with no persona file anywhere, and logs it once', async () => {
|
|
234
|
+
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
235
|
+
const globalDir = await mkTmpDir('sm-agentmodel-agents-'); // never written to
|
|
236
|
+
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-dangling', agentType: 'ghost-persona' } });
|
|
237
|
+
|
|
238
|
+
const call = () => resolveEpicModel({
|
|
239
|
+
cwd,
|
|
240
|
+
claudeSessionId: 'sess-dangling',
|
|
241
|
+
deps: { globalDir, validatePath: noopValidatePath },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(call).not.toThrow();
|
|
245
|
+
expect(call()).toBe(FALLBACK_MODEL);
|
|
246
|
+
call(); // second call must not log a second line (dedup per (cwd, agentType))
|
|
247
|
+
|
|
248
|
+
const { todayFile } = require('../lib/opsErrorLog.cjs');
|
|
249
|
+
const lines = fs.readFileSync(todayFile(cwd), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
250
|
+
const matches = lines.filter((l) => l.message.includes('ghost-persona'));
|
|
251
|
+
expect(matches).toHaveLength(1);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Chat (chatRunner.cjs, in-process) and Terminal (EpicTerminalPane.tsx, via
|
|
255
|
+
// the agents:resolve-epic-model IPC added in this change) both call this
|
|
256
|
+
// exact function with the same { cwd, claudeSessionId } shape — so "the same
|
|
257
|
+
// Epic resolves identically in both views" is structural (one function, one
|
|
258
|
+
// call signature), not a coincidence of two implementations agreeing. This
|
|
259
|
+
// asserts the function is a pure, deterministic read of that Epic's overlay-
|
|
260
|
+
// resolved persona model, which is what makes that guarantee hold.
|
|
261
|
+
test('resolveEpicModel resolves identically for the same Epic across repeated calls (the guarantee Chat and Terminal both rely on)', async () => {
|
|
262
|
+
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
263
|
+
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
264
|
+
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-shared', agentType: 'shared-epic-persona' } });
|
|
265
|
+
writePersona(globalDir, 'shared-epic-persona', ['model: haiku']);
|
|
266
|
+
|
|
267
|
+
const callArgs = { cwd, claudeSessionId: 'sess-shared', deps: { globalDir, validatePath: noopValidatePath } };
|
|
268
|
+
const chatResult = resolveEpicModel(callArgs);
|
|
269
|
+
const terminalResult = resolveEpicModel(callArgs); // simulates the IPC-forwarded call Terminal makes
|
|
270
|
+
|
|
271
|
+
expect(chatResult).toBe('haiku');
|
|
272
|
+
expect(terminalResult).toBe(chatResult);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// --model must never be left unpinned (CLAUDE.md model-pinning rule) on any
|
|
276
|
+
// of the three launch paths. resolvePrdPersonaForSpawn's own miss-path tests
|
|
277
|
+
// above already cover the scheduler call site; this covers the shared
|
|
278
|
+
// Chat/Terminal resolver.
|
|
279
|
+
test('resolveEpicModel never returns an empty/falsy --model value, even on every miss path', async () => {
|
|
280
|
+
expect(resolveEpicModel({})).toBeTruthy();
|
|
281
|
+
expect(resolveEpicModel({ cwd: '/nonexistent-cwd-xyz', claudeSessionId: 'no-such-session' })).toBeTruthy();
|
|
282
|
+
});
|
|
283
|
+
|
|
193
284
|
test('resolvePrdPersonaForSpawn caps the persona body at 6000 characters with a truncation notice naming the persona path', async () => {
|
|
194
285
|
const longBody = 'x'.repeat(6500);
|
|
195
286
|
const result = await resolvePrdPersonaForSpawn({
|
|
@@ -14,8 +14,8 @@ function makeProject() {
|
|
|
14
14
|
config.addAllowedRoot(root);
|
|
15
15
|
fs.mkdirSync(path.join(root, 'session-manager-operations', 'project-pages', 'output'), { recursive: true });
|
|
16
16
|
fs.writeFileSync(
|
|
17
|
-
path.join(root, 'session-manager-operations', 'project-pages', '
|
|
18
|
-
'<html><title>Demo</title>
|
|
17
|
+
path.join(root, 'session-manager-operations', 'project-pages', 'home.html'),
|
|
18
|
+
'<html><title>Demo</title>Home</html>',
|
|
19
19
|
);
|
|
20
20
|
fs.writeFileSync(
|
|
21
21
|
path.join(root, 'session-manager-operations', 'project-pages', 'output', 'feature.html'),
|
|
@@ -29,7 +29,7 @@ test('prepareBundle seeds a root document and writes dist/index.html + manifest.
|
|
|
29
29
|
const root = makeProject();
|
|
30
30
|
const result = await bilkoHost.prepareBundle({ cwd: root, slug: 'demo-app' });
|
|
31
31
|
assert.equal(fs.existsSync(path.join(result.distPath, 'index.html')), true);
|
|
32
|
-
assert.equal(fs.readFileSync(path.join(result.distPath, 'index.html'), 'utf8'), '<html><title>Demo</title>
|
|
32
|
+
assert.equal(fs.readFileSync(path.join(result.distPath, 'index.html'), 'utf8'), '<html><title>Demo</title>Home</html>');
|
|
33
33
|
assert.equal(result.manifest.slug, 'demo-app');
|
|
34
34
|
assert.equal(result.manifest.bundle.fileCount, 1);
|
|
35
35
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -101,4 +101,22 @@ test('flush() with no pending timer still sends (no-arm case)', async () => {
|
|
|
101
101
|
expect(sent).toEqual(['state-1']);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test('a getPayload() that throws does not wedge the coalescer — the next schedule() still sends', async () => {
|
|
105
|
+
const sent = [];
|
|
106
|
+
const send = vi.fn((payload) => sent.push(payload));
|
|
107
|
+
const getPayload = vi.fn()
|
|
108
|
+
.mockRejectedValueOnce(new Error('boom'))
|
|
109
|
+
.mockResolvedValue('state-recovered');
|
|
110
|
+
const coalescer = createBroadcastCoalescer({ delayMs: 200, send, getPayload });
|
|
111
|
+
|
|
112
|
+
coalescer.schedule();
|
|
113
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
114
|
+
expect(sent).toEqual([]);
|
|
115
|
+
|
|
116
|
+
coalescer.schedule();
|
|
117
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
118
|
+
expect(sent).toEqual(['state-recovered']);
|
|
119
|
+
expect(getPayload).toHaveBeenCalledTimes(2);
|
|
120
|
+
});
|
|
121
|
+
|
|
104
122
|
console.log('broadcastCoalescer tests: PASS');
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
* path (via SM_CLAUDE_BIN pointing at a stub process) and asserts a terminal
|
|
14
14
|
* event is broadcast on cancel.
|
|
15
15
|
*
|
|
16
|
-
* Run: timeout
|
|
16
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/chat-cancel-terminal.test.cjs
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
21
|
delete process.env.SM_CHAT_CONCURRENCY;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
import { test, afterAll } from 'vitest';
|
|
24
24
|
const assert = require('node:assert/strict');
|
|
25
25
|
const fs = require('node:fs');
|
|
26
26
|
const os = require('node:os');
|
|
@@ -30,12 +30,9 @@ const path = require('node:path');
|
|
|
30
30
|
// mimics a long-running headless run so cancel() has something to SIGTERM.
|
|
31
31
|
// Shebang + chmod makes it directly spawnable as a single executable (spawn
|
|
32
32
|
// does not split a path on spaces, so SM_CLAUDE_BIN must be one binary).
|
|
33
|
-
const stubPath =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`#!${process.execPath}\nsetInterval(() => {}, 1000);\n`,
|
|
37
|
-
{ mode: 0o755 },
|
|
38
|
-
);
|
|
33
|
+
const stubPath = require('../../../tests/helpers/claudeStub.cjs').writeClaudeStub({
|
|
34
|
+
body: 'setInterval(() => {}, 1000);',
|
|
35
|
+
});
|
|
39
36
|
process.env.SM_CLAUDE_BIN = stubPath;
|
|
40
37
|
|
|
41
38
|
const cr = require('../chatRunner.cjs');
|
|
@@ -114,7 +111,7 @@ function isTerminal(channel) {
|
|
|
114
111
|
);
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
|
|
114
|
+
afterAll(() => {
|
|
118
115
|
try { fs.unlinkSync(stubPath); } catch { /* already gone */ }
|
|
119
116
|
delete process.env.SM_CLAUDE_BIN;
|
|
120
117
|
});
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
* arrives, THEN close fires) rather than relying on real OS/pipe timing,
|
|
22
22
|
* which is not reliably reproducible from a real child process.
|
|
23
23
|
*
|
|
24
|
-
* Run: timeout
|
|
24
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/chat-exit-close-race.test.cjs
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
'use strict';
|
|
28
28
|
|
|
29
29
|
delete process.env.SM_CHAT_CONCURRENCY;
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
import { test, afterAll } from 'vitest';
|
|
32
32
|
const assert = require('node:assert/strict');
|
|
33
33
|
const EventEmitter = require('node:events');
|
|
34
34
|
const cp = require('node:child_process');
|
|
@@ -134,7 +134,7 @@ function isTerminal(channel) {
|
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
afterAll(() => {
|
|
138
138
|
cp.spawn = originalSpawn;
|
|
139
139
|
delete process.env.SM_CHAT_CONCURRENCY;
|
|
140
140
|
});
|
|
@@ -6,24 +6,23 @@
|
|
|
6
6
|
* asserts a `chat:run:notice` event is broadcast — informational, not a
|
|
7
7
|
* terminal event.
|
|
8
8
|
*
|
|
9
|
-
* Run: timeout
|
|
9
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/chat-mcp-consent-notice.test.cjs
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
delete process.env.SM_CHAT_CONCURRENCY;
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import { test } from 'vitest';
|
|
17
17
|
const assert = require('node:assert/strict');
|
|
18
18
|
const fs = require('node:fs');
|
|
19
19
|
const os = require('node:os');
|
|
20
20
|
const path = require('node:path');
|
|
21
|
+
const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
|
|
21
22
|
|
|
22
23
|
function writeStub(lines) {
|
|
23
|
-
const stubPath = path.join(os.tmpdir(), `sm-claude-stub-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
|
|
24
24
|
const body = lines.map((l) => `process.stdout.write(${JSON.stringify(JSON.stringify(l))} + "\\n");`).join('\n');
|
|
25
|
-
|
|
26
|
-
return stubPath;
|
|
25
|
+
return claudeStub.writeClaudeStub({ body: `${body}\nprocess.exit(0);` });
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
function isTerminal(channel) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* this cap/FIFO governs silent runs only — manual runs bypass it entirely
|
|
7
7
|
* (see chatRunner.spec.ts's "manual runs are uncapped" tests).
|
|
8
8
|
*
|
|
9
|
-
* Run: timeout
|
|
9
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/chat-queue.test.cjs
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
'use strict';
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// Force the default cap (2) regardless of the developer's shell env.
|
|
15
15
|
delete process.env.SM_CHAT_CONCURRENCY;
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
import { test } from 'vitest';
|
|
18
18
|
const assert = require('node:assert/strict');
|
|
19
19
|
const cr = require('../chatRunner.cjs');
|
|
20
20
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* chat-stop-signal.test.cjs — unit tests for chatRunner.parseStopSignal, the
|
|
3
3
|
* single source of truth for the terminal-chat stop-signal protocol (PRD 318).
|
|
4
4
|
*
|
|
5
|
-
* Run: timeout
|
|
5
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/chat-stop-signal.test.cjs
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
import { test } from 'vitest';
|
|
11
11
|
const assert = require('node:assert/strict');
|
|
12
12
|
const { parseStopSignal, STOP_SENTINEL } = require('../chatRunner.cjs');
|
|
13
13
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRD 1286 — archiving a row must never orphan its dependents, and a fully
|
|
3
|
+
* blocked queue must never read healthy.
|
|
4
|
+
*
|
|
5
|
+
* Option (b): the depends-gate (computeDepHistorySatisfaction) treats a dep
|
|
6
|
+
* whose PRD file was archived — including the manual-archive layout
|
|
7
|
+
* prds-archived/<ISO-ts>/<slug>.md — as satisfied. A dep with no row, no
|
|
8
|
+
* history and no archived file still HOLDS.
|
|
9
|
+
*
|
|
10
|
+
* Run: timeout 120 npx vitest run src/main/__tests__/dep-orphan-archive-health.test.cjs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
import { test, expect } from 'vitest';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
|
|
20
|
+
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1286-home-'));
|
|
21
|
+
process.env.HOME = tmpHome;
|
|
22
|
+
|
|
23
|
+
const scheduler = require('../scheduler.cjs');
|
|
24
|
+
const { pickNextBatch } = require('../lib/schedulerBatch.cjs');
|
|
25
|
+
const { evaluateUnresolvableDepHealth } = require('../health.cjs');
|
|
26
|
+
|
|
27
|
+
function projectWithDependent(dep) {
|
|
28
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1286-proj-'));
|
|
29
|
+
const jobs = [{ slug: '902-next', status: 'pending', cwd, dependsOn: [dep] }];
|
|
30
|
+
return { cwd, jobs };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function eligible(jobs) {
|
|
34
|
+
const satisfiedSlugsByCwd = await scheduler.computeDepHistorySatisfaction({ jobs });
|
|
35
|
+
const { batch: picked } = pickNextBatch(jobs, new Set(), 3, { satisfiedSlugsByCwd });
|
|
36
|
+
return { batch: picked.map((j) => j.slug ?? j.job?.slug ?? j), satisfiedSlugsByCwd };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test('a dep archived via the manual-archive layout (prds-archived/<ts>/) leaves its dependent eligible', async () => {
|
|
40
|
+
const { cwd, jobs } = projectWithDependent('fo-01-base'); // bare slug, as authored
|
|
41
|
+
const dir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', 'e1', 'prds-archived', '2026-09-18T01-00-00-000Z');
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
fs.writeFileSync(path.join(dir, '900-fo-01-base.md'), '# Goal\n');
|
|
44
|
+
const { batch } = await eligible(jobs);
|
|
45
|
+
expect(batch).toContain('902-next');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('a dep that never ran (no row, history or archived file) keeps its dependent held', async () => {
|
|
49
|
+
const { jobs } = projectWithDependent('fo-01-base');
|
|
50
|
+
const { batch } = await eligible(jobs);
|
|
51
|
+
expect(batch).not.toContain('902-next');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('health: every pending row held behind one unresolvable root is non-GREEN and names the root', async () => {
|
|
55
|
+
const { cwd } = projectWithDependent('fo-01-base');
|
|
56
|
+
const jobs = [
|
|
57
|
+
{ slug: '902-a', status: 'pending', cwd, dependsOn: ['fo-01-base'] },
|
|
58
|
+
{ slug: '903-b', status: 'pending', cwd, dependsOn: ['a'] }, // transitive, bare
|
|
59
|
+
];
|
|
60
|
+
const satisfied = await scheduler.computeDepHistorySatisfaction({ jobs });
|
|
61
|
+
const res = evaluateUnresolvableDepHealth(jobs, satisfied);
|
|
62
|
+
expect(res.ok).toBe(false);
|
|
63
|
+
expect(res.projects[0].roots).toEqual(['fo-01-base']);
|
|
64
|
+
expect(res.message).toContain('fo-01-base');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('health: a project with any dispatchable pending row, or a satisfied root, stays GREEN', async () => {
|
|
68
|
+
const { cwd } = projectWithDependent('x');
|
|
69
|
+
const mixed = [
|
|
70
|
+
{ slug: '902-a', status: 'pending', cwd, dependsOn: ['fo-01-base'] },
|
|
71
|
+
{ slug: '903-free', status: 'pending', cwd, dependsOn: [] },
|
|
72
|
+
];
|
|
73
|
+
const satisfied = await scheduler.computeDepHistorySatisfaction({ jobs: mixed });
|
|
74
|
+
expect(evaluateUnresolvableDepHealth(mixed, satisfied).ok).toBe(true);
|
|
75
|
+
const satisfiedRoot = new Map([[cwd, new Set(['900-fo-01-base'])]]);
|
|
76
|
+
expect(evaluateUnresolvableDepHealth([mixed[0]], satisfiedRoot).ok).toBe(true);
|
|
77
|
+
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dod-batchkey.test.cjs — unit tests for definitionOfDone.cjs helpers.
|
|
3
3
|
*
|
|
4
|
-
* Run: timeout
|
|
4
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/dod-batchkey.test.cjs
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import { test } from 'vitest';
|
|
10
10
|
const assert = require('node:assert/strict');
|
|
11
11
|
const os = require('node:os');
|
|
12
12
|
const fs = require('node:fs');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dod-drain-hook.test.cjs — drives the DoD drain handler.
|
|
3
3
|
*
|
|
4
|
-
* Run: timeout
|
|
4
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/dod-drain-hook.test.cjs
|
|
5
5
|
*
|
|
6
6
|
* Scenarios:
|
|
7
7
|
* 1. Drain with completed jobs → writes report once.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import { test, beforeEach, afterEach } from 'vitest';
|
|
17
17
|
const assert = require('node:assert/strict');
|
|
18
18
|
const os = require('node:os');
|
|
19
19
|
const fs = require('node:fs');
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dod-report.test.cjs — unit tests for flagRiskySurfaces + writeReport.
|
|
3
3
|
*
|
|
4
|
-
* Run: timeout
|
|
4
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/dod-report.test.cjs
|
|
5
5
|
*
|
|
6
6
|
* Fixtures: os.tmpdir() only — never writes into real runs/.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import { test } from 'vitest';
|
|
12
12
|
const assert = require('node:assert/strict');
|
|
13
13
|
const os = require('node:os');
|
|
14
14
|
const fs = require('node:fs');
|