@miroshnik/elves 2026.1026.0 → 2026.1026.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.
@@ -2,7 +2,7 @@
2
2
  import { readdirSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
- var FRAGMENT = new Set(["eslint.ts", "eslint.mts", "eslint.mjs", "eslint.js"]);
5
+ var FRAGMENT = /^(?:[^.]+\.)?eslint\.(?:ts|mts|mjs|js)$/;
6
6
  var SKIP = new Set(["node_modules", ".git"]);
7
7
  var SKIP_REL = new Set(["tests/lib"]);
8
8
  function eslintFragments(root, dirs = ["tests"]) {
@@ -19,7 +19,7 @@ function eslintFragments(root, dirs = ["tests"]) {
19
19
  if (e.isDirectory()) {
20
20
  if (!SKIP.has(e.name) && !SKIP_REL.has(path.relative(root, full).split(path.sep).join("/")))
21
21
  walk(full);
22
- } else if (FRAGMENT.has(e.name))
22
+ } else if (FRAGMENT.test(e.name))
23
23
  out.push(full);
24
24
  }
25
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miroshnik/elves",
3
- "version": "2026.1026.0",
3
+ "version": "2026.1026.1",
4
4
  "type": "module",
5
5
  "description": "Elves — a development flow with AI agents: shared rules and skills for any agent; install — bunx @miroshnik/elves install -g; the installer and skill scripts — TypeScript under Bun, tests — bun test in tests/",
6
6
  "repository": {
@@ -72,5 +72,5 @@
72
72
  "eslint-plugin-boundaries": "^7.2.0",
73
73
  "knip": "^6.41.0"
74
74
  },
75
- "gitHead": "c5e6bd32225415d44c4e961766e72a639142fd1e"
75
+ "gitHead": "6c5389f526d10b2b11b78bd6a8bdb6d31a6821a9"
76
76
  }
@@ -183,7 +183,7 @@ each one — `checks.md` next to this file:
183
183
 
184
184
  - a convention on every item of a registry from the code — `invariant`
185
185
  (`standards/<name>/`);
186
- - a lint rule — `examples`, `eslintLinter`, the folder's `eslint.ts` fragment
186
+ - a lint rule — `examples`, `eslintLinter`, an `eslint.ts` or `<section>.eslint.ts` fragment
187
187
  collected by `collectEslint`;
188
188
  - out of scope and exceptions — `fileOf`, marks `spec-outside(<decision>)` /
189
189
  `spec-exception(<decision>) #N`, `exceptionsIn()`, `lintExceptions`;
@@ -44,8 +44,8 @@ no decision claims is dead and is deleted.
44
44
  - a topic of one section has the same anatomy — `<topic>.md` and a section
45
45
  with a name of its own; a test file named after its folder
46
46
  (`<name>/<name>.test.ts`) is a decision not regrouped yet;
