@panaversity/ksor 0.0.2 → 0.0.4

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 (31) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +6 -4
  3. package/dist/cli.mjs +6348 -8
  4. package/dist/index.d.mts +3 -2
  5. package/dist/index.mjs +1 -36
  6. package/dist/src-CpDIVudJ.mjs +43 -0
  7. package/docs/index.md +10 -4
  8. package/package.json +18 -5
  9. package/schema/schema.sql +316 -0
  10. package/templates/scaffold/.agents/skills/add-sources/SKILL.md +4 -1
  11. package/templates/scaffold/.agents/skills/format-checker/SKILL.md +8 -1
  12. package/templates/scaffold/.agents/skills/format-checker/check.mjs +241 -9
  13. package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +27 -7
  14. package/templates/scaffold/.claude/skills/add-sources/SKILL.md +4 -1
  15. package/templates/scaffold/.claude/skills/format-checker/SKILL.md +8 -1
  16. package/templates/scaffold/.claude/skills/format-checker/check.mjs +241 -9
  17. package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +27 -7
  18. package/templates/scaffold/AGENTS.md +137 -14
  19. package/templates/scaffold/README.md +55 -3
  20. package/templates/scaffold/gitignore +3 -0
  21. package/templates/scaffold/instance.md +3 -2
  22. package/templates/scaffold/package.json +8 -1
  23. package/templates/scaffold/pnpm-workspace.yaml +21 -5
  24. package/templates/scaffold/system/site/app/(home)/page.tsx +2 -2
  25. package/templates/scaffold/system/site/app/docs/layout.tsx +2 -2
  26. package/templates/scaffold/system/site/components/footer-mark.tsx +22 -0
  27. package/templates/scaffold/system/site/lib/audience.ts +178 -0
  28. package/templates/scaffold/system/site/lib/shared.ts +14 -5
  29. package/templates/scaffold/system/site/lib/stage-knowledge.ts +301 -0
  30. package/templates/scaffold/system/site/source.config.ts +7 -1
  31. package/templates/scaffold/vercel.json +8 -0
