@bastani/atomic 0.9.0-alpha.2 → 0.9.0-alpha.4

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 (95) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/package.json +1 -1
  7. package/dist/builtin/workflows/CHANGELOG.md +24 -0
  8. package/dist/builtin/workflows/README.md +12 -12
  9. package/dist/builtin/workflows/builtin/goal-ledger.ts +2 -0
  10. package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -0
  11. package/dist/builtin/workflows/builtin/goal-reports.ts +5 -0
  12. package/dist/builtin/workflows/builtin/goal-runner.ts +103 -4
  13. package/dist/builtin/workflows/builtin/goal-types.ts +4 -0
  14. package/dist/builtin/workflows/builtin/goal.d.ts +4 -0
  15. package/dist/builtin/workflows/builtin/goal.ts +14 -2
  16. package/dist/builtin/workflows/builtin/index.d.ts +8 -8
  17. package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +359 -0
  18. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +254 -352
  19. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +256 -414
  20. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +272 -0
  21. package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +58 -68
  22. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +5 -9
  23. package/dist/builtin/workflows/builtin/open-claude-design.ts +14 -26
  24. package/dist/builtin/workflows/builtin/prompt-refinement.ts +102 -0
  25. package/dist/builtin/workflows/builtin/ralph-core.ts +6 -4
  26. package/dist/builtin/workflows/builtin/ralph-runner.ts +22 -24
  27. package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
  28. package/dist/builtin/workflows/builtin/ralph.ts +3 -1
  29. package/dist/builtin/workflows/package.json +1 -1
  30. package/dist/builtin/workflows/skills/impeccable/SKILL.md +14 -23
  31. package/dist/builtin/workflows/skills/impeccable/reference/brand.md +2 -2
  32. package/dist/builtin/workflows/skills/impeccable/reference/live.md +25 -4
  33. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +1 -1
  34. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +724 -29
  35. package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +1 -1
  36. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +219 -7
  37. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +57 -11
  38. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +750 -0
  39. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +648 -53
  40. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
  41. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
  42. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +44 -11
  43. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
  44. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
  45. package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +1 -1
  46. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
  47. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +401 -46
  48. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  49. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +6 -6
  50. package/dist/builtin/workflows/skills/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
  51. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  52. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  53. package/dist/builtin/workflows/skills/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +2 -2
  54. package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-args.mjs +42 -0
  55. package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  56. package/dist/builtin/workflows/skills/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +1 -0
  57. package/dist/builtin/workflows/skills/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
  58. package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
  59. package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  60. package/dist/builtin/workflows/skills/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
  61. package/dist/builtin/workflows/skills/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +21 -3
  62. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +835 -0
  63. package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
  64. package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +180 -0
  65. package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
  66. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +185 -60
  67. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +146 -0
  68. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +3369 -1026
  69. package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
  70. package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +2 -2
  71. package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
  72. package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +133 -9
  73. package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +42 -2
  74. package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +4 -4
  75. package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +21 -15
  76. package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +1 -1
  77. package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +205 -1269
  78. package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +2 -2
  79. package/dist/builtin/workflows/skills/impeccable/scripts/live-target.mjs +30 -0
  80. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +69 -26
  81. package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +73 -22
  82. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +3 -1
  83. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  84. package/dist/core/atomic-guide-command.js +5 -5
  85. package/dist/core/atomic-guide-command.js.map +1 -1
  86. package/dist/core/system-prompt.d.ts.map +1 -1
  87. package/dist/core/system-prompt.js +0 -1
  88. package/dist/core/system-prompt.js.map +1 -1
  89. package/docs/index.md +2 -2
  90. package/docs/quickstart.md +9 -9
  91. package/docs/workflows.md +816 -47
  92. package/package.json +2 -2
  93. package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
  94. package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +0 -126
  95. /package/dist/builtin/workflows/skills/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
@@ -16,8 +16,8 @@
16
16
  */
17
17
 
18
18
  import { buildManualEditEvidence } from './live-manual-edit-evidence.mjs';
