claude-code-session-manager 0.75.3 → 0.77.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.
Files changed (164) hide show
  1. package/dist/assets/{AgentLibrary-CzQqcObq.js → AgentLibrary-B2ie8bbw.js} +2 -2
  2. package/dist/assets/{DataModel-Bj_WlLz8.js → DataModel-BIJPYw32.js} +1 -1
  3. package/dist/assets/{History-DnSi_OHm.js → History-CeY6dk9S.js} +2 -2
  4. package/dist/assets/{Hooks-0BB0dp3S.js → Hooks-BFH2ocKg.js} +2 -2
  5. package/dist/assets/{HostBilko-DHpwwsLQ.js → HostBilko-36gj9wLz.js} +1 -1
  6. package/dist/assets/{Library-CaJVqVvi.js → Library-C-hBct39.js} +1 -1
  7. package/dist/assets/{ListDetail-C1W2HmC2.js → ListDetail-CNq64VWV.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-5Ob9FW3z.js → MarkdownEditor-Bh3qt5-1.js} +1 -1
  9. package/dist/assets/{McpServers-JxCSfm1S.js → McpServers-DpGN0oyz.js} +1 -1
  10. package/dist/assets/{Memory-BDeqlqwH.js → Memory-D59hUjC4.js} +6 -6
  11. package/dist/assets/{Panel-Dh9ZHuEj.js → Panel-DCgbaoci.js} +1 -1
  12. package/dist/assets/{Permissions-DXy-CbEY.js → Permissions-DAmQ0DYV.js} +2 -2
  13. package/dist/assets/{Plugins-_n1Iuc8T.js → Plugins-Dyfgn6Is.js} +2 -2
  14. package/dist/assets/{ProvenanceBadge-BP_evfxE.js → ProvenanceBadge-BiYhPO1U.js} +1 -1
  15. package/dist/assets/SaveBar-RV7B6sOh.js +1 -0
  16. package/dist/assets/Scheduler-BPaNqx1b.js +14 -0
  17. package/dist/assets/{ScopeSwitcher-CAWzM6RI.js → ScopeSwitcher-P4mdLGNU.js} +1 -1
  18. package/dist/assets/{Settings-DRRozLyT.js → Settings-BL4vf5aX.js} +1 -1
  19. package/dist/assets/{SkillReferenceGraph-DGHDWlz4.js → SkillReferenceGraph-BRBDyi1_.js} +1 -1
  20. package/dist/assets/{Skills-D8L66eiX.js → Skills-BV08gDUH.js} +2 -2
  21. package/dist/assets/{SystemPrompt-CYtUsonD.js → SystemPrompt-CLftSsDw.js} +1 -1
  22. package/dist/assets/TagLibrary-Bp8jGsd5.js +1 -0
  23. package/dist/assets/{TiptapBody-B2hRgbPE.js → TiptapBody-jCpuB6E5.js} +1 -1
  24. package/dist/assets/{Toggle-BTwsbxam.js → Toggle-D2paA1xf.js} +1 -1
  25. package/dist/assets/{index-DijufvkJ.js → index-BDRSqBl3.js} +704 -704
  26. package/dist/assets/{index-CMLnzdZC.css → index-CYhdtisq.css} +1 -1
  27. package/dist/assets/{settingsSchema-D6wzxAi6.js → settingsSchema-6IOLjZZN.js} +1 -1
  28. package/dist/index.html +2 -2
  29. package/package.json +8 -2
  30. package/plugins/session-manager-dev/skills/develop/standards.md +1 -1
  31. package/scripts/lib/activeSessions.cjs +116 -6
  32. package/scripts/project-pages-logic/dist/logic.cjs +4709 -0
  33. package/scripts/render-project-pages/dist/renderer.cjs +18900 -0
  34. package/scripts/render-project-pages.cjs +70 -0
  35. package/scripts/scheduler-mcp-server.cjs +269 -96
  36. package/scripts/validate-project-pages-summary.cjs +62 -0
  37. package/src/main/__tests__/agentModelResolve.test.cjs +66 -0
  38. package/src/main/__tests__/epicStatusMirror.test.cjs +110 -0
  39. package/src/main/__tests__/health-delegation-chain.test.cjs +106 -0
  40. package/src/main/__tests__/prdAdminRoutes.test.cjs +295 -0
  41. package/src/main/__tests__/prdAgentType.test.cjs +103 -0
  42. package/src/main/__tests__/prdCreate.test.cjs +247 -0
  43. package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +117 -0
  44. package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +108 -0
  45. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +485 -0
  46. package/src/main/__tests__/projectPages.test.cjs +73 -1
  47. package/src/main/__tests__/rcaReport.test.cjs +54 -0
  48. package/src/main/__tests__/runVerify.test.cjs +94 -0
  49. package/src/main/__tests__/scheduler-autofix-select.test.cjs +58 -3
  50. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +103 -0
  51. package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +41 -0
  52. package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +10 -0
  53. package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +78 -0
  54. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +242 -0
  55. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +31 -0
  56. package/src/main/__tests__/scheduler-launch-failure.test.cjs +201 -0
  57. package/src/main/__tests__/scheduler-leftover-fields.test.cjs +52 -0
  58. package/src/main/__tests__/scheduler-looks-done.test.cjs +241 -0
  59. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +135 -0
  60. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +222 -0
  61. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +207 -1
  62. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +212 -0
  63. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +185 -0
  64. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +194 -0
  65. package/src/main/__tests__/seedAgentPersonas.test.cjs +75 -14
  66. package/src/main/__tests__/seedSchedulerMcp.test.cjs +66 -0
  67. package/src/main/__tests__/uniquePrdNumbers.test.cjs +14 -5
  68. package/src/main/bilkoHost.cjs +4 -3
  69. package/src/main/chatRunner.cjs +6 -1
  70. package/src/main/config.cjs +25 -33
  71. package/src/main/health.cjs +153 -2
  72. package/src/main/index.cjs +64 -5
  73. package/src/main/ipcSchemas.cjs +69 -1
  74. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +179 -0
  75. package/src/main/lib/__tests__/childWithLog.test.cjs +141 -0
  76. package/src/main/lib/__tests__/delegationReadiness.test.cjs +391 -42
  77. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +91 -0
  78. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +5 -3
  79. package/src/main/lib/__tests__/fixChainDepth.test.cjs +40 -0
  80. package/src/main/lib/__tests__/gitWorktree.test.cjs +290 -5
  81. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +107 -0
  82. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +153 -0
  83. package/src/main/lib/__tests__/jobWorktree.test.cjs +6 -4
  84. package/src/main/lib/__tests__/landedSinceRun.test.cjs +73 -0
  85. package/src/main/lib/__tests__/launchFailure.test.cjs +220 -0
  86. package/src/main/lib/__tests__/loadGate.test.cjs +159 -0
  87. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +102 -0
  88. package/src/main/lib/__tests__/opsOwnership.test.cjs +7 -0
  89. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +151 -0
  90. package/src/main/lib/__tests__/opsRootResolve.test.cjs +149 -0
  91. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +82 -0
  92. package/src/main/lib/__tests__/projectRootResolve.test.cjs +148 -0
  93. package/src/main/lib/__tests__/queueHealth.test.cjs +58 -0
  94. package/src/main/lib/__tests__/quietMachineLease.test.cjs +39 -0
  95. package/src/main/lib/__tests__/reaperHelpers.test.cjs +133 -0
  96. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +19 -9
  97. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +213 -0
  98. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +125 -0
  99. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +127 -0
  100. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +109 -0
  101. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +71 -0
  102. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +217 -0
  103. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +350 -0
  104. package/src/main/lib/activeIndexMerge.cjs +15 -0
  105. package/src/main/lib/activeIndexRebuild.cjs +133 -0
  106. package/src/main/lib/agentModelResolve.cjs +58 -0
  107. package/src/main/lib/buildTarget.cjs +3 -2
  108. package/src/main/lib/childWithLog.cjs +69 -2
  109. package/src/main/lib/claudeBin.cjs +54 -1
  110. package/src/main/lib/crossProjectFeedback.cjs +8 -1
  111. package/src/main/lib/definitionOfDone.cjs +3 -2
  112. package/src/main/lib/delegationReadiness.cjs +514 -26
  113. package/src/main/lib/ephemeralCwd.cjs +78 -0
  114. package/src/main/lib/epicDelegationStats.cjs +2 -1
  115. package/src/main/lib/epicMint.cjs +17 -1
  116. package/src/main/lib/epicStatusMirror.cjs +95 -0
  117. package/src/main/lib/epicValidationHook.cjs +2 -1
  118. package/src/main/lib/epicWorktreeMint.cjs +5 -2
  119. package/src/main/lib/fixChainDepth.cjs +45 -0
  120. package/src/main/lib/gitWorktree.cjs +520 -21
  121. package/src/main/lib/jobWorktree.cjs +2 -0
  122. package/src/main/lib/landedSinceRun.cjs +55 -0
  123. package/src/main/lib/launchFailure.cjs +357 -0
  124. package/src/main/lib/loadGate.cjs +134 -0
  125. package/src/main/lib/mcpToolCatalog.cjs +370 -0
  126. package/src/main/lib/opsErrorLog.cjs +12 -1
  127. package/src/main/lib/opsOwnership.cjs +106 -0
  128. package/src/main/lib/prdAdminRoutes.cjs +43 -3
  129. package/src/main/lib/prdAgentType.cjs +84 -0
  130. package/src/main/lib/prdCreate.cjs +103 -15
  131. package/src/main/lib/prdDeclaredPaths.cjs +70 -0
  132. package/src/main/lib/prdFrontmatter.cjs +17 -3
  133. package/src/main/lib/prdLocations.cjs +13 -6
  134. package/src/main/lib/projectHomeAdminRoutes.cjs +402 -0
  135. package/src/main/lib/projectPageSummarySchema.cjs +181 -0
  136. package/src/main/lib/projectRootResolve.cjs +134 -0
  137. package/src/main/lib/promptSessionSchema.cjs +7 -0
  138. package/src/main/lib/queueHealth.cjs +38 -0
  139. package/src/main/lib/queueStore.cjs +40 -7
  140. package/src/main/lib/quietMachineLease.cjs +48 -0
  141. package/src/main/lib/rcaReport.cjs +54 -4
  142. package/src/main/lib/reaperHelpers.cjs +64 -1
  143. package/src/main/lib/scheduleJobSchema.cjs +31 -0
  144. package/src/main/lib/scheduleJobTransitions.cjs +6 -2
  145. package/src/main/lib/schedulerBatch.cjs +301 -55
  146. package/src/main/lib/schedulerConfig.cjs +99 -0
  147. package/src/main/projectBrief.cjs +3 -2
  148. package/src/main/projectPages.cjs +162 -3
  149. package/src/main/promptSessionTranscript.cjs +0 -0
  150. package/src/main/pty.cjs +5 -0
  151. package/src/main/queueOps.cjs +15 -8
  152. package/src/main/runVerify.cjs +50 -9
  153. package/src/main/scheduler/prdParser.cjs +18 -1
  154. package/src/main/scheduler.cjs +1701 -130
  155. package/src/main/seedAgentPersonas.cjs +62 -21
  156. package/src/main/seedSchedulerMcp.cjs +58 -4
  157. package/src/main/templates/project-pages-catalog.json +741 -0
  158. package/src/main/templates/project-pages-pipeline.md +417 -0
  159. package/src/preload/api.d.ts +187 -3
  160. package/src/preload/index.cjs +9 -0
  161. package/src/seed/agents/project-home-builder.md +59 -0
  162. package/dist/assets/SaveBar-D-gCUx4n.js +0 -1
  163. package/dist/assets/Scheduler-Bpd4OGju.js +0 -14
  164. package/dist/assets/TagLibrary-E5CLeuVk.js +0 -1
