claude-code-session-manager 0.98.0 → 0.101.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-CXaH2GED.js +1 -0
- package/dist/assets/{History-Bfdoywlh.js → History-Cerii-HA.js} +2 -2
- package/dist/assets/{Hooks-CKDJjTJV.js → Hooks-aXMb5LDy.js} +3 -3
- package/dist/assets/{Library-7akfgy67.js → Library-WrJFKEOJ.js} +1 -1
- package/dist/assets/{MarkdownEditor-DDesiqet.js → MarkdownEditor-D1BMCXS7.js} +1 -1
- package/dist/assets/{McpServers-D4lZtXof.js → McpServers-Cf0davLd.js} +2 -2
- package/dist/assets/{Memory-CeI8G1w5.js → Memory-Dn6NHWPe.js} +6 -6
- package/dist/assets/Permissions-CTVuFPjg.js +3 -0
- package/dist/assets/{Plugins-P4eCXy_S.js → Plugins-C2TyF8Mn.js} +2 -2
- package/dist/assets/ProvenanceBadge-F_jwHSwE.js +1 -0
- package/dist/assets/{SaveBar-l9jnEMES.js → SaveBar-DA1KVJST.js} +1 -1
- package/dist/assets/Scheduler-Bm6Q_hlu.js +16 -0
- package/dist/assets/{ScopeSwitcher-CjJi8ST5.js → ScopeSwitcher-BygZlJca.js} +1 -1
- package/dist/assets/Settings-BYCiv5KV.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CjbO01Sg.js → SkillReferenceGraph-CcLnl9np.js} +1 -1
- package/dist/assets/{Skills-BxZ9_fCR.js → Skills-ChffsdTD.js} +2 -2
- package/dist/assets/{SystemPrompt-D8vfFb8b.js → SystemPrompt-dNkfHE_M.js} +1 -1
- package/dist/assets/{TagLibrary-2vzUo7YL.js → TagLibrary-jVlEuB0m.js} +1 -1
- package/dist/assets/{TiptapBody-B8ZfbK90.js → TiptapBody-6_cdwcP0.js} +1 -1
- package/dist/assets/{Toggle-BbW4nGGf.js → Toggle-CVXqGpAP.js} +1 -1
- package/dist/assets/ViewTabs-DO1YVbpc.js +1 -0
- package/dist/assets/{index-CKH5Uxik.css → index-D89EUizB.css} +1 -1
- package/dist/assets/{index-Cd98Q1lD.js → index-DbCQPWWY.js} +747 -752
- package/dist/assets/{settingsSchema-IhUfucCF.js → settingsSchema-Wrvn-Yz1.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +25 -29
- package/plugins/CLAUDE.md +6 -6
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +21 -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 +64 -7
- package/src/main/agentLibrary.cjs +22 -2
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +4 -3
- 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 +40 -11
- package/src/main/ipcSchemas.cjs +35 -34
- 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/builtinMacros.cjs +39 -0
- 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 +480 -0
- package/src/main/lib/mcpToolCatalog.cjs +49 -17
- package/src/main/lib/needsReviewLedger.cjs +1 -1
- package/src/main/lib/opsErrorLog.cjs +11 -1
- package/src/main/lib/opsOwnership.cjs +5 -9
- package/src/main/lib/pidAlive.cjs +32 -0
- package/src/main/lib/prdCreate.cjs +121 -12
- 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/projectHomeAdminRoutes.cjs +136 -21
- 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 +226 -0
- package/src/main/lib/smfileHeaders.cjs +66 -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 +12 -25
- package/src/main/lib/workTypeLibrary.cjs +7 -1
- package/src/main/projectPages.cjs +43 -13
- package/src/main/promptSessionEvents.cjs +37 -13
- package/src/main/scheduler.cjs +910 -198
- package/src/main/seedAgentPersonas.cjs +279 -24
- 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 +53 -94
- package/src/preload/index.cjs +14 -9
- package/src/seed/agents/architect.md +2 -0
- package/src/seed/agents/demo-video-builder.md +84 -0
- package/src/seed/agents/dev-lead.md +18 -18
- package/src/seed/agents/project-home-builder.md +66 -35
- package/src/seed/agents/validator.md +10 -4
- package/dist/assets/DataModel-DCEgMTjH.js +0 -1
- package/dist/assets/HostBilko-CDVHE0hT.js +0 -1
- package/dist/assets/Permissions-BGleLja4.js +0 -3
- package/dist/assets/ProvenanceBadge-BFczzI1B.js +0 -1
- package/dist/assets/Scheduler-C_phpeUS.js +0 -16
- package/dist/assets/Settings-BIwZ3FGQ.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__/prdCreateAdoption.test.cjs +0 -205
- 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 -211
- 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-mutate-reentrancy.test.cjs +0 -117
- 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 -441
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
- package/src/main/lib/kgExchangePairing.cjs +0 -75
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agentLibrary.test.cjs — unit tests for the Agent Library nav page's
|
|
3
|
-
* backend: enumerating global `~/.claude/agents/*.md` personas and
|
|
4
|
-
* detecting per-project `.claude/agents/<name>.md` overlays among
|
|
5
|
-
* currently-open tabs.
|
|
6
|
-
*
|
|
7
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/agentLibrary.test.cjs
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { test, expect, afterEach } from 'vitest';
|
|
11
|
-
const fs = require('node:fs');
|
|
12
|
-
const fsp = require('node:fs/promises');
|
|
13
|
-
const os = require('node:os');
|
|
14
|
-
const path = require('node:path');
|
|
15
|
-
const { listPersonas, openProjects, parseTools, savePersona, deletePersona, removeOverride } = require('../agentLibrary.cjs');
|
|
16
|
-
|
|
17
|
-
const tmpDirs = [];
|
|
18
|
-
afterEach(async () => {
|
|
19
|
-
while (tmpDirs.length) {
|
|
20
|
-
const d = tmpDirs.pop();
|
|
21
|
-
await fsp.rm(d, { recursive: true, force: true });
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
async function mkTmp(prefix) {
|
|
26
|
-
const d = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
27
|
-
tmpDirs.push(d);
|
|
28
|
-
return d;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// listPersonas/openProjects take injectable deps; tests pass an identity
|
|
32
|
-
// validatePath since the fixture dirs live under os.tmpdir(), not the real
|
|
33
|
-
// home directory config.cjs's validatePath would otherwise enforce.
|
|
34
|
-
const identityValidatePath = (p) => p;
|
|
35
|
-
|
|
36
|
-
// savePersona/deletePersona/removeOverride take an injectable writeTextAtomic
|
|
37
|
-
// too, since the real config.cjs one enforces its own home-dir validatePath
|
|
38
|
-
// regardless of what's injected for the `validatePath` param here.
|
|
39
|
-
async function fakeWriteTextAtomic(abs, text) {
|
|
40
|
-
await fsp.mkdir(path.dirname(abs), { recursive: true });
|
|
41
|
-
await fsp.writeFile(abs, text, 'utf8');
|
|
42
|
-
return { ok: true, mtimeMs: Date.now() };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
test('parseTools splits + trims a comma-separated tools frontmatter value', () => {
|
|
46
|
-
expect(parseTools('Read, Grep, Glob, Bash')).toEqual(['Read', 'Grep', 'Glob', 'Bash']);
|
|
47
|
-
expect(parseTools('')).toEqual([]);
|
|
48
|
-
expect(parseTools(undefined)).toEqual([]);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('openProjects dedups by cwd and uses the last path segment as name', async () => {
|
|
52
|
-
const loadSessions = async () => ({
|
|
53
|
-
tabs: [
|
|
54
|
-
{ cwd: '/home/u/Projects/alpha' },
|
|
55
|
-
{ cwd: '/home/u/Projects/beta/' },
|
|
56
|
-
{ cwd: '/home/u/Projects/alpha' }, // duplicate — should collapse
|
|
57
|
-
{ cwd: null }, // malformed — should be skipped
|
|
58
|
-
],
|
|
59
|
-
});
|
|
60
|
-
const projects = await openProjects({ loadSessions });
|
|
61
|
-
expect(projects).toEqual([
|
|
62
|
-
{ cwd: '/home/u/Projects/alpha', name: 'alpha' },
|
|
63
|
-
{ cwd: '/home/u/Projects/beta/', name: 'beta' },
|
|
64
|
-
]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('listPersonas returns [] when the global agents dir does not exist', async () => {
|
|
68
|
-
const missingDir = path.join(os.tmpdir(), 'sm-agent-library-does-not-exist-' + Date.now());
|
|
69
|
-
const personas = await listPersonas({
|
|
70
|
-
globalDir: missingDir,
|
|
71
|
-
loadSessions: async () => ({ tabs: [] }),
|
|
72
|
-
validatePath: identityValidatePath,
|
|
73
|
-
});
|
|
74
|
-
expect(personas).toEqual([]);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('listPersonas parses frontmatter and reports overridingProjects for open tabs with a local overlay', async () => {
|
|
78
|
-
const globalDir = await mkTmp('sm-agent-library-global-');
|
|
79
|
-
await fsp.writeFile(
|
|
80
|
-
path.join(globalDir, 'builder.md'),
|
|
81
|
-
[
|
|
82
|
-
'---',
|
|
83
|
-
'name: builder',
|
|
84
|
-
'description: Watch git history and drive the next publish.',
|
|
85
|
-
'tools: Read, Grep, Glob, Bash',
|
|
86
|
-
'tags: feature, bug',
|
|
87
|
-
'---',
|
|
88
|
-
'',
|
|
89
|
-
'You are the Builder agent.',
|
|
90
|
-
'',
|
|
91
|
-
].join('\n'),
|
|
92
|
-
);
|
|
93
|
-
await fsp.writeFile(
|
|
94
|
-
path.join(globalDir, 'debugger.md'),
|
|
95
|
-
['---', 'name: debugger', 'description: Diagnose a failing test.', '---', '', 'Body.', ''].join('\n'),
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const projectWithOverlay = await mkTmp('sm-agent-library-project-a-');
|
|
99
|
-
await fsp.mkdir(path.join(projectWithOverlay, '.claude', 'agents'), { recursive: true });
|
|
100
|
-
await fsp.writeFile(
|
|
101
|
-
path.join(projectWithOverlay, '.claude', 'agents', 'builder.md'),
|
|
102
|
-
'---\nname: builder\n---\nProject-specific overlay.\n',
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const projectWithoutOverlay = await mkTmp('sm-agent-library-project-b-');
|
|
106
|
-
|
|
107
|
-
const loadSessions = async () => ({
|
|
108
|
-
tabs: [{ cwd: projectWithOverlay }, { cwd: projectWithoutOverlay }],
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const personas = await listPersonas({ globalDir, loadSessions, validatePath: identityValidatePath });
|
|
112
|
-
expect(personas).toHaveLength(2);
|
|
113
|
-
|
|
114
|
-
const byName = Object.fromEntries(personas.map((p) => [p.name, p]));
|
|
115
|
-
expect(byName.builder.description).toBe('Watch git history and drive the next publish.');
|
|
116
|
-
expect(byName.builder.tags).toEqual(['feature', 'bug']);
|
|
117
|
-
expect(byName.debugger.tags).toEqual([]);
|
|
118
|
-
expect(byName.builder.tools).toEqual(['Read', 'Grep', 'Glob', 'Bash']);
|
|
119
|
-
expect(byName.builder.body).toContain('You are the Builder agent.');
|
|
120
|
-
expect(byName.builder.overridingProjects).toEqual([path.basename(projectWithOverlay)]);
|
|
121
|
-
|
|
122
|
-
expect(byName.debugger.overridingProjects).toEqual([]);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('listPersonas skips a project cwd that validatePath rejects, rather than throwing', async () => {
|
|
126
|
-
const globalDir = await mkTmp('sm-agent-library-global-');
|
|
127
|
-
await fsp.writeFile(path.join(globalDir, 'builder.md'), '---\nname: builder\n---\nBody.\n');
|
|
128
|
-
|
|
129
|
-
const rejectedProject = await mkTmp('sm-agent-library-project-rejected-');
|
|
130
|
-
await fsp.mkdir(path.join(rejectedProject, '.claude', 'agents'), { recursive: true });
|
|
131
|
-
await fsp.writeFile(path.join(rejectedProject, '.claude', 'agents', 'builder.md'), 'overlay\n');
|
|
132
|
-
|
|
133
|
-
const loadSessions = async () => ({ tabs: [{ cwd: rejectedProject }] });
|
|
134
|
-
// Simulate config.cjs's validatePath throwing for anything under the
|
|
135
|
-
// rejected project (as it would for a cwd outside the allowed-roots set)
|
|
136
|
-
// while still allowing the global agents dir itself to resolve.
|
|
137
|
-
const selectiveValidatePath = (p) => {
|
|
138
|
-
if (p.startsWith(rejectedProject)) throw new Error('outside allowed boundaries');
|
|
139
|
-
return p;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const personas = await listPersonas({ globalDir, loadSessions, validatePath: selectiveValidatePath });
|
|
143
|
-
expect(personas).toHaveLength(1);
|
|
144
|
-
expect(personas[0].overridingProjects).toEqual([]);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('savePersona writes frontmatter + body, and rejects a non-slug name', async () => {
|
|
148
|
-
const globalDir = await mkTmp('sm-agent-library-save-');
|
|
149
|
-
await savePersona({
|
|
150
|
-
name: 'my-agent',
|
|
151
|
-
description: 'Does a thing.',
|
|
152
|
-
tools: ['Read', 'Grep'],
|
|
153
|
-
model: 'opus',
|
|
154
|
-
color: 'blue',
|
|
155
|
-
tags: ['feature', 'bug'],
|
|
156
|
-
body: 'You are my-agent.',
|
|
157
|
-
globalDir,
|
|
158
|
-
validatePath: identityValidatePath,
|
|
159
|
-
writeTextAtomic: fakeWriteTextAtomic,
|
|
160
|
-
});
|
|
161
|
-
const text = await fsp.readFile(path.join(globalDir, 'my-agent.md'), 'utf8');
|
|
162
|
-
expect(text).toContain('name: my-agent');
|
|
163
|
-
expect(text).toContain('description: Does a thing.');
|
|
164
|
-
expect(text).toContain('tools: Read, Grep');
|
|
165
|
-
expect(text).toContain('model: opus');
|
|
166
|
-
expect(text).toContain('color: blue');
|
|
167
|
-
expect(text).toContain('tags: feature, bug');
|
|
168
|
-
expect(text).toContain('You are my-agent.');
|
|
169
|
-
|
|
170
|
-
await expect(
|
|
171
|
-
savePersona({ name: 'Not Valid', body: '', globalDir, validatePath: identityValidatePath, writeTextAtomic: fakeWriteTextAtomic }),
|
|
172
|
-
).rejects.toThrow(/lowercase, hyphenated/);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('savePersona with originalName renames — writes the new file and removes the old one', async () => {
|
|
176
|
-
const globalDir = await mkTmp('sm-agent-library-rename-');
|
|
177
|
-
await fsp.writeFile(path.join(globalDir, 'old-name.md'), '---\nname: old-name\n---\nBody.\n');
|
|
178
|
-
await savePersona({
|
|
179
|
-
name: 'new-name',
|
|
180
|
-
originalName: 'old-name',
|
|
181
|
-
description: '',
|
|
182
|
-
tools: [],
|
|
183
|
-
model: 'inherit',
|
|
184
|
-
color: '',
|
|
185
|
-
body: 'Body.',
|
|
186
|
-
globalDir,
|
|
187
|
-
validatePath: identityValidatePath,
|
|
188
|
-
writeTextAtomic: fakeWriteTextAtomic,
|
|
189
|
-
});
|
|
190
|
-
expect(fs.existsSync(path.join(globalDir, 'new-name.md'))).toBe(true);
|
|
191
|
-
expect(fs.existsSync(path.join(globalDir, 'old-name.md'))).toBe(false);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('deletePersona removes the file, and is a no-op when it is already gone', async () => {
|
|
195
|
-
const globalDir = await mkTmp('sm-agent-library-delete-');
|
|
196
|
-
await fsp.writeFile(path.join(globalDir, 'gone-soon.md'), '---\nname: gone-soon\n---\nBody.\n');
|
|
197
|
-
await deletePersona({ name: 'gone-soon', globalDir, validatePath: identityValidatePath });
|
|
198
|
-
expect(fs.existsSync(path.join(globalDir, 'gone-soon.md'))).toBe(false);
|
|
199
|
-
await expect(deletePersona({ name: 'gone-soon', globalDir, validatePath: identityValidatePath })).resolves.toEqual({ ok: true });
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test('removeOverride deletes a project overlay resolved by project name, and rejects an unknown project', async () => {
|
|
203
|
-
const project = await mkTmp('sm-agent-library-override-');
|
|
204
|
-
await fsp.mkdir(path.join(project, '.claude', 'agents'), { recursive: true });
|
|
205
|
-
const overlayPath = path.join(project, '.claude', 'agents', 'builder.md');
|
|
206
|
-
await fsp.writeFile(overlayPath, 'overlay\n');
|
|
207
|
-
const loadSessions = async () => ({ tabs: [{ cwd: project }] });
|
|
208
|
-
|
|
209
|
-
await removeOverride({ name: 'builder', projectName: path.basename(project), loadSessions, validatePath: identityValidatePath });
|
|
210
|
-
expect(fs.existsSync(overlayPath)).toBe(false);
|
|
211
|
-
|
|
212
|
-
await expect(
|
|
213
|
-
removeOverride({ name: 'builder', projectName: 'not-open', loadSessions, validatePath: identityValidatePath }),
|
|
214
|
-
).rejects.toThrow(/project not open/);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test('projects/action/actionLabel round-trip through savePersona -> listPersonas, with newlines preserved', async () => {
|
|
218
|
-
const globalDir = await mkTmp('sm-agent-library-action-');
|
|
219
|
-
const action = '/builder\n\nCheck git vs the published package and publish anything new.';
|
|
220
|
-
await savePersona({
|
|
221
|
-
name: 'builder',
|
|
222
|
-
description: 'Drives the next publish.',
|
|
223
|
-
tools: ['Bash'],
|
|
224
|
-
model: 'inherit',
|
|
225
|
-
color: '',
|
|
226
|
-
tags: ['build'],
|
|
227
|
-
projects: ['/home/bilko/Projects/alpha', '/home/bilko/Projects/beta'],
|
|
228
|
-
action,
|
|
229
|
-
actionLabel: 'Run Build',
|
|
230
|
-
body: 'Body.',
|
|
231
|
-
globalDir,
|
|
232
|
-
validatePath: identityValidatePath,
|
|
233
|
-
writeTextAtomic: fakeWriteTextAtomic,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// The frontmatter parser is line-based, so the multi-line action must be
|
|
237
|
-
// stored escaped — a raw newline would silently truncate it on read.
|
|
238
|
-
const raw = await fsp.readFile(path.join(globalDir, 'builder.md'), 'utf8');
|
|
239
|
-
expect(raw).toContain('action: /builder\\n\\nCheck git');
|
|
240
|
-
expect(raw).toContain('projects: /home/bilko/Projects/alpha, /home/bilko/Projects/beta');
|
|
241
|
-
|
|
242
|
-
const [p] = await listPersonas({ globalDir, loadSessions: async () => ({ tabs: [] }), validatePath: identityValidatePath });
|
|
243
|
-
expect(p.projects).toEqual(['/home/bilko/Projects/alpha', '/home/bilko/Projects/beta']);
|
|
244
|
-
expect(p.action).toBe(action);
|
|
245
|
-
expect(p.actionLabel).toBe('Run Build');
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test('a persona with no Action fields reports them as empty/null rather than undefined', async () => {
|
|
249
|
-
const globalDir = await mkTmp('sm-agent-library-noaction-');
|
|
250
|
-
await fsp.writeFile(path.join(globalDir, 'plain.md'), '---\nname: plain\n---\nBody.\n');
|
|
251
|
-
const [p] = await listPersonas({ globalDir, loadSessions: async () => ({ tabs: [] }), validatePath: identityValidatePath });
|
|
252
|
-
expect(p.projects).toEqual([]);
|
|
253
|
-
expect(p.action).toBeNull();
|
|
254
|
-
expect(p.actionLabel).toBeNull();
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test('effort: written when set, omitted for inherit/unset, round-trips, and untouched personas gain no line', async () => {
|
|
258
|
-
const globalDir = await mkTmp('sm-agent-library-effort-');
|
|
259
|
-
const base = { tools: [], color: '', tags: [], body: 'B.', globalDir, validatePath: identityValidatePath, writeTextAtomic: fakeWriteTextAtomic };
|
|
260
|
-
await savePersona({ ...base, name: 'with-effort', model: 'opus', effort: 'xhigh' });
|
|
261
|
-
await savePersona({ ...base, name: 'inherit-effort', model: 'opus', effort: 'inherit' });
|
|
262
|
-
await savePersona({ ...base, name: 'unset-effort', model: 'opus' });
|
|
263
|
-
const raw = (n) => fsp.readFile(path.join(globalDir, `${n}.md`), 'utf8');
|
|
264
|
-
expect(await raw('with-effort')).toContain('effort: xhigh');
|
|
265
|
-
expect(await raw('inherit-effort')).not.toContain('effort:');
|
|
266
|
-
expect(await raw('unset-effort')).not.toContain('effort:');
|
|
267
|
-
|
|
268
|
-
const ps = await listPersonas({ globalDir, loadSessions: async () => ({ tabs: [] }), validatePath: identityValidatePath });
|
|
269
|
-
const by = Object.fromEntries(ps.map((p) => [p.name, p]));
|
|
270
|
-
expect(by['with-effort'].effort).toBe('xhigh');
|
|
271
|
-
expect(by['unset-effort'].effort).toBeNull();
|
|
272
|
-
|
|
273
|
-
// Untouched save of a no-effort persona (UI maps null -> 'inherit') must not add a line.
|
|
274
|
-
await savePersona({ ...base, name: 'unset-effort', model: 'opus', effort: by['unset-effort'].effort ?? 'inherit' });
|
|
275
|
-
expect(await raw('unset-effort')).not.toContain('effort:');
|
|
276
|
-
});
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agentModelResolve.test.cjs — unit tests for resolveEpicModel: the shared
|
|
3
|
-
* persona-model resolver both EpicTerminalPane.tsx's Terminal-view launch
|
|
4
|
-
* and chatRunner.cjs's headless Chat-view launch should agree with.
|
|
5
|
-
*
|
|
6
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/agentModelResolve.test.cjs
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
'use strict';
|
|
10
|
-
|
|
11
|
-
import { test, expect, afterEach } from 'vitest';
|
|
12
|
-
const fs = require('node:fs');
|
|
13
|
-
const fsp = require('node:fs/promises');
|
|
14
|
-
const os = require('node:os');
|
|
15
|
-
const path = require('node:path');
|
|
16
|
-
const {
|
|
17
|
-
FALLBACK_MODEL,
|
|
18
|
-
resolveEpicModel,
|
|
19
|
-
findAgentTypeByClaudeSessionId,
|
|
20
|
-
readOverlayAwarePersonaModel,
|
|
21
|
-
resolvePrdPersonaForSpawn,
|
|
22
|
-
} = require('../lib/agentModelResolve.cjs');
|
|
23
|
-
|
|
24
|
-
const tmpDirs = [];
|
|
25
|
-
afterEach(async () => {
|
|
26
|
-
while (tmpDirs.length) {
|
|
27
|
-
const d = tmpDirs.pop();
|
|
28
|
-
await fsp.rm(d, { recursive: true, force: true });
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
async function mkTmpDir(prefix) {
|
|
33
|
-
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
34
|
-
tmpDirs.push(dir);
|
|
35
|
-
return dir;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function writeIndex(cwd, sessions) {
|
|
39
|
-
const dir = path.join(cwd, 'session-manager-operations', 'prompt-sessions');
|
|
40
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
-
fs.writeFileSync(path.join(dir, 'active-index.json'), JSON.stringify({ sessions, events: {} }, null, 2));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function writePersona(globalDir, name, frontmatter) {
|
|
45
|
-
fs.mkdirSync(globalDir, { recursive: true });
|
|
46
|
-
const lines = ['---', `name: ${name}`, ...frontmatter, '---', 'body'];
|
|
47
|
-
fs.writeFileSync(path.join(globalDir, `${name}.md`), lines.join('\n'));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const noopValidatePath = (p) => p;
|
|
51
|
-
|
|
52
|
-
test('resolves the agentType persona model for a matching Epic', async () => {
|
|
53
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
54
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
55
|
-
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-opus', agentType: 'opus-persona' } });
|
|
56
|
-
writePersona(globalDir, 'opus-persona', ['model: opus']);
|
|
57
|
-
|
|
58
|
-
const model = resolveEpicModel({
|
|
59
|
-
cwd,
|
|
60
|
-
claudeSessionId: 'sess-opus',
|
|
61
|
-
deps: { globalDir, validatePath: noopValidatePath },
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
expect(model).toBe('opus');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('falls back to the fallback model when the Epic has no agentType', async () => {
|
|
68
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
69
|
-
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-none' } });
|
|
70
|
-
|
|
71
|
-
const model = resolveEpicModel({ cwd, claudeSessionId: 'sess-none' });
|
|
72
|
-
|
|
73
|
-
expect(model).toBe(FALLBACK_MODEL);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("falls back when the persona's model is 'inherit'", async () => {
|
|
77
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
78
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
79
|
-
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-inherit', agentType: 'inherit-persona' } });
|
|
80
|
-
writePersona(globalDir, 'inherit-persona', ['model: inherit']);
|
|
81
|
-
|
|
82
|
-
const model = resolveEpicModel({
|
|
83
|
-
cwd,
|
|
84
|
-
claudeSessionId: 'sess-inherit',
|
|
85
|
-
deps: { globalDir, validatePath: noopValidatePath },
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
expect(model).toBe(FALLBACK_MODEL);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('falls back when no Epic matches the claudeSessionId', () => {
|
|
92
|
-
const model = resolveEpicModel({ cwd: '/nonexistent-cwd-xyz', claudeSessionId: 'no-such-session' });
|
|
93
|
-
expect(model).toBe(FALLBACK_MODEL);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('never throws when cwd/claudeSessionId are missing', () => {
|
|
97
|
-
expect(resolveEpicModel({})).toBe(FALLBACK_MODEL);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('findAgentTypeByClaudeSessionId returns null for no match', async () => {
|
|
101
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
102
|
-
writeIndex(cwd, {});
|
|
103
|
-
expect(findAgentTypeByClaudeSessionId(cwd, 'missing')).toBeNull();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('readOverlayAwarePersonaModel returns null for a persona with no model field', async () => {
|
|
107
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
108
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
109
|
-
writePersona(globalDir, 'no-model-persona', []);
|
|
110
|
-
expect(readOverlayAwarePersonaModel('no-model-persona', { cwd, globalDir, validatePath: noopValidatePath })).toBeNull();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('readOverlayAwarePersonaModel returns null (never throws) for a path-traversal agentType rejected by validatePath', async () => {
|
|
114
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
115
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
116
|
-
const realValidatePath = (p) => {
|
|
117
|
-
const real = path.resolve(p);
|
|
118
|
-
if (!real.startsWith(path.resolve(globalDir) + path.sep)) throw new Error('outside allowed boundaries');
|
|
119
|
-
return real;
|
|
120
|
-
};
|
|
121
|
-
expect(readOverlayAwarePersonaModel('../../../etc/passwd', { cwd, globalDir, validatePath: realValidatePath })).toBeNull();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// READ side of the agentType FK: a persona that was valid when the Epic was
|
|
125
|
-
// created but got deleted afterward (rename/deletion in Agent Library) must
|
|
126
|
-
// still resolve to null, not throw — the WRITE side (epicMint.cjs's
|
|
127
|
-
// ensureEpic) is what refuses a bad reference at creation time.
|
|
128
|
-
test('readOverlayAwarePersonaModel returns null (does not throw) for a persona deleted after the Epic was created, and logs it once via opsErrorLog', async () => {
|
|
129
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
130
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-'); // never written to — simulates a deleted persona
|
|
131
|
-
|
|
132
|
-
expect(() => readOverlayAwarePersonaModel('deleted-persona', { globalDir, validatePath: noopValidatePath, cwd })).not.toThrow();
|
|
133
|
-
expect(readOverlayAwarePersonaModel('deleted-persona', { globalDir, validatePath: noopValidatePath, cwd })).toBeNull();
|
|
134
|
-
|
|
135
|
-
const { todayFile } = require('../lib/opsErrorLog.cjs');
|
|
136
|
-
const lines = fs.readFileSync(todayFile(cwd), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
137
|
-
const matches = lines.filter((l) => l.message.includes('deleted-persona'));
|
|
138
|
-
// Logged once despite two readOverlayAwarePersonaModel calls above — dedup key is (cwd, agentType).
|
|
139
|
-
expect(matches).toHaveLength(1);
|
|
140
|
-
expect(matches[0].level).toBe('warn');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// resolvePrdPersonaForSpawn — the PRD-path resolver scheduler.cjs's executeJob
|
|
144
|
-
// calls to turn a job's agentType into a --append-system-prompt body and a
|
|
145
|
-
// --model value (PRD 1115).
|
|
146
|
-
|
|
147
|
-
test('resolvePrdPersonaForSpawn returns the persona body and its own model, overriding the fallback', async () => {
|
|
148
|
-
const result = await resolvePrdPersonaForSpawn({
|
|
149
|
-
cwd: '/irrelevant',
|
|
150
|
-
agentType: 'dev-lead',
|
|
151
|
-
deps: {
|
|
152
|
-
getPersonaBody: async ({ name }) => {
|
|
153
|
-
expect(name).toBe('dev-lead');
|
|
154
|
-
return { path: '/home/user/.claude/agents/dev-lead.md', text: '---\nmodel: opus\n---\nOperate methodically.' };
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
expect(result.model).toBe('opus');
|
|
159
|
-
expect(result.systemPrompt).toBe('Operate methodically.');
|
|
160
|
-
expect(result.personaPath).toBe('/home/user/.claude/agents/dev-lead.md');
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("resolvePrdPersonaForSpawn falls back to the fallback model when the persona's model is 'inherit'", async () => {
|
|
164
|
-
const result = await resolvePrdPersonaForSpawn({
|
|
165
|
-
cwd: '/irrelevant',
|
|
166
|
-
agentType: 'dev-lead',
|
|
167
|
-
deps: { getPersonaBody: async () => ({ path: '/x/dev-lead.md', text: '---\nmodel: inherit\n---\nBody text.' }) },
|
|
168
|
-
});
|
|
169
|
-
expect(result.model).toBe(FALLBACK_MODEL);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test('resolvePrdPersonaForSpawn falls back without throwing when agentType no longer resolves to a persona file, and logs it once', async () => {
|
|
173
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
174
|
-
const call = () => resolvePrdPersonaForSpawn({
|
|
175
|
-
cwd,
|
|
176
|
-
agentType: 'ghost-persona',
|
|
177
|
-
deps: { getPersonaBody: async () => null },
|
|
178
|
-
});
|
|
179
|
-
await expect(call()).resolves.toEqual({ model: FALLBACK_MODEL, systemPrompt: null, personaPath: null });
|
|
180
|
-
await call(); // second call must not log a second line (dedup)
|
|
181
|
-
|
|
182
|
-
const { todayFile } = require('../lib/opsErrorLog.cjs');
|
|
183
|
-
const lines = fs.readFileSync(todayFile(cwd), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
184
|
-
const matches = lines.filter((l) => l.message.includes('ghost-persona'));
|
|
185
|
-
expect(matches).toHaveLength(1);
|
|
186
|
-
expect(matches[0].level).toBe('warn');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('resolvePrdPersonaForSpawn returns the fallback (no persona applied) when agentType is absent, without touching getPersonaBody', async () => {
|
|
190
|
-
const getPersonaBody = async () => { throw new Error('must not be called'); };
|
|
191
|
-
const result = await resolvePrdPersonaForSpawn({ cwd: '/irrelevant', agentType: null, deps: { getPersonaBody } });
|
|
192
|
-
expect(result).toEqual({ model: FALLBACK_MODEL, systemPrompt: null, personaPath: null });
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Overlay-aware resolution (unify-epic-model-resolution): resolveEpicModel
|
|
196
|
-
// must use the SAME project-overlay-then-global precedence
|
|
197
|
-
// resolvePrdPersonaForSpawn already uses — so a project's
|
|
198
|
-
// `.claude/agents/<name>.md` wins for a launched Chat/Terminal session
|
|
199
|
-
// exactly like it already does for a scheduled PRD.
|
|
200
|
-
|
|
201
|
-
test('resolveEpicModel prefers a project-overlay persona over the global one with the same name', async () => {
|
|
202
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
203
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
204
|
-
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-overlay', agentType: 'shared-persona' } });
|
|
205
|
-
writePersona(globalDir, 'shared-persona', ['model: opus']);
|
|
206
|
-
const projectAgentsDir = path.join(cwd, '.claude', 'agents');
|
|
207
|
-
writePersona(projectAgentsDir, 'shared-persona', ['model: haiku']);
|
|
208
|
-
|
|
209
|
-
const model = resolveEpicModel({
|
|
210
|
-
cwd,
|
|
211
|
-
claudeSessionId: 'sess-overlay',
|
|
212
|
-
deps: { globalDir, validatePath: noopValidatePath },
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
expect(model).toBe('haiku');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test('resolveEpicModel falls through to the global persona when no project overlay exists', async () => {
|
|
219
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
220
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
221
|
-
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-global-only', agentType: 'global-only-persona' } });
|
|
222
|
-
writePersona(globalDir, 'global-only-persona', ['model: opus']);
|
|
223
|
-
|
|
224
|
-
const model = resolveEpicModel({
|
|
225
|
-
cwd,
|
|
226
|
-
claudeSessionId: 'sess-global-only',
|
|
227
|
-
deps: { globalDir, validatePath: noopValidatePath },
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
expect(model).toBe('opus');
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test('resolveEpicModel falls back to FALLBACK_MODEL (never throws) for a dangling agentType with no persona file anywhere, and logs it once', async () => {
|
|
234
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
235
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-'); // never written to
|
|
236
|
-
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-dangling', agentType: 'ghost-persona' } });
|
|
237
|
-
|
|
238
|
-
const call = () => resolveEpicModel({
|
|
239
|
-
cwd,
|
|
240
|
-
claudeSessionId: 'sess-dangling',
|
|
241
|
-
deps: { globalDir, validatePath: noopValidatePath },
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
expect(call).not.toThrow();
|
|
245
|
-
expect(call()).toBe(FALLBACK_MODEL);
|
|
246
|
-
call(); // second call must not log a second line (dedup per (cwd, agentType))
|
|
247
|
-
|
|
248
|
-
const { todayFile } = require('../lib/opsErrorLog.cjs');
|
|
249
|
-
const lines = fs.readFileSync(todayFile(cwd), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
250
|
-
const matches = lines.filter((l) => l.message.includes('ghost-persona'));
|
|
251
|
-
expect(matches).toHaveLength(1);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// Chat (chatRunner.cjs, in-process) and Terminal (EpicTerminalPane.tsx, via
|
|
255
|
-
// the agents:resolve-epic-model IPC added in this change) both call this
|
|
256
|
-
// exact function with the same { cwd, claudeSessionId } shape — so "the same
|
|
257
|
-
// Epic resolves identically in both views" is structural (one function, one
|
|
258
|
-
// call signature), not a coincidence of two implementations agreeing. This
|
|
259
|
-
// asserts the function is a pure, deterministic read of that Epic's overlay-
|
|
260
|
-
// resolved persona model, which is what makes that guarantee hold.
|
|
261
|
-
test('resolveEpicModel resolves identically for the same Epic across repeated calls (the guarantee Chat and Terminal both rely on)', async () => {
|
|
262
|
-
const cwd = await mkTmpDir('sm-agentmodel-cwd-');
|
|
263
|
-
const globalDir = await mkTmpDir('sm-agentmodel-agents-');
|
|
264
|
-
writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-shared', agentType: 'shared-epic-persona' } });
|
|
265
|
-
writePersona(globalDir, 'shared-epic-persona', ['model: haiku']);
|
|
266
|
-
|
|
267
|
-
const callArgs = { cwd, claudeSessionId: 'sess-shared', deps: { globalDir, validatePath: noopValidatePath } };
|
|
268
|
-
const chatResult = resolveEpicModel(callArgs);
|
|
269
|
-
const terminalResult = resolveEpicModel(callArgs); // simulates the IPC-forwarded call Terminal makes
|
|
270
|
-
|
|
271
|
-
expect(chatResult).toBe('haiku');
|
|
272
|
-
expect(terminalResult).toBe(chatResult);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// --model must never be left unpinned (CLAUDE.md model-pinning rule) on any
|
|
276
|
-
// of the three launch paths. resolvePrdPersonaForSpawn's own miss-path tests
|
|
277
|
-
// above already cover the scheduler call site; this covers the shared
|
|
278
|
-
// Chat/Terminal resolver.
|
|
279
|
-
test('resolveEpicModel never returns an empty/falsy --model value, even on every miss path', async () => {
|
|
280
|
-
expect(resolveEpicModel({})).toBeTruthy();
|
|
281
|
-
expect(resolveEpicModel({ cwd: '/nonexistent-cwd-xyz', claudeSessionId: 'no-such-session' })).toBeTruthy();
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test('resolvePrdPersonaForSpawn caps the persona body at 6000 characters with a truncation notice naming the persona path', async () => {
|
|
285
|
-
const longBody = 'x'.repeat(6500);
|
|
286
|
-
const result = await resolvePrdPersonaForSpawn({
|
|
287
|
-
cwd: '/irrelevant',
|
|
288
|
-
agentType: 'dev-lead',
|
|
289
|
-
deps: { getPersonaBody: async () => ({ path: '/home/user/.claude/agents/dev-lead.md', text: longBody }) },
|
|
290
|
-
});
|
|
291
|
-
expect(result.systemPrompt.startsWith('x'.repeat(6000))).toBe(true);
|
|
292
|
-
expect(result.systemPrompt.length).toBeLessThan(longBody.length);
|
|
293
|
-
expect(result.systemPrompt.toLowerCase()).toContain('truncat');
|
|
294
|
-
expect(result.systemPrompt).toContain('/home/user/.claude/agents/dev-lead.md');
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// Epic-level override (New Session card) > persona frontmatter > fallback.
|
|
298
|
-
test('an Epic-level model override beats the persona model; an Epic without the field resolves unchanged', async () => {
|
|
299
|
-
const cwd = await mkTmpDir('sm-model-ovr-cwd-');
|
|
300
|
-
const globalDir = await mkTmpDir('sm-model-ovr-agents-');
|
|
301
|
-
writeIndex(cwd, {
|
|
302
|
-
withOverride: { id: 'withOverride', claudeSessionId: 's-ovr', agentType: 'p', model: 'claude-opus-5' },
|
|
303
|
-
legacy: { id: 'legacy', claudeSessionId: 's-old', agentType: 'p' },
|
|
304
|
-
inheritOverride: { id: 'inheritOverride', claudeSessionId: 's-inh', agentType: 'p', model: 'inherit' },
|
|
305
|
-
});
|
|
306
|
-
writePersona(globalDir, 'p', ['model: haiku']);
|
|
307
|
-
const deps = { globalDir, validatePath: (p) => p };
|
|
308
|
-
expect(resolveEpicModel({ cwd, claudeSessionId: 's-ovr', deps })).toBe('claude-opus-5');
|
|
309
|
-
expect(resolveEpicModel({ cwd, claudeSessionId: 's-old', deps })).toBe('haiku');
|
|
310
|
-
expect(resolveEpicModel({ cwd, claudeSessionId: 's-inh', deps })).toBe('haiku');
|
|
311
|
-
});
|