claude-code-session-manager 0.97.0 → 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/cli.cjs +16 -4
- package/bin/node-floor.cjs +41 -0
- package/dist/assets/{DataModel-D93FoaOm.js → DataModel-HdR0AxFq.js} +1 -1
- package/dist/assets/{History-CXxxqNP1.js → History-Dr4Jr_5z.js} +2 -2
- package/dist/assets/{Hooks-CzeYFQjq.js → Hooks-NhrvH3JP.js} +3 -3
- package/dist/assets/{Library-D2zPkwOC.js → Library-Dv_EtIMr.js} +1 -1
- package/dist/assets/{MarkdownEditor-DbinP78A.js → MarkdownEditor-DTKDWV_n.js} +1 -1
- package/dist/assets/{McpServers-Bu_sVGLr.js → McpServers-M034G9B3.js} +2 -2
- package/dist/assets/{Memory-BgiI6LZ_.js → Memory-DzF1B4gx.js} +6 -6
- package/dist/assets/{Permissions-C1MMzZRm.js → Permissions-DrgbSGWl.js} +3 -3
- package/dist/assets/{Plugins-Dhu6wAZi.js → Plugins-Bd3ef35w.js} +2 -2
- package/dist/assets/{ProvenanceBadge-C3AcWKMG.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
- package/dist/assets/{SaveBar-plwYm0iO.js → SaveBar-rimJem-Y.js} +1 -1
- package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
- package/dist/assets/{ScopeSwitcher-DFy0vv_o.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
- package/dist/assets/Settings-Dc1F_sSg.js +3 -0
- package/dist/assets/{SkillReferenceGraph-DsI7hRPB.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
- package/dist/assets/{Skills-3E748UfB.js → Skills-DRXd18gu.js} +2 -2
- package/dist/assets/{SystemPrompt-BqmfUJOd.js → SystemPrompt-ahy5-Fwe.js} +1 -1
- package/dist/assets/{TagLibrary-CPz6fO8Q.js → TagLibrary-C_tToDeL.js} +1 -1
- package/dist/assets/{TiptapBody-D9EoBQ1P.js → TiptapBody-DvdefS6K.js} +1 -1
- package/dist/assets/{Toggle-BHAmZNDn.js → Toggle-6Jl5Njc6.js} +1 -1
- package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
- package/dist/assets/{index-tn5JIVPj.js → index-vx8O73l8.js} +497 -502
- package/dist/assets/{settingsSchema-Crqy_aNz.js → settingsSchema-D6iRguNV.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +26 -29
- package/plugins/CLAUDE.md +6 -6
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +23 -19
- package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
- package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
- package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
- package/scripts/README.md +3 -11
- package/scripts/audit-ops-hygiene.cjs +3 -3
- package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
- package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
- package/scripts/mint-epic.cjs +2 -3
- package/scripts/scheduler-mcp-server.cjs +36 -7
- package/src/main/agentLibrary.cjs +22 -2
- package/src/main/build-info.json +4 -4
- package/src/main/config.cjs +41 -38
- package/src/main/docEdit.cjs +4 -1
- package/src/main/files.cjs +2 -5
- package/src/main/health.cjs +1 -1
- package/src/main/index.cjs +23 -10
- package/src/main/ipcSchemas.cjs +28 -35
- package/src/main/lib/agentPersonaSchema.cjs +13 -2
- package/src/main/lib/atomicFs.cjs +116 -0
- package/src/main/lib/branchSweep.cjs +13 -12
- package/src/main/lib/buildTarget.cjs +3 -3
- package/src/main/lib/claudeCliCaps.cjs +129 -0
- package/src/main/lib/credentials.cjs +2 -4
- package/src/main/lib/crossProjectFeedback.cjs +3 -3
- package/src/main/lib/cwdClassify.cjs +15 -3
- package/src/main/lib/definitionOfDone.cjs +207 -125
- package/src/main/lib/dodDrainHook.cjs +11 -5
- package/src/main/lib/epicMint.cjs +2 -4
- package/src/main/lib/epicStatusMirror.cjs +8 -10
- package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
- package/src/main/lib/gateAuthority.cjs +96 -0
- package/src/main/lib/gitExec.cjs +69 -0
- package/src/main/lib/gitWorktree.cjs +656 -92
- package/src/main/lib/instanceLock.cjs +5 -8
- package/src/main/lib/launchFailure.cjs +2 -3
- package/src/main/lib/macroLibrary.cjs +386 -0
- package/src/main/lib/mcpToolCatalog.cjs +26 -17
- package/src/main/lib/needsReviewLedger.cjs +1 -1
- package/src/main/lib/opsErrorLog.cjs +11 -1
- package/src/main/lib/opsOwnership.cjs +0 -5
- package/src/main/lib/pidAlive.cjs +32 -0
- package/src/main/lib/prdCreate.cjs +153 -19
- package/src/main/lib/prdDisposition.cjs +2 -2
- package/src/main/lib/prdGateFiles.cjs +284 -0
- package/src/main/lib/prdLocations.cjs +61 -0
- package/src/main/lib/prdMigration.cjs +2 -3
- package/src/main/lib/prdSizing.cjs +2 -1
- package/src/main/lib/promptSessionSchema.cjs +10 -2
- package/src/main/lib/rcaReport.cjs +4 -6
- package/src/main/lib/reaperHelpers.cjs +8 -1
- package/src/main/lib/reservationExpiry.cjs +6 -1
- package/src/main/lib/reviewNotice.cjs +263 -0
- package/src/main/lib/runClaudeP.cjs +4 -1
- package/src/main/lib/schedulerPaths.cjs +22 -2
- package/src/main/lib/sessionSlots.cjs +2 -4
- package/src/main/lib/shippedPersonaSeeds.cjs +179 -0
- package/src/main/lib/timeoutShim.cjs +123 -0
- package/src/main/lib/timeoutShimScript.cjs +275 -0
- package/src/main/lib/upgradeDrain.cjs +4 -10
- package/src/main/lib/watchdogHelpers.cjs +33 -30
- package/src/main/lib/workTypeLibrary.cjs +7 -1
- package/src/main/promptSessionEvents.cjs +37 -13
- package/src/main/queueOps.cjs +17 -6
- package/src/main/scheduler.cjs +975 -203
- package/src/main/seedAgentPersonas.cjs +278 -23
- package/src/main/supervisor.cjs +4 -1
- package/src/main/templates/PRD_AUTHORING.md +126 -406
- package/src/preload/__tests__/preload-surface.test.cjs +68 -0
- package/src/preload/api.d.ts +41 -94
- package/src/preload/index.cjs +14 -9
- package/src/seed/agents/architect.md +1 -0
- package/src/seed/agents/dev-lead.md +17 -18
- package/src/seed/agents/project-home-builder.md +1 -0
- package/src/seed/agents/validator.md +10 -4
- package/dist/assets/HostBilko-nESrTRIg.js +0 -1
- package/dist/assets/Scheduler-BVYTeh39.js +0 -16
- package/dist/assets/Settings-DIhGgdfs.js +0 -3
- package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
- package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
- package/src/main/__tests__/agentLibrary.test.cjs +0 -276
- package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
- package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
- package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
- package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
- package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
- package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
- package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
- package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
- package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
- package/src/main/__tests__/chat-queue.test.cjs +0 -97
- package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
- package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
- package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
- package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
- package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
- package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
- package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
- package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
- package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
- package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
- package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
- package/src/main/__tests__/docEdit.test.cjs +0 -244
- package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
- package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
- package/src/main/__tests__/dod-report.test.cjs +0 -304
- package/src/main/__tests__/dod-reverify.test.cjs +0 -285
- package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
- package/src/main/__tests__/epicMint.test.cjs +0 -332
- package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
- package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
- package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
- package/src/main/__tests__/exchanges.test.cjs +0 -122
- package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
- package/src/main/__tests__/extractJson.test.cjs +0 -51
- package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
- package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
- package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
- package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
- package/src/main/__tests__/health-credentials.test.cjs +0 -81
- package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
- package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
- package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
- package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
- package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
- package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
- package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
- package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
- package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
- package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
- package/src/main/__tests__/historyDashboard.test.cjs +0 -163
- package/src/main/__tests__/historyRollup.test.cjs +0 -333
- package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
- package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
- package/src/main/__tests__/kg-augment.test.cjs +0 -195
- package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
- package/src/main/__tests__/machineProfile.test.cjs +0 -152
- package/src/main/__tests__/mcpStatus.test.cjs +0 -61
- package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
- package/src/main/__tests__/memoryStale.test.cjs +0 -88
- package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
- package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
- package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
- package/src/main/__tests__/personaMerge.test.cjs +0 -169
- package/src/main/__tests__/planValidator.test.cjs +0 -125
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
- package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
- package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
- package/src/main/__tests__/prdAgentType.test.cjs +0 -103
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
- package/src/main/__tests__/prdCreate.test.cjs +0 -979
- package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
- package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
- package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
- package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
- package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
- package/src/main/__tests__/prdLocations.test.cjs +0 -195
- package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
- package/src/main/__tests__/prdMigration.test.cjs +0 -349
- package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
- package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
- package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
- package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
- package/src/main/__tests__/prdSizing.test.cjs +0 -106
- package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
- package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
- package/src/main/__tests__/proc-role-env.test.cjs +0 -125
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
- package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
- package/src/main/__tests__/projectPages.test.cjs +0 -137
- package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
- package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
- package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
- package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
- package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
- package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
- package/src/main/__tests__/pty-write-result.test.cjs +0 -46
- package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
- package/src/main/__tests__/queueHistory.test.cjs +0 -355
- package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
- package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
- package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -154
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
- package/src/main/__tests__/rcaReport.test.cjs +0 -266
- package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
- package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
- package/src/main/__tests__/runLogRetention.test.cjs +0 -489
- package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
- package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
- package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
- package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
- package/src/main/__tests__/runVerify.test.cjs +0 -1784
- package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
- package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
- package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
- package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
- package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
- package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
- package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
- package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
- package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
- package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
- package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
- package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
- package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
- package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
- package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
- package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
- package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
- package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
- package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
- package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
- package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
- package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
- package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
- package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
- package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
- package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
- package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
- package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
- package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
- package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
- package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
- package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
- package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
- package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
- package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
- package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
- package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
- package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
- package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
- package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
- package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
- package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
- package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
- package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
- package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
- package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
- package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
- package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
- package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
- package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
- package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
- package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
- package/src/main/__tests__/seedStatus.test.cjs +0 -100
- package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
- package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
- package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
- package/src/main/__tests__/telemetryContract.test.cjs +0 -930
- package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
- package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
- package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
- package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
- package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
- package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
- package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
- package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
- package/src/main/__tests__/validationSentinels.test.cjs +0 -84
- package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
- package/src/main/bilkoHost.cjs +0 -314
- package/src/main/bilkoHostCore.cjs +0 -89
- package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
- package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
- package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
- package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
- package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
- package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
- package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
- package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
- package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
- package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
- package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
- package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
- package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
- package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
- package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
- package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
- package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
- package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
- package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
- package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
- package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
- package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
- package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
- package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
- package/src/main/lib/__tests__/gateFixtures.json +0 -20
- package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
- package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
- package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
- package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
- package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
- package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
- package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
- package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
- package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
- package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
- package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
- package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
- package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
- package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
- package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
- package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
- package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
- package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
- package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
- package/src/main/lib/__tests__/procName.test.cjs +0 -92
- package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
- package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
- package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
- package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
- package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
- package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
- package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
- package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
- package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
- package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
- package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
- package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
- package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
- package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
- package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
- package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
- package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
- package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
- package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
- package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
- package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -375
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
- package/src/main/lib/kgExchangePairing.cjs +0 -75
|
@@ -4,40 +4,81 @@
|
|
|
4
4
|
* On a fresh install `~/.claude/agents/` is empty, so the Agent Library has
|
|
5
5
|
* nothing to offer — no persona to select for an Epic's Actor. This seeder
|
|
6
6
|
* copies the personas bundled at `src/seed/agents/*.md` into `~/.claude/agents/`,
|
|
7
|
-
* one file per persona
|
|
8
|
-
*
|
|
7
|
+
* one file per persona.
|
|
8
|
+
*
|
|
9
|
+
* Two independent passes run on every call, in this order:
|
|
10
|
+
*
|
|
11
|
+
* 1. UPGRADE PASS (upgradeStalePersonas): compares an installed persona's
|
|
12
|
+
* `seedVersion` frontmatter stamp (missing counts as 1) against the
|
|
13
|
+
* bundled file's own stamp. Same or higher stays untouched. Lower checks
|
|
14
|
+
* the installed file's body against every body this app has ever shipped
|
|
15
|
+
* for that persona (`lib/shippedPersonaSeeds.cjs`). No match means a
|
|
16
|
+
* person wrote or edited that file; it is left alone and the skip is
|
|
17
|
+
* logged. Why: a lower stamp alone does not prove the file is a stale
|
|
18
|
+
* shipped copy — only a body match does. A match replaces the installed
|
|
19
|
+
* file with the bundled one. Frontmatter keys the user changed are kept,
|
|
20
|
+
* including a `model:` line the user removed entirely; keys the user
|
|
21
|
+
* never touched take the bundled value. Why: an upgrade should not
|
|
22
|
+
* silently undo a choice the user made in the Agent Library editor. The
|
|
23
|
+
* body always comes from the bundled file on a match — it is never a
|
|
24
|
+
* hand edit, by definition. Before any write, the installed file is
|
|
25
|
+
* backed up to
|
|
26
|
+
* `~/.claude/session-manager/persona-backups/<name>.<content-hash>.md`,
|
|
27
|
+
* one file per distinct content. Why that folder: Claude Code loads agent
|
|
28
|
+
* files from `~/.claude/agents/` recursively, so a backup kept there would
|
|
29
|
+
* load as a second agent under the same name. Why the hash name: a
|
|
30
|
+
* failing upgrade retried on the next boot reuses the same backup file
|
|
31
|
+
* instead of piling up a new one. Runs every call, independent of the
|
|
32
|
+
* marker file below and of MAX_ATTEMPTS, so a machine that finished
|
|
33
|
+
* seeding years ago still picks up a persona fix shipped in a later
|
|
34
|
+
* release.
|
|
35
|
+
* 2. FIRST-SEED PASS (the rest of this file, unchanged in spirit): NEVER
|
|
36
|
+
* overwrites a file that doesn't yet carry a bundled persona under that
|
|
37
|
+
* name — hand-edited or user-authored personas at a name outside
|
|
38
|
+
* `PERSONAS` are untouched, and even a name inside `PERSONAS` is only
|
|
39
|
+
* copied the FIRST time, never again once `seeded` records it. The two
|
|
40
|
+
* passes don't overlap: the first-seed pass only ever touches a MISSING
|
|
41
|
+
* file, the upgrade pass only ever touches a file that already EXISTS.
|
|
9
42
|
*
|
|
10
43
|
* Idempotent the same way as seedDevPlugin.cjs, but per-persona rather than
|
|
11
44
|
* whole-run: the marker (`~/.claude/session-manager/.agent-personas-seeded`)
|
|
12
45
|
* records the SET of persona names already delivered (`seeded: string[]`),
|
|
13
46
|
* not just a single `done` flag. Every boot, any name in `PERSONAS` not yet in
|
|
14
|
-
* that set gets copied (subject to the
|
|
15
|
-
* to the set. This is what lets a persona added to
|
|
16
|
-
* release reach a machine that already booted
|
|
17
|
-
* plain one-shot `done:true` would silently never
|
|
18
|
-
* would never arrive. A name already in `seeded` is
|
|
19
|
-
* on-disk file was later deleted by hand — deleting a
|
|
20
|
-
* read as an intentional opt-out, not a request to reseed
|
|
47
|
+
* that set gets copied (subject to the first-seed pass's never-overwrite
|
|
48
|
+
* guard) and added to the set. This is what lets a persona added to
|
|
49
|
+
* `PERSONAS` in a later release reach a machine that already booted
|
|
50
|
+
* successfully long ago — a plain one-shot `done:true` would silently never
|
|
51
|
+
* re-run and that persona would never arrive. A name already in `seeded` is
|
|
52
|
+
* skipped even if its on-disk file was later deleted by hand — deleting a
|
|
53
|
+
* seeded persona is read as an intentional opt-out, not a request to reseed
|
|
54
|
+
* it.
|
|
21
55
|
*
|
|
22
|
-
* A whole-run FAILURE (thrown before any per-persona
|
|
23
|
-
* e.g. the bundled source dir is missing) only
|
|
24
|
-
* retries on the next few boots (bounded by
|
|
25
|
-
* exhausted the seeder gives up
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
56
|
+
* A whole-run FAILURE in the first-seed pass (thrown before any per-persona
|
|
57
|
+
* copy could be attempted, e.g. the bundled source dir is missing) only
|
|
58
|
+
* bumps an attempt counter and retries on the next few boots (bounded by
|
|
59
|
+
* MAX_ATTEMPTS); once attempts are exhausted the seeder gives up
|
|
60
|
+
* permanently and the machine is left exactly where it was — whatever subset
|
|
61
|
+
* of `PERSONAS` it had already reached stays in `seeded`, nothing is retried
|
|
62
|
+
* automatically, and delivering the rest is a manual copy. This is a
|
|
63
|
+
* deliberate choice: an install that has already failed MAX_ATTEMPTS times
|
|
64
|
+
* has something wrong with it (e.g. a read-only home dir) that retrying
|
|
65
|
+
* forever would not fix, and endlessly retrying on every boot would cost a
|
|
66
|
+
* syscall per persona per boot for no benefit. The upgrade pass has no such
|
|
67
|
+
* limit — a per-persona failure there is logged and skipped, never counted
|
|
68
|
+
* against this budget.
|
|
32
69
|
*
|
|
33
70
|
* Fire-and-forget: called post-window from index.cjs. Errors are logged,
|
|
34
|
-
* never thrown. Kill-switch: SM_SEED_AGENT_PERSONAS_DISABLE=1
|
|
71
|
+
* never thrown. Kill-switch: SM_SEED_AGENT_PERSONAS_DISABLE=1 (gates both
|
|
72
|
+
* passes).
|
|
35
73
|
*/
|
|
36
74
|
|
|
37
75
|
const fs = require('node:fs');
|
|
38
76
|
const path = require('node:path');
|
|
39
77
|
const os = require('node:os');
|
|
40
|
-
const
|
|
78
|
+
const crypto = require('node:crypto');
|
|
79
|
+
const { writeJsonSync, writeTextAtomic, validatePath, validateWrite } = require('./config.cjs');
|
|
80
|
+
const { splitFrontmatter } = require('./lib/prdFrontmatter.cjs');
|
|
81
|
+
const { SHIPPED_PERSONA_SEEDS } = require('./lib/shippedPersonaSeeds.cjs');
|
|
41
82
|
|
|
42
83
|
const PERSONAS = ['architect', 'dev-lead', 'project-home-builder', 'validator'];
|
|
43
84
|
// What `{ done: true }` (the pre-seeded-set marker format) meant: only these two personas existed
|
|
@@ -94,8 +135,211 @@ function writeMarker(state) {
|
|
|
94
135
|
}
|
|
95
136
|
}
|
|
96
137
|
|
|
97
|
-
|
|
138
|
+
/** `\r\n` and lone `\r` become `\n` — every parser in this file reads text through this first, so a CRLF-saved persona file still parses. */
|
|
139
|
+
function normalizeNewlines(text) {
|
|
140
|
+
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** sha256 hex of a persona's body only (frontmatter stripped, newlines normalized, trimmed) — the key the upgrade pass matches against `lib/shippedPersonaSeeds.cjs`. */
|
|
144
|
+
function personaBodyHash(text) {
|
|
145
|
+
const { body } = splitFrontmatter(normalizeNewlines(text));
|
|
146
|
+
return crypto.createHash('sha256').update(body.trim()).digest('hex');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** A persona file's `seedVersion` frontmatter stamp as a number. Missing, empty, non-integer, or below 1 all count as 1 — the version every file shipped at before this field existed. (An empty `seedVersion:` line parses to `''`; `Number('')` is 0, not 1 — handled explicitly below.) */
|
|
150
|
+
function readSeedVersion(text) {
|
|
151
|
+
const raw = splitFrontmatter(normalizeNewlines(text)).fm.seedVersion;
|
|
152
|
+
if (raw === undefined || raw === '') return 1;
|
|
153
|
+
const n = Number(raw);
|
|
154
|
+
if (!Number.isInteger(n) || n < 1) return 1;
|
|
155
|
+
return n;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Whether two frontmatter values for the same `key` are the same choice.
|
|
160
|
+
* Both `undefined` (neither file has the key) counts as same; only one
|
|
161
|
+
* `undefined` never does. `tools`/`tags`/`projects` are comma lists — compare
|
|
162
|
+
* them split on `,`, trimmed, emptied of blank items, and rejoined, so
|
|
163
|
+
* `"a, b"` and `"a,b"` are the same list. Every other key compares as a
|
|
164
|
+
* trimmed string.
|
|
165
|
+
*/
|
|
166
|
+
function sameFmValue(a, b, key) {
|
|
167
|
+
if (a === undefined && b === undefined) return true;
|
|
168
|
+
if (a === undefined || b === undefined) return false;
|
|
169
|
+
if (key === 'tools' || key === 'tags' || key === 'projects') {
|
|
170
|
+
const normalizeList = (v) => v.split(',').map((s) => s.trim()).filter((s) => s.length > 0).join(', ');
|
|
171
|
+
return normalizeList(a) === normalizeList(b);
|
|
172
|
+
}
|
|
173
|
+
return a.trim() === b.trim();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Where a stale persona's pre-upgrade content is kept — never inside `~/.claude/agents`, so Claude Code (which loads every file there recursively) never loads a backup as a second agent. */
|
|
177
|
+
function personaBackupsDir() {
|
|
178
|
+
return path.join(os.homedir(), '.claude', 'session-manager', 'persona-backups');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Content-addressed backup path for `name`'s installed file: the first 12 hex chars of the sha256 of its exact (pre-normalization) bytes. Same content always maps to the same path, so retrying a failing upgrade on every boot can never pile up backups. */
|
|
182
|
+
function backupPathFor(name, installedText) {
|
|
183
|
+
const sha12 = crypto.createHash('sha256').update(installedText).digest('hex').slice(0, 12);
|
|
184
|
+
return path.join(personaBackupsDir(), `${name}.${sha12}.md`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** `text`'s frontmatter block split into individual lines, fences excluded — `[]` when `text` has no parseable frontmatter fence. */
|
|
188
|
+
function frontmatterLines(text) {
|
|
189
|
+
if (!text.startsWith('---\n')) return [];
|
|
190
|
+
const end = text.indexOf('\n---', 4);
|
|
191
|
+
if (end === -1) return [];
|
|
192
|
+
return text.slice(4, end).split('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** The frontmatter key a single frontmatter line defines (same key shape `splitFrontmatter` matches), or `null` for a blank/comment/malformed line. */
|
|
196
|
+
function keyOfLine(line) {
|
|
197
|
+
const m = line.match(/^([A-Za-z][A-Za-z0-9_]*)\s*:\s*(.*)\s*$/);
|
|
198
|
+
return m ? m[1] : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The text to write when upgrading an installed persona to the bundled one,
|
|
203
|
+
* given `matches` — the non-empty list of `shippedPersonaSeeds.cjs` entries
|
|
204
|
+
* whose body matches the installed file (the caller only calls this once it
|
|
205
|
+
* has proven that). The body always comes from `bundledText`, untouched —
|
|
206
|
+
* on a match, by definition, nothing about the body is a hand edit.
|
|
207
|
+
*
|
|
208
|
+
* Per frontmatter key, except `name` and `seedVersion` (never diffed):
|
|
209
|
+
* the key is "user-changed" when NO entry in `matches` has the same value
|
|
210
|
+
* (`sameFmValue`) for it as the installed file — i.e. the installed value
|
|
211
|
+
* cannot be explained as some version of the shipped default, so it must be
|
|
212
|
+
* a deliberate choice.
|
|
213
|
+
* - Not user-changed: the bundled line wins (or bundled absence wins).
|
|
214
|
+
* - User-changed, installed file still has the key: the installed file's
|
|
215
|
+
* OWN line for that key is used verbatim — never rebuilt from the parsed
|
|
216
|
+
* value, so a value containing regex-replacement-special text like `$&`
|
|
217
|
+
* or `$1` is reproduced exactly. This never calls `String.prototype.replace`
|
|
218
|
+
* with a replacement string built from file content.
|
|
219
|
+
* - User-changed, installed file lacks the key: the key is dropped — the
|
|
220
|
+
* user's removal (meaning "inherit the app default") survives.
|
|
221
|
+
* Output always uses `\n` line endings, regardless of either input's.
|
|
222
|
+
*/
|
|
223
|
+
function buildUpgradedPersona(bundledText, installedText, matches) {
|
|
224
|
+
const normBundled = normalizeNewlines(bundledText);
|
|
225
|
+
if (matches.length === 0 || !normBundled.startsWith('---\n')) return bundledText; // should never happen — caller only calls this on a proven match; defensive only
|
|
226
|
+
const normInstalled = normalizeNewlines(installedText);
|
|
227
|
+
|
|
228
|
+
const { fm: bundledFm, body } = splitFrontmatter(normBundled);
|
|
229
|
+
const { fm: installedFm } = splitFrontmatter(normInstalled);
|
|
230
|
+
|
|
231
|
+
const installedLineByKey = new Map();
|
|
232
|
+
for (const line of frontmatterLines(normInstalled)) {
|
|
233
|
+
const key = keyOfLine(line);
|
|
234
|
+
if (key) installedLineByKey.set(key, line);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const allKeys = new Set([...Object.keys(bundledFm), ...Object.keys(installedFm)]);
|
|
238
|
+
allKeys.delete('name');
|
|
239
|
+
allKeys.delete('seedVersion');
|
|
240
|
+
|
|
241
|
+
const userChanged = new Set();
|
|
242
|
+
for (const key of allKeys) {
|
|
243
|
+
if (!matches.some((entry) => sameFmValue(entry.fm[key], installedFm[key], key))) userChanged.add(key);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const nextLines = [];
|
|
247
|
+
const emitted = new Set();
|
|
248
|
+
for (const line of frontmatterLines(normBundled)) {
|
|
249
|
+
const key = keyOfLine(line);
|
|
250
|
+
if (!key || !userChanged.has(key)) {
|
|
251
|
+
nextLines.push(line); // not user-changed (or not a key line at all) — bundled wins verbatim
|
|
252
|
+
if (key) emitted.add(key);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const installedLine = installedLineByKey.get(key);
|
|
256
|
+
if (installedLine !== undefined) nextLines.push(installedLine); // user-changed, installed kept it — use the user's own line
|
|
257
|
+
emitted.add(key); // else: user removed this key — drop the bundled line entirely
|
|
258
|
+
}
|
|
259
|
+
for (const key of userChanged) {
|
|
260
|
+
if (emitted.has(key)) continue; // a user-changed key the bundled file never had — append it
|
|
261
|
+
const installedLine = installedLineByKey.get(key);
|
|
262
|
+
if (installedLine !== undefined) nextLines.push(installedLine);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return `---\n${nextLines.join('\n')}\n---\n${body}`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Replaces a stale installed persona with the current bundled one, once per
|
|
270
|
+
* boot, for every name in `PERSONAS` whose installed file is both PRESENT
|
|
271
|
+
* and behind the bundled `seedVersion` AND whose body matches a body this
|
|
272
|
+
* app has shipped before (`shippedSeeds`, normally `lib/shippedPersonaSeeds.cjs`
|
|
273
|
+
* — injectable so tests can supply a fake list). See this file's header for
|
|
274
|
+
* the full rule, and `buildUpgradedPersona` for how frontmatter keys merge.
|
|
275
|
+
* Never overwrites a same-or-newer installed file, or one whose body never
|
|
276
|
+
* shipped from this app; never throws (a per-persona failure is logged and
|
|
277
|
+
* skipped, the loop continues). Independent of the marker/MAX_ATTEMPTS
|
|
278
|
+
* machinery the first-seed pass below uses — the caller gates both passes
|
|
279
|
+
* on the kill switch only.
|
|
280
|
+
*/
|
|
281
|
+
async function upgradeStalePersonas({ logger = console, writeLog = () => {}, shippedSeeds = SHIPPED_PERSONA_SEEDS } = {}) {
|
|
282
|
+
const srcDir = bundledSourceDir();
|
|
283
|
+
const destDir = destAgentsDir();
|
|
284
|
+
for (const name of PERSONAS) {
|
|
285
|
+
const destPath = path.join(destDir, `${name}.md`);
|
|
286
|
+
if (!fs.existsSync(destPath)) continue; // nothing installed yet — the first-seed pass below owns this name
|
|
287
|
+
try {
|
|
288
|
+
const bundledText = fs.readFileSync(path.join(srcDir, `${name}.md`), 'utf8');
|
|
289
|
+
const installedText = fs.readFileSync(destPath, 'utf8');
|
|
290
|
+
const bundledVersion = readSeedVersion(bundledText);
|
|
291
|
+
const installedVersion = readSeedVersion(installedText);
|
|
292
|
+
if (installedVersion >= bundledVersion) continue; // same or newer — never touched
|
|
293
|
+
|
|
294
|
+
const installedHash = personaBodyHash(installedText);
|
|
295
|
+
const matches = (shippedSeeds[name] ?? []).filter((entry) => entry.bodySha256 === installedHash);
|
|
296
|
+
if (matches.length === 0) {
|
|
297
|
+
// Lower seedVersion alone doesn't prove this is a stale shipped copy — only a body match does.
|
|
298
|
+
// No match means a person wrote or hand-edited this file; leave it alone.
|
|
299
|
+
logger.log?.(`[seedAgentPersonas] kept ${name}.md: its text differs from every shipped version, so the seedVersion ${bundledVersion} upgrade was skipped.`);
|
|
300
|
+
writeLog({
|
|
301
|
+
scope: 'seed-agent-personas',
|
|
302
|
+
level: 'info',
|
|
303
|
+
message: 'persona upgrade skipped: local edits',
|
|
304
|
+
meta: { name, installedVersion, bundledVersion },
|
|
305
|
+
});
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Confirm the write can succeed before any write happens — a doomed
|
|
310
|
+
// write (e.g. ~/.claude/agents symlinked outside the allowed roots)
|
|
311
|
+
// must never leave an orphan backup behind. Throws straight into the
|
|
312
|
+
// catch below on failure.
|
|
313
|
+
validateWrite(validatePath(destPath));
|
|
314
|
+
|
|
315
|
+
const backupPath = backupPathFor(name, installedText);
|
|
316
|
+
if (!fs.existsSync(backupPath)) {
|
|
317
|
+
fs.mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
318
|
+
await writeTextAtomic(backupPath, installedText);
|
|
319
|
+
} // same content already backed up by an earlier attempt — never write a second copy
|
|
320
|
+
|
|
321
|
+
const nextText = buildUpgradedPersona(bundledText, installedText, matches);
|
|
322
|
+
await writeTextAtomic(destPath, nextText);
|
|
323
|
+
logger.log?.(`[seedAgentPersonas] upgraded ${name}.md to seedVersion ${bundledVersion}. Backup saved at ${backupPath}.`);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
logger.warn?.('[seedAgentPersonas] upgrade error for', name, ':', err?.message ?? err);
|
|
326
|
+
writeLog({
|
|
327
|
+
scope: 'seed-agent-personas',
|
|
328
|
+
level: 'error',
|
|
329
|
+
message: 'persona upgrade error',
|
|
330
|
+
meta: { name, error: err?.message ?? String(err) },
|
|
331
|
+
});
|
|
332
|
+
// Never throws — a stale persona that fails to upgrade just stays stale; the next boot tries again.
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function seedAgentPersonas({ logger = console, writeLog = () => {}, shippedSeeds = SHIPPED_PERSONA_SEEDS } = {}) {
|
|
98
338
|
if (process.env.SM_SEED_AGENT_PERSONAS_DISABLE === '1') return;
|
|
339
|
+
|
|
340
|
+
// Every boot, regardless of marker state — see this file's header.
|
|
341
|
+
await upgradeStalePersonas({ logger, writeLog, shippedSeeds });
|
|
342
|
+
|
|
99
343
|
const marker = readMarker();
|
|
100
344
|
const pending = PERSONAS.filter((name) => !marker.seeded.includes(name));
|
|
101
345
|
if (pending.length === 0) return; // every known persona already delivered.
|
|
@@ -130,4 +374,15 @@ async function seedAgentPersonas({ logger = console, writeLog = () => {} } = {})
|
|
|
130
374
|
}
|
|
131
375
|
}
|
|
132
376
|
|
|
133
|
-
module.exports = {
|
|
377
|
+
module.exports = {
|
|
378
|
+
seedAgentPersonas,
|
|
379
|
+
upgradeStalePersonas,
|
|
380
|
+
buildUpgradedPersona,
|
|
381
|
+
personaBodyHash,
|
|
382
|
+
readSeedVersion,
|
|
383
|
+
normalizeNewlines,
|
|
384
|
+
sameFmValue,
|
|
385
|
+
markerPath,
|
|
386
|
+
MAX_ATTEMPTS,
|
|
387
|
+
PERSONAS,
|
|
388
|
+
};
|
package/src/main/supervisor.cjs
CHANGED
|
@@ -17,6 +17,7 @@ const os = require('node:os');
|
|
|
17
17
|
const { spawn, execFileSync } = require('node:child_process');
|
|
18
18
|
const { ipcMain } = require('electron');
|
|
19
19
|
const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
|
|
20
|
+
const { headlessPermissionArgs, ensureCliCapsProbed } = require('./lib/claudeCliCaps.cjs');
|
|
20
21
|
const { withProcRole } = require('./lib/cleanEnv.cjs');
|
|
21
22
|
const schedulerPaths = require('./lib/schedulerPaths.cjs');
|
|
22
23
|
|
|
@@ -204,7 +205,8 @@ Decision rules:
|
|
|
204
205
|
- "stuck"/kill-agent only if the agent itself is wedged (no child processes, no recent log events, > 60 min stale). Set targetPid to the claude root pid.`;
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
function runProbe(claudeBin, prompt) {
|
|
208
|
+
async function runProbe(claudeBin, prompt) {
|
|
209
|
+
await ensureCliCapsProbed();
|
|
208
210
|
return new Promise((resolve) => {
|
|
209
211
|
let child;
|
|
210
212
|
try {
|
|
@@ -216,6 +218,7 @@ function runProbe(claudeBin, prompt) {
|
|
|
216
218
|
'--output-format', 'json',
|
|
217
219
|
'--max-budget-usd', '0.10',
|
|
218
220
|
'--dangerously-skip-permissions',
|
|
221
|
+
...headlessPermissionArgs(),
|
|
219
222
|
'--allowedTools', 'Bash',
|
|
220
223
|
], { env: withProcRole(process.env, 'aux'), stdio: ['ignore', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
|
|
221
224
|
} catch (e) {
|