renma 0.18.2 → 0.18.3

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 (56) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/architecture.md +4 -0
  3. package/dist/catalog.d.ts +1 -1
  4. package/dist/catalog.d.ts.map +1 -1
  5. package/dist/catalog.js +5 -2
  6. package/dist/catalog.js.map +1 -1
  7. package/dist/commands/inspect.d.ts +2 -66
  8. package/dist/commands/inspect.d.ts.map +1 -1
  9. package/dist/commands/inspect.js +45 -295
  10. package/dist/commands/inspect.js.map +1 -1
  11. package/dist/commands/readiness.d.ts +3 -0
  12. package/dist/commands/readiness.d.ts.map +1 -1
  13. package/dist/commands/readiness.js +9 -6
  14. package/dist/commands/readiness.js.map +1 -1
  15. package/dist/commands/suggest-metadata.d.ts +4 -21
  16. package/dist/commands/suggest-metadata.d.ts.map +1 -1
  17. package/dist/commands/suggest-metadata.js +65 -514
  18. package/dist/commands/suggest-metadata.js.map +1 -1
  19. package/dist/decisions/metadata-suggestion.d.ts +60 -0
  20. package/dist/decisions/metadata-suggestion.d.ts.map +1 -0
  21. package/dist/decisions/metadata-suggestion.js +176 -0
  22. package/dist/decisions/metadata-suggestion.js.map +1 -0
  23. package/dist/discovery.d.ts.map +1 -1
  24. package/dist/discovery.js +4 -0
  25. package/dist/discovery.js.map +1 -1
  26. package/dist/evidence/classification.d.ts +4 -0
  27. package/dist/evidence/classification.d.ts.map +1 -0
  28. package/dist/evidence/classification.js +15 -0
  29. package/dist/evidence/classification.js.map +1 -0
  30. package/dist/evidence/inspect.d.ts +68 -0
  31. package/dist/evidence/inspect.d.ts.map +1 -0
  32. package/dist/evidence/inspect.js +2 -0
  33. package/dist/evidence/inspect.js.map +1 -0
  34. package/dist/evidence/target.d.ts +51 -0
  35. package/dist/evidence/target.d.ts.map +1 -0
  36. package/dist/evidence/target.js +161 -0
  37. package/dist/evidence/target.js.map +1 -0
  38. package/dist/renderers/inspect.d.ts +3 -0
  39. package/dist/renderers/inspect.d.ts.map +1 -0
  40. package/dist/renderers/inspect.js +159 -0
  41. package/dist/renderers/inspect.js.map +1 -0
  42. package/dist/renderers/metadata-suggestion.d.ts +3 -0
  43. package/dist/renderers/metadata-suggestion.d.ts.map +1 -0
  44. package/dist/renderers/metadata-suggestion.js +320 -0
  45. package/dist/renderers/metadata-suggestion.js.map +1 -0
  46. package/dist/repository-evidence.d.ts +7 -1
  47. package/dist/repository-evidence.d.ts.map +1 -1
  48. package/dist/repository-evidence.js +10 -2
  49. package/dist/repository-evidence.js.map +1 -1
  50. package/dist/scanner.d.ts.map +1 -1
  51. package/dist/scanner.js +3 -13
  52. package/dist/scanner.js.map +1 -1
  53. package/docs/README.md +2 -0
  54. package/docs/internal-architecture.md +296 -0
  55. package/package.json +1 -1
  56. package/scripts/verify-package.mjs +154 -9
