@bastani/atomic 0.9.0-alpha.3 → 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 (84) hide show
  1. package/CHANGELOG.md +15 -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 +17 -0
  8. package/dist/builtin/workflows/README.md +12 -12
  9. package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -0
  10. package/dist/builtin/workflows/builtin/goal-runner.ts +96 -1
  11. package/dist/builtin/workflows/builtin/goal-types.ts +2 -0
  12. package/dist/builtin/workflows/builtin/goal.d.ts +3 -0
  13. package/dist/builtin/workflows/builtin/goal.ts +12 -1
  14. package/dist/builtin/workflows/builtin/index.d.ts +8 -8
  15. package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +359 -0
  16. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +254 -352
  17. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +256 -414
  18. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +272 -0
  19. package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +58 -68
  20. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +5 -9
  21. package/dist/builtin/workflows/builtin/open-claude-design.ts +14 -26
  22. package/dist/builtin/workflows/package.json +1 -1
  23. package/dist/builtin/workflows/skills/impeccable/SKILL.md +14 -23
  24. package/dist/builtin/workflows/skills/impeccable/reference/brand.md +2 -2
  25. package/dist/builtin/workflows/skills/impeccable/reference/live.md +25 -4
  26. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +1 -1
  27. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +724 -29
  28. package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +1 -1
  29. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +219 -7
  30. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +57 -11
  31. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +750 -0
  32. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +648 -53
  33. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
  34. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
  35. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +44 -11
  36. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
  37. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
  38. package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +1 -1
  39. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
  40. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +401 -46
  41. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  42. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +6 -6
  43. package/dist/builtin/workflows/skills/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
  44. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  45. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  46. package/dist/builtin/workflows/skills/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +2 -2
  47. package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-args.mjs +42 -0
  48. package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  49. package/dist/builtin/workflows/skills/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +1 -0
  50. package/dist/builtin/workflows/skills/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
  51. package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
  52. package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  53. package/dist/builtin/workflows/skills/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
  54. package/dist/builtin/workflows/skills/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +21 -3
  55. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +835 -0
  56. package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
  57. package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +180 -0
  58. package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
  59. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +185 -60
  60. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +146 -0
  61. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +3369 -1026
  62. package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
  63. package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +2 -2
  64. package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
  65. package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +133 -9
  66. package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +42 -2
  67. package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +4 -4
  68. package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +21 -15
  69. package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +1 -1
  70. package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +205 -1269
  71. package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +2 -2
  72. package/dist/builtin/workflows/skills/impeccable/scripts/live-target.mjs +30 -0
  73. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +69 -26
  74. package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +73 -22
  75. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  76. package/dist/core/atomic-guide-command.js +5 -5
  77. package/dist/core/atomic-guide-command.js.map +1 -1
  78. package/docs/index.md +2 -2
  79. package/docs/quickstart.md +9 -9
  80. package/docs/workflows.md +42 -23
  81. package/package.json +2 -2
  82. package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
  83. package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +0 -126
  84. /package/dist/builtin/workflows/skills/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
