@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 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
- // 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
- }
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" },
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  variantsRoot
4
- } from "./chunk-A64VVTPU.js";
4
+ } from "./chunk-YGSF2TSO.js";
5
5
  import {
6
6
  findChrome,
7
7
  screenshot
@@ -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-P3H4N2EN.js";
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";
@@ -172,6 +172,10 @@ ${parts.join("\n\n")}` : "",
172
172
 
173
173
  export {
174
174
  appendDecision,
175
+ loadDecisions,
176
+ decisionsSection,
177
+ loadComponentInventory,
178
+ inventorySection,
175
179
  parseSkill,
176
180
  loadSkills,
177
181
  loadRules,
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  completeCommand
4
- } from "./chunk-S3WV5C3X.js";
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-F6JZSXEO.js";
25
+ } from "./chunk-QCHBDP46.js";
26
26
  import {
27
27
  applyVariant,
28
28
  cleanVariants,
29
29
  listVariants,
30
30
  runVariants
31
- } from "./chunk-A64VVTPU.js";
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-KGZILC3S.js";
65
+ } from "./chunk-YT6K2YIS.js";
66
+ import "./chunk-K5QJMSJH.js";
66
67
  import {
67
68
  composePrompt
68
- } from "./chunk-P3H4N2EN.js";
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-U2J7Z3B2.js");
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-2G5SLLY7.js");
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-3ZSINJGX.js");
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-E2AWBDCN.js");
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-3ZSINJGX.js");
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-3ZSINJGX.js");
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-3ZSINJGX.js");
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-B6I6C5LP.js").then(({ commandHelp }) => this.push("status", commandHelp()));
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.2" : "0.0.0-dev";
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);
@@ -3,7 +3,7 @@ import {
3
3
  COMMANDS,
4
4
  commandHelp,
5
5
  completeCommand
6
- } from "./chunk-S3WV5C3X.js";
6
+ } from "./chunk-O7NWGLS3.js";
7
7
  export {
8
8
  COMMANDS,
9
9
  commandHelp,
@@ -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
+ };
@@ -3,7 +3,8 @@ import {
3
3
  FAMILIES,
4
4
  getFamily,
5
5
  renderFamilyBrief
6
- } from "./chunk-P3H4N2EN.js";
6
+ } from "./chunk-K5QJMSJH.js";
7
+ import "./chunk-WAJXATCO.js";
7
8
  export {
8
9
  FAMILIES,
9
10
  getFamily,
@@ -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-F6JZSXEO.js";
5
- import "./chunk-A64VVTPU.js";
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-P3H4N2EN.js";
9
+ import "./chunk-K5QJMSJH.js";
10
+ import "./chunk-WAJXATCO.js";
10
11
  export {
11
12
  screenshotVariants
12
13
  };
@@ -6,7 +6,7 @@ import {
6
6
  loadSkills,
7
7
  matchSkills,
8
8
  parseSkill
9
- } from "./chunk-KGZILC3S.js";
9
+ } from "./chunk-YT6K2YIS.js";
10
10
  export {
11
11
  enrich,
12
12
  loadLocks,
@@ -9,10 +9,11 @@ import {
9
9
  runVariants,
10
10
  variantPrompt,
11
11
  variantsRoot
12
- } from "./chunk-A64VVTPU.js";
12
+ } from "./chunk-YGSF2TSO.js";
13
13
  import "./chunk-VH7OOFQP.js";
14
14
  import "./chunk-KVYGPLWW.js";
15
- import "./chunk-P3H4N2EN.js";
15
+ import "./chunk-K5QJMSJH.js";
16
+ import "./chunk-WAJXATCO.js";
16
17
  export {
17
18
  MAX_VARIANTS,
18
19
  applyVariant,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aayambansal/squint",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Lovable for your terminal — a frontend harness on top of Claude Code, Codex, and other coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",