47
- - the mechanism — in the same folder: an `eslint.ts` fragment, an ast-grep
48
- `rule.yml`. The project's tool configs (`eslint.config.*`, `sgconfig.yml`)
47
+ - the mechanism — in the same folder: an ESLint fragment (the topic's
48
+ `eslint.ts`, a section's `<section>.eslint.ts`), an ast-grep `rule.yml`. The project's tool configs (`eslint.config.*`, `sgconfig.yml`)
49
49
  only collect them from the tree (the harness's `collectEslint`) — a rule
50
50
  has one home;
51
51
  - `exceptions/<section>/` — exceptions for elements without a file of their
@@ -7,8 +7,8 @@ Which check for what:
7
7
  | What to check | With what | Where in `tests/` |
8
8
  |---|---|---|
9
9
  | A convention on every item of a registry from the code ("every mutation writes an audit record") | `invariant` | `standards/<name>/` |
10
- | A lint rule: «not allowed», «allowed», «outside scope» | `examples`, `eslintLinter` | `standards/<name>/` + an `eslint.ts` fragment |
11
- | An ESLint rule in a standard's folder | `collectEslint` in `eslint.config.*` | the tree's `eslint.ts` fragments |
10
+ | A lint rule: «not allowed», «allowed», «outside scope» | `examples`, `eslintLinter` | `standards/<topic>/` + an ESLint fragment (`eslint.ts`, `<section>.eslint.ts`) |
11
+ | An ESLint rule in a standard's folder | `collectEslint` in `eslint.config.*` | the tree's `eslint.ts` and `<section>.eslint.ts` fragments |
12
12
  | Out of scope and an item's exception with its own file | `fileOf` in `invariant`; a mark `spec-outside(<decision>)` / `spec-exception(<decision>) #N` in the file (`marksIn`), in JSON — the value of the `"//"` key | the item's file (a route, a script, a migration, a workspace package's `package.json`) |
13
13
  | Exceptions with a ratchet | `exceptionsIn()` → `exceptions` in `invariant`; a disable `-- #N reason` + `lintExceptions` | the decision folder's `exceptions/<item>.json`; migration — `spec-exceptions` |
14
14
  | Architecture model: module boundaries, directories, packages | `boundariesConfig`, `architecture` | `architecture/model.ts`, `architecture/<name>/` |
@@ -142,7 +142,8 @@ Biome will be added when a project needs them (the core doesn't depend on the
142
142
  linter, `Linter` is an interface with one required method, `lint`).
143
143
 
144
144
  **A rule lives in its standard's folder.** A flat config fragment is
145
- `eslint.ts` next to the test (`export default [{ files: ["src/domain/**"],
145
+ the topic's `eslint.ts` or a section's `<section>.eslint.ts` — each standard
146
+ of a topic keeps its own — next to the test (`export default [{ files: ["src/domain/**"],
146
147
  rules: {…} }]`, paths from the project root); the project's `eslint.config.*`
147
148
  only collects the tree's fragments — `lint` and the editor see the rule as
148
149
  usual:
@@ -168,7 +169,9 @@ export default restrict({ id: "no-alert", selectors: ["CallExpression[callee.nam
168
169
 
169
170
  A fragment is `eslint.ts`, `eslint.mts` (ESM even in a project without
170
171
  `"type": "module"`, without the `MODULE_TYPELESS_PACKAGE_JSON` warning),
171
- `eslint.mjs` or `eslint.js`; `tests/lib` holds helpers, not fragments.
172
+ `eslint.mjs` or `eslint.js`, a section's — the same under its prefix
173
+ (`<section>.eslint.mts`); a knip entry for the fragments —
174
+ `tests/**/*eslint.{ts,mts}`; `tests/lib` holds helpers, not fragments.
172
175
 
173
176
  **Exceptions are explicit, have a task, and go away when no longer needed.**
174
177
 
@@ -143,7 +143,7 @@ a flat decision as a topic of one section. One task, one PR, in this order:
143
143
  `<topic>/<name>.md` (the other `<name>.*` files alongside), another test
144
144
  file of the folder `<name>/<file>.test.ts` → `<topic>/<name>.<file>.test.ts`
145
145
  (by its own name it would be a section of its own), a helper file under the
146
- section's prefix (`<name>.preload.ts`), `<name>/exceptions/*.json` → `<topic>/exceptions/<name>/`, a package
146
+ section's prefix (`<name>.preload.ts`, the ESLint fragment `<name>/eslint.ts` → `<topic>/<name>.eslint.ts`), `<name>/exceptions/*.json` → `<topic>/exceptions/<name>/`, a package
147
147
  subfolder `<name>/<package>/` → `<topic>/<package>/`. The moves go in a
148
148
  commit of their own, without content edits, so history follows the files
149
149
  and `pr labels` reads them as mechanical. Each topic gets `<topic>.md`: its
@@ -1,7 +1,8 @@
1
1
  /**
2
- * eslint-config — assembling ESLint rules from the spec tree: a standard's rule lives as an `eslint.ts` fragment
3
- * (default — a flat config object or array) in its folder, next to the examples and the reason, and the project's
4
- * `eslint.config.*` only collects the fragments — `lint` and the editor see the rule as usual:
2
+ * eslint-config — assembling ESLint rules from the spec tree: a standard's rule lives as a fragment (default — a flat
3
+ * config object or array) in its topic folder, next to the examples and the reason — the topic's `eslint.ts` or a
4
+ * section's `<section>.eslint.ts`, so each standard of a topic keeps its own —, and the project's `eslint.config.*`
5
+ * only collects the fragments — `lint` and the editor see the rule as usual:
5
6
  *
6
7
  * import { collectEslint } from "@miroshnik/elves/spec/eslint-config";
7
8
  * export default [...base, ...(await collectEslint(import.meta.dirname))];
@@ -20,12 +21,13 @@ import { readdirSync } from "node:fs";
20
21
  import path from "node:path";
21
22
  import { pathToFileURL } from "node:url";
22
23
 
23
- const FRAGMENT = new Set(["eslint.ts", "eslint.mts", "eslint.mjs", "eslint.js"]);
24
+ /** A topic's fragment `eslint.ts` or a section's `<section>.eslint.ts` (`.mts`, `.mjs`, `.js` too). */
25
+ const FRAGMENT = /^(?:[^.]+\.)?eslint\.(?:ts|mts|mjs|js)$/;
24
26
  const SKIP = new Set(["node_modules", ".git"]);
25
27
  // test helpers, not the spec: `spec-doc` skips them too
26
28
  const SKIP_REL = new Set(["tests/lib"]);
27
29
 
28
- /** Paths of `eslint.ts` fragments in the directories (by default `tests/`), in order. */
30
+ /** Paths of the topics' `eslint.ts` and the sections' `<section>.eslint.ts` fragments in the directories (by default `tests/`), in order. */
29
31
  export function eslintFragments(root: string, dirs: string[] = ["tests"]): string[] {
30
32
  const out: string[] = [];
31
33
  const walk = (dir: string): void => {
@@ -39,7 +41,7 @@ export function eslintFragments(root: string, dirs: string[] = ["tests"]): strin
39
41
  const full = path.join(dir, e.name);
40
42
  if (e.isDirectory()) {
41
43
  if (!SKIP.has(e.name) && !SKIP_REL.has(path.relative(root, full).split(path.sep).join("/"))) walk(full);
42
- } else if (FRAGMENT.has(e.name)) out.push(full);
44
+ } else if (FRAGMENT.test(e.name)) out.push(full);
43
45
  }
44
46
  };
45
47
  for (const d of dirs) walk(path.join(root, d));
@@ -67,7 +69,7 @@ const SPEC_PLUGIN: { meta: { name: string }; rules: Record<string, object> } = {
67
69
 
68
70
  /**
69
71
  * A standard's ban by AST selectors (as in `no-restricted-syntax`) — as its own rule `spec/<id>` in the shared `spec`
70
- * plugin: bans of different standards on the same files all apply. A standard's `eslint.ts` fragment:
72
+ * plugin: bans of different standards on the same files all apply. A standard's fragment (`eslint.ts`, `<section>.eslint.ts`):
71
73
  *
72
74
  * export default restrict({ id: "no-alert", selectors: ["CallExpression[callee.name='alert']"], message: "…", files: ["src/**"] });
73
75
  */