@@ -0,0 +1,357 @@
1
+ import { validateEvent } from './event-validation.mjs';
2
+ import {
3
+ countByPage as countPendingByPage,
4
+ readBuffer as readManualEditsBuffer,
5
+ removeEntries as removeManualEditEntries,
6
+ stageEntry as stageManualEditEntry,
7
+ truncateBuffer as truncateManualEditsBuffer,
8
+ } from './manual-edits-buffer.mjs';
9
+ import {
10
+ summarizeManualApplyFailures,
11
+ summarizeManualDiagnostics,
12
+ summarizeManualLogFile,
13
+ } from './manual-apply.mjs';
14
+ import { buildManualEditEvidence } from '../live-manual-edit-evidence.mjs';
15
+ import { commitManualEdits } from '../live-commit-manual-edits.mjs';
16
+
17
+ export function createManualEditRoutes({
18
+ getToken,
19
+ manualApply,
20
+ recordManualEditActivity,
21
+ getManualEditStatus,
22
+ chatAgentLikelyActive,
23
+ cwd = () => process.cwd(),
24
+ env = () => process.env,
25
+ } = {}) {
26
+ const projectCwd = () => typeof cwd === 'function' ? cwd() : cwd || process.cwd();
27
+ const currentEnv = () => typeof env === 'function' ? env() : env || process.env;
28
+
29
+ return function handleManualEditRoute(req, res, url) {
30
+ const p = url.pathname;
31
+
32
+ // Save stages entries; Apply commits the staged page batch through the
33
+ // local AI copy-edit runner.
34
+ if (p === '/manual-edit-stash' && req.method === 'POST') {
35
+ let body = '';
36
+ req.on('data', (c) => { body += c; });
37
+ req.on('end', () => {
38
+ let msg;
39
+ try { msg = JSON.parse(body); } catch {
40
+ sendJson(res, 400, { error: 'Invalid JSON' });
41
+ return;
42
+ }
43
+ if (msg.token !== getToken()) {
44
+ sendJson(res, 401, { error: 'Unauthorized' });
45
+ return;
46
+ }
47
+ const error = validateEvent({ ...msg, type: 'manual_edits' });
48
+ if (error) {
49
+ sendJson(res, 400, { error });
50
+ return;
51
+ }
52
+ try {
53
+ stageManualEditEntry(projectCwd(), {
54
+ id: msg.id,
55
+ pageUrl: msg.pageUrl,
56
+ element: msg.element,
57
+ ops: msg.ops,
58
+ });
59
+ } catch (err) {
60
+ sendJson(res, 500, { error: 'stash_write_failed', message: err.message });
61
+ return;
62
+ }
63
+ const { totalCount, perPage } = countPendingByPage(projectCwd());
64
+ const pendingCount = perPage[msg.pageUrl] || 0;
65
+ recordManualEditActivity('manual_edit_stashed', {
66
+ id: msg.id,
67
+ pageUrl: msg.pageUrl,
68
+ opCount: msg.ops.length,
69
+ pendingCount,
70
+ totalCount,
71
+ hintedFileCount: new Set((msg.ops || []).map((op) => summarizeManualLogFile(op.sourceHint?.file, projectCwd())).filter(Boolean)).size,
72
+ });
73
+ sendJson(res, 200, { ok: true, pendingCount, totalCount, perPage });
74
+ });
75
+ return true;
76
+ }
77
+
78
+ if (p === '/manual-edit-stash' && req.method === 'GET') {
79
+ const token = url.searchParams.get('token');
80
+ if (token !== getToken()) { res.writeHead(401); res.end('Unauthorized'); return true; }
81
+ const pageUrl = url.searchParams.get('pageUrl') || '';
82
+ const { totalCount, perPage } = countPendingByPage(projectCwd());
83
+ const buffer = readManualEditsBuffer(projectCwd());
84
+ const entriesForPage = pageUrl ? buffer.entries.filter((e) => e.pageUrl === pageUrl) : buffer.entries;
85
+ sendJson(res, 200, {
86
+ count: pageUrl ? (perPage[pageUrl] || 0) : totalCount,
87
+ totalCount,
88
+ perPage,
89
+ entries: entriesForPage,
90
+ });
91
+ return true;
92
+ }
93
+
94
+ if (p === '/manual-edit-commit' && req.method === 'POST') {
95
+ const token = url.searchParams.get('token');
96
+ if (token !== getToken()) { res.writeHead(401); res.end('Unauthorized'); return true; }
97
+ const pageUrl = url.searchParams.get('pageUrl');
98
+ const asyncMode = /^(1|true|yes)$/i.test(url.searchParams.get('async') || '');
99
+ const repairOnly = /^(1|true|yes)$/i.test(url.searchParams.get('repair') || '');
100
+ const existingTransaction = manualApply.readTransaction();
101
+ if (repairOnly && !existingTransaction) {
102
+ sendJson(res, 409, { error: 'manual_edit_repair_transaction_missing' });
103
+ return true;
104
+ }
105
+ const recoveredTransaction = repairOnly ? null : manualApply.rollbackTransaction({
106
+ pageUrl,
107
+ reason: 'manual_edit_commit_recovered_abandoned_transaction',
108
+ });
109
+ const before = getManualEditStatus();
110
+ const pendingCount = pageUrl ? (before.perPage[pageUrl] || 0) : before.totalCount;
111
+ recordManualEditActivity('manual_edit_commit_started', {
112
+ pageUrl,
113
+ repairOnly,
114
+ pendingCount,
115
+ totalCount: before.totalCount,
116
+ recoveredTransaction: recoveredTransaction ? {
117
+ id: recoveredTransaction.id,
118
+ reason: recoveredTransaction.reason,
119
+ skipped: recoveredTransaction.skipped,
120
+ rolledBackFiles: recoveredTransaction.rolledBackFiles,
121
+ rollbackFailures: summarizeManualDiagnostics(recoveredTransaction.rollbackFailures, projectCwd()),
122
+ } : null,
123
+ ...summarizePendingManualEditBatch(projectCwd(), pageUrl),
124
+ });
125
+ if (asyncMode) {
126
+ sendJson(res, 202, {
127
+ status: 'started',
128
+ pendingCount,
129
+ totalCount: before.totalCount,
130
+ perPage: before.perPage,
131
+ });
132
+ }
133
+ (async () => {
134
+ let result;
135
+ let routedProvider = 'subprocess';
136
+ let transaction = null;
137
+ let commitBatch = null;
138
+ try {
139
+ if (pendingCount > 0) {
140
+ const transactionBatch = buildManualEditEvidence({ cwd: projectCwd(), pageUrl });
141
+ commitBatch = transactionBatch;
142
+ if (!repairOnly && manualApply.countOps(transactionBatch) > 0) {
143
+ transaction = manualApply.writeTransaction({
144
+ pageUrl,
145
+ batch: transactionBatch,
146
+ });
147
+ } else if (repairOnly && existingTransaction) {
148
+ transaction = existingTransaction;
149
+ }
150
+ }
151
+ const envValue = currentEnv();
152
+ const requestedMode = (envValue.IMPECCABLE_LIVE_COPY_AGENT || 'auto').trim().toLowerCase();
153
+ const useChatRoute = requestedMode === 'chat'
154
+ || (requestedMode === 'auto' && chatAgentLikelyActive());
155
+ if (useChatRoute) {
156
+ routedProvider = 'chat';
157
+ const timeoutMs = Number(envValue.IMPECCABLE_LIVE_COPY_AGENT_TIMEOUT_MS || 120000);
158
+ result = await commitManualEdits({
159
+ cwd: projectCwd(),
160
+ pageUrl,
161
+ provider: 'chat',
162
+ env: envValue,
163
+ timeoutMs,
164
+ chatAvailable: chatAgentLikelyActive,
165
+ applyBatchToSource: (batch, context) => manualApply.pushBatchInChunksAndWait(batch, pageUrl, context),
166
+ repairOnly,
167
+ transactionId: transaction?.id || existingTransaction?.id || null,
168
+ batch: commitBatch,
169
+ });
170
+ } else {
171
+ const timeoutMs = Number(envValue.IMPECCABLE_LIVE_COPY_AGENT_TIMEOUT_MS || 120000);
172
+ const provider = ['codex', 'claude', 'mock'].includes(requestedMode) ? requestedMode : undefined;
173
+ result = await commitManualEdits({
174
+ cwd: projectCwd(),
175
+ pageUrl,
176
+ provider,
177
+ env: envValue,
178
+ timeoutMs,
179
+ chatAvailable: chatAgentLikelyActive,
180
+ repairOnly,
181
+ transactionId: transaction?.id || existingTransaction?.id || null,
182
+ batch: commitBatch,
183
+ });
184
+ }
185
+ } catch (err) {
186
+ if (transaction) {
187
+ manualApply.rollbackTransaction({
188
+ pageUrl,
189
+ reason: 'manual_edit_commit_exception',
190
+ });
191
+ }
192
+ const message = err.stderr?.toString?.() || err.message;
193
+ recordManualEditActivity('manual_edit_commit_failed', {
194
+ pageUrl,
195
+ provider: routedProvider,
196
+ error: 'manual_edit_commit_failed',
197
+ message,
198
+ transactionId: transaction?.id || null,
199
+ });
200
+ if (!asyncMode) {
201
+ sendJson(res, 500, {
202
+ error: 'manual_edit_commit_failed',
203
+ message,
204
+ });
205
+ }
206
+ return;
207
+ } finally {
208
+ if (transaction) {
209
+ const shouldKeepTransaction = result?.needsManualDecision === true;
210
+ if (!shouldKeepTransaction) manualApply.clearTransaction(transaction.id);
211
+ }
212
+ }
213
+ const { totalCount, perPage } = countPendingByPage(projectCwd());
214
+ if (result?.needsManualDecision) {
215
+ recordManualEditActivity('manual_edit_repair_needs_decision', {
216
+ pageUrl,
217
+ provider: routedProvider,
218
+ transactionId: transaction?.id || existingTransaction?.id || null,
219
+ repair: result.repair || null,
220
+ failed: summarizeManualApplyFailures(result.failed, projectCwd()),
221
+ files: Array.isArray(result.files) ? result.files.slice(0, 20).map((file) => summarizeManualLogFile(file, projectCwd())).filter(Boolean) : [],
222
+ remainingCount: pageUrl ? (perPage[pageUrl] || 0) : totalCount,
223
+ totalCount,
224
+ });
225
+ } else {
226
+ recordManualEditActivity('manual_edit_commit_done', {
227
+ pageUrl,
228
+ provider: routedProvider,
229
+ reason: result.reason || null,
230
+ repair: result.repair || null,
231
+ appliedCount: Array.isArray(result.applied) ? result.applied.length : 0,
232
+ failedCount: Array.isArray(result.failed) ? result.failed.length : 0,
233
+ failed: summarizeManualApplyFailures(result.failed, projectCwd()),
234
+ files: Array.isArray(result.files) ? result.files.slice(0, 20).map((file) => summarizeManualLogFile(file, projectCwd())).filter(Boolean) : [],
235
+ warnings: summarizeManualDiagnostics(result.warnings, projectCwd()),
236
+ rolledBackFiles: Array.isArray(result.rolledBackFiles) ? result.rolledBackFiles.slice(0, 20).map((file) => summarizeManualLogFile(file, projectCwd())).filter(Boolean) : [],
237
+ rollbackFailures: summarizeManualDiagnostics(result.rollbackFailures, projectCwd()),
238
+ unreportedFiles: Array.isArray(result.unreportedFiles) ? result.unreportedFiles.slice(0, 20).map((file) => summarizeManualLogFile(file, projectCwd())).filter(Boolean) : undefined,
239
+ noteCount: Array.isArray(result.notes) ? result.notes.length : 0,
240
+ cleared: result.cleared || 0,
241
+ remainingCount: pageUrl ? (perPage[pageUrl] || 0) : totalCount,
242
+ totalCount,
243
+ });
244
+ }
245
+ if (!asyncMode) {
246
+ sendJson(res, 200, { ...result, totalCount, perPage });
247
+ }
248
+ })();
249
+ return true;
250
+ }
251
+
252
+ if (p === '/manual-edit-repair-decision' && req.method === 'POST') {
253
+ let body = '';
254
+ req.on('data', (chunk) => { body += chunk; });
255
+ req.on('end', () => {
256
+ let payload = {};
257
+ try { payload = body ? JSON.parse(body) : {}; } catch {
258
+ sendJson(res, 400, { error: 'Invalid JSON' });
259
+ return;
260
+ }
261
+ const token = payload.token || url.searchParams.get('token');
262
+ if (token !== getToken()) { res.writeHead(401); res.end('Unauthorized'); return; }
263
+ const pageUrl = payload.pageUrl || url.searchParams.get('pageUrl') || null;
264
+ const action = String(payload.action || url.searchParams.get('action') || '').trim().toLowerCase();
265
+ if (action !== 'rollback') {
266
+ sendJson(res, 400, { error: 'unsupported_manual_edit_repair_decision', action });
267
+ return;
268
+ }
269
+ const rollback = manualApply.rollbackTransaction({
270
+ pageUrl,
271
+ reason: 'manual_edit_user_requested_rollback',
272
+ });
273
+ const { totalCount, perPage } = countPendingByPage(projectCwd());
274
+ const response = {
275
+ action,
276
+ pageUrl,
277
+ rollback,
278
+ remainingCount: pageUrl ? (perPage[pageUrl] || 0) : totalCount,
279
+ totalCount,
280
+ perPage,
281
+ };
282
+ recordManualEditActivity('manual_edit_repair_rollback_done', response);
283
+ sendJson(res, 200, response);
284
+ });
285
+ return true;
286
+ }
287
+
288
+ if (p === '/manual-edit-discard' && req.method === 'POST') {
289
+ const token = url.searchParams.get('token');
290
+ if (token !== getToken()) { res.writeHead(401); res.end('Unauthorized'); return true; }
291
+ const pageUrl = url.searchParams.get('pageUrl');
292
+ let discarded;
293
+ let discardedEntries = [];
294
+ let canceledApplyEvents = [];
295
+ let transactionRollback = null;
296
+ try {
297
+ const buffer = readManualEditsBuffer(projectCwd());
298
+ transactionRollback = manualApply.rollbackTransaction({
299
+ pageUrl,
300
+ reason: 'manual_edit_discarded',
301
+ });
302
+ if (pageUrl) {
303
+ discardedEntries = buffer.entries.filter((entry) => entry.pageUrl === pageUrl);
304
+ discarded = removeManualEditEntries(projectCwd(), (entry) => entry.pageUrl === pageUrl);
305
+ } else {
306
+ discardedEntries = buffer.entries;
307
+ discarded = truncateManualEditsBuffer(projectCwd());
308
+ }
309
+ canceledApplyEvents = manualApply.cancelPendingEvents(pageUrl);
310
+ } catch (err) {
311
+ sendJson(res, 500, { error: 'discard_failed', message: err.message });
312
+ return true;
313
+ }
314
+ const { totalCount, perPage } = countPendingByPage(projectCwd());
315
+ recordManualEditActivity('manual_edit_discarded', {
316
+ pageUrl,
317
+ discarded,
318
+ canceledApplyIds: canceledApplyEvents.map((event) => event.id),
319
+ transactionRollback: transactionRollback ? {
320
+ id: transactionRollback.id,
321
+ rolledBackFiles: transactionRollback.rolledBackFiles?.map((file) => summarizeManualLogFile(file, projectCwd())).filter(Boolean) || [],
322
+ rollbackFailures: summarizeManualDiagnostics(transactionRollback.rollbackFailures, projectCwd()),
323
+ skipped: transactionRollback.skipped,
324
+ } : undefined,
325
+ totalCount,
326
+ });
327
+ sendJson(res, 200, { discarded, entries: discardedEntries, canceledApplyEvents, totalCount, perPage });
328
+ return true;
329
+ }
330
+
331
+ if (p === '/manual-edit' && req.method === 'POST') {
332
+ sendJson(res, 410, { error: '/manual-edit is removed; use /manual-edit-stash and /manual-edit-commit for staged copy edits.' });
333
+ return true;
334
+ }
335
+
336
+ return false;
337
+ };
338
+ }
339
+
340
+ function sendJson(res, status, body) {
341
+ res.writeHead(status, { 'Content-Type': 'application/json' });
342
+ res.end(JSON.stringify(body));
343
+ }
344
+
345
+ function summarizePendingManualEditBatch(cwd, pageUrl = null) {
346
+ try {
347
+ const buffer = readManualEditsBuffer(cwd);
348
+ const entries = (buffer.entries || [])
349
+ .filter((entry) => !pageUrl || entry.pageUrl === pageUrl);
350
+ return {
351
+ pendingEntryCount: entries.length,
352
+ pendingOpCount: entries.reduce((sum, entry) => sum + (entry.ops?.length || 0), 0),
353
+ };
354
+ } catch (err) {
355
+ return { pendingSummaryError: err.message || String(err) };
356
+ }
357
+ }
@@ -12,7 +12,7 @@
12
12
 
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
- import { getLiveDir } from './impeccable-paths.mjs';
15
+ import { getLiveDir } from '../lib/impeccable-paths.mjs';
16
16
 
