@miroshnik/elves 0.0.0 → 2026.1018.1

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.
Files changed (68) hide show
  1. package/AGENTS.md +310 -0
  2. package/README.md +344 -0
  3. package/bin/ai-dev.ts +7 -0
  4. package/bin/elves.ts +2847 -0
  5. package/bin/shims/timeout +113 -0
  6. package/claude/CLAUDE.md +71 -0
  7. package/dist/github/scripts/github.js +3428 -0
  8. package/dist/spec/scripts/architecture.js +628 -0
  9. package/dist/spec/scripts/eslint-config.js +54 -0
  10. package/dist/spec/scripts/harness-qzb932c8.js +959 -0
  11. package/dist/spec/scripts/harness-v9s1v9w3.js +16045 -0
  12. package/dist/spec/scripts/harness.js +53 -0
  13. package/dist/spec/scripts/knip-hints.cjs +34 -0
  14. package/docs/ci-concurrency.md +82 -0
  15. package/docs/cloud-sessions.md +204 -0
  16. package/docs/judgment.md +81 -0
  17. package/docs/parallel-checkouts.md +220 -0
  18. package/docs/pr-checks.md +117 -0
  19. package/docs/testing.md +304 -0
  20. package/package.json +70 -1
  21. package/skills/ci-wait/SKILL.md +180 -0
  22. package/skills/ci-wait/scripts/wait-ci.sh +377 -0
  23. package/skills/dashboard/SKILL.md +212 -0
  24. package/skills/dashboard/scripts/dashboard.ts +912 -0
  25. package/skills/dashboard/scripts/sessions.ts +772 -0
  26. package/skills/est/SKILL.md +222 -0
  27. package/skills/est/reference.md +381 -0
  28. package/skills/est/scripts/est.ts +4867 -0
  29. package/skills/github/SKILL.md +206 -0
  30. package/skills/github/locales/en.json +449 -0
  31. package/skills/github/locales/ru.json +449 -0
  32. package/skills/github/reference.md +1073 -0
  33. package/skills/github/scripts/github.ts +4390 -0
  34. package/skills/github/scripts/locale.ts +495 -0
  35. package/skills/github/scripts/package.json +3 -0
  36. package/skills/slot/SKILL.md +145 -0
  37. package/skills/slot/scripts/package.json +3 -0
  38. package/skills/slot/scripts/slot.ts +279 -0
  39. package/skills/spec/SKILL.md +247 -0
  40. package/skills/spec/canon.md +219 -0
  41. package/skills/spec/checks.md +709 -0
  42. package/skills/spec/ci.md +378 -0
  43. package/skills/spec/migration.md +125 -0
  44. package/skills/spec/reference.md +889 -0
  45. package/skills/spec/scripts/architecture.ts +839 -0
  46. package/skills/spec/scripts/c4.ts +184 -0
  47. package/skills/spec/scripts/eslint-config.ts +82 -0
  48. package/skills/spec/scripts/harness.ts +1483 -0
  49. package/skills/spec/scripts/knip-hints.cjs +34 -0
  50. package/skills/spec/scripts/knip-hints.d.cts +17 -0
  51. package/skills/spec/scripts/package-run.ts +22 -0
  52. package/skills/spec/scripts/package.json +3 -0
  53. package/skills/spec/scripts/spec-break.ts +232 -0
  54. package/skills/spec/scripts/spec-claims.ts +198 -0
  55. package/skills/spec/scripts/spec-diff.ts +1111 -0
  56. package/skills/spec/scripts/spec-doc.ts +744 -0
  57. package/skills/spec/scripts/spec-exceptions.ts +233 -0
  58. package/skills/spec/scripts/spec-publish.ts +214 -0
  59. package/skills/spec/scripts/spec-run.ts +235 -0
  60. package/skills/spec/scripts/spec-shard.ts +267 -0
  61. package/skills/spec/scripts/speclib.ts +826 -0
  62. package/skills/spec/scripts/vendor/babel-parser.LICENSE +19 -0
  63. package/skills/spec/scripts/vendor/babel-parser.cjs +14615 -0
  64. package/skills/spec/scripts/vendor/babel-parser.d.cts +3 -0
  65. package/skills/task-env/SKILL.md +229 -0
  66. package/skills/task-env/proxy/compose.yaml +48 -0
  67. package/skills/task-env/reference.md +246 -0
  68. package/skills/task-env/scripts/task-env.ts +981 -0
