pi-repl-py 0.6.14 → 0.7.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.
@@ -114,12 +114,7 @@ function marker(state: ExecuteRenderState, deps: RenderDeps): string {
114
114
  }
115
115
  }
116
116
 
117
- /**
118
- * highlight.js python emits no scope for plain identifiers, so they arrive as raw (uncolored)
119
- * text mixed with SGR-colored tokens and bare punctuation. Re-color only whole identifier runs
120
- * that sit outside any colored span, leaving keywords, strings, numbers, and other already
121
- * colored tokens untouched.
122
- */
117
+ /** highlight.js emits no scope for bare Python identifiers — re-color only raw runs outside colored spans. */
123
118
  function colorBareIdentifiers(line: string, paint: (id: string) => string): string {
124
119
  if (!line.includes("\x1b") && !/[a-zA-Z_]/.test(line)) return line;
125
120
  const out: string[] = [];
@@ -160,7 +155,6 @@ function colorBareIdentifiers(line: string, paint: (id: string) => string): stri
160
155
  i = end;
161
156
  continue;
162
157
  }
163
- // only raw (uncolored) text may be repainted; colored tokens pass through untouched
164
158
  if (colored) out.push(line[i]);
165
159
  else pending += line[i];
166
160
  i++;
@@ -242,9 +236,7 @@ function sanitizeTuiOutput(text: string): string {
242
236
  .replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f-\x9f]/g, "�");
243
237
  }
244
238
 