19
- import { readBuffer, readBufferStrict, writeBuffer, countByPage } from './live-manual-edits-buffer.mjs';
20
- import { isGeneratedFile } from './is-generated.mjs';
19
+ import { readBuffer, readBufferStrict, writeBuffer, countByPage } from './live/manual-edits-buffer.mjs';
20
+ import { isGeneratedFile } from './lib/is-generated.mjs';
21
21
  import {
22
22
  runCopyEditBatchAgent,
23
23
  runCopyEditPostApplyChecks,
@@ -3,8 +3,8 @@
3
3
  * Canonical durable completion acknowledgement for Impeccable live sessions.
4
4
  */
5
5
 
6
- import { createLiveSessionStore } from './live-session-store.mjs';
7
- import { readLiveServerInfo } from './impeccable-paths.mjs';
6
+ import { createLiveSessionStore } from './live/session-store.mjs';
7
+ import { readLiveServerInfo } from './lib/impeccable-paths.mjs';
8
8
 
9
9
  function parseArgs(argv) {
10
10
  const out = { status: 'complete' };
@@ -16,7 +16,7 @@
16
16
  * Output JSON: { discarded: N, entries: [...discardedEntries], totalCount: N }
17
17
  */
18
18
 
19
- import { readBuffer, removeEntries, truncateBuffer } from './live-manual-edits-buffer.mjs';
19
+ import { readBuffer, removeEntries, truncateBuffer } from './live/manual-edits-buffer.mjs';
20
20
 
21
21
  function argVal(args, name) {
22
22
  const prefix = name + '=';
@@ -16,12 +16,41 @@
16
16
  import fs from 'node:fs';
17
17
  import path from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
- import { resolveLiveConfigPath } from './impeccable-paths.mjs';
19
+ import { resolveLiveConfigPath } from './lib/impeccable-paths.mjs';
20
+ import {
21
+ applySvelteKitLiveAdapter,
22
+ detectSvelteKitProject,
23
+ removeSvelteKitLiveAdapter,
24
+ } from './live/sveltekit-adapter.mjs';
20
25
 
21
26
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
27
  const CONFIG_PATH = resolveLiveConfigPath({ cwd: process.cwd(), scriptsDir: __dirname });
23
28
  const MARKER_OPEN_TEXT = 'impeccable-live-start';
24
29
  const MARKER_CLOSE_TEXT = 'impeccable-live-end';
30
+ const IGNORE_MARKER_OPEN = '# impeccable-live-ignore-start';
31
+ const IGNORE_MARKER_CLOSE = '# impeccable-live-ignore-end';
32
+
33
+ export const LIVE_IGNORE_PATTERNS = Object.freeze([
34
+ '.impeccable/hook.cache.json',
35
+ '.impeccable/hook.pending.json',
36
+ '.impeccable/config.local.json',
37
+ '.impeccable/live/server.json',
38
+ '.impeccable/live/sessions/',
39
+ '.impeccable/live/previews/',
40
+ '.impeccable/live/annotations/',
41
+ '.impeccable/live/cache/',
42
+ '.impeccable/live/manual-edit-apply-transaction.json',
43
+ '.impeccable/live/manual-edit-events.jsonl',
44
+ '.impeccable/live/manual-edit-evidence/',
45
+ '.impeccable/live/pending-manual-edits.json',
46
+ '.impeccable/live/deferred-svelte-component-accepts.json',
47
+ '.impeccable-live.json',
48
+ '.impeccable-live/',
49
+ 'node_modules/.impeccable-live/',
50
+ 'src/lib/impeccable/ImpeccableLiveRoot.svelte',
51
+ 'src/lib/impeccable/__runtime.js',
52
+ 'src/lib/impeccable/[0-9a-f]*/',
53
+ ]);
25
54
 
26
55
  /**
27
56
  * Hard-excluded directory patterns. These are NEVER user-facing pages and
@@ -83,8 +112,14 @@ Output (JSON):
83
112
  validateConfig(config);
84
113
 
85
114
  const resolvedFiles = resolveFiles(process.cwd(), config);
115
+ const svelteKit = detectSvelteKitProject(process.cwd(), config);
86
116
 
87
117
  if (args.includes('--remove')) {
118
+ if (svelteKit) {
119
+ const adapterResult = removeSvelteKitLiveAdapter({ cwd: process.cwd(), config });
120
+ console.log(JSON.stringify({ ok: true, adapter: 'sveltekit', results: [adapterResult] }));
121
+ return;
122
+ }
88
123
  const results = resolvedFiles.map((relFile) => {
89
124
  const absFile = path.resolve(process.cwd(), relFile);
90
125
  if (!fs.existsSync(absFile)) return { file: relFile, error: 'file_not_found' };
@@ -110,6 +145,13 @@ Output (JSON):
110
145
  console.error(JSON.stringify({ ok: false, error: 'missing_port' }));
111
146
  process.exit(1);
112
147
  }
148
+ const gitIgnore = ensureLiveGitIgnores(process.cwd());
149
+
150
+ if (svelteKit) {
151
+ const adapterResult = applySvelteKitLiveAdapter({ cwd: process.cwd(), port, config });
152
+ console.log(JSON.stringify({ ok: true, port, adapter: 'sveltekit', gitIgnore, results: [adapterResult] }));
153
+ return;
154
+ }
113
155
 
114
156
  const results = resolvedFiles.map((relFile) => {
115
157
  const absFile = path.resolve(process.cwd(), relFile);
@@ -129,10 +171,68 @@ Output (JSON):
129
171
  };
130
172
  });
131
173
  const anyInserted = results.some((r) => r.inserted);
132
- console.log(JSON.stringify({ ok: anyInserted, port, results }));
174
+ console.log(JSON.stringify({ ok: anyInserted, port, gitIgnore, results }));
133
175
  if (!anyInserted) process.exit(1);
134
176
  }
135
177
 
178
+ export function ensureLiveGitIgnores(cwd = process.cwd()) {
179
+ const target = resolveIgnoreTarget(cwd);
180
+ const existing = fs.existsSync(target.path) ? fs.readFileSync(target.path, 'utf-8') : '';
181
+ const block = [
182
+ IGNORE_MARKER_OPEN,
183
+ ...LIVE_IGNORE_PATTERNS,
184
+ IGNORE_MARKER_CLOSE,
185
+ ].join('\n');
186
+ const markerRe = new RegExp(`${escapeRegExp(IGNORE_MARKER_OPEN)}[\\s\\S]*?${escapeRegExp(IGNORE_MARKER_CLOSE)}`);
187
+
188
+ let updated;
189
+ if (markerRe.test(existing)) {
190
+ updated = existing.replace(markerRe, block);
191
+ } else {
192
+ const prefix = existing.length === 0 ? '' : existing.endsWith('\n') ? existing : existing + '\n';
193
+ updated = `${prefix}${prefix.endsWith('\n\n') || prefix === '' ? '' : '\n'}${block}\n`;
194
+ }
195
+
196
+ if (updated !== existing) {
197
+ fs.mkdirSync(path.dirname(target.path), { recursive: true });
198
+ fs.writeFileSync(target.path, updated, 'utf-8');
199
+ }
200
+
201
+ return {
202
+ file: path.relative(cwd, target.path).split(path.sep).join('/'),
203
+ mode: target.mode,
204
+ changed: updated !== existing,
205
+ patterns: [...LIVE_IGNORE_PATTERNS],
206
+ };
207
+ }
208
+
209
+ function resolveIgnoreTarget(cwd) {
210
+ const gitExcludePath = resolveGitInfoExcludePath(cwd);
211
+ if (gitExcludePath) {
212
+ return { path: gitExcludePath, mode: 'git-info-exclude' };
213
+ }
214
+ return { path: path.join(cwd, '.gitignore'), mode: 'gitignore' };
215
+ }
216
+
217
+ function resolveGitInfoExcludePath(cwd) {
218
+ const dotGit = path.join(cwd, '.git');
219
+ if (!fs.existsSync(dotGit)) return null;
220
+
221
+ const stat = fs.statSync(dotGit);
222
+ if (stat.isDirectory()) return path.join(dotGit, 'info', 'exclude');
223
+ if (!stat.isFile()) return null;
224
+
225
+ const body = fs.readFileSync(dotGit, 'utf-8').trim();
226
+ const match = body.match(/^gitdir:\s*(.+)$/i);
227
+ if (!match) return null;
228
+ const gitDir = path.isAbsolute(match[1]) ? match[1] : path.resolve(cwd, match[1]);
229
+ return path.join(gitDir, 'info', 'exclude');
230
+ }
231
+
232
+ function escapeRegExp(value) {
233
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
234
+ }
235
+
136
236
  /**
137
237
  * Expand config.files (which may contain glob patterns) into a literal list
138
238
  * of existing file paths relative to rootDir. Literal entries pass through;
@@ -270,8 +370,26 @@ function buildTagBlock(syntax, port, filePath) {
270
370
  );
271
371
  }
272
372
 
373
+ function detectLineEnding(content) {
374
+ if (content.includes('\r\n')) return '\r\n';
375
+ if (content.includes('\r')) return '\r';
376
+ return '\n';
377
+ }
378
+
379
+ function normalizeLineEndings(content, lineEnding) {
380
+ return lineEnding === '\n' ? content : content.replace(/\n/g, lineEnding);
381
+ }
382
+
383
+ function readLineEndingAt(content, index) {
384
+ if (content[index] === '\r' && content[index + 1] === '\n') return '\r\n';
385
+ if (content[index] === '\n') return '\n';
386
+ if (content[index] === '\r') return '\r';
387
+ return '';
388
+ }
389
+
273
390
  function insertTag(content, config, port, filePath) {
274
- const block = buildTagBlock(config.commentSyntax, port, filePath);
391
+ const lineEnding = detectLineEnding(content);
392
+ const block = normalizeLineEndings(buildTagBlock(config.commentSyntax, port, filePath), lineEnding);
275
393
  // insertBefore: match the LAST occurrence. Anchors like `</body>` naturally
276
394
  // belong at the end, and the same literal can appear earlier in code blocks
277
395
  // within rendered documentation pages.
@@ -285,9 +403,15 @@ function insertTag(content, config, port, filePath) {
285
403
  const idx = content.indexOf(config.insertAfter);
286
404
  if (idx === -1) return content;
287
405
  const after = idx + config.insertAfter.length;
288
- // Preserve a single trailing newline if the anchor didn't end with one
289
- const prefix = content[after] === '\n' ? content.slice(0, after + 1) : content.slice(0, after) + '\n';
290
- return prefix + block + content.slice(prefix.length);
406
+ // Preserve an existing trailing newline if the anchor already has one.
407
+ // Slice the remainder from the original anchor offset, not prefix.length:
408
+ // in the no-newline case prefix is one char longer than the anchor (the
409
+ // appended '\n'), so slicing by prefix.length would drop the first real
410
+ // character after the anchor (#227).
411
+ const existingNewline = readLineEndingAt(content, after);
412
+ const prefix = content.slice(0, after) + (existingNewline || lineEnding);
413
+ const rest = content.slice(after + existingNewline.length);
414
+ return prefix + block + rest;
291
415
  }
292
416
 
293
417
  /**
@@ -303,8 +427,8 @@ function insertTag(content, config, port, filePath) {
303
427
  */
304
428
  function removeTag(content, _syntax) {
305
429
  const patterns = [
306
- /([ \t]*)<!--\s*impeccable-live-start\s*-->[\s\S]*?<!--\s*impeccable-live-end\s*-->([ \t]*(?:\n|$)?)/,
307
- /([ \t]*)\{\/\*\s*impeccable-live-start\s*\*\/\}[\s\S]*?\{\/\*\s*impeccable-live-end\s*\*\/\}([ \t]*(?:\n|$)?)/,
430
+ /([ \t]*)<!--\s*impeccable-live-start\s*-->[\s\S]*?<!--\s*impeccable-live-end\s*-->([ \t]*(?:\r\n|\n|\r|$)?)/,
431
+ /([ \t]*)\{\/\*\s*impeccable-live-start\s*\*\/\}[\s\S]*?\{\/\*\s*impeccable-live-end\s*\*\/\}([ \t]*(?:\r\n|\n|\r|$)?)/,
308
432
  ];
309
433
  for (const pat of patterns) {
310
434
  let changed = false;
@@ -312,7 +436,7 @@ function removeTag(content, _syntax) {
312
436
  do {
313
437
  content = next;
314
438
  next = content.replace(pat, (_match, leadingIndent, trailing = '') => {
315
- if (trailing.includes('\n')) return leadingIndent;
439
+ if (/[\r\n]/.test(trailing)) return leadingIndent;
316
440
  return leadingIndent || trailing || '';
317
441
  });
318
442
  if (next !== content) changed = true;
@@ -9,7 +9,7 @@
9
9
 
10
10
  import fs from 'node:fs';
11
11
  import path from 'node:path';
12
- import { isGeneratedFile } from './is-generated.mjs';
12
+ import { isGeneratedFile } from './lib/is-generated.mjs';
13
13
  import {
14
14
  buildSearchQueries,
15
15
  findElement,
@@ -21,6 +21,11 @@ import {
21
21
  buildCssAuthoring,
22
22
  buildCssSelectorPrefixExamples,
23
23
  } from './live-wrap.mjs';
24
+ import {
25
+ buildSvelteComponentCssAuthoring,
26
+ scaffoldSvelteComponentInsertSession,
27
+ shouldUseSvelteComponentInjection,
28
+ } from './live/svelte-component.mjs';
24
29
 
25
30
  const INSERT_POSITIONS = new Set(['before', 'after']);
26
31
 
@@ -192,6 +197,41 @@ Output (JSON):
192
197
  const styleMode = detectStyleMode(targetFile);
193
198
  const isJsx = commentSyntax.open === '{/*';
194
199
  const spliceIndex = computeInsertLine(startLine, endLine, position);
200
+ const relTargetFile = path.relative(process.cwd(), targetFile).split(path.sep).join('/');
201
+
202
+ if (shouldUseSvelteComponentInjection(targetFile)) {
203
+ const session = scaffoldSvelteComponentInsertSession({
204
+ id,
205
+ count,
206
+ sourceFile: relTargetFile,
207
+ insertLine: spliceIndex + 1,
208
+ position,
209
+ anchorStartLine: startLine + 1,
210
+ anchorEndLine: endLine + 1,
211
+ anchorLines: lines.slice(startLine, endLine + 1),
212
+ cwd: process.cwd(),
213
+ });
214
+ console.log(JSON.stringify({
215
+ mode: 'insert',
216
+ position,
217
+ file: session.manifestFile,
218
+ sourceFile: relTargetFile,
219
+ previewMode: 'svelte-component',
220
+ componentDir: session.componentDir,
221
+ propContract: session.propContract,
222
+ insertLine: 1,
223
+ sourceInsertLine: spliceIndex + 1,
224
+ anchorStartLine: startLine + 1,
225
+ anchorEndLine: endLine + 1,
226
+ commentSyntax,
227
+ styleMode: 'svelte-component',
228
+ styleTag: null,
229
+ cssSelectorPrefixExamples: [],
230
+ cssAuthoring: buildSvelteComponentCssAuthoring(count),
231
+ }));
232
+ return;
233
+ }
234
+
195
235
  const indent = lines[spliceIndex]?.match(/^(\s*)/)?.[1]
196
236
  ?? lines[startLine]?.match(/^(\s*)/)?.[1]
197
237
  ?? '';
@@ -216,7 +256,7 @@ Output (JSON):
216
256
  console.log(JSON.stringify({
217
257
  mode: 'insert',
218
258
  position,
219
- file: path.relative(process.cwd(), targetFile),
259
+ file: relTargetFile,
220
260
  insertLine: insertLine + 1,
221
261
  commentSyntax,
222
262
  styleMode: styleMode.mode,
@@ -10,8 +10,8 @@
10
10
 
11
11
  import fs from 'node:fs';
12
12
  import path from 'node:path';
13
- import { isGeneratedFile } from './is-generated.mjs';
14
- import { readBuffer, getBufferPath } from './live-manual-edits-buffer.mjs';
13
+ import { isGeneratedFile } from './lib/is-generated.mjs';
14
+ import { readBuffer, getBufferPath } from './live/manual-edits-buffer.mjs';
15
15
 
16
16
  const EVIDENCE_VERSION = 1;
17
17
  const TEXT_EXTENSIONS = new Set(['.html', '.jsx', '.tsx', '.vue', '.svelte', '.astro', '.js', '.mjs', '.ts']);
@@ -353,9 +353,9 @@ function decodeBasicHtml(value) {
353
353
  .replace(/&quot;/g, '"')
354
354
  .replace(/&#39;/g, "'")
355
355
  .replace(/&apos;/g, "'")
356
+ .replace(/&amp;/g, '&')
356
357
  .replace(/&lt;/g, '<')
357
- .replace(/&gt;/g, '>')
358
- .replace(/&amp;/g, '&');
358
+ .replace(/&gt;/g, '>');
359
359
  }
360
360
 
361
361
  function escapeRegExp(value) {
@@ -2,31 +2,37 @@
2
2
  * CLI client for the live variant mode poll/reply protocol.
3
3
  *
4
4
  * Usage:
5
- * npx impeccable poll # Block until browser event, print JSON
6
- * npx impeccable poll --stream # Experimental: keep polling; one JSON line per event
7
- * npx impeccable poll --timeout=600000 # Custom timeout (ms); default is long-poll friendly
8
- * npx impeccable poll --reply <id> done # Reply "done" to event <id>
9
- * npx impeccable poll --reply <id> error "msg" # Reply with error
5
+ * node <scripts_path>/live-poll.mjs # Block until browser event, print JSON
6
+ * node <scripts_path>/live-poll.mjs --stream # Experimental: keep polling; one JSON line per event
7
+ * node <scripts_path>/live-poll.mjs --timeout=600000 # Custom timeout (ms); default is long-poll friendly
8
+ * node <scripts_path>/live-poll.mjs --reply <id> done # Reply "done" to event <id>
9
+ * node <scripts_path>/live-poll.mjs --reply <id> error "msg" # Reply with error
10
10
  */
11
11
 
12
12
  import { execFileSync } from 'node:child_process';
13
13
  import path from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
- import { completionAckForAcceptResult, completionTypeForAcceptResult } from './live-completion.mjs';
16
- import { readLiveServerInfo } from './impeccable-paths.mjs';
15
+ import { completionAckForAcceptResult, completionTypeForAcceptResult } from './live/completion.mjs';
16
+ import { readLiveServerInfo } from './lib/impeccable-paths.mjs';
17
+
18
+ // Absolute path to a sibling script in this skill's scripts dir, so runtime
19
+ // error hints print a directly-runnable command instead of a placeholder.
20
+ const SELF_DIR = path.dirname(fileURLToPath(import.meta.url));
21
+ const scriptCmd = (name) => `node "${path.join(SELF_DIR, name)}"`;
17
22
 
18
23
  // Node's built-in fetch (undici under the hood) enforces a 300s headers
19
24
  // timeout that can't be lowered per-request. We cap each request below
20
25
  // that ceiling and loop in `pollOnce` to synthesize a long poll without
21
26
  // depending on the standalone undici package.
22
27
  export const PER_REQUEST_TIMEOUT_MS = 270_000;
28
+ export const DEFAULT_EVENT_LEASE_MS = 600_000;
23
29
 
24
30
  const EVENT_TYPES_NEEDING_AGENT_REPLY = new Set(['generate', 'steer', 'manual_edit_apply']);
25
31
 
26
32
  function readServerInfo() {
27
33
  const record = readLiveServerInfo(process.cwd());
28
34
  if (!record) {
29
- console.error('No running live server found. Start one with: npx impeccable live');
35
+ console.error(`No running live server found. Start one with: ${scriptCmd('live.mjs')}`);
30
36
  process.exit(1);
31
37
  }
32
38
  return record.info;
@@ -81,7 +87,7 @@ export function parseReplyArgs(args) {
81
87
  }
82
88
 
83
89
  function validateReplyArgs({ id, status }) {
84
- const usage = "Usage: npx impeccable poll --reply <id> <status> [--file path] [--data '<json>'] [message]";
90
+ const usage = `Usage: ${scriptCmd('live-poll.mjs')} --reply <id> <status> [--file path] [--data '<json>'] [message]`;
85
91
  if (!id || id.startsWith('--')) {
86
92
  const err = new Error(`${usage}\nMissing event id after --reply.`);
87
93
  err.code = 'INVALID_REPLY_ARGS';
@@ -156,7 +162,7 @@ export async function fetchNextEvent(base, token, { totalDeadline } = {}) {
156
162
  ? totalDeadline - Date.now()
157
163
  : PER_REQUEST_TIMEOUT_MS;
158
164
  const slice = Math.min(Math.max(remaining, 1000), PER_REQUEST_TIMEOUT_MS);
159
- const res = await fetch(`${base}/poll?token=${token}&timeout=${slice}`);
165
+ const res = await fetch(`${base}/poll?token=${token}&timeout=${slice}&leaseMs=${DEFAULT_EVENT_LEASE_MS}`);
160
166
 
161
167
  if (res.status === 401) {
162
168
  const err = new Error('Authentication failed. The server token may have changed.');
@@ -282,11 +288,11 @@ export async function runPollStream(base, token, {
282
288
  function handlePollError(err) {
283
289
  if (err.code === 'AUTH_FAILED') {
284
290
  console.error(err.message);
285
- console.error('Try restarting: npx impeccable live stop && npx impeccable live');
291
+ console.error(`Try restarting: ${scriptCmd('live-server.mjs')} stop && ${scriptCmd('live.mjs')}`);
286
292
  process.exit(1);
287
293
  }
288
294
  if (err.cause?.code === 'ECONNREFUSED') {
289
- console.error('Live server not running. Start one with: npx impeccable live');
295
+ console.error(`Live server not running. Start one with: ${scriptCmd('live.mjs')}`);
290
296
  process.exit(1);
291
297
  }
292
298
  if (err.code === 'ACK_TIMEOUT') {
@@ -317,7 +323,7 @@ Modes:
317
323
  Options:
318
324
  --timeout=MS One-shot poll timeout in ms (default: 600000). Ignored in --stream mode
319
325
  --ack-timeout=MS Stream mode: max wait for --reply after generate/steer (default: 600000)
320
- --file PATH Attach a source file path to the reply (generate flow)
326
+ --file PATH Attach a source file path to the reply (generate/steer flow)
321
327
  --data JSON Attach a JSON result object to the reply (manual_edit_apply flow). Must be valid JSON
322
328
  --help Show this help message
323
329
 
@@ -330,7 +336,7 @@ Harness note:
330
336
  const info = readServerInfo();
331
337
  const base = `http://localhost:${info.port}`;
332
338
 
333
- // Reply mode: npx impeccable poll --reply <id> <status> [--file path] [--data '<json>'] [message]
339
+ // Reply mode: node <scripts_path>/live-poll.mjs --reply <id> <status> [--file path] [--data '<json>'] [message]
334
340
  if (args.includes('--reply')) {
335
341
  let reply;
336
342
  try {
@@ -344,7 +350,7 @@ Harness note:
344
350
  await postReply(base, info.token, reply);
345
351
  } catch (err) {
346
352
  if (err.cause?.code === 'ECONNREFUSED') {
347
- console.error('Live server not running. Start one with: npx impeccable live');
353
+ console.error(`Live server not running. Start one with: ${scriptCmd('live.mjs')}`);
348
354
  } else {
349
355
  console.error('Reply failed:', err.message);
350
356
  }
@@ -3,7 +3,7 @@
3
3
  * Recover the next agent action from the durable live-session journal.
4
4
  */
5
5
 
6
- import { createLiveSessionStore } from './live-session-store.mjs';
6
+ import { createLiveSessionStore } from './live/session-store.mjs';
7
7
 
8
8
  function manualApplyReplyCommand(eventOrId = 'EVENT_ID') {
9
9
  const id = typeof eventOrId === 'string' ? eventOrId : eventOrId?.id || 'EVENT_ID';