@@ -18,7 +18,32 @@ pnpm dev # browse the knowledge at http://localhost:3000
18
18
  ```
19
19
 
20
20
  No pnpm? Run `npm install -g pnpm` — or `corepack enable pnpm` on Node
21
- versions that bundle corepack.
21
+ versions that bundle corepack. The first `pnpm install` also fetches the
22
+ `ksor` tool (pinned in `package.json`) and writes it into your lockfile —
23
+ commit the updated lockfile.
24
+
25
+ ### Serving to agents
26
+
27
+ The record's other surface is an MCP server for AI agents — the same
28
+ knowledge, cited, with honest abstention. It is the climbed rung: it needs a
29
+ Postgres store (with pgvector) and an embedding provider key, so it is not
30
+ part of `pnpm dev`. The ordered path is:
31
+
32
+ ```sh
33
+ export KSOR_DB_URL='postgresql://…' # the DSN var your instance.md names
34
+ export GEMINI_API_KEY='…' # the embedding provider key
35
+ pnpm schema # apply the database schema (once)
36
+ pnpm grant # authorize ingest for this corpus (once)
37
+ pnpm ingest # embed knowledge/ into a generation and activate it
38
+ pnpm serve # run the MCP server over the record
39
+ ```
40
+
41
+ One setup step comes before this — adding the `database:`/`embedding:` blocks
42
+ to `instance.md` — and there is more to know about the generation model and the
43
+ fail-closed security posture. `AGENTS.md` → "Serving to agents" is the
44
+ full runbook; your coding agent reads it first. `pnpm serve` binds loopback
45
+ with auth off for local use; a public bind fails closed unless auth is
46
+ configured. Any other operation is `pnpm exec ksor <verb>`.
22
47
 
23
48
  Then talk to your coding agent — `AGENTS.md` carries the working rules, and
24
49
  the agent kit in `.agents/skills/` knows how to interview you
@@ -34,7 +59,7 @@ different coding agent's way of finding the same working contract.
34
59
  | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
35
60
  | `knowledge/` | **the record** — your governed markdown. The product; everything else serves it. |
36
61
  | `system/` | the code that serves the record: the site today, more as you need it. |
37
- | `instance.md` | what this record is authoritative for; its `name:` is the identity every surface publishes (read at server/build start — restart `pnpm dev` after renaming). When the agent surface ships, this prose becomes its system prompt. |
62
+ | `instance.md` | what this record is authoritative for; its `name:` is the identity every surface publishes (read at server/build start — restart `pnpm dev` after renaming). This prose IS the agent surface's system prompt `ksor serve` wires it into the MCP server's instructions. |
38
63
  | `AGENTS.md` | the working contract every coding agent reads first — the rules for writing knowledge here. |
39
64
  | `CLAUDE.md` | one line, pointing at `AGENTS.md`. Claude Code looks for this filename, not that one. |
40
65
  | `.agents/skills/` | the agent kit: `intake-interview` (define the record with you), `add-sources` (turn source material into governed documents), `format-checker` (the rules, as a program). |
@@ -43,7 +68,7 @@ different coding agent's way of finding the same working contract.
43
68
  | `.github/workflows/validate.yml` | your CI: runs the same checker on every pull request and push to main. |
44
69
  | `.gitattributes` | markdown is checked out byte-stable on every platform, so the same commit hashes the same everywhere. |
45
70
  | `.gitignore` | keeps build output, `node_modules/`, and `.env*` out of the record's history. |
46
- | `package.json` | the `pnpm dev` / `pnpm build` / `pnpm check` commands, and the pnpm version this project pins. |
71
+ | `package.json` | the `pnpm dev` / `pnpm build` / `pnpm check` / `pnpm schema` / `pnpm grant` / `pnpm ingest` / `pnpm serve` commands, the pinned `@panaversity/ksor` tool, and the pnpm version this project pins. |
47
72
  | `pnpm-workspace.yaml` | where the workspace looks for code (`system/site`, plus reserved `system/gateways/*` and `system/packages/*`), and the supply-chain policy for installs. |
48
73
  | `pnpm-lock.yaml` | the exact dependency versions — the reason two machines build the same site. |
49
74
 
@@ -55,6 +80,33 @@ and how to fix it.
55
80
  Everything here is yours to change. The kit exists so that any coding agent can
56
81
  operate this project without being taught it first.
57
82
 
83
+ ## Deploying
84
+
85
+ The built site is a folder of files — 2 MB of HTML, JS and CSS with zero
86
+ host-specific dependencies. `pnpm build` writes it to `system/site/out/`,
87
+ and anything that can serve files can serve it.
88
+
89
+ - **Vercel** — connect the repository (or run `vercel`); the shipped
90
+ `vercel.json` answers the setup interview: deploy from the repo root
91
+ (never pin `system/site` as the root directory — the record lives
92
+ outside it), build with `pnpm build`, serve `system/site/out/`. If the
93
+ build image's pnpm predates the `packageManager` pin, set the
94
+ `ENABLE_EXPERIMENTAL_COREPACK=1` build environment variable.
95
+ - **GitHub Pages, nginx, S3, anything static** — run `pnpm build` and
96
+ upload `system/site/out/`. Hosted under a sub-path (like
97
+ `user.github.io/repo`)? Build with `KSOR_BASE_PATH=/repo pnpm build`.
98
+ - **Verify any deploy** the same way: the home page, one document page,
99
+ and `/llms.txt` all load; nothing else is required.
100
+
101
+ If `instance.md` declares `audiences:`, what you deploy is a **tier**.
102
+ Plain `pnpm build` always builds the public tier — safe for any host.
103
+ `KSOR_AUDIENCE=<audience> pnpm build` builds a wider tier for that
104
+ audience's own deployment, and that build carries an
105
+ "— not for publication" label because it must never reach a public host:
106
+ put it behind access control you already trust (VPN, SSO proxy,
107
+ authenticated host). The tiers govern what a build contains; where each
108
+ build may be served is yours to enforce.
109
+
58
110
  ## Ownership
59
111
 
60
112
  Everything here is yours. The scaffold was generated by
@@ -5,6 +5,9 @@ node_modules/
5
5
  system/site/.next/
6
6
  system/site/.source/
7
7
  system/site/out/
8
+ # the per-audience copy of the record a build stages — a filtered derivative,
9
+ # never a second record; committing it would publish what a build excluded
10
+ system/site/.staged-knowledge/
8
11
  *.tsbuildinfo
9
12
 
10
13
  # secrets never enter the record — system/ is their future home (serve)
@@ -18,8 +18,9 @@ the single most important sentence in the project._
18
18
 
19
19
  Everything below this frontmatter is the identity of this instance: what the
20
20
  corpus covers, who it serves, and how strictly it should decline questions it
21
- does not cover. When the agent surface ships, this prose becomes its system
22
- prompt — write it for a reader who must act on it.
21
+ does not cover. This prose IS the agent surface's system prompt `ksor serve`
22
+ wires it into the MCP server's instructions so write it for a reader who must
23
+ act on it.
23
24
 
24
25
  Ask your coding agent to run the **intake interview** (it knows how — see
25
26
  `.agents/skills/intake-interview/`), answer its questions, and let it write
@@ -6,7 +6,14 @@
6
6
  "scripts": {
7
7
  "dev": "pnpm -C system/site dev",
8
8
  "build": "pnpm -C system/site build",
9
- "check": "node .agents/skills/format-checker/check.mjs"
9
+ "check": "node .agents/skills/format-checker/check.mjs",
10
+ "schema": "ksor schema --instance instance.md --apply",
11
+ "grant": "ksor grant --instance instance.md",
12
+ "ingest": "ksor ingest --instance instance.md --knowledge knowledge --flip",
13
+ "serve": "ksor serve"
14
+ },
15
+ "dependencies": {
16
+ "@panaversity/ksor": "KSOR-STAMP-VERSION"
10
17
  },
11
18
  "engines": {
12
19
  "node": ">=24"
@@ -8,12 +8,28 @@ packages:
8
8
  # `pnpm install` here never picks up a day-zero compromised release.
9
9
  minimumReleaseAge: 2880
10
10
 
11
+ # The one exception is the tool that scaffolded this project: `ksor init` pins
12
+ # `@panaversity/ksor` to the EXACT version the adopter deliberately ran, so the
13
+ # quarantine (a defense against unknown third-party releases) must not block the
14
+ # first install for 48 hours after every ksor release. Remove this line only if
15
+ # you unpin the dependency.
16
+ minimumReleaseAgeExclude:
17
+ - "@panaversity/ksor"
18
+
11
19
  # Dependency install scripts are denied by default. Flip an entry to true
12
- # only with a comment naming what breaks without it. esbuild and sharp are
13
- # reviewed and stay denied: both ship prebuilt platform binaries as
14
- # optionalDependencies, so their install scripts are download fallbacks the
15
- # site never needs — but pnpm 11 exits 1 on every install/dev until each is
16
- # explicitly decided. (found live: fresh-scaffold pnpm dev, 2026-08-18)
20
+ # only with a comment naming what breaks without it. pnpm 11 exits 1 on every
21
+ # install until each build script is explicitly decided (found live:
22
+ # fresh-scaffold pnpm dev, 2026-08-18). All four below are reviewed and stay
23
+ # denied:
24
+ # esbuild, sharp prebuilt platform binaries ship as optionalDependencies,
25
+ # so their install scripts are download fallbacks the site never needs.
26
+ # @google/genai, protobufjs — pulled in by the `@panaversity/ksor` serve
27
+ # tool. genai's flagged script is its own dev `prepare` (nothing the
28
+ # published tarball needs built); protobufjs's postinstall is a
29
+ # version-compat warning, nothing built. (Same review as the root
30
+ # workspace, 2026-08-19; serve works with both denied — verified live.)
17
31
  allowBuilds:
18
32
  esbuild: false
19
33
  sharp: false
34
+ "@google/genai": false
35
+ protobufjs: false
@@ -3,7 +3,7 @@ import Link from "next/link";
3
3
  // The same file Next serves as the favicon (app/icon.png) — one mark, one
4
4
  // asset. Replace it with your own and the tab icon changes with the page.
5
5
  import mark from "@/app/icon.png";
6
- import { BuiltWith } from "@/components/built-with";
6
+ import { FooterMark } from "@/components/footer-mark";
7
7
  import { appName, appTitle } from "@/lib/shared";
8
8
  import { basePath, getSortedPages } from "@/lib/source";
9
9
 
@@ -75,7 +75,7 @@ export default function HomePage() {
75
75
 
76
76
  <footer className="mx-auto w-full max-w-2xl px-6 pb-10">
77
77
  <p className="border-t border-fd-border pt-6 text-xs">
78
- <BuiltWith />
78
+ <FooterMark />
79
79
  </p>
80
80
  </footer>
81
81
  </main>
@@ -1,7 +1,7 @@
1
1
  import { getSortedPageTree } from "@/lib/source";
2
2
  import { DocsLayout } from "fumadocs-ui/layouts/docs";
3
3
  import { baseOptions } from "@/lib/layout.shared";
4
- import { BuiltWith } from "@/components/built-with";
4
+ import { FooterMark } from "@/components/footer-mark";
5
5
 
6
6
  export default function Layout({ children }: LayoutProps<"/docs">) {
7
7
  return (
@@ -13,7 +13,7 @@ export default function Layout({ children }: LayoutProps<"/docs">) {
13
13
  sidebar={{
14
14
  footer: (
15
15
  <p className="mt-3 text-xs">
16
- <BuiltWith />
16
+ <FooterMark />
17
17
  </p>
18
18
  ),
19
19
  }}
@@ -0,0 +1,22 @@
1
+ import type { ReactElement } from "react";
2
+ import { BuiltWith } from "@/components/built-with";
3
+ import { audienceNotice } from "@/lib/audience";
4
+
5
+ /**
6
+ * The foot of the site chrome: who built it, and — on any build below the
7
+ * public tier — which audience that build was for, so a screenshot of an
8
+ * internal site names itself and a page that escapes carries its own
9
+ * warning.
10
+ *
11
+ * The public build renders the attribution ALONE, exactly as a site with no
12
+ * audience model does: the one build with nothing to disclose must not even
13
+ * carry the shape of a disclosure.
14
+ */
15
+ export function FooterMark(): ReactElement {
16
+ if (audienceNotice === null) return <BuiltWith />;
17
+ return (
18
+ <>
19
+ <BuiltWith /> &middot; <span className="text-fd-muted-foreground">{audienceNotice}</span>
20
+ </>
21
+ );
22
+ }
@@ -0,0 +1,178 @@
1
+ import { instanceFrontmatter } from "./shared";
2
+
3
+ /**
4
+ * The audience model, declared in instance.md — the record says who its
5
+ * readers are, and the build enforces it:
6
+ *
7
+ * audiences:
8
+ * - public
9
+ * - internal
10
+ * - restricted
11
+ * default_visibility: public
12
+ *
13
+ * Ordered least- to most-restricted, so "build the internal site" means
14
+ * "public and internal included" with no further configuration. A record
15
+ * that declares no audiences has no model and publishes every document —
16
+ * the behaviour of every instance written before this key existed.
17
+ */
18
+ export interface AudienceModel {
19
+ /** Least- to most-restricted, `public` first. */
20
+ readonly audiences: readonly string[];
21
+ /** The tier of a document that declares no `visibility:`. */
22
+ readonly defaultVisibility: string;
23
+ }
24
+
25
+ function unquote(raw: string): string {
26
+ const trimmed = raw.trim();
27
+ return /^(['"])(.*)\1$/.exec(trimmed)?.[2] ?? trimmed;
28
+ }
29
+
30
+ /** Every refusal this feature makes: a slug a pipeline can match, then the remedy. */
31
+ export function refuse(slug: string, what: string, why: string, fix: string): never {
32
+ // The slug leads, so a pipeline can match on it, and the three lines below
33
+ // it are the whole remedy — an operator never has to read this file.
34
+ throw new Error(`${slug}: ${what}\n why: ${why}\n fix: ${fix}`);
35
+ }
36
+
37
+ function readAudienceModel(): AudienceModel | null {
38
+ const block = instanceFrontmatter();
39
+ // Top-level key only: `^` under /m cannot match an indented child.
40
+ if (!/^audiences:/m.test(block)) return null;
41
+
42
+ // The grammar mirrors the checker's exactly — CRLF-tolerant, list items at
43
+ // ANY indent (YAML allows unindented block sequences), and a ` #` comment
44
+ // ends an unquoted entry (all three found live 2026-08-18: records the
45
+ // checker blessed either failed this build or silently lost a tier).
46
+ const stripComment = (value: string): string =>
47
+ /^["']/.test(value.trim()) ? value : value.replace(/\s+#.*$/, "");
48
+ // A line scanner, not a block regex: a blank line among the items or a
49
+ // comment on the key line broke the block capture and refused every build
50
+ // of a checker-green record (review finding, 2026-08-19).
51
+ const flow = /^audiences:[ \t]*\[(.*)\][ \t]*(?:#.*)?$/m.exec(block)?.[1];
52
+ let items: string[] = [];
53
+ if (flow !== undefined) {
54
+ items = flow.split(",");
55
+ } else {
56
+ const lines = block.split("\n");
57
+ const start = lines.findIndex((line) => /^audiences:[ \t]*(?:#.*)?$/.test(line));
58
+ if (start !== -1) {
59
+ for (const line of lines.slice(start + 1)) {
60
+ if (line.trim() === "") continue;
61
+ const item = /^[ \t]*-[ \t]+(.*)$/.exec(line);
62
+ if (item === null) break;
63
+ items.push(item[1] ?? "");
64
+ }
65
+ }
66
+ }
67
+ const audiences = items
68
+ .map(stripComment)
69
+ .map(unquote)
70
+ .filter((value) => value !== "");
71
+
72
+ // A declared-but-unreadable model must never read as "no model": that is
73
+ // the one parse failure that publishes the whole record.
74
+ if (audiences.length === 0) {
75
+ refuse(
76
+ "ksor-audiences-unreadable",
77
+ "instance.md declares `audiences:` but no audience could be read from it",
78
+ "an unreadable model reads as no model, and no model publishes every document — the one parse failure that leaks",
79
+ "write the audiences as a list, least-restricted first:\n audiences:\n - public\n - internal",
80
+ );
81
+ }
82
+
83
+ // The staging never depends on the checker having run: a
84
+ // most-restrictive-first model would make plain `pnpm build` publish
85
+ // every restricted document with no label (review finding, 2026-08-18).
86
+ if (audiences[0] !== "public") {
87
+ refuse(
88
+ "ksor-audiences-misordered",
89
+ `audiences: must start with public (it starts with "${audiences[0]}")`,
90
+ "the list is ordered least- to most-restricted, and an unset KSOR_AUDIENCE builds the FIRST entry — any other first entry makes the default build the leak",
91
+ "reorder audiences: with public first",
92
+ );
93
+ }
94
+ if (new Set(audiences).size !== audiences.length) {
95
+ refuse(
96
+ "ksor-audiences-duplicate",
97
+ `audiences: declares a tier twice (${audiences.join(", ")})`,
98
+ "a duplicated tier has two positions in the ordering, and which one a build honours is undefined",
99
+ "remove the duplicate entry",
100
+ );
101
+ }
102
+ const defaultVisibility = unquote(
103
+ stripComment(/^default_visibility:[ \t]*(.*)$/m.exec(block)?.[1] ?? ""),
104
+ );
105
+ if (defaultVisibility === "") {
106
+ refuse(
107
+ "ksor-default-visibility-missing",
108
+ "instance.md declares `audiences:` without `default_visibility:`",
109
+ "there is no safe guess: assuming the widest tier leaks on the first document that forgets the key, assuming the narrowest hides the record",
110
+ `add the tier a document without a visibility: key belongs to, e.g. default_visibility: ${audiences[0]}`,
111
+ );
112
+ }
113
+ if (!audiences.includes(defaultVisibility)) {
114
+ refuse(
115
+ "ksor-default-visibility-undeclared",
116
+ `default_visibility: ${defaultVisibility} is not one of the declared audiences (${audiences.join(", ")})`,
117
+ "every document without a visibility: key belongs to this tier — a tier no build understands is a record no build can publish honestly",
118
+ `set default_visibility: to one of ${audiences.join(", ")}, or declare ${defaultVisibility} in audiences:`,
119
+ );
120
+ }
121
+
122
+ return { audiences, defaultVisibility };
123
+ }
124
+
125
+ /** The declared model, or null when this record declares none. */
126
+ export const audienceModel: AudienceModel | null = readAudienceModel();
127
+
128
+ function resolveBuildAudience(model: AudienceModel | null): string {
129
+ const requested = process.env.KSOR_AUDIENCE?.trim() ?? "";
130
+ if (model === null) {
131
+ if (requested !== "") {
132
+ refuse(
133
+ "ksor-audiences-not-declared",
134
+ `KSOR_AUDIENCE="${requested}" was requested, but instance.md declares no audiences`,
135
+ "this build would publish every document — a build that cannot filter must never look like one that did",
136
+ "declare the model in instance.md (audiences: + default_visibility:), or build without KSOR_AUDIENCE",
137
+ );
138
+ }
139
+ return "";
140
+ }
141
+ // Unset means the least-restricted tier: the only default that cannot leak,
142
+ // so `pnpm build` keeps publishing the public site out of the box.
143
+ if (requested === "") return model.audiences[0] as string;
144
+ if (!model.audiences.includes(requested)) {
145
+ refuse(
146
+ "ksor-audience-undeclared",
147
+ `KSOR_AUDIENCE="${requested}" is not an audience this record declares (${model.audiences.join(", ")})`,
148
+ "an unrecognized audience could only be honoured by publishing more than the record names — so it refuses instead of widening",
149
+ `build with one of ${model.audiences.join(", ")}, or add "${requested}" to instance.md's audiences: list`,
150
+ );
151
+ }
152
+ return requested;
153
+ }
154
+
155
+ /** The audience this build publishes for; "" when the record has no model. */
156
+ export const buildAudience: string = resolveBuildAudience(audienceModel);
157
+
158
+ /** Whether a document of this visibility belongs in THIS build. */
159
+ export function visibleInBuild(visibility: string | null): boolean {
160
+ if (audienceModel === null) return true;
161
+ const value =
162
+ visibility === null || visibility === "" ? audienceModel.defaultVisibility : visibility;
163
+ const rank = audienceModel.audiences.indexOf(value);
164
+ // An undeclared visibility is refused, never published: a value no build
165
+ // understands is a typo, and a typo reads as a restriction.
166
+ if (rank === -1) return false;
167
+ return rank <= audienceModel.audiences.indexOf(buildAudience);
168
+ }
169
+
170
+ /**
171
+ * What a non-public build calls itself, in the site chrome — so a leaked
172
+ * screenshot of an internal site says which audience it was built for. The
173
+ * public build (the least-restricted tier) says nothing new.
174
+ */
175
+ export const audienceNotice: string | null =
176
+ audienceModel === null || buildAudience === audienceModel.audiences[0]
177
+ ? null
178
+ : `${buildAudience} build — not for publication`;
@@ -20,12 +20,21 @@ function findInstance(start: string): string {
20
20
  );
21
21
  }
22
22
 
23
- function readInstanceName(): string {
23
+ /**
24
+ * instance.md's frontmatter block — the configuration every surface reads
25
+ * (identity here, the audience model in lib/audience.ts). Only this block:
26
+ * body prose that looks like a key must never become configuration (review
27
+ * finding, 2026-08-18).
28
+ */
29
+ export function instanceFrontmatter(): string {
24
30
  const text = readFileSync(findInstance(process.cwd()), "utf8");
25
- // Only the frontmatter block: body prose mentioning `name:` must never
26
- // become the site's identity (review finding, 2026-08-18).
27
- const block = /^?---\r?\n([\s\S]*?)\r?\n---/.exec(text)?.[1] ?? "";
28
- const raw = /^name:[ \t]*(.*)$/m.exec(block)?.[1]?.trim() ?? "";
31
+ // The checker's boundary exactly: BOM stripped, CRLF normalized, lax close.
32
+ const normalized = text.replace(/^\uFEFF/, "").replaceAll("\r\n", "\n");
33
+ return /^---\n([\s\S]*?)\n---/.exec(normalized)?.[1] ?? "";
34
+ }
35
+
36
+ function readInstanceName(): string {
37
+ const raw = /^name:[ \t]*(.*)$/m.exec(instanceFrontmatter())?.[1]?.trim() ?? "";
29
38
  const unquoted = /^(['"])(.*)\1$/.exec(raw);
30
39
  const name = unquoted?.[2] ?? raw;
31
40
  if (name === "") {