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,718 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* prdAdminRoutes.test.cjs — unit tests for PRD 1024's admin HTTP routes
|
|
3
|
-
* (list/get/update/archive/cancel/retag) and the scheduler.cjs `remote`
|
|
4
|
-
* methods they delegate to. Exercises real HTTP round-trips against a real
|
|
5
|
-
* (temp-HOME) filesystem, matching prdCreate.test.cjs's pattern for the
|
|
6
|
-
* create-prd route.
|
|
7
|
-
*
|
|
8
|
-
* Run: timeout 120 npx vitest run src/main/__tests__/prdAdminRoutes.test.cjs
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
'use strict';
|
|
12
|
-
|
|
13
|
-
import { test, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
14
|
-
const fs = require('node:fs');
|
|
15
|
-
const fsp = require('node:fs/promises');
|
|
16
|
-
const os = require('node:os');
|
|
17
|
-
const path = require('node:path');
|
|
18
|
-
const http = require('node:http');
|
|
19
|
-
|
|
20
|
-
let tmpHome;
|
|
21
|
-
let originalHome;
|
|
22
|
-
let scheduler;
|
|
23
|
-
let prdAdminRoutes;
|
|
24
|
-
let config;
|
|
25
|
-
let createAdminHttp;
|
|
26
|
-
|
|
27
|
-
beforeAll(() => {
|
|
28
|
-
originalHome = process.env.HOME;
|
|
29
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-admin-prd-routes-'));
|
|
30
|
-
process.env.HOME = tmpHome;
|
|
31
|
-
|
|
32
|
-
// Load AFTER HOME is stubbed — scheduler.cjs/config.cjs snapshot
|
|
33
|
-
// os.homedir() into module-level constants at load time, not lazily.
|
|
34
|
-
scheduler = require('../scheduler.cjs');
|
|
35
|
-
prdAdminRoutes = require('../lib/prdAdminRoutes.cjs');
|
|
36
|
-
config = require('../config.cjs');
|
|
37
|
-
({ createAdminHttp } = require('../lib/localAdminHttp.cjs'));
|
|
38
|
-
|
|
39
|
-
if (!scheduler.PRDS_DIR.startsWith(tmpHome)) {
|
|
40
|
-
throw new Error(`refusing to run: PRDS_DIR (${scheduler.PRDS_DIR}) is not under the temp HOME (${tmpHome})`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// allProjectCwds()/activeProjectCwds() (queueStore.cjs's stateCwds, and
|
|
44
|
-
// prdLocations.cjs's resolvePrdsDirs) discover project cwds by scanning
|
|
45
|
-
// ~/.claude/projects/*/*.jsonl for a `cwd` field — fake one project
|
|
46
|
-
// transcript pointing at the real on-disk fixture cwd this file uses,
|
|
47
|
-
// same pattern as scheduler-verify-prd-path.test.cjs.
|
|
48
|
-
const projDir = path.join(tmpHome, '.claude', 'projects', 'fake-project-slug');
|
|
49
|
-
fs.mkdirSync(projDir, { recursive: true });
|
|
50
|
-
fs.writeFileSync(
|
|
51
|
-
path.join(projDir, 'session.jsonl'),
|
|
52
|
-
`${JSON.stringify({ cwd: path.join(tmpHome, 'fake-project-cwd') })}\n`,
|
|
53
|
-
'utf8',
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
afterAll(() => {
|
|
58
|
-
process.env.HOME = originalHome;
|
|
59
|
-
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
function projectCwd() {
|
|
63
|
-
const cwd = path.join(tmpHome, 'fake-project-cwd');
|
|
64
|
-
fs.mkdirSync(cwd, { recursive: true });
|
|
65
|
-
config.addAllowedRoot(cwd);
|
|
66
|
-
return cwd;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function uniqueSlug(prefix) {
|
|
70
|
-
return `${prefix}-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function writePrdFixture({ cwd, epicId, slug, extra = '' }) {
|
|
74
|
-
const prdsDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', epicId, 'prds');
|
|
75
|
-
await fsp.mkdir(prdsDir, { recursive: true });
|
|
76
|
-
const filePath = path.join(prdsDir, `${slug}.md`);
|
|
77
|
-
const body = `---\ntitle: fixture title\ncwd: ${cwd}\nestimateMinutes: 10\n${extra}---\n\n# Goal\n\ndo the thing\n`;
|
|
78
|
-
await fsp.writeFile(filePath, body, 'utf8');
|
|
79
|
-
return filePath;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function request(port, { method = 'GET', path: reqPath, token, body }) {
|
|
83
|
-
return new Promise((resolve, reject) => {
|
|
84
|
-
const headers = {};
|
|
85
|
-
if (token !== undefined) headers.Authorization = `Bearer ${token}`;
|
|
86
|
-
let payload;
|
|
87
|
-
if (body !== undefined) {
|
|
88
|
-
payload = JSON.stringify(body);
|
|
89
|
-
headers['Content-Type'] = 'application/json';
|
|
90
|
-
headers['Content-Length'] = Buffer.byteLength(payload);
|
|
91
|
-
}
|
|
92
|
-
const req = http.request({ hostname: '127.0.0.1', port, method, path: reqPath, headers }, (res) => {
|
|
93
|
-
const chunks = [];
|
|
94
|
-
res.on('data', (c) => chunks.push(c));
|
|
95
|
-
res.on('end', () => {
|
|
96
|
-
const text = Buffer.concat(chunks).toString('utf8');
|
|
97
|
-
let json = null;
|
|
98
|
-
try { json = JSON.parse(text); } catch { /* leave null */ }
|
|
99
|
-
resolve({ status: res.statusCode, json, text });
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
req.on('error', reject);
|
|
103
|
-
if (payload) req.write(payload);
|
|
104
|
-
req.end();
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function startAdmin() {
|
|
109
|
-
const prevE2e = process.env.SM_E2E;
|
|
110
|
-
process.env.SM_E2E = '1';
|
|
111
|
-
const admin = createAdminHttp();
|
|
112
|
-
prdAdminRoutes.registerAdminRoute(admin, scheduler.remote);
|
|
113
|
-
// Also wires reset-job/jobs (scheduler.cjs's own admin routes) onto the
|
|
114
|
-
// SAME admin instance/port, against the real (not faked) `remote` — this
|
|
115
|
-
// file's real-filesystem setup is what the reset-job/cancel-job/archive-prd
|
|
116
|
-
// slug-vs-not-found split needs, unlike scheduler-admin-routes.test.cjs's
|
|
117
|
-
// fake remote.
|
|
118
|
-
scheduler.registerAdminRoutes(admin, scheduler.remote);
|
|
119
|
-
const { port, token } = await admin.start();
|
|
120
|
-
if (prevE2e === undefined) delete process.env.SM_E2E; else process.env.SM_E2E = prevE2e;
|
|
121
|
-
return { admin, port, token };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
test('GET /admin/scheduler/prds without token returns 401', async () => {
|
|
125
|
-
const { admin, port } = await startAdmin();
|
|
126
|
-
try {
|
|
127
|
-
const res = await request(port, { path: '/admin/scheduler/prds' });
|
|
128
|
-
expect(res.status).toBe(401);
|
|
129
|
-
} finally {
|
|
130
|
-
await admin.stop();
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('GET /admin/scheduler/prds lists a live PRD with status null (no queue row yet), filterable by cwd', async () => {
|
|
135
|
-
const cwd = projectCwd();
|
|
136
|
-
const epicId = 'epic-list';
|
|
137
|
-
const slug = uniqueSlug('list-prds');
|
|
138
|
-
await writePrdFixture({ cwd, epicId, slug });
|
|
139
|
-
|
|
140
|
-
const { admin, port, token } = await startAdmin();
|
|
141
|
-
try {
|
|
142
|
-
const res = await request(port, { path: `/admin/scheduler/prds?cwd=${encodeURIComponent(cwd)}`, token });
|
|
143
|
-
expect(res.status).toBe(200);
|
|
144
|
-
expect(res.json.ok).toBe(true);
|
|
145
|
-
const entry = res.json.prds.find((p) => p.slug === slug);
|
|
146
|
-
expect(entry).toBeTruthy();
|
|
147
|
-
expect(entry.status).toBe(null);
|
|
148
|
-
} finally {
|
|
149
|
-
await admin.stop();
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('GET /admin/scheduler/prds defaults to limit=100/offset=0, reports total/hasMore, omits secondary fields', async () => {
|
|
154
|
-
const cwd = projectCwd();
|
|
155
|
-
const epicId = 'epic-list-defaults';
|
|
156
|
-
const slug = uniqueSlug('list-prds-defaults');
|
|
157
|
-
await writePrdFixture({ cwd, epicId, slug });
|
|
158
|
-
|
|
159
|
-
const { admin, port, token } = await startAdmin();
|
|
160
|
-
try {
|
|
161
|
-
const res = await request(port, { path: `/admin/scheduler/prds?cwd=${encodeURIComponent(cwd)}`, token });
|
|
162
|
-
expect(res.status).toBe(200);
|
|
163
|
-
expect(res.json.ok).toBe(true);
|
|
164
|
-
expect(res.json.limit).toBe(100);
|
|
165
|
-
expect(res.json.offset).toBe(0);
|
|
166
|
-
expect(res.json.total).toBe(res.json.prds.length);
|
|
167
|
-
expect(res.json.hasMore).toBe(false);
|
|
168
|
-
const entry = res.json.prds.find((p) => p.slug === slug);
|
|
169
|
-
expect(entry).toBeTruthy();
|
|
170
|
-
expect('epicId' in entry).toBe(false);
|
|
171
|
-
expect('sourcePromptId' in entry).toBe(false);
|
|
172
|
-
} finally {
|
|
173
|
-
await admin.stop();
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test('GET /admin/scheduler/prds?fields=full restores secondary fields', async () => {
|
|
178
|
-
const cwd = projectCwd();
|
|
179
|
-
const epicId = 'epic-list-full';
|
|
180
|
-
const slug = uniqueSlug('list-prds-full');
|
|
181
|
-
await writePrdFixture({ cwd, epicId, slug });
|
|
182
|
-
|
|
183
|
-
const { admin, port, token } = await startAdmin();
|
|
184
|
-
try {
|
|
185
|
-
const res = await request(port, { path: `/admin/scheduler/prds?cwd=${encodeURIComponent(cwd)}&fields=full`, token });
|
|
186
|
-
expect(res.status).toBe(200);
|
|
187
|
-
const entry = res.json.prds.find((p) => p.slug === slug);
|
|
188
|
-
expect(entry.epicId).toBe(epicId);
|
|
189
|
-
expect(entry.sourcePromptId).toBeTruthy();
|
|
190
|
-
} finally {
|
|
191
|
-
await admin.stop();
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test('GET /admin/scheduler/prds pages disjointly by limit/offset in stable slug order', async () => {
|
|
196
|
-
const cwd = projectCwd();
|
|
197
|
-
const epicId = 'epic-list-paged';
|
|
198
|
-
const base = uniqueSlug('list-prds-paged');
|
|
199
|
-
const slugs = [];
|
|
200
|
-
for (let i = 0; i < 5; i++) {
|
|
201
|
-
const slug = `${base}-${i}`;
|
|
202
|
-
slugs.push(slug);
|
|
203
|
-
await writePrdFixture({ cwd, epicId, slug });
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const { admin, port, token } = await startAdmin();
|
|
207
|
-
try {
|
|
208
|
-
const qs = `cwd=${encodeURIComponent(cwd)}&epicId=${encodeURIComponent(epicId)}`;
|
|
209
|
-
const page1 = await request(port, { path: `/admin/scheduler/prds?${qs}&limit=2&offset=0`, token });
|
|
210
|
-
const page2 = await request(port, { path: `/admin/scheduler/prds?${qs}&limit=2&offset=2`, token });
|
|
211
|
-
const page3 = await request(port, { path: `/admin/scheduler/prds?${qs}&limit=2&offset=4`, token });
|
|
212
|
-
|
|
213
|
-
expect(page1.json.prds).toHaveLength(2);
|
|
214
|
-
expect(page1.json.total).toBe(5);
|
|
215
|
-
expect(page1.json.hasMore).toBe(true);
|
|
216
|
-
expect(page2.json.prds).toHaveLength(2);
|
|
217
|
-
expect(page2.json.hasMore).toBe(true);
|
|
218
|
-
expect(page3.json.prds).toHaveLength(1);
|
|
219
|
-
expect(page3.json.hasMore).toBe(false);
|
|
220
|
-
|
|
221
|
-
const seenSlugs = [...page1.json.prds, ...page2.json.prds, ...page3.json.prds].map((p) => p.slug);
|
|
222
|
-
expect(new Set(seenSlugs).size).toBe(5);
|
|
223
|
-
const expectedOrder = [...seenSlugs].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
|
224
|
-
expect(seenSlugs).toEqual(expectedOrder);
|
|
225
|
-
} finally {
|
|
226
|
-
await admin.stop();
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test('GET /admin/scheduler/prds rejects limit over the hard max with a 400', async () => {
|
|
231
|
-
const cwd = projectCwd();
|
|
232
|
-
const { admin, port, token } = await startAdmin();
|
|
233
|
-
try {
|
|
234
|
-
const res = await request(port, { path: `/admin/scheduler/prds?cwd=${encodeURIComponent(cwd)}&limit=501`, token });
|
|
235
|
-
expect(res.status).toBe(400);
|
|
236
|
-
expect(res.json.ok).toBe(false);
|
|
237
|
-
} finally {
|
|
238
|
-
await admin.stop();
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test('GET /admin/scheduler/prds rejects a negative offset with a 400', async () => {
|
|
243
|
-
const cwd = projectCwd();
|
|
244
|
-
const { admin, port, token } = await startAdmin();
|
|
245
|
-
try {
|
|
246
|
-
const res = await request(port, { path: `/admin/scheduler/prds?cwd=${encodeURIComponent(cwd)}&offset=-1`, token });
|
|
247
|
-
expect(res.status).toBe(400);
|
|
248
|
-
expect(res.json.ok).toBe(false);
|
|
249
|
-
} finally {
|
|
250
|
-
await admin.stop();
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test('GET /admin/scheduler/prd?slug=&cwd= returns parsed frontmatter + body', async () => {
|
|
255
|
-
const cwd = projectCwd();
|
|
256
|
-
const epicId = 'epic-get';
|
|
257
|
-
const slug = uniqueSlug('get-prd');
|
|
258
|
-
await writePrdFixture({ cwd, epicId, slug });
|
|
259
|
-
|
|
260
|
-
const { admin, port, token } = await startAdmin();
|
|
261
|
-
try {
|
|
262
|
-
const res = await request(port, { path: `/admin/scheduler/prd?slug=${slug}&cwd=${encodeURIComponent(cwd)}`, token });
|
|
263
|
-
expect(res.status).toBe(200);
|
|
264
|
-
expect(res.json.ok).toBe(true);
|
|
265
|
-
expect(res.json.frontmatter.title).toBe('fixture title');
|
|
266
|
-
expect(res.json.body).toMatch(/do the thing/);
|
|
267
|
-
} finally {
|
|
268
|
-
await admin.stop();
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test('GET /admin/scheduler/prd for a nonexistent slug returns 404', async () => {
|
|
273
|
-
const cwd = projectCwd();
|
|
274
|
-
const { admin, port, token } = await startAdmin();
|
|
275
|
-
try {
|
|
276
|
-
const res = await request(port, { path: `/admin/scheduler/prd?slug=${uniqueSlug('nope')}&cwd=${encodeURIComponent(cwd)}`, token });
|
|
277
|
-
expect(res.status).toBe(404);
|
|
278
|
-
expect(res.json.ok).toBe(false);
|
|
279
|
-
} finally {
|
|
280
|
-
await admin.stop();
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test('POST /admin/scheduler/update-prd edits a not-yet-queued PRD and preserves unrecognized frontmatter keys', async () => {
|
|
285
|
-
const cwd = projectCwd();
|
|
286
|
-
const epicId = 'epic-update';
|
|
287
|
-
const slug = uniqueSlug('update-prd');
|
|
288
|
-
await writePrdFixture({ cwd, epicId, slug, extra: 'dependsOn: [some-other-slug]\n' });
|
|
289
|
-
|
|
290
|
-
const { admin, port, token } = await startAdmin();
|
|
291
|
-
try {
|
|
292
|
-
const res = await request(port, {
|
|
293
|
-
method: 'POST', path: '/admin/scheduler/update-prd', token,
|
|
294
|
-
body: { slug, cwd, frontmatter: { title: 'edited title', estimateMinutes: 25 } },
|
|
295
|
-
});
|
|
296
|
-
expect(res.status).toBe(200);
|
|
297
|
-
expect(res.json.ok).toBe(true);
|
|
298
|
-
|
|
299
|
-
const raw = await fsp.readFile(path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', epicId, 'prds', `${slug}.md`), 'utf8');
|
|
300
|
-
expect(raw).toMatch(/title: edited title/);
|
|
301
|
-
expect(raw).toMatch(/estimateMinutes: 25/);
|
|
302
|
-
// Unrecognized key round-trips verbatim.
|
|
303
|
-
expect(raw).toMatch(/dependsOn: \[some-other-slug\]/);
|
|
304
|
-
} finally {
|
|
305
|
-
await admin.stop();
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
test('POST /admin/scheduler/update-prd refuses a slug whose job is running, naming the current status', async () => {
|
|
310
|
-
const cwd = projectCwd();
|
|
311
|
-
const epicId = 'epic-update-running';
|
|
312
|
-
const slug = uniqueSlug('update-prd-running');
|
|
313
|
-
await writePrdFixture({ cwd, epicId, slug });
|
|
314
|
-
|
|
315
|
-
await scheduler.writeQueue({
|
|
316
|
-
jobs: [{ slug, title: 'x', cwd, status: 'running', runtime: {} }],
|
|
317
|
-
config: {},
|
|
318
|
-
paused: null,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
const { admin, port, token } = await startAdmin();
|
|
322
|
-
try {
|
|
323
|
-
const res = await request(port, {
|
|
324
|
-
method: 'POST', path: '/admin/scheduler/update-prd', token,
|
|
325
|
-
body: { slug, cwd, frontmatter: { title: 'should not land' } },
|
|
326
|
-
});
|
|
327
|
-
expect(res.status).toBe(409);
|
|
328
|
-
expect(res.json.ok).toBe(false);
|
|
329
|
-
expect(res.json.error).toMatch(/running/);
|
|
330
|
-
} finally {
|
|
331
|
-
await admin.stop();
|
|
332
|
-
await scheduler.writeQueue({ jobs: [], config: {}, paused: null });
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
test('POST /admin/scheduler/cancel-job cancels a pending job (no cancelled status exists — lands as failed)', async () => {
|
|
337
|
-
const cwd = projectCwd();
|
|
338
|
-
const slug = uniqueSlug('cancel-pending');
|
|
339
|
-
await scheduler.writeQueue({
|
|
340
|
-
jobs: [{ slug, title: 'x', cwd, status: 'pending' }],
|
|
341
|
-
config: {},
|
|
342
|
-
paused: null,
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
const { admin, port, token } = await startAdmin();
|
|
346
|
-
try {
|
|
347
|
-
const res = await request(port, { method: 'POST', path: '/admin/scheduler/cancel-job', token, body: { slug } });
|
|
348
|
-
expect(res.status).toBe(200);
|
|
349
|
-
expect(res.json.ok).toBe(true);
|
|
350
|
-
expect(res.json.status).toBe('failed');
|
|
351
|
-
expect(res.json.wasRunning).toBe(false);
|
|
352
|
-
} finally {
|
|
353
|
-
await admin.stop();
|
|
354
|
-
await scheduler.writeQueue({ jobs: [], config: {}, paused: null });
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
test('POST /admin/scheduler/cancel-job refuses an already-terminal job', async () => {
|
|
359
|
-
const cwd = projectCwd();
|
|
360
|
-
const slug = uniqueSlug('cancel-terminal');
|
|
361
|
-
await scheduler.writeQueue({
|
|
362
|
-
jobs: [{ slug, title: 'x', cwd, status: 'completed' }],
|
|
363
|
-
config: {},
|
|
364
|
-
paused: null,
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
const { admin, port, token } = await startAdmin();
|
|
368
|
-
try {
|
|
369
|
-
const res = await request(port, { method: 'POST', path: '/admin/scheduler/cancel-job', token, body: { slug } });
|
|
370
|
-
expect(res.status).toBe(409);
|
|
371
|
-
expect(res.json.ok).toBe(false);
|
|
372
|
-
expect(res.json.error).toMatch(/terminal/);
|
|
373
|
-
} finally {
|
|
374
|
-
await admin.stop();
|
|
375
|
-
await scheduler.writeQueue({ jobs: [], config: {}, paused: null });
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
test('POST /admin/scheduler/archive-prd moves a PRD to prds-archived/', async () => {
|
|
380
|
-
const cwd = projectCwd();
|
|
381
|
-
const epicId = 'epic-archive';
|
|
382
|
-
const slug = uniqueSlug('archive-prd');
|
|
383
|
-
const filePath = await writePrdFixture({ cwd, epicId, slug });
|
|
384
|
-
|
|
385
|
-
const { admin, port, token } = await startAdmin();
|
|
386
|
-
try {
|
|
387
|
-
const res = await request(port, { method: 'POST', path: '/admin/scheduler/archive-prd', token, body: { slugs: [slug] } });
|
|
388
|
-
expect(res.status).toBe(200);
|
|
389
|
-
expect(res.json.ok).toBe(true);
|
|
390
|
-
expect(res.json.archived).toBe(1);
|
|
391
|
-
expect(fs.existsSync(filePath)).toBe(false);
|
|
392
|
-
} finally {
|
|
393
|
-
await admin.stop();
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
// ─────────────────────────────────────── invalid-slug vs unknown-slug split
|
|
398
|
-
//
|
|
399
|
-
// PRD: safeSlugPath used to collapse "malformed slug" and "well-formed slug
|
|
400
|
-
// found nowhere" into the same `null` -> "invalid slug" response, so an
|
|
401
|
-
// agent that typed a real slug for a PRD in the wrong project (or a since-
|
|
402
|
-
// archived one) read the error as "I typed it wrong" and retried instead of
|
|
403
|
-
// listing PRDs. These tests pin the split for all three verbs plus the new
|
|
404
|
-
// optional `cwd` narrowing.
|
|
405
|
-
|
|
406
|
-
test('POST /admin/scheduler/reset-job with a malformed slug returns "invalid slug"', async () => {
|
|
407
|
-
const { admin, port, token } = await startAdmin();
|
|
408
|
-
try {
|
|
409
|
-
const res = await request(port, {
|
|
410
|
-
method: 'POST', path: '/admin/scheduler/reset-job', token, body: { slug: 'not a slug!!' },
|
|
411
|
-
});
|
|
412
|
-
expect(res.status).toBe(200);
|
|
413
|
-
expect(res.json.ok).toBe(false);
|
|
414
|
-
expect(res.json.error).toBe('invalid slug');
|
|
415
|
-
} finally {
|
|
416
|
-
await admin.stop();
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
test('POST /admin/scheduler/reset-job with a well-formed slug that exists nowhere returns a distinct unknown-slug error', async () => {
|
|
421
|
-
const { admin, port, token } = await startAdmin();
|
|
422
|
-
try {
|
|
423
|
-
const slug = uniqueSlug('definitely-not-a-real-prd');
|
|
424
|
-
const res = await request(port, {
|
|
425
|
-
method: 'POST', path: '/admin/scheduler/reset-job', token, body: { slug },
|
|
426
|
-
});
|
|
427
|
-
expect(res.status).toBe(200);
|
|
428
|
-
expect(res.json.ok).toBe(false);
|
|
429
|
-
expect(res.json.error).not.toBe('invalid slug');
|
|
430
|
-
expect(res.json.error).toMatch(/unknown slug/);
|
|
431
|
-
expect(res.json.error).toMatch(/scheduler_list_prds/);
|
|
432
|
-
} finally {
|
|
433
|
-
await admin.stop();
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
test('POST /admin/scheduler/reset-job with a cwd naming a project with no PRD dir yields the unknown-slug error, not a crash', async () => {
|
|
438
|
-
const cwd = projectCwd();
|
|
439
|
-
const epicId = 'epic-reset-cwd-miss';
|
|
440
|
-
const slug = uniqueSlug('reset-cwd-miss');
|
|
441
|
-
await writePrdFixture({ cwd, epicId, slug }); // exists under `cwd`, not under `otherCwd`
|
|
442
|
-
|
|
443
|
-
const otherCwd = path.join(tmpHome, 'fake-project-cwd-empty');
|
|
444
|
-
fs.mkdirSync(otherCwd, { recursive: true });
|
|
445
|
-
|
|
446
|
-
const { admin, port, token } = await startAdmin();
|
|
447
|
-
try {
|
|
448
|
-
const res = await request(port, {
|
|
449
|
-
method: 'POST', path: '/admin/scheduler/reset-job', token, body: { slug, cwd: otherCwd },
|
|
450
|
-
});
|
|
451
|
-
expect(res.status).toBe(200);
|
|
452
|
-
expect(res.json.ok).toBe(false);
|
|
453
|
-
expect(res.json.error).toMatch(/unknown slug/);
|
|
454
|
-
} finally {
|
|
455
|
-
await admin.stop();
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
test('POST /admin/scheduler/reset-job with a matching cwd resets a known job', async () => {
|
|
460
|
-
const cwd = projectCwd();
|
|
461
|
-
const epicId = 'epic-reset-cwd-hit';
|
|
462
|
-
const slug = uniqueSlug('reset-cwd-hit');
|
|
463
|
-
await writePrdFixture({ cwd, epicId, slug });
|
|
464
|
-
await scheduler.writeQueue({
|
|
465
|
-
jobs: [{ slug, title: 'x', cwd, status: 'needs_review' }],
|
|
466
|
-
config: {},
|
|
467
|
-
paused: null,
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
const { admin, port, token } = await startAdmin();
|
|
471
|
-
try {
|
|
472
|
-
const res = await request(port, {
|
|
473
|
-
method: 'POST', path: '/admin/scheduler/reset-job', token, body: { slug, cwd },
|
|
474
|
-
});
|
|
475
|
-
expect(res.status).toBe(200);
|
|
476
|
-
expect(res.json).toEqual({ ok: true, slug, status: 'pending' });
|
|
477
|
-
} finally {
|
|
478
|
-
await admin.stop();
|
|
479
|
-
await scheduler.writeQueue({ jobs: [], config: {}, paused: null });
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
test('POST /admin/scheduler/reset-job with cwd resets the SAME-slug job belonging to that project, not a same-slug job from another project (cwd narrows the job lookup, not just the file lookup)', async () => {
|
|
484
|
-
// Slugs derive from title text with no cwd salt, so two different projects
|
|
485
|
-
// can legitimately queue an identically-slugged job. `cwd` must disambiguate
|
|
486
|
-
// which one gets reset, not just which PRD file gets read for existence.
|
|
487
|
-
const cwdA = projectCwd();
|
|
488
|
-
const cwdB = path.join(tmpHome, 'fake-project-cwd-collision-b');
|
|
489
|
-
fs.mkdirSync(cwdB, { recursive: true });
|
|
490
|
-
config.addAllowedRoot(cwdB);
|
|
491
|
-
// queueStore.cjs's readMerged only reads back a project's shard if that
|
|
492
|
-
// project's cwd is discoverable via allProjectCwds (scans ~/.claude/projects/
|
|
493
|
-
// *.jsonl for a `cwd` field) — same fake-transcript trick beforeAll used for
|
|
494
|
-
// the shared fixture cwd.
|
|
495
|
-
const projDirB = path.join(tmpHome, '.claude', 'projects', 'fake-project-slug-b');
|
|
496
|
-
fs.mkdirSync(projDirB, { recursive: true });
|
|
497
|
-
fs.writeFileSync(path.join(projDirB, 'session.jsonl'), `${JSON.stringify({ cwd: cwdB })}\n`, 'utf8');
|
|
498
|
-
const { bustCwdCache } = require('../lib/queueStore.cjs');
|
|
499
|
-
bustCwdCache();
|
|
500
|
-
const slug = uniqueSlug('collision-slug');
|
|
501
|
-
const epicId = 'epic-reset-collision';
|
|
502
|
-
// Both projects' rows need a real PRD source — a 'needs_review' row whose
|
|
503
|
-
// PRD is invisible to reconcile()'s onDisk scan is silently dropped (never
|
|
504
|
-
// backfilled to history.jsonl the way a completed/failed/skipped orphan
|
|
505
|
-
// is), which would make jobA vanish for a reason unrelated to what this
|
|
506
|
-
// test actually verifies (cwd-scoped job lookup).
|
|
507
|
-
await writePrdFixture({ cwd: cwdA, epicId, slug });
|
|
508
|
-
await writePrdFixture({ cwd: cwdB, epicId, slug });
|
|
509
|
-
await scheduler.writeQueue({
|
|
510
|
-
jobs: [
|
|
511
|
-
{ slug, title: 'project A job', cwd: cwdA, status: 'needs_review' },
|
|
512
|
-
{ slug, title: 'project B job', cwd: cwdB, status: 'needs_review' },
|
|
513
|
-
],
|
|
514
|
-
config: {},
|
|
515
|
-
paused: null,
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
const { admin, port, token } = await startAdmin();
|
|
519
|
-
try {
|
|
520
|
-
const res = await request(port, {
|
|
521
|
-
method: 'POST', path: '/admin/scheduler/reset-job', token, body: { slug, cwd: cwdB },
|
|
522
|
-
});
|
|
523
|
-
expect(res.status).toBe(200);
|
|
524
|
-
expect(res.json).toEqual({ ok: true, slug, status: 'pending' });
|
|
525
|
-
|
|
526
|
-
const state = await scheduler.remote.listJobs();
|
|
527
|
-
// Filter by slug too, not just cwd: cwdA is a shared fixture directory
|
|
528
|
-
// reused by many tests in this file, each of which leaves its own
|
|
529
|
-
// never-archived PRD file behind (only the queue row is cleared in
|
|
530
|
-
// `finally`) — reconcile()'s fresh-discovery pass can quarantine one of
|
|
531
|
-
// those leftovers as a NEW row under this same cwd on any later pass, so
|
|
532
|
-
// `cwd` alone no longer uniquely identifies this test's own job.
|
|
533
|
-
const jobA = state.find((j) => j.cwd === cwdA && j.slug === slug);
|
|
534
|
-
const jobB = state.find((j) => j.cwd === cwdB && j.slug === slug);
|
|
535
|
-
expect(jobA.status).toBe('needs_review'); // untouched
|
|
536
|
-
expect(jobB.status).toBe('pending'); // the one actually targeted
|
|
537
|
-
} finally {
|
|
538
|
-
await admin.stop();
|
|
539
|
-
await scheduler.writeQueue({ jobs: [], config: {}, paused: null });
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
test('POST /admin/scheduler/cancel-job with a malformed slug is rejected at the request-schema layer (400) — never reaches "not found"', async () => {
|
|
544
|
-
const { admin, port, token } = await startAdmin();
|
|
545
|
-
try {
|
|
546
|
-
const res = await request(port, {
|
|
547
|
-
method: 'POST', path: '/admin/scheduler/cancel-job', token, body: { slug: 'not a slug!!' },
|
|
548
|
-
});
|
|
549
|
-
expect(res.status).toBe(400);
|
|
550
|
-
expect(res.json.ok).toBe(false);
|
|
551
|
-
expect(res.json.error).not.toMatch(/unknown slug|not found/i);
|
|
552
|
-
} finally {
|
|
553
|
-
await admin.stop();
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
test('remote.cancelJob itself (bypassing the HTTP schema layer) returns "invalid slug" for a malformed slug, distinct from "unknown slug"', async () => {
|
|
558
|
-
const malformed = await scheduler.remote.cancelJob('not a slug!!');
|
|
559
|
-
expect(malformed).toEqual({ ok: false, error: 'invalid slug' });
|
|
560
|
-
|
|
561
|
-
const wellFormedMissing = await scheduler.remote.cancelJob(uniqueSlug('cancel-remote-missing'));
|
|
562
|
-
expect(wellFormedMissing.ok).toBe(false);
|
|
563
|
-
expect(wellFormedMissing.error).not.toBe('invalid slug');
|
|
564
|
-
expect(wellFormedMissing.error).toMatch(/unknown slug/);
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
test('POST /admin/scheduler/cancel-job with a well-formed slug that has no queued job returns a distinct unknown-slug error (404)', async () => {
|
|
568
|
-
const { admin, port, token } = await startAdmin();
|
|
569
|
-
try {
|
|
570
|
-
const slug = uniqueSlug('cancel-missing');
|
|
571
|
-
const res = await request(port, {
|
|
572
|
-
method: 'POST', path: '/admin/scheduler/cancel-job', token, body: { slug },
|
|
573
|
-
});
|
|
574
|
-
expect(res.status).toBe(404);
|
|
575
|
-
expect(res.json.ok).toBe(false);
|
|
576
|
-
expect(res.json.error).not.toBe('invalid slug');
|
|
577
|
-
expect(res.json.error).toMatch(/unknown slug/);
|
|
578
|
-
} finally {
|
|
579
|
-
await admin.stop();
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
test('POST /admin/scheduler/cancel-job with cwd narrows job lookup to that project', async () => {
|
|
584
|
-
const cwd = projectCwd();
|
|
585
|
-
const slug = uniqueSlug('cancel-cwd-scope');
|
|
586
|
-
await scheduler.writeQueue({
|
|
587
|
-
jobs: [{ slug, title: 'x', cwd, status: 'pending' }],
|
|
588
|
-
config: {},
|
|
589
|
-
paused: null,
|
|
590
|
-
});
|
|
591
|
-
const otherCwd = path.join(tmpHome, 'fake-project-cwd-cancel-miss');
|
|
592
|
-
fs.mkdirSync(otherCwd, { recursive: true });
|
|
593
|
-
|
|
594
|
-
const { admin, port, token } = await startAdmin();
|
|
595
|
-
try {
|
|
596
|
-
const missRes = await request(port, {
|
|
597
|
-
method: 'POST', path: '/admin/scheduler/cancel-job', token, body: { slug, cwd: otherCwd },
|
|
598
|
-
});
|
|
599
|
-
expect(missRes.status).toBe(404);
|
|
600
|
-
expect(missRes.json.ok).toBe(false);
|
|
601
|
-
|
|
602
|
-
const hitRes = await request(port, {
|
|
603
|
-
method: 'POST', path: '/admin/scheduler/cancel-job', token, body: { slug, cwd },
|
|
604
|
-
});
|
|
605
|
-
expect(hitRes.status).toBe(200);
|
|
606
|
-
expect(hitRes.json.ok).toBe(true);
|
|
607
|
-
} finally {
|
|
608
|
-
await admin.stop();
|
|
609
|
-
await scheduler.writeQueue({ jobs: [], config: {}, paused: null });
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
test('POST /admin/scheduler/archive-prd with a malformed slug is rejected at the request-schema layer (400) — never reaches "not found"', async () => {
|
|
614
|
-
const { admin, port, token } = await startAdmin();
|
|
615
|
-
try {
|
|
616
|
-
const res = await request(port, {
|
|
617
|
-
method: 'POST', path: '/admin/scheduler/archive-prd', token, body: { slugs: ['not a slug!!'] },
|
|
618
|
-
});
|
|
619
|
-
expect(res.status).toBe(400);
|
|
620
|
-
expect(res.json.ok).toBe(false);
|
|
621
|
-
} finally {
|
|
622
|
-
await admin.stop();
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
test('queueOps.archiveMany itself (bypassing the HTTP schema layer) splits "invalid slug" from "not found in any PRDs dir"', async () => {
|
|
627
|
-
const queueOps = require('../queueOps.cjs');
|
|
628
|
-
const malformed = await queueOps.archiveMany(['not a slug!!']);
|
|
629
|
-
expect(malformed.results[0]).toMatchObject({ ok: false, error: 'invalid slug' });
|
|
630
|
-
|
|
631
|
-
const missing = await queueOps.archiveMany([uniqueSlug('archive-remote-missing')]);
|
|
632
|
-
expect(missing.results[0]).toMatchObject({ ok: false, error: 'not found in any PRDs dir' });
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
test('POST /admin/scheduler/archive-prd with a well-formed slug found nowhere returns a distinct not-found error', async () => {
|
|
636
|
-
const { admin, port, token } = await startAdmin();
|
|
637
|
-
try {
|
|
638
|
-
const slug = uniqueSlug('archive-missing');
|
|
639
|
-
const res = await request(port, {
|
|
640
|
-
method: 'POST', path: '/admin/scheduler/archive-prd', token, body: { slugs: [slug] },
|
|
641
|
-
});
|
|
642
|
-
expect(res.status).toBe(200);
|
|
643
|
-
expect(res.json.results[0].ok).toBe(false);
|
|
644
|
-
expect(res.json.results[0].error).toBe('not found in any PRDs dir');
|
|
645
|
-
expect(res.json.results[0].error).not.toBe('invalid slug');
|
|
646
|
-
} finally {
|
|
647
|
-
await admin.stop();
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
test('POST /admin/scheduler/archive-prd with cwd narrows the search: a cwd with no PRD dir misses, the real cwd hits', async () => {
|
|
652
|
-
const cwd = projectCwd();
|
|
653
|
-
const epicId = 'epic-archive-cwd';
|
|
654
|
-
const slug = uniqueSlug('archive-cwd-scope');
|
|
655
|
-
const filePath = await writePrdFixture({ cwd, epicId, slug });
|
|
656
|
-
|
|
657
|
-
const otherCwd = path.join(tmpHome, 'fake-project-cwd-archive-miss');
|
|
658
|
-
fs.mkdirSync(otherCwd, { recursive: true });
|
|
659
|
-
|
|
660
|
-
const { admin, port, token } = await startAdmin();
|
|
661
|
-
try {
|
|
662
|
-
const missRes = await request(port, {
|
|
663
|
-
method: 'POST', path: '/admin/scheduler/archive-prd', token, body: { slugs: [slug], cwd: otherCwd },
|
|
664
|
-
});
|
|
665
|
-
expect(missRes.json.results[0].ok).toBe(false);
|
|
666
|
-
expect(missRes.json.results[0].error).toBe('not found in any PRDs dir');
|
|
667
|
-
expect(fs.existsSync(filePath)).toBe(true);
|
|
668
|
-
|
|
669
|
-
const hitRes = await request(port, {
|
|
670
|
-
method: 'POST', path: '/admin/scheduler/archive-prd', token, body: { slugs: [slug], cwd },
|
|
671
|
-
});
|
|
672
|
-
expect(hitRes.json.results[0].ok).toBe(true);
|
|
673
|
-
expect(fs.existsSync(filePath)).toBe(false);
|
|
674
|
-
} finally {
|
|
675
|
-
await admin.stop();
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
test('GET /admin/mcp/readiness requires cwd and returns the delegation-readiness checks for it', async () => {
|
|
680
|
-
const cwd = projectCwd();
|
|
681
|
-
const { admin, port, token } = await startAdmin();
|
|
682
|
-
try {
|
|
683
|
-
const missing = await request(port, { path: '/admin/mcp/readiness', token });
|
|
684
|
-
expect(missing.status).toBe(400);
|
|
685
|
-
expect(missing.json.ok).toBe(false);
|
|
686
|
-
|
|
687
|
-
const res = await request(port, { path: `/admin/mcp/readiness?cwd=${encodeURIComponent(cwd)}`, token });
|
|
688
|
-
expect(res.status).toBe(200);
|
|
689
|
-
expect(res.json.ok).toBe(true);
|
|
690
|
-
expect(typeof res.json.ready).toBe('boolean');
|
|
691
|
-
expect(Array.isArray(res.json.checks)).toBe(true);
|
|
692
|
-
expect(res.json.checks.some((c) => c.id === 'scheduler-mcp')).toBe(true);
|
|
693
|
-
} finally {
|
|
694
|
-
await admin.stop();
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
test('POST /admin/scheduler/retag-prd rewrites estimateMinutes frontmatter', async () => {
|
|
699
|
-
const cwd = projectCwd();
|
|
700
|
-
const epicId = 'epic-retag';
|
|
701
|
-
const slug = uniqueSlug('retag-prd');
|
|
702
|
-
const filePath = await writePrdFixture({ cwd, epicId, slug });
|
|
703
|
-
|
|
704
|
-
const { admin, port, token } = await startAdmin();
|
|
705
|
-
try {
|
|
706
|
-
const res = await request(port, {
|
|
707
|
-
method: 'POST', path: '/admin/scheduler/retag-prd', token,
|
|
708
|
-
body: { items: [{ slug, estimateMinutes: 42 }] },
|
|
709
|
-
});
|
|
710
|
-
expect(res.status).toBe(200);
|
|
711
|
-
expect(res.json.ok).toBe(true);
|
|
712
|
-
expect(res.json.retagged).toBe(1);
|
|
713
|
-
const raw = await fsp.readFile(filePath, 'utf8');
|
|
714
|
-
expect(raw).toMatch(/estimateMinutes: 42/);
|
|
715
|
-
} finally {
|
|
716
|
-
await admin.stop();
|
|
717
|
-
}
|
|
718
|
-
});
|