@canonical/pragma-cli 0.32.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.
- package/README.md +72 -0
- package/dist/pragma +0 -0
- package/package.json +19 -15
- package/pragma.conf.ts +664 -0
- package/src/bin.ts +15 -6
- package/src/capabilities/block.parity.test.ts +250 -0
- package/src/capabilities/capabilities/capabilities.render.ts +3 -2
- package/src/capabilities/capabilities/capabilities.test.ts +10 -1
- package/src/capabilities/capabilities/capabilities.verb.ts +32 -14
- package/src/capabilities/capabilities/catalog.ts +20 -8
- package/src/capabilities/capabilities/hints.ts +21 -36
- package/src/capabilities/capabilities/types.ts +2 -0
- package/src/capabilities/colophon/collectColophon.ts +34 -19
- package/src/capabilities/colophon/colophon.render.test.ts +31 -4
- package/src/capabilities/colophon/colophon.render.ts +13 -4
- package/src/capabilities/colophon/colophon.test.ts +3 -3
- package/src/capabilities/colophon/colophon.verb.ts +11 -4
- package/src/capabilities/colophon/markdownTerminal.ts +33 -14
- package/src/capabilities/colophon/types.ts +16 -7
- package/src/capabilities/config/collectConfigShow.test.ts +57 -0
- package/src/capabilities/config/collectConfigShow.ts +41 -4
- package/src/capabilities/config/fields.ts +10 -108
- package/src/capabilities/config/set.test.ts +6 -6
- package/src/capabilities/config/set.verb.ts +12 -8
- package/src/capabilities/config/show.render.test.ts +137 -0
- package/src/capabilities/config/show.render.ts +57 -16
- package/src/capabilities/config/show.verb.ts +9 -8
- package/src/capabilities/config/types.ts +8 -0
- package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
- package/src/capabilities/create/byteEquality.test.ts +82 -101
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +53 -16
- package/src/capabilities/create/constants.ts +75 -0
- package/src/capabilities/create/create.mcp.test.ts +2 -2
- package/src/capabilities/create/create.test.ts +192 -16
- package/src/capabilities/create/create.verb.ts +208 -79
- package/src/capabilities/create/index.ts +2 -10
- package/src/capabilities/create/pickGenerator.ts +34 -28
- package/src/capabilities/create/templates.embedded.generated.ts +4 -29
- package/src/capabilities/create/types.ts +10 -6
- package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
- package/src/capabilities/distribution.test.ts +120 -0
- package/src/capabilities/distribution.ts +59 -0
- package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
- package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
- package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
- package/src/capabilities/doctor/checks/checkMcpConfigured.ts +15 -8
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
- package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
- package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
- package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
- package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
- package/src/capabilities/doctor/checks/deriveBand.test.ts +42 -0
- package/src/capabilities/doctor/checks/deriveBand.ts +28 -0
- package/src/capabilities/doctor/doctor.render.test.ts +170 -0
- package/src/capabilities/doctor/doctor.render.ts +117 -34
- package/src/capabilities/doctor/doctor.test.ts +68 -9
- package/src/capabilities/doctor/doctor.verb.ts +6 -2
- package/src/capabilities/doctor/runChecks.ts +33 -9
- package/src/capabilities/doctor/types.ts +16 -1
- package/src/capabilities/emptyRecovery.test.ts +32 -15
- package/src/capabilities/graph/inspect.verb.ts +3 -2
- package/src/capabilities/graph/query.verb.ts +3 -2
- package/src/capabilities/graph/runQuery.ts +8 -5
- package/src/capabilities/index.ts +22 -18
- package/src/capabilities/info/collectInfo.ts +10 -6
- package/src/capabilities/info/info.render.ts +3 -2
- package/src/capabilities/info/info.verb.ts +3 -2
- package/src/capabilities/lazy.test.ts +93 -2
- package/src/capabilities/ontology/index.ts +10 -3
- package/src/capabilities/ontology/ontology.test.ts +157 -5
- package/src/capabilities/ontology/verbs.ts +203 -104
- package/src/capabilities/prompt/prompt.render.test.ts +94 -0
- package/src/capabilities/prompt/prompt.render.ts +23 -6
- package/src/capabilities/prompt/prompt.test.ts +68 -1
- package/src/capabilities/prompt/verbs.ts +24 -11
- package/src/capabilities/reference.test.ts +32 -0
- package/src/capabilities/resources/provider.ts +58 -25
- package/src/capabilities/resources/resources.test.ts +176 -21
- package/src/capabilities/scopeTypeSync.test.ts +37 -0
- package/src/capabilities/setup/index.ts +7 -1
- package/src/capabilities/setup/operations/setupCompletions.ts +49 -16
- package/src/capabilities/setup/operations/setupGenerator.ts +166 -63
- package/src/capabilities/setup/operations/setupLsp.ts +56 -12
- package/src/capabilities/setup/operations/setupMcp.ts +216 -39
- package/src/capabilities/setup/operations/setupSkills.ts +10 -4
- package/src/capabilities/setup/setup.render.test.ts +109 -0
- package/src/capabilities/setup/setup.render.ts +61 -14
- package/src/capabilities/setup/setup.test.ts +352 -1
- package/src/capabilities/setup/setup.verb.ts +68 -9
- package/src/capabilities/setup/shell.ts +29 -3
- package/src/capabilities/setup/types.ts +59 -2
- package/src/capabilities/shared/bands.ts +16 -0
- package/src/capabilities/skill/discover.ts +11 -5
- package/src/capabilities/skill/render.ts +4 -5
- package/src/capabilities/skill/verbs.ts +7 -7
- package/src/capabilities/sources/collectStatus.ts +69 -49
- package/src/capabilities/sources/index.ts +1 -1
- package/src/capabilities/sources/installSkills.ts +2 -2
- package/src/capabilities/sources/runUpdate.ts +146 -98
- package/src/capabilities/sources/sources.test.ts +264 -84
- package/src/capabilities/sources/status.render.test.ts +94 -0
- package/src/capabilities/sources/status.render.ts +55 -33
- package/src/capabilities/sources/status.verb.ts +15 -8
- package/src/capabilities/sources/types.ts +18 -25
- package/src/capabilities/sources/update.render.ts +9 -2
- package/src/capabilities/sources/update.verb.ts +12 -23
- package/src/capabilities/surface.test.ts +65 -17
- package/src/capabilities/tier.parity.test.ts +144 -0
- package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
- package/src/constants.test.ts +9 -7
- package/src/constants.ts +49 -23
- package/src/identity.test.ts +333 -0
- package/src/index.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +18 -16
- package/src/kernel/completion/complete.test.ts +3 -5
- package/src/kernel/completion/emitScripts.test.ts +1 -1
- package/src/kernel/completion/emitScripts.ts +4 -2
- package/src/kernel/completion/entitySource.test.ts +185 -41
- package/src/kernel/completion/entitySource.ts +131 -113
- package/src/kernel/completion/model.test.ts +3 -3
- package/src/kernel/completion/model.ts +11 -1
- package/src/kernel/completion/parse.test.ts +2 -4
- package/src/kernel/completion/safety.test.ts +82 -36
- package/src/kernel/completion/shellDrive.test.ts +592 -0
- package/src/kernel/completion/templates/fish.ts +28 -6
- package/src/kernel/completion/types.ts +3 -3
- package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
- package/src/kernel/config/defaults.test.ts +85 -0
- package/src/kernel/config/defaults.ts +12 -23
- package/src/kernel/config/evaluateProjectConfig.ts +2 -3
- package/src/kernel/config/findProjectConfig.ts +4 -3
- package/src/kernel/config/firstRun.ts +12 -5
- package/src/kernel/config/globalConfig.ts +2 -2
- package/src/kernel/config/index.ts +4 -5
- package/src/kernel/config/paths.ts +17 -17
- package/src/kernel/config/readConfig.test.ts +242 -6
- package/src/kernel/config/readConfig.ts +8 -8
- package/src/kernel/config/schema.test.ts +50 -0
- package/src/kernel/config/schema.ts +97 -10
- package/src/kernel/config/types.ts +73 -19
- package/src/kernel/copy.test.ts +453 -0
- package/src/kernel/error/PragmaError.test.ts +5 -5
- package/src/kernel/error/constants.ts +3 -2
- package/src/kernel/error/index.ts +1 -1
- package/src/kernel/error/recovery.test.ts +22 -45
- package/src/kernel/error/recovery.ts +22 -27
- package/src/kernel/error/renderError.ts +1 -1
- package/src/kernel/error/types.ts +5 -4
- package/src/kernel/packs/collect.test.ts +195 -11
- package/src/kernel/packs/collect.ts +213 -58
- package/src/kernel/packs/compile.test.ts +163 -7
- package/src/kernel/packs/compile.ts +15 -12
- package/src/kernel/packs/disclosure.test.ts +5 -2
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
- package/src/kernel/packs/graphqlSource.test.ts +6 -3
- package/src/kernel/packs/iri.ts +5 -7
- package/src/kernel/packs/renderPack.ts +5 -3
- package/src/kernel/packs/resolveEntity.ts +21 -17
- package/src/kernel/packs/runBodies.ts +15 -14
- package/src/kernel/packs/schema.ts +96 -10
- package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
- package/src/kernel/packs/sparql/runSelect.ts +73 -34
- package/src/kernel/packs/toolDescription.test.ts +10 -2
- package/src/kernel/packs/types.ts +39 -4
- package/src/kernel/packs/uniqueness.ts +4 -4
- package/src/kernel/project/cli/buildProgram.test.ts +60 -11
- package/src/kernel/project/cli/buildProgram.ts +11 -0
- package/src/kernel/project/cli/dispatch.test.ts +67 -0
- package/src/kernel/project/cli/dispatch.ts +34 -12
- package/src/kernel/project/cli/globalFlags.test.ts +12 -4
- package/src/kernel/project/cli/globalFlags.ts +14 -14
- package/src/kernel/project/cli/helpFormat.ts +1 -1
- package/src/kernel/project/cli/rootHelp.ts +109 -107
- package/src/kernel/project/mcp/buildServer.ts +11 -6
- package/src/kernel/project/mcp/instructions.ts +12 -7
- package/src/kernel/project/mcp/mcpError.ts +52 -0
- package/src/kernel/project/mcp/parity.test.ts +39 -1
- package/src/kernel/project/mcp/prompts/prompts.test.ts +128 -5
- package/src/kernel/project/mcp/prompts/provider.ts +51 -7
- package/src/kernel/project/mcp/prompts/source.ts +68 -67
- package/src/kernel/project/mcp/registerVerb.ts +25 -6
- package/src/kernel/project/mcp/serve.ts +11 -3
- package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
- package/src/kernel/render/disclosure.ts +4 -2
- package/src/kernel/render/formatters.ts +5 -4
- package/src/kernel/render/prefixes.test.ts +36 -0
- package/src/kernel/render/prefixes.ts +40 -13
- package/src/kernel/render/renderers.test.ts +35 -1
- package/src/kernel/render/renderers.ts +25 -7
- package/src/kernel/render/style.test.ts +31 -0
- package/src/kernel/render/style.ts +74 -0
- package/src/kernel/runtime/graphpack/build.ts +57 -3
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +149 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +173 -50
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -8
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -4
- package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +37 -11
- package/src/kernel/runtime/graphpack/graphpack.test.ts +274 -6
- package/src/kernel/runtime/graphpack/hash.ts +3 -2
- package/src/kernel/runtime/graphpack/index.ts +1 -1
- package/src/kernel/runtime/graphpack/manifest.ts +114 -12
- package/src/kernel/runtime/graphpack/read.ts +32 -22
- package/src/kernel/runtime/graphpack/schemas.ts +74 -0
- package/src/kernel/runtime/graphpack/stories.ts +84 -0
- package/src/kernel/runtime/graphpack/types.ts +72 -45
- package/src/kernel/runtime/loadSession.ts +4 -12
- package/src/kernel/runtime/paths.ts +64 -15
- package/src/kernel/runtime/probe.test.ts +8 -4
- package/src/kernel/runtime/readEntity.ts +6 -5
- package/src/kernel/runtime/refs/gitOps.ts +1 -19
- package/src/kernel/runtime/refs/parseRef.ts +7 -7
- package/src/kernel/runtime/refs/resolve.ts +141 -65
- package/src/kernel/runtime/resolveSources.test.ts +63 -24
- package/src/kernel/runtime/resolveSources.ts +36 -25
- package/src/kernel/runtime/storeReadiness.ts +62 -0
- package/src/kernel/runtime/storeless.test.ts +1 -1
- package/src/kernel/runtime/types.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +25 -20
- package/src/kernel/spec/emitReference.test.ts +196 -0
- package/src/kernel/spec/emitReference.ts +624 -0
- package/src/kernel/spec/emitSurface.ts +13 -7
- package/src/kernel/spec/index.ts +2 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/surfaceConformance.ts +8 -0
- package/src/kernel/spec/types.ts +29 -9
- package/src/kernel/spec/validate.ts +2 -1
- package/src/kernel/vocabulary.test.ts +164 -0
- package/src/kernel/vocabulary.ts +146 -0
- package/src/testing/PARITY_GAPS.ts +9 -3
- package/src/testing/behavioral/README.md +3 -3
- package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
- package/src/testing/behavioral/completion.test.ts +21 -8
- package/src/testing/behavioral/disclosure.test.ts +12 -0
- package/src/testing/behavioral/docExamples.test.ts +283 -0
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +3 -2
- package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
- package/src/testing/behavioral/harness.foundation.test.ts +14 -5
- package/src/testing/behavioral/journeys.cli.test.ts +116 -73
- package/src/testing/behavioral/journeys.defaultPack.test.ts +477 -0
- package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
- package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +66 -0
- package/src/testing/behavioral/journeys.livePack.e2e.test.ts +129 -0
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +179 -0
- package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
- package/src/testing/behavioral/liveReadSurface.ts +4 -3
- package/src/testing/behavioral/mcpSurface.test.ts +10 -5
- package/src/testing/behavioral/staleCommands.test.ts +86 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +9 -9
- package/src/testing/eval/cases/assembly.ts +16 -9
- package/src/testing/eval/cases/effectful.ts +31 -16
- package/src/testing/eval/cases/readNouns.ts +23 -18
- package/src/testing/eval/cases/stable.ts +5 -1
- package/src/testing/eval/eval.test.ts +7 -1
- package/src/testing/fixtures/fixtureCapability.ts +201 -1
- package/src/testing/fixtures/graph/canonical.ts +6 -3
- package/src/testing/fixtures/graph/defaultPack.ts +179 -0
- package/src/testing/fixtures/graph/owlExport.ts +97 -0
- package/src/testing/helpers/extractSnippets.ts +29 -0
- package/src/testing/helpers/fixtureGraph.ts +33 -16
- package/src/testing/helpers/golden.ts +1 -1
- package/src/testing/helpers/packRuntime.ts +2 -3
- package/src/testing/helpers/projectCli.ts +2 -1
- package/src/testing/helpers/projectMcp.ts +6 -0
- package/src/testing/perf/budgets.test.ts +4 -0
- package/src/testing/perf/budgets.ts +22 -7
- package/src/testing/perf/globalSetup.ts +121 -6
- package/src/types.ts +2 -2
- package/src/capabilities/block/blockList.verb.ts +0 -150
- package/src/capabilities/block/index.ts +0 -25
- package/src/capabilities/block/pack.ts +0 -155
- package/src/capabilities/block/parity.test.ts +0 -180
- package/src/capabilities/block/tierChain.ts +0 -78
- package/src/capabilities/colophon/pragmaColophon.ts +0 -66
- package/src/capabilities/config/field.test.ts +0 -188
- package/src/capabilities/modifier/index.ts +0 -15
- package/src/capabilities/modifier/pack.ts +0 -64
- package/src/capabilities/standard/index.ts +0 -18
- package/src/capabilities/standard/pack.ts +0 -128
- package/src/capabilities/tier/index.ts +0 -19
- package/src/capabilities/tier/lookup.render.ts +0 -34
- package/src/capabilities/tier/lookup.test.ts +0 -68
- package/src/capabilities/tier/lookup.verb.ts +0 -44
- package/src/capabilities/tier/pack.ts +0 -28
- package/src/capabilities/tier/runLookup.ts +0 -59
- package/src/capabilities/token/addConfig.render.ts +0 -27
- package/src/capabilities/token/addConfig.test.ts +0 -62
- package/src/capabilities/token/addConfig.verb.ts +0 -40
- package/src/capabilities/token/index.ts +0 -19
- package/src/capabilities/token/pack.ts +0 -58
- package/src/capabilities/token/runAddConfig.ts +0 -95
- package/src/kernel/completion/bashDrive.test.ts +0 -73
- package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
- package/src/kernel/runtime/lock.ts +0 -90
|
@@ -4,46 +4,59 @@
|
|
|
4
4
|
* browser (`info`, `doctor`, MCP prompts, the resource provider) shares.
|
|
5
5
|
*
|
|
6
6
|
* {@link indexCompletionEnv} is the one source-dispatched reader wired at the
|
|
7
|
-
* `__complete` fast path (bin) and the `__complete` verb. It resolves
|
|
7
|
+
* `__complete` fast path (bin) and the `__complete` verb. It resolves three
|
|
8
8
|
* storeless sources — all disk-readable, never a store/SPARQL:
|
|
9
|
-
* - `index` → the active pack's `index.json
|
|
9
|
+
* - `index` → the active pack's `index.json`, filtered by a prefixed `type`
|
|
10
|
+
* and read from the declared `field` (`name` by default).
|
|
10
11
|
* - `skills` → SKILL.md names via {@link discoverSkills} (filesystem walk).
|
|
11
|
-
* - `prompts` → the pack index's `ds:Prompt` entities (`label || name`).
|
|
12
|
-
* - `tiers` → the pack index's `ds:Tier` entities (their `ds:name`, else
|
|
13
|
-
* `label ?? name` when the index carries no `ds:name`).
|
|
14
12
|
* - `prefixes` → the pack index's prefixes ∪ the default display prefixes.
|
|
15
13
|
*
|
|
14
|
+
* There is no per-family source. An entity family IS a `type` filter plus the
|
|
15
|
+
* field that family is addressed by, and both are declared at the verb, so this
|
|
16
|
+
* module names no entity type at all.
|
|
17
|
+
*
|
|
16
18
|
* The index is loaded once (lazy `readFileSync` + plain `JSON.parse` — never a
|
|
17
19
|
* store, facade, config evaluator, or zod schema; any I/O or parse error
|
|
18
|
-
* degrades to `[]`) and shared across the index
|
|
20
|
+
* degrades to `[]`) and shared across the index and prefixes sources;
|
|
19
21
|
* the skills walk is memoized to one walk per `__complete` process.
|
|
20
22
|
* {@link createIndexEntityReader} exposes the index read for the storeless
|
|
21
23
|
* behavioural/safety tests; {@link emptyNameSource} is the default (no names).
|
|
22
24
|
*
|
|
23
25
|
* {@link readPackIndex} and {@link entityTotal} expose the same storeless read
|
|
24
|
-
* to `info`/`doctor` and the resource surfaces
|
|
25
|
-
* the
|
|
26
|
-
*
|
|
26
|
+
* to `info`/`doctor` and the resource surfaces — but those callers are NOT on
|
|
27
|
+
* the fast path, so `readPackIndex` takes the boot decision rather than
|
|
28
|
+
* re-deriving one. Everything here relies only on the FROZEN `{ name, type }`
|
|
29
|
+
* index minimum (plus optional enrichment like `label`/`altNames`), so the
|
|
30
|
+
* index can grow without breaking completion.
|
|
27
31
|
*
|
|
28
32
|
* Storeless-graph notes:
|
|
29
33
|
* - The embedded fallback is read from `pack.index.generated` — its OWN
|
|
30
34
|
* generated module (only the index string) — so the storeless `__complete`
|
|
31
|
-
* path never
|
|
32
|
-
* `pack.generated`.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
35
|
+
* path never EVALUATES the n-quads/schema/manifest strings that live in
|
|
36
|
+
* `pack.generated`. It does not avoid parsing them: `bun build --compile`
|
|
37
|
+
* emits one script, so the whole embed is parsed at process start on every
|
|
38
|
+
* invocation (~+25 ms here, measured on `--version` against a toy-embed
|
|
39
|
+
* binary; BUDGETS.md records it). What the split buys is that a completion
|
|
40
|
+
* never allocates the 1.87 MB.
|
|
41
|
+
* - The active pack is resolved through `kernel/runtime/paths` — a LEAF module
|
|
42
|
+
* (node builtins only) that shares the pointer read with `resolveSources`.
|
|
43
|
+
* `resolveSources` itself is unreachable from here: it pulls the graphpack
|
|
44
|
+
* manifest schema (zod), which `safety.test.ts` forbids on this graph, and
|
|
45
|
+
* `origins.packs` needs the config evaluator. So {@link loadActiveIndex}
|
|
46
|
+
* implements only the POINTER half of the decision table — pointer → that
|
|
47
|
+
* pack (else nothing), no pointer → the embedded snapshot. It cannot tell a
|
|
48
|
+
* fresh install from a project that declared its own packs and never built
|
|
49
|
+
* them, so a `__complete` in the latter still offers the snapshot's names.
|
|
50
|
+
* That is the price of a config-free fast path, and it is bounded to
|
|
51
|
+
* completion candidates: every read, and every surface that takes the
|
|
52
|
+
* decision, refuses. `PackIndex`/`PackIndexEntity` are imported type-only, so
|
|
53
|
+
* no zod schema is loaded at runtime.
|
|
54
|
+
* - `discoverSkills` (`node:fs/os/path` only) and `DEFAULT_PREFIX_MAP` (whose
|
|
55
|
+
* domain half the distribution declares) are the only new edges — both
|
|
56
|
+
* leaf-clean, so the fast path stays free of boot/config/store/zod.
|
|
43
57
|
*/
|
|
44
58
|
|
|
45
|
-
import {
|
|
46
|
-
import { homedir } from "node:os";
|
|
59
|
+
import { readFileSync } from "node:fs";
|
|
47
60
|
import { join } from "node:path";
|
|
48
61
|
import { discoverSkills } from "../../capabilities/skill/discover.js";
|
|
49
62
|
// Inlined embedded index — its OWN generated module (only the index string), so
|
|
@@ -52,91 +65,97 @@ import { discoverSkills } from "../../capabilities/skill/discover.js";
|
|
|
52
65
|
import { DEFAULT_PREFIX_MAP } from "../render/prefixes.js";
|
|
53
66
|
import { indexJson as EMBEDDED_INDEX_JSON } from "../runtime/graphpack/embedded/pack.index.generated.js";
|
|
54
67
|
import type { PackIndex, PackIndexEntity } from "../runtime/graphpack/types.js";
|
|
55
|
-
import
|
|
68
|
+
import { packDir, readActivePack } from "../runtime/paths.js";
|
|
69
|
+
import type { SourcesDecision } from "../runtime/resolveSources.js";
|
|
70
|
+
import type { CompletionField, CompletionSourceRef } from "../spec/types.js";
|
|
56
71
|
import type { CompletionEnv } from "./types.js";
|
|
57
72
|
|
|
58
|
-
/** The prefixed KG type every prompt entity carries (inlined; no cycle). */
|
|
59
|
-
const PROMPT_TYPE = "ds:Prompt";
|
|
60
|
-
/** The prefixed KG type every tier entity carries (inlined; no cycle). */
|
|
61
|
-
const TIER_TYPE = "ds:Tier";
|
|
62
|
-
|
|
63
73
|
/** The default name source: no index tier, so no candidates. */
|
|
64
74
|
export const emptyNameSource: CompletionEnv["names"] = () => [];
|
|
65
75
|
|
|
66
|
-
/** The committed project lock basename (mirrors `kernel/runtime/paths`). */
|
|
67
|
-
const LOCK_BASENAME = "pragma.lock.json";
|
|
68
76
|
/** The pack index filename (kept local so this path never imports the zod schema). */
|
|
69
77
|
const INDEX_FILE = "index.json";
|
|
70
78
|
|
|
71
79
|
/**
|
|
72
|
-
*
|
|
80
|
+
* Read the storeless index of the pack the BOOT DECISION names — for `info`,
|
|
81
|
+
* `doctor`, the MCP resource browser, and native `prompts/list`. Never boots the
|
|
82
|
+
* store, never validates with zod — a plain `JSON.parse` off disk.
|
|
73
83
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const base = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
|
|
79
|
-
return join(base, "pragma", "packs", contentHash);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** The committed project lock file for a working directory. */
|
|
83
|
-
function lockPath(cwd: string): string {
|
|
84
|
-
return join(cwd, LOCK_BASENAME);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Read the active pack's storeless index (the locked pack, else the embedded
|
|
89
|
-
* fallback), for the resource browser's list/autocomplete. Never boots the
|
|
90
|
-
* store, never validates with zod — a plain `JSON.parse` off disk. Returns
|
|
91
|
-
* `undefined` when no index is reachable, so callers degrade to a recovery hint.
|
|
84
|
+
* It takes the decision rather than re-deriving one so these surfaces cannot
|
|
85
|
+
* list entities the same project's reads refuse: `unavailable` yields
|
|
86
|
+
* `undefined`, and callers degrade to a recovery hint (`buildResourceList`
|
|
87
|
+
* emits the `pragma:sources` entry, `info` omits the total).
|
|
92
88
|
*
|
|
93
|
-
* @param
|
|
94
|
-
* @returns The
|
|
89
|
+
* @param decision - The boot decision from `resolveSources`.
|
|
90
|
+
* @returns The answering pack's index, or `undefined` when none answers.
|
|
95
91
|
*/
|
|
96
|
-
export function readPackIndex(
|
|
97
|
-
|
|
92
|
+
export function readPackIndex(
|
|
93
|
+
decision: SourcesDecision,
|
|
94
|
+
): PackIndex | undefined {
|
|
95
|
+
switch (decision.kind) {
|
|
96
|
+
case "pack":
|
|
97
|
+
return readIndexFile(decision.dir);
|
|
98
|
+
case "embedded":
|
|
99
|
+
return parseIndex(EMBEDDED_INDEX_JSON);
|
|
100
|
+
case "unavailable":
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
/**
|
|
101
|
-
*
|
|
106
|
+
* Count a pack index's DISTINCT abox subjects — the "total entities" figure
|
|
102
107
|
* `info` and `doctor` report. Works over any {@link PackIndex}, whether read
|
|
103
108
|
* storelessly via {@link readPackIndex} or taken from a booted store session.
|
|
104
109
|
*
|
|
110
|
+
* NOT a sum of `instanceCountByType`: on a real OWL/Protégé export that raw
|
|
111
|
+
* multiset double-counts — every individual is typed as both its domain class
|
|
112
|
+
* AND `owl:NamedIndividual`, and the `owl:Class`/property meta-buckets pile on
|
|
113
|
+
* top — so the total ran ~2× the real entity count (A1). Counting distinct
|
|
114
|
+
* abox subjects (the individuals, each once) is the figure users expect; the
|
|
115
|
+
* tbox schema classes/properties are not "entities" in this count.
|
|
116
|
+
*
|
|
105
117
|
* @param index - A pack index.
|
|
106
|
-
* @returns The
|
|
118
|
+
* @returns The number of distinct abox subjects indexed.
|
|
107
119
|
*/
|
|
108
120
|
export function entityTotal(index: PackIndex): number {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
const subjects = new Set<string>();
|
|
122
|
+
for (const entity of index.entities) {
|
|
123
|
+
if (entity.box === "abox") subjects.add(entity.uri ?? entity.name);
|
|
124
|
+
}
|
|
125
|
+
return subjects.size;
|
|
113
126
|
}
|
|
114
127
|
|
|
115
|
-
/**
|
|
116
|
-
function
|
|
128
|
+
/** Parse an index JSON string; any malformed input degrades to `undefined`. */
|
|
129
|
+
function parseIndex(json: string): PackIndex | undefined {
|
|
117
130
|
try {
|
|
118
|
-
|
|
119
|
-
if (existsSync(path)) {
|
|
120
|
-
const lock = JSON.parse(readFileSync(path, "utf-8")) as {
|
|
121
|
-
contentHash?: unknown;
|
|
122
|
-
};
|
|
123
|
-
if (typeof lock.contentHash === "string") {
|
|
124
|
-
const indexPath = join(packDir(lock.contentHash), INDEX_FILE);
|
|
125
|
-
if (existsSync(indexPath)) {
|
|
126
|
-
return JSON.parse(readFileSync(indexPath, "utf-8")) as PackIndex;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
131
|
+
return JSON.parse(json) as PackIndex;
|
|
130
132
|
} catch {
|
|
131
|
-
|
|
133
|
+
return undefined;
|
|
132
134
|
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Read a pack directory's `index.json`; any I/O or parse error is `undefined`. */
|
|
138
|
+
function readIndexFile(dir: string): PackIndex | undefined {
|
|
133
139
|
try {
|
|
134
|
-
return
|
|
140
|
+
return parseIndex(readFileSync(join(dir, INDEX_FILE), "utf-8"));
|
|
135
141
|
} catch {
|
|
136
142
|
return undefined;
|
|
137
143
|
}
|
|
138
144
|
}
|
|
139
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The fast path's index — the POINTER half of the decision table (see the module
|
|
148
|
+
* docblock). A project with a pointer reads that pack and nothing else: a
|
|
149
|
+
* pointer whose pack the cache lost yields no candidates rather than the
|
|
150
|
+
* snapshot's, which is what the boot decision does with the same state.
|
|
151
|
+
*/
|
|
152
|
+
function loadActiveIndex(cwd: string): PackIndex | undefined {
|
|
153
|
+
const contentHash = readActivePack(cwd);
|
|
154
|
+
return contentHash === undefined
|
|
155
|
+
? parseIndex(EMBEDDED_INDEX_JSON)
|
|
156
|
+
: readIndexFile(packDir(contentHash));
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
/** Whether an entity matches a prefixed type filter (primary type or any type). */
|
|
141
160
|
function matchesType(entity: PackIndexEntity, type: string): boolean {
|
|
142
161
|
if (!type) return true;
|
|
@@ -172,42 +191,45 @@ export function createIndexEntityReader(
|
|
|
172
191
|
};
|
|
173
192
|
}
|
|
174
193
|
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
194
|
+
/**
|
|
195
|
+
* One entity's completable tokens for a declared field, with NO fallback.
|
|
196
|
+
*
|
|
197
|
+
* A verb declares the field because that is the one its lookup matches on, so
|
|
198
|
+
* an entity the pack did not enrich contributes nothing. Standing a label in
|
|
199
|
+
* for an absent alt name would offer a token the lookup provably cannot resolve
|
|
200
|
+
* — a completion that hands the user a value and then refuses it — which is the
|
|
201
|
+
* complete-but-cannot-resolve split this whole declaration exists to close.
|
|
202
|
+
* Offering nothing for an under-enriched entity is the honest answer.
|
|
203
|
+
*
|
|
204
|
+
* @param entity - One index entity.
|
|
205
|
+
* @param field - The declared field to read.
|
|
206
|
+
* @returns The tokens that entity contributes; empty when it carries none.
|
|
207
|
+
*/
|
|
208
|
+
function readEntityNames(
|
|
209
|
+
entity: PackIndexEntity,
|
|
210
|
+
field: CompletionField,
|
|
211
|
+
): readonly string[] {
|
|
212
|
+
switch (field) {
|
|
213
|
+
case "name":
|
|
214
|
+
return [entity.name];
|
|
215
|
+
case "label":
|
|
216
|
+
return entity.label ? [entity.label] : [];
|
|
217
|
+
case "altNames":
|
|
218
|
+
return entity.altNames ?? [];
|
|
192
219
|
}
|
|
193
|
-
return [...names].sort();
|
|
194
220
|
}
|
|
195
221
|
|
|
196
|
-
/**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
*/
|
|
202
|
-
function tierNames(index: PackIndex | undefined): string[] {
|
|
222
|
+
/** A ref's candidates: entities of its type, read from its field, sorted. */
|
|
223
|
+
function indexNames(
|
|
224
|
+
index: PackIndex | undefined,
|
|
225
|
+
ref: CompletionSourceRef,
|
|
226
|
+
): string[] {
|
|
203
227
|
if (!index) return [];
|
|
204
228
|
const names = new Set<string>();
|
|
205
229
|
for (const entity of index.entities) {
|
|
206
|
-
if (!matchesType(entity,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
} else {
|
|
210
|
-
names.add(entity.label || entity.name);
|
|
230
|
+
if (!matchesType(entity, ref.type ?? "")) continue;
|
|
231
|
+
for (const name of readEntityNames(entity, ref.field ?? "name")) {
|
|
232
|
+
names.add(name);
|
|
211
233
|
}
|
|
212
234
|
}
|
|
213
235
|
return [...names].sort();
|
|
@@ -227,8 +249,8 @@ function prefixNames(index: PackIndex | undefined): string[] {
|
|
|
227
249
|
* reader every name family shares. Wired at the `__complete` fast path (bin) and
|
|
228
250
|
* the `__complete` verb; storeless throughout (see the module docblock).
|
|
229
251
|
*
|
|
230
|
-
* The pack index is loaded once, lazily, and reused across the index
|
|
231
|
-
*
|
|
252
|
+
* The pack index is loaded once, lazily, and reused across the index and
|
|
253
|
+
* prefixes sources; the skills filesystem walk is memoized to one walk per
|
|
232
254
|
* process (the design's perf caveat). Every source returns the FULL candidate
|
|
233
255
|
* list in canonical casing — the resolver ranks/filters against the partial.
|
|
234
256
|
*
|
|
@@ -254,13 +276,9 @@ export function indexCompletionEnv(cwd: string): CompletionEnv {
|
|
|
254
276
|
names(ref: CompletionSourceRef): readonly string[] {
|
|
255
277
|
switch (ref.from) {
|
|
256
278
|
case "index":
|
|
257
|
-
return indexNames(getIndex(), ref
|
|
279
|
+
return indexNames(getIndex(), ref);
|
|
258
280
|
case "skills":
|
|
259
281
|
return getSkills();
|
|
260
|
-
case "prompts":
|
|
261
|
-
return promptNames(getIndex());
|
|
262
|
-
case "tiers":
|
|
263
|
-
return tierNames(getIndex());
|
|
264
282
|
case "prefixes":
|
|
265
283
|
return prefixNames(getIndex());
|
|
266
284
|
}
|
|
@@ -236,7 +236,7 @@ describe("buildCompletionModel — flags and sources", () => {
|
|
|
236
236
|
positional: true,
|
|
237
237
|
complete: {
|
|
238
238
|
kind: "names",
|
|
239
|
-
source: { from: "
|
|
239
|
+
source: { from: "index", type: "ex:Tier", field: "altNames" },
|
|
240
240
|
enabled: false,
|
|
241
241
|
},
|
|
242
242
|
},
|
|
@@ -264,13 +264,13 @@ describe("buildCompletionModel — flags and sources", () => {
|
|
|
264
264
|
const flags = Object.fromEntries(model.globalFlags.map((f) => [f.flag, f]));
|
|
265
265
|
expect(flags["--format"]?.source).toEqual({
|
|
266
266
|
kind: "values",
|
|
267
|
-
values: ["plain", "json"],
|
|
267
|
+
values: ["plain", "llm", "json"],
|
|
268
268
|
});
|
|
269
269
|
expect(flags["--detail"]?.source).toEqual({
|
|
270
270
|
kind: "values",
|
|
271
271
|
values: ["summary", "standard", "detailed"],
|
|
272
272
|
});
|
|
273
|
-
expect(flags["--llm"]
|
|
273
|
+
expect(flags["--llm"]).toBeUndefined();
|
|
274
274
|
expect(flags["--version"]?.rootOnly).toBe(true);
|
|
275
275
|
expect(flags["--help"]?.rootOnly).toBeUndefined();
|
|
276
276
|
});
|
|
@@ -40,6 +40,17 @@ import type {
|
|
|
40
40
|
* The allowlist every inlined completion token must match: an alphanumeric
|
|
41
41
|
* head, then word/URI-ish characters. No whitespace, quotes, `$`, backticks,
|
|
42
42
|
* semicolons, or globs — nothing a shell could expand or split.
|
|
43
|
+
*
|
|
44
|
+
* KNOWN GAP, deliberately not closed: it admits shell RESERVED WORDS — `esac`,
|
|
45
|
+
* `in`, `do`, `done`, `fi`. A noun so named emits a `case` arm bash and zsh both
|
|
46
|
+
* refuse to parse, so `_pragma` is never defined and the user gets NO completion
|
|
47
|
+
* at all rather than a wrong candidate (`fish -n` does not catch it either). The
|
|
48
|
+
* gap is owned by the syntax gates, not by this regex: `shellDrive.test.ts`
|
|
49
|
+
* runs `bash -n`/`zsh -n` over the emitted scripts, so the day a live noun is
|
|
50
|
+
* named `done` the build fails loudly and names the file. A denylist here would
|
|
51
|
+
* be new production code guarding a case nothing in the tree exhibits — and a
|
|
52
|
+
* partial one (reserved words differ across families and versions) reads as
|
|
53
|
+
* more protection than it gives.
|
|
43
54
|
*/
|
|
44
55
|
export const SAFE_TOKEN_RE = /^[A-Za-z0-9][A-Za-z0-9@/:._+-]*$/;
|
|
45
56
|
|
|
@@ -61,7 +72,6 @@ export function assertSafeToken(token: string, where: string): void {
|
|
|
61
72
|
|
|
62
73
|
/** The global flags completion offers, mirroring the surface's globalFlags. */
|
|
63
74
|
const GLOBAL_FLAGS: readonly FlagEntry[] = [
|
|
64
|
-
{ flag: "--llm", takesValue: false, repeatable: false, source: none() },
|
|
65
75
|
{
|
|
66
76
|
flag: "--format",
|
|
67
77
|
takesValue: true,
|
|
@@ -73,7 +73,7 @@ describe("parseWords — nouns and verbs (PROTECTED)", () => {
|
|
|
73
73
|
context: { kind: "noun" },
|
|
74
74
|
partial: "blo",
|
|
75
75
|
});
|
|
76
|
-
expect(parse(["--
|
|
76
|
+
expect(parse(["--verbose", "block", ""]).context).toEqual({
|
|
77
77
|
kind: "verb",
|
|
78
78
|
noun: "block",
|
|
79
79
|
});
|
|
@@ -83,7 +83,6 @@ describe("parseWords — nouns and verbs (PROTECTED)", () => {
|
|
|
83
83
|
describe("parseWords — flag names (PROTECTED)", () => {
|
|
84
84
|
it("offers only globals (with --version) at the root", () => {
|
|
85
85
|
expect(flagsOf(parse(["--"]).context)).toEqual([
|
|
86
|
-
"--llm",
|
|
87
86
|
"--format",
|
|
88
87
|
"--verbose",
|
|
89
88
|
"--detail",
|
|
@@ -105,7 +104,6 @@ describe("parseWords — flag names (PROTECTED)", () => {
|
|
|
105
104
|
"--out",
|
|
106
105
|
"--with-meta",
|
|
107
106
|
"--note",
|
|
108
|
-
"--llm",
|
|
109
107
|
"--format",
|
|
110
108
|
"--verbose",
|
|
111
109
|
"--detail",
|
|
@@ -192,7 +190,7 @@ describe("parseWords — flag values (PROTECTED)", () => {
|
|
|
192
190
|
context: {
|
|
193
191
|
kind: "flag-value",
|
|
194
192
|
flag: "--format",
|
|
195
|
-
source: { kind: "values", values: ["plain", "json"] },
|
|
193
|
+
source: { kind: "values", values: ["plain", "llm", "json"] },
|
|
196
194
|
},
|
|
197
195
|
});
|
|
198
196
|
expect(parse(["block", "get", "--detail", "s"])).toMatchObject({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdtempSync, readdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createStore } from "@canonical/ke";
|
|
7
7
|
import { describe, expect, it, vi } from "vitest";
|
|
@@ -207,6 +207,8 @@ function staticImportGraph(
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
210
|
+
/** The package root, so a failure names `src/…` instead of an absolute path. */
|
|
211
|
+
const pkgRoot = resolve(here, "../../..");
|
|
210
212
|
const has = (graph: Set<string>, fragment: string): boolean =>
|
|
211
213
|
[...graph].some((file) => file.includes(fragment));
|
|
212
214
|
|
|
@@ -224,16 +226,25 @@ describe("storeless guarantee (PROTECTED)", () => {
|
|
|
224
226
|
// Runtime boot (bootRuntime) — never on the completion path.
|
|
225
227
|
expect(has(graph, "kernel/runtime/boot.ts")).toBe(false);
|
|
226
228
|
// The config LAYER (reader, evaluator, paths, schema) — storeless means
|
|
227
|
-
// no read.
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
)
|
|
236
|
-
|
|
229
|
+
// no read. Its tolerated seams are POSITIVE-LISTED, not filtered out by a
|
|
230
|
+
// path fragment: the distribution config sits at the package root, so a
|
|
231
|
+
// `kernel/config/`-shaped filter would not see it and this guard would
|
|
232
|
+
// keep passing after it stopped being true.
|
|
233
|
+
// - `kernel/config/types.ts` — the ConfigLayers type, imported type-only
|
|
234
|
+
// via VerbSpec.run's PragmaRuntime and erased at runtime, as
|
|
235
|
+
// spec/types.ts and runtime/types.ts already are in this graph.
|
|
236
|
+
// - `pragma.conf.ts` — the distribution config, statically imported by
|
|
237
|
+
// `constants.ts` (identity), `render/prefixes.ts` (the domain
|
|
238
|
+
// namespaces) and `kernel/vocabulary.ts` (the domain terms). Safe only
|
|
239
|
+
// while it stays inert data; `capabilities/lazy.test.ts` pins that.
|
|
240
|
+
const configModules = [...graph]
|
|
241
|
+
.filter((file) => /config|\.conf\./.test(file))
|
|
242
|
+
.map((file) => relative(pkgRoot, file))
|
|
243
|
+
.sort();
|
|
244
|
+
expect(configModules).toEqual([
|
|
245
|
+
"pragma.conf.ts",
|
|
246
|
+
"src/kernel/config/types.ts",
|
|
247
|
+
]);
|
|
237
248
|
// zod stays off the fast path (validate.ts is the registration seam).
|
|
238
249
|
expect(has(graph, "kernel/spec/validate.ts")).toBe(false);
|
|
239
250
|
// No store/graph modules, present or future.
|
|
@@ -266,7 +277,7 @@ describe("storeless guarantee (PROTECTED)", () => {
|
|
|
266
277
|
required: true,
|
|
267
278
|
complete: {
|
|
268
279
|
kind: "names",
|
|
269
|
-
source: { from: "index", type: "
|
|
280
|
+
source: { from: "index", type: "ds:Component" },
|
|
270
281
|
},
|
|
271
282
|
},
|
|
272
283
|
],
|
|
@@ -286,23 +297,23 @@ describe("storeless guarantee (PROTECTED)", () => {
|
|
|
286
297
|
} as VerbSpec,
|
|
287
298
|
],
|
|
288
299
|
};
|
|
289
|
-
// Fresh cwd → no
|
|
300
|
+
// Fresh cwd → no pointer → the reader falls back to the embedded index.
|
|
290
301
|
const cwd = mkdtempSync(join(tmpdir(), "pragma-storespy-"));
|
|
291
302
|
const matches = await runComplete(
|
|
292
|
-
["block", "lookup", "
|
|
303
|
+
["block", "lookup", "ds:global.component.but"],
|
|
293
304
|
[lookupModule],
|
|
294
305
|
indexCompletionEnv(cwd),
|
|
295
306
|
);
|
|
296
307
|
// It DID read the index (a real name came back)…
|
|
297
|
-
expect(matches).
|
|
308
|
+
expect(matches).toContain("ds:global.component.button");
|
|
298
309
|
// …and it did so without ever constructing the store.
|
|
299
310
|
expect(vi.mocked(createStore)).not.toHaveBeenCalled();
|
|
300
311
|
});
|
|
301
312
|
|
|
302
|
-
it("
|
|
303
|
-
// skills (filesystem),
|
|
304
|
-
//
|
|
305
|
-
// capabilities. A fresh cwd → no
|
|
313
|
+
it("every declared name source resolves without constructing the store", async () => {
|
|
314
|
+
// skills (filesystem), prefixes (index ∪ default map) and the index's own
|
|
315
|
+
// `label`/`altNames` fields — every family completes storelessly against
|
|
316
|
+
// the live capabilities. A fresh cwd → no pointer → the embedded index.
|
|
306
317
|
const cwd = mkdtempSync(join(tmpdir(), "pragma-storeless-src-"));
|
|
307
318
|
const env = indexCompletionEnv(cwd);
|
|
308
319
|
|
|
@@ -311,16 +322,23 @@ describe("storeless guarantee (PROTECTED)", () => {
|
|
|
311
322
|
runComplete(["ontology", "show", "d"], capabilities, env),
|
|
312
323
|
).resolves.toContain("ds");
|
|
313
324
|
|
|
314
|
-
//
|
|
315
|
-
//
|
|
325
|
+
// index (derived, no field): the tier lookup is a declared pack lookup
|
|
326
|
+
// (L-OPEN-9), so its candidates are the index's entity NAMES for the
|
|
327
|
+
// declared type — prefixed IRIs, like every other declared lookup's
|
|
328
|
+
// (D9-A). `ds:apps` is a real `ds:Tier` subject in the embedded index,
|
|
329
|
+
// and the lookup resolves a prefixed-IRI candidate directly.
|
|
330
|
+
await expect(
|
|
331
|
+
runComplete(["tier", "lookup", "ap"], capabilities, env),
|
|
332
|
+
).resolves.toContain("ds:apps");
|
|
333
|
+
|
|
334
|
+
// skills (no skills root here) and prompt labels (this graph carries no
|
|
335
|
+
// prompt entities) have nothing to offer, and the honest storeless answer
|
|
336
|
+
// is an empty list rather than a store boot.
|
|
316
337
|
for (const words of [
|
|
317
338
|
["skill", "lookup", "do"],
|
|
318
339
|
["prompt", "lookup", "bu"],
|
|
319
|
-
["tier", "lookup", "ap"],
|
|
320
340
|
]) {
|
|
321
|
-
await expect(
|
|
322
|
-
runComplete(words, capabilities, env),
|
|
323
|
-
).resolves.toBeInstanceOf(Array);
|
|
341
|
+
await expect(runComplete(words, capabilities, env)).resolves.toEqual([]);
|
|
324
342
|
}
|
|
325
343
|
|
|
326
344
|
expect(vi.mocked(createStore)).not.toHaveBeenCalled();
|
|
@@ -328,31 +346,59 @@ describe("storeless guarantee (PROTECTED)", () => {
|
|
|
328
346
|
|
|
329
347
|
it("the spawned __complete fast path answers without touching any state", () => {
|
|
330
348
|
// The perf globalSetup guarantees dist/pragma exists.
|
|
349
|
+
// A FRESH INSTALL, reproduced: nothing is inherited from this process's
|
|
350
|
+
// environment, the cwd holds no config, and $HOME plus all three XDG roots
|
|
351
|
+
// are empty temps. Anything the binary answers here it answered from
|
|
352
|
+
// itself. (The suite before this inherited `...process.env`, so it could
|
|
353
|
+
// not tell a compiled-in answer from an ambient one.)
|
|
331
354
|
const binary = fileURLToPath(
|
|
332
355
|
new URL("../../../dist/pragma", import.meta.url),
|
|
333
356
|
);
|
|
357
|
+
const home = mkdtempSync(join(tmpdir(), "pragma-storeless-home-"));
|
|
334
358
|
const xdgConfig = mkdtempSync(join(tmpdir(), "pragma-storeless-cfg-"));
|
|
335
359
|
const xdgState = mkdtempSync(join(tmpdir(), "pragma-storeless-state-"));
|
|
336
360
|
const xdgCache = mkdtempSync(join(tmpdir(), "pragma-storeless-cache-"));
|
|
361
|
+
const emptyCwd = mkdtempSync(join(tmpdir(), "pragma-storeless-cwd-"));
|
|
362
|
+
const env = {
|
|
363
|
+
HOME: home,
|
|
364
|
+
XDG_CONFIG_HOME: xdgConfig,
|
|
365
|
+
XDG_STATE_HOME: xdgState,
|
|
366
|
+
XDG_CACHE_HOME: xdgCache,
|
|
367
|
+
};
|
|
337
368
|
|
|
338
|
-
const
|
|
369
|
+
const nouns = spawnSync(binary, ["__complete", "--", "co"], {
|
|
339
370
|
encoding: "utf-8",
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
XDG_CONFIG_HOME: xdgConfig,
|
|
343
|
-
XDG_STATE_HOME: xdgState,
|
|
344
|
-
XDG_CACHE_HOME: xdgCache,
|
|
345
|
-
},
|
|
371
|
+
cwd: emptyCwd,
|
|
372
|
+
env,
|
|
346
373
|
});
|
|
374
|
+
expect(nouns.status).toBe(0);
|
|
375
|
+
expect(nouns.stdout).toBe("colophon\nconfig\n");
|
|
376
|
+
expect(nouns.stderr).toBe("");
|
|
377
|
+
|
|
378
|
+
// The headline guarantee of the embedded pack: a user who has installed
|
|
379
|
+
// the binary and nothing else gets ENTITY candidates on the first TAB.
|
|
380
|
+
// Every other pin on the embedded index runs in-process, where a bundler
|
|
381
|
+
// change that made `pack.index.generated.ts` unreachable FROM THE BINARY
|
|
382
|
+
// would leave the whole suite green.
|
|
383
|
+
const entities = spawnSync(
|
|
384
|
+
binary,
|
|
385
|
+
["__complete", "--", "block", "lookup", "ds:global.component.but"],
|
|
386
|
+
{ encoding: "utf-8", cwd: emptyCwd, env },
|
|
387
|
+
);
|
|
388
|
+
expect(entities.status).toBe(0);
|
|
389
|
+
expect(entities.stderr).toBe("");
|
|
390
|
+
expect(entities.stdout.split("\n").at(0)).toBe(
|
|
391
|
+
"ds:global.component.button",
|
|
392
|
+
);
|
|
347
393
|
|
|
348
|
-
expect(result.status).toBe(0);
|
|
349
|
-
expect(result.stdout).toBe("colophon\nconfig\n");
|
|
350
|
-
expect(result.stderr).toBe("");
|
|
351
394
|
// Storeless and config-free: no first-run marker, no config cache, no
|
|
352
|
-
// store artifacts — the fast path must leave the environment untouched
|
|
395
|
+
// store artifacts — the fast path must leave the environment untouched,
|
|
396
|
+
// after BOTH kinds of context.
|
|
397
|
+
expect(readdirSync(home)).toEqual([]);
|
|
353
398
|
expect(readdirSync(xdgConfig)).toEqual([]);
|
|
354
399
|
expect(readdirSync(xdgState)).toEqual([]);
|
|
355
400
|
expect(readdirSync(xdgCache)).toEqual([]);
|
|
401
|
+
expect(readdirSync(emptyCwd)).toEqual([]);
|
|
356
402
|
});
|
|
357
403
|
|
|
358
404
|
it("the spawned fast path emits zero bytes for zero candidates, exit 0", () => {
|