@@ -40,7 +40,8 @@ const APPLICATION_SOURCE_PREFIXES = ['src/', 'scripts/', 'plugins/', 'bin/'];
40
40
  function countPrdsQueued(cwd, epicId, deps = {}) {
41
41
  const readdirSync = deps.readdirSync || fs.readdirSync;
42
42
  try {
43
- const dir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', epicId, 'prds');
43
+ const { opsPath } = require('./opsOwnership.cjs');
44
+ const dir = opsPath(cwd, 'scheduler', 'epics', epicId, 'prds');
44
45
  return readdirSync(dir).filter((name) => name.endsWith('.md')).length;
45
46
  } catch {
46
47
  return 0;
@@ -34,6 +34,7 @@ const crypto = require('node:crypto');
34
34
  const { resolveEpicPrdWriteDir } = require('./prdLocations.cjs');
35
35
  const { assertOpsWrite } = require('./opsOwnership.cjs');
36
36
  const { appendAuditEvent } = require('./auditLog.cjs');
37
+ const { mirrorEpicStatus, removeEpicMirror } = require('./epicStatusMirror.cjs');
37
38
  // Required as the module object (not destructured) so a test can
38
39
  // monkeypatch promptSessionSchema.assertValidPromptSession in place to
39
40
  // simulate a corrupted construction — the real construction below is
@@ -74,7 +75,10 @@ const MINT_AUTHORITIES = Object.freeze([
74
75
  ]);
75
76
 
76
77
  function activeIndexPath(cwd) {
77
- return path.join(cwd, 'session-manager-operations', 'prompt-sessions', 'active-index.json');
78
+ // Lazy: opsOwnership → ephemeralCwd → activeSessions; this module is
79
+ // required early by several of those neighbours.
80
+ const { opsPath } = require('./opsOwnership.cjs');
81
+ return opsPath(cwd, 'prompt-sessions', 'active-index.json');
78
82
  }
79
83
 
80
84
  function readActiveIndex(cwd) {
@@ -362,6 +366,13 @@ function ensureEpic(cwd, { goalText, tag, epicId: explicitEpicId, status = 'prop
362
366
  index.sessions[epicId] = session;
363
367
  index.events[epicId] = [firstEvent];
364
368
  writeActiveIndex(cwd, index);
369
+ // Status mirror (see epicStatusMirror.cjs's header) — every Epic is born
370
+ // 'proposed', so this is the FIRST mirror write for this id, inside the
371
+ // same withPathLock task the index write above just ran in. Carries the
372
+ // full validated `session` object (not just status) so a later rebuild
373
+ // (activeIndexRebuild.cjs) can reconstruct a complete row from this file
374
+ // alone.
375
+ mirrorEpicStatus(cwd, epicId, { session, status });
365
376
 
366
377
  // Every mint is logged — the trace-back point for "who created this Epic"
367
378
  // (see auditLog.cjs).
@@ -416,6 +427,10 @@ function removeEpic(cwd, epicId) {
416
427
  delete index.sessions[epicId];
417
428
  delete index.events[epicId];
418
429
  writeActiveIndex(cwd, index);
430
+ // Undo the mirror written by ensureEpic's mint branch too — otherwise
431
+ // the rolled-back id would linger as an orphan_file (a mirrored status
432
+ // with no index row and no tombstone) in health.cjs's epic_index check.
433
+ removeEpicMirror(cwd, epicId);
419
434
  return true;
420
435
  });
421
436
  }
@@ -429,6 +444,7 @@ module.exports = {
429
444
  removeEpic,
430
445
  activeIndexPath,
431
446
  readActiveIndex,
447
+ writeActiveIndex,
432
448
  personaFileExists,
433
449
  resolvePersonaPaths,
434
450
  // Exported so lib/activeIndexMerge.cjs's renderer-facing merge IPC handler
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * epicStatusMirror.cjs — mirrors an Epic's lifecycle `status` onto its own
5
+ * durable file, `prompt-sessions/<epicId>.json`, every time a write path
6
+ * changes that status in `active-index.json`.
7
+ *
8
+ * Why this exists: `active-index.json` is the ONLY place an Epic's status
9
+ * lives today. A lost, clobbered, git-reverted, or worktree-snapshotted index
10
+ * silently erases every open Epic it held, with no way to recover them —
11
+ * `activeIndexRebuild.cjs`'s `rebuildActiveIndex` is that recovery path, and
12
+ * it reconstructs rows from exactly the mirror this module writes. The
13
+ * archive file `markCompleted()` already writes at completion (session/
14
+ * events/transcript/archivedAt — see prompt-sessions/README.md) is the same
15
+ * file this module targets; a live (proposed/active) Epic gets a sparse
16
+ * mirror here instead, later overwritten wholesale by the real archive.
17
+ *
18
+ * Raw fs tmp+rename — NOT config.cjs's `writeJson` — so this module stays
19
+ * requirable from epicMint.cjs, which is deliberately Electron-free (its own
20
+ * header: "so the external watchdog scripts can require it"; config.cjs
21
+ * requires 'electron' at module load). The write shape matches config.cjs's
22
+ * writeJson exactly (`JSON.stringify(data, null, 2) + '\n'`, `<path>.tmp-
23
+ * <pid>` then rename) so main-process callers (activeIndexMerge.cjs) and
24
+ * epicMint.cjs never diverge in format despite using different write paths.
25
+ */
26
+
27
+ const fs = require('node:fs');
28
+ const path = require('node:path');
29
+ const { assertOpsWrite, opsPath } = require('./opsOwnership.cjs');
30
+
31
+ function epicMirrorPath(cwd, epicId) {
32
+ return opsPath(cwd, 'prompt-sessions', `${epicId}.json`);
33
+ }
34
+
35
+ function readExistingMirror(file) {
36
+ try {
37
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
38
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
39
+ } catch {
40
+ return {};
41
+ }
42
+ }
43
+
44
+ /**
45
+ * mirrorEpicStatus(cwd, epicId, { session?, status?, archivedAt?, writer? })
46
+ * — read-merge-write onto `prompt-sessions/<epicId>.json`, preserving
47
+ * whatever else is already there (e.g. a full `PromptSessionArchive` written
48
+ * by `markCompleted`, or a prior mirror).
49
+ *
50
+ * When `session` (the full PromptSession record) is given, it is spread onto
51
+ * the file too — not just the four mirror fields — so a live (proposed/
52
+ * active) Epic's file alone is enough for `activeIndexRebuild.cjs` to
53
+ * reconstruct a complete `active-index.json` row, not just its status.
54
+ * `status`/`archivedAt` may be passed independently of `session` (or to
55
+ * override `session.status`) for callers that only know the status.
56
+ *
57
+ * Never throws on a read failure (a missing/corrupt existing file is treated
58
+ * as "start fresh"); a write failure propagates, matching every other atomic
59
+ * writer in this codebase (config.cjs's writeTextAtomic, epicMint.cjs's own
60
+ * writeActiveIndex).
61
+ */
62
+ function mirrorEpicStatus(cwd, epicId, { session = null, status = null, archivedAt = null, writer = 'epics' } = {}) {
63
+ const resolvedStatus = status || (session && session.status) || null;
64
+ if (!cwd || !epicId || !resolvedStatus) return;
65
+ const file = epicMirrorPath(cwd, epicId);
66
+ assertOpsWrite(file, writer);
67
+ const existing = readExistingMirror(file);
68
+ const merged = {
69
+ ...existing,
70
+ ...(session || {}),
71
+ id: epicId,
72
+ cwd,
73
+ status: resolvedStatus,
74
+ archivedAt: archivedAt ?? existing.archivedAt ?? null,
75
+ indexedAt: new Date().toISOString(),
76
+ };
77
+ fs.mkdirSync(path.dirname(file), { recursive: true });
78
+ const tmp = `${file}.tmp-${process.pid}`;
79
+ fs.writeFileSync(tmp, JSON.stringify(merged, null, 2) + '\n');
80
+ fs.renameSync(tmp, file);
81
+ }
82
+
83
+ /**
84
+ * removeEpicMirror(cwd, epicId) — best-effort delete of the mirror file, used
85
+ * only by epicMint.cjs's removeEpic() rollback (undoing a mint whose caller
86
+ * failed to complete its own work). Never throws — a missing file is a no-op.
87
+ */
88
+ function removeEpicMirror(cwd, epicId) {
89
+ if (!cwd || !epicId) return;
90
+ try {
91
+ fs.unlinkSync(epicMirrorPath(cwd, epicId));
92
+ } catch { /* never existed, or already gone */ }
93
+ }
94
+
95
+ module.exports = { mirrorEpicStatus, removeEpicMirror, epicMirrorPath };
@@ -62,7 +62,8 @@ function pairKey(epicId, prdSlug) {
62
62
  */
63
63
  function defaultReadActiveIndex(cwd) {
64
64
  try {
65
- const p = path.join(cwd, 'session-manager-operations', 'prompt-sessions', 'active-index.json');
65
+ const { opsPath } = require('./opsOwnership.cjs');
66
+ const p = opsPath(cwd, 'prompt-sessions', 'active-index.json');
66
67
  return JSON.parse(fs.readFileSync(p, 'utf8'));
67
68
  } catch {
68
69
  return null;
@@ -13,8 +13,11 @@
13
13
  * that can only run main-process side, so approveProposed fires this IPC
14
14
  * call fire-and-forget and patches `worktree` onto the session once it
15
15
  * resolves — the transition itself is never blocked or delayed by it, and a
16
- * failure (not a repo / dirty base tree / disabled / cap reached) simply
17
- * leaves `worktree` unset, exactly as if this call had never been made.
16
+ * failure (not a repo / disabled / cap reached / carry-over of a dirty base
17
+ * tree's WIP failed) simply leaves `worktree` unset, exactly as if this call
18
+ * had never been made. A dirty base tree itself no longer disables
19
+ * isolation — its outstanding diff is carried into the fresh worktree
20
+ * instead (gitWorktree.cjs's createWorktree).
18
21
  *
19
22
  * Mirrors promptSessionsCreateEpic.cjs's shape: `validatePath(cwd)` closes
20
23
  * the same "arbitrary renderer-supplied cwd" gap that handler documents,
@@ -0,0 +1,45 @@
1
+ /**
2
+ * fixChainDepth.cjs — pure slug-inspection helpers for the fix-chain depth
3
+ * guard (PRD 1113). Generalizes scheduler.cjs's `isFixPlanSlug` (`^\d+-fix-`)
4
+ * into a counter: how many CONSECUTIVE '-fix-' segments immediately follow
5
+ * the leading `NN-` prefix. Only a run starting right at the prefix counts —
6
+ * a later segment that happens to read 'fix' (e.g.
7
+ * '13-fix-loop-extraction-and-round-trip', where 'loop' breaks the run right
8
+ * after the first 'fix') does not extend the count, and a segment that only
9
+ * contains 'fix' as a substring (e.g. '113-prefix-foo') never matches at all
10
+ * since segments are compared for exact equality after splitting on '-'.
11
+ *
12
+ * No filesystem access, no scheduler.cjs dependency — safe to require from
13
+ * lib/prdCreate.cjs without pulling in the scheduler module graph.
14
+ */
15
+ 'use strict';
16
+
17
+ function fixChainDepthOf(slug) {
18
+ const rest = String(slug).replace(/^\d+-/, '');
19
+ const segments = rest.split('-');
20
+ let depth = 0;
21
+ for (const seg of segments) {
22
+ if (seg !== 'fix') break;
23
+ depth += 1;
24
+ }
25
+ return depth;
26
+ }
27
+
28
+ /**
29
+ * Strips the leading consecutive '-fix-' chain a slug carries, returning the
30
+ * base slug the chain descends from — e.g. '113-fix-fix-foo' -> '113-foo'.
31
+ * Used only for error messages (naming what actually failed to close).
32
+ */
33
+ function baseSlugOf(slug) {
34
+ const str = String(slug);
35
+ const prefixMatch = str.match(/^(\d+)-/);
36
+ const prefix = prefixMatch ? prefixMatch[1] : '';
37
+ const rest = str.replace(/^\d+-/, '');
38
+ const segments = rest.split('-');
39
+ let i = 0;
40
+ while (i < segments.length && segments[i] === 'fix') i += 1;
41
+ const remainder = segments.slice(i).join('-');
42
+ return prefix ? `${prefix}-${remainder}` : remainder;
43
+ }
44
+
45
+ module.exports = { fixChainDepthOf, baseSlugOf };