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
|
@@ -15,6 +15,7 @@ const { spawn, spawnSync } = require('node:child_process');
|
|
|
15
15
|
const { splitFrontmatter } = require('./prdFrontmatter.cjs');
|
|
16
16
|
const { resolvePrdWriteDir } = require('./prdLocations.cjs');
|
|
17
17
|
const schedulerPaths = require('./schedulerPaths.cjs');
|
|
18
|
+
const atomicFs = require('./atomicFs.cjs');
|
|
18
19
|
|
|
19
20
|
// Regex identifying meta/dod slugs that must NOT influence the batchKey.
|
|
20
21
|
// This is the load-bearing loop-avoidance filter: when the gate job itself
|
|
@@ -89,10 +90,29 @@ function reportExists(key, runsDir = schedulerPaths.runsDir()) {
|
|
|
89
90
|
|
|
90
91
|
const DEFAULT_GATE_TIMEOUT_MS = 300_000;
|
|
91
92
|
|
|
93
|
+
// Any whitespace other than a plain space/tab, or a control char other than
|
|
94
|
+
// tab — refused everywhere a gate chain is checked, quoted or not. Why: a
|
|
95
|
+
// shell and this tokenizer must see the same bytes; plain `/\s/` would miss a
|
|
96
|
+
// no-break space, U+2028/U+2029 or U+FEFF, which a shell treats as ordinary
|
|
97
|
+
// (non-separator) characters inside a word.
|
|
98
|
+
const FOREIGN_WHITESPACE_RE = /[^\S \t]|[\u0000-\u0008\u000A-\u001F\u007F]/;
|
|
99
|
+
|
|
100
|
+
// Chars a shell treats specially — refused outside quotes. `#` and `~` are
|
|
101
|
+
// handled on their own (they are fine in the middle of a word).
|
|
102
|
+
const UNQUOTED_BAD_CHARS = '|<>;&`$\\*?[](){}!';
|
|
103
|
+
|
|
92
104
|
/**
|
|
93
|
-
* Quote-aware tokenizer for ONE command segment — no shell. Returns
|
|
94
|
-
*
|
|
95
|
-
*
|
|
105
|
+
* Quote-aware tokenizer for ONE command segment — no shell. Returns
|
|
106
|
+
* `{ ok: true, tokens }`, or `{ ok: false, error }` when the segment needs a
|
|
107
|
+
* shell to run the same way a human would read it. Rules enforced (see
|
|
108
|
+
* explainChain's doc comment for the full numbered list):
|
|
109
|
+
* - Outside quotes, only a space or tab separates words.
|
|
110
|
+
* - Outside quotes, any char in UNQUOTED_BAD_CHARS is refused.
|
|
111
|
+
* - An unquoted word may not start with `#` or `~`; `~` right after an
|
|
112
|
+
* unquoted `=` or `:` is refused too (`a#b`, `HEAD~1` are fine).
|
|
113
|
+
* - Inside double quotes, `$`, backtick and `\` are refused — a shell
|
|
114
|
+
* would expand them. Inside single quotes, everything is literal.
|
|
115
|
+
* - An unclosed quote is refused.
|
|
96
116
|
* Complexity: O(n) over the segment's characters.
|
|
97
117
|
*/
|
|
98
118
|
function tokenizeNoShell(seg) {
|
|
@@ -100,25 +120,51 @@ function tokenizeNoShell(seg) {
|
|
|
100
120
|
let cur = '';
|
|
101
121
|
let inTok = false;
|
|
102
122
|
let quote = null;
|
|
123
|
+
let atWordStart = true;
|
|
124
|
+
let afterEqualsOrColon = false;
|
|
103
125
|
for (let i = 0; i < seg.length; i++) {
|
|
104
126
|
const c = seg[i];
|
|
105
127
|
if (quote) {
|
|
106
|
-
if (c === quote) quote = null;
|
|
107
|
-
|
|
128
|
+
if (c === quote) { quote = null; continue; }
|
|
129
|
+
if (quote === '"' && (c === '$' || c === '`' || c === '\\')) {
|
|
130
|
+
return { ok: false, error: `has "${c}" inside double quotes. A shell would expand it; the scheduler would not. Use single quotes.` };
|
|
131
|
+
}
|
|
132
|
+
cur += c;
|
|
108
133
|
continue;
|
|
109
134
|
}
|
|
110
|
-
if (c === '"' || c === "'") {
|
|
111
|
-
|
|
135
|
+
if (c === '"' || c === "'") {
|
|
136
|
+
quote = c;
|
|
137
|
+
inTok = true;
|
|
138
|
+
atWordStart = false;
|
|
139
|
+
afterEqualsOrColon = false;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (c === ' ' || c === '\t') {
|
|
112
143
|
if (inTok) { tokens.push(cur); cur = ''; inTok = false; }
|
|
144
|
+
atWordStart = true;
|
|
145
|
+
afterEqualsOrColon = false;
|
|
113
146
|
continue;
|
|
114
147
|
}
|
|
115
|
-
if (
|
|
148
|
+
if (c === '#' && atWordStart) {
|
|
149
|
+
return { ok: false, error: 'has a word that starts with "#". A shell reads it as a comment. Quote it.' };
|
|
150
|
+
}
|
|
151
|
+
if (c === '~' && (atWordStart || afterEqualsOrColon)) {
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
error: 'has "~" at the start of a word or after "=" or ":". A shell would turn it into the home folder; the scheduler would not. Write the full path, or quote it.',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (UNQUOTED_BAD_CHARS.includes(c)) {
|
|
158
|
+
return { ok: false, error: `has an unquoted "${c}". The scheduler runs gate commands without a shell. Put it inside single quotes, or move the check into a test file.` };
|
|
159
|
+
}
|
|
116
160
|
cur += c;
|
|
117
161
|
inTok = true;
|
|
162
|
+
atWordStart = false;
|
|
163
|
+
afterEqualsOrColon = c === '=' || c === ':';
|
|
118
164
|
}
|
|
119
|
-
if (quote) return
|
|
165
|
+
if (quote) return { ok: false, error: 'has an unclosed quote.' };
|
|
120
166
|
if (inTok) tokens.push(cur);
|
|
121
|
-
return tokens;
|
|
167
|
+
return { ok: true, tokens };
|
|
122
168
|
}
|
|
123
169
|
|
|
124
170
|
/**
|
|
@@ -145,37 +191,83 @@ function splitOnAndAnd(text) {
|
|
|
145
191
|
}
|
|
146
192
|
|
|
147
193
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
194
|
+
* Explain whether a `&&`-joined command chain is a gate the scheduler can run
|
|
195
|
+
* without a shell, step by step. Rules, checked in this order (each failure
|
|
196
|
+
* returns its own `error`):
|
|
197
|
+
* 1. Any whitespace other than a plain space/tab, or a stray control char,
|
|
198
|
+
* anywhere in the text (quoted or not).
|
|
199
|
+
* 2. An empty step (`a && && b`, a trailing `&&`).
|
|
200
|
+
* 3-9. tokenizeNoShell's rules, applied to each step in turn.
|
|
201
|
+
* 10. A step with no command left after `NAME=value` words and `timeout N`.
|
|
202
|
+
* "Start of a word" means the first char after a separator or the start of
|
|
203
|
+
* the step, when that char is unquoted — a word may mix quoted and unquoted
|
|
204
|
+
* parts, as in bash (`a'b c'd` is one word `ab cd`).
|
|
205
|
+
*
|
|
206
|
+
* @param {string} text
|
|
207
|
+
* @returns {{ok:true, steps:Array<{argv:string[],timeoutMs:number,env:object,raw:string}>, timeoutGiven:boolean[]}|{ok:false, error:string}}
|
|
155
208
|
*/
|
|
156
|
-
function
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
209
|
+
function explainChain(text) {
|
|
210
|
+
if (FOREIGN_WHITESPACE_RE.test(text)) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
error: 'has a character that is not a plain space or tab (for example a no-break space or a line break). Retype it.',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const steps = [];
|
|
218
|
+
const timeoutGiven = [];
|
|
219
|
+
const segs = splitOnAndAnd(text);
|
|
220
|
+
for (let i = 0; i < segs.length; i++) {
|
|
221
|
+
const stepNo = i + 1;
|
|
222
|
+
const raw = segs[i].trim();
|
|
223
|
+
if (!raw) return { ok: false, error: `step ${stepNo} is empty. Check the "&&" joins.` };
|
|
224
|
+
// A dropped TMPDIR=$(mktemp -d) prefix is the one substitution we accept
|
|
225
|
+
// — the shadow runner always supplies its own isolated TMPDIR, so the
|
|
226
|
+
// un-runnable `$(mktemp -d)` is handled here by simply never evaluating it.
|
|
227
|
+
const stripped = raw.replace(/^TMPDIR=\$\(mktemp -d\)[ \t]+/, '');
|
|
228
|
+
const tokenized = tokenizeNoShell(stripped);
|
|
229
|
+
if (!tokenized.ok) return { ok: false, error: tokenized.error };
|
|
230
|
+
const tokens = tokenized.tokens;
|
|
165
231
|
const env = {};
|
|
166
232
|
while (tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0])) {
|
|
167
233
|
const [k, ...rest] = tokens.shift().split('=');
|
|
168
234
|
if (k !== 'TMPDIR') env[k] = rest.join('=');
|
|
169
235
|
}
|
|
170
236
|
let timeoutMs = DEFAULT_GATE_TIMEOUT_MS;
|
|
237
|
+
let hadTimeout = false;
|
|
171
238
|
if (tokens[0] === 'timeout' && /^\d+s?$/.test(tokens[1] || '')) {
|
|
172
239
|
timeoutMs = parseInt(tokens[1], 10) * 1000;
|
|
173
240
|
tokens.splice(0, 2);
|
|
241
|
+
hadTimeout = true;
|
|
174
242
|
}
|
|
175
|
-
if (!tokens.length) return
|
|
176
|
-
|
|
243
|
+
if (!tokens.length) return { ok: false, error: `step ${stepNo} has no command after "timeout" or NAME=value.` };
|
|
244
|
+
steps.push({ argv: tokens, timeoutMs, env, raw });
|
|
245
|
+
timeoutGiven.push(hadTimeout);
|
|
177
246
|
}
|
|
178
|
-
return
|
|
247
|
+
return { ok: true, steps, timeoutGiven };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Parse one `&&` chain into [{argv, timeoutMs, env, raw}]. Returns [] when any
|
|
252
|
+
* step is empty or needs a shell — a half-parsed gate must never run. See
|
|
253
|
+
* explainChain for the rules and the reason behind each refusal.
|
|
254
|
+
*/
|
|
255
|
+
function parseChain(text) {
|
|
256
|
+
const r = explainChain(text);
|
|
257
|
+
return r.ok ? r.steps : [];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* True when `entry` is the literal opt-out `"none"` (case-insensitive, outer
|
|
262
|
+
* whitespace ignored) — nothing else. A near-miss (`"none."`, `"'none'"`,
|
|
263
|
+
* `"NONE!"`) is NOT none; a caller that accepts a quoted YAML form strips the
|
|
264
|
+
* quotes first (see readExplicitGate).
|
|
265
|
+
*
|
|
266
|
+
* @param {unknown} entry
|
|
267
|
+
* @returns {boolean}
|
|
268
|
+
*/
|
|
269
|
+
function isNoneGate(entry) {
|
|
270
|
+
return typeof entry === 'string' && /^none$/i.test(entry.trim());
|
|
179
271
|
}
|
|
180
272
|
|
|
181
273
|
/** Locate the `# Acceptance criteria` lines (whole body when the heading is absent). */
|
|
@@ -215,7 +307,10 @@ function readExplicitGate(prdText) {
|
|
|
215
307
|
const m = fmLines[i].match(/^gate:\s*(.*)$/);
|
|
216
308
|
if (!m) continue;
|
|
217
309
|
const rest = m[1].trim();
|
|
218
|
-
|
|
310
|
+
// YAML quoting ('none' / "none") still reads as the opt-out — strip a
|
|
311
|
+
// matching pair of outer quotes, if present, before the isNoneGate test.
|
|
312
|
+
const unquotedRest = rest.replace(/^(['"])(.*)\1$/, '$2');
|
|
313
|
+
if (isNoneGate(unquotedRest)) return { kind: 'none' };
|
|
219
314
|
const chains = [];
|
|
220
315
|
if (rest.startsWith('[') && rest.endsWith(']')) {
|
|
221
316
|
// Inline list — items may hold commas inside quotes, so split quote-aware.
|
|
@@ -241,7 +336,7 @@ function readExplicitGate(prdText) {
|
|
|
241
336
|
const fence = prdText.match(/^```gate[ \t]*\r?\n([\s\S]*?)^```/m);
|
|
242
337
|
if (fence) {
|
|
243
338
|
const chains = fence[1].split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#'));
|
|
244
|
-
if (chains.length === 1 &&
|
|
339
|
+
if (chains.length === 1 && isNoneGate(chains[0])) return { kind: 'none' };
|
|
245
340
|
if (chains.length) return { kind: 'commands', chains };
|
|
246
341
|
}
|
|
247
342
|
return null;
|
|
@@ -254,7 +349,7 @@ function readExplicitGate(prdText) {
|
|
|
254
349
|
* `timeout N`). Accepts the PRD with or without frontmatter.
|
|
255
350
|
*
|
|
256
351
|
* @param {string} prdText
|
|
257
|
-
* @returns {{ source: 'none'|'explicit'|'ac-line'|'absent', sequence: Array<{argv:string[], timeoutMs:number, env:object, raw:string}> }}
|
|
352
|
+
* @returns {{ source: 'none'|'explicit'|'ac-line'|'ac-line-guess'|'absent', sequence: Array<{argv:string[], timeoutMs:number, env:object, raw:string}> }}
|
|
258
353
|
*/
|
|
259
354
|
function resolveGate(prdText) {
|
|
260
355
|
if (!prdText || typeof prdText !== 'string') return { source: 'absent', sequence: [] };
|
|
@@ -285,7 +380,9 @@ function resolveGate(prdText) {
|
|
|
285
380
|
while (tokens.length > 4 && /^[a-z]+$/.test(tokens[tokens.length - 1])) tokens.pop();
|
|
286
381
|
segs[segs.length - 1] = tokens.join(' ');
|
|
287
382
|
const seq = parseChain(segs.join('&&'));
|
|
288
|
-
|
|
383
|
+
// The command's end is a guess here ("timeout 300 npm run typecheck
|
|
384
|
+
// passes" becomes `npm run`), so gate authority never trusts it.
|
|
385
|
+
if (seq.length) return { source: 'ac-line-guess', sequence: seq };
|
|
289
386
|
}
|
|
290
387
|
}
|
|
291
388
|
return { source: 'absent', sequence: [] };
|
|
@@ -306,7 +403,8 @@ function extractAcSequence(prdBody) {
|
|
|
306
403
|
/**
|
|
307
404
|
* The single-command view of the gate, as the original `timeout NNN cmd …`
|
|
308
405
|
* string, or null when the gate is absent or is a multi-command chain
|
|
309
|
-
* (reverifyAc runs
|
|
406
|
+
* (reverifyAc runs the whole sequence; this view is for callers that only
|
|
407
|
+
* ever handle one command).
|
|
310
408
|
*
|
|
311
409
|
* @param {string} prdBody PRD markdown with frontmatter already stripped.
|
|
312
410
|
* @returns {string|null}
|
|
@@ -317,107 +415,98 @@ function extractAcCommand(prdBody) {
|
|
|
317
415
|
}
|
|
318
416
|
|
|
319
417
|
/**
|
|
320
|
-
* Re-run
|
|
418
|
+
* Re-run a completed job's authored gate and report whether it still holds.
|
|
419
|
+
*
|
|
420
|
+
* Finds the PRD with `resolvePrdPath(job)`, a caller-injected async function
|
|
421
|
+
* that returns an absolute path or null. This never guesses a path itself.
|
|
422
|
+
* Why: a PRD lives in its Epic's `prds/` dir while queued and moves to that
|
|
423
|
+
* Epic's `prds-archived/` sibling once it completes — only the caller (which
|
|
424
|
+
* knows about Epics and archiving) can say which one applies.
|
|
321
425
|
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
426
|
+
* Runs the resolved gate with `runGateSequence`, the same runner the
|
|
427
|
+
* scheduler's shadow gate uses: `&&` semantics, each run gets its own
|
|
428
|
+
* isolated TMPDIR and `CI=1`, and at most one gate runs machine-wide at a
|
|
429
|
+
* time. This never spawns `claude` and never writes to queue.json — it only
|
|
430
|
+
* re-runs the already-authored gate command.
|
|
431
|
+
*
|
|
432
|
+
* Result `status` meanings:
|
|
433
|
+
* - `pass` the gate went green.
|
|
434
|
+
* - `fail` a gate step exited non-zero, and it did not time out.
|
|
435
|
+
* - `unverifiable` nothing ran, or the result can't be trusted. `reason`
|
|
436
|
+
* says which: `no-prd-resolver` (opts.resolvePrdPath was not given),
|
|
437
|
+
* `prd-not-found` (resolver returned null/threw), `prd-unreadable`
|
|
438
|
+
* (resolved path doesn't exist or can't be read), `no-parseable-gate`
|
|
439
|
+
* (PRD opted out with `gate: none`, or has no gate the scheduler can
|
|
440
|
+
* run), `cwd-missing` (job.cwd no longer exists), `busy` (another gate
|
|
441
|
+
* is already running — see runGateSequence), `gate-unavailable` (a step
|
|
442
|
+
* could not even start), or `timeout` (a step hit its own `timeout N`
|
|
443
|
+
* and was killed — not evidence either way, so it is not reported as a
|
|
444
|
+
* failure).
|
|
324
445
|
*
|
|
325
446
|
* @param {{ slug: string, cwd: string }} job
|
|
326
|
-
* @param {{
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
* @returns {Promise<{ slug: string, status: 'pass'|'fail'|'unverifiable', code: number|null, ms: number }>}
|
|
447
|
+
* @param {{ resolvePrdPath?: (job: object) => (string|null|Promise<string|null>) }} opts
|
|
448
|
+
* resolvePrdPath Finds the PRD's current absolute path for this job.
|
|
449
|
+
* Required — with none, the result is always unverifiable.
|
|
450
|
+
* @returns {Promise<{ slug: string, status: 'pass'|'fail'|'unverifiable', code: number|null, ms: number, reason?: string }>}
|
|
331
451
|
*/
|
|
332
|
-
function reverifyAc(job, {
|
|
333
|
-
const resolvedPrdsDir = prdsDir ?? (job.cwd ? resolvePrdWriteDir(job.cwd) : null);
|
|
452
|
+
async function reverifyAc(job, { resolvePrdPath } = {}) {
|
|
334
453
|
const startNs = process.hrtime.bigint();
|
|
335
454
|
|
|
336
455
|
function elapsedMs() {
|
|
337
456
|
return Math.round(Number(process.hrtime.bigint() - startNs) / 1e6);
|
|
338
457
|
}
|
|
339
458
|
|
|
340
|
-
function unverifiable() {
|
|
341
|
-
return { slug: job.slug, status: 'unverifiable', code: null, ms: elapsedMs() };
|
|
459
|
+
function unverifiable(reason) {
|
|
460
|
+
return { slug: job.slug, status: 'unverifiable', code: null, ms: elapsedMs(), reason };
|
|
342
461
|
}
|
|
343
462
|
|
|
344
463
|
// Guard: cwd must exist (target project may have been deleted).
|
|
345
464
|
try {
|
|
346
465
|
fs.statSync(job.cwd);
|
|
347
466
|
} catch {
|
|
348
|
-
return
|
|
467
|
+
return unverifiable('cwd-missing');
|
|
349
468
|
}
|
|
350
469
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
let
|
|
470
|
+
if (typeof resolvePrdPath !== 'function') return unverifiable('no-prd-resolver');
|
|
471
|
+
|
|
472
|
+
let prdPath;
|
|
354
473
|
try {
|
|
355
|
-
|
|
356
|
-
prdBody = splitFrontmatter(raw).body;
|
|
474
|
+
prdPath = await resolvePrdPath(job);
|
|
357
475
|
} catch {
|
|
358
|
-
|
|
476
|
+
prdPath = null;
|
|
359
477
|
}
|
|
478
|
+
if (!prdPath) return unverifiable('prd-not-found');
|
|
360
479
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return new Promise((resolve) => {
|
|
371
|
-
let settled = false;
|
|
372
|
-
|
|
373
|
-
let child;
|
|
374
|
-
try {
|
|
375
|
-
child = spawn(argv[0], argv.slice(1), {
|
|
376
|
-
cwd: job.cwd,
|
|
377
|
-
stdio: 'ignore',
|
|
378
|
-
// No shell:true — tokenizeNoShell rejects commands with shell metacharacters.
|
|
379
|
-
});
|
|
380
|
-
} catch (err) {
|
|
381
|
-
resolve(unverifiable());
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
let escalate;
|
|
386
|
-
const killTimer = setTimeout(() => {
|
|
387
|
-
try { child.kill('SIGTERM'); } catch { /* already dead */ }
|
|
388
|
-
escalate = setTimeout(() => {
|
|
389
|
-
try { child.kill('SIGKILL'); } catch { /* race */ }
|
|
390
|
-
}, 5_000);
|
|
391
|
-
if (escalate.unref) escalate.unref();
|
|
392
|
-
}, timeoutMs);
|
|
393
|
-
if (killTimer.unref) killTimer.unref();
|
|
394
|
-
|
|
395
|
-
child.on('error', () => {
|
|
396
|
-
if (settled) return;
|
|
397
|
-
settled = true;
|
|
398
|
-
clearTimeout(killTimer);
|
|
399
|
-
clearTimeout(escalate);
|
|
400
|
-
resolve(unverifiable());
|
|
401
|
-
});
|
|
480
|
+
// Read the raw PRD text, frontmatter included, and resolve its gate the
|
|
481
|
+
// same way the shadow gate does.
|
|
482
|
+
let raw;
|
|
483
|
+
try {
|
|
484
|
+
raw = fs.readFileSync(prdPath, 'utf8');
|
|
485
|
+
} catch {
|
|
486
|
+
return unverifiable('prd-unreadable');
|
|
487
|
+
}
|
|
402
488
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
489
|
+
const { source, sequence } = resolveGate(raw);
|
|
490
|
+
if (source === 'none' || source === 'absent' || !sequence.length) return unverifiable('no-parseable-gate');
|
|
491
|
+
|
|
492
|
+
const result = await runGateSequence(sequence, { cwd: job.cwd });
|
|
493
|
+
if (result.status === 'green') return { slug: job.slug, status: 'pass', code: 0, ms: elapsedMs() };
|
|
494
|
+
if (result.status === 'busy') return unverifiable('busy');
|
|
495
|
+
if (result.status === 'unavailable') return unverifiable('gate-unavailable');
|
|
496
|
+
|
|
497
|
+
// status === 'red': a step exited non-zero. A step that was killed for
|
|
498
|
+
// running past its own `timeout N` is not trustworthy evidence of a real
|
|
499
|
+
// failure (the machine may just be slow right now), so it is reported as
|
|
500
|
+
// unverifiable instead of fail — same reasoning reverifyAc has always used
|
|
501
|
+
// for a step that couldn't even start.
|
|
502
|
+
const timedOutStep = result.results.find((r) => r.timedOut);
|
|
503
|
+
if (timedOutStep) return unverifiable('timeout');
|
|
504
|
+
const failedStep = result.results.find((r) => r.status === 'fail');
|
|
505
|
+
return { slug: job.slug, status: 'fail', code: failedStep ? failedStep.code : null, ms: elapsedMs() };
|
|
417
506
|
}
|
|
418
507
|
|
|
419
508
|
/**
|
|
420
|
-
* Re-run AC
|
|
509
|
+
* Re-run AC gates sequentially over a batch of completed jobs.
|
|
421
510
|
*
|
|
422
511
|
* Sequential execution respects the machine's max-3-concurrent rule — a drain
|
|
423
512
|
* event that fires reverifyBatch is already consuming one slot; sequential
|
|
@@ -431,13 +520,12 @@ function reverifyAc(job, { timeoutMs = 60_000, prdsDir } = {}) {
|
|
|
431
520
|
* by the scheduler queue size, not user-scaled data).
|
|
432
521
|
*
|
|
433
522
|
* @param {Array<{ slug: string, cwd: string }>} jobs
|
|
434
|
-
* @param {{
|
|
435
|
-
* timeoutMs Per-job kill ceiling (default 60s).
|
|
523
|
+
* @param {{ batchTimeoutMs?: number, resolvePrdPath?: (job: object) => (string|null|Promise<string|null>) }} opts
|
|
436
524
|
* batchTimeoutMs Total wall-time cap for the whole batch (default 10m).
|
|
437
|
-
*
|
|
525
|
+
* resolvePrdPath Forwarded to reverifyAc for every job — see its doc comment.
|
|
438
526
|
* @returns {Promise<Array<{ slug: string, status: string, code: number|null, ms: number }>>}
|
|
439
527
|
*/
|
|
440
|
-
async function reverifyBatch(jobs, {
|
|
528
|
+
async function reverifyBatch(jobs, { batchTimeoutMs = 600_000, resolvePrdPath } = {}) {
|
|
441
529
|
const batchStartNs = process.hrtime.bigint();
|
|
442
530
|
const results = [];
|
|
443
531
|
|
|
@@ -447,7 +535,7 @@ async function reverifyBatch(jobs, { timeoutMs = 60_000, batchTimeoutMs = 600_00
|
|
|
447
535
|
results.push({ slug: job.slug, status: 'unverifiable', code: null, ms: 0 });
|
|
448
536
|
continue;
|
|
449
537
|
}
|
|
450
|
-
const result = await reverifyAc(job, {
|
|
538
|
+
const result = await reverifyAc(job, { resolvePrdPath });
|
|
451
539
|
results.push(result);
|
|
452
540
|
}
|
|
453
541
|
|
|
@@ -560,18 +648,9 @@ function flagRiskySurfaces(jobs, { prdsDir, gitTimeoutMs = 10_000 } = {}) {
|
|
|
560
648
|
}
|
|
561
649
|
|
|
562
650
|
// ─── Atomic write helper ───────────────────────────────────────────────────────
|
|
563
|
-
//
|
|
564
|
-
// variant), avoiding an import of Electron IPC code in a pure-node context.
|
|
565
|
-
// Cross-ref: src/main/config.cjs writeJsonSync (same pattern).
|
|
651
|
+
// Delegates to atomicFs.cjs's tmp+rename recipe (pure-node, no Electron import).
|
|
566
652
|
function _writeFileAtomic(absPath, text) {
|
|
567
|
-
|
|
568
|
-
try {
|
|
569
|
-
fs.writeFileSync(tmp, text, 'utf8');
|
|
570
|
-
fs.renameSync(tmp, absPath);
|
|
571
|
-
} catch (err) {
|
|
572
|
-
try { fs.unlinkSync(tmp); } catch { /* tmp never created or already gone */ }
|
|
573
|
-
throw err;
|
|
574
|
-
}
|
|
653
|
+
atomicFs.writeTextAtomicSync(absPath, text);
|
|
575
654
|
}
|
|
576
655
|
|
|
577
656
|
const STATUS_EMOJI = { pass: '✅', fail: '❌', unverifiable: '⚠️' };
|
|
@@ -811,6 +890,9 @@ module.exports = {
|
|
|
811
890
|
reportExists,
|
|
812
891
|
extractAcCommand,
|
|
813
892
|
extractAcSequence,
|
|
893
|
+
parseChain,
|
|
894
|
+
explainChain,
|
|
895
|
+
isNoneGate,
|
|
814
896
|
resolveGate,
|
|
815
897
|
runGateSequence,
|
|
816
898
|
extractSection,
|
|
@@ -47,14 +47,20 @@ const _inFlight = new Set();
|
|
|
47
47
|
*
|
|
48
48
|
* @param {{ paused: object|null, jobs: Array }} state Scheduler queue state.
|
|
49
49
|
* @param {{
|
|
50
|
-
* cancelToken?:
|
|
51
|
-
* prdsDir?:
|
|
52
|
-
* runsDir?:
|
|
50
|
+
* cancelToken?: { cancelled: boolean },
|
|
51
|
+
* prdsDir?: string,
|
|
52
|
+
* runsDir?: string,
|
|
53
|
+
* resolvePrdPath?: (job: object) => (string|null|Promise<string|null>),
|
|
53
54
|
* }} opts
|
|
55
|
+
* prdsDir Forwarded to flagRiskySurfaces only (its own PRD lookup).
|
|
56
|
+
* resolvePrdPath Forwarded to reverifyBatch/reverifyAc — finds each job's
|
|
57
|
+
* PRD across the live Epic dir and its archive. Required
|
|
58
|
+
* for a real AC re-run; with none, every job in the report
|
|
59
|
+
* comes back unverifiable (see reverifyAc's doc comment).
|
|
54
60
|
* @returns {Promise<void>}
|
|
55
61
|
*/
|
|
56
62
|
async function runDefinitionOfDoneOnDrain(state, opts = {}) {
|
|
57
|
-
const { cancelToken = {}, prdsDir, runsDir } = opts;
|
|
63
|
+
const { cancelToken = {}, prdsDir, runsDir, resolvePrdPath } = opts;
|
|
58
64
|
|
|
59
65
|
if (process.env.SM_DOD_DISABLE === '1') return;
|
|
60
66
|
if (state.paused) return;
|
|
@@ -82,7 +88,7 @@ async function runDefinitionOfDoneOnDrain(state, opts = {}) {
|
|
|
82
88
|
|
|
83
89
|
_inFlight.add(key);
|
|
84
90
|
try {
|
|
85
|
-
const acResults = await reverifyBatch(completedJobs, {
|
|
91
|
+
const acResults = await reverifyBatch(completedJobs, { resolvePrdPath });
|
|
86
92
|
// Re-check after the slow await — scheduler may have stopped mid-flight.
|
|
87
93
|
if (cancelToken.cancelled) return;
|
|
88
94
|
const riskFlags = flagRiskySurfaces(completedJobs, { prdsDir });
|
|
@@ -35,6 +35,7 @@ const { resolveEpicPrdWriteDir } = require('./prdLocations.cjs');
|
|
|
35
35
|
const { assertOpsWrite } = require('./opsOwnership.cjs');
|
|
36
36
|
const { appendAuditEvent } = require('./auditLog.cjs');
|
|
37
37
|
const { mirrorEpicStatus, removeEpicMirror } = require('./epicStatusMirror.cjs');
|
|
38
|
+
const atomicFs = require('./atomicFs.cjs');
|
|
38
39
|
const telemetryCounters = require('./telemetryCounters.cjs');
|
|
39
40
|
// Required as the module object (not destructured) so a test can
|
|
40
41
|
// monkeypatch promptSessionSchema.assertValidPromptSession in place to
|
|
@@ -106,10 +107,7 @@ function writeActiveIndex(cwd, index) {
|
|
|
106
107
|
// holds a narrow delegation for active-index.json only (opsOwnership.cjs).
|
|
107
108
|
assertOpsWrite(activeIndexPath(cwd), 'scheduler');
|
|
108
109
|
const file = activeIndexPath(cwd);
|
|
109
|
-
|
|
110
|
-
const tmp = `${file}.tmp-${process.pid}`;
|
|
111
|
-
fs.writeFileSync(tmp, JSON.stringify(index, null, 2));
|
|
112
|
-
fs.renameSync(tmp, file);
|
|
110
|
+
atomicFs.writeJsonAtomicSync(file, index, { newline: false });
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
// index.sessions/index.events are plain objects parsed from JSON — bracket
|
|
@@ -15,18 +15,19 @@
|
|
|
15
15
|
* file this module targets; a live (proposed/active) Epic gets a sparse
|
|
16
16
|
* mirror here instead, later overwritten wholesale by the real archive.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* Uses atomicFs.cjs (NOT config.cjs's `writeJson`) so this module stays
|
|
19
19
|
* requirable from epicMint.cjs, which is deliberately Electron-free (its own
|
|
20
20
|
* header: "so the external watchdog scripts can require it"; config.cjs
|
|
21
|
-
* requires 'electron' at module load). The write
|
|
22
|
-
* writeJson exactly (`JSON.stringify(data, null,
|
|
23
|
-
*
|
|
24
|
-
* epicMint.cjs never diverge in format despite using different write
|
|
21
|
+
* requires 'electron' at module load — atomicFs.cjs does not). The write
|
|
22
|
+
* shape matches config.cjs's writeJson exactly (`JSON.stringify(data, null,
|
|
23
|
+
* 2) + '\n'`, tmp-then-rename) so main-process callers (activeIndexMerge.cjs)
|
|
24
|
+
* and epicMint.cjs never diverge in format despite using different write
|
|
25
|
+
* paths — both go through the same atomicFs primitive now.
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
const fs = require('node:fs');
|
|
28
|
-
const path = require('node:path');
|
|
29
29
|
const { assertOpsWrite, opsPath } = require('./opsOwnership.cjs');
|
|
30
|
+
const atomicFs = require('./atomicFs.cjs');
|
|
30
31
|
|
|
31
32
|
function epicMirrorPath(cwd, epicId) {
|
|
32
33
|
return opsPath(cwd, 'prompt-sessions', `${epicId}.json`);
|
|
@@ -74,10 +75,7 @@ function mirrorEpicStatus(cwd, epicId, { session = null, status = null, archived
|
|
|
74
75
|
archivedAt: archivedAt ?? existing.archivedAt ?? null,
|
|
75
76
|
indexedAt: new Date().toISOString(),
|
|
76
77
|
};
|
|
77
|
-
|
|
78
|
-
const tmp = `${file}.tmp-${process.pid}`;
|
|
79
|
-
fs.writeFileSync(tmp, JSON.stringify(merged, null, 2) + '\n');
|
|
80
|
-
fs.renameSync(tmp, file);
|
|
78
|
+
atomicFs.writeJsonAtomicSync(file, merged);
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
/**
|
|
@@ -36,6 +36,7 @@ const fs = require('node:fs');
|
|
|
36
36
|
const path = require('node:path');
|
|
37
37
|
const os = require('node:os');
|
|
38
38
|
const { assertOpsWrite, opsPath } = require('./opsOwnership.cjs');
|
|
39
|
+
const atomicFs = require('./atomicFs.cjs');
|
|
39
40
|
|
|
40
41
|
// The old machine-wide map. Read-only from here on (migration source only) —
|
|
41
42
|
// never written again, and deliberately never deleted so unmigrated projects
|
|
@@ -76,10 +77,7 @@ function readPerProjectConfigFile(cwd) {
|
|
|
76
77
|
function writePerProjectConfig(cwd, config, writer = 'epics') {
|
|
77
78
|
const file = perProjectConfigPath(cwd);
|
|
78
79
|
assertOpsWrite(file, writer);
|
|
79
|
-
|
|
80
|
-
const tmp = `${file}.tmp-${process.pid}`;
|
|
81
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n');
|
|
82
|
-
fs.renameSync(tmp, file);
|
|
80
|
+
atomicFs.writeJsonAtomicSync(file, config);
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
/**
|