@wirecat/cli-base 0.54.0 → 0.55.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/CHANGELOG.md CHANGED
@@ -8,6 +8,36 @@ Versions before 0.8.0 are in the [git tags](https://github.com/WireCatLabs/cli-b
8
8
  Every entry says what changed as a caller sees it, why, and what to watch for — the rules are
9
9
  [`docs/dev/CONVENTIONS.md`](docs/dev/CONVENTIONS.md#the-changelog).
10
10
 
11
+ ## 0.55.0 — 25.10.2026
12
+
13
+ ### Added
14
+
15
+ - **`WC-STORE-5` is checked** ([Store](docs/standards/store.md)): the shared `no-sqlite-driver` lint plugin, now in every repository's `config/wirecat/`, refuses a CLI's import of a SQLite driver or `drizzle-orm` outside tests and tooling scripts. cli-core turns it off. `WC-ARCH-21` now says it is in the handbook since 0.53.0, the first release that carried it.
16
+
17
+ - **`cli-dev changelog restore --land` puts the newest release's notes back to what shipped**
18
+ ([releases standard](docs/standards/releases.md#wc-rel-15--fold-a-late-pending-section-into-the-unpublished-version-before-publishing)).
19
+ A pull request checked before a release and merged after it adds its entry to that release's section, though its
20
+ code ships only in the next one, and no pull request check can see it. The command compares the newest tagged
21
+ version's section with its text in the tag; when they differ, it restores the tag's text and moves each entry the
22
+ tag lacks to the unreleased section, through a `chore(release): restore …` pull request. A CLI runs it at the start
23
+ of `bin/release`, before the fold, so the entry lands in the version that ships it. Older sections are not
24
+ compared.
25
+
26
+ ### Changed — may break callers
27
+
28
+ - **The changelog guard fails a pull request that adds a line to a dated section with no tag yet**
29
+ ([a released section is frozen](docs/standards/docs.md#wc-doc-20--a-released-section-is-frozen-and-a-new-entry-goes-under-an-existing-heading)),
30
+ unless its title has the `release` or `changelog` scope. Between the release pull request and the tag, a rebase onto
31
+ main puts a branch's new entries under the dated heading with no conflict, and when the release already runs
32
+ without that branch the note ships under the wrong version. Put the entry under the unreleased heading:
33
+ `bin/release` folds it into the dated version if it still publishes that commit (`chore(release): fold …`). A
34
+ repository picks this up when it moves its `changelog-guard.yml` pin to this version.
35
+
36
+ ### Fixed
37
+
38
+ - **The kit surface page links the two cli-template jobs** (cli-base's `template.yml`, cli-core's `parity.yml`), and
39
+ the roadmap no longer lists "a new CLI from a template" as next: it is `npx @wirecat/cli-template new`.
40
+
11
41
  ## 0.54.0 — 25.10.2026
12
42
 
13
43
  ### Added
@@ -13,6 +13,8 @@ export type Change = {
13
13
  line: string;
14
14
  };
15
15
  export declare const changelogChange: (text: string, version: string, unreleased: string) => Change | undefined;
16
+ /** Moves the entries that missed the newest tagged version to the unreleased section; see `restoreReleased`. */
17
+ export declare const restoreChange: (text: string, tagText: string, version: string, unreleased: string) => Change | undefined;
16
18
  export interface LandOptions {
17
19
  file: string;
18
20
  version: string;
@@ -20,10 +22,27 @@ export interface LandOptions {
20
22
  shell: Shell;
21
23
  log: (text: string) => void;
22
24
  }
25
+ /** Lands the changelog fold on `main` through a pull request; see `landChange`. */
26
+ export declare const landFold: ({ file, version, unreleased, shell, log }: LandOptions) => string | undefined;
23
27
  /**
24
- * Lands the changelog fold on `main` through a pull request. Each attempt folds on a fresh
25
- * `origin/main`; a pull request that main has since moved under (another session folded first, or
26
- * merged a change to CHANGELOG.md) is closed and the fold redone once. Returns the line to print, or
27
- * `undefined` when there was nothing to fold. Throws when it cannot land.
28
+ * Lands a changelog change on `main` through a pull request. Each attempt computes it on a fresh
29
+ * `origin/main`; a pull request that main has since moved under (another session landed first, or
30
+ * merged a change to CHANGELOG.md) is closed and the change redone once. Returns the line to print, or
31
+ * `undefined` when there was nothing to change. Throws when it cannot land.
28
32
  */
29
- export declare const landFold: ({ file, version, unreleased, shell, log }: LandOptions) => string | undefined;
33
+ export declare const landChange: ({ file, branch, change: changeOf, done, shell, log, }: {
34
+ file: string;
35
+ branch: string;
36
+ change: (text: string) => Change | undefined;
37
+ done: string;
38
+ shell: Shell;
39
+ log: (text: string) => void;
40
+ }) => string | undefined;
41
+ /**
42
+ * The newest `v<x.y.z>` tag in this commit's history and the changelog at it. Tags are fetched first: a clone made before the
43
+ * last release does not have its tag, and comparing against an older one would hide the newest drift.
44
+ */
45
+ export declare const newestTag: (shell: Shell, path: string) => {
46
+ version: string;
47
+ text: string;
48
+ };
package/dist/dev/land.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, writeFileSync } from "node:fs";
2
2
  import spawn from "cross-spawn";
3
- import { foldUnreleased, mergeRepeatedHeadings } from "../release/index.js";
3
+ import { foldUnreleased, mergeRepeatedHeadings, restoreReleased } from "../release/index.js";
4
4
  export const processShell = (cwd) => ({
5
5
  run: (command, args) => {
6
6
  const result = spawn.sync(command, [...args], { cwd, encoding: "utf8" });
@@ -28,14 +28,41 @@ export const changelogChange = (text, version, unreleased) => {
28
28
  line: `merged repeated headings in ${version}`,
29
29
  };
30
30
  };
31
+ /** Moves the entries that missed the newest tagged version to the unreleased section; see `restoreReleased`. */
32
+ export const restoreChange = (text, tagText, version, unreleased) => {
33
+ const restored = restoreReleased(text, tagText, version, unreleased);
34
+ if (!restored)
35
+ return undefined;
36
+ const moved = restored.moved.map((entry) => `\n${entry}`).join("");
37
+ return {
38
+ text: restored.text,
39
+ title: `chore(release): restore ${version}'s notes to its tag`,
40
+ body: `bin/release: CHANGELOG.md's ${version} section differed from the v${version} tag. It now matches the tag` +
41
+ (moved
42
+ ? `, and ${restored.moved.length} entr${restored.moved.length === 1 ? "y" : "ies"} that missed ${version} ` +
43
+ `moved to ${unreleased}:${moved}`
44
+ : ".") +
45
+ "\nOnly CHANGELOG.md changes.",
46
+ line: `restored ${version}'s notes to its tag${moved ? `; moved to ${unreleased}:${moved}` : ""}`,
47
+ };
48
+ };
31
49
  const ATTEMPTS = 2;
50
+ /** Lands the changelog fold on `main` through a pull request; see `landChange`. */
51
+ export const landFold = ({ file, version, unreleased, shell, log }) => landChange({
52
+ file,
53
+ branch: `release/fold-${version}`,
54
+ change: (text) => changelogChange(text, version, unreleased),
55
+ done: `main already has ${version}'s changelog folded`,
56
+ shell,
57
+ log,
58
+ });
32
59
  /**
33
- * Lands the changelog fold on `main` through a pull request. Each attempt folds on a fresh
34
- * `origin/main`; a pull request that main has since moved under (another session folded first, or
35
- * merged a change to CHANGELOG.md) is closed and the fold redone once. Returns the line to print, or
36
- * `undefined` when there was nothing to fold. Throws when it cannot land.
60
+ * Lands a changelog change on `main` through a pull request. Each attempt computes it on a fresh
61
+ * `origin/main`; a pull request that main has since moved under (another session landed first, or
62
+ * merged a change to CHANGELOG.md) is closed and the change redone once. Returns the line to print, or
63
+ * `undefined` when there was nothing to change. Throws when it cannot land.
37
64
  */
38
- export const landFold = ({ file, version, unreleased, shell, log }) => {
65
+ export const landChange = ({ file, branch, change: changeOf, done, shell, log, }) => {
39
66
  const must = (command, ...args) => {
40
67
  const result = shell.run(command, args);
41
68
  if (!result.ok)
@@ -51,7 +78,6 @@ export const landFold = ({ file, version, unreleased, shell, log }) => {
51
78
  }
52
79
  return "UNKNOWN";
53
80
  };
54
- const branch = `release/fold-${version}`;
55
81
  const abandon = (pr) => {
56
82
  log(`${pr} no longer merges into main — closing it and folding again on the new main`);
57
83
  must("gh", "pr", "close", pr, "--delete-branch");
@@ -60,10 +86,10 @@ export const landFold = ({ file, version, unreleased, shell, log }) => {
60
86
  for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
61
87
  must("git", "fetch", "-q", "origin");
62
88
  must("git", "merge", "-q", "--ff-only", "origin/main");
63
- const change = changelogChange(readFileSync(file, "utf8"), version, unreleased);
89
+ const change = changeOf(readFileSync(file, "utf8"));
64
90
  if (!change) {
65
91
  if (attempt > 1)
66
- log(`main already has ${version}'s changelog folded — nothing to land`);
92
+ log(`${done} — nothing to land`);
67
93
  return undefined;
68
94
  }
69
95
  writeFileSync(file, change.text);
@@ -95,5 +121,29 @@ export const landFold = ({ file, version, unreleased, shell, log }) => {
95
121
  must("git", "merge", "-q", "--ff-only", "origin/main");
96
122
  return `${change.line} (${pr})`;
97
123
  }
98
- throw new Error(`main moved under the fold twice — run bin/release again`);
124
+ throw new Error(`main moved under ${branch} twice — run bin/release again`);
125
+ };
126
+ /**
127
+ * The newest `v<x.y.z>` tag in this commit's history and the changelog at it. Tags are fetched first: a clone made before the
128
+ * last release does not have its tag, and comparing against an older one would hide the newest drift.
129
+ */
130
+ export const newestTag = (shell, path) => {
131
+ const fetched = shell.run("git", ["fetch", "-q", "--tags", "origin"]);
132
+ if (!fetched.ok)
133
+ throw new Error(`git fetch --tags failed: ${fetched.out}`);
134
+ const [version] = shell
135
+ .run("git", ["tag", "--list", "v*", "--merged", "HEAD"])
136
+ .out.split("\n")
137
+ .map((tag) => /^v(\d+\.\d+\.\d+)$/.exec(tag.trim())?.[1])
138
+ .filter((found) => found !== undefined)
139
+ .sort((a, b) => {
140
+ const [x, y] = [a, b].map((found) => found.split(".").map(Number));
141
+ return (y?.[0] ?? 0) - (x?.[0] ?? 0) || (y?.[1] ?? 0) - (x?.[1] ?? 0) || (y?.[2] ?? 0) - (x?.[2] ?? 0);
142
+ });
143
+ if (!version)
144
+ throw new Error("no v<x.y.z> tag to compare the changelog with");
145
+ const shown = shell.run("git", ["show", `v${version}:${path}`]);
146
+ if (!shown.ok)
147
+ throw new Error(`git show v${version}:${path} failed: ${shown.out}`);
148
+ return { version, text: shown.out };
99
149
  };
package/dist/dev/main.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
7
- import { isAbsolute, join } from "node:path";
7
+ import { isAbsolute, join, relative } from "node:path";
8
8
  import { pathToFileURL } from "node:url";
9
9
  import { parseArgs } from "node:util";
10
10
  import spawn from "cross-spawn";
@@ -18,7 +18,7 @@ import { issuesOf, RegistryError } from "../standards/registry.js";
18
18
  import { VOCABULARY } from "../standards/vocabulary.js";
19
19
  import { auditClis, scorecardTable, snakeKeys } from "./audit.js";
20
20
  import { readCapabilities } from "./capabilities.js";
21
- import { changelogChange, landFold, processShell } from "./land.js";
21
+ import { changelogChange, landChange, landFold, newestTag, processShell, restoreChange } from "./land.js";
22
22
  import { baselineOf, judge, lintCommands } from "./lint.js";
23
23
  import { nextVersion } from "./next-version.js";
24
24
  import { slowTests } from "./slow-tests.js";
@@ -36,6 +36,11 @@ const USAGE = `usage: cli-dev <command> [options]
36
36
  it changed the file, nothing otherwise. --land lands it on main
37
37
  through a pull request, folding again on the new main once if
38
38
  main moved under it. Run it only while that version is not on npm
39
+ changelog restore [--unreleased <title>] [--file <path>] [--land]
40
+ puts the newest tagged version's section back to its text in
41
+ the tag; an entry the tag lacks missed that release and moves
42
+ to the unreleased section. Prints a line when it changed the
43
+ file. --land lands it on main through a pull request
39
44
  docs-check [--rules <module>] [--ids <A,B>] [--pages]
40
45
  links, anchors and the changelog's shape; --pages also
41
46
  docs/ against docs/meta.json, the docs portal's structure
@@ -100,8 +105,8 @@ const docsCheck = async (args, io) => {
100
105
  };
101
106
  const changelog = (args, io) => {
102
107
  const [action, ...rest] = args;
103
- if (action !== "fold") {
104
- io.err(`cli-dev changelog: no action "${action ?? ""}" — the one there is: fold`);
108
+ if (action !== "fold" && action !== "restore") {
109
+ io.err(`cli-dev changelog: no action "${action ?? ""}" — the ones there are: fold, restore`);
105
110
  return 2;
106
111
  }
107
112
  const { values } = parseArgs({
@@ -114,8 +119,32 @@ const changelog = (args, io) => {
114
119
  },
115
120
  });
116
121
  const file = isAbsolute(values.file) ? values.file : join(io.cwd, values.file);
117
- const version = values.version ?? packageVersion(io.cwd);
118
122
  try {
123
+ if (action === "restore") {
124
+ const shell = processShell(io.cwd);
125
+ const tag = newestTag(shell, relative(io.cwd, file));
126
+ const change = (text) => restoreChange(text, tag.text, tag.version, values.unreleased);
127
+ if (values.land) {
128
+ const landed = landChange({
129
+ file,
130
+ branch: `release/restore-${tag.version}`,
131
+ change,
132
+ done: `main already has ${tag.version}'s notes as tagged`,
133
+ shell,
134
+ log: io.err,
135
+ });
136
+ if (landed)
137
+ io.out(landed);
138
+ return 0;
139
+ }
140
+ const restored = change(readFileSync(file, "utf8"));
141
+ if (!restored)
142
+ return 0;
143
+ writeFileSync(file, restored.text);
144
+ io.out(restored.line);
145
+ return 0;
146
+ }
147
+ const version = values.version ?? packageVersion(io.cwd);
119
148
  if (values.land) {
120
149
  const landed = landFold({
121
150
  file,
@@ -13,3 +13,14 @@ export declare const foldUnreleased: (text: string, version: string, unreleased?
13
13
  * when no heading repeats there. The caller makes sure the version is not on npm.
14
14
  */
15
15
  export declare const mergeRepeatedHeadings: (text: string, version: string) => string | undefined;
16
+ /**
17
+ * Puts the section of `version`, the newest tagged one, back to its text in that tag. A pull request
18
+ * checked before the version was dated or tagged and merged after it adds its entry there with no
19
+ * guard to see it, though that code ships only in the next version: each such entry moves to the unreleased
20
+ * section, after its entries under the same `###` heading. `undefined` when the section matches the tag the
21
+ * way the changelog guard compares them.
22
+ */
23
+ export declare const restoreReleased: (text: string, tagText: string, version: string, unreleased?: string) => {
24
+ text: string;
25
+ moved: string[];
26
+ } | undefined;
@@ -66,3 +66,56 @@ export const mergeRepeatedHeadings = (text, version) => {
66
66
  return undefined;
67
67
  return [preamble, render(section.title, [section]), ...rest].join("\n");
68
68
  };
69
+ const body = (block) => block
70
+ .split("\n")
71
+ .slice(1)
72
+ .map((line) => line.trimEnd())
73
+ .join("\n")
74
+ .trim();
75
+ const entries = (section) => [...section.headings].flatMap(([heading, lines]) => {
76
+ const found = [];
77
+ for (const line of lines) {
78
+ if (!line.trim())
79
+ continue;
80
+ const last = found.at(-1);
81
+ if (line.startsWith("- ") || !last)
82
+ found.push({ heading, text: line });
83
+ else
84
+ last.text += `\n${line}`;
85
+ }
86
+ return found;
87
+ });
88
+ /**
89
+ * Puts the section of `version`, the newest tagged one, back to its text in that tag. A pull request
90
+ * checked before the version was dated or tagged and merged after it adds its entry there with no
91
+ * guard to see it, though that code ships only in the next version: each such entry moves to the unreleased
92
+ * section, after its entries under the same `###` heading. `undefined` when the section matches the tag the
93
+ * way the changelog guard compares them.
94
+ */
95
+ export const restoreReleased = (text, tagText, version, unreleased = "Unreleased") => {
96
+ const dated = (block) => block.startsWith(`## ${version} — `);
97
+ const tagged = tagText.split(/\n(?=## )/).find(dated);
98
+ if (!tagged)
99
+ throw new Error(`the v${version} tag's changelog has no "## ${version} — " section`);
100
+ const blocks = text.split(/\n(?=## )/);
101
+ const index = blocks.findIndex(dated);
102
+ if (index < 0)
103
+ throw new Error(`no "## ${version} — " section to compare with the v${version} tag`);
104
+ const current = blocks[index] ?? "";
105
+ if (current.split("\n")[0]?.trimEnd() === tagged.split("\n")[0]?.trimEnd() && body(current) === body(tagged))
106
+ return undefined;
107
+ const shipped = new Set(entries(parseSection(tagged)).map((entry) => entry.text));
108
+ const missed = entries(parseSection(current)).filter((entry) => !shipped.has(entry.text));
109
+ blocks[index] = tagged.replace(/\n+$/, current.endsWith("\n") ? "\n" : "");
110
+ if (missed.length > 0) {
111
+ const headings = new Map();
112
+ for (const { heading, text: entry } of missed)
113
+ headings.set(heading, [...(headings.get(heading) ?? []), entry]);
114
+ const moved = { title: "", headings, repeated: false };
115
+ const top = blocks[1];
116
+ const open = top === `## ${unreleased}` || top?.startsWith(`## ${unreleased}\n`);
117
+ const section = render(`## ${unreleased}`, open && top ? [parseSection(top), moved] : [moved]);
118
+ blocks.splice(1, open ? 1 : 0, section);
119
+ }
120
+ return { text: blocks.join("\n"), moved: missed.map((entry) => entry.text.split("\n")[0] ?? "") };
121
+ };
@@ -1,4 +1,4 @@
1
- export { foldUnreleased, mergeRepeatedHeadings } from "./fold.js";
1
+ export { foldUnreleased, mergeRepeatedHeadings, restoreReleased } from "./fold.js";
2
2
  export { type ChangelogRules, changelogProblems, type DocsRules, docsProblems, idPattern, JOURNAL_IDS, markdownFiles, slug, } from "./markdown.js";
3
3
  export { REQUIRED_PAGES, type StructureRules, structureProblems } from "./structure.js";
4
4
  export type Check = {
@@ -6,7 +6,7 @@
6
6
  import { spawnSync } from "node:child_process";
7
7
  import { readFileSync, writeFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
- export { foldUnreleased, mergeRepeatedHeadings } from "./fold.js";
9
+ export { foldUnreleased, mergeRepeatedHeadings, restoreReleased } from "./fold.js";
10
10
  export { changelogProblems, docsProblems, idPattern, JOURNAL_IDS, markdownFiles, slug, } from "./markdown.js";
11
11
  export { REQUIRED_PAGES, structureProblems } from "./structure.js";
12
12
  /**
@@ -219,7 +219,7 @@ out of search and listed under `skipped`, with the hint to run that CLI once.
219
219
  `skipped` instead of reading their rows as messages.
220
220
  - **Exceptions:** the built-in providers of cli-core need no entry. Two entries that disagree on a kind are an error
221
221
  naming both.
222
- - **Applies to:** cli-core and every CLI with a provider of its own. **Since:** cli-base 0.53.1. **Rulings:** none
222
+ - **Applies to:** cli-core and every CLI with a provider of its own. **Since:** cli-base 0.53.0. **Rulings:** none
223
223
  (Wave 3 requirements 3.2).
224
224
 
225
225
  ## Sources
@@ -198,7 +198,11 @@ Rule ids on this page are `WC-DOC-<n>`. How to read a rule, how ids work and whi
198
198
  - **Enforced by:** `changelog-guard.yml`.
199
199
  - **Example:** the guard fails a change to a section whose `v<version>` tag exists, unless the change restores the
200
200
  exact text in that tag. It also fails a new repeated `###` heading in any section that has no tag yet.
201
- - **Exceptions:** a dated section with no tag is still open to fixes; a release is tagged only after npm has it.
201
+ A pull request whose title has no `release` or `changelog` scope also fails when it adds a line to a dated section
202
+ the base already had: a rebase onto a just-dated release moves its new entries there with no conflict.
203
+ - **Exceptions:** a dated section with no tag is still open to fixes from a title with the `release` or `changelog`
204
+ scope (`chore(release): fold …`, `docs(changelog): …`); a release is tagged only after npm has it.
205
+ Sections that already differed from their tag on 25.10.2026 stay as they are, and nothing reports them.
202
206
  - **Applies to:** every repository that calls the changelog guard. **Since:** cli-base 0.41.0. **Rulings:** none.
203
207
 
204
208
  ## Sources
@@ -30,8 +30,11 @@ A change that makes a CLI change its code for one of these exports ships with tw
30
30
 
31
31
  1. **A migration note in the changelog**, under "Changed — may break callers": what to change in your code, step by
32
32
  step. See [Documents](docs.md) for the changelog's shape.
33
- 2. **A green cli-template job before the pull request merges.** cli-base and cli-core build the template against
34
- each pull request, so the template is updated in the same change, and `upgrade` has the new version to bring.
33
+ 2. **A green cli-template job before the pull request merges.** cli-base
34
+ ([`template.yml`](https://github.com/WireCatLabs/cli-base/blob/main/.github/workflows/template.yml)) and cli-core
35
+ (the `template` job in [`parity.yml`](https://github.com/WireCatLabs/cli-core/blob/main/.github/workflows/parity.yml))
36
+ build the template against each pull request, so the template is updated in the same change, and `upgrade` has
37
+ the new version to bring.
35
38
 
36
39
  ## How your CLI stays current
37
40
 
@@ -167,9 +167,13 @@ on the [CI](ci.md) page.
167
167
  - **Why:** a pull request merged after the version was dated adds a pending section above it, and that change must
168
168
  ship in the version it lands in.
169
169
  - **Enforced by:** review only.
170
- - **Example:** `bin/release` runs `cli-dev changelog fold --land --version <x.y.z>` while npm does not have the version.
171
- It folds the pending section into the dated one, and merges any repeated `###` heading, through a pull request.
172
- - **Exceptions:** none.
170
+ - **Example:** `bin/release` first runs `cli-dev changelog restore --land`. It compares the newest tagged version's
171
+ section with its text in the tag: a pull request checked before that release and merged after it put its entry
172
+ there, though its code ships only now. The section goes back to the tag's text and the entry moves to the pending
173
+ section, through a pull request. Then, while npm does not have the version, `bin/release` runs
174
+ `cli-dev changelog fold --land --version <x.y.z>`. It folds the pending section into the dated one, and merges any
175
+ repeated `###` heading, through a pull request.
176
+ - **Exceptions:** only the newest tag is compared; an older section that differs from its tag stays as it is.
173
177
  - **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
174
178
 
175
179
  ### WC-REL-16 · The package ships only the files it names
@@ -69,11 +69,12 @@ Related pages: which account a read covers, and how a read across accounts is as
69
69
  ### WC-STORE-5 · A CLI writes no SQL against the shared store
70
70
 
71
71
  - **Why:** the schema belongs to cli-core; SQL in a CLI breaks on the next migration.
72
- - **Enforced by:** review only.
72
+ - **Enforced by:** `<app>: biome no-sqlite-driver`.
73
73
  - **Example:** a CLI that needs a new query asks cli-core for an area method, and the method lands in cli-core with
74
74
  its test. drive and zoom state "no SQL here" in their own architecture pages.
75
75
  - **Exceptions:** a provider engine's own file of keys and device state, outside `wirecat.db`
76
- ([WC-STORE-13](#wc-store-13--keep-keys-and-device-state-out-of-the-shared-store)).
76
+ ([WC-STORE-13](#wc-store-13--keep-keys-and-device-state-out-of-the-shared-store)). The check skips tests and tooling
77
+ (`scripts/`, `bench/`, `performance/`, `contracts/`, `testing/`), and cli-core, which owns the store, turns it off.
77
78
  - **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-383.
78
79
 
79
80
  ### WC-STORE-6 · One store method is one whole operation, with no `await` inside a transaction
@@ -193,8 +194,6 @@ request that builds the helper.
193
194
  - **A CLI's own tables live in its own SQLite file**, next to the shared store, with its own migrations. They refer to
194
195
  core rows by id only, and no other CLI opens that file. A cli-core helper opens it (the path, the migrations, the
195
196
  same driver); it is built when the first CLI needs private tables.
196
- - **WC-STORE-5 gets a check:** a grit plugin, `no-sqlite-driver`, that refuses a CLI's import of a SQLite driver or
197
- of `drizzle-orm`. cli-core, which owns the store, turns it off.
198
197
 
199
198
  ## Sources
200
199
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-base",
3
- "version": "0.54.0",
3
+ "version": "0.55.0",
4
4
  "description": "The parts every command line tool needs: output modes, terminal rendering, error model, exit codes, credentials, clocks",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",