@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
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parse a config `
|
|
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
|
|
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 {
|
|
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 `
|
|
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
|
|
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
|
-
'
|
|
46
|
+
'Pack declaration must have a non-empty "name".',
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
if (source === undefined) {
|
|
@@ -2,27 +2,26 @@
|
|
|
2
2
|
* Resolve a {@link PackageRef} to its pinned revision and RDF source contents.
|
|
3
3
|
*
|
|
4
4
|
* `file` reads the local path in place; `npm` resolves the installed package
|
|
5
|
-
* from the project's `node_modules`; `git` clones/fetches into the ref cache
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* from the project's `node_modules`; `git` clones/fetches into the ref cache.
|
|
6
|
+
* A ref that is already a commit SHA resolves to exactly that commit, which is
|
|
7
|
+
* how a project pins a revision. Each package contributes its `definitions/**`
|
|
8
|
+
* and `data/**` TTL files plus any `stories/*.json` it ships, labelled by a
|
|
9
|
+
* stable `pkg/relative-path` so the pack's content hash is machine-independent.
|
|
10
|
+
* Reached only from the `sources update` Task body.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
13
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
13
14
|
import { createRequire } from "node:module";
|
|
14
15
|
import { dirname, join, relative } from "node:path";
|
|
15
|
-
import { RECOVERY_CLI_PREFIX } from "../../../constants.js";
|
|
16
16
|
import { PragmaError } from "../../error/PragmaError.js";
|
|
17
|
-
import { cliRecovery } from "../../error/recovery.js";
|
|
18
17
|
import { refsCacheDir } from "../paths.js";
|
|
19
|
-
import {
|
|
18
|
+
import { cloneRef, fetchRef, headCommit } from "./gitOps.js";
|
|
20
19
|
import { type PackageRef, redactUrl } from "./parseRef.js";
|
|
21
20
|
|
|
22
21
|
/** A resolved package: its pinned revision and labelled RDF sources. */
|
|
23
22
|
export interface ResolvedPackage {
|
|
24
23
|
readonly name: string;
|
|
25
|
-
/** The config `
|
|
24
|
+
/** The config `packs` source ref, verbatim. */
|
|
26
25
|
readonly source: string;
|
|
27
26
|
/** The resolved commit / version / absolute path. */
|
|
28
27
|
readonly resolved: string;
|
|
@@ -34,19 +33,26 @@ export interface ResolvedPackage {
|
|
|
34
33
|
readonly root: string;
|
|
35
34
|
/** The labelled RDF sources (path label + content). */
|
|
36
35
|
readonly sources: { readonly path: string; readonly content: string }[];
|
|
36
|
+
/**
|
|
37
|
+
* The package's `stories/*.json` files, as RAW TEXT — never parsed here. The
|
|
38
|
+
* pack carries the package's bytes verbatim so every interpretation failure
|
|
39
|
+
* (malformed JSON and schema-invalid JSON alike) is caught behind one guard
|
|
40
|
+
* at dispatch instead of two half-guards at two layers.
|
|
41
|
+
*/
|
|
42
|
+
readonly stories: { readonly path: string; readonly content: string }[];
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
/** Options for a resolution
|
|
45
|
+
/** Options for a resolution — the project directory an `npm` ref resolves from. */
|
|
40
46
|
export interface ResolveOptions {
|
|
41
47
|
readonly cwd: string;
|
|
42
|
-
readonly frozen: boolean;
|
|
43
|
-
/** The lock's pinned revision for this package (used under `--frozen`). */
|
|
44
|
-
readonly pinned?: string;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
/** The package subdirectories scanned for `.ttl` sources. */
|
|
48
51
|
const TTL_DIRS = ["definitions", "data"];
|
|
49
52
|
|
|
53
|
+
/** The package subdirectory whose immediate `*.json` children are read stories. */
|
|
54
|
+
const STORIES_DIR = "stories";
|
|
55
|
+
|
|
50
56
|
/** Sanitize a ref for use as a cache path segment. */
|
|
51
57
|
const sanitize = (value: string): string => value.replace(/[/\\:*?"<>|]/g, "_");
|
|
52
58
|
|
|
@@ -84,9 +90,28 @@ function walkTtl(
|
|
|
84
90
|
// build (a `.`-prefixed Turtle local name isn't even valid).
|
|
85
91
|
if (entry.name.startsWith(".")) continue;
|
|
86
92
|
const full = join(dir, entry.name);
|
|
87
|
-
|
|
93
|
+
// Resolve symlinks: pnpm / workspace trees link their sources, but
|
|
94
|
+
// `Dirent.isFile`/`isDirectory` are BOTH false for a symlink — so a
|
|
95
|
+
// symlinked `.ttl` was silently skipped (L6). Follow the link to recover a
|
|
96
|
+
// symlinked FILE, but NEVER recurse into a symlinked DIRECTORY: a symlink
|
|
97
|
+
// cycle (a link to an ancestor dir) would recurse without bound — a
|
|
98
|
+
// stack-overflow RangeError surfacing as an INTERNAL "please report" — and a
|
|
99
|
+
// linked directory could pull `.ttl` from OUTSIDE the package root. Linking
|
|
100
|
+
// individual source files (all L6 needs) stays supported; a dangling link is
|
|
101
|
+
// skipped, not fatal.
|
|
102
|
+
let isDir = entry.isDirectory();
|
|
103
|
+
let isFile = entry.isFile();
|
|
104
|
+
if (entry.isSymbolicLink()) {
|
|
105
|
+
try {
|
|
106
|
+
isFile = statSync(full).isFile();
|
|
107
|
+
isDir = false;
|
|
108
|
+
} catch {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (isDir) {
|
|
88
113
|
walkTtl(full, base, label, out);
|
|
89
|
-
} else if (
|
|
114
|
+
} else if (isFile && entry.name.endsWith(".ttl")) {
|
|
90
115
|
out.push({
|
|
91
116
|
path: `${label}/${relative(base, full)}`,
|
|
92
117
|
content: readFileSync(full, "utf-8"),
|
|
@@ -108,6 +133,47 @@ function readTtlSources(
|
|
|
108
133
|
return sources;
|
|
109
134
|
}
|
|
110
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Read a package's `stories/*.json` as raw text, with stable labels.
|
|
138
|
+
*
|
|
139
|
+
* IMMEDIATE children only — the same discipline `skills/` uses, and recursion
|
|
140
|
+
* buys nothing. Hidden entries are skipped and a symlinked FILE is followed but
|
|
141
|
+
* a symlinked DIRECTORY is never recursed into, exactly as {@link walkTtl}
|
|
142
|
+
* argues. Sorted so the pack's content hash does not depend on readdir order.
|
|
143
|
+
*
|
|
144
|
+
* @param rootDir - The package root.
|
|
145
|
+
* @param label - The package name, used as the label prefix.
|
|
146
|
+
* @returns The labelled story files (`pkg/stories/name.json` + raw content).
|
|
147
|
+
* @note Impure — reads from disk.
|
|
148
|
+
*/
|
|
149
|
+
function readStorySources(
|
|
150
|
+
rootDir: string,
|
|
151
|
+
label: string,
|
|
152
|
+
): { path: string; content: string }[] {
|
|
153
|
+
const dir = join(rootDir, STORIES_DIR);
|
|
154
|
+
if (!existsSync(dir)) return [];
|
|
155
|
+
const stories: { path: string; content: string }[] = [];
|
|
156
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
157
|
+
if (entry.name.startsWith(".") || !entry.name.endsWith(".json")) continue;
|
|
158
|
+
const full = join(dir, entry.name);
|
|
159
|
+
let isFile = entry.isFile();
|
|
160
|
+
if (entry.isSymbolicLink()) {
|
|
161
|
+
try {
|
|
162
|
+
isFile = statSync(full).isFile();
|
|
163
|
+
} catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (!isFile) continue;
|
|
168
|
+
stories.push({
|
|
169
|
+
path: `${label}/${STORIES_DIR}/${entry.name}`,
|
|
170
|
+
content: readFileSync(full, "utf-8"),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
stories.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
174
|
+
return stories;
|
|
175
|
+
}
|
|
176
|
+
|
|
111
177
|
/**
|
|
112
178
|
* A Turtle/SPARQL prefix prologue declaration: `@prefix ex: <iri> .` or the
|
|
113
179
|
* keyword-form `PREFIX ex: <iri>`. Both are legal in Turtle 1.1; the keyword is
|
|
@@ -143,6 +209,47 @@ export function harvestPrefixes(
|
|
|
143
209
|
return prefixes;
|
|
144
210
|
}
|
|
145
211
|
|
|
212
|
+
/** One `@prefix` clash: a label declared with two or more distinct IRIs. */
|
|
213
|
+
export interface PrefixClash {
|
|
214
|
+
readonly label: string;
|
|
215
|
+
/** The distinct IRIs bound to the label, in first-seen order. */
|
|
216
|
+
readonly iris: readonly string[];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Detect `@prefix` label clashes across a set of sources.
|
|
221
|
+
*
|
|
222
|
+
* {@link harvestPrefixes} resolves clashes last-wins (Turtle's own rule),
|
|
223
|
+
* silently compacting the losing package's URIs to the wrong prefix (or to full
|
|
224
|
+
* IRIs). This surfaces the clashes so `sources update` can warn (A5): a label
|
|
225
|
+
* bound to two or more distinct IRIs. Harmless same-label/same-IRI
|
|
226
|
+
* redeclarations (the common case) are NOT clashes. Result is sorted by label
|
|
227
|
+
* so the warnings are deterministic.
|
|
228
|
+
*
|
|
229
|
+
* @param sources - The packages' labelled RDF sources.
|
|
230
|
+
* @returns One entry per clashing label, each with its distinct IRIs.
|
|
231
|
+
*/
|
|
232
|
+
export function detectPrefixClashes(
|
|
233
|
+
sources: readonly { readonly content: string }[],
|
|
234
|
+
): PrefixClash[] {
|
|
235
|
+
const byLabel = new Map<string, string[]>();
|
|
236
|
+
for (const { content } of sources) {
|
|
237
|
+
for (const match of content.matchAll(PREFIX_DECL)) {
|
|
238
|
+
const [, label, iri] = match;
|
|
239
|
+
if (label === undefined || iri === undefined) continue;
|
|
240
|
+
const iris = byLabel.get(label) ?? [];
|
|
241
|
+
if (!iris.includes(iri)) iris.push(iri);
|
|
242
|
+
byLabel.set(label, iris);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const clashes: PrefixClash[] = [];
|
|
246
|
+
for (const [label, iris] of byLabel) {
|
|
247
|
+
if (iris.length > 1) clashes.push({ label, iris });
|
|
248
|
+
}
|
|
249
|
+
clashes.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
|
|
250
|
+
return clashes;
|
|
251
|
+
}
|
|
252
|
+
|
|
146
253
|
/**
|
|
147
254
|
* Locate a package's `package.json` from the project.
|
|
148
255
|
*
|
|
@@ -194,31 +301,15 @@ export function resolvePackageJson(
|
|
|
194
301
|
* Resolve a package reference to its revision and RDF sources.
|
|
195
302
|
*
|
|
196
303
|
* @param ref - The parsed package reference.
|
|
197
|
-
* @param options - The
|
|
304
|
+
* @param options - The project directory an `npm` ref resolves from.
|
|
198
305
|
* @returns The resolved package.
|
|
199
306
|
* @throws PragmaError on a missing file path or unresolvable npm package.
|
|
200
|
-
* @throws PragmaError under `--frozen` when the package has no lock entry.
|
|
201
307
|
* @note Impure — may clone/fetch git, reads TTL from disk.
|
|
202
308
|
*/
|
|
203
309
|
export async function resolvePackage(
|
|
204
310
|
ref: PackageRef,
|
|
205
311
|
options: ResolveOptions,
|
|
206
312
|
): Promise<ResolvedPackage> {
|
|
207
|
-
// `--frozen` means "reproduce the locked state exactly". A configured package
|
|
208
|
-
// with no lock entry has nothing to reproduce, so refuse rather than silently
|
|
209
|
-
// resolving it fresh (which would advance the very state the lock pins).
|
|
210
|
-
if (options.frozen && options.pinned === undefined) {
|
|
211
|
-
throw PragmaError.configError(
|
|
212
|
-
`Cannot resolve "${ref.pkg}" under --frozen: it has no entry in the lock.`,
|
|
213
|
-
{
|
|
214
|
-
recovery: cliRecovery(
|
|
215
|
-
`${RECOVERY_CLI_PREFIX}sources update`,
|
|
216
|
-
"Update the lock without --frozen, then commit it.",
|
|
217
|
-
),
|
|
218
|
-
},
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
313
|
switch (ref.kind) {
|
|
223
314
|
case "file": {
|
|
224
315
|
if (!existsSync(ref.path)) {
|
|
@@ -232,6 +323,7 @@ export async function resolvePackage(
|
|
|
232
323
|
resolved: ref.path,
|
|
233
324
|
root: ref.path,
|
|
234
325
|
sources: readTtlSources(ref.path, ref.pkg),
|
|
326
|
+
stories: readStorySources(ref.path, ref.pkg),
|
|
235
327
|
};
|
|
236
328
|
}
|
|
237
329
|
|
|
@@ -262,22 +354,15 @@ export async function resolvePackage(
|
|
|
262
354
|
resolved: version,
|
|
263
355
|
root: dir,
|
|
264
356
|
sources: readTtlSources(dir, ref.pkg),
|
|
357
|
+
stories: readStorySources(dir, ref.pkg),
|
|
265
358
|
};
|
|
266
359
|
}
|
|
267
360
|
|
|
268
361
|
case "git": {
|
|
269
|
-
const
|
|
270
|
-
const dir = join(
|
|
271
|
-
refsCacheDir(),
|
|
272
|
-
sanitize(ref.pkg),
|
|
273
|
-
sanitize(useCommit ? (options.pinned as string) : ref.ref),
|
|
274
|
-
);
|
|
362
|
+
const dir = join(refsCacheDir(), sanitize(ref.pkg), sanitize(ref.ref));
|
|
275
363
|
let resolved: string;
|
|
276
364
|
try {
|
|
277
|
-
if (
|
|
278
|
-
checkoutCommit(ref.url, options.pinned as string, dir);
|
|
279
|
-
resolved = options.pinned as string;
|
|
280
|
-
} else if (existsSync(dir)) {
|
|
365
|
+
if (existsSync(dir)) {
|
|
281
366
|
resolved = fetchRef(ref.url, ref.ref, dir);
|
|
282
367
|
} else {
|
|
283
368
|
cloneRef(ref.url, ref.ref, dir);
|
|
@@ -285,28 +370,18 @@ export async function resolvePackage(
|
|
|
285
370
|
}
|
|
286
371
|
} catch (error) {
|
|
287
372
|
// A git clone/fetch/checkout failed — an unreachable remote, a moved or
|
|
288
|
-
// deleted ref, an auth/credential problem, or
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
throw
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
)
|
|
301
|
-
: PragmaError.configError(
|
|
302
|
-
`Cannot resolve "${ref.pkg}" from ${redactUrl(ref.url)}#${ref.ref}: ${errorDetail(error)}`,
|
|
303
|
-
{
|
|
304
|
-
recovery: {
|
|
305
|
-
message:
|
|
306
|
-
"Check the git URL and ref, your network, and your git credentials, then re-run the update.",
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
);
|
|
373
|
+
// deleted ref, an auth/credential problem, or a pinned SHA that has been
|
|
374
|
+
// force-pushed away. Name the package + ref with git's own reason so it
|
|
375
|
+
// reads as a fixable data error, not INTERNAL_ERROR.
|
|
376
|
+
throw PragmaError.configError(
|
|
377
|
+
`Cannot resolve "${ref.pkg}" from ${redactUrl(ref.url)}#${ref.ref}: ${errorDetail(error)}`,
|
|
378
|
+
{
|
|
379
|
+
recovery: {
|
|
380
|
+
message:
|
|
381
|
+
"Check the git URL and ref, your network, and your git credentials, then re-run the update.",
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
);
|
|
310
385
|
}
|
|
311
386
|
return {
|
|
312
387
|
name: ref.pkg,
|
|
@@ -314,6 +389,7 @@ export async function resolvePackage(
|
|
|
314
389
|
resolved,
|
|
315
390
|
root: dir,
|
|
316
391
|
sources: readTtlSources(dir, ref.pkg),
|
|
392
|
+
stories: readStorySources(dir, ref.pkg),
|
|
317
393
|
};
|
|
318
394
|
}
|
|
319
395
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
5
|
import type { ConfigLayers } from "../config/types.js";
|
|
6
6
|
import { loadStoreSession } from "./loadSession.js";
|
|
7
|
-
import {
|
|
8
|
-
import { packDir } from "./paths.js";
|
|
7
|
+
import { activePackPath, packDir } from "./paths.js";
|
|
9
8
|
import { resolveSources } from "./resolveSources.js";
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -21,24 +20,21 @@ const tmp = (): string => {
|
|
|
21
20
|
return dir;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
|
-
/** Config layers whose only relevant knob is the `
|
|
25
|
-
function layersWith(
|
|
23
|
+
/** Config layers whose only relevant knob is the `packs` origin. */
|
|
24
|
+
function layersWith(packsOrigin: "default" | "project"): ConfigLayers {
|
|
26
25
|
return {
|
|
27
26
|
config: {
|
|
28
27
|
channel: "normal",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
? [{ name: "x", source: "file:///x" }]
|
|
32
|
-
: [],
|
|
28
|
+
packs:
|
|
29
|
+
packsOrigin === "project" ? [{ name: "x", source: "file:///x" }] : [],
|
|
33
30
|
},
|
|
34
31
|
origins: {
|
|
35
32
|
tier: "default",
|
|
36
33
|
channel: "default",
|
|
37
34
|
detail: "default",
|
|
38
|
-
|
|
35
|
+
packs: packsOrigin,
|
|
39
36
|
stories: "default",
|
|
40
37
|
prefixes: "default",
|
|
41
|
-
prompts: "default",
|
|
42
38
|
},
|
|
43
39
|
global: { path: "/nonexistent", exists: false },
|
|
44
40
|
project: { exists: false },
|
|
@@ -67,10 +63,18 @@ function writeCompletePack(hash: string): string {
|
|
|
67
63
|
writeFileSync(join(dir, "data.nq"), "<urn:s> <urn:p> <urn:o> .\n");
|
|
68
64
|
writeFileSync(join(dir, "schema.json"), "{}");
|
|
69
65
|
writeFileSync(join(dir, "index.json"), "{}");
|
|
66
|
+
writeFileSync(join(dir, "stories.json"), "[]");
|
|
70
67
|
writeManifest(dir, hash);
|
|
71
68
|
return dir;
|
|
72
69
|
}
|
|
73
70
|
|
|
71
|
+
/** Plant the active-pack pointer a `sources update` would have written. */
|
|
72
|
+
function writePointer(cwd: string, content: string): void {
|
|
73
|
+
const path = activePackPath(cwd);
|
|
74
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
75
|
+
writeFileSync(path, content);
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
beforeEach(() => {
|
|
75
79
|
roots = [];
|
|
76
80
|
});
|
|
@@ -79,11 +83,11 @@ afterEach(() => {
|
|
|
79
83
|
});
|
|
80
84
|
|
|
81
85
|
describe("resolveSources decision table", () => {
|
|
82
|
-
it("
|
|
86
|
+
it("pointer present + pack cached → load the built pack", () => {
|
|
83
87
|
const cwd = tmp();
|
|
84
88
|
const hash = "a".repeat(64);
|
|
85
89
|
const dir = writeCompletePack(hash);
|
|
86
|
-
|
|
90
|
+
writePointer(cwd, hash);
|
|
87
91
|
|
|
88
92
|
expect(resolveSources(layersWith("default"), cwd)).toEqual({
|
|
89
93
|
kind: "pack",
|
|
@@ -92,46 +96,81 @@ describe("resolveSources decision table", () => {
|
|
|
92
96
|
});
|
|
93
97
|
});
|
|
94
98
|
|
|
95
|
-
it("
|
|
99
|
+
it("pointer present + pack evicted → STORE_UNAVAILABLE", () => {
|
|
96
100
|
const cwd = tmp();
|
|
97
|
-
// A
|
|
98
|
-
|
|
101
|
+
// A pointer whose content-addressed pack is absent from the cache.
|
|
102
|
+
writePointer(cwd, "b".repeat(64));
|
|
99
103
|
|
|
100
104
|
expect(resolveSources(layersWith("default"), cwd)).toEqual({
|
|
101
105
|
kind: "unavailable",
|
|
102
|
-
reason: "the
|
|
106
|
+
reason: "the built pack is missing from the cache",
|
|
103
107
|
});
|
|
104
108
|
});
|
|
105
109
|
|
|
106
|
-
it("
|
|
110
|
+
it("pointer present + pack with a torn schema/index → STORE_UNAVAILABLE", () => {
|
|
107
111
|
const cwd = tmp();
|
|
108
112
|
const hash = "c".repeat(64);
|
|
109
113
|
// manifest + non-empty dump present, but the extracted schema/index are
|
|
110
114
|
// missing (a torn or partially-evicted pack). This used to slip through
|
|
111
115
|
// `packIsComplete` and then crash at read time as an INTERNAL error; it must
|
|
112
|
-
// now be treated as not-built so the boot surfaces STORE_UNAVAILABLE
|
|
116
|
+
// now be treated as not-built so the boot surfaces STORE_UNAVAILABLE — and
|
|
117
|
+
// say INCOMPLETE, not "missing", because the directory is right there. This
|
|
118
|
+
// is also the shape every pack built before `stories.json` now takes.
|
|
113
119
|
const dir = packDir(hash);
|
|
114
120
|
mkdirSync(dir, { recursive: true });
|
|
115
121
|
writeFileSync(join(dir, "data.nq"), "<urn:s> <urn:p> <urn:o> .\n");
|
|
116
122
|
writeManifest(dir, hash);
|
|
117
|
-
|
|
123
|
+
writePointer(cwd, hash);
|
|
124
|
+
|
|
125
|
+
expect(resolveSources(layersWith("default"), cwd)).toEqual({
|
|
126
|
+
kind: "unavailable",
|
|
127
|
+
reason: "the built pack is incomplete — an older or torn build",
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("pointer present + a pack whose stories.json is not an array → STORE_UNAVAILABLE", () => {
|
|
132
|
+
const cwd = tmp();
|
|
133
|
+
const hash = "d".repeat(64);
|
|
134
|
+
const dir = writeCompletePack(hash);
|
|
135
|
+
// Every file present and non-empty, but `stories.json` holds an object.
|
|
136
|
+
// Gated on size alone this passed, so `buildPack` REUSED the directory and
|
|
137
|
+
// every package-declared noun disappeared while `sources update` reported
|
|
138
|
+
// success. It is a torn build like any other, and says so.
|
|
139
|
+
writeFileSync(join(dir, "stories.json"), '{"noun":"recipe"}');
|
|
140
|
+
writePointer(cwd, hash);
|
|
118
141
|
|
|
119
142
|
expect(resolveSources(layersWith("default"), cwd)).toEqual({
|
|
120
143
|
kind: "unavailable",
|
|
121
|
-
reason: "the
|
|
144
|
+
reason: "the built pack is incomplete — an older or torn build",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("a malformed pointer is treated as absent, not as a pack name", () => {
|
|
149
|
+
// A truncated/garbage pointer must never name a cache directory. With
|
|
150
|
+
// default packs it falls through to the embedded row, exactly as no
|
|
151
|
+
// pointer at all would.
|
|
152
|
+
const cwd = tmp();
|
|
153
|
+
writePointer(cwd, "not-a-content-hash\n");
|
|
154
|
+
|
|
155
|
+
expect(resolveSources(layersWith("default"), cwd)).toEqual({
|
|
156
|
+
kind: "embedded",
|
|
157
|
+
});
|
|
158
|
+
expect(resolveSources(layersWith("project"), cwd)).toEqual({
|
|
159
|
+
kind: "unavailable",
|
|
160
|
+
reason: "packs are configured but the store has not been built",
|
|
122
161
|
});
|
|
123
162
|
});
|
|
124
163
|
|
|
125
|
-
it("no
|
|
164
|
+
it("no pointer + default packs → embedded fallback", () => {
|
|
126
165
|
expect(resolveSources(layersWith("default"), tmp())).toEqual({
|
|
127
166
|
kind: "embedded",
|
|
128
167
|
});
|
|
129
168
|
});
|
|
130
169
|
|
|
131
|
-
it("no
|
|
170
|
+
it("no pointer + packs configured → STORE_UNAVAILABLE", () => {
|
|
132
171
|
expect(resolveSources(layersWith("project"), tmp())).toEqual({
|
|
133
172
|
kind: "unavailable",
|
|
134
|
-
reason: "
|
|
173
|
+
reason: "packs are configured but the store has not been built",
|
|
135
174
|
});
|
|
136
175
|
});
|
|
137
176
|
});
|
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The store-boot decision table — storeless, and the single place the boot
|
|
3
|
-
* strategy is decided.
|
|
3
|
+
* strategy is decided. No other module re-derives it: `sources status`, `info`,
|
|
4
|
+
* `doctor`, and the native MCP prompt/resource surfaces all switch on what this
|
|
5
|
+
* returns (`readPackIndex` takes the decision rather than resolving a pack of
|
|
6
|
+
* its own). The one module that cannot is the shell-completion fast path, which
|
|
7
|
+
* is denied the config evaluator and so implements the pointer half only — it
|
|
8
|
+
* says so, and it answers with completion candidates, never content.
|
|
4
9
|
*
|
|
5
|
-
* Boot never touches the network. From the
|
|
10
|
+
* Boot never touches the network. From the project's active-pack pointer (the
|
|
11
|
+
* content hash `sources update` last built here) and the resolved config:
|
|
6
12
|
*
|
|
7
|
-
* |
|
|
8
|
-
*
|
|
9
|
-
* | yes
|
|
10
|
-
* | yes
|
|
11
|
-
* | no
|
|
12
|
-
* | no
|
|
13
|
+
* | pointer | pack complete | packs origin | → decision |
|
|
14
|
+
* |---------|---------------|--------------|-----------------------|
|
|
15
|
+
* | yes | yes | — | load the built pack |
|
|
16
|
+
* | yes | no | — | STORE_UNAVAILABLE |
|
|
17
|
+
* | no | — | default | embedded fallback |
|
|
18
|
+
* | no | — | configured | STORE_UNAVAILABLE |
|
|
13
19
|
*
|
|
14
|
-
* A configured-but-unbuilt store (or a
|
|
20
|
+
* A configured-but-unbuilt store (or a pointer whose pack the cache lost)
|
|
15
21
|
* surfaces STORE_UNAVAILABLE with a single recovery: `pragma sources update`.
|
|
16
|
-
* "
|
|
17
|
-
* a fresh install — so the embedded
|
|
22
|
+
* "packs origin default" means the user has not pinned their own packs —
|
|
23
|
+
* a fresh install — so the embedded pack answers reads offline. A project that
|
|
24
|
+
* DID configure its own packs is never quietly served the distribution's graph.
|
|
18
25
|
*/
|
|
19
26
|
|
|
27
|
+
import { existsSync } from "node:fs";
|
|
20
28
|
import type { ConfigLayers } from "../config/types.js";
|
|
21
29
|
import { packIsComplete } from "./graphpack/manifest.js";
|
|
22
|
-
import {
|
|
23
|
-
import { packDir } from "./paths.js";
|
|
30
|
+
import { packDir, readActivePack } from "./paths.js";
|
|
24
31
|
|
|
25
32
|
/** The resolved boot strategy. */
|
|
26
33
|
export type SourcesDecision =
|
|
@@ -35,32 +42,36 @@ export type SourcesDecision =
|
|
|
35
42
|
/**
|
|
36
43
|
* Decide how (or whether) to boot the store — without any network or store I/O.
|
|
37
44
|
*
|
|
38
|
-
* @param layers - The resolved config layers (for the `
|
|
39
|
-
* @param cwd - The project directory (for the
|
|
45
|
+
* @param layers - The resolved config layers (for the `packs` origin).
|
|
46
|
+
* @param cwd - The project directory (for the active-pack pointer).
|
|
40
47
|
* @returns The boot decision.
|
|
41
|
-
* @note Impure — reads the
|
|
48
|
+
* @note Impure — reads the pointer and probes the pack cache.
|
|
42
49
|
*/
|
|
43
50
|
export function resolveSources(
|
|
44
51
|
layers: ConfigLayers,
|
|
45
52
|
cwd: string,
|
|
46
53
|
): SourcesDecision {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
const dir = packDir(
|
|
50
|
-
if (packIsComplete(dir)) {
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
const contentHash = readActivePack(cwd);
|
|
55
|
+
if (contentHash !== undefined) {
|
|
56
|
+
const dir = packDir(contentHash);
|
|
57
|
+
if (packIsComplete(dir)) return { kind: "pack", dir, contentHash };
|
|
58
|
+
// Two different things, and the recovery text is the only thing a user has
|
|
59
|
+
// to go on: the cache evicted the pack, or the directory is there but does
|
|
60
|
+
// not hold a whole pack — the state EVERY install upgraded past the
|
|
61
|
+
// four-file pack lands in, where "missing" is plainly contradicted by `ls`.
|
|
53
62
|
return {
|
|
54
63
|
kind: "unavailable",
|
|
55
|
-
reason:
|
|
64
|
+
reason: existsSync(dir)
|
|
65
|
+
? "the built pack is incomplete — an older or torn build"
|
|
66
|
+
: "the built pack is missing from the cache",
|
|
56
67
|
};
|
|
57
68
|
}
|
|
58
69
|
|
|
59
|
-
if (layers.origins.
|
|
70
|
+
if (layers.origins.packs === "default") {
|
|
60
71
|
return { kind: "embedded" };
|
|
61
72
|
}
|
|
62
73
|
return {
|
|
63
74
|
kind: "unavailable",
|
|
64
|
-
reason: "
|
|
75
|
+
reason: "packs are configured but the store has not been built",
|
|
65
76
|
};
|
|
66
77
|
}
|