quiver-cli 0.6.0 → 0.7.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli.js +125 -43
  3. package/package.json +1 -1
  4. package/template/.agents/skills/agent-browser/SKILL.md +4 -9
  5. package/template/.agents/skills/apps/skybridge/SKILL.md +1 -1
  6. package/template/.agents/skills/code/improve/SKILL.md +9 -5
  7. package/template/.agents/skills/code/improve/references/audit-playbook.md +10 -10
  8. package/template/.agents/skills/code/improve/references/closing-the-loop.md +4 -3
  9. package/template/.agents/skills/code/improve/references/plan-template.md +5 -0
  10. package/template/.agents/skills/design/impeccable/SKILL.md +14 -24
  11. package/template/.agents/skills/design/impeccable/reference/animate.md +1 -1
  12. package/template/.agents/skills/design/impeccable/reference/bolder.md +1 -1
  13. package/template/.agents/skills/design/impeccable/reference/brand.md +2 -2
  14. package/template/.agents/skills/design/impeccable/reference/colorize.md +1 -1
  15. package/template/.agents/skills/design/impeccable/reference/critique.md +6 -6
  16. package/template/.agents/skills/design/impeccable/reference/delight.md +1 -1
  17. package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
  18. package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
  19. package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
  20. package/template/.agents/skills/design/impeccable/reference/hooks.md +90 -0
  21. package/template/.agents/skills/design/impeccable/reference/init.md +5 -5
  22. package/template/.agents/skills/design/impeccable/reference/live.md +16 -16
  23. package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
  24. package/template/.agents/skills/design/impeccable/reference/polish.md +2 -2
  25. package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
  26. package/template/.agents/skills/design/impeccable/reference/shape.md +2 -2
  27. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +1 -1
  28. package/template/.agents/skills/design/impeccable/scripts/context.mjs +724 -33
  29. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +1 -1
  30. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +204 -0
  31. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +57 -11
  32. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +750 -0
  33. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +633 -46
  34. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
  35. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
  36. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +43 -10
  37. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
  38. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
  39. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +1 -1
  40. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
  41. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +401 -46
  42. package/template/.agents/skills/design/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  43. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +661 -0
  44. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +476 -0
  45. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +1632 -0
  46. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +61 -0
  47. package/template/.agents/skills/design/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
  48. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  49. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  50. package/template/.agents/skills/design/impeccable/scripts/lib/target-args.mjs +42 -0
  51. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  52. package/template/.agents/skills/design/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
  53. package/template/.agents/skills/design/impeccable/scripts/live/manual-apply.mjs +939 -0
  54. package/template/.agents/skills/design/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  55. package/template/.agents/skills/design/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
  56. package/template/.agents/skills/design/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +1 -1
  57. package/template/.agents/skills/design/impeccable/scripts/{live-ui-core.mjs → live/ui-core.mjs} +2 -1
  58. package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +36 -0
  59. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +3 -3
  60. package/template/.agents/skills/design/impeccable/scripts/live-browser-dom.js +146 -0
  61. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1456 -599
  62. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
  63. package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +2 -2
  64. package/template/.agents/skills/design/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
  65. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +35 -9
  66. package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +2 -2
  67. package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +2 -2
  68. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +18 -13
  69. package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +1 -1
  70. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +77 -1264
  71. package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +2 -2
  72. package/template/.agents/skills/design/impeccable/scripts/live-target.mjs +30 -0
  73. package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +4 -4
  74. package/template/.agents/skills/design/impeccable/scripts/live.mjs +73 -22
  75. package/template/.agents/skills/writing/humanizer/SKILL.md +621 -0
  76. package/template/.agents/upstreams.json +17 -8
  77. package/template/.agents/skills/apps/skybridge/references/architecture.md +0 -175
  78. package/template/.agents/skills/apps/skybridge/references/copy-template.md +0 -24
  79. package/template/.agents/skills/apps/skybridge/references/csp.md +0 -33
  80. package/template/.agents/skills/apps/skybridge/references/deploy.md +0 -33
  81. package/template/.agents/skills/apps/skybridge/references/discover.md +0 -84
  82. package/template/.agents/skills/apps/skybridge/references/download-file.md +0 -77
  83. package/template/.agents/skills/apps/skybridge/references/fetch-and-render-data.md +0 -151
  84. package/template/.agents/skills/apps/skybridge/references/oauth.md +0 -115
  85. package/template/.agents/skills/apps/skybridge/references/open-external-links.md +0 -71
  86. package/template/.agents/skills/apps/skybridge/references/prompt-llm.md +0 -20
  87. package/template/.agents/skills/apps/skybridge/references/publish.md +0 -19
  88. package/template/.agents/skills/apps/skybridge/references/run-locally.md +0 -51
  89. package/template/.agents/skills/apps/skybridge/references/state-and-context.md +0 -151
  90. package/template/.agents/skills/apps/skybridge/references/ui-guidelines.md +0 -205
  91. package/template/.agents/skills/design/impeccable/scripts/cleanup-deprecated.mjs +0 -284
  92. package/template/.agents/skills/design/impeccable/scripts/impeccable-paths.mjs +0 -126
  93. /package/template/.agents/skills/design/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +0 -0
  94. /package/template/.agents/skills/design/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +0 -0
  95. /package/template/.agents/skills/design/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
  96. /package/template/.agents/skills/design/impeccable/scripts/{live-svelte-component.mjs → live/svelte-component.mjs} +0 -0
  97. /package/template/.agents/skills/design/impeccable/scripts/{live-sveltekit-adapter.mjs → live/sveltekit-adapter.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
 
@@ -42,9 +42,10 @@ export const LIVE_UI_SURFACES = Object.freeze([
42
42
  'impeccable-live-highlight',
43
43
  'impeccable-live-tooltip',
44
44
  'impeccable-live-bar',
45
- 'impeccable-live-configure-input-wrap',
45
+ 'impeccable-live-selection-pill',
46
46
  'impeccable-live-input',
47
47
  'impeccable-live-configure-voice',
48
+ 'impeccable-live-configure-bar-tooltip',
48
49
  ],
49
50
  states: ['rest', 'hover', 'focus-visible', 'pressed', 'disabled'],
50
51
  },
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Canonical design-command vocabulary for Live Mode: each command's value, human
3
+ * label, and SVG icon. Icons stack above the chip label; strokes use currentColor
4
+ * so the icon recolors when its chip is selected.
5
+ *
6
+ * Single source of truth, consumed by:
7
+ * - skill/scripts/live/event-validation.mjs — re-exports VISUAL_ACTIONS.
8
+ * - skill/scripts/live-browser.js — the real picker. It is served raw and
9
+ * injected as an IIFE, so it cannot import this at runtime; live-server.mjs
10
+ * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the
11
+ * token/port, and live-browser.js builds its ICONS + ACTIONS from that.
12
+ * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported
13
+ * at build time).
14
+ *
15
+ * Add, rename, or reorder a verb here and all three follow.
16
+ */
17
+
18
+ const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"';
19
+
20
+ export const LIVE_COMMANDS = [
21
+ { value: 'impeccable', label: 'Freeform', icon: `<svg ${ICON_ATTRS}><path d="M4 20l4-1L18 9l-3-3L5 16z"/><path d="M14 7l3 3"/></svg>` },
22
+ { value: 'bolder', label: 'Bolder', icon: `<svg ${ICON_ATTRS}><rect x="6" y="12" width="4" height="7" rx="0.5"/><rect x="14" y="5" width="4" height="14" rx="0.5"/></svg>` },
23
+ { value: 'quieter', label: 'Quieter', icon: `<svg ${ICON_ATTRS}><rect x="6" y="5" width="4" height="14" rx="0.5"/><rect x="14" y="12" width="4" height="7" rx="0.5"/></svg>` },
24
+ { value: 'distill', label: 'Distill', icon: `<svg ${ICON_ATTRS}><path d="M4 5h16l-6 8v7l-4-2v-5z"/></svg>` },
25
+ { value: 'polish', label: 'Polish', icon: `<svg ${ICON_ATTRS}><path d="M15 3l1 3 3 1-3 1-1 3-1-3-3-1 3-1z"/><path d="M7 13l0.6 1.8 1.8 0.6-1.8 0.6-0.6 1.8-0.6-1.8-1.8-0.6 1.8-0.6z"/></svg>` },
26
+ { value: 'typeset', label: 'Typeset', icon: `<svg ${ICON_ATTRS}><path d="M5 6h14" stroke-width="2.6"/><path d="M5 12h9" stroke-width="1.9"/><path d="M5 18h5" stroke-width="1.3"/></svg>` },
27
+ { value: 'colorize', label: 'Colorize', icon: `<svg ${ICON_ATTRS}><circle cx="9" cy="10" r="5"/><circle cx="15" cy="10" r="5"/><circle cx="12" cy="15" r="5"/></svg>` },
28
+ { value: 'layout', label: 'Layout', icon: `<svg ${ICON_ATTRS}><rect x="3" y="4" width="8" height="16" rx="0.5"/><rect x="13" y="4" width="8" height="7" rx="0.5"/><rect x="13" y="13" width="8" height="7" rx="0.5"/></svg>` },
29
+ { value: 'adapt', label: 'Adapt', icon: `<svg ${ICON_ATTRS}><rect x="2.5" y="5" width="12" height="11" rx="1"/><line x1="2.5" y1="19" x2="14.5" y2="19"/><rect x="16.5" y="8" width="5" height="11" rx="1"/></svg>` },
30
+ { value: 'animate', label: 'Animate', icon: `<svg ${ICON_ATTRS}><path d="M3 18c4-4 6-10 10-10"/><path d="M13 8c3 0 5 5 8 10"/><circle cx="13" cy="8" r="1.6" fill="currentColor" stroke="none"/></svg>` },
31
+ { value: 'delight', label: 'Delight', icon: `<svg ${ICON_ATTRS}><path d="M12 3l2 6 6 2-6 2-2 6-2-6-6-2 6-2z"/></svg>` },
32
+ { value: 'overdrive', label: 'Overdrive', icon: `<svg ${ICON_ATTRS}><path d="M13 3L5 13h5l-1 8 9-12h-6z"/></svg>` },
33
+ ];
34
+
35
+ // Action values accepted by the live event protocol, in palette order.
36
+ export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value);
@@ -15,14 +15,14 @@
15
15
 
