aiwcli 0.10.3 → 0.11.1

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 (191) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.js +28 -131
  3. package/dist/commands/init/index.js +3 -3
  4. package/dist/lib/gitignore-manager.d.ts +32 -0
  5. package/dist/lib/gitignore-manager.js +141 -2
  6. package/dist/templates/CLAUDE.md +8 -8
  7. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  8. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  9. package/dist/templates/_shared/.claude/settings.json +7 -7
  10. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  11. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  12. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  13. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  14. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  15. package/dist/templates/_shared/hooks-ts/session_end.ts +107 -0
  16. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  17. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  18. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  19. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  20. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  21. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
  22. package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
  23. package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
  24. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
  25. package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
  26. package/dist/templates/_shared/lib-ts/base/logger.ts +15 -2
  27. package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
  28. package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
  29. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +142 -0
  30. package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
  31. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
  32. package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
  33. package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
  34. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +43 -23
  35. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
  36. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
  37. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +158 -0
  38. package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
  39. package/dist/templates/_shared/lib-ts/types.ts +68 -55
  40. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  41. package/dist/templates/_shared/scripts/resume_handoff.ts +345 -0
  42. package/dist/templates/_shared/scripts/save_handoff.ts +3 -3
  43. package/dist/templates/_shared/scripts/status_line.ts +687 -0
  44. package/dist/templates/cc-native/.claude/settings.json +175 -185
  45. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  46. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
  47. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  48. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  49. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1027 -0
  50. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  51. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -0
  52. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +792 -0
  53. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  54. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -0
  55. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  56. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  57. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
  58. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  59. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +120 -0
  60. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  62. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +250 -0
  63. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +275 -0
  64. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  65. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +107 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  67. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +240 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  70. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +385 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  72. package/dist/templates/cc-native/_cc-native/plan-review.config.json +14 -1
  73. package/oclif.manifest.json +1 -1
  74. package/package.json +2 -2
  75. package/dist/templates/_shared/hooks/__init__.py +0 -16
  76. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  77. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  78. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  79. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  80. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  81. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  82. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  83. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  84. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  85. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  86. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  87. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  88. package/dist/templates/_shared/hooks/archive_plan.py +0 -177
  89. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  90. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  91. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  92. package/dist/templates/_shared/hooks/session_end.py +0 -173
  93. package/dist/templates/_shared/hooks/session_start.py +0 -206
  94. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  95. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  96. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  97. package/dist/templates/_shared/lib/__init__.py +0 -1
  98. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  99. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  100. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  102. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  103. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  104. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  107. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  108. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  109. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  110. package/dist/templates/_shared/lib/base/constants.py +0 -358
  111. package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
  112. package/dist/templates/_shared/lib/base/inference.py +0 -307
  113. package/dist/templates/_shared/lib/base/logger.py +0 -305
  114. package/dist/templates/_shared/lib/base/stop_words.py +0 -221
  115. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  116. package/dist/templates/_shared/lib/base/utils.py +0 -263
  117. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  118. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  130. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  131. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  132. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  133. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  134. package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
  135. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  136. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  137. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  138. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  139. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  140. package/dist/templates/_shared/lib/templates/README.md +0 -206
  141. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  142. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  145. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  146. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  147. package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
  148. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  149. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  150. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  151. package/dist/templates/_shared/scripts/status_line.py +0 -716
  152. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  153. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  154. package/dist/templates/cc-native/MIGRATION.md +0 -86
  155. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  156. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  157. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  158. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  159. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  160. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  161. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  162. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
  163. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  164. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  165. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  166. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  167. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  171. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  172. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  174. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  175. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  176. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  177. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  178. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  179. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  180. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  181. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  187. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  188. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
  189. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  190. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  191. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -14,29 +14,30 @@
14
14
  */
15
15
 
16
16
  import * as crypto from "node:crypto";
17
- import {
18
- getContext,
19
- getAllContexts,
20
- getContextBySessionId,
21
- createContextFromPrompt,
22
- createContext,
23
- completeContext,
24
- bindSession,
25
- updateMode,
26
- } from "./context-store.js";
17
+
27
18
  import {
28
19
  formatActiveContextReminder,
20
+ formatActiveContinuation as _formatActiveContinuation,
21
+ formatCommandFeedback,
29
22
  formatContextCreated,
30
23
  formatContextPickerStderr,
31
- formatCommandFeedback,
32
24
  formatHandoffContinuation,
33
25
  formatPlanContinuation,
34
- formatActiveContinuation,
35
26
  } from "./context-formatter.js";
