@bastani/atomic 0.9.11-alpha.6 → 0.9.11-alpha.7
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 +14 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +33 -129
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_asset_producer.toml +11 -3
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_documenter.toml +26 -0
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_finish_reviewer.toml +35 -0
- package/dist/builtin/workflows/skills/impeccable/reference/android.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/animate.md +72 -189
- package/dist/builtin/workflows/skills/impeccable/reference/audit.md +10 -9
- package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +19 -108
- package/dist/builtin/workflows/skills/impeccable/reference/clarify.md +59 -253
- package/dist/builtin/workflows/skills/impeccable/reference/colorize.md +51 -222
- package/dist/builtin/workflows/skills/impeccable/reference/craft-floor.md +45 -0
- package/dist/builtin/workflows/skills/impeccable/reference/craft.md +3 -121
- package/dist/builtin/workflows/skills/impeccable/reference/critique.md +28 -20
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/asset-producer.md +97 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/documenter.md +23 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/finish-reviewer.md +32 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/dist/builtin/workflows/skills/impeccable/reference/delight.md +47 -279
- package/dist/builtin/workflows/skills/impeccable/reference/distill.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/doctor.md +53 -0
- package/dist/builtin/workflows/skills/impeccable/reference/document.md +60 -73
- package/dist/builtin/workflows/skills/impeccable/reference/harden.md +1 -12
- package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +17 -4
- package/dist/builtin/workflows/skills/impeccable/reference/init.md +64 -163
- package/dist/builtin/workflows/skills/impeccable/reference/ios.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/layout.md +52 -153
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +44 -32
- package/dist/builtin/workflows/skills/impeccable/reference/new-work.md +105 -0
- package/dist/builtin/workflows/skills/impeccable/reference/{product.md → operate.md} +6 -5
- package/dist/builtin/workflows/skills/impeccable/reference/optimize.md +4 -4
- package/dist/builtin/workflows/skills/impeccable/reference/overdrive.md +1 -4
- package/dist/builtin/workflows/skills/impeccable/reference/polish.md +68 -212
- package/dist/builtin/workflows/skills/impeccable/reference/quieter.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/routing.md +18 -0
- package/dist/builtin/workflows/skills/impeccable/reference/shape.md +38 -144
- package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +48 -269
- package/dist/builtin/workflows/skills/impeccable/reference/visualize.md +38 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/command-metadata.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/concept-seed.mjs +584 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +117 -9
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +486 -59
- package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +16 -45
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +96 -10
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +143 -26
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +181 -12
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +3187 -182
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +102 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +282 -70
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +183 -15
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +22 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/findings.mjs +7 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +16 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +155 -42
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +2988 -155
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/constants.mjs +11 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/doctor.mjs +336 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/generate-image.mjs +232 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +90 -10
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +410 -78
- package/dist/builtin/workflows/skills/impeccable/scripts/hook.mjs +25 -8
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/artifact-schema.mjs +93 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/composition-catalog.mjs +165 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/concept-catalog.mjs +329 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +20 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +16 -8
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/provider.mjs +1 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-deep.mjs +455 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-notice.mjs +169 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness.mjs +457 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/surface-briefs.mjs +151 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-slug.mjs +33 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/template-extensions.mjs +146 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/completion.mjs +10 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live/event-validation.mjs +15 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/generation-preflight.mjs +149 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/poll-lanes.mjs +14 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/session-store.mjs +109 -32
- package/dist/builtin/workflows/skills/impeccable/scripts/live/source-lock.mjs +105 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/source-search.mjs +105 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +8 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +208 -68
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +563 -112
- package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +3 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +198 -13
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +24 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +6 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +44 -14
- package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +398 -33
- package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +9 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +81 -67
- package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +7 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/palette.mjs +76 -81
- package/dist/builtin/workflows/skills/impeccable/scripts/pin.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/serve-question.mjs +890 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/surface-brief.mjs +74 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +201 -22
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +23 -9
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +1 -0
- package/dist/builtin/workflows/src/runs/foreground/postmortem-stage-chat.ts +20 -5
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +10 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-delivery-activity.ts +86 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +6 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +19 -13
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +8 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +16 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-delivery-activity.ts +121 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +42 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +16 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +4 -0
- package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-actions.js +2 -14
- package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.js +9 -0
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +25 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.js +53 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts +13 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +22 -1
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +9 -3
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/docs/tui.md +7 -2
- package/docs/workflows.md +1 -1
- package/npm-shrinkwrap.json +23 -23
- package/package.json +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/brand.md +0 -108
- package/dist/builtin/workflows/skills/impeccable/reference/codex.md +0 -105
- package/dist/builtin/workflows/skills/impeccable/reference/interaction-design.md +0 -189
|
@@ -8,9 +8,14 @@
|
|
|
8
8
|
* with zero LLM involvement.
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
|
-
* node live-inject.mjs --port PORT
|
|
12
|
-
* node live-inject.mjs --remove
|
|
13
|
-
* node live-inject.mjs --check
|
|
11
|
+
* node live-inject.mjs --port PORT [--token TOKEN] # Insert the live script tag
|
|
12
|
+
* node live-inject.mjs --remove # Remove the live script tag
|
|
13
|
+
* node live-inject.mjs --check # Check whether live config exists
|
|
14
|
+
*
|
|
15
|
+
* When --token is supplied, it is appended to the /live.js src as `?token=...`
|
|
16
|
+
* so the server's token-gated /live.js handler will serve the bundle. Omitting
|
|
17
|
+
* the token yields a bare `/live.js` src (legacy behavior; the server returns
|
|
18
|
+
* 401 for it under the current gate).
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
import fs from 'node:fs';
|
|
@@ -22,11 +27,18 @@ import {
|
|
|
22
27
|
detectSvelteKitProject,
|
|
23
28
|
removeSvelteKitLiveAdapter,
|
|
24
29
|
} from './live/sveltekit-adapter.mjs';
|
|
30
|
+
import {
|
|
31
|
+
applyTanStackLiveAdapter,
|
|
32
|
+
detectTanStackStartProject,
|
|
33
|
+
removeTanStackLiveAdapter,
|
|
34
|
+
} from './live/tanstack-adapter.mjs';
|
|
25
35
|
|
|
26
36
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
37
|
const CONFIG_PATH = resolveLiveConfigPath({ cwd: process.cwd(), scriptsDir: __dirname });
|
|
28
38
|
const MARKER_OPEN_TEXT = 'impeccable-live-start';
|
|
29
39
|
const MARKER_CLOSE_TEXT = 'impeccable-live-end';
|
|
40
|
+
const NUXT_PLUGIN_MARKER = 'impeccable-live-nuxt-plugin';
|
|
41
|
+
const NUXT_PLUGIN_NAME = 'impeccable-live.client.ts';
|
|
30
42
|
const IGNORE_MARKER_OPEN = '# impeccable-live-ignore-start';
|
|
31
43
|
const IGNORE_MARKER_CLOSE = '# impeccable-live-ignore-end';
|
|
32
44
|
|
|
@@ -37,6 +49,9 @@ export const LIVE_IGNORE_PATTERNS = Object.freeze([
|
|
|
37
49
|
'.impeccable/live/sessions/',
|
|
38
50
|
'.impeccable/live/previews/',
|
|
39
51
|
'.impeccable/live/annotations/',
|
|
52
|
+
'.impeccable/live/artifacts/',
|
|
53
|
+
'.impeccable/live/accept-receipts/',
|
|
54
|
+
'.impeccable/live/locks/',
|
|
40
55
|
'.impeccable/live/cache/',
|
|
41
56
|
'.impeccable/live/manual-edit-apply-transaction.json',
|
|
42
57
|
'.impeccable/live/manual-edit-events.jsonl',
|
|
@@ -45,10 +60,15 @@ export const LIVE_IGNORE_PATTERNS = Object.freeze([
|
|
|
45
60
|
'.impeccable/live/deferred-svelte-component-accepts.json',
|
|
46
61
|
'.impeccable-live.json',
|
|
47
62
|
'.impeccable-live/',
|
|
63
|
+
'app/.impeccable-live/',
|
|
64
|
+
'src/.impeccable-live/',
|
|
48
65
|
'node_modules/.impeccable-live/',
|
|
49
66
|
'src/lib/impeccable/ImpeccableLiveRoot.svelte',
|
|
50
67
|
'src/lib/impeccable/__runtime.js',
|
|
51
68
|
'src/lib/impeccable/[0-9a-f]*/',
|
|
69
|
+
'plugins/impeccable-live.client.ts',
|
|
70
|
+
'app/plugins/impeccable-live.client.ts',
|
|
71
|
+
'src/plugins/impeccable-live.client.ts',
|
|
52
72
|
]);
|
|
53
73
|
|
|
54
74
|
/**
|
|
@@ -112,6 +132,8 @@ Output (JSON):
|
|
|
112
132
|
|
|
113
133
|
const resolvedFiles = resolveFiles(process.cwd(), config);
|
|
114
134
|
const svelteKit = detectSvelteKitProject(process.cwd(), config);
|
|
135
|
+
const nuxt = detectNuxtProject(process.cwd());
|
|
136
|
+
const tanstack = svelteKit || nuxt ? null : detectTanStackStartProject(process.cwd());
|
|
115
137
|
|
|
116
138
|
if (args.includes('--remove')) {
|
|
117
139
|
if (svelteKit) {
|
|
@@ -119,6 +141,18 @@ Output (JSON):
|
|
|
119
141
|
console.log(JSON.stringify({ ok: true, adapter: 'sveltekit', results: [adapterResult] }));
|
|
120
142
|
return;
|
|
121
143
|
}
|
|
144
|
+
if (tanstack) {
|
|
145
|
+
const adapterResult = removeTanStackLiveAdapter({ cwd: process.cwd(), project: tanstack });
|
|
146
|
+
console.log(JSON.stringify({ ok: !adapterResult.error, adapter: 'tanstack-start', results: [adapterResult] }));
|
|
147
|
+
if (adapterResult.error) process.exitCode = 1;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (nuxt) {
|
|
151
|
+
const adapterResult = removeNuxtLiveAdapter({ cwd: process.cwd(), project: nuxt });
|
|
152
|
+
console.log(JSON.stringify({ ok: !adapterResult.error, adapter: 'nuxt', results: [adapterResult] }));
|
|
153
|
+
if (adapterResult.error) process.exitCode = 1;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
122
156
|
const results = resolvedFiles.map((relFile) => {
|
|
123
157
|
const absFile = path.resolve(process.cwd(), relFile);
|
|
124
158
|
if (!fs.existsSync(absFile)) return { file: relFile, error: 'file_not_found' };
|
|
@@ -144,20 +178,51 @@ Output (JSON):
|
|
|
144
178
|
console.error(JSON.stringify({ ok: false, error: 'missing_port' }));
|
|
145
179
|
process.exit(1);
|
|
146
180
|
}
|
|
147
|
-
|
|
181
|
+
// Optional server token: appended to the /live.js src so the token-gated
|
|
182
|
+
// /live.js handler authorizes the browser fetch. `live.mjs` always passes it.
|
|
183
|
+
const tokenIdx = args.indexOf('--token');
|
|
184
|
+
const token = tokenIdx !== -1 ? args[tokenIdx + 1] : undefined;
|
|
185
|
+
const gitIgnore = ensureLiveGitIgnores(
|
|
186
|
+
process.cwd(),
|
|
187
|
+
nuxt ? [nuxt.pluginFile] : tanstack ? [tanstack.componentFile] : [],
|
|
188
|
+
);
|
|
148
189
|
|
|
149
190
|
if (svelteKit) {
|
|
150
|
-
const adapterResult = applySvelteKitLiveAdapter({ cwd: process.cwd(), port, config });
|
|
191
|
+
const adapterResult = applySvelteKitLiveAdapter({ cwd: process.cwd(), port, token, config });
|
|
151
192
|
console.log(JSON.stringify({ ok: true, port, adapter: 'sveltekit', gitIgnore, results: [adapterResult] }));
|
|
152
193
|
return;
|
|
153
194
|
}
|
|
195
|
+
if (tanstack) {
|
|
196
|
+
const adapterResult = applyTanStackLiveAdapter({ cwd: process.cwd(), port, token, project: tanstack });
|
|
197
|
+
console.log(JSON.stringify({
|
|
198
|
+
ok: !adapterResult.error,
|
|
199
|
+
port,
|
|
200
|
+
adapter: 'tanstack-start',
|
|
201
|
+
gitIgnore,
|
|
202
|
+
results: [adapterResult],
|
|
203
|
+
}));
|
|
204
|
+
if (adapterResult.error) process.exitCode = 1;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (nuxt) {
|
|
208
|
+
const adapterResult = applyNuxtLiveAdapter({ cwd: process.cwd(), port, token, project: nuxt });
|
|
209
|
+
console.log(JSON.stringify({
|
|
210
|
+
ok: !adapterResult.error,
|
|
211
|
+
port,
|
|
212
|
+
adapter: 'nuxt',
|
|
213
|
+
gitIgnore,
|
|
214
|
+
results: [adapterResult],
|
|
215
|
+
}));
|
|
216
|
+
if (adapterResult.error) process.exitCode = 1;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
154
219
|
|
|
155
220
|
const results = resolvedFiles.map((relFile) => {
|
|
156
221
|
const absFile = path.resolve(process.cwd(), relFile);
|
|
157
222
|
if (!fs.existsSync(absFile)) return { file: relFile, error: 'file_not_found' };
|
|
158
223
|
const content = fs.readFileSync(absFile, 'utf-8');
|
|
159
224
|
const withoutOld = revertCspMeta(removeTag(content, config.commentSyntax));
|
|
160
|
-
const withTag = insertTag(withoutOld, config, port, relFile);
|
|
225
|
+
const withTag = insertTag(withoutOld, config, port, relFile, token);
|
|
161
226
|
if (withTag === withoutOld) {
|
|
162
227
|
return { file: relFile, error: 'insertion_point_not_found', anchor: config.insertBefore || config.insertAfter };
|
|
163
228
|
}
|
|
@@ -174,12 +239,12 @@ Output (JSON):
|
|
|
174
239
|
if (!anyInserted) process.exit(1);
|
|
175
240
|
}
|
|
176
241
|
|
|
177
|
-
export function ensureLiveGitIgnores(cwd = process.cwd()) {
|
|
242
|
+
export function ensureLiveGitIgnores(cwd = process.cwd(), extraPatterns = []) {
|
|
178
243
|
const target = resolveIgnoreTarget(cwd);
|
|
179
244
|
const existing = fs.existsSync(target.path) ? fs.readFileSync(target.path, 'utf-8') : '';
|
|
180
245
|
const block = [
|
|
181
246
|
IGNORE_MARKER_OPEN,
|
|
182
|
-
...LIVE_IGNORE_PATTERNS,
|
|
247
|
+
...new Set([...LIVE_IGNORE_PATTERNS, ...extraPatterns]),
|
|
183
248
|
IGNORE_MARKER_CLOSE,
|
|
184
249
|
].join('\n');
|
|
185
250
|
const markerRe = new RegExp(`${escapeRegExp(IGNORE_MARKER_OPEN)}[\\s\\S]*?${escapeRegExp(IGNORE_MARKER_CLOSE)}`);
|
|
@@ -201,10 +266,119 @@ export function ensureLiveGitIgnores(cwd = process.cwd()) {
|
|
|
201
266
|
file: path.relative(cwd, target.path).split(path.sep).join('/'),
|
|
202
267
|
mode: target.mode,
|
|
203
268
|
changed: updated !== existing,
|
|
204
|
-
patterns: [...LIVE_IGNORE_PATTERNS],
|
|
269
|
+
patterns: [...new Set([...LIVE_IGNORE_PATTERNS, ...extraPatterns])],
|
|
205
270
|
};
|
|
206
271
|
}
|
|
207
272
|
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Nuxt adapter
|
|
275
|
+
//
|
|
276
|
+
// A script element placed in app.vue is compiled as Vue-rendered DOM and is
|
|
277
|
+
// not executed. Nuxt instead auto-discovers client plugins. Keep the adapter
|
|
278
|
+
// generated, dev-only, and outside user-authored source: Live creates one
|
|
279
|
+
// marked .client.ts plugin on start and removes it on stop.
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
export function detectNuxtProject(cwd = process.cwd()) {
|
|
283
|
+
const configFile = fs.readdirSync(cwd, { withFileTypes: true })
|
|
284
|
+
.find((entry) => entry.isFile() && /^nuxt\.config\.(?:js|mjs|cjs|ts|mts|cts)$/.test(entry.name))
|
|
285
|
+
?.name;
|
|
286
|
+
if (!configFile) return null;
|
|
287
|
+
|
|
288
|
+
const config = fs.readFileSync(path.join(cwd, configFile), 'utf-8');
|
|
289
|
+
const literalSrcDir = config.match(/\bsrcDir\s*:\s*(['"])([^'"]+)\1/);
|
|
290
|
+
let appDir = '';
|
|
291
|
+
if (literalSrcDir) {
|
|
292
|
+
const candidate = literalSrcDir[2]
|
|
293
|
+
.replace(/\\/g, '/')
|
|
294
|
+
.replace(/^\.\//, '')
|
|
295
|
+
.replace(/\/+$/, '');
|
|
296
|
+
const normalized = path.posix.normalize(candidate);
|
|
297
|
+
if (normalized !== '..' && !normalized.startsWith('../') && !path.isAbsolute(normalized)) {
|
|
298
|
+
appDir = normalized === '.' ? '' : normalized;
|
|
299
|
+
}
|
|
300
|
+
} else if (
|
|
301
|
+
fs.existsSync(path.join(cwd, 'app', 'app.vue'))
|
|
302
|
+
|| fs.existsSync(path.join(cwd, 'app', 'pages'))
|
|
303
|
+
) {
|
|
304
|
+
appDir = 'app';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const pluginFile = [appDir, 'plugins', NUXT_PLUGIN_NAME].filter(Boolean).join('/');
|
|
308
|
+
return { configFile, appDir, pluginFile };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function buildNuxtPlugin(port, token) {
|
|
312
|
+
return `/* ${NUXT_PLUGIN_MARKER} */
|
|
313
|
+
const liveSrc = '${buildLiveScriptSrc(port, token)}';
|
|
314
|
+
const liveSelector = 'script[data-impeccable-live-nuxt]';
|
|
315
|
+
|
|
316
|
+
export default defineNuxtPlugin(() => {
|
|
317
|
+
if (!import.meta.dev || typeof document === 'undefined') return;
|
|
318
|
+
|
|
319
|
+
const expectedSrc = new URL(liveSrc, window.location.href).href;
|
|
320
|
+
let script = document.querySelector(liveSelector);
|
|
321
|
+
if (script?.src === expectedSrc) return;
|
|
322
|
+
script?.remove();
|
|
323
|
+
|
|
324
|
+
script = document.createElement('script');
|
|
325
|
+
script.src = liveSrc;
|
|
326
|
+
script.async = true;
|
|
327
|
+
script.dataset.impeccableLiveNuxt = '';
|
|
328
|
+
document.head.appendChild(script);
|
|
329
|
+
|
|
330
|
+
import.meta.hot?.dispose(() => {
|
|
331
|
+
if (script?.isConnected) script.remove();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
/* /${NUXT_PLUGIN_MARKER} */
|
|
335
|
+
`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function applyNuxtLiveAdapter({ cwd = process.cwd(), port, token, project = detectNuxtProject(cwd) }) {
|
|
339
|
+
if (!project) return { error: 'nuxt_not_detected' };
|
|
340
|
+
const absFile = path.join(cwd, project.pluginFile);
|
|
341
|
+
const existing = fs.existsSync(absFile) ? fs.readFileSync(absFile, 'utf-8') : null;
|
|
342
|
+
if (existing !== null && !existing.includes(NUXT_PLUGIN_MARKER)) {
|
|
343
|
+
return {
|
|
344
|
+
file: project.pluginFile,
|
|
345
|
+
error: 'nuxt_plugin_conflict',
|
|
346
|
+
hint: `${project.pluginFile} already exists and is not managed by Impeccable Live`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const content = buildNuxtPlugin(port, token);
|
|
351
|
+
fs.mkdirSync(path.dirname(absFile), { recursive: true });
|
|
352
|
+
if (content !== existing) fs.writeFileSync(absFile, content, 'utf-8');
|
|
353
|
+
return {
|
|
354
|
+
file: project.pluginFile,
|
|
355
|
+
inserted: true,
|
|
356
|
+
changed: content !== existing,
|
|
357
|
+
devOnly: true,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function removeNuxtLiveAdapter({ cwd = process.cwd(), project = detectNuxtProject(cwd) }) {
|
|
362
|
+
if (!project) return { error: 'nuxt_not_detected' };
|
|
363
|
+
const absFile = path.join(cwd, project.pluginFile);
|
|
364
|
+
if (!fs.existsSync(absFile)) {
|
|
365
|
+
return { file: project.pluginFile, removed: false, note: 'no adapter present' };
|
|
366
|
+
}
|
|
367
|
+
const content = fs.readFileSync(absFile, 'utf-8');
|
|
368
|
+
if (!content.includes(NUXT_PLUGIN_MARKER)) {
|
|
369
|
+
return {
|
|
370
|
+
file: project.pluginFile,
|
|
371
|
+
removed: false,
|
|
372
|
+
error: 'nuxt_plugin_conflict',
|
|
373
|
+
hint: `${project.pluginFile} is not managed by Impeccable Live`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
fs.unlinkSync(absFile);
|
|
377
|
+
const pluginDir = path.dirname(absFile);
|
|
378
|
+
if (fs.readdirSync(pluginDir).length === 0) fs.rmdirSync(pluginDir);
|
|
379
|
+
return { file: project.pluginFile, removed: true };
|
|
380
|
+
}
|
|
381
|
+
|
|
208
382
|
function resolveIgnoreTarget(cwd) {
|
|
209
383
|
const gitExcludePath = resolveGitInfoExcludePath(cwd);
|
|
210
384
|
if (gitExcludePath) {
|
|
@@ -355,7 +529,18 @@ function validateConfig(cfg) {
|
|
|
355
529
|
function commentOpen(syntax) { return syntax === 'jsx' ? '{/*' : '<!--'; }
|
|
356
530
|
function commentClose(syntax) { return syntax === 'jsx' ? '*/}' : '-->'; }
|
|
357
531
|
|
|
358
|
-
|
|
532
|
+
/**
|
|
533
|
+
* Build the /live.js src the browser loads. When a token is supplied it rides
|
|
534
|
+
* as a `?token=...` query param so the server's token-gated /live.js handler
|
|
535
|
+
* authorizes the fetch. Shared by every injection path (HTML/JSX script tag,
|
|
536
|
+
* the Nuxt plugin, the SvelteKit root component) so they stay in sync.
|
|
537
|
+
*/
|
|
538
|
+
export function buildLiveScriptSrc(port, token) {
|
|
539
|
+
const base = 'http://localhost:' + port + '/live.js';
|
|
540
|
+
return token ? base + '?token=' + encodeURIComponent(token) : base;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function buildTagBlock(syntax, port, filePath, token) {
|
|
359
544
|
const open = commentOpen(syntax);
|
|
360
545
|
const close = commentClose(syntax);
|
|
361
546
|
// Astro processes <script> tags by default and rewrites src to its own
|
|
@@ -364,7 +549,7 @@ function buildTagBlock(syntax, port, filePath) {
|
|
|
364
549
|
const scriptAttrs = isAstro ? 'is:inline ' : '';
|
|
365
550
|
return (
|
|
366
551
|
open + ' ' + MARKER_OPEN_TEXT + ' ' + close + '\n' +
|
|
367
|
-
'<script ' + scriptAttrs + 'src="
|
|
552
|
+
'<script ' + scriptAttrs + 'src="' + buildLiveScriptSrc(port, token) + '"></script>\n' +
|
|
368
553
|
open + ' ' + MARKER_CLOSE_TEXT + ' ' + close + '\n'
|
|
369
554
|
);
|
|
370
555
|
}
|
|
@@ -386,9 +571,9 @@ function readLineEndingAt(content, index) {
|
|
|
386
571
|
return '';
|
|
387
572
|
}
|
|
388
573
|
|
|
389
|
-
function insertTag(content, config, port, filePath) {
|
|
574
|
+
function insertTag(content, config, port, filePath, token) {
|
|
390
575
|
const lineEnding = detectLineEnding(content);
|
|
391
|
-
const block = normalizeLineEndings(buildTagBlock(config.commentSyntax, port, filePath), lineEnding);
|
|
576
|
+
const block = normalizeLineEndings(buildTagBlock(config.commentSyntax, port, filePath, token), lineEnding);
|
|
392
577
|
// insertBefore: match the LAST occurrence. Anchors like `</body>` naturally
|
|
393
578
|
// belong at the end, and the same literal can appear earlier in code blocks
|
|
394
579
|
// within rendered documentation pages.
|
|
@@ -131,6 +131,9 @@ Output (JSON):
|
|
|
131
131
|
const query = argVal(args, '--query');
|
|
132
132
|
const filePath = argVal(args, '--file');
|
|
133
133
|
const text = argVal(args, '--text');
|
|
134
|
+
// See live-wrap.mjs: preflight computes the scaffold but leaves source
|
|
135
|
+
// untouched so the agent's single edit is the only framework reload.
|
|
136
|
+
const deferSourceWrite = args.includes('--defer-source-write');
|
|
134
137
|
|
|
135
138
|
if (!id) { console.error('Missing --id'); process.exit(1); }
|
|
136
139
|
if (!position) { console.error('Missing --position (before | after)'); process.exit(1); }
|
|
@@ -244,12 +247,23 @@ Output (JSON):
|
|
|
244
247
|
isJsx,
|
|
245
248
|
});
|
|
246
249
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
let deferredWrapper = null;
|
|
251
|
+
if (deferSourceWrite) {
|
|
252
|
+
// Insert-as-empty-range: the agent inserts `wrapperBlock` (variants spliced
|
|
253
|
+
// at the marker) at spliceIndex without removing any source line.
|
|
254
|
+
deferredWrapper = {
|
|
255
|
+
block: wrapperLines.join('\n'),
|
|
256
|
+
replaceStartLine: spliceIndex + 1,
|
|
257
|
+
replaceEndLine: spliceIndex, // empty range (endLine < startLine) => insertion
|
|
258
|
+
};
|
|
259
|
+
} else {
|
|
260
|
+
const newLines = [
|
|
261
|
+
...lines.slice(0, spliceIndex),
|
|
262
|
+
...wrapperLines,
|
|
263
|
+
...lines.slice(spliceIndex),
|
|
264
|
+
];
|
|
265
|
+
fs.writeFileSync(targetFile, newLines.join('\n'), 'utf-8');
|
|
266
|
+
}
|
|
253
267
|
|
|
254
268
|
const insertLine = spliceIndex + 3;
|
|
255
269
|
|
|
@@ -257,6 +271,10 @@ Output (JSON):
|
|
|
257
271
|
mode: 'insert',
|
|
258
272
|
position,
|
|
259
273
|
file: relTargetFile,
|
|
274
|
+
sourceWritten: deferredWrapper ? false : undefined,
|
|
275
|
+
wrapperBlock: deferredWrapper ? deferredWrapper.block : undefined,
|
|
276
|
+
replaceStartLine: deferredWrapper ? deferredWrapper.replaceStartLine : undefined,
|
|
277
|
+
replaceEndLine: deferredWrapper ? deferredWrapper.replaceEndLine : undefined,
|
|
260
278
|
insertLine: insertLine + 1,
|
|
261
279
|
commentSyntax,
|
|
262
280
|
styleMode: styleMode.mode,
|
|
@@ -14,7 +14,12 @@ import { isGeneratedFile } from './lib/is-generated.mjs';
|
|
|
14
14
|
import { readBuffer, getBufferPath } from './live/manual-edits-buffer.mjs';
|
|
15
15
|
|
|
16
16
|
const EVIDENCE_VERSION = 1;
|
|
17
|
-
const TEXT_EXTENSIONS = new Set([
|
|
17
|
+
const TEXT_EXTENSIONS = new Set([
|
|
18
|
+
'.html', '.jsx', '.tsx', '.vue', '.svelte', '.astro', '.js', '.mjs', '.ts',
|
|
19
|
+
// Phoenix keeps `~H"""` markup in .ex alongside standalone .heex/.eex
|
|
20
|
+
// templates, so copy edits land in all three.
|
|
21
|
+
'.ex', '.heex', '.eex',
|
|
22
|
+
]);
|
|
18
23
|
const SEARCH_DIRS = ['src', 'app', 'pages', 'components', 'public', 'views', 'templates', 'site', 'lib', 'data'];
|
|
19
24
|
const STRONG_LITERAL_MATCH_LIMIT = 8;
|
|
20
25
|
const WEAK_LITERAL_MATCH_LIMIT = 4;
|
|
@@ -27,7 +27,7 @@ const scriptCmd = (name) => `node "${path.join(SELF_DIR, name)}"`;
|
|
|
27
27
|
export const PER_REQUEST_TIMEOUT_MS = 270_000;
|
|
28
28
|
export const DEFAULT_EVENT_LEASE_MS = 600_000;
|
|
29
29
|
|
|
30
|
-
const EVENT_TYPES_NEEDING_AGENT_REPLY = new Set(['generate', 'steer', 'manual_edit_apply']);
|
|
30
|
+
const EVENT_TYPES_NEEDING_AGENT_REPLY = new Set(['generate', 'steer', 'manual_edit_apply', 'carbonize_cleanup']);
|
|
31
31
|
|
|
32
32
|
function readServerInfo() {
|
|
33
33
|
const record = readLiveServerInfo(process.cwd());
|
|
@@ -38,8 +38,8 @@ function readServerInfo() {
|
|
|
38
38
|
return record.info;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export function buildPollReplyPayload(token, { id, type, message, file, data }) {
|
|
42
|
-
return { token, id, type, message, file, data };
|
|
41
|
+
export function buildPollReplyPayload(token, { id, type, message, file, data, sourceEventType }) {
|
|
42
|
+
return { token, id, type, message, file, data, sourceEventType };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function manualApplyPollBanner(event = {}) {
|
|
@@ -152,7 +152,14 @@ export async function waitForEventAck(base, token, eventId, {
|
|
|
152
152
|
return false;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
export async function fetchNextEvent(base, token, {
|
|
155
|
+
export async function fetchNextEvent(base, token, {
|
|
156
|
+
totalDeadline,
|
|
157
|
+
types,
|
|
158
|
+
resolveTypes,
|
|
159
|
+
perRequestTimeoutMs = PER_REQUEST_TIMEOUT_MS,
|
|
160
|
+
leaseMs = DEFAULT_EVENT_LEASE_MS,
|
|
161
|
+
signal,
|
|
162
|
+
} = {}) {
|
|
156
163
|
while (true) {
|
|
157
164
|
if (totalDeadline && Date.now() >= totalDeadline) {
|
|
158
165
|
return { type: 'timeout' };
|
|
@@ -161,8 +168,15 @@ export async function fetchNextEvent(base, token, { totalDeadline } = {}) {
|
|
|
161
168
|
const remaining = totalDeadline
|
|
162
169
|
? totalDeadline - Date.now()
|
|
163
170
|
: PER_REQUEST_TIMEOUT_MS;
|
|
164
|
-
const slice = Math.min(Math.max(remaining, 1000),
|
|
165
|
-
const
|
|
171
|
+
const slice = Math.min(Math.max(remaining, 1000), perRequestTimeoutMs);
|
|
172
|
+
const query = new URLSearchParams({
|
|
173
|
+
token,
|
|
174
|
+
timeout: String(slice),
|
|
175
|
+
leaseMs: String(leaseMs),
|
|
176
|
+
});
|
|
177
|
+
const normalizedTypes = normalizePollTypes(resolveTypes ? await resolveTypes() : types);
|
|
178
|
+
if (normalizedTypes.length > 0) query.set('types', normalizedTypes.join(','));
|
|
179
|
+
const res = await fetch(`${base}/poll?${query}`, { signal });
|
|
166
180
|
|
|
167
181
|
if (res.status === 401) {
|
|
168
182
|
const err = new Error('Authentication failed. The server token may have changed.');
|
|
@@ -202,11 +216,17 @@ export async function augmentEventWithAcceptHandling(event, base, token) {
|
|
|
202
216
|
event._acceptResult = { handled: false, mode: 'error', error: err.message };
|
|
203
217
|
}
|
|
204
218
|
|
|
219
|
+
await completeAcceptHandling(event, base, token);
|
|
220
|
+
return event;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function completeAcceptHandling(event, base, token) {
|
|
205
224
|
const completionType = completionTypeForAcceptResult(event.type, event._acceptResult);
|
|
206
225
|
try {
|
|
207
226
|
await postReply(base, token, {
|
|
208
227
|
id: event.id,
|
|
209
228
|
type: completionType,
|
|
229
|
+
sourceEventType: event.type,
|
|
210
230
|
message: event._acceptResult?.error,
|
|
211
231
|
file: event._acceptResult?.file,
|
|
212
232
|
data: event._acceptResult?.carbonize === true ? { carbonize: true } : undefined,
|
|
@@ -217,7 +237,6 @@ export async function augmentEventWithAcceptHandling(event, base, token) {
|
|
|
217
237
|
if (!event._completionAck) {
|
|
218
238
|
event._completionAck = completionAckForAcceptResult(event.id, completionType, event._acceptResult);
|
|
219
239
|
}
|
|
220
|
-
|
|
221
240
|
return event;
|
|
222
241
|
}
|
|
223
242
|
|
|
@@ -245,9 +264,9 @@ export function printPollEvent(event) {
|
|
|
245
264
|
console.log(JSON.stringify(event));
|
|
246
265
|
}
|
|
247
266
|
|
|
248
|
-
export async function runPollOnce(base, token, { totalTimeout = 600_000 } = {}) {
|
|
267
|
+
export async function runPollOnce(base, token, { totalTimeout = 600_000, types, resolveTypes, perRequestTimeoutMs } = {}) {
|
|
249
268
|
const deadline = Date.now() + totalTimeout;
|
|
250
|
-
const event = await fetchNextEvent(base, token, { totalDeadline: deadline });
|
|
269
|
+
const event = await fetchNextEvent(base, token, { totalDeadline: deadline, types, resolveTypes, perRequestTimeoutMs });
|
|
251
270
|
await augmentEventWithAcceptHandling(event, base, token);
|
|
252
271
|
writeCarbonizeBanner(event);
|
|
253
272
|
printPollEvent(event);
|
|
@@ -258,11 +277,14 @@ export async function runPollStream(base, token, {
|
|
|
258
277
|
ackTimeoutMs = 600_000,
|
|
259
278
|
ackPollIntervalMs = 400,
|
|
260
279
|
shouldContinue = () => true,
|
|
280
|
+
types,
|
|
281
|
+
resolveTypes,
|
|
282
|
+
perRequestTimeoutMs,
|
|
261
283
|
} = {}) {
|
|
262
284
|
process.stderr.write('[impeccable-poll] stream mode: one JSON object per line on stdout; use --reply while this process stays running\n');
|
|
263
285
|
|
|
264
286
|
while (shouldContinue()) {
|
|
265
|
-
const event = await fetchNextEvent(base, token);
|
|
287
|
+
const event = await fetchNextEvent(base, token, { types, resolveTypes, perRequestTimeoutMs });
|
|
266
288
|
await augmentEventWithAcceptHandling(event, base, token);
|
|
267
289
|
writeCarbonizeBanner(event);
|
|
268
290
|
printPollEvent(event);
|
|
@@ -322,14 +344,15 @@ Modes:
|
|
|
322
344
|
|
|
323
345
|
Options:
|
|
324
346
|
--timeout=MS One-shot poll timeout in ms (default: 600000). Ignored in --stream mode
|
|
347
|
+
--types=A,B Lease only these event types
|
|
325
348
|
--ack-timeout=MS Stream mode: max wait for --reply after generate/steer (default: 600000)
|
|
326
349
|
--file PATH Attach a source file path to the reply (generate/steer flow)
|
|
327
350
|
--data JSON Attach a JSON result object to the reply (manual_edit_apply flow). Must be valid JSON
|
|
328
351
|
--help Show this help message
|
|
329
352
|
|
|
330
353
|
Harness note:
|
|
331
|
-
Default one-shot mode is the
|
|
332
|
-
--stream is
|
|
354
|
+
Default one-shot mode is the primary contract, including Codex foreground polling.
|
|
355
|
+
--stream is retained for harnesses with measured, reliable incremental stdout.`);
|
|
333
356
|
process.exit(0);
|
|
334
357
|
}
|
|
335
358
|
|
|
@@ -360,23 +383,30 @@ Harness note:
|
|
|
360
383
|
}
|
|
361
384
|
|
|
362
385
|
const streamMode = args.includes('--stream');
|
|
386
|
+
const typesArg = args.find((a) => a.startsWith('--types='));
|
|
387
|
+
const types = normalizePollTypes(typesArg ? typesArg.slice('--types='.length) : null);
|
|
363
388
|
const ackTimeoutArg = args.find((a) => a.startsWith('--ack-timeout='));
|
|
364
389
|
const ackTimeoutMs = ackTimeoutArg ? parseInt(ackTimeoutArg.split('=')[1], 10) : 600_000;
|
|
365
390
|
|
|
366
391
|
try {
|
|
367
392
|
if (streamMode) {
|
|
368
|
-
await runPollStream(base, info.token, { ackTimeoutMs });
|
|
393
|
+
await runPollStream(base, info.token, { ackTimeoutMs, types });
|
|
369
394
|
return;
|
|
370
395
|
}
|
|
371
396
|
|
|
372
397
|
const timeoutArg = args.find((a) => a.startsWith('--timeout='));
|
|
373
398
|
const totalTimeout = timeoutArg ? parseInt(timeoutArg.split('=')[1], 10) : 600_000;
|
|
374
|
-
await runPollOnce(base, info.token, { totalTimeout });
|
|
399
|
+
await runPollOnce(base, info.token, { totalTimeout, types });
|
|
375
400
|
} catch (err) {
|
|
376
401
|
handlePollError(err);
|
|
377
402
|
}
|
|
378
403
|
}
|
|
379
404
|
|
|
405
|
+
export function normalizePollTypes(value) {
|
|
406
|
+
const values = Array.isArray(value) ? value : String(value || '').split(',');
|
|
407
|
+
return [...new Set(values.map((type) => String(type).trim()).filter(Boolean))];
|
|
408
|
+
}
|
|
409
|
+
|
|
380
410
|
// Auto-execute when run directly
|
|
381
411
|
const _running = process.argv[1];
|
|
382
412
|
if (_running?.endsWith('live-poll.mjs') || _running?.endsWith('live-poll.mjs/')) {
|