245
- /** Wrap a ready-colored span into `lines`: first row takes the prefix, wrapped
246
- * continuations take indent (and an optional extra indent), each row is
247
- * truncated to pane width and closed if it ends on an open SGR color. */
239
+ /** Wrap a colored span: first row takes the prefix, continuations the indent; truncate to width and close open SGR colors. */
248
240
  function pushWrappedLines(
249
241
  lines: string[],
250
242
  prefix: string,
@@ -306,12 +298,7 @@ function renderCode(state: ExecuteRenderState, lines: string[], width: number, d
306
298
  return true;
307
299
  }
308
300
 
309
- /**
310
- * Streaming output is append-only, so a wrapped blob only changes at its tail.
311
- * The cache lives on the (persistent) state object via a WeakMap, letting an
312
- * updated body re-wrap just the appended delta: O(chunk) instead of O(output).
313
- * Fresh states (unit tests, first render) fall back to a full wrap.
314
- */
301
+ /** Output is append-only: the cached wrap re-wraps just the tail — O(chunk) instead of O(output). */
315
302
  interface BlobWrapEntry {
316
303
  text: string;
317
304
  color: string;
@@ -321,20 +308,14 @@ interface BlobWrapEntry {
321
308
  }
322
309
 
323
310
  const blobWrapCache = new WeakMap<ExecuteRenderState, Map<number, BlobWrapEntry>>();
324
- /** Wrapped+highlighted rows rebuild only when their inputs (text/width) change; the
325
- * TUI re-renders bodies on every frame, so these caches turn per-frame work into
326
- * one build per change. They live on the persistent per-call state, which the host
327
- * keeps for the session, so a small bound per state keeps resize churn bounded. */
311
+ /** Row caches keyed by text/width turn per-frame renders into one build per change; bounded per state against resize churn. */
328
312
  interface CodeEntry {
329
313
  code: string;
330
314
  lines: string[];
331
315
  }
332
316
  const codeWrapCache = new WeakMap<ExecuteRenderState, Map<number, CodeEntry>>();
333
317
 
334
- /** Window resizes add a per-width entry per cell; keep a small bound so a long
335
- * session with resize churn cannot grow the wrap caches without limit. Map
336
- * iteration order is insertion order, so evicting the first key drops the oldest
337
- * width rather than the one in use. */
318
+ /** Bound per-width cache entries; insertion order evicts the oldest width first. */
338
319
  const MAX_CACHED_WIDTHS_PER_STATE = 3;
339
320
  function boundWidthCache(perWidth: Map<number, unknown>): void {
340
321
  while (perWidth.size >= MAX_CACHED_WIDTHS_PER_STATE) {
@@ -352,9 +333,7 @@ function widthOf(ch: string): number {
352
333
  return code < 0x80 ? 1 : CJK_WIDE_RE.test(ch) ? 2 : 1;
353
334
  }
354
335
 
355
- /** Fast single-pass word wrap for ANSI-free text: pi-tui's ANSI-aware wrap is
356
- * ~45ms on a 45K blob; sanitized output needs no ANSI handling, so this splits
357
- * at spaces with an O(width) scan per row and hard-breaks overlong words. */
336
+ /** Fast word wrap for sanitized text: pi-tui's ANSI-aware wrap costs ~45ms on a 45K blob. */
358
337
  function wrapPlainText(text: string, width: number): string[] {
359
338
  if (width <= 0 || text.length <= width) return [text];
360
339
  // --- fast path: pure ASCII, break by char index ---
@@ -430,8 +409,7 @@ function wrapPlainText(text: string, width: number): string[] {
430
409
  return rows;
431
410
  }
432
411
 
433
- /** Wrap a raw output line exactly the way the section loops did: sanitize, wrap (fast),
434
- * then colorize row-by-row so the color span never bleeds across rows. */
412
+ /** Sanitize fast wrap colorize rows, so an SGR span never bleeds across rows. */
435
413
  function wrapRawOutputLine(lines: string[], raw: string, color: string, width: number, deps: RenderDeps): string[] {
436
414
  const before = lines.length;
437
415
  const safe = sanitizeTuiOutput(raw || " ");
@@ -517,8 +495,7 @@ function renderOutput(
517
495
  }
518
496
 
519
497
  if (details?.errorStack && details.errorStack.length > 0) {
520
- // --- a traceback IS output: without this flag a pure-traceback error cell (no
521
- // --- stdout/stderr/result, the common error shape) also rendered "no output" below it ---
498
+ // --- a traceback IS output: without this, a pure-traceback error cell also rendered "no output" below it ---
522
499
  renderedText = true;
523
500
  output.push(` ${OUTPUT_INDENT}${deps.fg("dim", "traceback:")}`);
524
501
  for (const line of details.errorStack) {
@@ -532,10 +509,7 @@ function renderOutput(
532
509
  addWrapped(output, OUTPUT_INDENT, deps.fg("muted", message), width, deps, { sanitize: false });
533
510
  }
534
511
 
535
- // --- bottom cushion: streams end with a newline, so blobs already render a trailing blank
536
- // --- row; traceback and the placeholder have no such newline and would sit flush against
537
- // --- the panel's bottom edge. Normalize: the panel always ends with one blank painted row.
538
- // --- (SGR stripped before the blank test; the colored rows themselves stay untouched.) ---
512
+ // --- the panel always ends with one blank row: streams end with a newline, tracebacks and placeholders don't ---
539
513
  const lastRow = output[output.length - 1];
540
514
  if (lastRow !== undefined && lastRow.replace(SGR_PATTERN, "").trim() !== "") output.push("");
541
515
 
@@ -544,7 +518,6 @@ function renderOutput(
544
518
  lines.push(...output);
545
519
  }
546
520
 
547
- /** Paint the status-matched panel background across the row, surviving inner SGR resets. */
548
521
  export function paintBackground(line: string, width: number, kind: StatusKind, deps: RenderDeps): string {
549
522
  const bgAnsi = deps.getBgAnsi(backgroundFor(kind));
550
523
  const padded = line + " ".repeat(Math.max(0, width - deps.visibleWidth(line)));
@@ -30,10 +30,7 @@ function makeDeps(theme: Theme): RenderDeps {
30
30
  };
31
31
  }
32
32
 
33
- /** O(1) key: a host-bumped dirty counter plus mode state. `withSpinner` folds
34
- * the animation frame so the header alone animates while running; the body key
35
- * excludes it, so a running cell only redraws when output actually changes
36
- * instead of re-wrapping the whole body every 120ms. */
33
+ /** O(1) body key excludes the spinner frame, so a running cell redraws only when output changes, not every 120ms. */
37
34
  function renderVersion(state: ExecuteRenderState, withSpinner: boolean): string {
38
35
  const spinner = withSpinner && statusKind(state) === "running" ? Math.floor(Date.now() / 120) % 4 : -1;
39
36
  return `${state.version ?? 0}|${state.expanded}|${spinner}`;
@@ -1,8 +1,6 @@
1
- // The lifecycle owns boot, session binding, and the reset announcements. Recovery is a
2
- // background engine job (the quiet-gap restore): the first tool call waits only for the
3
- // kernel to come up, and the reset notice lands on the first cell AFTER the restore lands.
1
+ // Lifecycle: boot, session binding, reset announcements. Recovery is a background quiet-gap job; the notice lands on the first cell after the restore.
4
2
 
5
- import type { RestoreResult } from "../engine/index.js";
3
+ import type { HelperLoadResult, RestoreResult } from "../engine/index.js";
6
4
 
7
5
  /** Show enough names to orient, then count the rest (a revive can carry hundreds). */
8
6
  function summarizeNames(names: readonly string[], limit: number): string {
@@ -14,8 +12,7 @@ function summarizeNames(names: readonly string[], limit: number): string {
14
12
  export interface RevivableEngine {
15
13
  /** Boot the kernel (and preload helpers), independent of snapshot recovery. */
16
14
  start(skipRestore?: boolean): Promise<void>;
17
- /** Resolves once this engine's recovery has settled: revived names, or null when there was
18
- * nothing to revive, recovery was skipped, or recovery failed. Never rejects. */
15
+ /** Recovery outcome: revived names, or null when nothing to revive / skipped / failed. Never rejects. */
19
16
  restoreResult(): Promise<RestoreResult | null>;
20
17
  /** True when recovery was deliberately skipped (a prior revive wedged). */
21
18
  restoreWasSkipped(): boolean;
@@ -24,25 +21,21 @@ export interface RevivableEngine {
24
21
  }
25
22
 
26
23
  export interface EngineLifecycleDeps<E extends RevivableEngine> {
27
- /** Builds a fresh engine. Called at most once per lifecycle generation; `skipRestore` is
28
- * true on the retry after a wedged boot, so the poisoned snapshot cannot wedge twice. */
24
+ /** Fresh engine; skipRestore is the retry after a wedged boot, so a poisoned snapshot can't wedge twice. */
29
25
  create(skipRestore?: boolean): E;
30
26
  /** Tears the current engine down, flushing its final snapshot. */
31
27
  dispose(engine: E): Promise<void>;
32
28
  /** Kill-then-rebuild when a wedged engine cannot serve the snapshot flush. */
33
29
  discard?(engine: E): Promise<void>;
34
- /** Boot deadline in ms; a boot (kernel start + helpers preload) that outlives it is killed
35
- * and retried fresh. Default 90s. Recovery is NOT inside this deadline: it runs in the
36
- * background and is bounded by the engine's own restore-cell watchdog. */
30
+ /** Boot deadline (default 90s): an overlong boot is killed and retried. Recovery is outside it background, bounded by the restore-cell watchdog. */
37
31
  bootTimeoutMs?: number;
38
32
  }
39
33
 
40
- /** `startup` restores then announces when the conversation has a saved past; `cell` means an engine was rebuilt mid-session and announces immediately. */
34
+ /** startup: announce when the conversation has a saved past; cell: a mid-session rebuild announces immediately. */
41
35
  export type AcquireOrigin = "startup" | "cell";
42
36
 
43
37
  const DEFAULT_BOOT_TIMEOUT_MS = 90_000;
44
38
 
45
- /** Model-facing body for a boot whose snapshot revive wedged and was skipped. */
46
39
  function revivedNoticeBody(origin: AcquireOrigin): string {
47
40
  const resumed = origin === "startup";
48
41
  return resumed
@@ -50,9 +43,7 @@ function revivedNoticeBody(origin: AcquireOrigin): string {
50
43
  : "The evaluator wedged while reviving its saved namespace, so the snapshot was skipped and the namespace is empty.";
51
44
  }
52
45
 
53
- // --- Terse TUI toast for the human, separate from the model-facing cell marker: the user
54
- // --- asked for the classic subtle notification instead of a showy in-cell message. Counts
55
- // --- come from the same restore the marker describes, so the two never disagree. ---
46
+ // --- human toast, separate from the model marker; counts come from the same restore, so the two never disagree ---
56
47
  export function formatResetToast(origin: AcquireOrigin, restore: RestoreResult | null, wedged = false): string {
57
48
  const resumed = origin === "startup";
58
49
  const verb = resumed ? "repl session resumed" : "repl kernel rebuilt";
@@ -118,22 +109,48 @@ function formatEngineResetNotice(restore: RestoreResult | null, origin: AcquireO
118
109
  return lines.join("\n");
119
110
  }
120
111
 
112
+ /** Human toast when a /fork'd conversation inherited its parent's namespace — same counts as the reset marker, named as a fork so it can't pass for a plain resume. */
113
+ export function formatForkToast(restore: RestoreResult | null): string {
114
+ const revived = restore?.restored.length ?? 0;
115
+ if (revived > 0) {
116
+ const noun = revived === 1 ? "name" : "names";
117
+ return `repl fork started — ${revived} ${noun} inherited from the parent session`;
118
+ }
119
+ return restore === null
120
+ ? "repl fork started — nothing to inherit"
121
+ : "repl fork started — parent snapshot revived nothing";
122
+ }
123
+
124
+ // --- split audience: toast for the human whenever a boot preloaded anything; a marker for the model ONLY when a helper failed (all-good boots are silent) ---
125
+
126
+ export function formatHelperToast(report: readonly HelperLoadResult[]): string {
127
+ const loaded = report.filter((h) => h.ok).map((h) => h.name);
128
+ const failed = report.filter((h) => !h.ok);
129
+ const loadedPart = loaded.length > 0 ? `helpers loaded: ${loaded.join(", ")}` : "no helpers loaded";
130
+ const failedPart =
131
+ failed.length > 0 ? ` · failed: ${failed.map((f) => `${f.name} (${f.error ?? "failed to load"})`).join(", ")}` : "";
132
+ return `repl ${loadedPart}${failedPart}`;
133
+ }
134
+
135
+ export function formatHelperFailuresLine(report: readonly HelperLoadResult[] | null): string | undefined {
136
+ if (!report) return undefined;
137
+ const failed = report.filter((h) => !h.ok);
138
+ if (failed.length === 0) return undefined;
139
+ return `<repl_helpers_failed: ${failed.map((f) => `${f.name} (${f.error ?? "failed to load"})`).join(", ")}>`;
140
+ }
141
+
121
142
  export class EngineLifecycle<E extends RevivableEngine> {
122
143
  private engine?: E;
123
144
  private pendingNotice?: string;
124
145
  private pendingReset?: { origin: AcquireOrigin; restore: RestoreResult | null; wedged: boolean };
125
146
  private teardown?: Promise<void>;
126
- /** First-build in progress. */
127
147
  private acquiring?: Promise<{ engine: E; restore: RestoreResult | null; created: boolean }>;
128
148
  /** The conversation this engine was built for; a different key on acquire tears it down. */
129
149
  private boundKey?: string;
130
150
 
131
151
  constructor(private readonly deps: EngineLifecycleDeps<E>) {}
132
152
 
133
- /** Race one BOOT attempt (kernel start + helpers preload) against the deadline. Recovery is
134
- * deliberately outside this race: it runs as a background quiet-gap job and is bounded by the
135
- * engine's own restore-cell watchdog. A failed start is soft — the first cell observes it and
136
- * the caller rebuilds. */
153
+ /** Race one boot (kernel start + helpers preload) against the deadline; a failed start is soft — the first cell observes it and rebuilds. */
137
154
  private bootOnce(engine: E, deadlineMs: number): Promise<boolean> {
138
155
  const work = Promise.resolve()
139
156
  .then(() => engine.start(false))
@@ -146,16 +163,7 @@ export class EngineLifecycle<E extends RevivableEngine> {
146
163
  return Promise.race([work.then(() => true), guard]).finally(() => clearTimeout(timer));
147
164
  }
148
165
 
149
- /**
150
- * Built and booted on demand; the snapshot restore proceeds in the background, so acquire()
151
- * NEVER waits on it. The engine's revive is announced (reset notice + toast) on the first
152
- * cell after it completes.
153
- *
154
- * `sessionKey` guards against sessions bleeding into each other: pi tears the old session
155
- * down before starting the next, but a missed or out-of-order shutdown must never serve one
156
- * conversation's engine and namespace to another — acquire for a different key tears the
157
- * bound engine down (flushing its snapshot) before building the new one.
158
- */
166
+ /** Built on demand; acquire() never waits on the background restore (announced on the first cell after it completes). sessionKey: a different conversation's acquire tears the bound engine down, so sessions can't bleed into each other. */
159
167
  async acquire(
160
168
  origin: AcquireOrigin,
161
169
  sessionKey?: string,
@@ -179,8 +187,7 @@ export class EngineLifecycle<E extends RevivableEngine> {
179
187
  this.engine = engine;
180
188
  let booted = await this.bootOnce(engine, deadline);
181
189
  if (!booted) {
182
- // --- the kernel is alive but stuck; only a kill frees it. Retry once WITHOUT the
183
- // --- snapshot, so a poisoned snapshot cannot wedge the session twice in a row. ---
190
+ // --- the kernel is stuck, not dead; kill and retry once WITHOUT the snapshot so a poisoned one can't wedge twice ---
184
191
  await (this.deps.discard ?? this.deps.dispose)(engine);
185
192
  engine = this.deps.create(true);
186
193
  this.engine = engine;
@@ -192,11 +199,7 @@ export class EngineLifecycle<E extends RevivableEngine> {
192
199
  throw new Error("evaluator boot timed out twice (kernel/helpers wedged); no session was started");
193
200
  }
194
201
  }
195
- // --- recovery is async and off the first call's critical path: the engine revives in
196
- // --- the first quiet gap and the notice lands on the first cell AFTER it completes
197
- // --- (index.ts takes it with takeResetNotice after the next execute). announce when
198
- // --- mid-session rebuilds happen, or on startup for a conversation with a saved past;
199
- // --- a first-ever session stays quiet. ---
202
+ // --- the notice lands on the first cell AFTER the restore completes; announce mid-session rebuilds and resumes with a saved past, never first sessions ---
200
203
  const announce = origin === "cell" || (origin === "startup" && engine.hasSnapshotHistory());
201
204
  void engine.restoreResult().then((restore) => {
202
205
  if (this.engine !== engine) return; // a replacement engine took over; no stale notice
@@ -222,8 +225,7 @@ export class EngineLifecycle<E extends RevivableEngine> {
222
225
  }
223
226
  }
224
227
 
225
- /** Returns the pending reset notice exactly once (alongside its origin, restore result, and
226
- * whether the restore was skipped), then clears it. */
228
+ /** The pending reset notice, taken exactly once. */
227
229
  takeResetNotice():
228
230
  | { notice: string; origin: AcquireOrigin; restore: RestoreResult | null; wedged: boolean }
229
231
  | undefined {
@@ -1,5 +1,4 @@
1
- // --- skills cannot reach the prompt in --repl: pi gates <available_skills> on the read tool
2
- // (hasRead), which repl doesn't have. Re-emit them with pi's own formatter in pi's slot. ---
1
+ // --- pi gates skills on the read tool (absent in repl); re-emit them with pi's own formatter in pi's slot ---
3
2
  import { formatSkillsForPrompt, type Skill } from "@mariozechner/pi-coding-agent";
4
3
 
5
4
  const CWD_MARKER = "\nCurrent working directory:"; // skills sit just before this, pi's last line
@@ -1,34 +1,21 @@
1
- // --- where a conversation's kernel state lives: ~/.pi/agent/pi-repl/state/<key>.
2
- // --- Keys must never collide across conversations, so the project-root slug joins the
3
- // --- conversation name. Pre-slug legacy dirs (bare name) migrate to the slug key on the
4
- // --- owning conversation's next start; the orphan sweep still recognizes both formats,
5
- // --- so nothing live is ever swept and a deleted conversation loses all its snapshots. ---
6
- import { existsSync, renameSync } from "node:fs";
1
+ // --- state lives at ~/.pi/agent/pi-repl/state/<slug>__<conv>: legacy bare-name dirs migrate on the owning conversation's next start, and the sweep knows both formats, so nothing live is swept and a deleted conversation loses its snapshots ---
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync } from "node:fs";
7
3
  import { basename, dirname, join } from "node:path";
8
4
 
9
- /** The conversation's own name: the session file's basename without .jsonl (unique per conversation). */
10
5
  export function conversationName(sessionFile: string): string {
11
6
  return basename(sessionFile).replace(/\.jsonl$/, "");
12
7
  }
13
8
 
14
- /** Slug-keyed state dir name: unique among all conversations under one sessions root, so two
15
- * conversations whose files happen to share a basename (copied/renamed session files) can never
16
- * share a snapshot. */
9
+ /** Slug-keyed dir name: conversations whose files share a basename can never share a snapshot. */
17
10
  export function sessionStateDirName(sessionFile: string): string {
18
11
  return `${basename(dirname(sessionFile))}__${conversationName(sessionFile)}`;
19
12
  }
20
13
 
21
- /** The pre-slug dir name; still honored when migrating or scanning live conversations. */
22
14
  function legacyStateDirName(sessionFile: string): string {
23
15
  return conversationName(sessionFile);
24
16
  }
25
17
 
26
- /**
27
- * Resolve a conversation's state dir and snapshot file, migrating a legacy bare-name dir to the
28
- * slug key on first start. Two conversations whose files share a basename therefore never share a
29
- * snapshot file: whichever starts first migrates the legacy dir to its own key; the other starts
30
- * empty rather than bleeding into the first's namespace.
31
- */
18
+ /** Resolve the state dir, migrating a legacy bare-name dir on first start — whichever conversation starts first owns the legacy dir; the other starts empty. */
32
19
  export function resolveStateDir(stateRoot: string, sessionFile: string): { dir: string; snapshotPath: string } {
33
20
  const dir = join(stateRoot, sessionStateDirName(sessionFile));
34
21
  const legacy = join(stateRoot, legacyStateDirName(sessionFile));
@@ -43,3 +30,29 @@ export function resolveStateDir(stateRoot: string, sessionFile: string): { dir:
43
30
  }
44
31
  return { dir, snapshotPath: join(dir, "namespace.snapshot") };
45
32
  }
33
+
34
+ /** The /fork header's parentSession -> the parent's own snapshot file, when the fork has no history yet. */
35
+ export function forkParentSnapshot(stateRoot: string, sessionFile: string, snapshotPath: string): string | undefined {
36
+ if (existsSync(snapshotPath)) return undefined; // the fork already built its own history
37
+ let header: { parentSession?: unknown };
38
+ try {
39
+ const first = readFileSync(sessionFile, "utf8").split("\n", 1)[0] ?? "";
40
+ header = JSON.parse(first) as { parentSession?: unknown };
41
+ } catch {
42
+ return undefined; // unreadable / not a session file — not a fork
43
+ }
44
+ if (typeof header.parentSession !== "string" || header.parentSession === "") return undefined;
45
+ const parent = resolveStateDir(stateRoot, header.parentSession);
46
+ return existsSync(parent.snapshotPath) ? parent.snapshotPath : undefined;
47
+ }
48
+
49
+ /** Copy the parent's last snapshot into the fork's own key once (true when something was inherited); the fork then restores like any resume, and the parent is never touched. */
50
+ export function inheritForkSnapshot(stateRoot: string, sessionFile: string, snapshotPath: string): boolean {
51
+ const parentSnap = forkParentSnapshot(stateRoot, sessionFile, snapshotPath);
52
+ if (parentSnap === undefined) return false;
53
+ mkdirSync(dirname(snapshotPath), { recursive: true });
54
+ const tmp = `${snapshotPath}.tmp`;
55
+ copyFileSync(parentSnap, tmp);
56
+ renameSync(tmp, snapshotPath);
57
+ return true;
58
+ }
@@ -1,15 +1,8 @@
1
- // --- tool-meta: thin surface assembling the execute tool's prompt from pure modules ---
2
- // --- the model contract lives in prompt.ts; only the helpers wiring stays here ---
3
-
4
- import { buildHelpersMap, buildHelpersMapForCwd } from "./helpers.js";
5
1
  import { buildPromptGuidelines, executePromptSnippet, executeToolDescription } from "./prompt.js";
6
2
 
7
3
  export const EXECUTE_DESCRIPTION = executeToolDescription;
8
4
  export const EXECUTE_PROMPT_SNIPPET = executePromptSnippet;
9
5
 
10
- // --- build the guidelines from project + global helper dirs ---
11
- export function buildExecutePromptGuidelines(cwd?: string): string[] {
12
- const map = cwd ? buildHelpersMapForCwd(cwd) : buildHelpersMap();
13
- const preloaded = map.length > 0 ? map : [];
14
- return buildPromptGuidelines(preloaded);
6
+ export function buildExecutePromptGuidelines(): string[] {
7
+ return buildPromptGuidelines([]);
15
8
  }