@@ -0,0 +1,296 @@
1
+ # Renma Internal Architecture
2
+
3
+ This document describes the implemented internal architecture for the 0.18.3
4
+ maintainability release. It is contributor guidance, not a new CLI or JSON
5
+ contract. Renma 0.18.2 remains the behavioral baseline: internal types and
6
+ module boundaries may become clearer, but public fields, classifications,
7
+ diagnostics, severities, exit behavior, and migration direction must not change
8
+ as a side effect.
9
+
10
+ The high-level product boundary remains in [Architecture](../architecture.md).
11
+ Stable classification and decision fields are documented in the
12
+ [Diagnostics Reference](diagnostics.md), and the versioned BOM contract is in
13
+ [Repository Context BOM](repository-context-bom.md).
14
+
15
+ ## Dependency Flow
16
+
17
+ ```text
18
+ repository files + config
19
+ |
20
+ v
21
+ discovery + parsing + repository resolution
22
+ |
23
+ v
24
+ RepositorySnapshot (facts and snapshot-scoped indexes)
25
+ |
26
+ +--> scan + graph --> Readiness --> BOM
27
+ |
28
+ v
29
+ target evidence --> governance evidence --> command decision --> renderer
30
+ ```
31
+
32
+ Dependencies should continue to point from low-level parsing and resolution to
33
+ evidence, then to decisions, renderers, and command orchestration. A lower-level
34
+ module must not import a command renderer. Commands coordinate these layers;
35
+ they should not independently reinterpret repository layout.
36
+
37
+ The dependency direction is checked in CI. Type-only imports are treated as
38
+ architectural dependencies, so lower layers must not import command or renderer
39
+ modules even when the import is erased at runtime.
40
+
41
+ ## RepositorySnapshot Is the Repository Evidence Source
42
+
43
+ `collectRepositorySnapshot` in `src/repository-evidence.ts` performs one
44
+ repository collection and creates the shared in-memory source of repository
45
+ facts. The snapshot contains:
46
+
47
+ - resolved root, configuration, configuration path, and scan count;
48
+ - discovered artifacts and parsed documents;
49
+ - the normalized catalog, Context Lens summary, and diagnostic partitions;
50
+ - repository-relative paths and their captured filesystem states;
51
+ - structural classification evidence indexed by repository-relative path;
52
+ - a parent-Skill index used for exact parent resolution;
53
+ - effective security-policy evidence with its provenance.
54
+
55
+ The indexes belong to the snapshot. Consumers should reuse them instead of
56
+ reparsing files or rebuilding command-specific lookup tables. A new projection
57
+ should normally accept `RepositorySnapshot`, or the narrowest existing
58
+ projection of it, rather than perform another discovery pass.
59
+
60
+ `RepositorySnapshot` does not freeze the working tree. It records what one
61
+ collection read, after which downstream projections operate on that collected
62
+ state. In particular, Readiness builds its graph and scan from one snapshot,
63
+ and BOM builds its graph, scan, Readiness evidence, policy inventory, and
64
+ diagnostics from one snapshot. This prevents one command result from combining
65
+ independently recollected repository states.
66
+
67
+ `collectRepositoryEvidence` is a narrower compatibility projection for
68
+ consumers such as catalog and graph. It deliberately omits parsed documents,
69
+ sets, maps, and indexes that are implementation details.
70
+
71
+ ## Structural And Repository-Backed Resolution
72
+
73
+ Resolution stages answer different questions and must remain separate.
74
+
75
+ ### Repository boundary
76
+
77
+ `repositoryClassificationPath` determines whether a target can be expressed
78
+ relative to one safe repository root. Evidence is considered in this order:
79
+
80
+ 1. an explicit repository root;
81
+ 2. the nearest valid `.git`, `renma.config.json`, or `.renma.json` marker;
82
+ 3. an unambiguous strong structural boundary such as `skills`, `.agents`,
83
+ `contexts`, `context`, `lenses`, or `tools`, plus recognized root files such
84
+ as `AGENTS.md`;
85
+ 4. an unresolved or ambiguous result.
86
+
87
+ Current-working-directory containment is not proof of a repository boundary.
88
+ Renma may be invoked from a parent workspace, and a target may be absolute or
89
+ belong to a neighboring repository. Traversal outside an explicit root is
90
+ rejected instead of being normalized into that root.
91
+
92
+ The names `profiles`, `references`, `examples`, `scripts`, and `assets` are
93
+ negative guard evidence only. They can make a later structural interpretation
94
+ ambiguous, but they never establish a repository root by themselves. Once an
95
+ outer strong boundary has a recognized interpretation, nested boundary-like
96
+ names do not replace it.
97
+
98
+ ### Structural classification
99
+
100
+ `classifyAssetPath` consumes only a normalized repository-relative path and,
101
+ where relevant, parsed metadata type. It produces path interpretation such as
102
+ `kind`, `scope`, `matchedRule`, `reasonCode`, `recognizedRoot`, competing rules,
103
+ ignored nested segments, and a possible parent-Skill path.
104
+
105
+ For Skill-local support, structural placement yields
106
+ `parentResolution: "structural-candidate"`. This means only that the path has a
107
+ canonical Skill-local shape. It does not prove that the candidate file exists,
108
+ is unique, owns the support file, or supplies policy.
109
+
110
+ ### Repository-backed enrichment
111
+
112
+ `buildSkillParentIndex` records all discovered Skill entrypoint candidates by
113
+ logical Skill directory. `resolveSkillSupportParent` then returns `resolved`
114
+ only when exactly one candidate exists; zero candidates are `missing`, and
115
+ multiple candidates are `ambiguous`. `withResolvedSkillParent` attaches that
116
+ result without changing the original structural kind or scope.
117
+
118
+ Snapshot construction creates this parent index before catalog construction and
119
+ passes the same instance into `buildCatalog`. Catalog ownership, target parent
120
+ resolution, and governance enrichment therefore share one snapshot-scoped
121
+ source rather than reconstructing equivalent indexes independently. The public
122
+ `buildCatalog(documents, repositoryPaths)` call remains compatible and creates
123
+ an index for standalone callers.
124
+
125
+ Ownership, policy, catalog membership, and relationships also require
126
+ repository evidence. None of them may be manufactured from path shape alone.
127
+
128
+ ## Shared Target Evidence
129
+
130
+ `inspect` and `suggest-metadata` share the target-evidence pipeline in
131
+ `src/evidence/target.ts`:
132
+
133
+ - `collectTargetDocumentEvidence` reads one target, resolves its repository
134
+ boundary, parses it, and builds structural classification evidence.
135
+ - `collectTargetRepositoryEvidence` collects the resolved repository snapshot
136
+ and enriches the target with catalog membership, parent resolution, policy,
137
+ and governance evidence.
138
+
139
+ The two stages remain separate because a readable file does not guarantee a
140
+ resolvable repository. When repository collection is unavailable, the target
141
+ retains structural evidence but does not fall back to guessed catalog identity,
142
+ parentage, ownership, or policy.
143
+
144
+ Unavailable evidence preserves the exact boundary result:
145
+ `repository-boundary-unresolved` and `repository-boundary-ambiguous` remain
146
+ distinct, while a failure after a root resolves is `snapshot-unavailable`.
147
+
148
+ ## Classification, Governance, And Decisions
149
+
150
+ These evidence layers are related, but none is a substitute for another.
151
+
152
+ | Layer | Answers | Does not prove |
153
+ | --- | --- | --- |
154
+ | Classification | What repository path rule matched, what kind and scope result, and whether a structural or resolved parent is known | Ownership, policy, authority, or human intent |
155
+ | Governance | Whether ownership and policy are declared, inherited, or missing, including provenance | That a proposed edit is applicable |
156
+ | Decision | Whether a command result is deterministic, requires confirmation, is blocked, or recommends no change | A different path classification or permission to discard evidence |
157
+
158
+ Ownership is explicit governance evidence. A declared local owner remains
159
+ declared. A Skill-local file may inherit an effective owner only from exactly
160
+ one resolved parent Skill that declares an owner. Missing or ambiguous parents
161
+ remain unowned. Renma never derives ownership from directory names, prose, Git
162
+ authors, or modification history.
163
+
164
+ Policy provenance is tracked separately from ownership provenance. A local
165
+ policy must not be relabeled as inherited, and an absent effective policy stays
166
+ missing. Likewise, Renma does not infer that local support should be promoted,
167
+ that an independent asset should be created, or that a maintainer intended an
168
+ owner or lifecycle value. Those are human repository-design decisions.
169
+
170
+ ## Decisions, Renderers, And Commands
171
+
172
+ Decision construction belongs in `src/decisions/`. A decision object carries
173
+ the authoritative `decisionStatus`, stable reason code, summary, and any
174
+ remaining human question. Candidate construction must honor that status before
175
+ anything is presented as an applicable edit.
176
+
177
+ Metadata suggestion uses pure builders in
178
+ `src/decisions/metadata-suggestion.ts` for Skill migrations, Skill-local parent
179
+ and governance states, unsupported targets, owner conflicts, and independent
180
+ metadata candidates. The command retains filesystem collision checks,
181
+ candidate assembly, next-action construction, and orchestration.
182
+
183
+ Renderers in `src/renderers/` turn an already-decided result into human text.
184
+ They may improve wording and layout, but they must not rediscover a parent,
185
+ infer governance, change applicability, or create candidate data. JSON output
186
+ serializes the command result directly; it is not reconstructed from rendered
187
+ text.
188
+
189
+ Command modules should stay orchestration-oriented:
190
+
191
+ ```text
192
+ collect context -> build evidence -> decide -> render or serialize
193
+ ```
194
+
195
+ A `no-change-recommended` decision is a successful result. It means Renma
196
+ completed the analysis and found no supported change. The command must not
197
+ manufacture metadata, a migration, or verification work merely to return an
198
+ edit-shaped response.
199
+
200
+ ## Public JSON And Internal Working Types
201
+
202
+ Public machine-readable output is protected at the serialized command boundary.
203
+ Fields such as classification evidence, governance provenance,
204
+ `decisionStatus`, command invocation `command` and `args`, and established
205
+ diagnostic fields must remain compatible with 0.18.2.
206
+
207
+ Internal working types have a different role. `RepositorySnapshot`, parsed
208
+ documents, `ReadonlyMap` indexes, `ReadonlySet` repository paths, and
209
+ intermediate target-resolution unions exist to keep implementation states
210
+ explicit. They are not public JSON merely because they are TypeScript types.
211
+ They may be narrowed or reorganized when behavior-focused tests prove that the
212
+ serialized contract is unchanged.
213
+
214
+ Inspect renderer DTOs live in `src/evidence/inspect.ts`, so renderers do not
215
+ depend on command modules. `src/commands/inspect.ts` re-exports the established
216
+ types to preserve existing TypeScript deep imports.
217
+
218
+ Human-readable reasons and prompts may evolve unless a test intentionally
219
+ protects exact wording. Stable branching must use typed fields such as
220
+ `matchedRule`, `reasonCode`, `parentResolution`, ownership provenance, and
221
+ `decisionStatus`, not prose parsing.
222
+
223
+ ## Intentional 0.18.2 Compatibility Seams
224
+
225
+ Two parallel-looking paths remain intentional in 0.18.3.
226
+
227
+ ### Scan keeps structural parent evidence
228
+
229
+ The snapshot classification index used to annotate scan findings and
230
+ diagnostics is structural. A Skill-local scan detail may therefore retain
231
+ `parentResolution: "structural-candidate"`. Target-oriented commands such as
232
+ `inspect` and `suggest-metadata` enrich the same structural classification with
233
+ snapshot-backed `resolved`, `missing`, or `ambiguous` parent evidence.
234
+
235
+ Do not silently make scan annotations repository-enriched merely to make the
236
+ implementations look uniform. The distinction preserves the 0.18.2 public
237
+ diagnostic shape; changing it requires an explicit contract decision and
238
+ characterization tests.
239
+
240
+ ### Blocked migrations retain partial diagnostic maps
241
+
242
+ A blocked Agent Skills migration may retain partial
243
+ `candidateAgentSkillsFields` and `candidateRenmaMetadata` maps. These fields are
244
+ diagnostic evidence retained for 0.18.2 JSON compatibility, not an applicable
245
+ patch.
246
+
247
+ `decisionStatus` is authoritative. A frontmatter migration is applicable only
248
+ when the decision is not blocked and `canonicalFrontmatter` is present.
249
+ Blocked results must not expose canonical frontmatter or renderer patch
250
+ instructions, and consumers must never treat the partial candidate maps as an
251
+ override of the gate.
252
+
253
+ ## Fail-Closed Boundaries
254
+
255
+ The following constraints are safety and compatibility invariants:
256
+
257
+ - An unresolved or ambiguous repository boundary produces no guessed root,
258
+ catalog identity, inherited governance, or executable repository action.
259
+ - Guard directory names are never positive repository-root evidence.
260
+ - Repository discovery does not follow symbolic links, and a symbolic-link
261
+ marker does not establish a boundary.
262
+ - Structural Skill-local placement never establishes inheritance by itself.
263
+ - A missing or ambiguous parent supplies neither inherited ownership nor
264
+ inherited policy.
265
+ - Explicit local governance is preserved as local governance.
266
+ - A blocked decision suppresses applicable candidate metadata and canonical
267
+ frontmatter; renderers cannot reopen the decision.
268
+ - A no-change decision produces no synthetic work.
269
+
270
+ When one of these states looks inconvenient, preserve it and improve the
271
+ evidence presented to the maintainer. Do not replace uncertainty with a guess.
272
+
273
+ ## Runtime Boundary
274
+
275
+ Renma analyzes declared repository state. It does not execute Skills, select a
276
+ Skill for a live task, assemble prompts, invoke Skill tools, observe model runs,
277
+ or collect runtime telemetry. Static instructions and policies are evidence
278
+ about repository content; they are not proof of runtime behavior.
279
+
280
+ Future runtime signals may be imported as a separate, explicitly versioned
281
+ evidence artifact linked to a repository snapshot or BOM. Signal production,
282
+ collection, storage, and Skill execution remain outside this architecture.
283
+
284
+ ## Contributor Checklist
285
+
286
+ For an internal change:
287
+
288
+ 1. Add or confirm a behavior-focused test for the 0.18.2 result.
289
+ 2. Reuse the snapshot and shared resolution paths before adding another
290
+ collector or index.
291
+ 3. Keep structural facts, repository-backed governance, decisions, and
292
+ rendering in their respective layers.
293
+ 4. Verify stable JSON fields and fail-closed states, not only human text.
294
+ 5. For Readiness or BOM changes, prove all derived sections use one snapshot.
295
+ 6. Run targeted tests, type checking, linting, the full test suite, build, and
296
+ package verification before release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "renma",
3
- "version": "0.18.2",
3
+ "version": "0.18.3",
4
4
  "description": "Manage human-curated context assets for LLMs and agents in Git.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,25 +1,53 @@