16
16
  import fs from 'node:fs';
17
17
  import path from 'node:path';
18
- import { isGeneratedFile } from './is-generated.mjs';
19
- import { readBuffer as readManualEditsBuffer, writeBuffer as writeManualEditsBuffer } from './live-manual-edits-buffer.mjs';
18
+ import { isGeneratedFile } from './lib/is-generated.mjs';
19
+ import { readBuffer as readManualEditsBuffer, writeBuffer as writeManualEditsBuffer } from './live/manual-edits-buffer.mjs';
20
20
  import {
21
21
  applyDeferredSvelteComponentAccepts,
22
22
  findSvelteComponentManifest,
23
23
  inlineSvelteComponentAccept,
24
24
  removeSvelteComponentSession,
25
- } from './live-svelte-component.mjs';
25
+ } from './live/svelte-component.mjs';
26
26
 
27
27
  const EXTENSIONS = ['.html', '.jsx', '.tsx', '.vue', '.svelte', '.astro'];
28
28
 
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Browser-side DOM helpers for Impeccable live mode.
3
+ *
4
+ * Kept separate from live-browser.js so future browser script parts can share
5
+ * chrome mounting, lookup, focus, and picker helpers without depending on the
6
+ * full overlay UI bundle.
7
+ */
8
+ (function (root) {
9
+ 'use strict';
10
+ if (!root) return;
11
+
12
+ function createLiveBrowserDomHelpers({
13
+ prefix,
14
+ skipTags,
15
+ document: doc = root.document,
16
+ css = root.CSS,
17
+ crypto = root.crypto,
18
+ } = {}) {
19
+ if (!prefix) throw new Error('prefix required');
20
+ if (!doc) throw new Error('document required');
21
+ const tagsToSkip = skipTags || new Set();
22
+
23
+ function own(el) {
24
+ return el && (el.id?.startsWith(prefix) || el.closest?.('[id^="' + prefix + '"]'));
25
+ }
26
+
27
+ function pickable(el) {
28
+ if (!el || el.nodeType !== 1) return false;
29
+ if (tagsToSkip.has(String(el.tagName || '').toLowerCase())) return false;
30
+ if (own(el)) return false;
31
+ const r = el.getBoundingClientRect();
32
+ return r.width >= 20 && r.height >= 20;
33
+ }
34
+
35
+ function desc(el) {
36
+ if (!el) return '';
37
+ let s = el.tagName.toLowerCase();
38
+ if (el.id) s += '#' + el.id;
39
+ else if (el.classList.length) s += '.' + [...el.classList].slice(0, 2).join('.');
40
+ return s;
41
+ }
42
+
43
+ function rectIsUsableAnchor(rect) {
44
+ return !!rect && rect.width > 0.5 && rect.height > 0.5;
45
+ }
46
+
47
+ function makeFrozenAnchor(el) {
48
+ if (!el || !el.getBoundingClientRect) return null;
49
+ const r = el.getBoundingClientRect();
50
+ if (!rectIsUsableAnchor(r)) return null;
51
+ const rect = {
52
+ x: r.x, y: r.y,
53
+ top: r.top, left: r.left,
54
+ right: r.right, bottom: r.bottom,
55
+ width: r.width, height: r.height,
56
+ };
57
+ return {
58
+ __impeccableFrozenAnchor: true,
59
+ tagName: el.tagName || 'DIV',
60
+ id: el.id || '',
61
+ classList: el.classList ? [...el.classList] : [],
62
+ hasAttribute: () => false,
63
+ getBoundingClientRect: () => rect,
64
+ };
65
+ }
66
+
67
+ function id8() {
68
+ if (crypto?.randomUUID) return crypto.randomUUID().replace(/-/g, '').slice(0, 8);
69
+ return (Math.random().toString(16).slice(2) + Date.now().toString(16)).slice(0, 8);
70
+ }
71
+
72
+ function cssId(id) {
73
+ if (css?.escape) return css.escape(id);
74
+ return String(id).replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
75
+ }
76
+
77
+ function liveUiRoot() {
78
+ const uiRoot = root.__IMPECCABLE_LIVE_UI_ROOT__;
79
+ if (uiRoot && typeof uiRoot.appendChild === 'function') return uiRoot;
80
+ return doc.body;
81
+ }
82
+
83
+ function uiAppend(el) {
84
+ liveUiRoot().appendChild(el);
85
+ return el;
86
+ }
87
+
88
+ function uiAppendStyle(styleEl) {
89
+ const uiRoot = liveUiRoot();
90
+ if (uiRoot && uiRoot !== doc.body) uiRoot.appendChild(styleEl);
91
+ else doc.head.appendChild(styleEl);
92
+ return styleEl;
93
+ }
94
+
95
+ function uiGetById(id) {
96
+ const uiRoot = liveUiRoot();
97
+ if (uiRoot?.getElementById) {
98
+ const found = uiRoot.getElementById(id);
99
+ if (found) return found;
100
+ }
101
+ if (uiRoot?.querySelector) {
102
+ const found = uiRoot.querySelector('#' + cssId(id));
103
+ if (found) return found;
104
+ }
105
+ return doc.getElementById(id);
106
+ }
107
+
108
+ function activeElementDeep() {
109
+ let active = doc.activeElement;
110
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
111
+ return active;
112
+ }
113
+
114
+ function defangOutsideHandlers(rootEl, { setPointerEvents = true } = {}) {
115
+ if (!rootEl) return;
116
+ if (setPointerEvents) {
117
+ rootEl.style.setProperty('pointer-events', 'auto', 'important');
118
+ }
119
+ const stop = (e) => e.stopPropagation();
120
+ rootEl.addEventListener('pointerdown', stop);
121
+ rootEl.addEventListener('mousedown', stop);
122
+ rootEl.addEventListener('focusin', stop);
123
+ }
124
+
125
+ return {
126
+ own,
127
+ pickable,
128
+ desc,
129
+ rectIsUsableAnchor,
130
+ makeFrozenAnchor,
131
+ id8,
132
+ cssId,
133
+ liveUiRoot,
134
+ uiAppend,
135
+ uiAppendStyle,
136
+ uiGetById,
137
+ activeElementDeep,
138
+ defangOutsideHandlers,
139
+ };
140
+ }
141
+
142
+ root.__IMPECCABLE_LIVE_DOM__ = {
143
+ version: 1,
144
+ createLiveBrowserDomHelpers,
145
+ };
146
+ })(typeof window !== 'undefined' ? window : globalThis);