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,7 +11,9 @@
|
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const os = require('node:os');
|
|
13
13
|
const path = require('node:path');
|
|
14
|
-
const { KIND_CONFIG: WORKTREE_KIND_CONFIG } = require('
|
|
14
|
+
const { KIND_CONFIG: WORKTREE_KIND_CONFIG } = require('./gitWorktree.cjs');
|
|
15
|
+
const { OPS_ROOT_DIR } = require('./opsOwnership.cjs');
|
|
16
|
+
const { classifyCwd, worktreeMainRootOf } = require('./cwdClassify.cjs');
|
|
15
17
|
|
|
16
18
|
const HOME = os.homedir();
|
|
17
19
|
const TMPDIR = os.tmpdir();
|
|
@@ -31,7 +33,7 @@ const TMPDIR = os.tmpdir();
|
|
|
31
33
|
// could not — e.g. a bare, .git-less worktree-root directory.
|
|
32
34
|
const TMP_DROP_ROOTS = {
|
|
33
35
|
exactDropCwds: [TMPDIR],
|
|
34
|
-
prefixDropRoots
|
|
36
|
+
get prefixDropRoots() { return [WORKTREE_KIND_CONFIG.job.root, WORKTREE_KIND_CONFIG.epic.root]; },
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
// Transcript reads only need the last line with a `cwd` field — a few dozen
|
|
@@ -41,6 +43,30 @@ const TAIL_BYTES = 64 * 1024;
|
|
|
41
43
|
// Safety cap on distinct cwds to bound result set size. O(1) extra space.
|
|
42
44
|
const MAX_CWDS = 50;
|
|
43
45
|
|
|
46
|
+
// activeProjectCwds' scan is a readdirSync of every dir under ~/.claude
|
|
47
|
+
// /projects (2209 dirs / 8.8 GB observed) plus a statSync per transcript —
|
|
48
|
+
// ~270 ms warm. reconcile() calls into it (directly or via allProjectCwds)
|
|
49
|
+
// several times per pass, so cache the resolved cwd list keyed on the
|
|
50
|
+
// resolved argument TUPLE (projectsDir, maxAgeMin, maxCwds) — never on
|
|
51
|
+
// `!opts`, since allProjectCwds always constructs an options object and that
|
|
52
|
+
// guard would cache nothing on the hottest path.
|
|
53
|
+
//
|
|
54
|
+
// Invalidation: projectsDir's own mtime (bumps when a project DIRECTORY is
|
|
55
|
+
// added/removed — NOT when an existing transcript gains a line, which is
|
|
56
|
+
// fine: a project already in the list stays in the list) plus a short TTL
|
|
57
|
+
// as the backstop for everything else. Same mtime-keyed idiom as
|
|
58
|
+
// scheduler/prdParser.cjs's dirCache and queueHistory.cjs's historyCacheKey.
|
|
59
|
+
// TTL sits above the 60 s dispatch-loop interval (scheduler POLL_INTERVAL_MS) so
|
|
60
|
+
// one pass never re-scans cold. Cached arrays are frozen — shared, never mutated.
|
|
61
|
+
const CACHE_TTL_MS = 120_000;
|
|
62
|
+
const EMPTY_CWDS = Object.freeze([]);
|
|
63
|
+
const cwdScanCache = new Map(); // key -> { dirMtimeMs, cachedAt, result }
|
|
64
|
+
|
|
65
|
+
/** Forces the next activeProjectCwds/allProjectCwds call to rescan. */
|
|
66
|
+
function bustProjectCwdCache() {
|
|
67
|
+
cwdScanCache.clear();
|
|
68
|
+
}
|
|
69
|
+
|
|
44
70
|
// The ops-root folder name. A transcript's `cwd` can point INSIDE it whenever
|
|
45
71
|
// an agent `cd`s into an artifact directory (a PRD folder, prompt-sessions,
|
|
46
72
|
// scheduler/state) — Claude Code records the new absolute cwd on every
|
|
@@ -55,95 +81,18 @@ const MAX_CWDS = 50;
|
|
|
55
81
|
// Truncating at the segment is strictly better than dropping the row: the
|
|
56
82
|
// ancestor IS the project the agent was working in, so the active-project
|
|
57
83
|
// signal survives instead of being silently lost.
|
|
58
|
-
const OPS_DIRNAME =
|
|
59
|
-
|
|
60
|
-
// Bound on the ancestor walk in worktreeMainRootOf — well past any real
|
|
61
|
-
// filesystem depth, purely to guarantee termination without relying on
|
|
62
|
-
// reaching '/' (e.g. a symlink loop or an unusually deep path).
|
|
63
|
-
const MAX_WORKTREE_WALK = 40;
|
|
64
|
-
|
|
65
|
-
const WORKTREES_MARKER = `${path.sep}.git${path.sep}worktrees${path.sep}`;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* worktreeMainRootOf(cwd) → the main tree's root when cwd sits inside a
|
|
69
|
-
* linked `git worktree` (job/epic worktrees under os.tmpdir(), or a
|
|
70
|
-
* user-made one anywhere). Walks UP from cwd looking for a `.git` entry:
|
|
71
|
-
* - `.git` is a FILE (linked worktree) whose first line is
|
|
72
|
-
* `gitdir: <main>/.git/worktrees/<name>` → returns <main>.
|
|
73
|
-
* - `.git` is a DIRECTORY (already the main tree) → returns that ancestor.
|
|
74
|
-
* - nothing found within MAX_WORKTREE_WALK levels → returns null.
|
|
75
|
-
* Pure fs, synchronous, never throws — any unexpected shape (garbled file,
|
|
76
|
-
* unreadable entry, gitdir path without the worktrees marker) yields null so
|
|
77
|
-
* the caller leaves the cwd unchanged rather than guessing.
|
|
78
|
-
*/
|
|
79
|
-
function worktreeMainRootOf(cwd) {
|
|
80
|
-
let dir = cwd;
|
|
81
|
-
for (let i = 0; i < MAX_WORKTREE_WALK; i++) {
|
|
82
|
-
const gitPath = path.join(dir, '.git');
|
|
83
|
-
let stat;
|
|
84
|
-
try {
|
|
85
|
-
stat = fs.statSync(gitPath);
|
|
86
|
-
} catch {
|
|
87
|
-
stat = null;
|
|
88
|
-
}
|
|
89
|
-
if (stat) {
|
|
90
|
-
if (stat.isDirectory()) return dir;
|
|
91
|
-
if (stat.isFile()) {
|
|
92
|
-
let body;
|
|
93
|
-
try {
|
|
94
|
-
body = fs.readFileSync(gitPath, 'utf8');
|
|
95
|
-
} catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
const firstLine = body.split('\n', 1)[0].trim();
|
|
99
|
-
const prefix = 'gitdir:';
|
|
100
|
-
if (!firstLine.startsWith(prefix)) return null;
|
|
101
|
-
const gitdir = firstLine.slice(prefix.length).trim();
|
|
102
|
-
const markerIdx = gitdir.indexOf(WORKTREES_MARKER);
|
|
103
|
-
if (markerIdx <= 0) return null;
|
|
104
|
-
const candidateMain = gitdir.slice(0, markerIdx);
|
|
105
|
-
const worktreeName = gitdir.slice(markerIdx + WORKTREES_MARKER.length).split(path.sep)[0];
|
|
106
|
-
// Round-trip verification: gitdir's content is a plain file that
|
|
107
|
-
// anything on disk could have written (e.g. a nested `.git` file
|
|
108
|
-
// tracked inside an untrusted repo), so it must not be trusted to
|
|
109
|
-
// redirect callers to an arbitrary attacker-chosen absolute path.
|
|
110
|
-
// A genuine linked worktree's admin dir back-references the exact
|
|
111
|
-
// `.git` FILE we are resolving via its own `gitdir` pointer file —
|
|
112
|
-
// require that round trip before accepting candidateMain.
|
|
113
|
-
if (!worktreeName) return null;
|
|
114
|
-
const adminGitdirFile = path.join(candidateMain, '.git', 'worktrees', worktreeName, 'gitdir');
|
|
115
|
-
let backRef;
|
|
116
|
-
try {
|
|
117
|
-
backRef = fs.readFileSync(adminGitdirFile, 'utf8').trim();
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
if (path.resolve(backRef) !== path.resolve(gitPath)) return null;
|
|
122
|
-
return candidateMain;
|
|
123
|
-
}
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
const parent = path.dirname(dir);
|
|
127
|
-
if (parent === dir) return null;
|
|
128
|
-
dir = parent;
|
|
129
|
-
}
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
84
|
+
const OPS_DIRNAME = OPS_ROOT_DIR;
|
|
132
85
|
|
|
133
86
|
/**
|
|
134
87
|
* projectRootOf(cwd) → the project root for a cwd that may sit inside an ops
|
|
135
88
|
* tree and/or a linked git worktree. Returns cwd unchanged when neither
|
|
136
|
-
* applies
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
89
|
+
* applies, or when the worktree cannot be proven (kind `unknown` — callers that
|
|
90
|
+
* WRITE must consult classifyCwd; this stays total so dispatch never breaks).
|
|
91
|
+
* The classification itself (outermost ops truncation, then worktree
|
|
92
|
+
* resolution) lives in cwdClassify.cjs.
|
|
140
93
|
*/
|
|
141
94
|
function projectRootOf(cwd) {
|
|
142
|
-
|
|
143
|
-
const i = parts.indexOf(OPS_DIRNAME);
|
|
144
|
-
const truncated = i > 0 ? (parts.slice(0, i).join(path.sep) || path.sep) : cwd;
|
|
145
|
-
const mainRoot = worktreeMainRootOf(truncated);
|
|
146
|
-
return mainRoot || truncated;
|
|
95
|
+
return classifyCwd(cwd).projectRoot ?? cwd;
|
|
147
96
|
}
|
|
148
97
|
|
|
149
98
|
/**
|
|
@@ -190,6 +139,23 @@ function activeProjectCwds(maxAgeMin = 90, {
|
|
|
190
139
|
maxCwds = MAX_CWDS,
|
|
191
140
|
tmpDropRoots = TMP_DROP_ROOTS,
|
|
192
141
|
} = {}) {
|
|
142
|
+
const cacheKey = [projectsDir, maxAgeMin, maxCwds].join('|');
|
|
143
|
+
let dirMtimeMs;
|
|
144
|
+
try {
|
|
145
|
+
dirMtimeMs = fs.statSync(projectsDir).mtimeMs;
|
|
146
|
+
} catch {
|
|
147
|
+
// Missing/unreadable dir: nothing to scan, and any prior cache entry for
|
|
148
|
+
// this key must not survive to be handed back once the dir later
|
|
149
|
+
// appears — drop it rather than caching this empty result.
|
|
150
|
+
cwdScanCache.delete(cacheKey);
|
|
151
|
+
return EMPTY_CWDS;
|
|
152
|
+
}
|
|
153
|
+
const cached = cwdScanCache.get(cacheKey);
|
|
154
|
+
const now0 = Date.now();
|
|
155
|
+
if (cached && cached.dirMtimeMs === dirMtimeMs && now0 - cached.cachedAt < CACHE_TTL_MS) {
|
|
156
|
+
return cached.result;
|
|
157
|
+
}
|
|
158
|
+
|
|
193
159
|
// maxAgeMin === Infinity means "every project ever seen, no recency filter"
|
|
194
160
|
// (allProjectCwds below). Date.now() - Infinity is -Infinity, which every
|
|
195
161
|
// mtime clears — spelled out here because it reads like an accident.
|
|
@@ -202,14 +168,18 @@ function activeProjectCwds(maxAgeMin = 90, {
|
|
|
202
168
|
// Normalize an ops-internal cwd up to its project root BEFORE any of the
|
|
203
169
|
// checks below — the stray-ops-root incident (see OPS_DIRNAME above) got
|
|
204
170
|
// through precisely because such a path is absolute and does exist.
|
|
205
|
-
const
|
|
171
|
+
const classified = classifyCwd(rawCwd);
|
|
172
|
+
// `unknown` (a .git file we cannot resolve) is non-registrable: registering
|
|
173
|
+
// it would hand every consumer a project whose ops writes are refused.
|
|
174
|
+
if (classified.kind === 'unknown') return;
|
|
175
|
+
const cwd = classified.projectRoot;
|
|
206
176
|
// Must be ABSOLUTE. A relative fragment would pass the statSync below
|
|
207
177
|
// whenever it happens to resolve against THIS process's own cwd, and
|
|
208
178
|
// every consumer (queueStore.projectStateDir, prdLocations) then joins
|
|
209
179
|
// it into an ops-root path that lands somewhere arbitrary. Callers key
|
|
210
180
|
// whole per-project state off these strings — a project is a cwd, and a
|
|
211
181
|
// cwd is an absolute path.
|
|
212
|
-
if (!path.isAbsolute(cwd)) return;
|
|
182
|
+
if (!cwd || !path.isAbsolute(cwd)) return;
|
|
213
183
|
// Drop-guard of last resort: a cwd that is (or sits inside) a known
|
|
214
184
|
// worktree-scratch root after projectRootOf's normalization is one
|
|
215
185
|
// worktreeMainRootOf could not resolve to a main tree — never a real
|
|
@@ -231,7 +201,7 @@ function activeProjectCwds(maxAgeMin = 90, {
|
|
|
231
201
|
|
|
232
202
|
// Scan ~/.claude/projects/*/ transcript *.jsonl files.
|
|
233
203
|
let slugs;
|
|
234
|
-
try { slugs = fs.readdirSync(projectsDir); } catch { return result; }
|
|
204
|
+
try { slugs = fs.readdirSync(projectsDir); } catch { cwdScanCache.delete(cacheKey); return Object.freeze(result); }
|
|
235
205
|
|
|
236
206
|
for (const slug of slugs) {
|
|
237
207
|
if (result.length >= maxCwds) break;
|
|
@@ -266,6 +236,8 @@ function activeProjectCwds(maxAgeMin = 90, {
|
|
|
266
236
|
}
|
|
267
237
|
}
|
|
268
238
|
|
|
239
|
+
Object.freeze(result);
|
|
240
|
+
cwdScanCache.set(cacheKey, { dirMtimeMs, cachedAt: now0, result });
|
|
269
241
|
return result;
|
|
270
242
|
}
|
|
271
243
|
|
|
@@ -288,4 +260,10 @@ function allProjectCwds(opts = {}) {
|
|
|
288
260
|
return activeProjectCwds(Infinity, { maxCwds: 500, ...opts });
|
|
289
261
|
}
|
|
290
262
|
|
|
291
|
-
module.exports = {
|
|
263
|
+
module.exports = {
|
|
264
|
+
activeProjectCwds,
|
|
265
|
+
allProjectCwds,
|
|
266
|
+
projectRootOf,
|
|
267
|
+
worktreeMainRootOf,
|
|
268
|
+
bustProjectCwdCache,
|
|
269
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* adoptedRunSupervisor.cjs — re-arms the budget watchdog, idle-tail kill and
|
|
5
|
+
* deadman for an executor the boot SPARED (scheduler.cjs stamps
|
|
6
|
+
* `adoptedAtBoot`), from the durable `<slug>.supervisor.json` record.
|
|
7
|
+
*
|
|
8
|
+
* executeJob attaches those three timers to the child it spawned; an adopted
|
|
9
|
+
* executor belongs to a dead app process, so nothing supervised it (and a
|
|
10
|
+
* quietMachine row held no lease). Here the same three conditions are polled
|
|
11
|
+
* against the record's own numbers (budgetMs / startedAt, idleKillMs vs the
|
|
12
|
+
* log mtime, maxDurationMs) by the app process that owns the row. The
|
|
13
|
+
* signal goes to the recorded process group only after the pid is proven to
|
|
14
|
+
* still be the recorded process (procIdentity, fail-closed). It never mints
|
|
15
|
+
* a slot token: the adopted row stays `untrackedRunning` in pickNextBatch.
|
|
16
|
+
*
|
|
17
|
+
* Plain Node, no electron — every effect is injected so it is unit-testable
|
|
18
|
+
* with fake timers and without scheduler.cjs.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* evaluateAdoptedRun(record, { now, logMtimeMs, defaultMaxDurationMs })
|
|
23
|
+
* → { kind: 'deadman'|'budget'|'idle-tail', reason } | null
|
|
24
|
+
* Pure. Precedence deadman > budget > idle-tail (hardest kill first).
|
|
25
|
+
*/
|
|
26
|
+
function evaluateAdoptedRun(record, { now, logMtimeMs, defaultMaxDurationMs = 0 }) {
|
|
27
|
+
const started = Number.isFinite(record.startedAt) ? record.startedAt : null;
|
|
28
|
+
const elapsed = started === null ? null : now - started;
|
|
29
|
+
const deadmanMs = record.maxDurationMs > 0 ? record.maxDurationMs : defaultMaxDurationMs;
|
|
30
|
+
if (elapsed !== null && deadmanMs > 0 && elapsed >= deadmanMs) {
|
|
31
|
+
return { kind: 'deadman', reason: `deadman: ran ${Math.round(elapsed / 60_000)}m (>= ${Math.round(deadmanMs / 60_000)}m)` };
|
|
32
|
+
}
|
|
33
|
+
if (elapsed !== null && record.budgetMs > 0 && elapsed >= record.budgetMs) {
|
|
34
|
+
return {
|
|
35
|
+
kind: 'budget',
|
|
36
|
+
reason: `wall-clock budget exceeded: ran ${Math.round(elapsed / 60_000)}m against a ${Math.round(record.budgetMs / 60_000)}m budget (adopted after app restart)`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (record.idleKillMs > 0 && Number.isFinite(logMtimeMs) && now - logMtimeMs > record.idleKillMs) {
|
|
40
|
+
return { kind: 'idle-tail', reason: `idle-output: log mtime stalled ${Math.round((now - logMtimeMs) / 1000)}s (> ${Math.round(record.idleKillMs / 1000)}s)` };
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* superviseAdoptedRun(record, deps) → { cancel(), check() }
|
|
47
|
+
* One poll timer per adopted run; `check()` runs once immediately so a run
|
|
48
|
+
* already past its budget at boot is killed without waiting a full interval.
|
|
49
|
+
* deps: { logPath, now, statLogMtimeMs, pidAlive, identityOf, isDifferentProcess,
|
|
50
|
+
* killGroup(pid, signal), stampKill(kind, reason) → Promise, log(msg),
|
|
51
|
+
* checkIntervalMs, sigkillAfterMs, defaultMaxDurationMs }
|
|
52
|
+
*/
|
|
53
|
+
function superviseAdoptedRun(record, deps) {
|
|
54
|
+
const {
|
|
55
|
+
logPath, now = Date.now, statLogMtimeMs, pidAlive, identityOf, isDifferentProcess, killGroup,
|
|
56
|
+
stampKill, log = () => {}, checkIntervalMs, sigkillAfterMs, defaultMaxDurationMs = 0,
|
|
57
|
+
} = deps;
|
|
58
|
+
let done = false;
|
|
59
|
+
let timer = null;
|
|
60
|
+
let killTimer = null;
|
|
61
|
+
|
|
62
|
+
const isOurs = () => pidAlive(record.pid) && !isDifferentProcess(record.identity, identityOf(record.pid));
|
|
63
|
+
const cancel = () => {
|
|
64
|
+
done = true;
|
|
65
|
+
if (timer) clearInterval(timer);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
async function check() {
|
|
69
|
+
if (done) return null;
|
|
70
|
+
if (!isOurs()) { cancel(); return 'gone'; } // exited (reaper finalizes) or pid recycled — never signal
|
|
71
|
+
const verdict = evaluateAdoptedRun(record, {
|
|
72
|
+
now: now(), logMtimeMs: statLogMtimeMs(logPath), defaultMaxDurationMs,
|
|
73
|
+
});
|
|
74
|
+
if (!verdict) return null;
|
|
75
|
+
cancel();
|
|
76
|
+
try { await stampKill(verdict.kind, verdict.reason); } catch (e) { log(`stampKill failed: ${e?.message ?? e}`); }
|
|
77
|
+
if (!isOurs()) return 'gone'; // exited while the stamp was in flight
|
|
78
|
+
const first = verdict.kind === 'deadman' ? 'SIGKILL' : 'SIGTERM';
|
|
79
|
+
log(`adopted-run ${verdict.kind} watchdog: ${verdict.reason} — ${first} process group`);
|
|
80
|
+
killGroup(record.pgid || record.pid, first, record.pid);
|
|
81
|
+
if (first === 'SIGTERM') {
|
|
82
|
+
killTimer = setTimeout(() => {
|
|
83
|
+
if (isOurs()) killGroup(record.pgid || record.pid, 'SIGKILL', record.pid);
|
|
84
|
+
}, sigkillAfterMs);
|
|
85
|
+
if (killTimer.unref) killTimer.unref();
|
|
86
|
+
}
|
|
87
|
+
return verdict.kind;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
timer = setInterval(() => { check().catch((e) => log(`check failed: ${e?.message ?? e}`)); }, checkIntervalMs);
|
|
91
|
+
if (timer.unref) timer.unref();
|
|
92
|
+
return {
|
|
93
|
+
cancel() { cancel(); if (killTimer) clearTimeout(killTimer); },
|
|
94
|
+
check,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* superviseAdoptedRuns(jobs, ctx) → Promise<string[]> (slugs newly armed)
|
|
100
|
+
* Arms a supervisor for every `running` row stamped adoptedAtBoot that has
|
|
101
|
+
* none in `ctx.registry` (Map key `slug|runId`), drops registry entries whose
|
|
102
|
+
* row is no longer that run, re-acquires the quietMachine lease and stamps
|
|
103
|
+
* supervisedAt. ctx: { registry, runDir(row), readRecord(runDir, slug),
|
|
104
|
+
* lease: { acquire, holder }, markSupervised(row), makeDeps(row, record) }
|
|
105
|
+
* Complexity: O(jobs).
|
|
106
|
+
*/
|
|
107
|
+
async function superviseAdoptedRuns(jobs, ctx) {
|
|
108
|
+
const { registry, runDir, readRecord, lease, markSupervised, makeDeps } = ctx;
|
|
109
|
+
const live = new Set();
|
|
110
|
+
const armed = [];
|
|
111
|
+
for (const j of jobs) {
|
|
112
|
+
if (j.status !== 'running' || !j.adoptedAtBoot) continue;
|
|
113
|
+
const key = `${j.slug}|${j.runId ?? ''}`;
|
|
114
|
+
live.add(key);
|
|
115
|
+
if (registry.has(key)) continue;
|
|
116
|
+
const dir = runDir(j);
|
|
117
|
+
const record = dir ? readRecord(dir, j.slug) : null;
|
|
118
|
+
if (!record || !record.pid) continue; // no durable record → nothing to re-arm from
|
|
119
|
+
if (j.quietMachine === true && !lease.acquire(j.slug) && lease.holder() !== j.slug) {
|
|
120
|
+
console.warn(`[scheduler] adopted quietMachine run ${j.slug}: lease held by ${lease.holder()} — not re-acquired`);
|
|
121
|
+
}
|
|
122
|
+
const handle = superviseAdoptedRun(record, makeDeps(j, record));
|
|
123
|
+
registry.set(key, handle);
|
|
124
|
+
armed.push(j.slug);
|
|
125
|
+
await markSupervised(j);
|
|
126
|
+
handle.check().catch(() => {});
|
|
127
|
+
}
|
|
128
|
+
for (const [key, handle] of registry) {
|
|
129
|
+
if (live.has(key)) continue;
|
|
130
|
+
handle.cancel();
|
|
131
|
+
registry.delete(key);
|
|
132
|
+
}
|
|
133
|
+
return armed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { evaluateAdoptedRun, superviseAdoptedRun, superviseAdoptedRuns };
|
|
@@ -3,14 +3,19 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* agentModelResolve.cjs — resolves the `--model` an Epic's session should
|
|
5
5
|
* launch a headless `claude -p` (or Terminal `claude`) process with: its
|
|
6
|
-
* agentType persona's own `model` field
|
|
7
|
-
*
|
|
6
|
+
* agentType persona's own `model` field, project-overlay-then-global
|
|
7
|
+
* (`epicMint.cjs`'s `resolvePersonaPaths`), when the Epic has one set and
|
|
8
|
+
* it isn't 'inherit', else `fallbackModel`.
|
|
8
9
|
*
|
|
9
10
|
* Extracted from PRD agent-model-default-terminal (which wired this for the
|
|
10
11
|
* Terminal-view launch in EpicTerminalPane.tsx) so chatRunner.cjs's headless
|
|
11
12
|
* Chat-view launch resolves the SAME persona-derived model — Chat and
|
|
12
13
|
* Terminal are two VIEWS over one Epic session (CLAUDE.md) and must agree on
|
|
13
|
-
* which model that session launches with.
|
|
14
|
+
* which model that session launches with. Terminal reaches `resolveEpicModel`
|
|
15
|
+
* through the `agents:resolve-epic-model` IPC (main-process only; the
|
|
16
|
+
* renderer has no fs access) rather than a second implementation, so the two
|
|
17
|
+
* views literally call the same function instead of two functions that are
|
|
18
|
+
* merely supposed to agree.
|
|
14
19
|
*
|
|
15
20
|
* Plain Node module (sync fs) — no Electron deps, mirrors epicMint.cjs's
|
|
16
21
|
* readActiveIndex so this can be called from chatRunner.cjs's synchronous
|
|
@@ -21,7 +26,8 @@ const fs = require('node:fs');
|
|
|
21
26
|
const path = require('node:path');
|
|
22
27
|
const os = require('node:os');
|
|
23
28
|
const { splitFrontmatter } = require('./prdFrontmatter.cjs');
|
|
24
|
-
const { readActiveIndex } = require('./epicMint.cjs');
|
|
29
|
+
const { readActiveIndex, resolvePersonaPaths } = require('./epicMint.cjs');
|
|
30
|
+
const { PERSONA_NAME_RE } = require('../agentLibrary.cjs');
|
|
25
31
|
const configMgr = require('../config.cjs');
|
|
26
32
|
|
|
27
33
|
/** The hardcoded floor every branch of resolveEpicModel falls back to — --model must never be omitted (CLAUDE.md "Automation model pinning"). */
|
|
@@ -73,30 +79,87 @@ function findAgentTypeByClaudeSessionId(cwd, claudeSessionId, deps = {}) {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
82
|
+
* Shared miss-tolerant reader `readOverlayAwarePersonaModel` below is a thin
|
|
83
|
+
* wrapper over: tries each candidate path in order (`validatePath` then sync
|
|
84
|
+
* `readFileSync`), and returns the first readable file's `model` frontmatter
|
|
85
|
+
* field — even if that field is absent, which is why an empty-`model`
|
|
86
|
+
* overlay file does NOT fall through to a later candidate (matches
|
|
87
|
+
* `getPersonaBody`'s own semantics). Logs the dangling-persona warning at
|
|
88
|
+
* most once per `(deps.cwd, agentType)` only when EVERY candidate misses.
|
|
89
|
+
* Never throws.
|
|
79
90
|
*/
|
|
80
|
-
function
|
|
81
|
-
if (!agentType) return null;
|
|
82
|
-
const globalDir = deps.globalDir || path.join(os.homedir(), '.claude', 'agents');
|
|
91
|
+
function readModelFromCandidatePaths(candidates, agentType, deps) {
|
|
83
92
|
const validatePath = deps.validatePath || configMgr.validatePath;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
let real;
|
|
95
|
+
try {
|
|
96
|
+
real = validatePath(candidate);
|
|
97
|
+
} catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const text = fs.readFileSync(real, 'utf8');
|
|
102
|
+
const { fm } = splitFrontmatter(text);
|
|
103
|
+
return fm.model || null;
|
|
104
|
+
} catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
90
107
|
}
|
|
91
|
-
|
|
108
|
+
logDanglingPersonaOnce(deps.cwd, agentType);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Reads an Epic's agentType persona's `model` frontmatter field with the SAME
|
|
114
|
+
* project-overlay-then-global precedence `agentLibrary.cjs`'s `getPersonaBody`
|
|
115
|
+
* (and, through it, `resolvePrdPersonaForSpawn` below) already applies for a
|
|
116
|
+
* scheduled PRD — `epicMint.cjs`'s `resolvePersonaPaths` is the shared path
|
|
117
|
+
* resolver both readers go through, so there is exactly one place that decides
|
|
118
|
+
* which of the two files wins.
|
|
119
|
+
*
|
|
120
|
+
* Sync (not `getPersonaBody`'s async `fsp.readFile`) because `resolveEpicModel`
|
|
121
|
+
* must stay synchronous: `chatRunner.cjs`'s `executeRun()` registers its
|
|
122
|
+
* cancel handle into `inFlight` synchronously, and its caller (`pump()`) reads
|
|
123
|
+
* that entry back immediately after invoking the executor — an `await`
|
|
124
|
+
* inserted ahead of that registration would run it a tick late and silently
|
|
125
|
+
* break `cancel()`. Returns null on a total miss (neither location resolves)
|
|
126
|
+
* — never throws.
|
|
127
|
+
*/
|
|
128
|
+
function readOverlayAwarePersonaModel(agentType, deps = {}) {
|
|
129
|
+
// Same guard agentLibrary.cjs's getPersonaBody applies before it joins
|
|
130
|
+
// `name` into a path: without it, a caller-supplied agentType like
|
|
131
|
+
// "../../other-project/CLAUDE" survives resolvePersonaPaths' plain
|
|
132
|
+
// path.join and validatePath's allowed-roots check (which only confirms
|
|
133
|
+
// the resolved path stays under the home dir / an opened project), letting
|
|
134
|
+
// any readable .md under those roots be read on this --model spawn path.
|
|
135
|
+
if (!agentType || !PERSONA_NAME_RE.test(agentType)) return null;
|
|
136
|
+
const resolvePaths = deps.resolvePersonaPaths || resolvePersonaPaths;
|
|
137
|
+
const { projectPath, globalPath } = resolvePaths(deps.cwd, agentType, deps);
|
|
138
|
+
return readModelFromCandidatePaths([projectPath, globalPath], agentType, deps);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* True when `personaPath` (an already-resolved persona file path, e.g. from
|
|
143
|
+
* agentLibrary.cjs's getPersonaBody) is the PROJECT overlay candidate rather
|
|
144
|
+
* than the global one — the same "which of resolvePersonaPaths' two
|
|
145
|
+
* candidates won" question readModelFromCandidatePaths above answers
|
|
146
|
+
* structurally by trying the project path first. Exported so a caller that
|
|
147
|
+
* already has a resolved persona path from a DIFFERENT reader (one that
|
|
148
|
+
* needs async I/O or richer provenance than this module's sync
|
|
149
|
+
* readOverlayAwarePersonaModel returns — e.g. effectiveModelInfo.cjs) can
|
|
150
|
+
* ask this module the overlay question instead of re-deriving its own
|
|
151
|
+
* resolvePersonaPaths-based comparison. Never throws; a resolution failure
|
|
152
|
+
* conservatively answers "not overlay" (assume global).
|
|
153
|
+
*/
|
|
154
|
+
function isProjectOverlayPersonaPath(cwd, agentType, personaPath, deps = {}) {
|
|
155
|
+
if (!personaPath) return false;
|
|
92
156
|
try {
|
|
93
|
-
|
|
157
|
+
const resolvePaths = deps.resolvePersonaPaths || resolvePersonaPaths;
|
|
158
|
+
const { projectPath } = resolvePaths(cwd, agentType, deps);
|
|
159
|
+
return Boolean(projectPath) && path.resolve(projectPath) === path.resolve(personaPath);
|
|
94
160
|
} catch {
|
|
95
|
-
|
|
96
|
-
return null;
|
|
161
|
+
return false;
|
|
97
162
|
}
|
|
98
|
-
const { fm } = splitFrontmatter(text);
|
|
99
|
-
return fm.model || null;
|
|
100
163
|
}
|
|
101
164
|
|
|
102
165
|
/**
|
|
@@ -105,6 +168,12 @@ function readPersonaModel(agentType, deps = {}) {
|
|
|
105
168
|
* file) falls back to `fallbackModel` (default: 'sonnet', the same literal
|
|
106
169
|
* every claude -p call site pins per CLAUDE.md's model-pinning rule).
|
|
107
170
|
*
|
|
171
|
+
* This is the SINGLE authority both Chat (`chatRunner.cjs`, in-process) and
|
|
172
|
+
* Terminal (`EpicTerminalPane.tsx`, via the `agents:resolve-epic-model` IPC
|
|
173
|
+
* added in the same change) call for an Epic-backed launch, so the two views
|
|
174
|
+
* of one Epic agree on which model that session launches with — see this
|
|
175
|
+
* module's header.
|
|
176
|
+
*
|
|
108
177
|
* @param {{ cwd: string, claudeSessionId: string, fallbackModel?: string, deps?: object }} opts
|
|
109
178
|
* @returns {string}
|
|
110
179
|
*/
|
|
@@ -112,7 +181,7 @@ function resolveEpicModel({ cwd, claudeSessionId, fallbackModel = FALLBACK_MODEL
|
|
|
112
181
|
try {
|
|
113
182
|
const agentType = findAgentTypeByClaudeSessionId(cwd, claudeSessionId, deps);
|
|
114
183
|
if (!agentType) return fallbackModel;
|
|
115
|
-
const model =
|
|
184
|
+
const model = readOverlayAwarePersonaModel(agentType, { ...deps, cwd });
|
|
116
185
|
if (!model || model === 'inherit') return fallbackModel;
|
|
117
186
|
return model;
|
|
118
187
|
} catch {
|
|
@@ -181,6 +250,7 @@ module.exports = {
|
|
|
181
250
|
FALLBACK_MODEL,
|
|
182
251
|
resolveEpicModel,
|
|
183
252
|
findAgentTypeByClaudeSessionId,
|
|
184
|
-
|
|
253
|
+
readOverlayAwarePersonaModel,
|
|
254
|
+
isProjectOverlayPersonaPath,
|
|
185
255
|
resolvePrdPersonaForSpawn,
|
|
186
256
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Canonical runtime schema for an Agent persona WRITE payload, mirroring the
|
|
3
|
-
* TS `AgentPersonaSaveInput` interface
|
|
3
|
+
* TS `AgentPersonaSaveInput` interface in src/preload/api.d.ts
|
|
4
4
|
* field-for-field.
|
|
5
5
|
*
|
|
6
6
|
* Modelled on promptSessionSchema.cjs / scheduleJobSchema.cjs's "zod schema
|
|
@@ -39,7 +39,7 @@ const ProjectEntrySchema = z.string().refine(
|
|
|
39
39
|
`must be "${ALL_PROJECTS}" or an absolute path`,
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
// Mirrors AgentPersonaSaveInput (src/preload/api.d.ts
|
|
42
|
+
// Mirrors AgentPersonaSaveInput (src/preload/api.d.ts). `tags` is the
|
|
43
43
|
// Epic-mission WorkType union, NOT a free-form string list — the same
|
|
44
44
|
// concept as PromptSessionSchema's `tag` / scheduleRetagPrd's `tag`, just
|
|
45
45
|
// plural here since a persona can be associated with more than one mission.
|
|
@@ -16,10 +16,9 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const fs = require('node:fs');
|
|
19
|
-
const os = require('node:os');
|
|
20
19
|
const path = require('node:path');
|
|
21
20
|
|
|
22
|
-
const
|
|
21
|
+
const { auditLogPath } = require('./schedulerPaths.cjs');
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Best-effort identity of the process making this call — there is no
|
|
@@ -50,11 +49,37 @@ function appendAuditEvent(kind, fields = {}) {
|
|
|
50
49
|
...fields,
|
|
51
50
|
caller: callerContext(),
|
|
52
51
|
};
|
|
53
|
-
|
|
54
|
-
fs.
|
|
52
|
+
const target = auditLogPath();
|
|
53
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
54
|
+
fs.appendFileSync(target, `${JSON.stringify(record)}\n`);
|
|
55
55
|
} catch (e) {
|
|
56
56
|
console.error('[auditLog] failed to append audit event', e?.message ?? String(e));
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* readTail(maxBytes, target?) → string[] — the complete JSONL lines within the
|
|
62
|
+
* last `maxBytes` of the log, oldest first. O(maxBytes), never O(file): the
|
|
63
|
+
* audit log is a never-rotated provenance record and grows without bound. A
|
|
64
|
+
* line cut by the window's start is dropped. Missing/unreadable file → [].
|
|
65
|
+
*/
|
|
66
|
+
function readTail(maxBytes, target = auditLogPath()) {
|
|
67
|
+
let fd;
|
|
68
|
+
try {
|
|
69
|
+
fd = fs.openSync(target, 'r');
|
|
70
|
+
const { size } = fs.fstatSync(fd);
|
|
71
|
+
const readSize = Math.min(Math.max(0, maxBytes), size);
|
|
72
|
+
if (readSize === 0) return [];
|
|
73
|
+
const buf = Buffer.alloc(readSize);
|
|
74
|
+
fs.readSync(fd, buf, 0, readSize, size - readSize);
|
|
75
|
+
const lines = buf.toString('utf8').split('\n');
|
|
76
|
+
if (readSize < size) lines.shift(); // first element is a partial line
|
|
77
|
+
return lines.filter(Boolean);
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
} finally {
|
|
81
|
+
if (fd !== undefined) { try { fs.closeSync(fd); } catch { /* */ } }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { appendAuditEvent, auditLogPath, readTail };
|