1
- import { mkdtemp, readFile, rm } from "node:fs/promises";
1
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
+ import { pathToFileURL } from "node:url";
5
6
 
6
- const cache = await mkdtemp(path.join(os.tmpdir(), "renma-npm-pack-"));
7
+ const temporaryRoot = await mkdtemp(
8
+ path.join(os.tmpdir(), "renma package verification-"),
9
+ );
10
+ const packDirectory = path.join(temporaryRoot, "packed artifact");
11
+ const consumerDirectory = path.join(temporaryRoot, "clean consumer");
12
+ const cacheDirectory = path.join(temporaryRoot, "npm cache");
7
13
 
8
14
  try {
15
+ await mkdir(packDirectory, { recursive: true });
16
+ await mkdir(consumerDirectory, { recursive: true });
17
+ await mkdir(cacheDirectory, { recursive: true });
9
18
  const packed = spawnSync(
10
19
  "npm",
11
- ["pack", "--dry-run", "--json", "--cache", cache],
20
+ [
21
+ "pack",
22
+ "--json",
23
+ "--cache",
24
+ cacheDirectory,
25
+ "--pack-destination",
26
+ packDirectory,
27
+ ],
12
28
  { cwd: process.cwd(), encoding: "utf8" },
13
29
  );
14
- if (packed.error) throw packed.error;
30
+ if (packed.error) {
31
+ throw new Error(`npm pack failed: ${packed.error.message}`);
32
+ }
15
33
  if (packed.status !== 0) {
16
- throw new Error(packed.stderr.trim() || "npm pack --dry-run failed.");
34
+ throw new Error(
35
+ `npm pack failed: ${packed.stderr.trim() || `exit code ${packed.status}`}`,
36
+ );
17
37
  }
18
38
 
19
- const reports = JSON.parse(packed.stdout);
39
+ let reports;
40
+ try {
41
+ reports = JSON.parse(packed.stdout);
42
+ } catch {
43
+ throw new Error("npm pack failed: npm returned invalid JSON output.");
44
+ }
20
45
  const report = reports[0];
21
46
  if (!report || !Array.isArray(report.files)) {
22
- throw new Error("npm pack --dry-run returned no package file list.");
47
+ throw new Error("npm pack returned no package file list.");
48
+ }
49
+ if (typeof report.filename !== "string" || report.filename.length === 0) {
50
+ throw new Error("npm pack returned no tarball filename.");
23
51
  }
24
52
  const files = new Set(report.files.map((file) => file.path));
25
53
 
@@ -27,6 +55,17 @@ try {
27
55
  "package.json",
28
56
  "README.md",
29
57
  "dist/index.js",
58
+ "dist/types.js",
59
+ "dist/types.d.ts",
60
+ "dist/discovery.js",
61
+ "dist/discovery.d.ts",
62
+ "dist/commands/inspect.js",
63
+ "dist/commands/inspect.d.ts",
64
+ "dist/commands/suggest-metadata.js",
65
+ "dist/commands/suggest-metadata.d.ts",
66
+ "dist/skill-migration.js",
67
+ "dist/skill-migration.d.ts",
68
+ "docs/internal-architecture.md",
30
69
  "docs/trust-graph.md",
31
70
  "docs/schemas/repository-context-bom-v2.schema.json",
32
71
  "docs/schemas/trust-graph-v2.schema.json",
@@ -62,11 +101,117 @@ try {
62
101
  }
63
102
  }
64
103
 
104
+ const tarballPath = path.resolve(packDirectory, report.filename);
105
+ const packageRoot = await installInTemporaryConsumer(
106
+ consumerDirectory,
107
+ tarballPath,
108
+ cacheDirectory,
109
+ );
110
+ await verifyInspectDeclarationCompatibility(packageRoot);
111
+ for (const modulePath of [
112
+ "dist/commands/inspect.js",
113
+ "dist/commands/suggest-metadata.js",
114
+ "dist/discovery.js",
115
+ "dist/skill-migration.js",
116
+ ]) {
117
+ try {
118
+ await import(pathToFileURL(path.join(packageRoot, modulePath)).href);
119
+ } catch (error) {
120
+ throw new Error(
121
+ `Deep import failed for ${modulePath}: ${errorMessage(error)}`,
122
+ { cause: error },
123
+ );
124
+ }
125
+ }
126
+
65
127
  process.stdout.write(
66
- `Verified ${files.size} packaged files and every README-relative target.\n`,
128
+ `Verified ${files.size} packaged files, deep imports, inspect declarations, and every README-relative target.\n`,
67
129
  );
68
130
  } finally {
69
- await rm(cache, { recursive: true, force: true });
131
+ await rm(temporaryRoot, { recursive: true, force: true });
132
+ }
133
+
134
+ async function installInTemporaryConsumer(
135
+ consumerDirectory,
136
+ tarballPath,
137
+ cacheDirectory,
138
+ ) {
139
+ await writeFile(
140
+ path.join(consumerDirectory, "package.json"),
141
+ `${JSON.stringify(
142
+ {
143
+ name: "renma-package-verification",
144
+ private: true,
145
+ type: "module",
146
+ },
147
+ null,
148
+ 2,
149
+ )}\n`,
150
+ );
151
+ const installed = spawnSync(
152
+ "npm",
153
+ [
154
+ "install",
155
+ "--ignore-scripts",
156
+ "--no-audit",
157
+ "--no-fund",
158
+ "--package-lock=false",
159
+ "--cache",
160
+ cacheDirectory,
161
+ tarballPath,
162
+ ],
163
+ { cwd: consumerDirectory, encoding: "utf8" },
164
+ );
165
+ if (installed.error) {
166
+ throw new Error(
167
+ `Temporary consumer installation failed: ${installed.error.message}`,
168
+ );
169
+ }
170
+ if (installed.status !== 0) {
171
+ throw new Error(
172
+ `Temporary consumer installation failed: ${installed.stderr.trim() || `npm exited with code ${installed.status}`}`,
173
+ );
174
+ }
175
+ const packageRoot = path.join(consumerDirectory, "node_modules", "renma");
176
+ try {
177
+ await readFile(path.join(packageRoot, "package.json"), "utf8");
178
+ } catch (error) {
179
+ throw new Error(
180
+ `Temporary consumer installation failed: installed renma package is missing (${errorMessage(error)}).`,
181
+ { cause: error },
182
+ );
183
+ }
184
+ return packageRoot;
185
+ }
186
+
187
+ async function verifyInspectDeclarationCompatibility(packageRoot) {
188
+ const declarations = await readFile(
189
+ path.join(packageRoot, "dist/commands/inspect.d.ts"),
190
+ "utf8",
191
+ );
192
+ for (const typeName of [
193
+ "InspectOutline",
194
+ "InspectAssetSummary",
195
+ "InspectRelationship",
196
+ "InspectRelationshipChain",
197
+ "InspectSlice",
198
+ ]) {
199
+ const declared = new RegExp(
200
+ `export\\s+(?:interface|type)\\s+${typeName}\\b`,
201
+ ).test(declarations);
202
+ const reexported = new RegExp(
203
+ `export\\s+type\\s*\\{[\\s\\S]*?\\b${typeName}\\b[\\s\\S]*?\\}\\s*from`,
204
+ ).test(declarations);
205
+ if (!declared && !reexported) {
206
+ throw new Error(
207
+ `dist/commands/inspect.d.ts no longer exports ${typeName}.`,
208
+ );
209
+ }
210
+ }
211
+ }
212
+
213
+ function errorMessage(error) {
214
+ return error instanceof Error ? error.message : String(error);
70
215
  }
71
216
 
72
217
  function requirePackagedPath(files, target) {