@aayambansal/squint 0.4.2 → 0.4.3
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 +6 -1
- package/dist/{chunk-P3H4N2EN.js → chunk-K5QJMSJH.js} +3 -58
- package/dist/{chunk-S3WV5C3X.js → chunk-O7NWGLS3.js} +1 -0
- package/dist/{chunk-F6JZSXEO.js → chunk-QCHBDP46.js} +1 -1
- package/dist/chunk-WAJXATCO.js +64 -0
- package/dist/{chunk-A64VVTPU.js → chunk-YGSF2TSO.js} +4 -2
- package/dist/{chunk-KGZILC3S.js → chunk-YT6K2YIS.js} +4 -0
- package/dist/cli.js +43 -14
- package/dist/{commands-B6I6C5LP.js → commands-VDICJJT2.js} +1 -1
- package/dist/contextDoctor-M3R3PICP.js +73 -0
- package/dist/{families-2G5SLLY7.js → families-3ARYRBMH.js} +2 -1
- package/dist/rulepacks-JOA675OJ.js +135 -0
- package/dist/{shots-E2AWBDCN.js → shots-OKWOYF7F.js} +4 -3
- package/dist/{skills-U2J7Z3B2.js → skills-GTTF4PNU.js} +1 -1
- package/dist/{variants-3ZSINJGX.js → variants-3IEP7DFY.js} +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -167,7 +167,12 @@ squint doctor --probe # run every engine end to end, verify auth act
|
|
|
167
167
|
pixel-compared with the last (drift as a number), load performance is tracked with
|
|
168
168
|
deltas (`perf: LCP 812ms (+420ms)`), hardcoded colors get pointed at the nearest
|
|
169
169
|
design token, and the mechanical anti-slop sweep flags generic-AI tells as
|
|
170
|
-
distinctiveness debt in `/review`.
|
|
170
|
+
distinctiveness debt in `/review`. The phantom-class check diffs every DOM class
|
|
171
|
+
against the compiled CSS — hallucinated utilities surface as named problems instead
|
|
172
|
+
of silently unstyled elements.
|
|
173
|
+
- **The design ledger**: `/decide` (plus chosen variants, rollbacks, accepted
|
|
174
|
+
sandboxes) appends to a committed `.squint/design-log.jsonl`; recent decisions ride
|
|
175
|
+
into every ask so they stop getting silently undone between sessions.
|
|
171
176
|
- **`/btw <question>`** asks about the codebase read-only without touching the main
|
|
172
177
|
thread's context. `.squint/locks` lists paths the engine must never touch; `/save`
|
|
173
178
|
exports the transcript as markdown.
|
|
@@ -1,60 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import path from "path";
|
|
6
|
-
var DEFAULT_BRIEF = `You are acting as a senior product designer and frontend engineer. Treat every change as production work, not a demo.
|
|
7
|
-
|
|
8
|
-
Direction before code:
|
|
9
|
-
- Commit to one specific visual direction derived from what this product is for, and state it in one sentence before implementing. If the direction could be guessed from the product category alone, sharpen it until it couldn't.
|
|
10
|
-
- Choose a color strategy deliberately: restrained (neutrals + one accent), committed (one dominant color owning 30\u201360% of the surface), or a small palette of 3\u20135 named roles. Never purple/violet gradients unless asked. Body text contrast stays at least 4.5:1 \u2014 no light gray body copy "for elegance".
|
|
11
|
-
- Typography: at most two families, paired on contrast (display serif + geometric sans, or sans + mono). Never Inter/Roboto/Open Sans/system defaults for display type. Pair weight extremes (300 against 700\u2013900); display sizes jump 3x over body, not 1.5x.
|
|
12
|
-
|
|
13
|
-
Tokens are the system:
|
|
14
|
-
- Define or extend design tokens (CSS variables / theme config) first, then compose the UI from them. Never scatter literal colors or one-off spacing values through components.
|
|
15
|
-
- One spacing rhythm on a 4/8px grid; one type scale with a ratio of at least 1.25 between steps.
|
|
16
|
-
|
|
17
|
-
Banned tells \u2014 these read instantly as machine-generated:
|
|
18
|
-
- Purple gradient on white; glassmorphism cards; cream/beige page background as a reflex "warmth" move
|
|
19
|
-
- Emoji as icons; identical icon-topped card grids; stat banner rows; numbered 01/02/03 section markers
|
|
20
|
-
- Tiny all-caps tracked eyebrow labels over every section; gradient text; colored left-border strips on cards
|
|
21
|
-
- Centered hero + badge + three feature cards; unmodified component-library defaults
|
|
22
|
-
If someone could look at the result and say "AI made that" without doubt, it has failed.
|
|
23
|
-
|
|
24
|
-
Craft details:
|
|
25
|
-
- Every interactive element gets hover, focus-visible, and active treatment; keyboard focus stays visible.
|
|
26
|
-
- Anything that loads data gets loading, empty, and error states.
|
|
27
|
-
- Motion: 150\u2013250ms ease-out; one orchestrated entrance with staggered reveals beats scattered micro-interactions; honor prefers-reduced-motion; never leave content invisible until a scroll observer fires.
|
|
28
|
-
- Body line length 65\u201375ch; text-wrap: balance on headings.
|
|
29
|
-
|
|
30
|
-
Engineering:
|
|
31
|
-
- Follow the repo's existing conventions and extend its patterns; new components in new files, small and focused; semantic HTML.
|
|
32
|
-
- Responsive from 360px up with no horizontal overflow \u2014 check intermediate widths, not just phone and desktop.
|
|
33
|
-
- Let errors surface instead of swallowing them in try/catch; log clearly so failures can be traced.
|
|
34
|
-
- Not done until the app builds cleanly, typechecks, and renders without console errors.`;
|
|
35
|
-
var FIRST_TURN_ADDENDUM = `This is the opening move on this task: establish the design foundation before building. State the visual direction, set up the tokens/theme first, then build components from them. The first render should feel like a designed product, not a scaffold \u2014 impressive on sight.`;
|
|
36
|
-
function loadBrief(cwd) {
|
|
37
|
-
const custom = path.join(cwd, ".squint", "brief.md");
|
|
38
|
-
try {
|
|
39
|
-
const text = fs.readFileSync(custom, "utf8").trim();
|
|
40
|
-
if (text.length > 0) return text;
|
|
41
|
-
} catch {
|
|
42
|
-
}
|
|
43
|
-
return DEFAULT_BRIEF;
|
|
44
|
-
}
|
|
45
|
-
function composePrompt(ask, opts) {
|
|
46
|
-
if (opts.noBrief) return ask;
|
|
47
|
-
const brief = loadBrief(opts.cwd);
|
|
48
|
-
const firstTurn = opts.firstTurn ?? true;
|
|
49
|
-
const addendum = firstTurn ? `
|
|
50
|
-
|
|
51
|
-
${FIRST_TURN_ADDENDUM}` : "";
|
|
52
|
-
return `${brief}${addendum}
|
|
53
|
-
|
|
54
|
-
## Task
|
|
55
|
-
|
|
56
|
-
${ask}`;
|
|
57
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_BRIEF
|
|
4
|
+
} from "./chunk-WAJXATCO.js";
|
|
58
5
|
|
|
59
6
|
// src/prompt/families.ts
|
|
60
7
|
var FAMILIES = [
|
|
@@ -174,8 +121,6 @@ ${coreStandards()}`;
|
|
|
174
121
|
}
|
|
175
122
|
|
|
176
123
|
export {
|
|
177
|
-
FIRST_TURN_ADDENDUM,
|
|
178
|
-
composePrompt,
|
|
179
124
|
FAMILIES,
|
|
180
125
|
getFamily,
|
|
181
126
|
renderFamilyBrief
|
|
@@ -27,6 +27,7 @@ var COMMANDS = [
|
|
|
27
27
|
{ name: "save", group: "session", description: "export the transcript to .squint/transcripts/" },
|
|
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
|
+
{ name: "context", group: "session", description: "what squint injects per ask, token-costed, with staleness warnings" },
|
|
30
31
|
{ name: "resume", group: "session", description: "pick up the previous session for this repo" },
|
|
31
32
|
{ name: "clear", group: "session", description: "new session (transcript, totals, persisted state)" },
|
|
32
33
|
{ name: "help", group: "session", description: "list commands" },
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/prompt/brief.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
var DEFAULT_BRIEF = `You are acting as a senior product designer and frontend engineer. Treat every change as production work, not a demo.
|
|
7
|
+
|
|
8
|
+
Direction before code:
|
|
9
|
+
- Commit to one specific visual direction derived from what this product is for, and state it in one sentence before implementing. If the direction could be guessed from the product category alone, sharpen it until it couldn't.
|
|
10
|
+
- Choose a color strategy deliberately: restrained (neutrals + one accent), committed (one dominant color owning 30\u201360% of the surface), or a small palette of 3\u20135 named roles. Never purple/violet gradients unless asked. Body text contrast stays at least 4.5:1 \u2014 no light gray body copy "for elegance".
|
|
11
|
+
- Typography: at most two families, paired on contrast (display serif + geometric sans, or sans + mono). Never Inter/Roboto/Open Sans/system defaults for display type. Pair weight extremes (300 against 700\u2013900); display sizes jump 3x over body, not 1.5x.
|
|
12
|
+
|
|
13
|
+
Tokens are the system:
|
|
14
|
+
- Define or extend design tokens (CSS variables / theme config) first, then compose the UI from them. Never scatter literal colors or one-off spacing values through components.
|
|
15
|
+
- One spacing rhythm on a 4/8px grid; one type scale with a ratio of at least 1.25 between steps.
|
|
16
|
+
|
|
17
|
+
Banned tells \u2014 these read instantly as machine-generated:
|
|
18
|
+
- Purple gradient on white; glassmorphism cards; cream/beige page background as a reflex "warmth" move
|
|
19
|
+
- Emoji as icons; identical icon-topped card grids; stat banner rows; numbered 01/02/03 section markers
|
|
20
|
+
- Tiny all-caps tracked eyebrow labels over every section; gradient text; colored left-border strips on cards
|
|
21
|
+
- Centered hero + badge + three feature cards; unmodified component-library defaults
|
|
22
|
+
If someone could look at the result and say "AI made that" without doubt, it has failed.
|
|
23
|
+
|
|
24
|
+
Craft details:
|
|
25
|
+
- Every interactive element gets hover, focus-visible, and active treatment; keyboard focus stays visible.
|
|
26
|
+
- Anything that loads data gets loading, empty, and error states.
|
|
27
|
+
- Motion: 150\u2013250ms ease-out; one orchestrated entrance with staggered reveals beats scattered micro-interactions; honor prefers-reduced-motion; never leave content invisible until a scroll observer fires.
|
|
28
|
+
- Body line length 65\u201375ch; text-wrap: balance on headings.
|
|
29
|
+
|
|
30
|
+
Engineering:
|
|
31
|
+
- Follow the repo's existing conventions and extend its patterns; new components in new files, small and focused; semantic HTML.
|
|
32
|
+
- Responsive from 360px up with no horizontal overflow \u2014 check intermediate widths, not just phone and desktop.
|
|
33
|
+
- Let errors surface instead of swallowing them in try/catch; log clearly so failures can be traced.
|
|
34
|
+
- Not done until the app builds cleanly, typechecks, and renders without console errors.`;
|
|
35
|
+
var FIRST_TURN_ADDENDUM = `This is the opening move on this task: establish the design foundation before building. State the visual direction, set up the tokens/theme first, then build components from them. The first render should feel like a designed product, not a scaffold \u2014 impressive on sight.`;
|
|
36
|
+
function loadBrief(cwd) {
|
|
37
|
+
const custom = path.join(cwd, ".squint", "brief.md");
|
|
38
|
+
try {
|
|
39
|
+
const text = fs.readFileSync(custom, "utf8").trim();
|
|
40
|
+
if (text.length > 0) return text;
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
return DEFAULT_BRIEF;
|
|
44
|
+
}
|
|
45
|
+
function composePrompt(ask, opts) {
|
|
46
|
+
if (opts.noBrief) return ask;
|
|
47
|
+
const brief = loadBrief(opts.cwd);
|
|
48
|
+
const firstTurn = opts.firstTurn ?? true;
|
|
49
|
+
const addendum = firstTurn ? `
|
|
50
|
+
|
|
51
|
+
${FIRST_TURN_ADDENDUM}` : "";
|
|
52
|
+
return `${brief}${addendum}
|
|
53
|
+
|
|
54
|
+
## Task
|
|
55
|
+
|
|
56
|
+
${ask}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
DEFAULT_BRIEF,
|
|
61
|
+
FIRST_TURN_ADDENDUM,
|
|
62
|
+
loadBrief,
|
|
63
|
+
composePrompt
|
|
64
|
+
};
|
|
@@ -4,9 +4,11 @@ import {
|
|
|
4
4
|
} from "./chunk-VH7OOFQP.js";
|
|
5
5
|
import {
|
|
6
6
|
FAMILIES,
|
|
7
|
-
FIRST_TURN_ADDENDUM,
|
|
8
7
|
renderFamilyBrief
|
|
9
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-K5QJMSJH.js";
|
|
9
|
+
import {
|
|
10
|
+
FIRST_TURN_ADDENDUM
|
|
11
|
+
} from "./chunk-WAJXATCO.js";
|
|
10
12
|
|
|
11
13
|
// src/variants/variants.ts
|
|
12
14
|
import { execFileSync } from "child_process";
|
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-O7NWGLS3.js";
|
|
5
5
|
import {
|
|
6
6
|
applySandbox,
|
|
7
7
|
discardSandbox,
|
|
@@ -22,13 +22,13 @@ import {
|
|
|
22
22
|
buildFixPrompt,
|
|
23
23
|
detectDevCommand,
|
|
24
24
|
screenshotVariants
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-QCHBDP46.js";
|
|
26
26
|
import {
|
|
27
27
|
applyVariant,
|
|
28
28
|
cleanVariants,
|
|
29
29
|
listVariants,
|
|
30
30
|
runVariants
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-YGSF2TSO.js";
|
|
32
32
|
import {
|
|
33
33
|
buildGatePrompt,
|
|
34
34
|
detectFastGates,
|
|
@@ -62,10 +62,11 @@ import "./chunk-Y47X4ENN.js";
|
|
|
62
62
|
import {
|
|
63
63
|
appendDecision,
|
|
64
64
|
enrich
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-YT6K2YIS.js";
|
|
66
|
+
import "./chunk-K5QJMSJH.js";
|
|
66
67
|
import {
|
|
67
68
|
composePrompt
|
|
68
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-WAJXATCO.js";
|
|
69
70
|
|
|
70
71
|
// src/cli.tsx
|
|
71
72
|
import { Command } from "commander";
|
|
@@ -263,7 +264,7 @@ import pc3 from "picocolors";
|
|
|
263
264
|
function registerProject(program2) {
|
|
264
265
|
const skillsCommand = program2.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
|
|
265
266
|
skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
|
|
266
|
-
const { loadRules, loadSkills } = await import("./skills-
|
|
267
|
+
const { loadRules, loadSkills } = await import("./skills-GTTF4PNU.js");
|
|
267
268
|
const cwd = process.cwd();
|
|
268
269
|
const rules = loadRules(cwd);
|
|
269
270
|
console.log(
|
|
@@ -305,7 +306,7 @@ function registerProject(program2) {
|
|
|
305
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) => {
|
|
306
307
|
const fs3 = await import("fs");
|
|
307
308
|
const nodePath = await import("path");
|
|
308
|
-
const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-
|
|
309
|
+
const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-3ARYRBMH.js");
|
|
309
310
|
if (!familyId) {
|
|
310
311
|
console.log(pc3.bold("Aesthetic families") + pc3.dim(" \u2014 squint brief <id>\n"));
|
|
311
312
|
for (const family2 of FAMILIES) {
|
|
@@ -380,7 +381,7 @@ function registerProject(program2) {
|
|
|
380
381
|
process.exitCode = 1;
|
|
381
382
|
return;
|
|
382
383
|
}
|
|
383
|
-
const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-
|
|
384
|
+
const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-3IEP7DFY.js");
|
|
384
385
|
const config = loadConfig(defaultPaths(cwd));
|
|
385
386
|
const engineId = resolveEngineId(config, options.engine);
|
|
386
387
|
const engine = getEngine(engineId);
|
|
@@ -398,7 +399,7 @@ function registerProject(program2) {
|
|
|
398
399
|
);
|
|
399
400
|
const succeeded = runs.filter((r) => r.result.ok);
|
|
400
401
|
if (options.shots && succeeded.length > 0) {
|
|
401
|
-
const { screenshotVariants: screenshotVariants2 } = await import("./shots-
|
|
402
|
+
const { screenshotVariants: screenshotVariants2 } = await import("./shots-OKWOYF7F.js");
|
|
402
403
|
console.log(pc3.dim("capturing screenshots\u2026"));
|
|
403
404
|
const shots = await screenshotVariants2(cwd, succeeded.map((r) => r.variant));
|
|
404
405
|
for (const shot of shots) {
|
|
@@ -413,7 +414,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
413
414
|
}
|
|
414
415
|
);
|
|
415
416
|
variantsCommand.command("list").description("List generated variants").action(async () => {
|
|
416
|
-
const { listVariants: listVariants2 } = await import("./variants-
|
|
417
|
+
const { listVariants: listVariants2 } = await import("./variants-3IEP7DFY.js");
|
|
417
418
|
const ids = listVariants2(process.cwd());
|
|
418
419
|
if (ids.length === 0) {
|
|
419
420
|
console.log(pc3.dim('no variants \u2014 squint variants gen <n> "<ask>"'));
|
|
@@ -422,7 +423,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
422
423
|
for (const id of ids) console.log(id);
|
|
423
424
|
});
|
|
424
425
|
variantsCommand.command("apply").description("Apply one variant\u2019s changes to the main tree and discard the rest").argument("<id>", "family id of the winning variant").action(async (id) => {
|
|
425
|
-
const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-
|
|
426
|
+
const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-3IEP7DFY.js");
|
|
426
427
|
const cwd = process.cwd();
|
|
427
428
|
const result = applyVariant2(cwd, id);
|
|
428
429
|
if (!result.ok) {
|
|
@@ -434,7 +435,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
434
435
|
console.log(pc3.green(`\u2713 applied ${id} to the working tree`) + pc3.dim(" \u2014 review with git diff"));
|
|
435
436
|
});
|
|
436
437
|
variantsCommand.command("clean").description("Discard all variants").action(async () => {
|
|
437
|
-
const { cleanVariants: cleanVariants2 } = await import("./variants-
|
|
438
|
+
const { cleanVariants: cleanVariants2 } = await import("./variants-3IEP7DFY.js");
|
|
438
439
|
const count = cleanVariants2(process.cwd());
|
|
439
440
|
console.log(pc3.dim(`removed ${count} variant(s)`));
|
|
440
441
|
});
|
|
@@ -1051,6 +1052,26 @@ ${driftSummary(drift)}`);
|
|
|
1051
1052
|
}
|
|
1052
1053
|
} catch {
|
|
1053
1054
|
}
|
|
1055
|
+
try {
|
|
1056
|
+
const { rulePackSummary, scanRulePacks } = await import("./rulepacks-JOA675OJ.js");
|
|
1057
|
+
const findings = scanRulePacks(this.execCwd(), checkpoint.snapshot.stashHash ?? "HEAD");
|
|
1058
|
+
const hard = findings.filter((f) => f.hard);
|
|
1059
|
+
if (hard.length > 0) {
|
|
1060
|
+
this.addProblem(
|
|
1061
|
+
"gates",
|
|
1062
|
+
`${hard.length} class(es)/idiom(s) from an older toolchain major
|
|
1063
|
+
${rulePackSummary(hard)}`,
|
|
1064
|
+
`This project's toolchain is newer than the patterns just written. Apply the exact renames below \u2014 do not downgrade dependencies or add compatibility configs:
|
|
1065
|
+
${rulePackSummary(hard)}`
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
const soft = findings.filter((f) => !f.hard);
|
|
1069
|
+
if (soft.length > 0) {
|
|
1070
|
+
this.push("status", `renamed-scale traps (verify intent):
|
|
1071
|
+
${rulePackSummary(soft)}`);
|
|
1072
|
+
}
|
|
1073
|
+
} catch {
|
|
1074
|
+
}
|
|
1054
1075
|
}
|
|
1055
1076
|
runHook(this.opts.cwd, "on-turn-end", {
|
|
1056
1077
|
cost: String(result.costUsd ?? 0),
|
|
@@ -1411,6 +1432,14 @@ They are objective defects, not style preferences.`
|
|
|
1411
1432
|
}
|
|
1412
1433
|
break;
|
|
1413
1434
|
}
|
|
1435
|
+
case "context": {
|
|
1436
|
+
import("./contextDoctor-M3R3PICP.js").then(({ contextReport, formatContextReport }) => {
|
|
1437
|
+
this.push("status", formatContextReport(contextReport(this.execCwd())));
|
|
1438
|
+
}).catch((error) => {
|
|
1439
|
+
this.push("status", `context report failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1440
|
+
});
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1414
1443
|
case "decide": {
|
|
1415
1444
|
if (!arg) {
|
|
1416
1445
|
this.push("status", "usage: /decide <the decision> \u2014 recorded in .squint/design-log.jsonl and injected into every future ask");
|
|
@@ -1845,7 +1874,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1845
1874
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1846
1875
|
break;
|
|
1847
1876
|
case "help": {
|
|
1848
|
-
void import("./commands-
|
|
1877
|
+
void import("./commands-VDICJJT2.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1849
1878
|
break;
|
|
1850
1879
|
}
|
|
1851
1880
|
case "quit":
|
|
@@ -2464,7 +2493,7 @@ function registerTui(program2) {
|
|
|
2464
2493
|
}
|
|
2465
2494
|
|
|
2466
2495
|
// src/cli.tsx
|
|
2467
|
-
var VERSION = true ? "0.4.
|
|
2496
|
+
var VERSION = true ? "0.4.3" : "0.0.0-dev";
|
|
2468
2497
|
var program = new Command();
|
|
2469
2498
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2470
2499
|
registerRun(program);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
decisionsSection,
|
|
4
|
+
inventorySection,
|
|
5
|
+
loadComponentInventory,
|
|
6
|
+
loadDecisions,
|
|
7
|
+
loadLocks,
|
|
8
|
+
loadRules,
|
|
9
|
+
loadSkills
|
|
10
|
+
} from "./chunk-YT6K2YIS.js";
|
|
11
|
+
import {
|
|
12
|
+
loadBrief
|
|
13
|
+
} from "./chunk-WAJXATCO.js";
|
|
14
|
+
|
|
15
|
+
// src/quality/contextDoctor.ts
|
|
16
|
+
import fs from "fs";
|
|
17
|
+
import path from "path";
|
|
18
|
+
var tokens = (text) => Math.ceil(text.length / 4);
|
|
19
|
+
function contextReport(cwd) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
const brief = loadBrief(cwd);
|
|
23
|
+
const custom = fs.existsSync(path.join(cwd, ".squint", "brief.md"));
|
|
24
|
+
lines.push({ source: custom ? "brief (.squint/brief.md)" : "brief (built-in)", tokens: tokens(brief), when: "first turn" });
|
|
25
|
+
if (tokens(brief) > 1500) warnings.push(`the brief is ${tokens(brief)} tokens \u2014 past ~1500 the engine starts skimming; distill it`);
|
|
26
|
+
const rules = loadRules(cwd);
|
|
27
|
+
if (rules) {
|
|
28
|
+
lines.push({ source: "rules (.squint/rules.md)", tokens: tokens(rules), when: "every ask" });
|
|
29
|
+
if (tokens(rules) > 800) warnings.push(`rules.md is ${tokens(rules)} tokens of always-on context \u2014 move situational parts into triggered skills`);
|
|
30
|
+
}
|
|
31
|
+
const decisions = decisionsSection(cwd);
|
|
32
|
+
if (decisions) {
|
|
33
|
+
lines.push({ source: `design ledger (${loadDecisions(cwd).length} recent decisions)`, tokens: tokens(decisions), when: "every ask" });
|
|
34
|
+
}
|
|
35
|
+
const inventory = loadComponentInventory(cwd);
|
|
36
|
+
if (inventory) {
|
|
37
|
+
lines.push({ source: "component inventory", tokens: tokens(inventorySection(inventory)), when: "every ask" });
|
|
38
|
+
}
|
|
39
|
+
const locks = loadLocks(cwd);
|
|
40
|
+
if (locks.length > 0) {
|
|
41
|
+
lines.push({ source: `locks (${locks.length} paths)`, tokens: tokens(locks.join("\n")) + 40, when: "every ask" });
|
|
42
|
+
for (const lock of locks) {
|
|
43
|
+
if (!fs.existsSync(path.join(cwd, lock))) warnings.push(`stale lock: ${lock} no longer exists \u2014 remove it from .squint/locks`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const skill of loadSkills(cwd)) {
|
|
47
|
+
lines.push({
|
|
48
|
+
source: `skill: ${skill.name}`,
|
|
49
|
+
tokens: tokens(skill.body),
|
|
50
|
+
when: `when the ask mentions ${skill.triggers.map((t) => `"${t}"`).join(", ")}`
|
|
51
|
+
});
|
|
52
|
+
const generic = skill.triggers.filter((t) => t.length <= 3);
|
|
53
|
+
if (generic.length > 0) {
|
|
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
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const totalAlways = lines.filter((l) => l.when === "every ask").reduce((sum, l) => sum + l.tokens, 0);
|
|
58
|
+
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
|
+
return { lines, warnings, totalAlways };
|
|
60
|
+
}
|
|
61
|
+
function formatContextReport(report) {
|
|
62
|
+
const width = Math.max(...report.lines.map((l) => l.source.length), 10);
|
|
63
|
+
const rows = report.lines.map((l) => ` ${l.source.padEnd(width)} ~${String(l.tokens).padStart(5)} tok ${l.when}`);
|
|
64
|
+
const out = [`what squint injects (estimates):`, ...rows, ` ${"always-on total".padEnd(width)} ~${String(report.totalAlways).padStart(5)} tok every ask`];
|
|
65
|
+
if (report.warnings.length > 0) {
|
|
66
|
+
out.push("", "warnings:", ...report.warnings.map((w) => ` \u26A0 ${w}`));
|
|
67
|
+
}
|
|
68
|
+
return out.join("\n");
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
contextReport,
|
|
72
|
+
formatContextReport
|
|
73
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/quality/rulepacks.ts
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var TAILWIND_V4_RULES = [
|
|
8
|
+
{
|
|
9
|
+
re: /\bbg-gradient-to-(t|tr|r|br|b|bl|l|tl)\b/g,
|
|
10
|
+
hint: (m) => `${m} is v3 \u2014 v4 renamed it bg-linear-to-${m.split("-").pop()}`,
|
|
11
|
+
hard: true
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
re: /\bflex-(shrink|grow)(-\d+)?\b/g,
|
|
15
|
+
hint: (m) => `${m} is v3 \u2014 v4 uses ${m.replace("flex-", "")}`,
|
|
16
|
+
hard: true
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
re: /\boverflow-ellipsis\b/g,
|
|
20
|
+
hint: () => "overflow-ellipsis is v3 \u2014 v4 uses text-ellipsis",
|
|
21
|
+
hard: true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
re: /\bdecoration-(slice|clone)\b/g,
|
|
25
|
+
hint: (m) => `${m} is v3 \u2014 v4 uses box-${m}`,
|
|
26
|
+
hard: true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
re: /\b(shadow|rounded|blur|drop-shadow)-sm\b/g,
|
|
30
|
+
hint: (m) => `${m} means one step larger in v4 (the scale shifted; v3 ${m} is now ${m.replace(/-sm$/, "-xs")}) \u2014 verify the intent`,
|
|
31
|
+
hard: false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
re: /\boutline-none\b/g,
|
|
35
|
+
hint: () => "outline-none changed meaning in v4 (now outline-style: none; the old invisible-but-accessible reset is outline-hidden) \u2014 verify the intent",
|
|
36
|
+
hard: false
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
var VITE_RULES = [
|
|
40
|
+
{
|
|
41
|
+
re: /\bsplitVendorChunkPlugin\b/g,
|
|
42
|
+
hint: () => "splitVendorChunkPlugin was removed \u2014 use build.rollupOptions.output.manualChunks",
|
|
43
|
+
hard: true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
re: /\boptimizeDeps:\s*\{[^}]*esbuildOptions\b|\besbuildOptions\b/g,
|
|
47
|
+
hint: () => "optimizeDeps.esbuildOptions is esbuild-era \u2014 Rolldown Vite uses optimizeDeps.rollupOptions",
|
|
48
|
+
hard: false
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
function readPackageJson(cwd) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function depMajor(pkg, name) {
|
|
59
|
+
if (!pkg) return null;
|
|
60
|
+
for (const key of ["dependencies", "devDependencies"]) {
|
|
61
|
+
const deps = pkg[key];
|
|
62
|
+
const range = deps?.[name];
|
|
63
|
+
const major = range?.match(/(\d+)/)?.[1];
|
|
64
|
+
if (major) return Number.parseInt(major, 10);
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function detectTailwindMajor(cwd) {
|
|
69
|
+
const pkg = readPackageJson(cwd);
|
|
70
|
+
const major = depMajor(pkg, "tailwindcss");
|
|
71
|
+
if (major !== null) return major;
|
|
72
|
+
if (depMajor(pkg, "@tailwindcss/vite") !== null) return 4;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
function detectViteMajor(cwd) {
|
|
76
|
+
return depMajor(readPackageJson(cwd), "vite");
|
|
77
|
+
}
|
|
78
|
+
function scanRulePacks(cwd, source) {
|
|
79
|
+
const packs = [];
|
|
80
|
+
const tailwind = detectTailwindMajor(cwd);
|
|
81
|
+
if (tailwind !== null && tailwind >= 4) packs.push({ rules: TAILWIND_V4_RULES });
|
|
82
|
+
const vite = detectViteMajor(cwd);
|
|
83
|
+
if (vite !== null && vite >= 6) packs.push({ rules: VITE_RULES, files: /vite\.config\.[cm]?[jt]s$/ });
|
|
84
|
+
if (packs.length === 0) return [];
|
|
85
|
+
let diff;
|
|
86
|
+
try {
|
|
87
|
+
diff = execFileSync("git", ["diff", "-U0", source, "--", "*.tsx", "*.jsx", "*.ts", "*.js", "*.html", "*.css", "*.vue", "*.svelte"], {
|
|
88
|
+
cwd,
|
|
89
|
+
encoding: "utf8",
|
|
90
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const findings = [];
|
|
96
|
+
const seen = /* @__PURE__ */ new Set();
|
|
97
|
+
let currentFile = "";
|
|
98
|
+
for (const line of diff.split("\n")) {
|
|
99
|
+
if (line.startsWith("+++ b/")) {
|
|
100
|
+
currentFile = line.slice(6);
|
|
101
|
+
if (tailwind !== null && tailwind >= 4 && /(^|\/)tailwind\.config\.[cm]?[jt]s$/.test(currentFile)) {
|
|
102
|
+
findings.push({
|
|
103
|
+
file: currentFile,
|
|
104
|
+
match: "tailwind.config",
|
|
105
|
+
hint: "Tailwind v4 is CSS-first \u2014 configure with @theme in your CSS, not a tailwind.config file",
|
|
106
|
+
hard: true
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (!line.startsWith("+") || line.startsWith("+++")) continue;
|
|
112
|
+
for (const pack of packs) {
|
|
113
|
+
if (pack.files && !pack.files.test(currentFile)) continue;
|
|
114
|
+
for (const rule of pack.rules) {
|
|
115
|
+
for (const match of line.matchAll(rule.re)) {
|
|
116
|
+
const key = `${currentFile}:${match[0]}`;
|
|
117
|
+
if (seen.has(key)) continue;
|
|
118
|
+
seen.add(key);
|
|
119
|
+
findings.push({ file: currentFile, match: match[0], hint: rule.hint(match[0]), hard: rule.hard });
|
|
120
|
+
if (findings.length >= 12) return findings;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return findings;
|
|
126
|
+
}
|
|
127
|
+
function rulePackSummary(findings) {
|
|
128
|
+
return findings.map((f) => ` ${f.file}: ${f.hint}`).join("\n");
|
|
129
|
+
}
|
|
130
|
+
export {
|
|
131
|
+
detectTailwindMajor,
|
|
132
|
+
detectViteMajor,
|
|
133
|
+
rulePackSummary,
|
|
134
|
+
scanRulePacks
|
|
135
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
screenshotVariants
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-QCHBDP46.js";
|
|
5
|
+
import "./chunk-YGSF2TSO.js";
|
|
6
6
|
import "./chunk-VH7OOFQP.js";
|
|
7
7
|
import "./chunk-IMDRXXFU.js";
|
|
8
8
|
import "./chunk-KVYGPLWW.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-K5QJMSJH.js";
|
|
10
|
+
import "./chunk-WAJXATCO.js";
|
|
10
11
|
export {
|
|
11
12
|
screenshotVariants
|
|
12
13
|
};
|
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
runVariants,
|
|
10
10
|
variantPrompt,
|
|
11
11
|
variantsRoot
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YGSF2TSO.js";
|
|
13
13
|
import "./chunk-VH7OOFQP.js";
|
|
14
14
|
import "./chunk-KVYGPLWW.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-K5QJMSJH.js";
|
|
16
|
+
import "./chunk-WAJXATCO.js";
|
|
16
17
|
export {
|
|
17
18
|
MAX_VARIANTS,
|
|
18
19
|
applyVariant,
|
package/package.json
CHANGED