17
17
  const BUFFER_VERSION = 1;
18
18
  const BUFFER_FILENAME = 'pending-manual-edits.json';
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { getLegacyLiveSessionsDir, getLiveSessionsDir } from './impeccable-paths.mjs';
3
+ import { getLegacyLiveSessionsDir, getLiveSessionsDir } from '../lib/impeccable-paths.mjs';
4
4
 
5
5
  const COMPLETED_PHASES = new Set(['completed', 'discarded']);
6
6
 
@@ -106,6 +106,8 @@ function baseSnapshot(id) {
106
106
  phase: 'new',
107
107
  pageUrl: null,
108
108
  sourceFile: null,
109
+ previewFile: null,
110
+ previewMode: null,
109
111
  expectedVariants: 0,
110
112
  arrivedVariants: 0,
111
113
  visibleVariant: null,
@@ -177,8 +179,10 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) {
177
179
  case 'variants_ready':
178
180
  case 'agent_done':
179
181
  next.phase = event.carbonize === true ? 'carbonize_required' : 'variants_ready';
180
- next.sourceFile = event.file ?? next.sourceFile;
181
- next.arrivedVariants = event.arrivedVariants ?? (next.arrivedVariants ?? next.expectedVariants);
182
+ next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;
183
+ next.previewFile = event.previewFile ?? next.previewFile;
184
+ next.previewMode = event.previewMode ?? next.previewMode;
185
+ next.arrivedVariants = event.arrivedVariants ?? (next.expectedVariants || next.arrivedVariants || 0);
182
186
  next.pendingEventSeq = null;
183
187
  next.pendingEvent = null;
184
188
  if (event.carbonize === true) {
@@ -190,12 +194,19 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) {
190
194
  }
191
195
  break;
192
196
  case 'checkpoint':
197
+ if (COMPLETED_PHASES.has(next.phase)) {
198
+ next.diagnostics.push({ error: 'checkpoint_after_terminal_ignored', phase: event.phase ?? null, revision: event.revision ?? null });
199
+ break;
200
+ }
193
201
  if ((event.revision ?? 0) >= (next.checkpointRevision ?? 0)) {
194
202
  next.phase = event.phase ?? next.phase;
195
203
  next.checkpointRevision = event.revision ?? next.checkpointRevision;
196
204
  next.activeOwner = event.owner ?? next.activeOwner;
197
205
  next.arrivedVariants = event.arrivedVariants ?? next.arrivedVariants;
198
206
  next.visibleVariant = event.visibleVariant ?? next.visibleVariant;
207
+ next.sourceFile = event.sourceFile ?? next.sourceFile;
208
+ next.previewFile = event.previewFile ?? next.previewFile;
209
+ next.previewMode = event.previewMode ?? next.previewMode;
199
210
  if (event.paramValues) next.paramValues = { ...event.paramValues };
200
211
  } else {
201
212
  next.diagnostics.push({ error: 'stale_checkpoint_ignored', revision: event.revision });
@@ -223,6 +234,10 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) {
223
234
  break;
224
235
  case 'steer_done':
225
236
  next.phase = 'steer_done';
237
+ next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;
238
+ next.previewFile = event.previewFile ?? next.previewFile;
239
+ next.previewMode = event.previewMode ?? next.previewMode;
240
+ next.message = event.message ?? next.message;
226
241
  next.pendingEventSeq = null;
227
242
  next.pendingEvent = null;
228
243
  break;
@@ -238,6 +253,9 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) {
238
253
  break;
239
254
  case 'complete':
240
255
  next.phase = 'completed';
256
+ next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;
257
+ next.previewFile = event.previewFile ?? next.previewFile;
258
+ next.previewMode = event.previewMode ?? next.previewMode;
241
259
  next.pendingEventSeq = null;
242
260
  next.pendingEvent = null;
243
261
  break;