@rmrdeveloper/sideroom-pi 9.0.1 → 9.2.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 (63) hide show
  1. package/README.md +35 -40
  2. package/assets/visual-context/browser/background.js +2 -2
  3. package/assets/visual-context/browser/popup.js +1 -2
  4. package/extensions/ask/index.ts +0 -1
  5. package/extensions/ask/model.ts +1 -1
  6. package/extensions/ask/selection.ts +0 -4
  7. package/extensions/done/guard.ts +18 -35
  8. package/extensions/done/index.ts +0 -1
  9. package/extensions/done/model.ts +2 -31
  10. package/extensions/explain/index.ts +4 -7
  11. package/extensions/explain/prompt.ts +13 -0
  12. package/extensions/guidelines/execute.ts +16 -2
  13. package/extensions/guidelines/guard.ts +1 -2
  14. package/extensions/guidelines/index.ts +7 -7
  15. package/extensions/guidelines/prompt.ts +0 -2
  16. package/extensions/guidelines/review.ts +9 -10
  17. package/extensions/jev/client.ts +0 -7
  18. package/extensions/jev/context.ts +0 -2
  19. package/extensions/jev/guard.ts +78 -44
  20. package/extensions/jev/index.ts +0 -3
  21. package/extensions/jev/key.ts +0 -3
  22. package/extensions/jev/model.ts +0 -8
  23. package/extensions/jev/queue.ts +125 -0
  24. package/extensions/jev/ui.ts +0 -14
  25. package/extensions/modified-files/index.ts +0 -1
  26. package/extensions/monorepo-skills/index.ts +0 -1
  27. package/extensions/persona/checks.ts +0 -4
  28. package/extensions/persona/guard.ts +45 -6
  29. package/extensions/persona/index.ts +4 -2
  30. package/extensions/persona/model.ts +0 -2
  31. package/extensions/persona/prompt.ts +0 -2
  32. package/extensions/rules/checks.ts +0 -5
  33. package/extensions/rules/index.ts +0 -1
  34. package/extensions/rules/lexer.ts +1 -1
  35. package/extensions/shared/check-status.ts +35 -0
  36. package/extensions/shared/file-path.ts +0 -3
  37. package/extensions/shared/read-file.ts +0 -2
  38. package/extensions/shared/settle.ts +78 -13
  39. package/extensions/todo/guards.ts +40 -84
  40. package/extensions/todo/index.ts +1 -2
  41. package/extensions/todo/model.ts +6 -48
  42. package/extensions/todo/session.ts +1 -4
  43. package/extensions/visual-context/broker.ts +10 -10
  44. package/extensions/visual-context/configure.ts +4 -4
  45. package/extensions/visual-context/files.ts +0 -3
  46. package/extensions/visual-context/index.ts +8 -9
  47. package/extensions/visual-context/literal.ts +0 -1
  48. package/extensions/visual-context/markup.ts +3 -5
  49. package/extensions/visual-context/model.ts +2 -2
  50. package/extensions/visual-context/pairing.ts +1 -2
  51. package/extensions/visual-context/parse-astro.ts +9 -12
  52. package/extensions/visual-context/parse-jsx.ts +2 -2
  53. package/extensions/visual-context/parse-vue.ts +1 -1
  54. package/extensions/visual-context/setup.ts +8 -8
  55. package/extensions/visual-context/source.ts +0 -3
  56. package/package.json +15 -9
  57. package/skills/sideroom-guidelines/SKILL.md +1 -1
  58. package/skills/sideroom-persona/SKILL.md +9 -7
  59. package/skills/sideroom-user-stories/SKILL.md +78 -0
  60. package/skills/sideroom-user-stories/references/review.md +84 -0
  61. package/skills/sideroom-user-stories/references/templates.md +131 -0
  62. package/extensions/explain/guard.ts +0 -70
  63. package/extensions/explain/model.ts +0 -71
