@canonical/pragma-cli 0.33.0 → 0.34.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.
Files changed (250) hide show
  1. package/README.md +7 -7
  2. package/dist/pragma +0 -0
  3. package/package.json +18 -15
  4. package/pragma.conf.ts +664 -0
  5. package/src/bin.ts +13 -4
  6. package/src/capabilities/block.parity.test.ts +250 -0
  7. package/src/capabilities/capabilities/capabilities.render.ts +3 -2
  8. package/src/capabilities/capabilities/capabilities.verb.ts +31 -13
  9. package/src/capabilities/capabilities/catalog.ts +18 -10
  10. package/src/capabilities/capabilities/hints.ts +13 -19
  11. package/src/capabilities/colophon/collectColophon.ts +34 -19
  12. package/src/capabilities/colophon/colophon.verb.ts +8 -4
  13. package/src/capabilities/colophon/types.ts +16 -7
  14. package/src/capabilities/config/collectConfigShow.test.ts +57 -0
  15. package/src/capabilities/config/collectConfigShow.ts +41 -4
  16. package/src/capabilities/config/fields.ts +0 -27
  17. package/src/capabilities/config/set.verb.ts +7 -3
  18. package/src/capabilities/config/show.render.test.ts +77 -5
  19. package/src/capabilities/config/show.render.ts +7 -12
  20. package/src/capabilities/config/show.verb.ts +4 -3
  21. package/src/capabilities/config/types.ts +8 -0
  22. package/src/capabilities/create/byteEquality.test.ts +78 -100
  23. package/src/capabilities/create/compiledCreate.subprocess.test.ts +52 -15
  24. package/src/capabilities/create/constants.ts +75 -0
  25. package/src/capabilities/create/create.mcp.test.ts +2 -2
  26. package/src/capabilities/create/create.test.ts +162 -15
  27. package/src/capabilities/create/create.verb.ts +140 -69
  28. package/src/capabilities/create/index.ts +2 -10
  29. package/src/capabilities/create/pickGenerator.ts +34 -28
  30. package/src/capabilities/create/templates.embedded.generated.ts +4 -29
  31. package/src/capabilities/create/types.ts +10 -6
  32. package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
  33. package/src/capabilities/distribution.test.ts +120 -0
  34. package/src/capabilities/distribution.ts +59 -0
  35. package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
  36. package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
  37. package/src/capabilities/doctor/checks/checkMcpConfigured.ts +7 -6
  38. package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
  39. package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
  40. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
  41. package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
  42. package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
  43. package/src/capabilities/doctor/doctor.render.test.ts +6 -6
  44. package/src/capabilities/doctor/doctor.render.ts +2 -1
  45. package/src/capabilities/doctor/doctor.test.ts +37 -8
  46. package/src/capabilities/doctor/doctor.verb.ts +6 -2
  47. package/src/capabilities/doctor/runChecks.ts +5 -5
  48. package/src/capabilities/doctor/types.ts +1 -1
  49. package/src/capabilities/emptyRecovery.test.ts +32 -15
  50. package/src/capabilities/graph/inspect.verb.ts +3 -2
  51. package/src/capabilities/graph/query.verb.ts +3 -2
  52. package/src/capabilities/graph/runQuery.ts +8 -5
  53. package/src/capabilities/index.ts +22 -18
  54. package/src/capabilities/info/collectInfo.ts +10 -6
  55. package/src/capabilities/info/info.render.ts +3 -2
  56. package/src/capabilities/info/info.verb.ts +3 -2
  57. package/src/capabilities/lazy.test.ts +93 -2
  58. package/src/capabilities/ontology/verbs.ts +21 -21
  59. package/src/capabilities/prompt/prompt.test.ts +12 -6
  60. package/src/capabilities/prompt/verbs.ts +24 -11
  61. package/src/capabilities/resources/provider.ts +30 -11
  62. package/src/capabilities/resources/resources.test.ts +85 -29
  63. package/src/capabilities/setup/operations/setupGenerator.ts +16 -9
  64. package/src/capabilities/setup/operations/setupMcp.ts +2 -2
  65. package/src/capabilities/setup/operations/setupSkills.ts +3 -3
  66. package/src/capabilities/setup/setup.verb.ts +12 -5
  67. package/src/capabilities/setup/shell.ts +29 -3
  68. package/src/capabilities/skill/discover.ts +11 -5
  69. package/src/capabilities/skill/render.ts +4 -5
  70. package/src/capabilities/skill/verbs.ts +7 -7
  71. package/src/capabilities/sources/collectStatus.ts +55 -50
  72. package/src/capabilities/sources/index.ts +1 -1
  73. package/src/capabilities/sources/installSkills.ts +2 -2
  74. package/src/capabilities/sources/runUpdate.ts +113 -122
  75. package/src/capabilities/sources/sources.test.ts +111 -117
  76. package/src/capabilities/sources/status.render.test.ts +53 -24
  77. package/src/capabilities/sources/status.render.ts +24 -29
  78. package/src/capabilities/sources/status.verb.ts +15 -8
  79. package/src/capabilities/sources/types.ts +18 -25
  80. package/src/capabilities/sources/update.render.ts +9 -2
  81. package/src/capabilities/sources/update.verb.ts +12 -23
  82. package/src/capabilities/surface.test.ts +33 -10
  83. package/src/capabilities/tier.parity.test.ts +144 -0
  84. package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
  85. package/src/constants.test.ts +8 -6
  86. package/src/constants.ts +47 -21
  87. package/src/identity.test.ts +333 -0
  88. package/src/index.ts +2 -2
  89. package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +5 -2
  90. package/src/kernel/completion/emitScripts.test.ts +1 -1
  91. package/src/kernel/completion/emitScripts.ts +4 -2
  92. package/src/kernel/completion/entitySource.test.ts +185 -41
  93. package/src/kernel/completion/entitySource.ts +117 -107
  94. package/src/kernel/completion/model.test.ts +1 -1
  95. package/src/kernel/completion/model.ts +11 -0
  96. package/src/kernel/completion/safety.test.ts +82 -36
  97. package/src/kernel/completion/shellDrive.test.ts +592 -0
  98. package/src/kernel/completion/templates/fish.ts +28 -6
  99. package/src/kernel/completion/types.ts +3 -3
  100. package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
  101. package/src/kernel/config/defaults.test.ts +85 -0
  102. package/src/kernel/config/defaults.ts +12 -23
  103. package/src/kernel/config/evaluateProjectConfig.ts +2 -3
  104. package/src/kernel/config/findProjectConfig.ts +4 -3
  105. package/src/kernel/config/firstRun.ts +12 -5
  106. package/src/kernel/config/globalConfig.ts +2 -2
  107. package/src/kernel/config/index.ts +4 -5
  108. package/src/kernel/config/paths.ts +17 -17
  109. package/src/kernel/config/readConfig.test.ts +242 -6
  110. package/src/kernel/config/readConfig.ts +8 -8
  111. package/src/kernel/config/schema.test.ts +50 -0
  112. package/src/kernel/config/schema.ts +97 -10
  113. package/src/kernel/config/types.ts +73 -19
  114. package/src/kernel/copy.test.ts +453 -0
  115. package/src/kernel/error/PragmaError.test.ts +1 -1
  116. package/src/kernel/error/constants.ts +3 -2
  117. package/src/kernel/error/index.ts +1 -1
  118. package/src/kernel/error/recovery.test.ts +22 -45
  119. package/src/kernel/error/recovery.ts +22 -27
  120. package/src/kernel/error/types.ts +5 -4
  121. package/src/kernel/packs/collect.test.ts +195 -11
  122. package/src/kernel/packs/collect.ts +213 -58
  123. package/src/kernel/packs/compile.test.ts +163 -7
  124. package/src/kernel/packs/compile.ts +15 -12
  125. package/src/kernel/packs/disclosure.test.ts +5 -2
  126. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
  127. package/src/kernel/packs/graphqlSource.test.ts +6 -3
  128. package/src/kernel/packs/iri.ts +5 -7
  129. package/src/kernel/packs/renderPack.ts +5 -3
  130. package/src/kernel/packs/resolveEntity.ts +21 -17
  131. package/src/kernel/packs/runBodies.ts +15 -14
  132. package/src/kernel/packs/schema.ts +96 -10
  133. package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
  134. package/src/kernel/packs/sparql/runSelect.ts +73 -34
  135. package/src/kernel/packs/toolDescription.test.ts +10 -2
  136. package/src/kernel/packs/types.ts +39 -4
  137. package/src/kernel/packs/uniqueness.ts +4 -4
  138. package/src/kernel/project/cli/buildProgram.test.ts +9 -4
  139. package/src/kernel/project/cli/dispatch.test.ts +67 -0
  140. package/src/kernel/project/cli/dispatch.ts +34 -12
  141. package/src/kernel/project/cli/helpFormat.ts +1 -1
  142. package/src/kernel/project/cli/rootHelp.ts +105 -105
  143. package/src/kernel/project/mcp/buildServer.ts +11 -6
  144. package/src/kernel/project/mcp/instructions.ts +11 -6
  145. package/src/kernel/project/mcp/parity.test.ts +39 -1
  146. package/src/kernel/project/mcp/prompts/prompts.test.ts +120 -5
  147. package/src/kernel/project/mcp/prompts/provider.ts +22 -8
  148. package/src/kernel/project/mcp/prompts/source.ts +68 -67
  149. package/src/kernel/project/mcp/registerVerb.ts +25 -6
  150. package/src/kernel/project/mcp/serve.ts +11 -3
  151. package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
  152. package/src/kernel/render/disclosure.ts +4 -2
  153. package/src/kernel/render/prefixes.test.ts +36 -0
  154. package/src/kernel/render/prefixes.ts +40 -13
  155. package/src/kernel/render/renderers.test.ts +1 -1
  156. package/src/kernel/runtime/graphpack/build.ts +29 -3
  157. package/src/kernel/runtime/graphpack/buildIndex.test.ts +46 -0
  158. package/src/kernel/runtime/graphpack/buildIndex.ts +52 -17
  159. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -5
  160. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -3
  161. package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
  162. package/src/kernel/runtime/graphpack/embedded.ts +37 -11
  163. package/src/kernel/runtime/graphpack/graphpack.test.ts +205 -5
  164. package/src/kernel/runtime/graphpack/hash.ts +3 -2
  165. package/src/kernel/runtime/graphpack/index.ts +1 -1
  166. package/src/kernel/runtime/graphpack/manifest.ts +114 -12
  167. package/src/kernel/runtime/graphpack/read.ts +4 -9
  168. package/src/kernel/runtime/graphpack/schemas.ts +74 -0
  169. package/src/kernel/runtime/graphpack/stories.ts +84 -0
  170. package/src/kernel/runtime/graphpack/types.ts +61 -48
  171. package/src/kernel/runtime/paths.ts +64 -15
  172. package/src/kernel/runtime/probe.test.ts +8 -4
  173. package/src/kernel/runtime/readEntity.ts +6 -5
  174. package/src/kernel/runtime/refs/gitOps.ts +1 -19
  175. package/src/kernel/runtime/refs/parseRef.ts +7 -7
  176. package/src/kernel/runtime/refs/resolve.ts +78 -62
  177. package/src/kernel/runtime/resolveSources.test.ts +63 -24
  178. package/src/kernel/runtime/resolveSources.ts +36 -25
  179. package/src/kernel/runtime/storeReadiness.ts +9 -8
  180. package/src/kernel/runtime/storeless.test.ts +1 -1
  181. package/src/kernel/runtime/wasmEmbed.test.ts +22 -19
  182. package/src/kernel/spec/emitReference.test.ts +3 -3
  183. package/src/kernel/spec/emitReference.ts +144 -11
  184. package/src/kernel/spec/emitSurface.ts +9 -5
  185. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  186. package/src/kernel/spec/surfaceConformance.ts +8 -0
  187. package/src/kernel/spec/types.ts +29 -9
  188. package/src/kernel/spec/validate.ts +2 -1
  189. package/src/kernel/vocabulary.test.ts +164 -0
  190. package/src/kernel/vocabulary.ts +146 -0
  191. package/src/testing/PARITY_GAPS.ts +8 -2
  192. package/src/testing/behavioral/README.md +3 -3
  193. package/src/testing/behavioral/completion.test.ts +19 -6
  194. package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
  195. package/src/testing/behavioral/errorMatrix.storeless.test.ts +1 -1
  196. package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
  197. package/src/testing/behavioral/harness.foundation.test.ts +14 -5
  198. package/src/testing/behavioral/journeys.cli.test.ts +116 -75
  199. package/src/testing/behavioral/journeys.defaultPack.test.ts +82 -176
  200. package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
  201. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +9 -6
  202. package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
  203. package/src/testing/behavioral/liveReadSurface.ts +4 -3
  204. package/src/testing/behavioral/mcpSurface.test.ts +9 -4
  205. package/src/testing/behavioral/staleCommands.test.ts +10 -0
  206. package/src/testing/eval/__snapshots__/eval.test.ts.snap +7 -7
  207. package/src/testing/eval/cases/assembly.ts +13 -6
  208. package/src/testing/eval/cases/effectful.ts +25 -13
  209. package/src/testing/eval/cases/readNouns.ts +22 -20
  210. package/src/testing/eval/cases/stable.ts +5 -1
  211. package/src/testing/eval/eval.test.ts +7 -1
  212. package/src/testing/fixtures/fixtureCapability.ts +45 -1
  213. package/src/testing/fixtures/graph/canonical.ts +6 -3
  214. package/src/testing/fixtures/graph/defaultPack.ts +6 -5
  215. package/src/testing/fixtures/graph/owlExport.ts +7 -5
  216. package/src/testing/helpers/fixtureGraph.ts +33 -16
  217. package/src/testing/helpers/packRuntime.ts +2 -3
  218. package/src/testing/helpers/projectCli.ts +2 -1
  219. package/src/testing/helpers/projectMcp.ts +6 -0
  220. package/src/testing/perf/budgets.test.ts +4 -0
  221. package/src/testing/perf/budgets.ts +22 -7
  222. package/src/testing/perf/globalSetup.ts +121 -6
  223. package/src/types.ts +2 -2
  224. package/src/capabilities/block/blockList.test.ts +0 -79
  225. package/src/capabilities/block/blockList.verb.ts +0 -163
  226. package/src/capabilities/block/index.ts +0 -25
  227. package/src/capabilities/block/pack.ts +0 -166
  228. package/src/capabilities/block/parity.test.ts +0 -187
  229. package/src/capabilities/block/tierChain.ts +0 -78
  230. package/src/capabilities/colophon/pragmaColophon.ts +0 -66
  231. package/src/capabilities/modifier/index.ts +0 -15
  232. package/src/capabilities/modifier/pack.ts +0 -64
  233. package/src/capabilities/standard/index.ts +0 -18
  234. package/src/capabilities/standard/pack.ts +0 -128
  235. package/src/capabilities/tier/index.ts +0 -19
  236. package/src/capabilities/tier/lookup.render.test.ts +0 -72
  237. package/src/capabilities/tier/lookup.render.ts +0 -59
  238. package/src/capabilities/tier/lookup.test.ts +0 -83
  239. package/src/capabilities/tier/lookup.verb.ts +0 -44
  240. package/src/capabilities/tier/pack.ts +0 -28
  241. package/src/capabilities/tier/runLookup.ts +0 -87
  242. package/src/capabilities/token/addConfig.render.ts +0 -27
  243. package/src/capabilities/token/addConfig.test.ts +0 -62
  244. package/src/capabilities/token/addConfig.verb.ts +0 -40
  245. package/src/capabilities/token/index.ts +0 -19
  246. package/src/capabilities/token/pack.ts +0 -58
  247. package/src/capabilities/token/runAddConfig.ts +0 -95
  248. package/src/kernel/completion/bashDrive.test.ts +0 -73
  249. package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
  250. package/src/kernel/runtime/lock.ts +0 -90
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Read the read stories the ANSWERING pack carries.
3
+ *
4
+ * It CONSUMES the one boot decision ({@link resolveSources}) rather than
5
+ * re-deriving it, so the nouns a project's reads answer with can never differ
6
+ * from the nouns its pack carries: a project pointed at its own pack gets that
7
+ * pack's stories, a fresh install gets the embedded snapshot's, and a
8
+ * configured-but-unbuilt project gets none (its reads refuse too).
9
+ *
10
+ * Total: any unreadable or unparseable `stories.json` yields `[]`. The records
11
+ * are RAW TEXT — validating them is `kernel/packs/collect.validateStories`'s
12
+ * job, behind one guard, because a third-party story must never break a command.
13
+ *
14
+ * The embedded snapshot's records come from their OWN generated module, so
15
+ * reading them costs a small string and never loads `pack.generated.ts`'s
16
+ * ~1.9 MB of n-quads (a measured +28 ms on every invocation).
17
+ */
18
+
19
+ import { readFileSync } from "node:fs";
20
+ import { join } from "node:path";
21
+ import type { SourcesDecision } from "../resolveSources.js";
22
+ import { storiesJson } from "./embedded/pack.stories.generated.js";
23
+ import { STORIES_FILE } from "./types.js";
24
+
25
+ /** One package-declared story as the pack carries it: the file it came from, verbatim. */
26
+ export interface PackStoryRecord {
27
+ /** Where the story was declared, e.g. `@canonical/design-system/stories/block.json`. */
28
+ readonly source: string;
29
+ /** The file's raw text — unparsed, uninterpreted. */
30
+ readonly content: string;
31
+ }
32
+
33
+ /** A record shaped as this module promises — the file is a user-writable cache. */
34
+ function isRecord(value: unknown): value is PackStoryRecord {
35
+ return (
36
+ typeof value === "object" &&
37
+ value !== null &&
38
+ typeof (value as PackStoryRecord).source === "string" &&
39
+ typeof (value as PackStoryRecord).content === "string"
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Parse a `stories.json` body; anything malformed degrades to no stories.
45
+ *
46
+ * The elements are checked, not just the container: a record that is not
47
+ * `{ source, content }` would otherwise reach `validateStories` and be reported
48
+ * as `Ignored story undefined: …` — a diagnostic that names no file.
49
+ */
50
+ function parseRecords(json: string): readonly PackStoryRecord[] {
51
+ try {
52
+ const parsed: unknown = JSON.parse(json);
53
+ return Array.isArray(parsed) ? parsed.filter(isRecord) : [];
54
+ } catch {
55
+ return [];
56
+ }
57
+ }
58
+
59
+ /**
60
+ * The read stories the answering pack carries.
61
+ *
62
+ * @param decision - The boot decision from `resolveSources`.
63
+ * @returns The carried story records — `[]` when the pack carries none, no pack
64
+ * answers, or the file is unreadable.
65
+ * @note Impure — reads `stories.json` from the answering pack directory.
66
+ */
67
+ export function activeStories(
68
+ decision: SourcesDecision,
69
+ ): readonly PackStoryRecord[] {
70
+ switch (decision.kind) {
71
+ case "pack":
72
+ try {
73
+ return parseRecords(
74
+ readFileSync(join(decision.dir, STORIES_FILE), "utf-8"),
75
+ );
76
+ } catch {
77
+ return [];
78
+ }
79
+ case "embedded":
80
+ return parseRecords(storiesJson);
81
+ case "unavailable":
82
+ return [];
83
+ }
84
+ }
@@ -1,28 +1,56 @@
1
1
  /**
2
- * Graphpack artifact contracts — the four files a built pack directory holds
3
- * and the zod schemas that keep them honest on read.
2
+ * Graphpack artifact contracts — the five files a built pack directory holds,
3
+ * as hand-written types with NO runtime dependency.
4
4
  *
5
5
  * A pack is the content-addressed, boot-ready form of a set of RDF sources:
6
6
  * `data.nq` (the store's n-quads dump — boots via ke's cache path, no TTL
7
7
  * parse), `schema.json` (the serialized ke-graphql extraction — boots via
8
8
  * `compileFromExtraction`, no live 7-pass compile), `index.json` (the storeless
9
- * entity index the completion tier and reads consume), and `manifest.json`
9
+ * entity index the completion tier and reads consume), `stories.json` (the read
10
+ * stories the packages ship, carried verbatim as raw text), and `manifest.json`
10
11
  * (provenance + the prefixes the store was built with). A directory missing
11
12
  * `manifest.json` is treated as absent (a torn build), so writes are always
12
13
  * temp-dir + atomic rename.
13
14
  *
14
- * This module is reached only behind a dynamic import (pack build / read /
15
- * store boot), so its zod dependency never lands on the storeless fast path.
15
+ * FIVE AND ONLY FIVE: the constants below are the single place the artifact set
16
+ * is named, and three modules must agree with them — `buildPack` writes them,
17
+ * `packIsComplete` gates on them, and `materializeEmbeddedPack` writes them back
18
+ * out. A sixth artifact added to only some of those makes a pack whose content
19
+ * hash claims more than its directory holds, which the next build then reuses,
20
+ * silently dropping the difference. The agreement is pinned by `graphpack.test.ts`'s
21
+ * "the committed embedded pack (PROTECTED) > materializes exactly the files
22
+ * buildPack produces" — extend the set here and that test fails until every
23
+ * side follows.
24
+ *
25
+ * This module is on the STORELESS FAST PATH — `capabilities/index.ts` →
26
+ * `resolveSources` → `packIsComplete` → `readManifest` reaches it while
27
+ * `__complete` builds the command tree — so it carries NO runtime import at
28
+ * all. It used to hold the zod schemas too, which put zod on that graph through
29
+ * a genuine value import and cost ~3–4 ms of a ~30 ms budget; they now live in
30
+ * `schemas.ts`, imported only by the two readers that are already off the fast
31
+ * path. `capabilities/lazy.test.ts` pins the result — no module on that graph
32
+ * imports zod — so the split cannot silently close.
16
33
  */
17
34
 
18
- import { z } from "zod";
19
-
20
35
  /** The n-quads store dump — ke boots it via `createStore({ cache })`. */
21
36
  export const DATA_FILE = "data.nq";
22
37
  /** The serialized ke-graphql extraction — boots via `compileFromExtraction`. */
23
38
  export const SCHEMA_FILE = "schema.json";
24
39
  /** The storeless entity index (PR-C's dynamic-completion contract). */
25
40
  export const INDEX_FILE = "index.json";
41
+ /**
42
+ * The read stories the packages shipped, as raw text: one
43
+ * `{ source, content }` record per `stories/*.json` file, in a JSON array.
44
+ *
45
+ * Written ALWAYS, even as `[]`, and gated by `packIsComplete` alongside the
46
+ * other three — an optional artifact would put the same condition in all three
47
+ * modules below, which is exactly how a pack ends up claiming stories its
48
+ * directory does not hold. Raw text rather than parsed definitions so the pack stays a
49
+ * faithful carrier of the package's bytes and EVERY interpretation failure
50
+ * (malformed JSON and schema-invalid JSON alike) is caught behind the one guard
51
+ * in `kernel/packs/collect.validateStories`.
52
+ */
53
+ export const STORIES_FILE = "stories.json";
26
54
  /** Provenance + prefixes; its presence marks a pack directory as complete. */
27
55
  export const MANIFEST_FILE = "manifest.json";
28
56
 
@@ -45,9 +73,10 @@ export interface PackIndexEntity {
45
73
  /** Human label (rdfs:label / skos:prefLabel / dcterms:title / schema:name). */
46
74
  readonly label?: string | null;
47
75
  /**
48
- * Alternative completable names — the `ds:name` values a family (e.g.
49
- * `ds:Tier`, matched by `tier lookup`) is addressed by, when they differ from
50
- * `name`/`label`. Enrichment for the storeless name-completion sources.
76
+ * Alternative completable names — the values of the distribution's declared
77
+ * alternative-name property (`kernel/vocabulary.ts`), which is what a
78
+ * bespoke lookup matches on, when they differ from `name`/`label`.
79
+ * Enrichment for the storeless name-completion sources.
51
80
  */
52
81
  readonly altNames?: readonly string[];
53
82
  /** Schema (`tbox`) vs individual (`abox`). */
@@ -74,37 +103,24 @@ export interface PackIndex {
74
103
  readonly instanceCountByType: Readonly<Record<string, number>>;
75
104
  }
76
105
 
77
- /** zod schema validating a persisted {@link PackIndexEntity}. */
78
- export const packIndexEntitySchema: z.ZodType<PackIndexEntity> = z.object({
79
- name: z.string(),
80
- type: z.string(),
81
- uri: z.string().optional(),
82
- prefixed: z.string().optional(),
83
- types: z.array(z.string()).optional(),
84
- label: z.string().nullable().optional(),
85
- altNames: z.array(z.string()).optional(),
86
- box: z.enum(["tbox", "abox"]).optional(),
87
- description: z.string().nullable().optional(),
88
- });
89
-
90
- /** zod schema validating a persisted {@link PackIndex}. */
91
- export const packIndexSchema: z.ZodType<PackIndex> = z.object({
92
- version: z.union([z.literal(1), z.literal(2)]),
93
- contentHash: z.string(),
94
- prefixes: z.record(z.string(), z.string()),
95
- entities: z.array(packIndexEntitySchema),
96
- instanceCountByType: z.record(z.string(), z.number()),
97
- });
98
-
99
- /** zod schema validating a persisted `manifest.json`. */
100
- export const manifestSchema = z.object({
101
- name: z.string(),
102
- version: z.string(),
103
- /** The config `packages` ref this pack was built from (verbatim), or a label. */
104
- sourceRef: z.string(),
105
- contentHash: z.string(),
106
- prefixes: z.record(z.string(), z.string()),
107
- createdAt: z.string(),
106
+ /**
107
+ * Pack provenance and the prefixes the store was built with — the parsed shape
108
+ * of `manifest.json`.
109
+ *
110
+ * Hand-written rather than inferred from a zod schema, so reading a manifest
111
+ * needs no validator library (see the module docblock). `schemas.ts`'s
112
+ * {@link manifestSchema} is annotated against this type, so the two cannot
113
+ * drift in their FIELDS; `graphpack.test.ts` pins that they do not drift in
114
+ * what they ACCEPT either.
115
+ */
116
+ export interface Manifest {
117
+ readonly name: string;
118
+ readonly version: string;
119
+ /** The config `packs` ref this pack was built from (verbatim), or a label. */
120
+ readonly sourceRef: string;
121
+ readonly contentHash: string;
122
+ readonly prefixes: Readonly<Record<string, string>>;
123
+ readonly createdAt: string;
108
124
  /**
109
125
  * The store's triple count at build time. Cross-checked against the booted
110
126
  * store so a truncated-but-non-empty `data.nq` (a partial graph that passes
@@ -112,14 +128,11 @@ export const manifestSchema = z.object({
112
128
  * than being served silently (A9). Optional — packs built before this field
113
129
  * skip the check.
114
130
  */
115
- tripleCount: z.number().optional(),
131
+ readonly tripleCount?: number;
116
132
  /**
117
- * The distinct abox entity count (matches {@link entityTotal}). Lets
133
+ * The distinct abox entity count (matches `entityTotal`). Lets
118
134
  * `sources status` report the figure without parsing the whole `index.json`
119
135
  * (A10). Optional — packs built before this field fall back to the index read.
120
136
  */
121
- entityCount: z.number().optional(),
122
- });
123
-
124
- /** Pack provenance and the prefixes the store was built with. */
125
- export type Manifest = z.infer<typeof manifestSchema>;
137
+ readonly entityCount?: number;
138
+ }
@@ -1,27 +1,44 @@
1
1
  /**
2
2
  * Filesystem locations for the store layer: the content-addressed pack cache,
3
- * the git-ref checkout cache, and the project lock file.
3
+ * the git-ref checkout cache, and the per-project active-pack pointer.
4
4
  *
5
- * The pack cache lives under `$XDG_CACHE_HOME/pragma/packs/<contentHash>/`
5
+ * The pack cache lives under `$XDG_CACHE_HOME/<bin>/packs/<contentHash>/`
6
6
  * (content-addressed, so a new source set is a new directory and invalidation
7
- * is free); ref checkouts under `.../pragma/refs/`. The lock is a committed
8
- * project artifact at `<cwd>/pragma.lock.json`. All cache paths derive from the
9
- * config kernel's {@link cacheDir}, so the XDG isolation the tests install
10
- * contains them.
7
+ * is free); ref checkouts under `.../<bin>/refs/`. Which pack answers a
8
+ * project's reads is recorded by a one-line POINTER under
9
+ * `.../<bin>/projects/` — the content hash `sources update` last built there,
10
+ * and nothing else. It lives in the cache rather than the repo because the pack
11
+ * it names is machine-local: a committed pointer would promise a store the next
12
+ * machine does not have.
13
+ *
14
+ * A LEAF module on purpose: it imports node builtins and the distribution's
15
+ * {@link BIN_NAME}, and owns the cache root's XDG resolution outright
16
+ * (`kernel/config/paths.ts` keeps config and state). The storeless `__complete`
17
+ * fast path (`kernel/completion/entitySource.ts`) has to read the pointer, and
18
+ * `completion/safety.test.ts` forbids the config LAYER anywhere on that import
19
+ * graph — so the cache root is resolved here rather than imported. `constants.ts`
20
+ * is not the config layer: it reads inert data, and that same guard positive-lists
21
+ * it.
11
22
  */
12
23
 
13
- import { join } from "node:path";
14
- import { cacheDir } from "../config/paths.js";
24
+ import { createHash } from "node:crypto";
25
+ import { existsSync, readFileSync } from "node:fs";
26
+ import { homedir } from "node:os";
27
+ import { join, resolve } from "node:path";
28
+ import { BIN_NAME } from "../../constants.js";
15
29
 
16
- /** The lock file basename, committed at the project root. */
17
- export const LOCK_BASENAME = "pragma.lock.json";
30
+ /** `$XDG_CACHE_HOME/<bin>` (default `~/.cache/<bin>`) — the cache root. */
31
+ function cacheDir(): string {
32
+ const base = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
33
+ return join(base, BIN_NAME);
34
+ }
18
35
 
19
- /** `$XDG_CACHE_HOME/pragma/packs` — the content-addressed pack cache root. */
36
+ /** `$XDG_CACHE_HOME/<bin>/packs` — the content-addressed pack cache root. */
20
37
  export function packsCacheDir(): string {
21
38
  return join(cacheDir(), "packs");
22
39
  }
23
40
 
24
- /** `$XDG_CACHE_HOME/pragma/refs` — cached git-ref checkouts. */
41
+ /** `$XDG_CACHE_HOME/<bin>/refs` — cached git-ref checkouts. */
25
42
  export function refsCacheDir(): string {
26
43
  return join(cacheDir(), "refs");
27
44
  }
@@ -31,7 +48,39 @@ export function packDir(contentHash: string): string {
31
48
  return join(packsCacheDir(), contentHash);
32
49
  }
33
50
 
34
- /** The project lock file for a working directory. */
35
- export function lockPath(cwd: string): string {
36
- return join(cwd, LOCK_BASENAME);
51
+ /**
52
+ * The active-pack pointer for a project: `$XDG_CACHE_HOME/<bin>/projects/<key>`.
53
+ *
54
+ * The key is the SHA-256 of the resolved `cwd` — a hash rather than the path
55
+ * itself so a deep project directory can never exceed the 255-byte filename
56
+ * limit, and a cryptographic one so two projects can never collide onto each
57
+ * other's graph.
58
+ */
59
+ export function activePackPath(cwd: string): string {
60
+ return join(
61
+ cacheDir(),
62
+ "projects",
63
+ createHash("sha256").update(resolve(cwd)).digest("hex"),
64
+ );
65
+ }
66
+
67
+ /**
68
+ * The content hash of the pack `sources update` built for a project.
69
+ *
70
+ * @param cwd - The project directory.
71
+ * @returns The 64-hex content hash, or `undefined` when the project was never
72
+ * built. A pointer that is unreadable, or that does not hold exactly one
73
+ * hash, is treated as absent — a corrupt pointer must never name an arbitrary
74
+ * cache directory.
75
+ * @note Impure — reads the pointer from the cache.
76
+ */
77
+ export function readActivePack(cwd: string): string | undefined {
78
+ const path = activePackPath(cwd);
79
+ if (!existsSync(path)) return undefined;
80
+ try {
81
+ const hash = readFileSync(path, "utf-8").trim();
82
+ return /^[0-9a-f]{64}$/.test(hash) ? hash : undefined;
83
+ } catch {
84
+ return undefined;
85
+ }
37
86
  }
@@ -1,16 +1,20 @@
1
1
  import { describe, expect, it } from "vitest";
2
+ import { embeddedManifest } from "./graphpack/embedded.js";
2
3
  import { runStoreProbe } from "./probe.js";
3
4
 
4
5
  describe("store probe (in-process)", () => {
5
- it("boots the embedded pack and reports entity + triple counts", async () => {
6
+ it("boots the embedded pack and finds the triples its manifest claims", async () => {
6
7
  const out = JSON.parse(await runStoreProbe()) as {
7
8
  ok: boolean;
8
9
  entities: number;
9
10
  triples: string;
10
11
  };
11
12
  expect(out.ok).toBe(true);
12
- // The embedded sample pack ships six indexed entities (3 abox + 3 tbox).
13
- expect(out.entities).toBe(6);
14
- expect(Number(out.triples)).toBeGreaterThan(0);
13
+ // Against the COMMITTED manifest, not a recomputation: a store booted from
14
+ // `data.nq` must really hold what `manifest.json` says was built into it.
15
+ // That catches a regenerated dump that disagrees with its own provenance,
16
+ // and needs no literal that churns whenever upstream moves.
17
+ expect(Number(out.triples)).toBe(embeddedManifest().tripleCount);
18
+ expect(out.entities).toBeGreaterThan(0);
15
19
  });
16
20
  });
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { PragmaError } from "../error/PragmaError.js";
19
+ import { cliRecovery } from "../error/recovery.js";
19
20
  import { resolveUri } from "../packs/iri.js";
20
21
  import type { PragmaRuntime } from "./types.js";
21
22
 
@@ -57,11 +58,11 @@ export async function readEntity(
57
58
 
58
59
  if (result.type !== "select" || result.bindings.length === 0) {
59
60
  throw PragmaError.notFound("entity", uri, {
60
- recovery: {
61
- message: "Check the URI, or list known entities.",
62
- cli: "pragma graph query 'SELECT ?s WHERE { ?s ?p ?o } LIMIT 10'",
63
- mcp: { tool: "graph_query" },
64
- },
61
+ recovery: cliRecovery(
62
+ `graph query 'SELECT ?s WHERE { ?s ?p ?o } LIMIT 10'`,
63
+ "Check the URI, or list known entities.",
64
+ { tool: "graph_query" },
65
+ ),
65
66
  });
66
67
  }
67
68
 
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { execFileSync } from "node:child_process";
12
- import { existsSync, mkdirSync } from "node:fs";
12
+ import { mkdirSync } from "node:fs";
13
13
  import { dirname } from "node:path";
14
14
 
15
15
  /** Whether a ref looks like a commit SHA (7-40 hex chars). */
@@ -64,24 +64,6 @@ export function fetchRef(url: string, ref: string, dest: string): string {
64
64
  return gitOutput(dest, ["rev-parse", "HEAD"]);
65
65
  }
66
66
 
67
- /** Check out an exact commit in an existing clone (fetching it if needed). */
68
- export function checkoutCommit(
69
- url: string,
70
- commit: string,
71
- dest: string,
72
- ): void {
73
- if (!existsSync(dest)) {
74
- cloneRef(url, commit, dest);
75
- return;
76
- }
77
- try {
78
- git(dest, ["checkout", commit]);
79
- } catch {
80
- git(dest, ["fetch", "--depth", "1", url, commit]);
81
- git(dest, ["checkout", commit]);
82
- }
83
- }
84
-
85
67
  /** The resolved HEAD commit of a checkout. */
86
68
  export function headCommit(dest: string): string {
87
69
  return gitOutput(dest, ["rev-parse", "HEAD"]);
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Parse a config `packages` entry into a typed {@link PackageRef}.
2
+ * Parse a config `packs` declaration into a typed {@link PackageRef}.
3
3
  *
4
4
  * Ported from the v1 refs domain. Three resolution strategies, discriminated by
5
5
  * `kind`: `npm` (a bare package name, resolved via `require.resolve`), `file`
6
6
  * (a `file://` local path override), and `git` (a `git+<proto>://…#ref` shallow
7
- * clone). The `source` string is kept verbatim so the lock records exactly what
8
- * the config declared.
7
+ * clone). The `source` string is kept verbatim so `sources status` reports
8
+ * exactly what the config declared.
9
9
  */
10
10
 
11
- import type { PackageEntry } from "../../config/types.js";
11
+ import type { PackDeclaration } from "../../config/types.js";
12
12
  import { PragmaError } from "../../error/PragmaError.js";
13
13
 
14
14
  /** A parsed, validated package reference — discriminated by `kind`. */
@@ -29,13 +29,13 @@ export type PackageRef =
29
29
  };
30
30
 
31
31
  /**
32
- * Parse a config `packages` entry into a {@link PackageRef}.
32
+ * Parse a config `packs` declaration into a {@link PackageRef}.
33
33
  *
34
34
  * @param entry - A bare npm name, or a `{ name, source }` declaration.
35
35
  * @returns The parsed reference (with the verbatim `source`).
36
36
  * @throws PragmaError CONFIG_ERROR on an invalid `source`.
37
37
  */
38
- export function parsePackageEntry(entry: PackageEntry): PackageRef {
38
+ export function parsePackDeclaration(entry: PackDeclaration): PackageRef {
39
39
  if (typeof entry === "string") {
40
40
  return { kind: "npm", pkg: entry, source: entry };
41
41
  }
@@ -43,7 +43,7 @@ export function parsePackageEntry(entry: PackageEntry): PackageRef {
43
43
  const { name, source } = entry;
44
44
  if (typeof name !== "string" || name.length === 0) {
45
45
  throw PragmaError.configError(
46
- 'Package entry must have a non-empty "name".',
46
+ 'Pack declaration must have a non-empty "name".',
47
47
  );
48
48
  }
49
49
  if (source === undefined) {