@@ -0,0 +1,628 @@
1
+ import {
2
+ locale_default
3
+ } from "./harness-qzb932c8.js";
4
+ import {
5
+ invariant2,
6
+ codeOnly2,
7
+ envNamesInCode2
8
+ } from "./harness-v9s1v9w3.js";
9
+
10
+ // skills/spec/scripts/architecture.ts
11
+ import { readdirSync, readFileSync } from "node:fs";
12
+ import { builtinModules } from "node:module";
13
+ import path from "node:path";
14
+ function deployUnits(root) {
15
+ const out = new Set;
16
+ for (const f of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
17
+ let text;
18
+ try {
19
+ text = readFileSync(path.join(root, f), "utf8");
20
+ } catch {
21
+ continue;
22
+ }
23
+ let inServices = false;
24
+ for (const line of text.split(/\r?\n/)) {
25
+ if (/^services:\s*$/.test(line))
26
+ inServices = true;
27
+ else if (/^\S/.test(line))
28
+ inServices = false;
29
+ else if (inServices) {
30
+ const m = /^ {2}([A-Za-z0-9_.-]+):\s*$/.exec(line);
31
+ if (m)
32
+ out.add(`compose:${m[1]}`);
33
+ }
34
+ }
35
+ }
36
+ try {
37
+ for (const e of readdirSync(path.join(root, "supabase", "functions"), { withFileTypes: true })) {
38
+ if (e.isDirectory() && !e.name.startsWith("_"))
39
+ out.add(`supabase-function:${e.name}`);
40
+ }
41
+ } catch {}
42
+ try {
43
+ const v = JSON.parse(readFileSync(path.join(root, "vercel.json"), "utf8"));
44
+ for (const c of v.crons ?? [])
45
+ out.add(`vercel-cron:${c.path}`);
46
+ } catch {}
47
+ walkDirs(root, (rel, entries) => {
48
+ for (const e of entries) {
49
+ if (!e.isFile() || !e.name.endsWith(".tf"))
50
+ continue;
51
+ for (const m of readFileSync(path.join(root, rel, e.name), "utf8").matchAll(TF_RESOURCE)) {
52
+ if (TERRAFORM_UNITS.has(m[1]))
53
+ out.add(`terraform:${m[1]}.${m[2]}`);
54
+ }
55
+ }
56
+ });
57
+ return sorted([...out]);
58
+ }
59
+ var TERRAFORM_UNITS = new Set([
60
+ "aws_ecs_service",
61
+ "aws_lambda_function",
62
+ "aws_apprunner_service",
63
+ "google_cloud_run_service",
64
+ "google_cloud_run_v2_service",
65
+ "google_cloud_run_v2_job",
66
+ "google_cloudfunctions_function",
67
+ "google_cloudfunctions2_function",
68
+ "azurerm_container_app",
69
+ "azurerm_linux_function_app",
70
+ "kubernetes_deployment",
71
+ "kubernetes_deployment_v1",
72
+ "kubernetes_cron_job_v1"
73
+ ]);
74
+ var TF_RESOURCE = /^[ \t]*resource\s+"([\w-]+)"\s+"([\w-]+)"/gm;
75
+ function workspacePackages(root) {
76
+ const patterns = [];
77
+ try {
78
+ const ws = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8")).workspaces;
79
+ patterns.push(...Array.isArray(ws) ? ws : ws?.packages ?? []);
80
+ } catch {}
81
+ try {
82
+ let inPackages = false;
83
+ for (const line of readFileSync(path.join(root, "pnpm-workspace.yaml"), "utf8").split(/\r?\n/)) {
84
+ if (/^packages:\s*$/.test(line))
85
+ inPackages = true;
86
+ else if (/^\S/.test(line))
87
+ inPackages = false;
88
+ else if (inPackages) {
89
+ const m = /^\s+-\s+(["']?)(.+?)\1\s*(?:#.*)?$/.exec(line);
90
+ if (m)
91
+ patterns.push(m[2]);
92
+ }
93
+ }
94
+ } catch {}
95
+ if (!patterns.length)
96
+ return [];
97
+ const include = patterns.filter((p) => !p.startsWith("!")).map(glob);
98
+ const exclude = patterns.filter((p) => p.startsWith("!")).map((p) => glob(p.slice(1)));
99
+ const out = [];
100
+ walkDirs(root, (rel, entries) => {
101
+ if (!rel || !entries.some((e) => e.isFile() && e.name === "package.json"))
102
+ return;
103
+ if (include.some((r) => r.test(rel)) && !exclude.some((r) => r.test(rel)))
104
+ out.push(rel);
105
+ });
106
+ return sorted(out);
107
+ }
108
+ function glob(pattern) {
109
+ const segs = pattern.replace(/^\.\//, "").replace(/\/+$/, "").split("/");
110
+ const body = segs.map((s) => s === "**" ? "\x00" : s.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]")).join("/").replace(/\0\//g, "(?:.*/)?").replace(/\/\0/g, "(?:/.*)?").replace(/\0/g, ".*");
111
+ return new RegExp(`^${body}$`);
112
+ }
113
+ function walkDirs(root, visit, rel = "") {
114
+ let entries;
115
+ try {
116
+ entries = readdirSync(path.join(root, rel), { withFileTypes: true });
117
+ } catch {
118
+ return;
119
+ }
120
+ visit(rel, entries);
121
+ for (const e of entries) {
122
+ if (e.isDirectory() && !SKIP.has(e.name) && !e.name.startsWith("."))
123
+ walkDirs(root, visit, rel ? `${rel}/${e.name}` : e.name);
124
+ }
125
+ }
126
+ function hasCode(dir) {
127
+ let entries;
128
+ try {
129
+ entries = readdirSync(dir, { withFileTypes: true });
130
+ } catch {
131
+ return false;
132
+ }
133
+ return entries.some((e) => e.isDirectory() ? !SKIP.has(e.name) && hasCode(path.join(dir, e.name)) : CODE.test(e.name) && !/\.d\.[cm]?ts$/.test(e.name));
134
+ }
135
+ var CODE = /\.[cm]?[jt]sx?$/;
136
+ var SKIP = new Set(["node_modules", ".git", "dist", "build", "coverage", ".next"]);
137
+ var BUILTIN = new Set(builtinModules);
138
+ var paths = (m) => (Array.isArray(m.path) ? m.path : [m.path]).map((p) => p.replace(/\/+$/, ""));
139
+ var names = (model) => Object.keys(model.modules).sort();
140
+ var sorted = (xs) => [...xs].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
141
+ function boundariesConfig(model, boundaries, opts = {}) {
142
+ const all = names(model);
143
+ const roots = model.roots ?? ["src"];
144
+ const policies = all.flatMap((name) => {
145
+ const allowed = new Set([name, ...model.modules[name].dependsOn ?? []]);
146
+ const denied = all.filter((n) => !allowed.has(n));
147
+ if (!denied.length)
148
+ return [];
149
+ return [
150
+ {
151
+ from: { element: { type: name } },
152
+ disallow: { to: { element: { types: { anyOf: denied } } } },
153
+ message: `module ${name} doesn't depend on {{to.element.type}} — module dependencies are in tests/architecture/model.ts`
154
+ }
155
+ ];
156
+ });
157
+ return [
158
+ {
159
+ files: opts.files ?? roots.map((r) => `${r}/**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}`),
160
+ plugins: { boundaries },
161
+ settings: {
162
+ "boundaries/elements": all.flatMap((name) => paths(model.modules[name]).map((p) => ({ type: name, pattern: p }))).sort((a, b) => b.pattern.length - a.pattern.length),
163
+ "import/resolver": { node: { extensions: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"] } },
164
+ ...opts.settings
165
+ },
166
+ rules: { "boundaries/dependencies": [2, { default: "allow", policies }] }
167
+ }
168
+ ];
169
+ }
170
+ var IMPORT = /^\s*(import|export)\s+(type\s+)?[^;'"]*?\s*from\s*["']([^"']+)["']|^\s*import\s*["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)|\brequire\s*\(\s*["']([^"']+)["']\s*\)/gm;
171
+ function importsIn(text, aliases = {}) {
172
+ const out = [];
173
+ for (const m of text.matchAll(IMPORT)) {
174
+ if (m[2])
175
+ continue;
176
+ const spec = m[3] ?? m[4] ?? m[5] ?? m[6];
177
+ if (!spec || spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:"))
178
+ continue;
179
+ if (Object.keys(aliases).some((a) => spec.startsWith(a)))
180
+ continue;
181
+ const parts = spec.split("/");
182
+ const pkg = spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
183
+ if (BUILTIN.has(pkg) || out.includes(pkg))
184
+ continue;
185
+ out.push(pkg);
186
+ }
187
+ return out;
188
+ }
189
+ var URL_HOST = /["'`]https?:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)+)(?::\d+)?(?=[\/"'`?#])/gi;
190
+ var isLocal = (h) => h === "localhost" || h.endsWith(".localhost") || /^127(\.\d+){3}$/.test(h) || h === "0.0.0.0" || h === "[::1]";
191
+ var isReserved = (h) => /(^|\.)(example|invalid|test)$|(^|\.)example\.(com|net|org)$/.test(h);
192
+ var XMLNS = /xmlns(?::[\w-]+)?\s*=\s*$/;
193
+ var NAMESPACE_HOSTS = new Set(["www.w3.org"]);
194
+ var HREF = /(?<![.\w$])["']?href["']?\s*(?:=\s*\{?|:)\s*$/;
195
+ var OPEN_TAG = /<([A-Za-z][\w.:-]*)[^<>]*$/;
196
+ var isLink = (text, at) => HREF.test(text.slice(Math.max(0, at - 40), at)) && OPEN_TAG.exec(text.slice(Math.max(0, at - 2000), at))?.[1] !== "link";
197
+ function hostsIn(source, file) {
198
+ return hostsInCode(codeOnly2(source, file));
199
+ }
200
+ function hostsInCode(text) {
201
+ const out = new Set;
202
+ for (const m of text.matchAll(URL_HOST)) {
203
+ const h = m[1].toLowerCase();
204
+ if (isLocal(h) || isReserved(h) || NAMESPACE_HOSTS.has(h) || XMLNS.test(text.slice(Math.max(0, m.index - 40), m.index)))
205
+ continue;
206
+ if (isLink(text, m.index))
207
+ continue;
208
+ out.add(h);
209
+ }
210
+ return [...out];
211
+ }
212
+ var HEADER_LITERAL = /(["'`])([A-Za-z][A-Za-z0-9-]*)\1/g;
213
+ function headersInCode(text, declared) {
214
+ const out = new Set;
215
+ for (const m of text.matchAll(HEADER_LITERAL)) {
216
+ const h = m[2].toLowerCase();
217
+ if (declared.has(h))
218
+ out.add(h);
219
+ }
220
+ return [...out];
221
+ }
222
+ function sources(root, model, readers) {
223
+ const out = [];
224
+ const declared = new Set(Object.values(model.externals ?? {}).flatMap((e) => (e.headers ?? []).map((h) => h.toLowerCase())));
225
+ const walk = (rel) => {
226
+ let entries;
227
+ try {
228
+ entries = readdirSync(path.join(root, rel), { withFileTypes: true });
229
+ } catch {
230
+ return;
231
+ }
232
+ for (const e of entries) {
233
+ const child = `${rel}/${e.name}`;
234
+ if (e.isDirectory()) {
235
+ if (!SKIP.has(e.name))
236
+ walk(child);
237
+ } else if (CODE.test(e.name) && !/\.d\.[cm]?ts$/.test(e.name)) {
238
+ const text = readFileSync(path.join(root, child), "utf8");
239
+ const code = codeOnly2(text, child);
240
+ out.push({
241
+ file: child,
242
+ dir: rel,
243
+ packages: importsIn(text, model.aliases),
244
+ hosts: hostsInCode(code),
245
+ env: envNamesInCode2(code, { file: child, readers }),
246
+ headers: declared.size ? headersInCode(code, declared) : []
247
+ });
248
+ }
249
+ }
250
+ };
251
+ for (const r of model.roots ?? ["src"])
252
+ walk(r.replace(/\/+$/, ""));
253
+ return out.sort((a, b) => a.file < b.file ? -1 : 1);
254
+ }
255
+ function owners(model, dir) {
256
+ let best = -1;
257
+ let out = [];
258
+ for (const n of names(model)) {
259
+ for (const p of paths(model.modules[n])) {
260
+ if (dir !== p && !dir.startsWith(p + "/"))
261
+ continue;
262
+ if (p.length > best)
263
+ [best, out] = [p.length, [n]];
264
+ else if (p.length === best && !out.includes(n))
265
+ out.push(n);
266
+ }
267
+ }
268
+ return out;
269
+ }
270
+ function architecture(it, opts) {
271
+ const { model } = opts;
272
+ const N = locale_default.names(locale_default.projectLanguage());
273
+ const specs = [];
274
+ const add = (spec) => void specs.push(spec);
275
+ const src = sources(opts.root, model, opts.readers);
276
+ const dirs = sorted([...new Set(src.map((s) => s.dir))]);
277
+ const moduleOf = (dir) => owners(model, dir);
278
+ add({
279
+ registry: N.spec.architecture.dirsRegistry,
280
+ items: dirs,
281
+ name: (d) => locale_default.fill(N.spec.architecture.dirName, { dir: d, module: moduleOf(d).length === 1 ? moduleOf(d)[0] : "?" }),
282
+ key: (d) => d,
283
+ check: (d) => {
284
+ const own = moduleOf(d);
285
+ if (!own.length)
286
+ throw new Error(`${d}: code outside the model's modules — add a module to tests/architecture/model.ts or move the code`);
287
+ if (own.length > 1)
288
+ throw new Error(`${d}: the directory is in several modules — ${own.join(", ")}`);
289
+ },
290
+ violator: { name: N.spec.architecture.dirViolator, item: "__outside_model__" }
291
+ });
292
+ const roots = (model.roots ?? ["src"]).map((r) => r.replace(/^\.\//, "").replace(/\/+$/, ""));
293
+ const inRoots = (p) => roots.some((r) => r === p || r.startsWith(p + "/") || p.startsWith(r + "/"));
294
+ const packages = roots.some((r) => r === "" || r === ".") ? [] : workspacePackages(opts.root).filter((p) => hasCode(path.join(opts.root, p)));
295
+ if (packages.length) {
296
+ add({
297
+ registry: N.spec.architecture.workspaceRegistry,
298
+ items: packages,
299
+ name: (p) => locale_default.fill(N.spec.architecture.workspaceName, { path: p }),
300
+ key: (p) => p,
301
+ fileOf: (p) => path.resolve(opts.root, p, "package.json"),
302
+ check: (p) => {
303
+ if (!inRoots(p))
304
+ throw new Error(`${p}: a workspace package with code outside the model's roots — add it to roots in tests/architecture/model.ts`);
305
+ },
306
+ violator: { name: N.spec.architecture.workspaceViolator, item: "__outside_roots__" }
307
+ });
308
+ }
309
+ const used = new Map;
310
+ for (const s of src) {
311
+ const own = moduleOf(s.dir);
312
+ if (own.length !== 1)
313
+ continue;
314
+ const byPkg = used.get(own[0]) ?? new Map;
315
+ for (const p of s.packages)
316
+ byPkg.set(p, [...byPkg.get(p) ?? [], s.file]);
317
+ used.set(own[0], byPkg);
318
+ }
319
+ const imports = [...used].flatMap(([mod, byPkg]) => [...byPkg].map(([pkg, files]) => ({ mod, pkg, files })));
320
+ const allowed = (mod) => model.modules[mod]?.packages ?? [];
321
+ add({
322
+ registry: N.spec.architecture.importsRegistry,
323
+ items: imports,
324
+ name: (i) => locale_default.fill(N.spec.architecture.importsName, { module: i.mod, package: i.pkg }),
325
+ key: (i) => `${i.mod}:${i.pkg}`,
326
+ check: (i) => {
327
+ if (!allowed(i.mod).includes(i.pkg))
328
+ throw new Error(`package ${i.pkg} is not allowed for module ${i.mod}: ${i.files.join(", ")}`);
329
+ },
330
+ violator: { name: N.spec.architecture.importsViolator, item: { mod: names(model)[0] ?? "", pkg: "__not_allowed__", files: ["__violator__"] } }
331
+ });
332
+ const declared = names(model).flatMap((mod) => allowed(mod).map((pkg) => ({ mod, pkg })));
333
+ add({
334
+ registry: N.spec.architecture.allowedRegistry,
335
+ items: declared,
336
+ name: (d) => locale_default.fill(N.spec.architecture.allowedName, { module: d.mod, package: d.pkg }),
337
+ key: (d) => `${d.mod}:${d.pkg}`,
338
+ check: (d) => {
339
+ if (!used.get(d.mod)?.has(d.pkg))
340
+ throw new Error(`module ${d.mod} doesn't import ${d.pkg} — remove the package from the model or fix the code`);
341
+ },
342
+ violator: { name: N.spec.architecture.allowedViolator, item: { mod: "__no_module__", pkg: "__unused__" } }
343
+ });
344
+ if (model.externals)
345
+ c1(add, N, model, src);
346
+ if (model.containers)
347
+ c2(add, N, opts.root, model, src);
348
+ const exceptions = route(opts.exceptions ?? [], specs);
349
+ const outside = route(opts.outside ?? [], specs);
350
+ for (const [i, spec] of specs.entries())
351
+ invariant2(it, { ...spec, exceptions: exceptions[i], outside: outside[i] });
352
+ }
353
+ var REGISTRIES = [
354
+ (n) => n.spec.architecture.dirsRegistry,
355
+ (n) => n.spec.architecture.workspaceRegistry,
356
+ (n) => n.spec.architecture.importsRegistry,
357
+ (n) => n.spec.architecture.allowedRegistry,
358
+ (n) => n.spec.architecture.containersRegistry,
359
+ (n) => n.spec.architecture.edgesRegistry,
360
+ (n) => n.spec.architecture.unitsRegistry,
361
+ (n) => n.spec.architecture.clientsRegistry,
362
+ (n) => n.spec.architecture.hostsRegistry,
363
+ (n) => n.spec.architecture.packagesRegistry,
364
+ (n) => n.spec.architecture.envRegistry,
365
+ (n) => n.spec.architecture.headersRegistry
366
+ ];
367
+ var registryOf = (rule, n) => REGISTRIES.find((pick) => locale_default.every(pick).includes(rule))?.(n) ?? rule;
368
+ function route(xs, specs) {
369
+ const keys = specs.map((sp) => new Set(sp.items.map((x) => String((sp.key ?? sp.name)(x)))));
370
+ const out = specs.map(() => []);
371
+ const n = locale_default.names(locale_default.projectLanguage());
372
+ for (const { rule, ...x } of xs) {
373
+ const byRule = rule ? specs.findIndex((sp) => sp.registry === registryOf(rule, n)) : -1;
374
+ let to = byRule >= 0 ? [byRule] : keys.flatMap((k, i) => !rule && k.has(x.item) ? [i] : []);
375
+ if (!to.length)
376
+ to = [0];
377
+ for (const i of to)
378
+ out[i].push(x);
379
+ }
380
+ return out;
381
+ }
382
+ function moduleContainers(model) {
383
+ const cont = model.containers ?? {};
384
+ const out = new Map(Object.keys(model.modules).map((m) => [m, new Set(Object.keys(cont).filter((c) => (cont[c].modules ?? []).includes(m)))]));
385
+ for (let changed = true;changed; ) {
386
+ changed = false;
387
+ for (const [m, x] of Object.entries(model.modules)) {
388
+ for (const d of x.dependsOn ?? []) {
389
+ if (!model.modules[d]?.library)
390
+ continue;
391
+ const to = out.get(d);
392
+ for (const c of out.get(m)) {
393
+ if (to.has(c))
394
+ continue;
395
+ to.add(c);
396
+ changed = true;
397
+ }
398
+ }
399
+ }
400
+ }
401
+ return new Map([...out].map(([m, cs]) => [m, sorted([...cs])]));
402
+ }
403
+ function c2(add, N, root, model, src) {
404
+ const cont = model.containers;
405
+ const of = moduleContainers(model);
406
+ const containerOf = (m) => of.get(m) ?? [];
407
+ const isLibrary = (m) => !!model.modules[m]?.library;
408
+ add({
409
+ registry: N.spec.architecture.containersRegistry,
410
+ items: names(model),
411
+ name: (m) => isLibrary(m) ? locale_default.fill(N.spec.architecture.libraryName, { module: m, containers: containerOf(m).join(", ") || "?" }) : locale_default.fill(N.spec.architecture.moduleName, { module: m, container: containerOf(m).length === 1 ? containerOf(m)[0] : "?" }),
412
+ key: (m) => m,
413
+ check: (m) => {
414
+ const c = containerOf(m);
415
+ if (isLibrary(m)) {
416
+ if (!c.length)
417
+ throw new Error(`library ${m} is in no container: no module of the containers depends on it`);
418
+ return;
419
+ }
420
+ if (!c.length)
421
+ throw new Error(`module ${m} is in no container of the model`);
422
+ if (c.length > 1)
423
+ throw new Error(`module ${m} is in several containers: ${c.join(", ")} — mark a shared package with library: true`);
424
+ },
425
+ violator: { name: N.spec.architecture.containersViolator, item: "__outside_containers__" }
426
+ });
427
+ const edges = names(model).flatMap((m) => (model.modules[m].dependsOn ?? []).map((d) => ({ m, d })));
428
+ if (edges.length) {
429
+ add({
430
+ registry: N.spec.architecture.edgesRegistry,
431
+ items: edges,
432
+ name: (e) => locale_default.fill(N.spec.architecture.edgeName, { from: e.m, to: e.d }),
433
+ key: (e) => `${e.m}→${e.d}`,
434
+ check: (e) => {
435
+ const a = containerOf(e.m);
436
+ const b = containerOf(e.d);
437
+ if (!a.length || a.some((c) => !b.includes(c))) {
438
+ throw new Error(`${e.m} (${a.join(", ") || "?"}) depends on ${e.d} (${b.join(", ") || "?"}) — across containers only a uses link`);
439
+ }
440
+ },
441
+ violator: { name: N.spec.architecture.edgeViolator, item: { m: "__a__", d: "__b__" } }
442
+ });
443
+ }
444
+ const found = deployUnits(root);
445
+ const declared = new Map;
446
+ for (const [c, x] of Object.entries(cont))
447
+ for (const d of x.deploy ?? [])
448
+ declared.set(d, c);
449
+ add({
450
+ registry: N.spec.architecture.unitsRegistry,
451
+ items: sorted([...new Set([...found, ...declared.keys()])]),
452
+ name: (u) => locale_default.fill(N.spec.architecture.unitName, { unit: u, container: declared.get(u) ?? "?" }),
453
+ key: (u) => u,
454
+ check: (u) => {
455
+ if (!declared.has(u))
456
+ throw new Error(`${u} is in the deploy configs but not in the model — add a container to tests/architecture/model.ts`);
457
+ if (!found.includes(u))
458
+ throw new Error(`${u} of container ${declared.get(u)} is not in the deploy configs — the model diverged from the deploy`);
459
+ },
460
+ violator: { name: N.spec.architecture.unitViolator, item: "__outside_model__" }
461
+ });
462
+ const storages = Object.entries(cont).flatMap(([s, x]) => (x.clients ?? []).map((pkg) => ({ s, pkg })));
463
+ if (storages.length) {
464
+ const uses = new Map;
465
+ for (const src1 of src) {
466
+ const mod = owners(model, src1.dir);
467
+ if (mod.length !== 1)
468
+ continue;
469
+ for (const c of containerOf(mod[0])) {
470
+ for (const st of storages) {
471
+ if (!src1.packages.includes(st.pkg))
472
+ continue;
473
+ const k = `${st.pkg}:${st.s}:${c}`;
474
+ const u = uses.get(k) ?? { ...st, c, files: [] };
475
+ u.files.push(src1.file);
476
+ uses.set(k, u);
477
+ }
478
+ }
479
+ }
480
+ add({
481
+ registry: N.spec.architecture.clientsRegistry,
482
+ items: [...uses.values()],
483
+ name: (u) => locale_default.fill(N.spec.architecture.clientName, { package: u.pkg, storage: u.s, container: u.c }),
484
+ key: (u) => `${u.pkg}:${u.c}`,
485
+ check: (u) => {
486
+ if (u.c !== u.s && !(cont[u.c]?.uses ?? []).includes(u.s)) {
487
+ throw new Error(`container ${u.c} imports client ${u.pkg} of storage ${u.s} without a uses link: ${u.files.join(", ")}`);
488
+ }
489
+ },
490
+ violator: { name: N.spec.architecture.clientViolator, item: { s: "__storage__", pkg: "__client__", c: "__container__", files: [] } }
491
+ });
492
+ }
493
+ }
494
+ var adaptersOf = (e) => e.adapter === undefined ? [] : Array.isArray(e.adapter) ? e.adapter : [e.adapter];
495
+ var adapterWord = (a, one, many) => `${a.length === 1 ? one : many} ${a.join(", ")}`;
496
+ function c1(add, N, model, src) {
497
+ const ext = model.externals;
498
+ const inAdapters = (s, a) => owners(model, s.dir).some((m) => a.includes(m));
499
+ const files = (xs) => xs.map((s) => s.file).join(", ");
500
+ const hostOwner = new Map;
501
+ for (const [name, e] of Object.entries(ext))
502
+ for (const h of e.hosts ?? [])
503
+ hostOwner.set(h.toLowerCase(), name);
504
+ const hostFiles = new Map;
505
+ for (const s of src)
506
+ for (const h of s.hosts)
507
+ hostFiles.set(h, [...hostFiles.get(h) ?? [], s]);
508
+ const hosts = sorted([...new Set([...hostOwner.keys(), ...hostFiles.keys()])]);
509
+ if (hosts.length) {
510
+ add({
511
+ registry: N.spec.architecture.hostsRegistry,
512
+ items: hosts,
513
+ name: (h) => {
514
+ const x = hostOwner.get(h);
515
+ if (!x)
516
+ return locale_default.fill(N.spec.architecture.hostName, { host: h, adapters: locale_default.fill(N.spec.architecture.inAdapter, { list: "?" }) });
517
+ const a = adaptersOf(ext[x]);
518
+ const adapters = locale_default.fill(a.length === 1 ? N.spec.architecture.inAdapter : N.spec.architecture.inAdapters, { list: a.join(", ") });
519
+ return a.length ? locale_default.fill(N.spec.architecture.hostName, { host: h, adapters }) : locale_default.fill(N.spec.architecture.hostPerimeter, { host: h, system: x });
520
+ },
521
+ key: (h) => h,
522
+ check: (h) => {
523
+ const found = hostFiles.get(h) ?? [];
524
+ const x = hostOwner.get(h);
525
+ if (!x)
526
+ throw new Error(`host ${h} is declared by no external system of the model: ${files(found)}`);
527
+ const a = adaptersOf(ext[x]);
528
+ const outside = found.filter((s) => !inAdapters(s, a));
529
+ if (!outside.length)
530
+ return;
531
+ if (!a.length)
532
+ throw new Error(`external system ${x} has no adapter, yet host ${h} is in the code: ${files(outside)}`);
533
+ throw new Error(`host ${h} of external system ${x} is outside its ${adapterWord(a, "adapter", "adapters")}: ${files(outside)}`);
534
+ },
535
+ violator: { name: N.spec.architecture.hostViolator, item: "__outside_model__.example" }
536
+ });
537
+ }
538
+ const pkgs = Object.entries(ext).flatMap(([x, e]) => (e.packages ?? []).map((p) => ({ x, p, a: adaptersOf(e) })));
539
+ if (pkgs.length) {
540
+ add({
541
+ registry: N.spec.architecture.packagesRegistry,
542
+ items: pkgs,
543
+ name: (i) => i.a.length ? locale_default.fill(N.spec.architecture.packageName, { package: i.p, system: i.x, adapters: locale_default.fill(i.a.length === 1 ? N.spec.architecture.withAdapter : N.spec.architecture.withAdapters, { list: i.a.join(", ") }) }) : locale_default.fill(N.spec.architecture.packagePerimeter, { package: i.p, system: i.x }),
544
+ key: (i) => `${i.x}:${i.p}`,
545
+ check: (i) => {
546
+ const allowedTo = names(model).filter((n) => (model.modules[n].packages ?? []).includes(i.p));
547
+ if (!i.a.length) {
548
+ if (allowedTo.length)
549
+ throw new Error(`external system ${i.x} has no adapter, yet package ${i.p} is allowed for modules: ${allowedTo.join(", ")}`);
550
+ return;
551
+ }
552
+ if (!i.a.some((a) => allowedTo.includes(a))) {
553
+ throw new Error(`package ${i.p} of external system ${i.x} is not allowed in the model for ${i.a.length === 1 ? `adapter ${i.a[0]}` : `any of adapters ${i.a.join(", ")}`}`);
554
+ }
555
+ const others = allowedTo.filter((n) => !i.a.includes(n));
556
+ if (others.length)
557
+ throw new Error(`package ${i.p} of external system ${i.x} is allowed not only for ${i.a.length === 1 ? "the adapter" : "the adapters"}: ${others.join(", ")}`);
558
+ },
559
+ violator: { name: N.spec.architecture.packageViolator, item: { x: "__violator__", p: "__none__", a: ["__no_module__"] } }
560
+ });
561
+ }
562
+ const readOnlyByAdapters = (registry, noun, word, list, reads, fake) => {
563
+ const items = Object.entries(ext).flatMap(([x, e]) => (list(e) ?? []).map((k) => ({ x, k, a: adaptersOf(e) })));
564
+ if (!items.length)
565
+ return;
566
+ add({
567
+ registry,
568
+ items,
569
+ name: (i) => i.a.length ? locale_default.fill(i.a.length === 1 ? N.spec.architecture.readName : N.spec.architecture.readNameMany, { noun, key: i.k, system: i.x, list: i.a.join(", ") }) : locale_default.fill(N.spec.architecture.readPerimeter, { noun, key: i.k, system: i.x }),
570
+ key: (i) => `${i.x}:${i.k}`,
571
+ check: (i) => {
572
+ const outside = (i.src ?? src).filter((s) => reads(s, i.k) && !inAdapters(s, i.a));
573
+ if (!outside.length)
574
+ return;
575
+ if (!i.a.length)
576
+ throw new Error(`external system ${i.x} has no adapter, yet ${word} ${i.k} is read in the code: ${files(outside)}`);
577
+ throw new Error(`${word} ${i.k} of external system ${i.x} is read outside its ${adapterWord(i.a, "adapter", "adapters")}: ${files(outside)}`);
578
+ },
579
+ violator: {
580
+ name: locale_default.fill(N.spec.architecture.readViolator, { noun }),
581
+ item: { x: "__violator__", k: "__KEY__", a: ["__no_module__"], src: [{ file: "__violator__.ts", dir: "__outside_model__", packages: [], hosts: [], env: [], headers: [], ...fake }] }
582
+ }
583
+ });
584
+ };
585
+ readOnlyByAdapters(N.spec.architecture.envRegistry, N.spec.architecture.envNoun, "key", (e) => e.env, (s, k) => s.env.includes(k), { env: ["__KEY__"] });
586
+ readOnlyByAdapters(N.spec.architecture.headersRegistry, N.spec.architecture.headerNoun, "header", (e) => e.headers, (s, k) => s.headers.includes(k.toLowerCase()), { headers: ["__key__"] });
587
+ }
588
+ function networkGuard(model, fetchImpl = globalThis.fetch, opts = {}) {
589
+ const owner = new Map;
590
+ for (const [name, e] of Object.entries(model.externals ?? {}))
591
+ for (const h of e.hosts ?? [])
592
+ owner.set(h.toLowerCase(), name);
593
+ const allow = new Set((opts.allow ?? []).map((h) => h.toLowerCase()));
594
+ const rejected = [];
595
+ const guard = async (input, init) => {
596
+ const href = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
597
+ const host = new URL(href).hostname.toLowerCase();
598
+ if (isLocal(host) || allow.has(host))
599
+ return fetchImpl(input, init);
600
+ rejected.push(host);
601
+ const system = owner.get(host);
602
+ throw new Error(system ? `request to host ${host} of external system ${system} without a mock — mock the request: tests go to the network only to local addresses and allow` : `request to host ${host} outside the model — declare the external system in tests/architecture/model.ts and mock the request`);
603
+ };
604
+ const check = () => {
605
+ if (!rejected.length)
606
+ return;
607
+ const hosts = [...new Set(rejected)].join(", ");
608
+ rejected.length = 0;
609
+ throw new Error(`network requests rejected by the guard: ${hosts} — mock them (the code under test may have caught the fetch exception)`);
610
+ };
611
+ return Object.assign(guard, { rejected, check });
612
+ }
613
+ function cspConnectSrc(model, self = ["'self'"]) {
614
+ const hosts = sorted([...new Set(Object.values(model.externals ?? {}).flatMap((e) => (e.hosts ?? []).map((h) => h.toLowerCase())))]);
615
+ return ["connect-src", ...self, ...hosts.map((h) => `https://${h}`)].join(" ");
616
+ }
617
+ export {
618
+ adaptersOf,
619
+ architecture,
620
+ boundariesConfig,
621
+ cspConnectSrc,
622
+ deployUnits,
623
+ hostsIn,
624
+ importsIn,
625
+ moduleContainers,
626
+ networkGuard,
627
+ workspacePackages
628
+ };
@@ -0,0 +1,54 @@
1
+ // skills/spec/scripts/eslint-config.ts
2
+ import { readdirSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ var FRAGMENT = new Set(["eslint.ts", "eslint.mts", "eslint.mjs", "eslint.js"]);
6
+ var SKIP = new Set(["node_modules", ".git"]);
7
+ var SKIP_REL = new Set(["tests/lib"]);
8
+ function eslintFragments(root, dirs = ["tests"]) {
9
+ const out = [];
10
+ const walk = (dir) => {
11
+ let entries;
12
+ try {
13
+ entries = readdirSync(dir, { withFileTypes: true });
14
+ } catch {
15
+ return;
16
+ }
17
+ for (const e of entries) {
18
+ const full = path.join(dir, e.name);
19
+ if (e.isDirectory()) {
20
+ if (!SKIP.has(e.name) && !SKIP_REL.has(path.relative(root, full).split(path.sep).join("/")))
21
+ walk(full);
22
+ } else if (FRAGMENT.has(e.name))
23
+ out.push(full);
24
+ }
25
+ };
26
+ for (const d of dirs)
27
+ walk(path.join(root, d));
28
+ return out.sort();
29
+ }
30
+ async function collectEslint(root, dirs = ["tests"]) {
31
+ const configs = [{ linterOptions: { reportUnusedDisableDirectives: "error" } }];
32
+ for (const file of eslintFragments(root, dirs)) {
33
+ const mod = await import(pathToFileURL(file).href);
34
+ if (!mod.default)
35
+ throw new Error(`${path.relative(root, file)}: no export default — not a flat config fragment`);
36
+ configs.push(...Array.isArray(mod.default) ? mod.default : [mod.default]);
37
+ }
38
+ return configs;
39
+ }
40
+ var SPEC_PLUGIN = { meta: { name: "spec" }, rules: {} };
41
+ function restrict(o) {
42
+ SPEC_PLUGIN.rules[o.id] = {
43
+ meta: { type: "problem", schema: [], docs: { description: o.message } },
44
+ create(context) {
45
+ return Object.fromEntries(o.selectors.map((sel) => [sel, (node) => context.report({ node, message: o.message })]));
46
+ }
47
+ };
48
+ return [{ files: o.files, ...o.ignores ? { ignores: o.ignores } : {}, plugins: { spec: SPEC_PLUGIN }, rules: { [`spec/${o.id}`]: "error" } }];
49
+ }
50
+ export {
51
+ collectEslint,
52
+ eslintFragments,
53
+ restrict
54
+ };