27
+ import {
28
+ bindSession,
29
+ completeContext,
30
+ createContext,
31
+ createContextFromPrompt,
32
+ getAllContexts,
33
+ getContext,
34
+ getContextBySessionId,
35
+ updateMode,
36
+ } from "./context-store.js";
36
37
  import { normalizePlanContent } from "./plan-manager.js";
38
+ import { logDebug, logError, logInfo, logWarn as _logWarn } from "../base/logger.js";
37
39
  import { isInternalCall } from "../base/subprocess-utils.js";
38
- import { logDebug, logInfo, logWarn, logError } from "../base/logger.js";
39
- import type { ContextState, CaretCommand } from "../types.js";
40
+ import type { CaretCommand, ContextState } from "../types.js";
40
41
 
41
42
  /** Minimum characters required for new context description. */
42
43
  const MIN_NEW_CONTEXT_CHARS = 10;
@@ -65,7 +66,7 @@ export class BlockRequest extends Error {
65
66
  export function resolveContextByPrefix(
66
67
  query: string,
67
68
  contexts: ContextState[],
68
- ): [number | null, string | null] {
69
+ ): [null | number, null | string] {
69
70
  const q = query.toLowerCase();
70
71
  const available = contexts.map(c => c.id).join(", ");
71
72
 
@@ -107,7 +108,7 @@ export function resolveContextByPrefix(
107
108
  export function parseChainedCaret(
108
109
  prompt: string,
109
110
  contexts: ContextState[],
110
- ): [CaretCommand | null, string | null] {
111
+ ): [CaretCommand | null, null | string] {
111
112
  if (!prompt.startsWith("^")) return [null, null];
112
113
 
113
114
  const match = prompt.match(/^\^(\S+)(?:\s+(.*))?$/s);
@@ -120,7 +121,7 @@ export function parseChainedCaret(
120
121
 
121
122
  // ^N shorthand
122
123
  if (/^\d+$/.test(commandStr)) {
123
- const num = parseInt(commandStr, 10);
124
+ const num = Number.parseInt(commandStr, 10);
124
125
  if (num === 0) {
125
126
  if (remaining.length < MIN_NEW_CONTEXT_CHARS) {
126
127
  return [null,
@@ -130,21 +131,25 @@ export function parseChainedCaret(
130
131
  `Example: ^0 implement user authentication with JWT tokens`,
131
132
  ];
132
133
  }
134
+
133
135
  return [{ ends: [], select: null, new_context_desc: remaining, remaining_prompt: "" }, null];
134
136
  }
137
+
135
138
  if (num < 1 || num > contexts.length) {
136
139
  if (contexts.length === 0) {
137
140
  return [null, "No existing contexts. Use ^0 <description> to create a new one."];
138
141
  }
142
+
139
143
  return [null, `Invalid selection. Choose 1-${contexts.length} for existing contexts, or ^0 for new.`];
140
144
  }
145
+
141
146
  const ctx = contexts[num - 1]!;
142
147
  return [{ ends: [], select: ctx.id, new_context_desc: null, remaining_prompt: remaining }, null];
143
148
  }
144
149
 
145
150
  // Parse chained commands
146
151
  const ends: string[] = [];
147
- let select: string | null = null;
152
+ let select: null | string = null;
148
153
  let pos = 0;
149
154
 
150
155
  while (pos < commandStr.length) {
@@ -174,11 +179,13 @@ export function parseChainedCaret(
174
179
  if (numStart === pos) {
175
180
  return [null, `Expected number, '*', or ':prefix' after 'E' at position ${numStart + 1}`];
176
181
  }
177
- const num = parseInt(commandStr.slice(numStart, pos), 10);
182
+
183
+ const num = Number.parseInt(commandStr.slice(numStart, pos), 10);
178
184
  if (num < 1 || num > contexts.length) {
179
185
  if (contexts.length === 0) return [null, "No contexts to end."];
180
186
  return [null, `Context ^E${num} invalid. Choose 1-${contexts.length}.`];
181
187
  }
188
+
182
189
  if (pos < commandStr.length && commandStr[pos] === "+") {
183
190
  pos++;
184
191
  for (let i = num; i <= contexts.length; i++) {
@@ -208,13 +215,16 @@ export function parseChainedCaret(
208
215
  if (numStart === pos) {
209
216
  return [null, `Expected number or ':prefix' after 'S' at position ${numStart + 1}`];
210
217
  }
211
- const num = parseInt(commandStr.slice(numStart, pos), 10);
218
+
219
+ const num = Number.parseInt(commandStr.slice(numStart, pos), 10);
212
220
  if (num < 1 || num > contexts.length) {
213
221
  if (contexts.length === 0) return [null, "No contexts to select."];
214
222
  return [null, `Context ^S${num} invalid. Choose 1-${contexts.length}.`];
215
223
  }
224
+
216
225
  ctx = contexts[num - 1]!;
217
226
  }
227
+
218
228
  if (select === null) select = ctx.id;
219
229
  } else {
220
230
  return [null,
@@ -274,9 +284,9 @@ function matchPlanContent(prompt: string, hasPlanContexts: ContextState[]): Cont
274
284
  }
275
285
 
276
286
  // Tier 4 (legacy): Signature match
277
- const promptHead = prompt.slice(0, 500);
287
+ const promptHead = new Set(prompt.slice(0, 500));
278
288
  for (const ctx of hasPlanContexts) {
279
- if (ctx.plan_signature && promptHead.includes(ctx.plan_signature)) {
289
+ if (ctx.plan_signature && promptHead.has(ctx.plan_signature)) {
280
290
  logDebug("context_selector", `Tier 4 legacy signature match: ${ctx.id}`);
281
291
  return ctx;
282
292
  }
@@ -292,15 +302,15 @@ function matchPlanContent(prompt: string, hasPlanContexts: ContextState[]): Cont
292
302
  function createNewContext(
293
303
  prompt: string,
294
304
  projectRoot?: string,
295
- ): [string | null, string, string | null] {
305
+ ): [null | string, string, null | string] {
296
306
  try {
297
307
  const newCtx = createContextFromPrompt(prompt, projectRoot);
298
308
  updateMode(newCtx.id, "active", projectRoot);
299
309
  newCtx.mode = "active";
300
310
  logInfo("context_selector", `Auto-created context: ${newCtx.id}`);
301
311
  return [newCtx.id, "auto_created", formatContextCreated(newCtx)];
302
- } catch (e: any) {
303
- logError("context_selector", `Primary context creation failed: ${e}`);
312
+ } catch (error: any) {
313
+ logError("context_selector", `Primary context creation failed: ${error}`);
304
314
  try {
305
315
  const now = new Date();
306
316
  const yy = String(now.getFullYear()).slice(2);
@@ -320,8 +330,8 @@ function createNewContext(
320
330
  newCtx.mode = "active";
321
331
  logInfo("context_selector", `Fallback context created: ${newCtx.id}`);
322
332
  return [newCtx.id, "auto_created_fallback", formatContextCreated(newCtx)];
323
- } catch (e2: any) {
324
- logError("context_selector", `ALL context creation failed: ${e2}`);
333
+ } catch (error: any) {
334
+ logError("context_selector", `ALL context creation failed: ${error}`);
325
335
  return [null, "creation_failed", null];
326
336
  }
327
337
  }
@@ -335,7 +345,7 @@ function handleCaretCommand(
335
345
  prompt: string,
336
346
  contexts: ContextState[],
337
347
  projectRoot?: string,
338
- ): [string | null, string, string | null] {
348
+ ): [null | string, string, null | string] {
339
349
  if (contexts.length === 0) {
340
350
  const match = prompt.match(/^\^(\S+)(?:\s+(.*))?$/s);
341
351
  if (!match) {
@@ -344,14 +354,16 @@ function handleCaretCommand(
344
354
  "Example: ^0 implement user authentication system",
345
355
  );
346
356
  }
357
+
347
358
  const prefixValue = match[1]!;
348
359
  const remaining = match[2] ?? "";
349
- if (!/^\d+$/.test(prefixValue) || parseInt(prefixValue, 10) !== 0) {
360
+ if (!/^\d+$/.test(prefixValue) || Number.parseInt(prefixValue, 10) !== 0) {
350
361
  throw new BlockRequest(
351
362
  "No existing contexts to select. Use ^0 <description> to create a new context.\n" +
352
363
  "Example: ^0 implement user authentication system",
353
364
  );
354
365
  }
366
+
355
367
  const description = remaining.trim();
356
368
  if (description.length < MIN_NEW_CONTEXT_CHARS) {
357
369
  throw new BlockRequest(
@@ -361,6 +373,7 @@ function handleCaretCommand(
361
373
  `Example: ^0 implement user authentication with JWT tokens`,
362
374
  );
363
375
  }
376
+
364
377
  return createNewContext(description, projectRoot);
365
378
  }
366
379
 
@@ -374,6 +387,7 @@ function handleCaretCommand(
374
387
  if (!ctxToEnd) {
375
388
  throw new BlockRequest(`Context '${ctxId}' no longer exists.\n` + formatContextPickerStderr(contexts));
376
389
  }
390
+
377
391
  completeContext(ctxToEnd.id, projectRoot);
378
392
  endedContexts.push(ctxToEnd);
379
393
  logInfo("context_selector", `Ended context: ${ctxToEnd.id}`);
@@ -384,9 +398,10 @@ function handleCaretCommand(
384
398
  if (ctxId && endedContexts.length > 0) {
385
399
  const newCtx = getContext(ctxId, projectRoot);
386
400
  const feedback = formatCommandFeedback(endedContexts, newCtx);
387
- return [ctxId, method !== "creation_failed" ? "caret_new" : method, feedback];
401
+ return [ctxId, method === "creation_failed" ? method : "caret_new", feedback];
388
402
  }
389
- return [ctxId, method !== "creation_failed" ? "caret_new" : method, output];
403
+
404
+ return [ctxId, method === "creation_failed" ? method : "caret_new", output];
390
405
  }
391
406
 
392
407
  if (cmd.select) {
@@ -394,6 +409,7 @@ function handleCaretCommand(
394
409
  if (!selectedCtx) {
395
410
  throw new BlockRequest(`Context '${cmd.select}' no longer exists.\n` + formatContextPickerStderr(contexts));
396
411
  }
412
+
397
413
  logInfo("context_selector", `Caret-selected context: ${selectedCtx.id}`);
398
414
  return [selectedCtx.id, "caret_select", formatCommandFeedback(endedContexts, selectedCtx)];
399
415
  }
@@ -408,6 +424,7 @@ function handleCaretCommand(
408
424
  "Example: implement user authentication system",
409
425
  );
410
426
  }
427
+
411
428
  throw new BlockRequest(
412
429
  feedback + "\nNo context selected.\n\nSelect a context to continue:\n" +
413
430
  formatContextPickerStderr(remainingContexts),
@@ -432,7 +449,7 @@ export function determineContext(
432
449
  prompt: string,
433
450
  sessionId?: string,
434
451
  projectRoot?: string,
435
- ): [string | null, string, string | null] {
452
+ ): [null | string, string, null | string] {
436
453
  if (isInternalCall()) {
437
454
  logDebug("context_selector", "Skipping: internal subprocess call");
438
455
  return [null, "skip_internal", null];
@@ -460,6 +477,7 @@ export function determineContext(
460
477
  "Example: implement user authentication system",
461
478
  );
462
479
  }
480
+
463
481
  throw new BlockRequest(formatContextPickerStderr(contexts));
464
482
  }
465
483
 
@@ -9,20 +9,21 @@
9
9
 
10
10
  import * as fs from "node:fs";
11
11
  import * as path from "node:path";
12
- import { readStateJson, writeStateJson, toDict, dictToState } from "../base/state-io.js";
12
+
13
13
  import { atomicWrite } from "../base/atomic-write.js";
14
14
  import {
15
+ getArchiveContextDir,
16
+ getArchiveDir,
17
+ getArchiveIndexPath,
15
18
  getContextDir,
16
19
  getContextsDir,
17
20
  getIndexPath,
18
- getArchiveDir,
19
- getArchiveContextDir,
20
- getArchiveIndexPath,
21
21
  validateContextId,
22
22
  } from "../base/constants.js";
23
- import { logDebug, logInfo, logWarn, logError, setContextPath } from "../base/logger.js";
24
- import { nowIso, generateContextId } from "../base/utils.js";
25
- import type { ContextState, IndexFile, IndexEntry, Mode } from "../types.js";
23
+ import { logDebug as _logDebug, logError, logInfo, logWarn, setContextPath } from "../base/logger.js";
24
+ import { dictToState as _dictToState, readStateJson, toDict as _toDict, writeStateJson } from "../base/state-io.js";
25
+ import { generateContextId, nowIso } from "../base/utils.js";
26
+ import type { ContextState, IndexEntry, IndexFile, Mode } from "../types.js";
26
27
 
27
28
  const INDEX_VERSION = "3.0";
28
29
 
@@ -34,12 +35,13 @@ function loadIndex(projectRoot?: string): IndexFile {
34
35
  const indexPath = getIndexPath(projectRoot);
35
36
  if (fs.existsSync(indexPath)) {
36
37
  try {
37
- const raw = fs.readFileSync(indexPath, "utf-8");
38
+ const raw = fs.readFileSync(indexPath, "utf8");
38
39
  return JSON.parse(raw) as IndexFile;
39
- } catch (e: any) {
40
- logWarn("context_store", `Failed to read index, recreating: ${e}`);
40
+ } catch (error: any) {
41
+ logWarn("context_store", `Failed to read index, recreating: ${error}`);
41
42
  }
42
43
  }
44
+
43
45
  return { version: INDEX_VERSION, updated_at: nowIso(), sessions: {}, contexts: {} };
44
46
  }
45
47
 
@@ -50,6 +52,7 @@ function saveIndex(index: IndexFile, projectRoot?: string): boolean {
50
52
  if (!success) {
51
53
  logWarn("context_store", `Failed to write index: ${error}`);
52
54
  }
55
+
53
56
  return success;
54
57
  }
55
58
 
@@ -69,7 +72,7 @@ function migrateContextJson(contextId: string, projectRoot?: string): ContextSta
69
72
  if (!fs.existsSync(legacyPath)) return null;
70
73
 
71
74
  try {
72
- const data = JSON.parse(fs.readFileSync(legacyPath, "utf-8"));
75
+ const data = JSON.parse(fs.readFileSync(legacyPath, "utf8"));
73
76
  const inFlight = data.in_flight ?? {};
74
77
  const oldMode = inFlight.mode ?? "none";
75
78
  const MODE_MIGRATION: Record<string, string> = {
@@ -104,8 +107,8 @@ function migrateContextJson(contextId: string, projectRoot?: string): ContextSta
104
107
  last_session: null,
105
108
  tasks: [],
106
109
  };
107
- } catch (e: any) {
108
- logWarn("context_store", `Failed to migrate context.json for '${contextId}': ${e}`);
110
+ } catch (error: any) {
111
+ logWarn("context_store", `Failed to migrate context.json for '${contextId}': ${error}`);
109
112
  return null;
110
113
  }
111
114
  }
@@ -134,7 +137,7 @@ export function saveState(
134
137
  contextId: string,
135
138
  state: ContextState,
136
139
  projectRoot?: string,
137
- ): [boolean, string | null] {
140
+ ): [boolean, null | string] {
138
141
  // Ensure the state ID matches
139
142
  state.id = contextId;
140
143
 
@@ -152,10 +155,12 @@ export function saveState(
152
155
  if (!index.sessions) index.sessions = {} as Record<string, string>;
153
156
  index.sessions[sid] = contextId;
154
157
  }
158
+
155
159
  const indexOk = saveIndex(index, projectRoot);
156
160
  if (!indexOk) {
157
161
  return [true, "state.json saved but index.json update failed"];
158
162
  }
163
+
159
164
  return [true, null];
160
165
  }
161
166
 
@@ -165,7 +170,7 @@ export function saveState(
165
170
  * See SPEC.md §7.4
166
171
  */
167
172
  export function createContext(
168
- contextId: string | null,
173
+ contextId: null | string,
169
174
  summary: string,
170
175
  method = "",
171
176
  projectRoot?: string,
@@ -185,23 +190,8 @@ export function createContext(
185
190
  } catch { /* ignore */ }
186
191
  }
187
192
  }
188
- // generateContextId is async but we need sync here — use a simple fallback
189
- const now = new Date();
190
- const yy = String(now.getFullYear()).slice(2);
191
- const mm = String(now.getMonth() + 1).padStart(2, "0");
192
- const dd = String(now.getDate()).padStart(2, "0");
193
- const hh = String(now.getHours()).padStart(2, "0");
194
- const min = String(now.getMinutes()).padStart(2, "0");
195
- const timestamp = `${yy}${mm}${dd}-${hh}${min}`;
196
- const words = summary.toLowerCase().split(/\s+/).filter(w => w.length > 2).slice(0, 6);
197
- const slug = words.length > 0 ? words.join("-").replace(/[^a-z0-9-]/g, "").slice(0, 50) : "context";
198
- contextId = `${timestamp}-${slug}`;
199
-
200
- if (existingIds.has(contextId)) {
201
- let counter = 2;
202
- while (existingIds.has(`${contextId}-${counter}`)) counter++;
203
- contextId = `${contextId}-${counter}`;
204
- }
193
+
194
+ contextId = generateContextId(summary, existingIds);
205
195
  }
206
196
 
207
197
  contextId = validateContextId(contextId);
@@ -251,6 +241,7 @@ export function getContext(contextId: string, projectRoot?: string): ContextStat
251
241
  } catch {
252
242
  return null;
253
243
  }
244
+
254
245
  return loadState(contextId, projectRoot);
255
246
  }
256
247
 
@@ -288,6 +279,7 @@ export function getAllContexts(
288
279
  try {
289
280
  if (!fs.statSync(fullPath).isDirectory()) continue;
290
281
  } catch { continue; }
282
+
291
283
  const state = loadState(entry, projectRoot);
292
284
  if (state && (!status || state.status === status)) {
293
285
  results.push(state);
@@ -306,7 +298,7 @@ export function getAllContexts(
306
298
  */
307
299
  export function updateContext(
308
300
  contextId: string,
309
- updates: Partial<Pick<ContextState, "summary" | "tags" | "method">>,
301
+ updates: Partial<Pick<ContextState, "method" | "summary" | "tags">>,
310
302
  projectRoot?: string,
311
303
  ): ContextState | null {
312
304
  const state = getContext(contextId, projectRoot);
@@ -356,6 +348,7 @@ export function getContextBySessionId(
356
348
  return state;
357
349
  }
358
350
  }
351
+
359
352
  return null;
360
353
  }
361
354
 
@@ -387,6 +380,7 @@ export function bindSession(
387
380
  if (!state.session_ids.includes(sessionId)) {
388
381
  state.session_ids.push(sessionId);
389
382
  }
383
+
390
384
  state.last_active = nowIso();
391
385
 
392
386
  const [success] = saveState(contextId, state, projectRoot);
@@ -402,13 +396,13 @@ export function updateMode(
402
396
  mode: Mode,
403
397
  projectRoot?: string,
404
398
  opts?: {
405
- plan_path?: string;
406
- plan_hash?: string;
407
- plan_signature?: string;
408
- plan_id?: string;
399
+ handoff_consumed?: boolean;
409
400
  plan_anchors?: string[];
410
401
  plan_consumed?: boolean;
411
- handoff_consumed?: boolean;
402
+ plan_hash?: string;
403
+ plan_id?: string;
404
+ plan_path?: string;
405
+ plan_signature?: string;
412
406
  },
413
407
  ): ContextState | null {
414
408
  const state = getContext(contextId, projectRoot);
@@ -506,6 +500,7 @@ export function archiveContext(contextId: string, projectRoot?: string): Context
506
500
  logWarn("context_store", `Cannot archive: context '${contextId}' not found`);
507
501
  return null;
508
502
  }
503
+
509
504
  if (state.status !== "completed") {
510
505
  logWarn("context_store", `Cannot archive: context '${contextId}' not completed`);
511
506
  return null;
@@ -524,8 +519,8 @@ export function archiveContext(contextId: string, projectRoot?: string): Context
524
519
 
525
520
  try {
526
521
  fs.renameSync(sourceDir, archiveDest);
527
- } catch (e: any) {
528
- logError("context_store", `Failed to move context to archive: ${e}`);
522
+ } catch (error: any) {
523
+ logError("context_store", `Failed to move context to archive: ${error}`);
529
524
  return null;
530
525
  }
531
526
 
@@ -536,6 +531,7 @@ export function archiveContext(contextId: string, projectRoot?: string): Context
536
531
  for (const [sid, cid] of Object.entries(sessions)) {
537
532
  if (cid === contextId) delete sessions[sid];
538
533
  }
534
+
539
535
  saveIndex(index, projectRoot);
540
536
 
541
537
  // Add to archive index
@@ -555,6 +551,7 @@ export function reopenContext(contextId: string, projectRoot?: string): ContextS
555
551
  if (!state) {
556
552
  state = restoreFromArchive(contextId, projectRoot);
557
553
  }
554
+
558
555
  if (!state) return null;
559
556
 
560
557
  if (state.status === "active") {
@@ -595,6 +592,35 @@ export function createContextFromPrompt(
595
592
  );
596
593
  }
597
594
 
595
+ /**
596
+ * Find the active context ID programmatically.
597
+ * Checks CONTEXT_ID env var first, then searches for the single active context.
598
+ * Returns null if no active context or multiple active contexts found.
599
+ */
600
+ export function findActiveContextId(projectRoot?: string): null | string {
601
+ // Env var takes priority
602
+ const envId = process.env.CONTEXT_ID;
603
+ if (envId) {
604
+ const ctx = getContext(envId, projectRoot);
605
+ if (ctx) return ctx.id;
606
+ }
607
+
608
+ // Search for active contexts
609
+ const active = getAllContexts("active", projectRoot)
610
+ .filter(c => c.mode === "active" || c.mode === "has_plan" || c.mode === "has_handoff");
611
+
612
+ if (active.length === 1) return active[0]!.id;
613
+ if (active.length > 1) {
614
+ // Multiple active — try to find the most recently active
615
+ const sorted = active.sort((a, b) =>
616
+ (b.last_active ?? "").localeCompare(a.last_active ?? ""),
617
+ );
618
+ return sorted[0]!.id;
619
+ }
620
+
621
+ return null;
622
+ }
623
+
598
624
  // ---------------------------------------------------------------------------
599
625
  // Archive helpers
600
626
  // ---------------------------------------------------------------------------
@@ -613,9 +639,9 @@ function updateArchiveIndex(state: ContextState, projectRoot?: string): boolean
613
639
 
614
640
  if (fs.existsSync(archiveIndexPath)) {
615
641
  try {
616
- archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "utf-8"));
617
- } catch (e: any) {
618
- logWarn("context_store", `Failed to read archive index, recreating: ${e}`);
642
+ archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "utf8"));
643
+ } catch (error_: any) {
644
+ logWarn("context_store", `Failed to read archive index, recreating: ${error_}`);
619
645
  }
620
646
  }
621
647
 
@@ -627,6 +653,7 @@ function updateArchiveIndex(state: ContextState, projectRoot?: string): boolean
627
653
  if (!success) {
628
654
  logWarn("context_store", `Failed to write archive index: ${error}`);
629
655
  }
656
+
630
657
  return success;
631
658
  }
632
659
 
@@ -642,8 +669,8 @@ function restoreFromArchive(contextId: string, projectRoot?: string): ContextSta
642
669
 
643
670
  try {
644
671
  fs.renameSync(archiveDir, activeDir);
645
- } catch (e: any) {
646
- logError("context_store", `Failed to restore context from archive: ${e}`);
672
+ } catch (error: any) {
673
+ logError("context_store", `Failed to restore context from archive: ${error}`);
647
674
  return null;
648
675
  }
649
676
 
@@ -660,7 +687,7 @@ function removeFromArchiveIndex(contextId: string, projectRoot?: string): boolea
660
687
  if (!fs.existsSync(archiveIndexPath)) return true;
661
688
 
662
689
  try {
663
- const archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "utf-8")) as IndexFile;
690
+ const archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "utf8")) as IndexFile;
664
691
  if (archiveIndex.contexts[contextId]) {
665
692
  delete archiveIndex.contexts[contextId];
666
693
  archiveIndex.updated_at = nowIso();
@@ -671,9 +698,10 @@ function removeFromArchiveIndex(contextId: string, projectRoot?: string): boolea
671
698
  return false;
672
699
  }
673
700
  }
701
+
674
702
  return true;
675
- } catch (e: any) {
676
- logWarn("context_store", `Failed to read archive index: ${e}`);
703
+ } catch (error: any) {
704
+ logWarn("context_store", `Failed to read archive index: ${error}`);
677
705
  return false;
678
706
  }
679
707
  }
@@ -13,8 +13,8 @@ import * as path from "node:path";
13
13
  import * as crypto from "node:crypto";
14
14
  import { getContextDir, getContextPlansDir, sanitizeTitle } from "../base/constants.js";
15
15
  import { atomicWrite } from "../base/atomic-write.js";
16
- import { readStateJson } from "../base/state-io.js";
17
16
  import { logDebug, logInfo, logWarn, logError } from "../base/logger.js";
17
+ import { generateSlug } from "../base/utils.js";
18
18
  import type { ContextState } from "../types.js";
19
19
 
20
20
  // ---------------------------------------------------------------------------
@@ -30,11 +30,11 @@ import type { ContextState } from "../types.js";
30
30
  * Returns [archivedPath, planHash, planSignature] on success,
31
31
  * or [null, null, null] on error.
32
32
  */
33
- export async function archivePlan(
33
+ export function archivePlan(
34
34
  planPath: string,
35
35
  contextId: string,
36
36
  projectRoot?: string,
37
- ): Promise<[string | null, string | null, string | null]> {
37
+ ): [string | null, string | null, string | null] {
38
38
  if (!fs.existsSync(planPath)) {
39
39
  logWarn("plan_manager", `Plan file not found: ${planPath}`);
40
40
  return [null, null, null];
@@ -69,23 +69,12 @@ export async function archivePlan(
69
69
  String(now.getMinutes()).padStart(2, "0"),
70
70
  ].join("");
71
71
 
72
- // Try AI inference for a descriptive slug
73
- let slug: string | null = null;
74
- try {
75
- const { generateContextIdSlug } = await import("../base/inference.js");
76
- const aiSlug = generateContextIdSlug(content.slice(0, 500), 5);
77
- if (aiSlug) {
78
- slug = sanitizeTitle(aiSlug, 60);
79
- }
80
- } catch {
81
- // AI inference unavailable — use filename fallback
82
- }
83
-
84
- // Fallback: use plan filename
85
- if (!slug) {
86
- const stem = path.basename(planPath, path.extname(planPath));
87
- slug = sanitizeTitle(stem, 30);
88
- }
72
+ // Extract a clean summary from plan content for slug generation.
73
+ // Headings describe the plan's intent better than raw markdown body.
74
+ const summary = extractPlanSummary(content);
75
+ const slug = generateSlug(summary, 60, sanitizeTitle(
76
+ path.basename(planPath, path.extname(planPath)), 30,
77
+ ));
89
78
 
90
79
  let archiveName = `${dateStr}-${slug}.md`;
91
80
  let archivePath = path.join(plansDir, archiveName);
@@ -109,6 +98,35 @@ export async function archivePlan(
109
98
  return [archivePath, planHash, planSignature];
110
99
  }
111
100
 
101
+ /**
102
+ * Extract a human-readable summary from plan markdown content.
103
+ * Pulls headings and the first substantial paragraph, producing
104
+ * text suitable for the AI slug generator (which expects conversational input).
105
+ */
106
+ function extractPlanSummary(content: string): string {
107
+ const lines = content.split(/\r?\n/);
108
+ const parts: string[] = [];
109
+ let firstParagraph = "";
110
+
111
+ for (const line of lines) {
112
+ const trimmed = line.trim();
113
+ // Collect markdown headings (strip # prefix)
114
+ if (trimmed.startsWith("#")) {
115
+ const heading = trimmed.replace(/^#+\s*/, "");
116
+ if (heading.length > 2) parts.push(heading);
117
+ }
118
+ // Grab first substantial non-heading line as context
119
+ if (!firstParagraph && !trimmed.startsWith("#") && trimmed.length > 20) {
120
+ firstParagraph = trimmed.slice(0, 120);
121
+ }
122
+ // Enough material for the AI
123
+ if (parts.length >= 5) break;
124
+ }
125
+
126
+ if (firstParagraph) parts.push(firstParagraph);
127
+ return parts.join(" ").slice(0, 500) || content.slice(0, 500);
128
+ }
129
+
112
130
  // ---------------------------------------------------------------------------
113
131
  // Plan lookup
114
132
  // ---------------------------------------------------------------------------
@@ -124,7 +142,9 @@ export function findLatestPlan(
124
142
  ): string | null {
125
143
  // 1. Check state.json plan_path first
126
144
  try {
127
- const state = readStateJson(contextId, projectRoot);
145
+ // Dynamic import to avoid circular dependency at module level
146
+ const stateIo = require("../base/state-io.js");
147
+ const state = stateIo.readStateJson(contextId, projectRoot);
128
148
  if (state?.plan_path && fs.existsSync(state.plan_path)) {
129
149
  return state.plan_path;
130
150
  }
@@ -183,7 +203,7 @@ export function normalizePlanContent(text: string): string {
183
203
  */
184
204
  export function extractPlanAnchors(content: string, maxAnchors = 5): string[] {
185
205
  const anchors: string[] = [];
186
- for (const line of content.split("\n")) {
206
+ for (const line of content.split(/\r?\n/)) {
187
207
  const trimmed = line.trim();
188
208
  if (trimmed.startsWith("#") && trimmed.length > 3) {
189
209
  anchors.push(trimmed.slice(0, 80));
@@ -228,7 +248,7 @@ export function findPlanPathInTranscript(transcriptPath: string): string | null
228
248
 
229
249
  let lines: string[];
230
250
  try {
231
- lines = fs.readFileSync(transcriptPath, "utf-8").split("\n");
251
+ lines = fs.readFileSync(transcriptPath, "utf-8").split(/\r?\n/);
232
252
  } catch (e: any) {
233
253
  logWarn("plan_manager", `Failed to read transcript: ${e}`);
234
254
  return null;