claude-code-session-manager 0.98.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-DCEgMTjH.js → DataModel-HdR0AxFq.js} +1 -1
- package/dist/assets/{History-Bfdoywlh.js → History-Dr4Jr_5z.js} +2 -2
- package/dist/assets/{Hooks-CKDJjTJV.js → Hooks-NhrvH3JP.js} +3 -3
- package/dist/assets/{Library-7akfgy67.js → Library-Dv_EtIMr.js} +1 -1
- package/dist/assets/{MarkdownEditor-DDesiqet.js → MarkdownEditor-DTKDWV_n.js} +1 -1
- package/dist/assets/{McpServers-D4lZtXof.js → McpServers-M034G9B3.js} +2 -2
- package/dist/assets/{Memory-CeI8G1w5.js → Memory-DzF1B4gx.js} +6 -6
- package/dist/assets/{Permissions-BGleLja4.js → Permissions-DrgbSGWl.js} +3 -3
- package/dist/assets/{Plugins-P4eCXy_S.js → Plugins-Bd3ef35w.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BFczzI1B.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
- package/dist/assets/{SaveBar-l9jnEMES.js → SaveBar-rimJem-Y.js} +1 -1
- package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
- package/dist/assets/{ScopeSwitcher-CjJi8ST5.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
- package/dist/assets/Settings-Dc1F_sSg.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CjbO01Sg.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
- package/dist/assets/{Skills-BxZ9_fCR.js → Skills-DRXd18gu.js} +2 -2
- package/dist/assets/{SystemPrompt-D8vfFb8b.js → SystemPrompt-ahy5-Fwe.js} +1 -1
- package/dist/assets/{TagLibrary-2vzUo7YL.js → TagLibrary-C_tToDeL.js} +1 -1
- package/dist/assets/{TiptapBody-B8ZfbK90.js → TiptapBody-DvdefS6K.js} +1 -1
- package/dist/assets/{Toggle-BbW4nGGf.js → Toggle-6Jl5Njc6.js} +1 -1
- package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
- package/dist/assets/{index-Cd98Q1lD.js → index-vx8O73l8.js} +497 -502
- package/dist/assets/{settingsSchema-IhUfucCF.js → settingsSchema-D6iRguNV.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 +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 +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/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 +12 -25
- package/src/main/lib/workTypeLibrary.cjs +7 -1
- package/src/main/promptSessionEvents.cjs +37 -13
- package/src/main/scheduler.cjs +910 -198
- 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-CDVHE0hT.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,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* activeIndexMerge.test.cjs — unit tests for the main-process read-merge-write
|
|
3
|
-
* of a cwd's prompt-sessions/active-index.json (lib/activeIndexMerge.cjs),
|
|
4
|
-
* moved here from the renderer (state/promptSessions.ts's old
|
|
5
|
-
* persistActiveIndex, commit 3d12e19) so the merge runs inside the same
|
|
6
|
-
* withPathLock instance epicMint.cjs's ensureEpic already serializes through.
|
|
7
|
-
*
|
|
8
|
-
* Covers the two holes a purely-renderer merge left open:
|
|
9
|
-
* - resurrection: a tombstoned id must never be written back by a later
|
|
10
|
-
* merge call whose memory contribution still thinks it's open.
|
|
11
|
-
* - TOCTOU: a mint (ensureEpic) interleaved with a merge call must survive,
|
|
12
|
-
* since both now go through the same lock.
|
|
13
|
-
*
|
|
14
|
-
* Run: timeout 300 npx vitest run src/main/__tests__/activeIndexMerge.test.cjs
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
'use strict';
|
|
18
|
-
|
|
19
|
-
import { test, expect, afterEach } from 'vitest';
|
|
20
|
-
const fsp = require('node:fs/promises');
|
|
21
|
-
const os = require('node:os');
|
|
22
|
-
const path = require('node:path');
|
|
23
|
-
const { mergeActiveIndex } = require('../lib/activeIndexMerge.cjs');
|
|
24
|
-
const { ensureEpic, readActiveIndex, MINT_AUTHORITY_NEW_EPIC_UI } = require('../lib/epicMint.cjs');
|
|
25
|
-
const { appendResponseEventIfKnown } = require('../promptSessionEvents.cjs');
|
|
26
|
-
const config = require('../config.cjs');
|
|
27
|
-
|
|
28
|
-
const tmpDirs = [];
|
|
29
|
-
afterEach(async () => {
|
|
30
|
-
while (tmpDirs.length) {
|
|
31
|
-
const d = tmpDirs.pop();
|
|
32
|
-
await fsp.rm(d, { recursive: true, force: true });
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
async function mkCwd() {
|
|
37
|
-
const cwd = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-activeindexmerge-cwd-'));
|
|
38
|
-
// config.cjs's readJson/writeJson restrict to registered roots (home dir by
|
|
39
|
-
// default) — register this tmp dir so the merge's config.readJson/writeJson
|
|
40
|
-
// calls don't reject it as outside the allowed boundary.
|
|
41
|
-
config.addAllowedRoot(cwd);
|
|
42
|
-
tmpDirs.push(cwd);
|
|
43
|
-
return cwd;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function session(id, overrides = {}) {
|
|
47
|
-
return {
|
|
48
|
-
id,
|
|
49
|
-
cwd: overrides.cwd || '/proj',
|
|
50
|
-
goalText: overrides.goalText || `goal for ${id}`,
|
|
51
|
-
claudeSessionId: overrides.claudeSessionId || `claude-${id}`,
|
|
52
|
-
status: overrides.status || 'active',
|
|
53
|
-
createdAt: overrides.createdAt || '2026-08-02T13:48:00.000Z',
|
|
54
|
-
completedAt: overrides.completedAt ?? null,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function promptEvent(id, promptSessionId, at) {
|
|
59
|
-
return { id, promptSessionId, kind: 'prompt', causedByEventId: null, at, text: 'x' };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Incident 2026-08-02: two Epics created by another (already-running) window
|
|
63
|
-
// sat on disk. A second window booted empty and, on its first mutation,
|
|
64
|
-
// persisted its own (empty-of-those-two) contribution straight over the file.
|
|
65
|
-
// The main-side merge must still preserve every pre-existing on-disk Epic.
|
|
66
|
-
test('a fresh caller contributing one Epic preserves pre-existing on-disk Epics untouched', async () => {
|
|
67
|
-
const cwd = await mkCwd();
|
|
68
|
-
const other = session('psess-other');
|
|
69
|
-
await mergeActiveIndex(cwd, {
|
|
70
|
-
sessions: { [other.id]: other },
|
|
71
|
-
events: { [other.id]: [promptEvent('evt-1', other.id, other.createdAt)] },
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const fresh = session('psess-fresh', { createdAt: '2026-08-02T13:50:00.000Z' });
|
|
75
|
-
await mergeActiveIndex(cwd, {
|
|
76
|
-
sessions: { [fresh.id]: fresh },
|
|
77
|
-
events: { [fresh.id]: [promptEvent('evt-2', fresh.id, fresh.createdAt)] },
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const index = readActiveIndex(cwd);
|
|
81
|
-
expect(index.sessions[other.id]).toEqual(other);
|
|
82
|
-
expect(index.sessions[fresh.id]).toEqual(fresh);
|
|
83
|
-
expect(index.events[other.id]).toHaveLength(1);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('memory wins over disk on id collision', async () => {
|
|
87
|
-
const cwd = await mkCwd();
|
|
88
|
-
const original = session('psess-1', { goalText: 'Fresh title' });
|
|
89
|
-
await mergeActiveIndex(cwd, { sessions: { [original.id]: original }, events: {} });
|
|
90
|
-
|
|
91
|
-
const updated = { ...original, goalText: 'Updated title' };
|
|
92
|
-
await mergeActiveIndex(cwd, { sessions: { [updated.id]: updated }, events: {} });
|
|
93
|
-
|
|
94
|
-
const index = readActiveIndex(cwd);
|
|
95
|
-
expect(index.sessions[original.id].goalText).toBe('Updated title');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('removedIds drops the row from the merged write and records a removal tombstone', async () => {
|
|
99
|
-
const cwd = await mkCwd();
|
|
100
|
-
const s = session('psess-1');
|
|
101
|
-
await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: {} });
|
|
102
|
-
|
|
103
|
-
await mergeActiveIndex(cwd, { sessions: {}, events: {}, removedIds: [s.id] });
|
|
104
|
-
|
|
105
|
-
const index = readActiveIndex(cwd);
|
|
106
|
-
expect(index.sessions[s.id]).toBeUndefined();
|
|
107
|
-
expect(index.tombstones[s.id]).toBeTruthy();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// The resurrection hole this PRD closes: two simulated windows. Window A
|
|
111
|
-
// completes (markCompleted) and persists the removal. Window B never
|
|
112
|
-
// hydrated that removal — its in-memory snapshot still holds the Epic as
|
|
113
|
-
// 'active' — and persists afterward. The tombstone must win: the Epic stays
|
|
114
|
-
// gone, not resurrected.
|
|
115
|
-
test('regression: a later merge whose memory still holds a removed Epic as active does not resurrect it', async () => {
|
|
116
|
-
const cwd = await mkCwd();
|
|
117
|
-
const s = session('psess-1', { status: 'active' });
|
|
118
|
-
await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: { [s.id]: [promptEvent('e1', s.id, s.createdAt)] } });
|
|
119
|
-
|
|
120
|
-
// Window A: markCompleted removes it.
|
|
121
|
-
await mergeActiveIndex(cwd, { sessions: {}, events: {}, removedIds: [s.id] });
|
|
122
|
-
expect(readActiveIndex(cwd).sessions[s.id]).toBeUndefined();
|
|
123
|
-
|
|
124
|
-
// Window B: stale in-memory snapshot still has it as 'active', persists
|
|
125
|
-
// its own (unrelated) mutation — a resurrection-prone merge.
|
|
126
|
-
const windowBOther = session('psess-2', { status: 'proposed' });
|
|
127
|
-
await mergeActiveIndex(cwd, {
|
|
128
|
-
sessions: { [s.id]: s, [windowBOther.id]: windowBOther },
|
|
129
|
-
events: { [s.id]: [promptEvent('e1', s.id, s.createdAt)] },
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const index = readActiveIndex(cwd);
|
|
133
|
-
expect(index.sessions[s.id]).toBeUndefined();
|
|
134
|
-
expect(index.events[s.id]).toBeUndefined();
|
|
135
|
-
expect(index.sessions[windowBOther.id]).toEqual(windowBOther);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test('event union: disk-only events are appended and re-sorted chain-aware; memory-only events kept', async () => {
|
|
139
|
-
const cwd = await mkCwd();
|
|
140
|
-
const s = session('psess-1');
|
|
141
|
-
const e1 = promptEvent('e1', s.id, '2026-08-02T13:00:00.000Z');
|
|
142
|
-
await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: { [s.id]: [e1] } });
|
|
143
|
-
|
|
144
|
-
// A scheduler job appended a response event directly to disk between our
|
|
145
|
-
// two merge calls (simulated by mutating the file out of band).
|
|
146
|
-
const midIndex = readActiveIndex(cwd);
|
|
147
|
-
const schedulerEvent = { id: 'e2', promptSessionId: s.id, kind: 'response', causedByEventId: 'e1', at: '2026-08-02T13:05:00.000Z', text: 'done' };
|
|
148
|
-
midIndex.events[s.id] = [e1, schedulerEvent];
|
|
149
|
-
await config.writeJson(path.join(cwd, 'session-manager-operations', 'prompt-sessions', 'active-index.json'), midIndex, { writer: 'epics' });
|
|
150
|
-
|
|
151
|
-
// This caller's memory still only knows about e1 plus its own optimistic
|
|
152
|
-
// append e3 (not yet on disk).
|
|
153
|
-
const e3 = { id: 'e3', promptSessionId: s.id, kind: 'prd_created', causedByEventId: 'e2', at: '2026-08-02T13:10:00.000Z', prdSlug: 'x' };
|
|
154
|
-
await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: { [s.id]: [e1, e3] } });
|
|
155
|
-
|
|
156
|
-
const finalEvents = readActiveIndex(cwd).events[s.id];
|
|
157
|
-
expect(finalEvents.map((e) => e.id)).toEqual(['e1', 'e2', 'e3']);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// Electron's structured-clone IPC transport (unlike JSON.parse) can produce
|
|
161
|
-
// an own-enumerable "__proto__" key on a cloned object. A plain-object merge
|
|
162
|
-
// target's bracket assignment with that key would re-link its own prototype
|
|
163
|
-
// instead of storing a normal entry, silently dropping/corrupting the Epic
|
|
164
|
-
// and potentially breaking every later Object.keys/JSON.stringify over it.
|
|
165
|
-
test('a "__proto__"-keyed session id merges as an ordinary entry, not a prototype pollution', async () => {
|
|
166
|
-
const cwd = await mkCwd();
|
|
167
|
-
const polluted = session('__proto__', { goalText: 'polluted' });
|
|
168
|
-
// Computed key, not `{ __proto__: polluted }` — the latter is special-cased
|
|
169
|
-
// by object-literal syntax to set the prototype rather than create an own
|
|
170
|
-
// enumerable "__proto__" property, which wouldn't exercise this hazard.
|
|
171
|
-
await mergeActiveIndex(cwd, { sessions: { ['__proto__']: polluted }, events: {} });
|
|
172
|
-
|
|
173
|
-
const index = readActiveIndex(cwd);
|
|
174
|
-
expect(Object.prototype.hasOwnProperty.call(index.sessions, '__proto__')).toBe(true);
|
|
175
|
-
expect(index.sessions.__proto__).toEqual(polluted);
|
|
176
|
-
expect(Object.getPrototypeOf({})).toBe(Object.prototype);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('falls back to writing memory state alone when the disk index is corrupt JSON', async () => {
|
|
180
|
-
const cwd = await mkCwd();
|
|
181
|
-
const filePath = path.join(cwd, 'session-manager-operations', 'prompt-sessions', 'active-index.json');
|
|
182
|
-
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
183
|
-
await fsp.writeFile(filePath, '{ not valid json', 'utf8');
|
|
184
|
-
|
|
185
|
-
const s = session('psess-1');
|
|
186
|
-
await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: {} });
|
|
187
|
-
|
|
188
|
-
const index = readActiveIndex(cwd);
|
|
189
|
-
expect(index.sessions[s.id]).toEqual(s);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// TOCTOU: a mint interleaved with a merge call must survive, since both now
|
|
193
|
-
// serialize through the same withPathLock (epicMint.cjs exports it; this
|
|
194
|
-
// module requires it via the same module — Node's require cache means one
|
|
195
|
-
// lock instance, not two independent maps that could still interleave).
|
|
196
|
-
test('mint-during-persist: an ensureEpic mint interleaved with a merge call survives', async () => {
|
|
197
|
-
const cwd = await mkCwd();
|
|
198
|
-
const existing = session('psess-existing');
|
|
199
|
-
await mergeActiveIndex(cwd, { sessions: { [existing.id]: existing }, events: {} });
|
|
200
|
-
|
|
201
|
-
const [mintResult] = await Promise.all([
|
|
202
|
-
ensureEpic(cwd, { goalText: 'minted during a concurrent merge', mintAuthority: MINT_AUTHORITY_NEW_EPIC_UI }),
|
|
203
|
-
mergeActiveIndex(cwd, { sessions: { [existing.id]: existing }, events: {} }),
|
|
204
|
-
]);
|
|
205
|
-
|
|
206
|
-
const index = readActiveIndex(cwd);
|
|
207
|
-
expect(index.sessions[mintResult.epicId]).toBeDefined();
|
|
208
|
-
expect(index.sessions[existing.id]).toBeDefined();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// The third writer: promptSessionEvents.cjs's appendResponseEventIfKnown
|
|
212
|
-
// (the scheduler's narrow 'scheduler'-delegation append) used to hold its
|
|
213
|
-
// OWN independent lock map — a merge call and a response-event append could
|
|
214
|
-
// still race past each other's stale read. It now imports activeIndexPath/
|
|
215
|
-
// withPathLock from epicMint.cjs (same module, same require-cache instance),
|
|
216
|
-
// so a response-event append interleaved with a merge call must survive too.
|
|
217
|
-
test('a scheduler response-event append interleaved with a merge call survives (third writer shares the same lock)', async () => {
|
|
218
|
-
const cwd = await mkCwd();
|
|
219
|
-
const s = session('psess-1', { status: 'active' });
|
|
220
|
-
await mergeActiveIndex(cwd, {
|
|
221
|
-
sessions: { [s.id]: s },
|
|
222
|
-
events: { [s.id]: [promptEvent('e1', s.id, s.createdAt)] },
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
const [, appended] = await Promise.all([
|
|
226
|
-
mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: { [s.id]: [promptEvent('e1', s.id, s.createdAt)] } }),
|
|
227
|
-
appendResponseEventIfKnown(cwd, s.id, 'PRD finished'),
|
|
228
|
-
]);
|
|
229
|
-
|
|
230
|
-
expect(appended).toBe(true);
|
|
231
|
-
const index = readActiveIndex(cwd);
|
|
232
|
-
expect(index.sessions[s.id]).toBeDefined();
|
|
233
|
-
expect(index.events[s.id]).toHaveLength(2);
|
|
234
|
-
expect(index.events[s.id][1].kind).toBe('response');
|
|
235
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agentEffortResolve.test.cjs — the effort twin of agentModelResolve.test.cjs:
|
|
3
|
-
* persona `effort:` wins (overlay beats global), inherit/absent/dangling → no flag.
|
|
4
|
-
*
|
|
5
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/agentEffortResolve.test.cjs
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use strict';
|
|
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 { resolveEpicEffort, effortArgs } = require('../lib/agentEffortResolve.cjs');
|
|
16
|
-
|
|
17
|
-
const tmpDirs = [];
|
|
18
|
-
afterEach(async () => {
|
|
19
|
-
while (tmpDirs.length) await fsp.rm(tmpDirs.pop(), { recursive: true, force: true });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
async function mkTmpDir(prefix) {
|
|
23
|
-
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
24
|
-
tmpDirs.push(dir);
|
|
25
|
-
return dir;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function writeIndex(cwd, sessions) {
|
|
29
|
-
const dir = path.join(cwd, 'session-manager-operations', 'prompt-sessions');
|
|
30
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
31
|
-
fs.writeFileSync(path.join(dir, 'active-index.json'), JSON.stringify({ sessions, events: {} }));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function writePersona(dir, name, frontmatter) {
|
|
35
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
36
|
-
fs.writeFileSync(path.join(dir, `${name}.md`), ['---', `name: ${name}`, ...frontmatter, '---', 'body'].join('\n'));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const noopValidatePath = (p) => p;
|
|
40
|
-
|
|
41
|
-
test('persona effort wins (global) for a matching Epic', async () => {
|
|
42
|
-
const cwd = await mkTmpDir('sm-effort-cwd-');
|
|
43
|
-
const globalDir = await mkTmpDir('sm-effort-agents-');
|
|
44
|
-
writeIndex(cwd, { e1: { id: 'e1', claudeSessionId: 's1', agentType: 'p' } });
|
|
45
|
-
writePersona(globalDir, 'p', ['effort: high']);
|
|
46
|
-
expect(resolveEpicEffort({ cwd, claudeSessionId: 's1', deps: { globalDir, validatePath: noopValidatePath } }))
|
|
47
|
-
.toEqual({ effort: 'high', source: 'persona' });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('project overlay beats global', async () => {
|
|
51
|
-
const cwd = await mkTmpDir('sm-effort-cwd-');
|
|
52
|
-
const globalDir = await mkTmpDir('sm-effort-agents-');
|
|
53
|
-
const projectDir = await mkTmpDir('sm-effort-proj-');
|
|
54
|
-
writePersona(globalDir, 'p', ['effort: low']);
|
|
55
|
-
writePersona(projectDir, 'p', ['effort: max']);
|
|
56
|
-
expect(resolveEpicEffort({ cwd, agentType: 'p', deps: { globalDir, projectDir, validatePath: noopValidatePath } }))
|
|
57
|
-
.toEqual({ effort: 'max', source: 'persona-overlay' });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('inherit and absent resolve to null effort', async () => {
|
|
61
|
-
const cwd = await mkTmpDir('sm-effort-cwd-');
|
|
62
|
-
const globalDir = await mkTmpDir('sm-effort-agents-');
|
|
63
|
-
writePersona(globalDir, 'inh', ['effort: inherit']);
|
|
64
|
-
writePersona(globalDir, 'none', []);
|
|
65
|
-
const deps = { globalDir, validatePath: noopValidatePath };
|
|
66
|
-
expect(resolveEpicEffort({ cwd, agentType: 'inh', deps })).toEqual({ effort: null, source: 'inherit' });
|
|
67
|
-
expect(resolveEpicEffort({ cwd, agentType: 'none', deps })).toEqual({ effort: null, source: 'inherit' });
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('a persona effort of `auto` (a /effort reset verb) resolves to no flag', async () => {
|
|
71
|
-
const cwd = await mkTmpDir('sm-effort-cwd-');
|
|
72
|
-
const globalDir = await mkTmpDir('sm-effort-agents-');
|
|
73
|
-
writePersona(globalDir, 'au', ['effort: auto']);
|
|
74
|
-
const deps = { globalDir, validatePath: noopValidatePath };
|
|
75
|
-
const r = resolveEpicEffort({ cwd, agentType: 'au', deps });
|
|
76
|
-
expect(r.effort).toBeNull();
|
|
77
|
-
expect(effortArgs(r.effort)).toEqual([]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('dangling agentType / unknown Epic / empty opts resolve to null without throwing', async () => {
|
|
81
|
-
const cwd = await mkTmpDir('sm-effort-cwd-');
|
|
82
|
-
const globalDir = await mkTmpDir('sm-effort-agents-');
|
|
83
|
-
writeIndex(cwd, {});
|
|
84
|
-
const deps = { globalDir, validatePath: noopValidatePath };
|
|
85
|
-
expect(resolveEpicEffort({ cwd, agentType: 'gone', deps })).toEqual({ effort: null, source: null });
|
|
86
|
-
expect(resolveEpicEffort({ cwd, claudeSessionId: 'nope', deps })).toEqual({ effort: null, source: null });
|
|
87
|
-
expect(resolveEpicEffort({})).toEqual({ effort: null, source: null });
|
|
88
|
-
expect(resolveEpicEffort({ cwd, agentType: '../../etc/passwd', deps })).toEqual({ effort: null, source: null });
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('an unknown level is passed through unchanged (CLI warns and degrades)', async () => {
|
|
92
|
-
const cwd = await mkTmpDir('sm-effort-cwd-');
|
|
93
|
-
const globalDir = await mkTmpDir('sm-effort-agents-');
|
|
94
|
-
writePersona(globalDir, 'odd', ['effort: turbo']);
|
|
95
|
-
expect(resolveEpicEffort({ cwd, agentType: 'odd', deps: { globalDir, validatePath: noopValidatePath } }).effort).toBe('turbo');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('effortArgs emits the pair only for a real level', () => {
|
|
99
|
-
expect(effortArgs('high')).toEqual(['--effort', 'high']);
|
|
100
|
-
for (const v of [null, undefined, '', 'inherit']) expect(effortArgs(v)).toEqual([]);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Epic-level override (New Session card) > persona frontmatter > no flag.
|
|
104
|
-
test('an Epic-level effort override beats the persona effort; an Epic without the field resolves unchanged', async () => {
|
|
105
|
-
const cwd = await mkTmpDir('sm-effort-ovr-cwd-');
|
|
106
|
-
const globalDir = await mkTmpDir('sm-effort-ovr-agents-');
|
|
107
|
-
writeIndex(cwd, {
|
|
108
|
-
withOverride: { id: 'withOverride', claudeSessionId: 's-ovr', agentType: 'p', effort: 'max' },
|
|
109
|
-
legacy: { id: 'legacy', claudeSessionId: 's-old', agentType: 'p' },
|
|
110
|
-
noPersona: { id: 'noPersona', claudeSessionId: 's-np', effort: 'low' },
|
|
111
|
-
});
|
|
112
|
-
writePersona(globalDir, 'p', ['effort: low']);
|
|
113
|
-
const deps = { globalDir, validatePath: noopValidatePath };
|
|
114
|
-
expect(resolveEpicEffort({ cwd, claudeSessionId: 's-ovr', deps })).toEqual({ effort: 'max', source: 'epic' });
|
|
115
|
-
expect(resolveEpicEffort({ cwd, claudeSessionId: 's-old', deps })).toEqual({ effort: 'low', source: 'persona' });
|
|
116
|
-
expect(resolveEpicEffort({ cwd, claudeSessionId: 's-np', deps })).toEqual({ effort: 'low', source: 'epic' });
|
|
117
|
-
});
|
|
@@ -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
|
-
});
|