@@ -39,9 +39,6 @@ const DEBUG_PATTERNS: readonly RegExp[] = [
39
39
  /\bdbg!\s*\(/,
40
40
  ];
41
41
 
42
- // Keep these patterns on syntax that can only introduce a type. Value-level
43
- // commas, assignments, and operators may legally surround an identifier named
44
- // `any`, so they are deliberately absent.
45
42
  const EXPLICIT_ANY_PATTERNS: readonly RegExp[] = [
46
43
  /\bas\s+any\b/,
47
44
  /\b(?:keyof|satisfies)\s+any\b/,
@@ -54,8 +51,6 @@ const EXPLICIT_ANY_PATTERNS: readonly RegExp[] = [
54
51
  /^\s*(?:(?:public|private|protected|readonly|static|declare|abstract)\s+)*[$\w]+[?!]?\s*:\s*(?:(?:=>)|[^;,=\n])*\bany\b[^;=\n]*(?:;|=)/,
55
52
  ];
56
53
 
57
- // A suppression is a comment whose first token is the directive; prose that
58
- // mentions the directive, or a string that quotes it, is not one.
59
54
  const SUPPRESSED_TYPE_ERRORS =
60
55
  /(?:^|\s)(?:\/\/|\/\*)\s*@ts-(?:ignore|nocheck)\b/;
61
56
 
@@ -5,7 +5,6 @@ import {
5
5
  resetRulesState,
6
6
  } from './guard.ts';
7
7
 
8
- // Pi loads extensions/*/index.ts through export default.
9
8
  export default function registerRules(pi: ExtensionAPI): void {
10
9
  const state = createRulesState();
11
10
  registerRulesGuard(pi, state);
@@ -10,7 +10,7 @@ const LEXICAL_MODE = {
10
10
  type LexicalMode = (typeof LEXICAL_MODE)[keyof typeof LEXICAL_MODE];
11
11
 
12
12
  export interface MaskOptions {
13
- /** Keep comment text so a caller can read the directives inside it. */
13
+
14
14
  readonly keepComments?: boolean;
15
15
  }
16
16
 
@@ -0,0 +1,35 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from '@earendil-works/pi-coding-agent';
5
+
6
+ const CHECK_STATUS_EVENT = 'sideroom:check-status';
7
+
8
+ export interface CheckStatus {
9
+ readonly command: string;
10
+ readonly green: boolean;
11
+ }
12
+
13
+ interface CheckStatusRequest {
14
+ readonly ctx: ExtensionContext;
15
+ status: CheckStatus | undefined;
16
+ }
17
+
18
+ export function registerCheckStatus(
19
+ pi: ExtensionAPI,
20
+ resolve: (ctx: ExtensionContext) => CheckStatus | undefined,
21
+ ): void {
22
+ pi.events.on(CHECK_STATUS_EVENT, (payload) => {
23
+ const request = payload as CheckStatusRequest;
24
+ request.status = resolve(request.ctx);
25
+ });
26
+ }
27
+
28
+ export function readCheckStatus(
29
+ pi: ExtensionAPI,
30
+ ctx: ExtensionContext,
31
+ ): CheckStatus | undefined {
32
+ const request: CheckStatusRequest = { ctx, status: undefined };
33
+ pi.events.emit(CHECK_STATUS_EVENT, request);
34
+ return request.status;
35
+ }
@@ -10,7 +10,6 @@ const MISSING_PATH_ERROR_CODE = {
10
10
  } as const;
11
11
  const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
12
12
 
13
- // File guards must resolve the model's path exactly as Pi's built-in tools do.
14
13
  export function resolveFileToolPath(
15
14
  cwd: string,
16
15
  path: string,
@@ -29,8 +28,6 @@ export function resolveFileToolPath(
29
28
  return realpathOr(absolutePath);
30
29
  }
31
30
 
32
- // A file that does not exist yet keeps its unresolved path, so it is matched
33
- // against the literal working directory as well as the canonical one.
34
31
  export function projectRelativePath(
35
32
  cwd: string,
36
33
  absolutePath: string,
@@ -1,7 +1,5 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
 
3
- // A missing file is an expected answer to "what was there before", not a
4
- // failure. Anything else about the read is a real problem and surfaces.
5
3
  export function readFileIfExists(path: string): string | undefined {
6
4
  try {
7
5
  return readFileSync(path, 'utf8');
@@ -10,8 +10,59 @@ import type {
10
10
  const COMPLETED_OUTCOME = 'completed';
11
11
  const CUSTOM_MESSAGE_ENTRY = 'custom_message';
12
12
  const SETTLE_PRIORITY_EVENT = 'sideroom:settle-priority';
13
+ const SETTLE_SUPPLEMENT_EVENT = 'sideroom:settle-supplement';
14
+ const SETTLE_WAIT_EVENT = 'sideroom:settle-wait';
13
15
 
14
- export const SETTLE_PRIORITY = { review: 0, done: 1, explain: 2 } as const;
16
+ interface SettleWaitRequest {
17
+ readonly ctx: ExtensionContext;
18
+ readonly waits: Promise<void>[];
19
+ }
20
+
21
+ export function registerSettleWait(
22
+ pi: ExtensionAPI,
23
+ wait: (ctx: ExtensionContext) => Promise<void>,
24
+ ): void {
25
+ pi.events.on(SETTLE_WAIT_EVENT, (payload) => {
26
+ const request = payload as SettleWaitRequest;
27
+ request.waits.push(wait(request.ctx));
28
+ });
29
+ pi.on('agent_before_settle', async (event, ctx) => {
30
+ if (acceptsSettleSteer(event)) {
31
+ await wait(ctx);
32
+ }
33
+ });
34
+ }
35
+
36
+ async function waitForSettle(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
37
+ const request: SettleWaitRequest = { ctx, waits: [] };
38
+ pi.events.emit(SETTLE_WAIT_EVENT, request);
39
+ await Promise.all(request.waits);
40
+ }
41
+
42
+ export const SETTLE_PRIORITY = { review: 0, persona: 1 } as const;
43
+
44
+ interface SettleSupplement {
45
+ readonly customType: string;
46
+ readonly content: string;
47
+ }
48
+
49
+ interface SettleSupplementRequest {
50
+ readonly ctx: ExtensionContext;
51
+ readonly messages: SettleSupplement[];
52
+ }
53
+
54
+ export function registerSettleSupplement(
55
+ pi: ExtensionAPI,
56
+ prepare: (ctx: ExtensionContext) => SettleSupplement | undefined,
57
+ ): void {
58
+ pi.events.on(SETTLE_SUPPLEMENT_EVENT, (payload) => {
59
+ const request = payload as SettleSupplementRequest;
60
+ const message = prepare(request.ctx);
61
+ if (message !== undefined) {
62
+ request.messages.push(message);
63
+ }
64
+ });
65
+ }
15
66
 
16
67
  interface SettlePriorityRequest {
17
68
  readonly ctx: ExtensionContext;
@@ -31,8 +82,6 @@ const USER_INPUT_SOURCES: ReadonlySet<InputSource> = new Set([
31
82
  'rpc',
32
83
  ]);
33
84
 
34
- // A message typed while the agent runs joins the current run as a steer or
35
- // follow-up; only input that arrives idle opens a new turn.
36
85
  export function startsUserTurn(event: InputEvent): boolean {
37
86
  return (
38
87
  USER_INPUT_SOURCES.has(event.source) &&
@@ -40,8 +89,6 @@ export function startsUserTurn(event: InputEvent): boolean {
40
89
  );
41
90
  }
42
91
 
43
- // Any queued message or an earlier handler's continuation still has work to
44
- // run. Yield without consuming the guard's state until a quiet boundary.
45
92
  export function acceptsSettleSteer(event: AgentBeforeSettleEvent): boolean {
46
93
  return (
47
94
  event.outcome === COMPLETED_OUTCOME &&
@@ -50,9 +97,6 @@ export function acceptsSettleSteer(event: AgentBeforeSettleEvent): boolean {
50
97
  );
51
98
  }
52
99
 
53
- // Pi gives each extension its own API object. Its synchronous event bus is
54
- // shared, so ask which stages are pending rather than relying on load order
55
- // or module-global state that would leak across sessions and reloads.
56
100
  export function registerSettleGuard(
57
101
  pi: ExtensionAPI,
58
102
  guard: SettleGuard,
@@ -64,8 +108,12 @@ export function registerSettleGuard(
64
108
  }
65
109
  });
66
110
 
67
- pi.on('agent_before_settle', (event, ctx) => {
68
- if (!acceptsSettleSteer(event) || !guard.pending(ctx)) {
111
+ pi.on('agent_before_settle', async (event, ctx) => {
112
+ if (!acceptsSettleSteer(event)) {
113
+ return;
114
+ }
115
+ await waitForSettle(pi, ctx);
116
+ if (ctx.signal?.aborted || ctx.hasPendingMessages() || !acceptsSettleSteer(event) || !guard.pending(ctx)) {
69
117
  return;
70
118
  }
71
119
  const request: SettlePriorityRequest = { ctx, priorities: new Set() };
@@ -73,12 +121,29 @@ export function registerSettleGuard(
73
121
  if ([...request.priorities].some((priority) => priority < guard.priority)) {
74
122
  return;
75
123
  }
76
- return guard.handle(event, ctx);
124
+ const continuation = guard.handle(event, ctx);
125
+ if (!continuation?.continue) {
126
+ return continuation;
127
+ }
128
+ const supplements: SettleSupplementRequest = { ctx, messages: [] };
129
+ pi.events.emit(SETTLE_SUPPLEMENT_EVENT, supplements);
130
+ return {
131
+ ...continuation,
132
+ entries: [
133
+ ...(continuation.entries ?? event.entries),
134
+ ...supplements.messages.map(
135
+ (message) =>
136
+ ({
137
+ type: CUSTOM_MESSAGE_ENTRY,
138
+ ...message,
139
+ display: false,
140
+ }) as const,
141
+ ),
142
+ ],
143
+ };
77
144
  });
78
145
  }
79
146
 
80
- // Handlers chain on agent_before_settle, so keep the entries earlier handlers
81
- // proposed; replacing them would drop another extension's steer.
82
147
  interface HiddenSteerOptions {
83
148
  readonly event: AgentBeforeSettleEvent;
84
149
  readonly customType: string;
@@ -1,109 +1,65 @@
1
- import type {
2
- ExtensionAPI,
3
- ExtensionContext,
4
- } from '@earendil-works/pi-coding-agent';
5
1
  import {
6
- shouldNudgePropose,
7
- shouldWatchdogUpdate,
8
- TOOL_NAME,
9
- } from './model.ts';
2
+ type ExtensionAPI,
3
+ type ExtensionContext,
4
+ isEditToolResult,
5
+ isWriteToolResult,
6
+ } from '@earendil-works/pi-coding-agent';
7
+ import { startsUserTurn } from '../shared/settle.ts';
8
+ import { TODO_STATUS } from './model.ts';
10
9
  import type { TodoStore } from './session.ts';
11
10
 
12
- const NUDGE_TYPE = 'sideroom-todo-nudge';
13
- const WATCHDOG_TYPE = 'sideroom-todo-watchdog';
14
- const MUTATING_TOOL_NAMES = new Set(['write', 'edit', 'bash']);
15
-
16
- interface TodoRunState {
17
- proposeNudgedThisRun: boolean;
18
- updateWatchdogThisTurn: boolean;
19
- turnMutated: boolean;
20
- turnCalledTodo: boolean;
21
- steerFromUs: boolean;
22
- }
11
+ const REMINDER_TYPE = 'sideroom-todo-reminder';
23
12
 
24
13
  export function registerGuardEvents(
25
14
  pi: ExtensionAPI,
26
15
  store: TodoStore,
27
16
  restore: (ctx: ExtensionContext) => void,
28
17
  ): void {
29
- const runState = createTodoRunState();
18
+ let remindedThisTurn = false;
30
19
 
31
20
  pi.on('input', (event) => {
32
- if (event.source === 'interactive' || event.source === 'rpc') {
33
- runState.proposeNudgedThisRun = false;
34
- runState.steerFromUs = false;
21
+ if (startsUserTurn(event)) {
22
+ remindedThisTurn = false;
35
23
  }
36
24
  });
25
+ pi.on('session_start', () => {
26
+ remindedThisTurn = false;
27
+ });
37
28
 
38
- pi.on('turn_start', () => resetTurnState(runState));
39
-
40
- pi.on('tool_execution_start', (event, ctx) => {
41
- if (event.toolName === TOOL_NAME) {
42
- runState.turnCalledTodo = true;
29
+ pi.on('tool_result', (event, ctx) => {
30
+ if (
31
+ remindedThisTurn ||
32
+ event.isError ||
33
+ (!isWriteToolResult(event) && !isEditToolResult(event))
34
+ ) {
43
35
  return;
44
36
  }
45
- if (!MUTATING_TOOL_NAMES.has(event.toolName)) {
46
- return;
47
- }
48
-
49
- runState.turnMutated = true;
50
37
  restore(ctx);
51
- if (!shouldNudgePropose({ items: store.items, ...runState })) {
38
+ if (store.items.length === 0 && ctx.mode !== 'tui') {
52
39
  return;
53
40
  }
54
-
55
- runState.proposeNudgedThisRun = true;
56
- sendSteer(
57
- pi,
58
- runState,
59
- NUDGE_TYPE,
60
- 'Use sideroom_todo propose now to create the live board before continuing implementation.',
61
- );
62
- });
63
-
64
- pi.on('turn_end', (_event, ctx) => {
65
- restore(ctx);
66
- if (!shouldWatchdogUpdate({ items: store.items, ...runState })) {
41
+ if (
42
+ store.items.length > 0 &&
43
+ !store.items.some(
44
+ (item) =>
45
+ item.status === TODO_STATUS.pending ||
46
+ item.status === TODO_STATUS.inProgress,
47
+ )
48
+ ) {
67
49
  return;
68
50
  }
69
51
 
70
- runState.updateWatchdogThisTurn = true;
71
- sendSteer(
72
- pi,
73
- runState,
74
- WATCHDOG_TYPE,
75
- 'Use sideroom_todo update to reflect this work before continuing to the next item.',
52
+ remindedThisTurn = true;
53
+ pi.sendMessage(
54
+ {
55
+ customType: REMINDER_TYPE,
56
+ content:
57
+ store.items.length === 0
58
+ ? 'Use sideroom_todo propose if this implementation has multiple steps. Maintain the board at task milestones, not after every tool call.'
59
+ : 'Update sideroom_todo when a task changes status, combining completion and the next task in one call. Do not update merely because a tool ran.',
60
+ display: false,
61
+ },
62
+ { triggerTurn: false, deliverAs: 'steer' },
76
63
  );
77
64
  });
78
65
  }
79
-
80
- function createTodoRunState(): TodoRunState {
81
- return {
82
- proposeNudgedThisRun: false,
83
- updateWatchdogThisTurn: false,
84
- turnMutated: false,
85
- turnCalledTodo: false,
86
- steerFromUs: false,
87
- };
88
- }
89
-
90
- function resetTurnState(runState: TodoRunState): void {
91
- runState.turnMutated = false;
92
- runState.turnCalledTodo = false;
93
- runState.updateWatchdogThisTurn = false;
94
- // A steer only silences the turn that sent it; later turns may steer again.
95
- runState.steerFromUs = false;
96
- }
97
-
98
- function sendSteer(
99
- pi: ExtensionAPI,
100
- runState: TodoRunState,
101
- customType: string,
102
- content: string,
103
- ): void {
104
- runState.steerFromUs = true;
105
- pi.sendMessage(
106
- { customType, content, display: false },
107
- { triggerTurn: true, deliverAs: 'steer' },
108
- );
109
- }
@@ -29,13 +29,12 @@ export const TODO_PROMPT_SNIPPET =
29
29
  export const TODO_PROMPT_GUIDELINES = [
30
30
  'Use sideroom_todo to maintain a live work board when implementation has more than one step.',
31
31
  'While any item is pending, exactly one must be in_progress.',
32
- 'Complete the current item and start the next item in the same update call.',
32
+ 'Update at task milestones, not after each tool call. Complete the current item and start the next item in the same update call.',
33
33
  'propose is interactive-TUI-only; update also works in print, JSON, and RPC modes.',
34
34
  ];
35
35
 
36
36
  const BOARD_SHORTCUT = 'f9';
37
37
 
38
- // Pi loads extensions/*/index.ts through export default.
39
38
  export default function registerTodo(pi: ExtensionAPI): void {
40
39
  const store = createTodoStore();
41
40
  const overlay = createTodoBoardOverlayController(() => store.items);
@@ -66,25 +66,11 @@ const UpdateParamsSchema = Type.Object({
66
66
  patches: TodoPatchesField,
67
67
  });
68
68
 
69
- /**
70
- * Strict validator and types for sideroom_todo arguments. Kept as a
71
- * discriminated union so invalid action/field combinations are rejected
72
- * exactly. This schema is not registered with Pi; see
73
- * TodoToolParametersSchema.
74
- */
75
69
  export const TodoParamsSchema = Type.Union([
76
70
  ProposeParamsSchema,
77
71
  UpdateParamsSchema,
78
72
  ]);
79
73
 
80
- /**
81
- * Tool-facing schema registered with Pi. It must stay a top-level object:
82
- * Claude Code silently drops every tool of an MCP server when one tool's
83
- * inputSchema has a non-object top level (anyOf/oneOf/allOf), which is exactly
84
- * what TodoParamsSchema emits. The discriminated union is still enforced at
85
- * runtime by parseTodoParams. See docs/extensions/todo.md ("Tool schema
86
- * compatibility") for the bug references and the condition to revert.
87
- */
88
74
  export const TodoToolParametersSchema = Type.Object({
89
75
  action: TodoActionSchema,
90
76
  items: Type.Optional(TodoItemsField),
@@ -110,13 +96,13 @@ export interface TodoPatch {
110
96
 
111
97
  export type ParsedTodoParams =
112
98
  | {
113
- readonly action: typeof TODO_ACTION.propose;
114
- readonly items: readonly TodoItem[];
115
- }
99
+ readonly action: typeof TODO_ACTION.propose;
100
+ readonly items: readonly TodoItem[];
101
+ }
116
102
  | {
117
- readonly action: typeof TODO_ACTION.update;
118
- readonly patches: readonly TodoPatch[];
119
- };
103
+ readonly action: typeof TODO_ACTION.update;
104
+ readonly patches: readonly TodoPatch[];
105
+ };
120
106
 
121
107
  export type ParseTodoResult =
122
108
  | { readonly ok: true; readonly params: ParsedTodoParams }
@@ -126,15 +112,6 @@ export type ApplyTodoResult =
126
112
  | { readonly ok: true; readonly items: readonly TodoItem[] }
127
113
  | { readonly ok: false; readonly message: string };
128
114
 
129
- export interface TodoGuardState {
130
- readonly items: readonly TodoItem[];
131
- readonly turnMutated: boolean;
132
- readonly turnCalledTodo: boolean;
133
- readonly proposeNudgedThisRun: boolean;
134
- readonly updateWatchdogThisTurn: boolean;
135
- readonly steerFromUs: boolean;
136
- }
137
-
138
115
  const STATUS_MARKS: Readonly<Record<TodoStatus, string>> = {
139
116
  [TODO_STATUS.pending]: '-',
140
117
  [TODO_STATUS.inProgress]: '>',
@@ -293,25 +270,6 @@ export function formatBoardBlock(items: readonly TodoItem[]): string {
293
270
  ].join('\n');
294
271
  }
295
272
 
296
- export function shouldNudgePropose(state: TodoGuardState): boolean {
297
- return (
298
- state.turnMutated &&
299
- state.items.length === 0 &&
300
- !state.proposeNudgedThisRun &&
301
- !state.steerFromUs
302
- );
303
- }
304
-
305
- export function shouldWatchdogUpdate(state: TodoGuardState): boolean {
306
- return (
307
- state.turnMutated &&
308
- state.items.length > 0 &&
309
- !state.turnCalledTodo &&
310
- !state.updateWatchdogThisTurn &&
311
- !state.steerFromUs
312
- );
313
- }
314
-
315
273
  function normalizeItems(
316
274
  items: readonly Static<typeof TodoItemSchema>[],
317
275
  ):
@@ -94,8 +94,7 @@ export function registerSessionRefreshEvents(
94
94
  store: TodoStore,
95
95
  restoreAndRefresh: BoardLifecycle,
96
96
  ): void {
97
- // A restore can drop the sent block from the model's context, so an unchanged
98
- // board still has to be announced again.
97
+
99
98
  const restoreSession = (ctx: ExtensionContext) => {
100
99
  restoreAndRefresh(ctx);
101
100
  forgetSentBoardBlock(store);
@@ -132,8 +131,6 @@ export function registerBoardContext(
132
131
  });
133
132
  }
134
133
 
135
- // The block rides a session message instead of the system prompt: a volatile
136
- // system prompt invalidates the cached prefix of the whole request.
137
134
  function pendingBoardBlock(store: TodoStore): string | undefined {
138
135
  if (store.items.length === 0) {
139
136
  return undefined;
@@ -176,17 +176,17 @@ export async function startBroker({
176
176
  delivery.browser,
177
177
  typeof message.error === 'string' || stale
178
178
  ? {
179
- type: MESSAGE_TYPE.error,
180
- id: delivery.id,
181
- message: stale
182
- ? 'The session changed. Select the element again.'
183
- : String(message.error).slice(0, 500),
184
- }
179
+ type: MESSAGE_TYPE.error,
180
+ id: delivery.id,
181
+ message: stale
182
+ ? 'The session changed. Select the element again.'
183
+ : String(message.error).slice(0, 500),
184
+ }
185
185
  : {
186
- type: MESSAGE_TYPE.received,
187
- id: delivery.id,
188
- candidates: message.candidates,
189
- },
186
+ type: MESSAGE_TYPE.received,
187
+ id: delivery.id,
188
+ candidates: message.candidates,
189
+ },
190
190
  );
191
191
  return;
192
192
  }
@@ -102,8 +102,8 @@ function staticObject(expression: unknown, document: Document): ConfigObject {
102
102
  entry.type === 'ExportNamedDeclaration' ? entry.declaration : entry;
103
103
  return statement?.type === 'VariableDeclaration'
104
104
  ? statement.declarations.flatMap((item) =>
105
- item === declaration ? [] : [item.init],
106
- )
105
+ item === declaration ? [] : [item.init],
106
+ )
107
107
  : [];
108
108
  });
109
109
  if (
@@ -338,7 +338,7 @@ export function configureSource({
338
338
  if (value.start === null || value.start === undefined) {
339
339
  throw new Error('The plugin array location is unavailable.');
340
340
  }
341
- // Inspect original markup before framework plugins erase its source positions.
341
+
342
342
  replacements.push({
343
343
  start: value.start + 1,
344
344
  end: value.start + 1,
@@ -415,7 +415,7 @@ export function configureSource({
415
415
  replacement.text +
416
416
  configured.slice(replacement.end);
417
417
  }
418
- // Reject a broken patch before the parent agent receives a configuration edit.
418
+
419
419
  parse(configured, { sourceType: 'unambiguous', plugins: ['typescript'] });
420
420
  return { source: configured };
421
421
  }
@@ -67,8 +67,6 @@ export interface VisualPathPolicy {
67
67
  readonly allowedPath: (path: string) => string | undefined;
68
68
  }
69
69
 
70
- // Reuse ignore rules only within one synchronous operation. File metadata is
71
- // always read again: a remembered path must not authorize a replacement symlink.
72
70
  export function createPathPolicy(cwd: string): VisualPathPolicy {
73
71
  const root = realpathSync(cwd);
74
72
  const policies = new Map<
@@ -133,7 +131,6 @@ export function createPathPolicy(cwd: string): VisualPathPolicy {
133
131
  }
134
132
  }
135
133
 
136
- // Browser paths are untrusted, including paths reported by compiler plugins.
137
134
  export function allowedPath(cwd: string, path: string): string | undefined {
138
135
  return createPathPolicy(cwd).allowedPath(path);
139
136
  }
@@ -71,7 +71,7 @@ export default function registerVisualContext(
71
71
  commitPending({ store, pi, pending });
72
72
  refresh(ctx);
73
73
  };
74
- // An async dialog may only act while it still belongs to the active session.
74
+
75
75
  const dialogIsCurrent = (ctx: ExtensionContext, epoch: number): boolean =>
76
76
  epoch === generation && activeContext === ctx;
77
77
  async function activate(ctx: ExtensionContext): Promise<void> {
@@ -191,8 +191,7 @@ export default function registerVisualContext(
191
191
  images: event.images,
192
192
  };
193
193
  });
194
- // Input precedes model/auth validation in Pi. Keep the pending snapshot until
195
- // the run actually starts so a failed submission cannot lose the attachment.
194
+
196
195
  pi.on('before_agent_start', (event, ctx) => {
197
196
  const attachment = preparedContext;
198
197
  preparedContext = undefined;
@@ -304,13 +303,13 @@ export default function registerVisualContext(
304
303
  const pending =
305
304
  selected.action === 'remove'
306
305
  ? store.pending.filter(
307
- (item) => item.selection.id !== entry.selection.id,
308
- )
306
+ (item) => item.selection.id !== entry.selection.id,
307
+ )
309
308
  : store.pending.map((item) =>
310
- item.selection.id === entry.selection.id
311
- ? { ...item, chosen: candidate }
312
- : item,
313
- );
309
+ item.selection.id === entry.selection.id
310
+ ? { ...item, chosen: candidate }
311
+ : item,
312
+ );
314
313
  savePending(ctx, pending);
315
314
  } finally {
316
315
  opening = false;
@@ -13,7 +13,6 @@ export function isBabelSyntaxError(cause: unknown): boolean {
13
13
  );
14
14
  }
15
15
 
16
- // Only syntax literals are read. No inspected source is imported or executed.
17
16
  export function literalText(expression: string): string | undefined {
18
17
  let node: ReturnType<typeof parseExpression>;
19
18
  try {
@@ -12,9 +12,9 @@ export type SourceNode =
12
12
  | SourceElement
13
13
  | { readonly kind: 'text'; readonly value: string }
14
14
  | {
15
- readonly kind: 'group' | 'dynamic';
16
- readonly children: readonly SourceNode[];
17
- };
15
+ readonly kind: 'group' | 'dynamic';
16
+ readonly children: readonly SourceNode[];
17
+ };
18
18
 
19
19
  export interface ParsedMarkup {
20
20
  readonly nodes: readonly SourceNode[];
@@ -81,8 +81,6 @@ interface ElementRangeInput {
81
81
  readonly tag: string;
82
82
  }
83
83
 
84
- // Parsers may recover malformed syntax or omit locations. Such nodes must not
85
- // silently become source claims, even if their descriptors look convincing.
86
84
  export function verifiedElementRange({
87
85
  source,
88
86
  start,
@@ -118,9 +118,9 @@ export interface SourceCandidate {
118
118
 
119
119
  export interface SourceResolution {
120
120
  readonly candidates: readonly SourceCandidate[];
121
- // A limit or unparseable file left the search incomplete; no match is not proof of absence.
121
+
122
122
  readonly truncated: boolean;
123
- // More matches existed than the candidate cap lists.
123
+
124
124
  readonly capped: boolean;
125
125
  readonly issue?: string;
126
126
  }