@aayambansal/squint 0.4.4 → 0.4.5
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/README.md +2 -0
- package/dist/{cdp-C6MOKVEN.js → cdp-Q4H6ZHPT.js} +1 -1
- package/dist/{chunk-O7NWGLS3.js → chunk-43NQNIJY.js} +2 -0
- package/dist/{chunk-ZUEGDTAM.js → chunk-CV5WVKHU.js} +16 -6
- package/dist/{chunk-JFFNQIBU.js → chunk-RQHOE5MV.js} +48 -1
- package/dist/{chunk-YT6K2YIS.js → chunk-ZLEP2TWF.js} +5 -0
- package/dist/cli.js +72 -36
- package/dist/{commands-VDICJJT2.js → commands-BY44HDQ6.js} +1 -1
- package/dist/{contextDoctor-M3R3PICP.js → contextDoctor-U3YTDFVG.js} +2 -1
- package/dist/{preview-Q43LOK6P.js → preview-ZUCVWGZG.js} +2 -2
- package/dist/{skills-GTTF4PNU.js → skills-6NZP67QT.js} +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -174,6 +174,8 @@ squint doctor --probe # run every engine end to end, verify auth act
|
|
|
174
174
|
flagged from the live page, and on Next 16+ the framework's own `/_next/mcp`
|
|
175
175
|
channel feeds structured errors straight into the fix loop. `/context` itemizes
|
|
176
176
|
the injected-context bill per source, with staleness warnings.
|
|
177
|
+
- **Visual approval**: engines ask before contested changes — the request renders
|
|
178
|
+
with its screenshot, `/yes` / `/no` answer it, the ledger remembers.
|
|
177
179
|
- **The design ledger**: `/decide` (plus chosen variants, rollbacks, accepted
|
|
178
180
|
sandboxes) appends to a committed `.squint/design-log.jsonl`; recent decisions ride
|
|
179
181
|
into every ask so they stop getting silently undone between sessions.
|
|
@@ -28,6 +28,8 @@ var COMMANDS = [
|
|
|
28
28
|
{ name: "find", args: "<term>", group: "session", description: "search this session and saved transcripts" },
|
|
29
29
|
{ name: "decide", args: "<text>", group: "session", description: "record a design decision; injected into every future ask" },
|
|
30
30
|
{ name: "context", group: "session", description: "what squint injects per ask, token-costed, with staleness warnings" },
|
|
31
|
+
{ name: "yes", args: "[note]", group: "build", description: "approve the engine's pending visual-approval request" },
|
|
32
|
+
{ name: "no", args: "[note]", group: "build", description: "reject the engine's pending visual-approval request" },
|
|
31
33
|
{ name: "resume", group: "session", description: "pick up the previous session for this repo" },
|
|
32
34
|
{ name: "clear", group: "session", description: "new session (transcript, totals, persisted state)" },
|
|
33
35
|
{ name: "help", group: "session", description: "list commands" },
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
cdpCapture,
|
|
11
11
|
hasWebSocket
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-RQHOE5MV.js";
|
|
13
13
|
|
|
14
14
|
// src/preview/preview.ts
|
|
15
15
|
import fs from "fs";
|
|
@@ -47,7 +47,7 @@ async function captureViewports(cwd, url) {
|
|
|
47
47
|
const base = url.replace(/\/+$/, "");
|
|
48
48
|
if (hasWebSocket()) {
|
|
49
49
|
try {
|
|
50
|
-
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true);
|
|
50
|
+
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true);
|
|
51
51
|
const errors2 = [];
|
|
52
52
|
for (const route of routes.slice(1)) {
|
|
53
53
|
try {
|
|
@@ -62,7 +62,7 @@ async function captureViewports(cwd, url) {
|
|
|
62
62
|
errors2.push(`${route}: capture failed`);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions };
|
|
65
|
+
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components };
|
|
66
66
|
} catch {
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -133,7 +133,7 @@ async function probeRuntime(url, cwd) {
|
|
|
133
133
|
async function comparePulse(previous, current) {
|
|
134
134
|
const chrome = findChrome();
|
|
135
135
|
if (!chrome || !hasWebSocket()) return null;
|
|
136
|
-
const { pixelDiffPct } = await import("./cdp-
|
|
136
|
+
const { pixelDiffPct } = await import("./cdp-Q4H6ZHPT.js");
|
|
137
137
|
return pixelDiffPct(chrome, previous, current);
|
|
138
138
|
}
|
|
139
139
|
function buildRuntimeFixPrompt(report) {
|
|
@@ -161,6 +161,16 @@ ${narration.join("\n")}
|
|
|
161
161
|
|
|
162
162
|
Judge this narration as an experience: does the reading order make sense? do names actually describe their targets? is anything announced as "(no accessible name)"? Fix real incoherence \u2014 this is how non-visual users meet the page.`;
|
|
163
163
|
}
|
|
164
|
+
function componentSection(components) {
|
|
165
|
+
if (!components || components.length === 0) return "";
|
|
166
|
+
return `
|
|
167
|
+
|
|
168
|
+
## Component map (from React fibers)
|
|
169
|
+
|
|
170
|
+
${components.join("\n")}
|
|
171
|
+
|
|
172
|
+
Use these owner chains to name the component (and file) an issue lives in instead of describing regions.`;
|
|
173
|
+
}
|
|
164
174
|
function vtSection(viewTransitions) {
|
|
165
175
|
if (!viewTransitions || viewTransitions.length === 0) return "";
|
|
166
176
|
return `
|
|
@@ -191,13 +201,13 @@ ${findings.join("\n")}
|
|
|
191
201
|
|
|
192
202
|
These patterns make the page read as template output. Rework them within the committed design direction \u2014 this is style debt, not a defect list.`;
|
|
193
203
|
}
|
|
194
|
-
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions) {
|
|
204
|
+
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components) {
|
|
195
205
|
const list = shots.map((s) => `- ${s.name}: ${s.path}`).join("\n");
|
|
196
206
|
return `Screenshots of the running app were just captured:
|
|
197
207
|
|
|
198
208
|
${list}
|
|
199
209
|
|
|
200
|
-
Read each screenshot and review the rendered UI against the design standards you were given. Check: visual hierarchy and spacing rhythm, typography, color and contrast, alignment, empty-looking or broken regions, and whether the mobile capture shows horizontal overflow or cramped layout. List the concrete issues you can SEE (not hypothetical ones), ranked by visual impact${extra ? `, with special attention to: ${extra}` : ""}. Then fix them and verify the app still builds.${runtimeSection(runtime)}${a11ySection(a11y)}${slopSection(slop)}${narrationSection(narration)}${phantomSection(phantoms)}${vtSection(viewTransitions)}`;
|
|
210
|
+
Read each screenshot and review the rendered UI against the design standards you were given. Check: visual hierarchy and spacing rhythm, typography, color and contrast, alignment, empty-looking or broken regions, and whether the mobile capture shows horizontal overflow or cramped layout. List the concrete issues you can SEE (not hypothetical ones), ranked by visual impact${extra ? `, with special attention to: ${extra}` : ""}. Then fix them and verify the app still builds.${runtimeSection(runtime)}${a11ySection(a11y)}${slopSection(slop)}${narrationSection(narration)}${phantomSection(phantoms)}${vtSection(viewTransitions)}${componentSection(components)}`;
|
|
201
211
|
}
|
|
202
212
|
|
|
203
213
|
export {
|
|
@@ -400,6 +400,43 @@ var VT_AUDIT = `(() => {
|
|
|
400
400
|
}
|
|
401
401
|
return findings;
|
|
402
402
|
})()`;
|
|
403
|
+
var FIBER_AUDIT = `(() => {
|
|
404
|
+
const fiberKey = (el) => Object.keys(el).find((k) => k.startsWith('__reactFiber$'));
|
|
405
|
+
const all = document.querySelectorAll('*');
|
|
406
|
+
let reactSeen = false;
|
|
407
|
+
for (let i = 0; i < all.length && i < 300; i++) {
|
|
408
|
+
if (fiberKey(all[i])) { reactSeen = true; break; }
|
|
409
|
+
}
|
|
410
|
+
if (!reactSeen) return [];
|
|
411
|
+
const nameOf = (t) => {
|
|
412
|
+
if (typeof t === 'function') return t.displayName || t.name || '';
|
|
413
|
+
if (t && typeof t === 'object') return t.displayName || (t.render && (t.render.displayName || t.render.name)) || '';
|
|
414
|
+
return '';
|
|
415
|
+
};
|
|
416
|
+
const chainFor = (el) => {
|
|
417
|
+
const key = fiberKey(el);
|
|
418
|
+
if (!key) return null;
|
|
419
|
+
let fiber = el[key];
|
|
420
|
+
const names = [];
|
|
421
|
+
let hops = 0;
|
|
422
|
+
while (fiber && hops < 50 && names.length < 3) {
|
|
423
|
+
const n = nameOf(fiber.type);
|
|
424
|
+
if (n && !names.includes(n)) names.push(n);
|
|
425
|
+
fiber = fiber.return;
|
|
426
|
+
hops++;
|
|
427
|
+
}
|
|
428
|
+
return names.length > 0 ? names.join(' < ') : null;
|
|
429
|
+
};
|
|
430
|
+
const out = [];
|
|
431
|
+
for (const sel of ['header', 'nav', 'main', 'footer', 'h1', 'h2', 'form', 'aside', '[role="dialog"]', 'table']) {
|
|
432
|
+
const el = document.querySelector(sel);
|
|
433
|
+
if (!el) continue;
|
|
434
|
+
const chain = chainFor(el);
|
|
435
|
+
if (chain) out.push(sel + ' \u2014 ' + chain);
|
|
436
|
+
if (out.length >= 10) break;
|
|
437
|
+
}
|
|
438
|
+
return out;
|
|
439
|
+
})()`;
|
|
403
440
|
async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, audit = false) {
|
|
404
441
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
405
442
|
const report = { consoleErrors: [], pageErrors: [], failedRequests: [] };
|
|
@@ -410,6 +447,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
410
447
|
let narration = [];
|
|
411
448
|
let phantoms = [];
|
|
412
449
|
let viewTransitions = [];
|
|
450
|
+
let components = [];
|
|
413
451
|
const requests = /* @__PURE__ */ new Map();
|
|
414
452
|
let connection = null;
|
|
415
453
|
try {
|
|
@@ -500,6 +538,15 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
500
538
|
if (Array.isArray(result?.value)) viewTransitions = result.value.map(String);
|
|
501
539
|
} catch {
|
|
502
540
|
}
|
|
541
|
+
try {
|
|
542
|
+
const { result } = await connection.send(
|
|
543
|
+
"Runtime.evaluate",
|
|
544
|
+
{ expression: FIBER_AUDIT, returnByValue: true },
|
|
545
|
+
sessionId
|
|
546
|
+
);
|
|
547
|
+
if (Array.isArray(result?.value)) components = result.value.map(String);
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
503
550
|
try {
|
|
504
551
|
await connection.send("Accessibility.enable", {}, sessionId);
|
|
505
552
|
const { nodes } = await connection.send("Accessibility.getFullAXTree", {}, sessionId);
|
|
@@ -564,7 +611,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
564
611
|
child.kill("SIGKILL");
|
|
565
612
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
566
613
|
}
|
|
567
|
-
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions };
|
|
614
|
+
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components };
|
|
568
615
|
}
|
|
569
616
|
|
|
570
617
|
export {
|
|
@@ -156,6 +156,11 @@ ${locks.map((l) => `- ${l}`).join("\n")}
|
|
|
156
156
|
If the task appears to require changing them, stop and explain instead.`
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
|
+
parts.push(
|
|
160
|
+
`## Requesting visual approval
|
|
161
|
+
|
|
162
|
+
For a visual decision you should not make alone (a redesign direction, removing something deliberate, reversing a design decision on record): write .squint/approval-request.json containing {"summary": "<one line>", "screenshot": "<path, optional>"} and end your turn immediately without making the change. The user's verdict arrives as the next message.`
|
|
163
|
+
);
|
|
159
164
|
const matched = matchSkills(loadSkills(cwd), ask);
|
|
160
165
|
for (const skill of matched) {
|
|
161
166
|
parts.push(`## Project notes: ${skill.name}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
completeCommand
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-43NQNIJY.js";
|
|
5
5
|
import {
|
|
6
6
|
applySandbox,
|
|
7
7
|
discardSandbox,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
comparePulse,
|
|
43
43
|
probeRuntime,
|
|
44
44
|
runtimeSummary
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-CV5WVKHU.js";
|
|
46
46
|
import {
|
|
47
47
|
clearState,
|
|
48
48
|
loadState,
|
|
@@ -58,11 +58,11 @@ import {
|
|
|
58
58
|
detectEngines,
|
|
59
59
|
getEngine
|
|
60
60
|
} from "./chunk-KVYGPLWW.js";
|
|
61
|
-
import "./chunk-
|
|
61
|
+
import "./chunk-RQHOE5MV.js";
|
|
62
62
|
import {
|
|
63
63
|
appendDecision,
|
|
64
64
|
enrich
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-ZLEP2TWF.js";
|
|
66
66
|
import "./chunk-K5QJMSJH.js";
|
|
67
67
|
import {
|
|
68
68
|
composePrompt
|
|
@@ -240,7 +240,7 @@ function registerEnv(program2) {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
const { findChrome: findChrome2 } = await import("./chrome-SBV3H77F.js");
|
|
243
|
-
const { hasWebSocket } = await import("./cdp-
|
|
243
|
+
const { hasWebSocket } = await import("./cdp-Q4H6ZHPT.js");
|
|
244
244
|
const chrome = findChrome2();
|
|
245
245
|
console.log(
|
|
246
246
|
chrome ? `${pc2.green("\u2713")} Chrome ${pc2.dim(chrome)}` : `${pc2.yellow("\u25CB")} Chrome ${pc2.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -264,7 +264,7 @@ import pc3 from "picocolors";
|
|
|
264
264
|
function registerProject(program2) {
|
|
265
265
|
const skillsCommand = program2.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
|
|
266
266
|
skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
|
|
267
|
-
const { loadRules, loadSkills } = await import("./skills-
|
|
267
|
+
const { loadRules, loadSkills } = await import("./skills-6NZP67QT.js");
|
|
268
268
|
const cwd = process.cwd();
|
|
269
269
|
const rules = loadRules(cwd);
|
|
270
270
|
console.log(
|
|
@@ -280,22 +280,22 @@ function registerProject(program2) {
|
|
|
280
280
|
}
|
|
281
281
|
});
|
|
282
282
|
skillsCommand.command("init").description("Scaffold .squint/rules.md and an example skill").action(async () => {
|
|
283
|
-
const
|
|
283
|
+
const fs4 = await import("fs");
|
|
284
284
|
const nodePath = await import("path");
|
|
285
285
|
const cwd = process.cwd();
|
|
286
286
|
const skillsDir = nodePath.join(cwd, ".squint", "skills");
|
|
287
|
-
|
|
287
|
+
fs4.mkdirSync(skillsDir, { recursive: true });
|
|
288
288
|
const rules = nodePath.join(cwd, ".squint", "rules.md");
|
|
289
|
-
if (!
|
|
290
|
-
|
|
289
|
+
if (!fs4.existsSync(rules)) {
|
|
290
|
+
fs4.writeFileSync(
|
|
291
291
|
rules,
|
|
292
292
|
"# Project rules\n\nThese ride along on every squint ask. Keep them short \u2014 cut anything that would not cause a mistake if removed.\n"
|
|
293
293
|
);
|
|
294
294
|
console.log(pc3.green("\u2713 .squint/rules.md"));
|
|
295
295
|
}
|
|
296
296
|
const example = nodePath.join(skillsDir, "example.md");
|
|
297
|
-
if (!
|
|
298
|
-
|
|
297
|
+
if (!fs4.existsSync(example)) {
|
|
298
|
+
fs4.writeFileSync(
|
|
299
299
|
example,
|
|
300
300
|
"---\ntriggers: example, sample\n---\n\nThis note is injected only when an ask mentions one of the triggers above.\nDocument the parts of this repo an agent would otherwise rediscover every time:\nwhere state lives, which helpers to reuse, what not to touch.\n"
|
|
301
301
|
);
|
|
@@ -304,7 +304,7 @@ function registerProject(program2) {
|
|
|
304
304
|
console.log(pc3.dim("rules are always-on; skills inject when an ask mentions a trigger"));
|
|
305
305
|
});
|
|
306
306
|
program2.command("brief").description("Set a committed design direction for this project (.squint/brief.md)").argument("[family]", "aesthetic family id (omit to list)").option("--force", "overwrite an existing project brief").action(async (familyId, options) => {
|
|
307
|
-
const
|
|
307
|
+
const fs4 = await import("fs");
|
|
308
308
|
const nodePath = await import("path");
|
|
309
309
|
const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-3ARYRBMH.js");
|
|
310
310
|
if (!familyId) {
|
|
@@ -322,13 +322,13 @@ function registerProject(program2) {
|
|
|
322
322
|
return;
|
|
323
323
|
}
|
|
324
324
|
const target = nodePath.join(process.cwd(), ".squint", "brief.md");
|
|
325
|
-
if (
|
|
325
|
+
if (fs4.existsSync(target) && !options.force) {
|
|
326
326
|
console.error(pc3.red(`\u2717 ${target} exists \u2014 use --force to overwrite`));
|
|
327
327
|
process.exitCode = 1;
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
fs4.mkdirSync(nodePath.dirname(target), { recursive: true });
|
|
331
|
+
fs4.writeFileSync(target, renderFamilyBrief(family) + "\n");
|
|
332
332
|
console.log(pc3.green(`\u2713 ${family.name} direction written to .squint/brief.md`));
|
|
333
333
|
console.log(pc3.dim("every squint ask in this repo now holds this direction \u2014 edit the file to remix"));
|
|
334
334
|
});
|
|
@@ -463,7 +463,7 @@ function registerQuality(program2) {
|
|
|
463
463
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
464
464
|
});
|
|
465
465
|
program2.command("shot").description("Screenshot a running app at mobile/tablet/desktop viewports (+ .squint/routes)").argument("<url>", "URL of the running app (e.g. http://localhost:5173)").action(async (url) => {
|
|
466
|
-
const { captureViewports: captureViewports2 } = await import("./preview-
|
|
466
|
+
const { captureViewports: captureViewports2 } = await import("./preview-ZUCVWGZG.js");
|
|
467
467
|
const result = await captureViewports2(process.cwd(), url);
|
|
468
468
|
if (!result) {
|
|
469
469
|
console.error(pc4.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -581,21 +581,21 @@ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
581
581
|
console.log(pc6.dim("then describe what to build \u2014 /dev starts the preview server"));
|
|
582
582
|
});
|
|
583
583
|
program2.command("tag").description("Add the element picker to this Vite app (Alt+S in the browser \u2192 click \u2192 file:line:col)").action(async () => {
|
|
584
|
-
const
|
|
584
|
+
const fs4 = await import("fs");
|
|
585
585
|
const nodePath = await import("path");
|
|
586
586
|
const { patchViteConfig, TAGGER_FILENAME, TAGGER_SOURCE } = await import("./source-ZZU245VN.js");
|
|
587
587
|
const cwd = process.cwd();
|
|
588
588
|
const taggerPath = nodePath.join(cwd, TAGGER_FILENAME);
|
|
589
|
-
|
|
589
|
+
fs4.writeFileSync(taggerPath, TAGGER_SOURCE);
|
|
590
590
|
console.log(pc6.green(`\u2713 ${TAGGER_FILENAME} written`));
|
|
591
|
-
const configPath = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].map((name) => nodePath.join(cwd, name)).find((candidate) =>
|
|
591
|
+
const configPath = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].map((name) => nodePath.join(cwd, name)).find((candidate) => fs4.existsSync(candidate));
|
|
592
592
|
if (!configPath) {
|
|
593
593
|
console.log(pc6.yellow("\u25CB no vite config found \u2014 add the plugin manually:"));
|
|
594
594
|
console.log(pc6.dim(` import squintTagger from './${TAGGER_FILENAME}'
|
|
595
595
|
plugins: [squintTagger(), \u2026]`));
|
|
596
596
|
return;
|
|
597
597
|
}
|
|
598
|
-
const source =
|
|
598
|
+
const source = fs4.readFileSync(configPath, "utf8");
|
|
599
599
|
const patched = patchViteConfig(source);
|
|
600
600
|
if (patched === "already") {
|
|
601
601
|
console.log(pc6.dim("vite config already wired"));
|
|
@@ -604,7 +604,7 @@ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
604
604
|
console.log(pc6.dim(` import squintTagger from './${TAGGER_FILENAME}'
|
|
605
605
|
plugins: [squintTagger(), \u2026]`));
|
|
606
606
|
} else {
|
|
607
|
-
|
|
607
|
+
fs4.writeFileSync(configPath, patched);
|
|
608
608
|
console.log(pc6.green(`\u2713 ${nodePath.basename(configPath)} wired`));
|
|
609
609
|
}
|
|
610
610
|
console.log(
|
|
@@ -623,6 +623,7 @@ import path4 from "path";
|
|
|
623
623
|
import { useMemo, useRef, useState as useState2, useSyncExternalStore } from "react";
|
|
624
624
|
|
|
625
625
|
// src/session/engine.ts
|
|
626
|
+
import fs3 from "fs";
|
|
626
627
|
import path3 from "path";
|
|
627
628
|
|
|
628
629
|
// src/session/hooks.ts
|
|
@@ -711,6 +712,7 @@ var Session = class {
|
|
|
711
712
|
checkpoints = [];
|
|
712
713
|
fixAttempts = 0;
|
|
713
714
|
reviewTipShown = false;
|
|
715
|
+
pendingApproval = null;
|
|
714
716
|
lastPulse = null;
|
|
715
717
|
lastPerf = null;
|
|
716
718
|
autoReviewedThisAsk = false;
|
|
@@ -825,7 +827,7 @@ ${question}`,
|
|
|
825
827
|
return;
|
|
826
828
|
}
|
|
827
829
|
await this.runTurn(
|
|
828
|
-
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions),
|
|
830
|
+
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components),
|
|
829
831
|
`\u{1F441} polish round ${round}/${rounds}`
|
|
830
832
|
);
|
|
831
833
|
}
|
|
@@ -1041,6 +1043,21 @@ ${question}`,
|
|
|
1041
1043
|
const stat = checkpoint ? diffStatSince(this.opts.cwd, checkpoint.snapshot) : null;
|
|
1042
1044
|
const work = stat ? ` \xB7 ${stat}` : this.turnEdits > 0 ? ` \xB7 ${this.turnEdits} edit${this.turnEdits === 1 ? "" : "s"}` : this.turnTools > 0 ? ` \xB7 ${this.turnTools} tool call${this.turnTools === 1 ? "" : "s"}` : "";
|
|
1043
1045
|
this.push("status", `done${secs}${cost}${work}`);
|
|
1046
|
+
try {
|
|
1047
|
+
const reqPath = path3.join(this.opts.cwd, ".squint", "approval-request.json");
|
|
1048
|
+
if (fs3.existsSync(reqPath)) {
|
|
1049
|
+
const req = JSON.parse(fs3.readFileSync(reqPath, "utf8"));
|
|
1050
|
+
fs3.rmSync(reqPath, { force: true });
|
|
1051
|
+
if (typeof req?.summary === "string" && req.summary.length > 0) {
|
|
1052
|
+
this.pendingApproval = req.summary;
|
|
1053
|
+
const shot = typeof req.screenshot === "string" ? path3.resolve(this.execCwd(), req.screenshot) : null;
|
|
1054
|
+
if (shot && fs3.existsSync(shot)) this.push("image", shot);
|
|
1055
|
+
this.push("status", `\u23F8 approval requested: ${req.summary}
|
|
1056
|
+
/yes approves \xB7 /no rejects \xB7 or type feedback`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
} catch {
|
|
1060
|
+
}
|
|
1044
1061
|
if (checkpoint) {
|
|
1045
1062
|
try {
|
|
1046
1063
|
const { driftSummary, loadTokenIndex, scanDrift } = await import("./tokens-XYGRRAXC.js");
|
|
@@ -1175,7 +1192,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1175
1192
|
const captureResult = await this.capture();
|
|
1176
1193
|
if (captureResult) {
|
|
1177
1194
|
await this.runTurn(
|
|
1178
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions),
|
|
1195
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components),
|
|
1179
1196
|
"\u{1F441} auto-review rendered UI"
|
|
1180
1197
|
);
|
|
1181
1198
|
}
|
|
@@ -1463,13 +1480,32 @@ They are objective defects, not style preferences.`
|
|
|
1463
1480
|
break;
|
|
1464
1481
|
}
|
|
1465
1482
|
case "context": {
|
|
1466
|
-
import("./contextDoctor-
|
|
1483
|
+
import("./contextDoctor-U3YTDFVG.js").then(({ contextReport, formatContextReport }) => {
|
|
1467
1484
|
this.push("status", formatContextReport(contextReport(this.execCwd())));
|
|
1468
1485
|
}).catch((error) => {
|
|
1469
1486
|
this.push("status", `context report failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1470
1487
|
});
|
|
1471
1488
|
break;
|
|
1472
1489
|
}
|
|
1490
|
+
case "yes":
|
|
1491
|
+
case "no": {
|
|
1492
|
+
if (!this.pendingApproval) {
|
|
1493
|
+
this.push("status", `nothing awaiting approval \u2014 /${name} answers an engine's approval request`);
|
|
1494
|
+
break;
|
|
1495
|
+
}
|
|
1496
|
+
const summary = this.pendingApproval;
|
|
1497
|
+
this.pendingApproval = null;
|
|
1498
|
+
const approved = name === "yes";
|
|
1499
|
+
appendDecision(this.opts.cwd, {
|
|
1500
|
+
decision: `${approved ? "approved" : "rejected"}: ${summary}${arg ? ` \u2014 ${arg}` : ""}`,
|
|
1501
|
+
source: "approval"
|
|
1502
|
+
});
|
|
1503
|
+
void this.runTurn(
|
|
1504
|
+
approved ? `Approved: "${summary}". Proceed.${arg ? ` Note from the user: ${arg}` : ""}` : `Rejected: "${summary}". Do not proceed with it.${arg ? ` The user says: ${arg}` : " Stop and await direction."}`,
|
|
1505
|
+
approved ? `\u2713 approved${arg ? ` \u2014 ${arg}` : ""}` : `\u2717 rejected${arg ? ` \u2014 ${arg}` : ""}`
|
|
1506
|
+
);
|
|
1507
|
+
break;
|
|
1508
|
+
}
|
|
1473
1509
|
case "decide": {
|
|
1474
1510
|
if (!arg) {
|
|
1475
1511
|
this.push("status", "usage: /decide <the decision> \u2014 recorded in .squint/design-log.jsonl and injected into every future ask");
|
|
@@ -1500,13 +1536,13 @@ They are objective defects, not style preferences.`
|
|
|
1500
1536
|
if (matches.length < 8) {
|
|
1501
1537
|
void (async () => {
|
|
1502
1538
|
try {
|
|
1503
|
-
const
|
|
1539
|
+
const fs4 = await import("fs");
|
|
1504
1540
|
const path5 = await import("path");
|
|
1505
1541
|
const dir = path5.join(this.opts.cwd, ".squint", "transcripts");
|
|
1506
|
-
const files =
|
|
1542
|
+
const files = fs4.readdirSync(dir).filter((f) => f.endsWith(".md")).sort().reverse().slice(0, 20);
|
|
1507
1543
|
for (const file of files) {
|
|
1508
1544
|
if (matches.length >= 8) break;
|
|
1509
|
-
const text =
|
|
1545
|
+
const text = fs4.readFileSync(path5.join(dir, file), "utf8");
|
|
1510
1546
|
for (const line of text.split("\n")) {
|
|
1511
1547
|
if (line.toLowerCase().includes(needle)) {
|
|
1512
1548
|
matches.push(`[${file.replace(/\.md$/, "")}] ${line.trim().slice(0, 90)}`);
|
|
@@ -1529,10 +1565,10 @@ They are objective defects, not style preferences.`
|
|
|
1529
1565
|
}
|
|
1530
1566
|
case "save": {
|
|
1531
1567
|
void (async () => {
|
|
1532
|
-
const
|
|
1568
|
+
const fs4 = await import("fs");
|
|
1533
1569
|
const path5 = await import("path");
|
|
1534
1570
|
const dir = path5.join(this.opts.cwd, ".squint", "transcripts");
|
|
1535
|
-
|
|
1571
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1536
1572
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
1537
1573
|
const file = path5.join(dir, `${stamp}.md`);
|
|
1538
1574
|
const lines = [`# squint session \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`, ""];
|
|
@@ -1557,7 +1593,7 @@ They are objective defects, not style preferences.`
|
|
|
1557
1593
|
}
|
|
1558
1594
|
}
|
|
1559
1595
|
lines.push("", `> ${this.summary()}`);
|
|
1560
|
-
|
|
1596
|
+
fs4.writeFileSync(file, lines.join("\n") + "\n");
|
|
1561
1597
|
this.push("status", `saved transcript \u2192 ${path5.relative(this.opts.cwd, file)}`);
|
|
1562
1598
|
})();
|
|
1563
1599
|
break;
|
|
@@ -1579,8 +1615,8 @@ They are objective defects, not style preferences.`
|
|
|
1579
1615
|
this.push("error", "no Chrome/Chromium found for flows");
|
|
1580
1616
|
return;
|
|
1581
1617
|
}
|
|
1582
|
-
const { runFlow } = await import("./cdp-
|
|
1583
|
-
const { previewDir } = await import("./preview-
|
|
1618
|
+
const { runFlow } = await import("./cdp-Q4H6ZHPT.js");
|
|
1619
|
+
const { previewDir } = await import("./preview-ZUCVWGZG.js");
|
|
1584
1620
|
this.push("status", `replaying ${flows.length} flow(s)\u2026`);
|
|
1585
1621
|
this.notify({ running: true, runStartedAt: Date.now() });
|
|
1586
1622
|
const failures = [];
|
|
@@ -1718,7 +1754,7 @@ They are objective defects, not style preferences.`
|
|
|
1718
1754
|
const result = await this.capture();
|
|
1719
1755
|
if (result) {
|
|
1720
1756
|
await this.runTurn(
|
|
1721
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions),
|
|
1757
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components),
|
|
1722
1758
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1723
1759
|
);
|
|
1724
1760
|
}
|
|
@@ -1904,7 +1940,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1904
1940
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1905
1941
|
break;
|
|
1906
1942
|
case "help": {
|
|
1907
|
-
void import("./commands-
|
|
1943
|
+
void import("./commands-BY44HDQ6.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1908
1944
|
break;
|
|
1909
1945
|
}
|
|
1910
1946
|
case "quit":
|
|
@@ -2523,7 +2559,7 @@ function registerTui(program2) {
|
|
|
2523
2559
|
}
|
|
2524
2560
|
|
|
2525
2561
|
// src/cli.tsx
|
|
2526
|
-
var VERSION = true ? "0.4.
|
|
2562
|
+
var VERSION = true ? "0.4.5" : "0.0.0-dev";
|
|
2527
2563
|
var program = new Command();
|
|
2528
2564
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2529
2565
|
registerRun(program);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
loadLocks,
|
|
8
8
|
loadRules,
|
|
9
9
|
loadSkills
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZLEP2TWF.js";
|
|
11
11
|
import {
|
|
12
12
|
loadBrief
|
|
13
13
|
} from "./chunk-WAJXATCO.js";
|
|
@@ -54,6 +54,7 @@ function contextReport(cwd) {
|
|
|
54
54
|
warnings.push(`skill "${skill.name}" has trigger(s) ${generic.map((t) => `"${t}"`).join(", ")} short enough to match almost any ask \u2014 make them more specific`);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
lines.push({ source: "approval protocol (built-in)", tokens: 70, when: "every ask" });
|
|
57
58
|
const totalAlways = lines.filter((l) => l.when === "every ask").reduce((sum, l) => sum + l.tokens, 0);
|
|
58
59
|
if (totalAlways > 3e3) warnings.push(`~${totalAlways} tokens ride on every single ask \u2014 that is real money and real attention; trim the always-on set`);
|
|
59
60
|
return { lines, warnings, totalAlways };
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
probeRuntime,
|
|
11
11
|
routeShotName,
|
|
12
12
|
runtimeSummary
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CV5WVKHU.js";
|
|
14
14
|
import "./chunk-O2S6PAJE.js";
|
|
15
15
|
import "./chunk-IMDRXXFU.js";
|
|
16
16
|
import "./chunk-KVYGPLWW.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-RQHOE5MV.js";
|
|
18
18
|
export {
|
|
19
19
|
VIEWPORTS,
|
|
20
20
|
buildReviewPrompt,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aayambansal/squint",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Lovable for your terminal
|
|
3
|
+
"version": "0.4.5",
|
|
4
|
+
"description": "Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and other coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Aayam Bansal",
|