@cosmicdrift/kumiko-guards 0.330.2 → 0.331.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-guards",
3
- "version": "0.330.2",
3
+ "version": "0.331.0",
4
4
  "description": "AST-based security guards for Kumiko repos: direct-fs/fetch, tenant escalation, admin-API, escape hatches and related checks, run over a shared ts-morph project.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -28,7 +28,7 @@
28
28
  "kumiko-pre-push": "./src/pre-push.sh"
29
29
  },
30
30
  "dependencies": {
31
- "@cosmicdrift/kumiko-repo-manifest": "0.330.2",
31
+ "@cosmicdrift/kumiko-repo-manifest": "0.331.0",
32
32
  "ts-morph": "^28.0.0"
33
33
  },
34
34
  "publishConfig": {
package/src/changes.json CHANGED
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.331.0",
4
+ "type": "improvement",
5
+ "title": "Primitives-discipline guard flags hand-built mx-auto + max-w-* screen containers",
6
+ "detail": "A className combining `mx-auto` with a `max-w-*` token now fails the guard and points to `<PageSection maxWidth>` (3xl/4xl/full); files under a `public/` segment are exempt because PublicShell is their container. Web code under a `client/` path segment (enterprise ai-foundation) is now scanned too.",
7
+ "migration": "Replace the hand-rolled container with `<PageSection maxWidth=\"3xl|4xl|full\">`; a deliberate exception (e.g. a narrow centered card) carries `// kumiko-lint-ignore primitives-discipline <reason>`. Web code under `client/` folders is now scanned as well."
8
+ },
2
9
  {
3
10
  "version": "0.327.0",
4
11
  "type": "improvement",
@@ -22,6 +22,8 @@
22
22
  * <dialog> → <DefaultDialog>
23
23
  * alert() / window.alert() → <DefaultDialog> from @cosmicdrift/kumiko-renderer-web
24
24
  * className "bg-card" (hand-rolled card) → <Card> (slots/options)
25
+ * className "mx-auto" + "max-w-*" (hand-rolled screen container)
26
+ * → <PageSection maxWidth> (3xl/4xl/full)
25
27
  *
26
28
  * Allowed: containers and text — `<div>`, `<span>`, `<section>`,
27
29
  * `<header>`, `<main>`, `<nav>`, `<aside>`, `<article>`,
@@ -73,6 +75,16 @@ const FORBIDDEN_CLASSES: ReadonlyArray<{
73
75
  readonly counterpart: string;
74
76
  }> = [{ token: "bg-card", counterpart: "<Card>" }];
75
77
 
78
+ // Hand-rolled screen container: `mx-auto` and a `max-w-*` token in the same
79
+ // line. Screens get their width from `<PageSection maxWidth>`; a private
80
+ // container drifts from the shell (an ai-models screen rendered 896px wide).
81
+ const SCREEN_CONTAINER_LABEL = "mx-auto + max-w-*";
82
+ const SCREEN_CONTAINER_COUNTERPART = "<PageSection maxWidth> (3xl/4xl/full)";
83
+ const MX_AUTO_PATTERN = /(?<![\w-])mx-auto(?![\w-])/u;
84
+ // Arbitrary values (`max-w-[1100px]`) contain `[`/`]`/`.`, so the token body
85
+ // is any non-whitespace/non-quote run.
86
+ const MAX_W_PATTERN = /(?<![\w-])max-w-[^\s"'`}]+/u;
87
+
76
88
  const IGNORE_TAG = "kumiko-lint-ignore primitives-discipline";
77
89
 
78
90
  export type ScopeKind = "bundled-features" | "samples" | "app";
@@ -127,13 +139,14 @@ type Violation = {
127
139
  };
128
140
 
129
141
  function isWebFile(absPath: string, scopeRoot: string): boolean {
130
- // Only .tsx files under a `web/` or `public/` path segment (prevents
142
+ // Only .tsx files under a `web/`, `client/` or `public/` path segment
143
+ // (`client` = enterprise ai-foundation keeps its web code there; prevents
131
144
  // scanning random .tsx in screens/ or feature roots — those are schema
132
145
  // definitions, not web code).
133
146
  if (!absPath.endsWith(".tsx")) return false;
134
147
  const rel = path.relative(scopeRoot, absPath);
135
148
  const parts = rel.split(path.sep);
136
- return parts.includes("web") || parts.includes("public");
149
+ return parts.includes("web") || parts.includes("client") || parts.includes("public");
137
150
  }
138
151
 
139
152
  function walk(dir: string, out: string[]): void {
@@ -178,6 +191,12 @@ const CLASS_PATTERNS = FORBIDDEN_CLASSES.map((c) => ({
178
191
  pattern: new RegExp(`(?<![\\w-])${c.token}(?![\\w-])`, "u"),
179
192
  }));
180
193
 
194
+ function isPublicPage(file: string): boolean {
195
+ // Public pages live outside the app shell; PublicShell is their container,
196
+ // so a centered max-width there is not a screen container.
197
+ return file.split(path.sep).includes("public");
198
+ }
199
+
181
200
  function hasIgnore(currentLine: string, prevLine: string): boolean {
182
201
  return currentLine.includes(IGNORE_TAG) || prevLine.includes(IGNORE_TAG);
183
202
  }
@@ -190,6 +209,7 @@ export function checkFile(
190
209
  const text = fs.readFileSync(file, "utf-8");
191
210
  const lines = text.split("\n");
192
211
  const violations: Violation[] = [];
212
+ const publicPage = isPublicPage(path.relative(root, file));
193
213
  let inBlockComment = false;
194
214
  for (let i = 0; i < lines.length; i++) {
195
215
  const line = lines[i] ?? "";
@@ -252,6 +272,17 @@ export function checkFile(
252
272
  });
253
273
  }
254
274
  }
275
+ if (!publicPage && MX_AUTO_PATTERN.test(codeOnly) && MAX_W_PATTERN.test(codeOnly)) {
276
+ violations.push({
277
+ file: path.relative(root, file),
278
+ line: i + 1,
279
+ kind: "class",
280
+ tag: SCREEN_CONTAINER_LABEL,
281
+ counterpart: SCREEN_CONTAINER_COUNTERPART,
282
+ excerpt: trimmed.length > 120 ? `${trimmed.slice(0, 117)}...` : trimmed,
283
+ scope,
284
+ });
285
+ }
255
286
  }
256
287
  return violations;
257
288
  }