@bastani/atomic 0.8.20 → 0.8.21-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.
- package/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/agents/code-simplifier.md +78 -22
- package/dist/builtin/subagents/agents/debugger.md +4 -3
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +19 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +169 -125
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +89 -80
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_asset_producer.toml +92 -0
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_manual_edit_applier.toml +95 -0
- package/dist/builtin/workflows/skills/impeccable/agents/openai.yaml +4 -0
- package/dist/builtin/workflows/skills/impeccable/reference/adapt.md +122 -1
- package/dist/builtin/workflows/skills/impeccable/reference/animate.md +38 -12
- package/dist/builtin/workflows/skills/impeccable/reference/audit.md +5 -5
- package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +7 -7
- package/dist/builtin/workflows/skills/impeccable/reference/brand.md +4 -14
- package/dist/builtin/workflows/skills/impeccable/reference/clarify.md +115 -1
- package/dist/builtin/workflows/skills/impeccable/reference/codex.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/colorize.md +109 -6
- package/dist/builtin/workflows/skills/impeccable/reference/craft.md +7 -7
- package/dist/builtin/workflows/skills/impeccable/reference/critique.md +623 -94
- package/dist/builtin/workflows/skills/impeccable/reference/delight.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/distill.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/document.md +16 -14
- package/dist/builtin/workflows/skills/impeccable/reference/extract.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/harden.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/init.md +172 -0
- package/dist/builtin/workflows/skills/impeccable/reference/interaction-design.md +0 -6
- package/dist/builtin/workflows/skills/impeccable/reference/layout.md +33 -13
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +96 -19
- package/dist/builtin/workflows/skills/impeccable/reference/onboard.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/optimize.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/overdrive.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/polish.md +3 -4
- package/dist/builtin/workflows/skills/impeccable/reference/product.md +1 -3
- package/dist/builtin/workflows/skills/impeccable/reference/quieter.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/shape.md +5 -5
- package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +158 -3
- package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/command-metadata.json +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +225 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +266 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +17 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/design-parser.mjs +16 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detect.mjs +21 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +1725 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +244 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +4543 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +43 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +252 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +535 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +986 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +208 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/findings.mjs +12 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +198 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +419 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +2316 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/constants.mjs +101 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +17 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/is-generated.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +139 -96
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +4491 -526
- package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-event-validation.mjs +136 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +22 -9
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert-ui.mjs +458 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +232 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edits-buffer.mjs +152 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +288 -110
- package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +47 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +1443 -100
- package/dist/builtin/workflows/skills/impeccable/scripts/live-session-store.mjs +17 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +17 -3
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +216 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +2 -3
- package/dist/builtin/workflows/skills/impeccable/scripts/palette.mjs +633 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/pin.mjs +1 -1
- package/dist/builtin/workflows/src/extension/index.ts +67 -3
- package/dist/builtin/workflows/src/extension/render-result.ts +26 -1
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +227 -3
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +94 -7
- package/dist/builtin/workflows/src/shared/stage-prompt.ts +326 -0
- package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +62 -7
- package/dist/builtin/workflows/src/shared/store-types.ts +43 -0
- package/dist/builtin/workflows/src/shared/store.ts +37 -0
- package/dist/builtin/workflows/src/tui/chat-surface-message.ts +22 -4
- package/dist/builtin/workflows/src/tui/graph-view.ts +47 -0
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +43 -1
- package/dist/builtin/workflows/src/tui/run-detail.ts +10 -4
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +117 -15
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +9 -0
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +2 -5
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +11 -29
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/docs/quickstart.md +1 -2
- package/package.json +4 -4
- package/dist/builtin/workflows/skills/impeccable/reference/cognitive-load.md +0 -106
- package/dist/builtin/workflows/skills/impeccable/reference/color-and-contrast.md +0 -105
- package/dist/builtin/workflows/skills/impeccable/reference/heuristics-scoring.md +0 -234
- package/dist/builtin/workflows/skills/impeccable/reference/motion-design.md +0 -109
- package/dist/builtin/workflows/skills/impeccable/reference/personas.md +0 -179
- package/dist/builtin/workflows/skills/impeccable/reference/responsive-design.md +0 -114
- package/dist/builtin/workflows/skills/impeccable/reference/spatial-design.md +0 -100
- package/dist/builtin/workflows/skills/impeccable/reference/teach.md +0 -156
- package/dist/builtin/workflows/skills/impeccable/reference/typography.md +0 -159
- package/dist/builtin/workflows/skills/impeccable/reference/ux-writing.md +0 -107
- package/dist/builtin/workflows/skills/impeccable/scripts/load-context.mjs +0 -141
|
@@ -209,6 +209,23 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) {
|
|
|
209
209
|
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
210
210
|
next.pendingEvent = toPendingEvent(event);
|
|
211
211
|
break;
|
|
212
|
+
case 'manual_edit_apply':
|
|
213
|
+
next.phase = 'manual_edit_apply_requested';
|
|
214
|
+
next.pageUrl = event.pageUrl ?? next.pageUrl;
|
|
215
|
+
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
216
|
+
next.pendingEvent = toPendingEvent(event);
|
|
217
|
+
break;
|
|
218
|
+
case 'steer':
|
|
219
|
+
next.phase = 'steer_requested';
|
|
220
|
+
next.pageUrl = event.pageUrl ?? next.pageUrl;
|
|
221
|
+
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
222
|
+
next.pendingEvent = toPendingEvent(event);
|
|
223
|
+
break;
|
|
224
|
+
case 'steer_done':
|
|
225
|
+
next.phase = 'steer_done';
|
|
226
|
+
next.pendingEventSeq = null;
|
|
227
|
+
next.pendingEvent = null;
|
|
228
|
+
break;
|
|
212
229
|
case 'discard':
|
|
213
230
|
next.phase = 'discard_requested';
|
|
214
231
|
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { createLiveSessionStore } from './live-session-store.mjs';
|
|
7
7
|
import { readLiveServerInfo } from './impeccable-paths.mjs';
|
|
8
|
+
import { manualApplyResumeHint } from './live-resume.mjs';
|
|
8
9
|
|
|
9
10
|
function readServerInfo() {
|
|
10
11
|
return readLiveServerInfo(process.cwd())?.info || null;
|
|
@@ -26,21 +27,34 @@ export async function statusCli() {
|
|
|
26
27
|
const server = await fetchServerStatus(info);
|
|
27
28
|
const store = createLiveSessionStore({ cwd: process.cwd() });
|
|
28
29
|
const activeSessions = store.listActiveSessions();
|
|
30
|
+
const manualApply = findPendingManualApply(server, activeSessions);
|
|
29
31
|
const payload = {
|
|
30
32
|
liveServer: server ? {
|
|
31
33
|
status: server.status,
|
|
32
34
|
port: server.port,
|
|
33
35
|
connectedClients: server.connectedClients,
|
|
36
|
+
agentPolling: server.agentPolling,
|
|
34
37
|
pendingEvents: server.pendingEvents,
|
|
35
38
|
} : null,
|
|
36
39
|
activeSessions: server?.activeSessions || activeSessions,
|
|
37
|
-
recoveryHint:
|
|
38
|
-
?
|
|
39
|
-
:
|
|
40
|
+
recoveryHint: manualApply
|
|
41
|
+
? manualApplyResumeHint(manualApply)
|
|
42
|
+
: server
|
|
43
|
+
? 'Run live-poll.mjs to continue pending work, or live-complete.mjs --id <session> after manual cleanup.'
|
|
44
|
+
: 'Start live-server.mjs to requeue pending durable events, then run live-poll.mjs.',
|
|
40
45
|
};
|
|
41
46
|
console.log(JSON.stringify(payload, null, 2));
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
function findPendingManualApply(server, activeSessions) {
|
|
50
|
+
const fromServer = server?.pendingEvents?.find((event) => event?.type === 'manual_edit_apply');
|
|
51
|
+
if (fromServer) return fromServer;
|
|
52
|
+
const fromSession = activeSessions
|
|
53
|
+
?.map((session) => session.pendingEvent)
|
|
54
|
+
.find((event) => event?.type === 'manual_edit_apply');
|
|
55
|
+
return fromSession || null;
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
const _running = process.argv[1];
|
|
45
59
|
if (_running?.endsWith('live-status.mjs') || _running?.endsWith('live-status.mjs/')) {
|
|
46
60
|
statusCli();
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { isGeneratedFile } from './is-generated.mjs';
|
|
17
|
+
import { readBuffer as readManualEditsBuffer } from './live-manual-edits-buffer.mjs';
|
|
17
18
|
|
|
18
19
|
const EXTENSIONS = ['.html', '.jsx', '.tsx', '.vue', '.svelte', '.astro'];
|
|
19
20
|
|
|
@@ -31,7 +32,7 @@ Required:
|
|
|
31
32
|
|
|
32
33
|
Element identification (at least one required):
|
|
33
34
|
--element-id ID HTML id attribute of the element
|
|
34
|
-
--classes A,B,C Comma-separated CSS class names
|
|
35
|
+
--classes A,B,C Comma- or space-separated CSS class names
|
|
35
36
|
--tag TAG Tag name (div, section, etc.)
|
|
36
37
|
--query TEXT Fallback: raw text to search for
|
|
37
38
|
|
|
@@ -41,6 +42,9 @@ Optional:
|
|
|
41
42
|
classes/tag match multiple sibling elements (e.g. a list
|
|
42
43
|
of <Card>s with the same className). Pass the first ~80
|
|
43
44
|
chars of event.element.textContent.
|
|
45
|
+
--page-url URL Current page URL. Required when pending manual edits may
|
|
46
|
+
affect the picked source block. Pending edits are filtered
|
|
47
|
+
to this page so an edit on /a doesn't bleed into /b.
|
|
44
48
|
--help Show this help message
|
|
45
49
|
|
|
46
50
|
Output (JSON):
|
|
@@ -58,6 +62,7 @@ The agent should insert variant HTML at insertLine.`);
|
|
|
58
62
|
const query = argVal(args, '--query');
|
|
59
63
|
const filePath = argVal(args, '--file');
|
|
60
64
|
const text = argVal(args, '--text');
|
|
65
|
+
const pageUrl = argVal(args, '--page-url');
|
|
61
66
|
|
|
62
67
|
if (!id) { console.error('Missing --id'); process.exit(1); }
|
|
63
68
|
if (!elementId && !classes && !query) {
|
|
@@ -196,7 +201,62 @@ The agent should insert variant HTML at insertLine.`);
|
|
|
196
201
|
// the inner element at its parent's depth instead of nested inside it.
|
|
197
202
|
// Strip only the COMMON minimum leading whitespace across the picked lines;
|
|
198
203
|
// `deindentContent` on the accept side already mirrors this convention.
|
|
199
|
-
|
|
204
|
+
let originalLines = lines.slice(startLine, endLine + 1);
|
|
205
|
+
|
|
206
|
+
// Buffer-aware "original" content: if the user has pending manual edits for
|
|
207
|
+
// this page whose originalText appears in the picked source range, apply
|
|
208
|
+
// them so the wrap block's "original" variant reflects what the user was
|
|
209
|
+
// looking at (their edited DOM), not the raw source. Source itself stays
|
|
210
|
+
// untouched here — only the wrap block's embedded "original" copy is
|
|
211
|
+
// adjusted. The pending edits remain in the buffer until committed.
|
|
212
|
+
//
|
|
213
|
+
// Apply buffered edits only when the browser provided the current page URL.
|
|
214
|
+
// Without it, fail if pending edits plausibly touch this exact source range;
|
|
215
|
+
// otherwise skip buffer awareness so unrelated staged edits on another page
|
|
216
|
+
// do not block normal wrap work.
|
|
217
|
+
let pendingBuffer = { entries: [] };
|
|
218
|
+
try { pendingBuffer = readManualEditsBuffer(process.cwd()); } catch {}
|
|
219
|
+
const pendingEntriesForTarget = pageUrl
|
|
220
|
+
? []
|
|
221
|
+
: pendingEntriesThatMayAffectWrap(pendingBuffer.entries, targetFile, originalLines, startLine, process.cwd());
|
|
222
|
+
if (pendingEntriesForTarget.length > 0) {
|
|
223
|
+
console.error(JSON.stringify({
|
|
224
|
+
error: 'missing_page_url_with_pending_edits',
|
|
225
|
+
pendingEntries: pendingEntriesForTarget.length,
|
|
226
|
+
hint: 'Pending manual edits may affect the selected source block. Pass --page-url=$event.pageUrl so the wrap block reflects the user\'s staged DOM.',
|
|
227
|
+
}));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
if (pageUrl) {
|
|
231
|
+
const failedBufferedOps = [];
|
|
232
|
+
for (const entry of pendingBuffer.entries || []) {
|
|
233
|
+
if (entry.pageUrl !== pageUrl) continue;
|
|
234
|
+
for (const op of entry.ops || []) {
|
|
235
|
+
const mayAffectWrap = manualEditMayAffectWrap(op, targetFile, originalLines, startLine, process.cwd());
|
|
236
|
+
const result = applyBufferedManualEditToLines(originalLines, startLine, op);
|
|
237
|
+
if (result.changed) {
|
|
238
|
+
originalLines = result.lines;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (!mayAffectWrap) continue;
|
|
242
|
+
failedBufferedOps.push({
|
|
243
|
+
entryId: entry.id,
|
|
244
|
+
ref: op?.ref || null,
|
|
245
|
+
originalText: op?.originalText || null,
|
|
246
|
+
reason: 'ambiguous_or_unmatched_pending_edit',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (failedBufferedOps.length > 0) {
|
|
251
|
+
console.error(JSON.stringify({
|
|
252
|
+
error: 'manual_edit_buffer_apply_failed',
|
|
253
|
+
pendingOps: failedBufferedOps,
|
|
254
|
+
hint: 'A staged copy edit appears to affect the selected source block, but could not be applied unambiguously to the wrap original. Apply or discard copy edits first, or write the wrapper manually.',
|
|
255
|
+
}));
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
200
260
|
const originalBaseIndent = minLeadingSpaces(originalLines);
|
|
201
261
|
const reindentOriginal = (extra) => originalLines
|
|
202
262
|
.map((l) => (l.trim() === '' ? '' : indent + extra + l.slice(originalBaseIndent)))
|
|
@@ -283,10 +343,140 @@ The agent should insert variant HTML at insertLine.`);
|
|
|
283
343
|
// ---------------------------------------------------------------------------
|
|
284
344
|
|
|
285
345
|
function argVal(args, flag) {
|
|
346
|
+
const prefix = flag + '=';
|
|
347
|
+
for (const arg of args) {
|
|
348
|
+
if (arg.startsWith(prefix)) return arg.slice(prefix.length);
|
|
349
|
+
}
|
|
286
350
|
const idx = args.indexOf(flag);
|
|
287
351
|
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
|
|
288
352
|
}
|
|
289
353
|
|
|
354
|
+
function pendingEntriesThatMayAffectWrap(entries, targetFile, originalLines, selectionStartLine, cwd) {
|
|
355
|
+
const targetAbs = path.resolve(cwd, targetFile);
|
|
356
|
+
return (entries || []).filter((entry) => {
|
|
357
|
+
return (entry.ops || []).some((op) => {
|
|
358
|
+
return manualEditMayAffectWrap(op, targetAbs, originalLines, selectionStartLine, cwd);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function manualEditMayAffectWrap(op, targetFile, originalLines, selectionStartLine, cwd) {
|
|
364
|
+
const targetAbs = path.resolve(cwd, targetFile);
|
|
365
|
+
if (manualEditHintFallsInsideSelection(op, targetAbs, originalLines, selectionStartLine, cwd)) return true;
|
|
366
|
+
if (manualEditLocatorMatchesSelection(op, originalLines)) return true;
|
|
367
|
+
if (typeof op?.originalText === 'string' && op.originalText.length > 0) {
|
|
368
|
+
return originalLines.join('\n').includes(op.originalText);
|
|
369
|
+
}
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function manualEditHintFallsInsideSelection(op, targetAbs, originalLines, selectionStartLine, cwd) {
|
|
374
|
+
const hintFile = op?.sourceHint?.file;
|
|
375
|
+
const hintedLine = Number(op?.sourceHint?.line);
|
|
376
|
+
if (!hintFile || !Number.isFinite(hintedLine)) return false;
|
|
377
|
+
const hintAbs = path.isAbsolute(hintFile) ? hintFile : path.resolve(cwd, hintFile);
|
|
378
|
+
if (path.resolve(hintAbs) !== targetAbs) return false;
|
|
379
|
+
const hintedIndex = hintedLine - 1 - selectionStartLine;
|
|
380
|
+
return hintedIndex >= 0
|
|
381
|
+
&& hintedIndex < originalLines.length
|
|
382
|
+
&& typeof op?.originalText === 'string'
|
|
383
|
+
&& originalLines[hintedIndex].includes(op.originalText);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function manualEditLocatorMatchesSelection(op, originalLines) {
|
|
387
|
+
if (!op || typeof op.originalText !== 'string' || op.originalText.length === 0) return false;
|
|
388
|
+
return originalLines.some((line) => (
|
|
389
|
+
line.includes(op.originalText) && lineMatchesManualEditLocator(line, op)
|
|
390
|
+
));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function applyBufferedManualEditToLines(originalLines, selectionStartLine, op) {
|
|
394
|
+
if (
|
|
395
|
+
!op
|
|
396
|
+
|| typeof op.originalText !== 'string'
|
|
397
|
+
|| op.originalText.length === 0
|
|
398
|
+
|| typeof op.newText !== 'string'
|
|
399
|
+
) {
|
|
400
|
+
return { lines: originalLines, changed: false };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const replaceLine = (lineIndex) => ({
|
|
404
|
+
lines: originalLines.map((line, index) => (
|
|
405
|
+
index === lineIndex ? replaceOnce(line, op.originalText, op.newText) : line
|
|
406
|
+
)),
|
|
407
|
+
changed: true,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const hintedLine = Number(op.sourceHint?.line);
|
|
411
|
+
if (Number.isFinite(hintedLine)) {
|
|
412
|
+
const hintedIndex = hintedLine - 1 - selectionStartLine;
|
|
413
|
+
if (hintedIndex >= 0 && hintedIndex < originalLines.length && originalLines[hintedIndex].includes(op.originalText)) {
|
|
414
|
+
return replaceLine(hintedIndex);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const locatorMatches = [];
|
|
419
|
+
for (let index = 0; index < originalLines.length; index += 1) {
|
|
420
|
+
const line = originalLines[index];
|
|
421
|
+
if (!line.includes(op.originalText)) continue;
|
|
422
|
+
if (!lineMatchesManualEditLocator(line, op)) continue;
|
|
423
|
+
locatorMatches.push(index);
|
|
424
|
+
}
|
|
425
|
+
if (locatorMatches.length === 1) return replaceLine(locatorMatches[0]);
|
|
426
|
+
|
|
427
|
+
const originalBlock = originalLines.join('\n');
|
|
428
|
+
if (countOccurrences(originalBlock, op.originalText) === 1) {
|
|
429
|
+
return {
|
|
430
|
+
lines: replaceOnce(originalBlock, op.originalText, op.newText).split('\n'),
|
|
431
|
+
changed: true,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return { lines: originalLines, changed: false };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function lineMatchesManualEditLocator(line, op) {
|
|
439
|
+
if (op.tag) {
|
|
440
|
+
const tagRe = new RegExp('<\\s*' + escapeRegExp(op.tag) + '(?=[\\s>/]|$)', 'i');
|
|
441
|
+
if (!tagRe.test(line)) return false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (op.elementId) {
|
|
445
|
+
const id = escapeRegExp(op.elementId);
|
|
446
|
+
const idRe = new RegExp('\\bid\\s*=\\s*["\']' + id + '["\']');
|
|
447
|
+
if (!idRe.test(line)) return false;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const classes = Array.isArray(op.classes) ? op.classes.filter(Boolean) : [];
|
|
451
|
+
for (const className of classes) {
|
|
452
|
+
if (!line.includes(className)) return false;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function replaceOnce(value, needle, replacement) {
|
|
459
|
+
const index = value.indexOf(needle);
|
|
460
|
+
if (index === -1) return value;
|
|
461
|
+
return value.slice(0, index) + replacement + value.slice(index + needle.length);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function countOccurrences(value, needle) {
|
|
465
|
+
if (!needle) return 0;
|
|
466
|
+
let count = 0;
|
|
467
|
+
let index = 0;
|
|
468
|
+
while (true) {
|
|
469
|
+
index = value.indexOf(needle, index);
|
|
470
|
+
if (index === -1) return count;
|
|
471
|
+
count += 1;
|
|
472
|
+
index += needle.length;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function escapeRegExp(value) {
|
|
477
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
478
|
+
}
|
|
479
|
+
|
|
290
480
|
/**
|
|
291
481
|
* Build search query strings in priority order (most specific first).
|
|
292
482
|
* ID is most reliable, then specific class combos, then single classes, then raw query.
|
|
@@ -303,13 +493,15 @@ function buildSearchQueries(elementId, classes, tag, query) {
|
|
|
303
493
|
// Emit both class="..." (HTML) and className="..." (React/JSX) so whichever
|
|
304
494
|
// convention the file uses will match.
|
|
305
495
|
if (classes) {
|
|
306
|
-
const classList = classes
|
|
496
|
+
const classList = splitClassList(classes);
|
|
307
497
|
if (classList.length > 1) {
|
|
308
498
|
const joined = classList.join(' ');
|
|
309
499
|
const sorted = [...classList].sort((a, b) => b.length - a.length);
|
|
310
500
|
queries.push('class="' + joined + '"');
|
|
311
501
|
queries.push('className="' + joined + '"');
|
|
312
|
-
|
|
502
|
+
for (const className of sorted) {
|
|
503
|
+
queries.push(className);
|
|
504
|
+
}
|
|
313
505
|
} else if (classList.length === 1) {
|
|
314
506
|
queries.push(classList[0]);
|
|
315
507
|
}
|
|
@@ -318,7 +510,7 @@ function buildSearchQueries(elementId, classes, tag, query) {
|
|
|
318
510
|
// 3. Tag + class combo (e.g., <section class="hero">).
|
|
319
511
|
// Same dual-emit for JSX compatibility.
|
|
320
512
|
if (tag && classes) {
|
|
321
|
-
const firstClass = classes
|
|
513
|
+
const firstClass = splitClassList(classes)[0];
|
|
322
514
|
queries.push('<' + tag + ' class="' + firstClass);
|
|
323
515
|
queries.push('<' + tag + ' className="' + firstClass);
|
|
324
516
|
}
|
|
@@ -331,6 +523,10 @@ function buildSearchQueries(elementId, classes, tag, query) {
|
|
|
331
523
|
return queries;
|
|
332
524
|
}
|
|
333
525
|
|
|
526
|
+
function splitClassList(classes) {
|
|
527
|
+
return String(classes).split(/[,\s]+/).map(c => c.trim()).filter(Boolean);
|
|
528
|
+
}
|
|
529
|
+
|
|
334
530
|
function detectCommentSyntax(filePath) {
|
|
335
531
|
const ext = path.extname(filePath).toLowerCase();
|
|
336
532
|
if (ext === '.jsx' || ext === '.tsx') {
|
|
@@ -370,11 +566,14 @@ function buildCssAuthoring(styleMode, count) {
|
|
|
370
566
|
selectorExamples: variantNumbers.map((n) => `[data-impeccable-variant="${n}"] > .variant-class`),
|
|
371
567
|
requirements: [
|
|
372
568
|
'Use the styleTag exactly; the is:inline attribute is required for this file.',
|
|
569
|
+
'Put raw CSS directly between the styleTag opening and a plain </style> close.',
|
|
373
570
|
'Prefix every preview selector with the matching [data-impeccable-variant="N"] selector.',
|
|
374
571
|
'Keep selectors anchored to the generated variant wrapper; do not rely on component CSS scoping for preview rules.',
|
|
375
572
|
],
|
|
376
573
|
forbidden: [
|
|
377
574
|
'Do not use @scope for this styleMode.',
|
|
575
|
+
'Do not wrap style content in a JSX/TSX template literal ({` ... `}); that syntax is for .tsx/.jsx only.',
|
|
576
|
+
'Do not put { immediately after the style opening tag; Astro parses { as expression syntax.',
|
|
378
577
|
],
|
|
379
578
|
};
|
|
380
579
|
}
|
|
@@ -629,4 +828,15 @@ if (_running?.endsWith('live-wrap.mjs') || _running?.endsWith('live-wrap.mjs/'))
|
|
|
629
828
|
}
|
|
630
829
|
|
|
631
830
|
// Test exports (used by tests/live-wrap.test.mjs)
|
|
632
|
-
export {
|
|
831
|
+
export {
|
|
832
|
+
buildSearchQueries,
|
|
833
|
+
findElement,
|
|
834
|
+
findClosingLine,
|
|
835
|
+
detectCommentSyntax,
|
|
836
|
+
findAllElements,
|
|
837
|
+
filterByText,
|
|
838
|
+
findFileWithQuery,
|
|
839
|
+
detectStyleMode,
|
|
840
|
+
buildCssAuthoring,
|
|
841
|
+
buildCssSelectorPrefixExamples,
|
|
842
|
+
};
|
|
@@ -21,7 +21,7 @@ import { execSync } from 'node:child_process';
|
|
|
21
21
|
import fs from 'node:fs';
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
|
-
import { loadContext } from './
|
|
24
|
+
import { loadContext } from './context.mjs';
|
|
25
25
|
import { resolveFiles } from './live-inject.mjs';
|
|
26
26
|
import { readLiveServerInfo } from './impeccable-paths.mjs';
|
|
27
27
|
|
|
@@ -80,7 +80,7 @@ The agent should then:
|
|
|
80
80
|
process.exit(1);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// 4. Load PRODUCT.md + DESIGN.md context
|
|
83
|
+
// 4. Load PRODUCT.md + DESIGN.md context.
|
|
84
84
|
const ctx = loadContext(process.cwd());
|
|
85
85
|
|
|
86
86
|
// 5. Compute drift-heal: compare resolved inject targets against the
|
|
@@ -102,7 +102,6 @@ The agent should then:
|
|
|
102
102
|
hasDesign: ctx.hasDesign,
|
|
103
103
|
design: ctx.design,
|
|
104
104
|
designPath: ctx.designPath,
|
|
105
|
-
migrated: ctx.migrated,
|
|
106
105
|
}, null, 2));
|
|
107
106
|
}
|
|
108
107
|
|