@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
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
SectionDef,
|
|
16
16
|
} from "./contracts.js";
|
|
17
17
|
import { DEFAULT_PREFIX_MAP } from "./prefixes.js";
|
|
18
|
+
import { defaultStyle, type RenderStyle, styleFor } from "./style.js";
|
|
18
19
|
|
|
19
20
|
type RenderMode = "plain" | "llm";
|
|
20
21
|
|
|
@@ -104,27 +105,40 @@ export function renderListLlm<T>(
|
|
|
104
105
|
return lines.join("\n");
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Render an entity lookup as plain text.
|
|
110
|
+
*
|
|
111
|
+
* @param entity - The looked-up entity.
|
|
112
|
+
* @param options - The lookup layout (title, fields, sections).
|
|
113
|
+
* @param style - TTY styling; defaults to the process style. On a color-capable
|
|
114
|
+
* terminal the title is bold, its `═` rule dim, and field labels cyan; off a
|
|
115
|
+
* TTY the styler is inert, so the output is byte-identical to the plain form.
|
|
116
|
+
* @returns The formatted lookup block.
|
|
117
|
+
*/
|
|
107
118
|
export function renderLookupPlain<T>(
|
|
108
119
|
entity: T,
|
|
109
120
|
options: RenderLookupOptions<T>,
|
|
121
|
+
style: RenderStyle = defaultStyle(),
|
|
110
122
|
): string {
|
|
111
|
-
return renderLookup(entity, options, "plain");
|
|
123
|
+
return renderLookup(entity, options, "plain", style);
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
export function renderLookupLlm<T>(
|
|
115
127
|
entity: T,
|
|
116
128
|
options: RenderLookupOptions<T>,
|
|
117
129
|
): string {
|
|
118
|
-
|
|
130
|
+
// The condensed form is a byte-frozen agent contract — never styled.
|
|
131
|
+
return renderLookup(entity, options, "llm", styleFor(false));
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
function renderLookup<T>(
|
|
122
135
|
entity: T,
|
|
123
136
|
options: RenderLookupOptions<T>,
|
|
124
137
|
mode: RenderMode,
|
|
138
|
+
style: RenderStyle,
|
|
125
139
|
): string {
|
|
126
140
|
const title = options.title(entity);
|
|
127
|
-
const fields = renderLookupFields(entity, options, mode);
|
|
141
|
+
const fields = renderLookupFields(entity, options, mode, style);
|
|
128
142
|
const sections = renderLookupSections(entity, options, mode);
|
|
129
143
|
|
|
130
144
|
if (mode === "llm") {
|
|
@@ -132,13 +146,17 @@ function renderLookup<T>(
|
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
const headingRule = "═".repeat(Math.max(title.length, 24));
|
|
135
|
-
|
|
149
|
+
const head = style.enabled
|
|
150
|
+
? [style.bold(title), style.dim(headingRule)]
|
|
151
|
+
: [title, headingRule];
|
|
152
|
+
return [...head, "", ...fields, ...sections].join("\n").trimEnd();
|
|
136
153
|
}
|
|
137
154
|
|
|
138
155
|
function renderLookupFields<T>(
|
|
139
156
|
entity: T,
|
|
140
157
|
options: RenderLookupOptions<T>,
|
|
141
158
|
mode: RenderMode,
|
|
159
|
+
style: RenderStyle,
|
|
142
160
|
): string[] {
|
|
143
161
|
const prefixes = options.prefixes ?? DEFAULT_PREFIX_MAP;
|
|
144
162
|
return options.fields.flatMap((field) => {
|
|
@@ -148,9 +166,9 @@ function renderLookupFields<T>(
|
|
|
148
166
|
}
|
|
149
167
|
|
|
150
168
|
const formatted = formatInlineValue(value, prefixes);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
if (mode === "llm") return [`- ${field.label}: ${formatted}`];
|
|
170
|
+
const label = style.enabled ? style.cyan(field.label) : field.label;
|
|
171
|
+
return [` ${label}: ${formatted}`];
|
|
154
172
|
});
|
|
155
173
|
}
|
|
156
174
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { defaultStyle, styleFor } from "./style.js";
|
|
3
|
+
|
|
4
|
+
describe("styleFor", () => {
|
|
5
|
+
it("passes every color through unchanged when disabled", () => {
|
|
6
|
+
const style = styleFor(false);
|
|
7
|
+
expect(style.enabled).toBe(false);
|
|
8
|
+
expect(style.bold("x")).toBe("x");
|
|
9
|
+
expect(style.dim("x")).toBe("x");
|
|
10
|
+
expect(style.cyan("x")).toBe("x");
|
|
11
|
+
expect(style.green("x")).toBe("x");
|
|
12
|
+
expect(style.yellow("x")).toBe("x");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("reports enabled and drives chalk when enabled", () => {
|
|
16
|
+
const style = styleFor(true);
|
|
17
|
+
expect(style.enabled).toBe(true);
|
|
18
|
+
// A non-TTY test process leaves chalk at level 0, so the text survives
|
|
19
|
+
// verbatim; what the beautified renderers gate on is `enabled`.
|
|
20
|
+
expect(style.cyan("x")).toContain("x");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("defaultStyle", () => {
|
|
25
|
+
it("is disabled when stdout is not a TTY (keeps piped output byte-stable)", () => {
|
|
26
|
+
// The vitest runner's stdout is never a TTY, so the beautify seam is inert
|
|
27
|
+
// here — the guarantee that piped / agent output is unstyled and stable.
|
|
28
|
+
expect(process.stdout.isTTY).not.toBe(true);
|
|
29
|
+
expect(defaultStyle().enabled).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal-styling seam for the human (TTY) output path.
|
|
3
|
+
*
|
|
4
|
+
* The condensed/agent (`llm`) and JSON paths are byte-frozen contracts; only an
|
|
5
|
+
* interactive terminal earns color and alignment. A {@link RenderStyle} carries
|
|
6
|
+
* that decision as data so a pure renderer can consult it without reading the
|
|
7
|
+
* process itself: {@link defaultStyle} probes chalk's own TTY/env detection (the
|
|
8
|
+
* same signal the help system leans on), so piped / redirected / MCP output —
|
|
9
|
+
* where `enabled` is false — renders byte-for-byte as before, while an attended
|
|
10
|
+
* terminal renders with `enabled: true`. Passing an explicit style keeps the
|
|
11
|
+
* beautified renderers deterministically testable in both modes.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
|
|
16
|
+
/** The palette + gate a renderer consults to beautify the human path. */
|
|
17
|
+
export interface RenderStyle {
|
|
18
|
+
/** Whether to align + colorize — true only on a color-capable TTY. */
|
|
19
|
+
readonly enabled: boolean;
|
|
20
|
+
/** Emphasize a heading or primary label. */
|
|
21
|
+
bold(text: string): string;
|
|
22
|
+
/** De-emphasize secondary text (rules, markers, paths, punctuation). */
|
|
23
|
+
dim(text: string): string;
|
|
24
|
+
/** Highlight a value or identifier. */
|
|
25
|
+
cyan(text: string): string;
|
|
26
|
+
/** Tint a healthy / up-to-date status. */
|
|
27
|
+
green(text: string): string;
|
|
28
|
+
/** Tint a status that wants attention. */
|
|
29
|
+
yellow(text: string): string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The identity styler — every function returns its input unchanged. */
|
|
33
|
+
const PLAIN_STYLE: RenderStyle = {
|
|
34
|
+
enabled: false,
|
|
35
|
+
bold: (text) => text,
|
|
36
|
+
dim: (text) => text,
|
|
37
|
+
cyan: (text) => text,
|
|
38
|
+
green: (text) => text,
|
|
39
|
+
yellow: (text) => text,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build a {@link RenderStyle} for a known color decision.
|
|
44
|
+
*
|
|
45
|
+
* @param enabled - True to colorize + align via chalk; false for the identity
|
|
46
|
+
* styler (byte-identical to the pre-beautify plain output).
|
|
47
|
+
* @returns A style whose functions color, or pass through, accordingly.
|
|
48
|
+
*/
|
|
49
|
+
export function styleFor(enabled: boolean): RenderStyle {
|
|
50
|
+
if (!enabled) return PLAIN_STYLE;
|
|
51
|
+
return {
|
|
52
|
+
enabled: true,
|
|
53
|
+
bold: (text) => chalk.bold(text),
|
|
54
|
+
dim: (text) => chalk.dim(text),
|
|
55
|
+
cyan: (text) => chalk.cyan(text),
|
|
56
|
+
green: (text) => chalk.green(text),
|
|
57
|
+
yellow: (text) => chalk.yellow(text),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The active style for this process's stdout.
|
|
63
|
+
*
|
|
64
|
+
* Gated on BOTH signals: stdout must be an attended TTY, so piped / redirected /
|
|
65
|
+
* MCP output stays byte-stable even where `supports-color` fires off a TTY (CI
|
|
66
|
+
* sets `GITHUB_ACTIONS`, which chalk honors); AND chalk must report a usable
|
|
67
|
+
* color level, so `NO_COLOR` on a real terminal still yields the plain form.
|
|
68
|
+
*
|
|
69
|
+
* @returns A colorizing style on a color-capable TTY, else the identity styler.
|
|
70
|
+
* @note Impure — reads `process.stdout.isTTY` and chalk's color level.
|
|
71
|
+
*/
|
|
72
|
+
export function defaultStyle(): RenderStyle {
|
|
73
|
+
return styleFor(process.stdout.isTTY === true && chalk.level > 0);
|
|
74
|
+
}
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
MANIFEST_FILE,
|
|
40
40
|
type Manifest,
|
|
41
41
|
SCHEMA_FILE,
|
|
42
|
+
STORIES_FILE,
|
|
42
43
|
} from "./types.js";
|
|
43
44
|
|
|
44
45
|
/** One RDF source to build into a pack. */
|
|
@@ -53,10 +54,17 @@ export interface BuildPackInput {
|
|
|
53
54
|
export interface BuildPackOptions {
|
|
54
55
|
readonly name: string;
|
|
55
56
|
readonly version: string;
|
|
56
|
-
/** The config `
|
|
57
|
+
/** The config `packs` ref (verbatim) or a label. */
|
|
57
58
|
readonly sourceRef: string;
|
|
58
59
|
/** Prefixes the store (and every query) is built with. */
|
|
59
60
|
readonly prefixes?: Readonly<Record<string, string>>;
|
|
61
|
+
/**
|
|
62
|
+
* The `stories/*.json` files the resolved packages ship, as raw text. Hash
|
|
63
|
+
* inputs like any other source — a story-only edit is a new pack, and a set
|
|
64
|
+
* of packages shipping none keeps the hash it had before stories existed, so
|
|
65
|
+
* no cached pack is invalidated by rehashing.
|
|
66
|
+
*/
|
|
67
|
+
readonly stories?: readonly BuildPackInput[];
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
/** The outcome of a pack build (or cache hit). */
|
|
@@ -72,7 +80,8 @@ export interface BuildPackResult {
|
|
|
72
80
|
* Build (or reuse) the pack for a set of source inputs.
|
|
73
81
|
*
|
|
74
82
|
* @param inputs - The RDF sources (path + content).
|
|
75
|
-
* @param options - Provenance
|
|
83
|
+
* @param options - Provenance, the prefixes to build with, and the packages'
|
|
84
|
+
* carried read stories.
|
|
76
85
|
* @returns The pack directory, its content hash, and whether it was reused.
|
|
77
86
|
* @note Impure — creates a store, compiles the schema, writes the pack.
|
|
78
87
|
*/
|
|
@@ -80,8 +89,12 @@ export async function buildPack(
|
|
|
80
89
|
inputs: readonly BuildPackInput[],
|
|
81
90
|
options: BuildPackOptions,
|
|
82
91
|
): Promise<BuildPackResult> {
|
|
92
|
+
const stories = options.stories ?? [];
|
|
83
93
|
const hash = await contentHash(
|
|
84
|
-
inputs.map((input) => ({
|
|
94
|
+
[...inputs, ...stories].map((input) => ({
|
|
95
|
+
path: input.path,
|
|
96
|
+
content: input.content,
|
|
97
|
+
})),
|
|
85
98
|
);
|
|
86
99
|
const dir = packDir(hash);
|
|
87
100
|
|
|
@@ -121,6 +134,29 @@ export async function buildPack(
|
|
|
121
134
|
const index = await buildIndex(store, store.prefixes, hash);
|
|
122
135
|
writeFileSync(join(temp, INDEX_FILE), JSON.stringify(index));
|
|
123
136
|
|
|
137
|
+
// Written UNCONDITIONALLY (`[]` when the packages ship none): the artifact
|
|
138
|
+
// set is FIVE AND ONLY FIVE, and `packIsComplete` gates on it, so a pack
|
|
139
|
+
// whose hash covers stories always holds them.
|
|
140
|
+
writeFileSync(
|
|
141
|
+
join(temp, STORIES_FILE),
|
|
142
|
+
JSON.stringify(
|
|
143
|
+
stories.map((story) => ({
|
|
144
|
+
source: story.path,
|
|
145
|
+
content: story.content,
|
|
146
|
+
})),
|
|
147
|
+
),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const tripleCount = await countTriples(store);
|
|
151
|
+
// Distinct abox subjects — the same figure `entityTotal` reports (A10),
|
|
152
|
+
// so `sources status` reads it from the manifest without parsing the
|
|
153
|
+
// whole index.json. Keep this in sync with `entityTotal` in entitySource.
|
|
154
|
+
const entityCount = new Set(
|
|
155
|
+
index.entities
|
|
156
|
+
.filter((entity) => entity.box === "abox")
|
|
157
|
+
.map((entity) => entity.uri ?? entity.name),
|
|
158
|
+
).size;
|
|
159
|
+
|
|
124
160
|
const manifest: Manifest = {
|
|
125
161
|
name: options.name,
|
|
126
162
|
version: options.version,
|
|
@@ -128,6 +164,8 @@ export async function buildPack(
|
|
|
128
164
|
contentHash: hash,
|
|
129
165
|
prefixes: { ...store.prefixes },
|
|
130
166
|
createdAt: new Date().toISOString(),
|
|
167
|
+
tripleCount,
|
|
168
|
+
entityCount,
|
|
131
169
|
};
|
|
132
170
|
// Written LAST — the completeness marker for the directory.
|
|
133
171
|
writeFileSync(join(temp, MANIFEST_FILE), JSON.stringify(manifest));
|
|
@@ -146,3 +184,19 @@ export async function buildPack(
|
|
|
146
184
|
if (existsSync(temp)) rmSync(temp, { recursive: true, force: true });
|
|
147
185
|
}
|
|
148
186
|
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Count a store's triples (a cheap aggregate over the union graph).
|
|
190
|
+
*
|
|
191
|
+
* @param store - The booted store.
|
|
192
|
+
* @returns The number of triples.
|
|
193
|
+
* @note Impure — runs a COUNT query against the store.
|
|
194
|
+
*/
|
|
195
|
+
async function countTriples(
|
|
196
|
+
store: Awaited<ReturnType<typeof createStore>>,
|
|
197
|
+
): Promise<number> {
|
|
198
|
+
const result = (await store.query(
|
|
199
|
+
"SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }" as never,
|
|
200
|
+
)) as import("@canonical/ke").SelectResult;
|
|
201
|
+
return Number(result.bindings.at(0)?.n ?? 0);
|
|
202
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createStore } from "@canonical/ke";
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
OWL_EXPORT_ABOX_SUBJECTS,
|
|
5
|
+
OWL_EXPORT_PREFIXES,
|
|
6
|
+
OWL_EXPORT_TTL,
|
|
7
|
+
} from "../../../testing/fixtures/graph/owlExport.js";
|
|
8
|
+
import { entityTotal } from "../../completion/entitySource.js";
|
|
9
|
+
import { VOCABULARY } from "../../vocabulary.js";
|
|
10
|
+
import { buildIndex } from "./buildIndex.js";
|
|
11
|
+
import type { PackIndex } from "./types.js";
|
|
12
|
+
|
|
13
|
+
type Store = import("@canonical/ke").Store;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The counters/classifiers `buildIndex` builds are masked by the embedded
|
|
17
|
+
* single-typed toy graph; the multi-typed OWL-export fixture is where the
|
|
18
|
+
* backlog-A bugs bite. Each `it` fails on the pre-fix code and passes after.
|
|
19
|
+
*/
|
|
20
|
+
describe("buildIndex — real-data counters/classifiers (backlog A)", () => {
|
|
21
|
+
let store: Store;
|
|
22
|
+
let index: PackIndex;
|
|
23
|
+
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
store = await createStore({
|
|
26
|
+
sources: [{ content: OWL_EXPORT_TTL, path: "owl-export.ttl" }],
|
|
27
|
+
prefixes: OWL_EXPORT_PREFIXES,
|
|
28
|
+
});
|
|
29
|
+
index = await buildIndex(store, OWL_EXPORT_PREFIXES, "test-hash");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterAll(() => {
|
|
33
|
+
store.dispose();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const byName = (name: string): PackIndex["entities"][number][] =>
|
|
37
|
+
index.entities.filter((entity) => entity.name === name);
|
|
38
|
+
|
|
39
|
+
it("A1: entityTotal counts DISTINCT abox subjects, not the raw multiset", () => {
|
|
40
|
+
// Every individual once — despite Protégé's `owl:NamedIndividual` dual-typing.
|
|
41
|
+
expect(entityTotal(index)).toBe(OWL_EXPORT_ABOX_SUBJECTS.length);
|
|
42
|
+
|
|
43
|
+
// The raw multiset the old code summed is strictly larger: each individual
|
|
44
|
+
// double-counts under `owl:NamedIndividual`, plus the class/property
|
|
45
|
+
// meta-buckets pile on top.
|
|
46
|
+
const rawSum = Object.values(index.instanceCountByType).reduce(
|
|
47
|
+
(sum, n) => sum + n,
|
|
48
|
+
0,
|
|
49
|
+
);
|
|
50
|
+
expect(rawSum).toBeGreaterThan(entityTotal(index));
|
|
51
|
+
|
|
52
|
+
// The abox names match the oracle exactly.
|
|
53
|
+
const aboxNames = index.entities
|
|
54
|
+
.filter((entity) => entity.box === "abox")
|
|
55
|
+
.map((entity) => entity.name);
|
|
56
|
+
expect([...new Set(aboxNames)].sort()).toEqual([
|
|
57
|
+
...OWL_EXPORT_ABOX_SUBJECTS,
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("A3: a multi-domain individual gets a deterministic (lexical) primary type", () => {
|
|
62
|
+
// ex:toggle is both ex:Interactive and ex:FormControl; the smaller wins,
|
|
63
|
+
// every build/engine — never whichever type the store returned first.
|
|
64
|
+
const toggle = byName("ex:toggle").at(0);
|
|
65
|
+
expect(toggle?.type).toBe("ex:FormControl");
|
|
66
|
+
expect(toggle?.box).toBe("abox");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("A6: a blank-node rdf:type is ignored, never a garbage primary type", () => {
|
|
70
|
+
// ex:field is typed by an anonymous class too; the blank node must not win.
|
|
71
|
+
expect(byName("ex:field").at(0)?.type).toBe("ex:FormControl");
|
|
72
|
+
// No entity type, no listed type, and no instance-count bucket is a blank node.
|
|
73
|
+
for (const entity of index.entities) {
|
|
74
|
+
expect(entity.type.startsWith("_:")).toBe(false);
|
|
75
|
+
for (const type of entity.types ?? []) {
|
|
76
|
+
expect(type.startsWith("_:")).toBe(false);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const key of Object.keys(index.instanceCountByType)) {
|
|
80
|
+
expect(key.startsWith("_:")).toBe(false);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("A7: a multilingual label resolves to @en, deterministically", () => {
|
|
85
|
+
// "Button"@en / "Bouton"@fr → Button; the @lang no longer flips the pick.
|
|
86
|
+
expect(byName("ds:button").at(0)?.label).toBe("Button");
|
|
87
|
+
expect(byName("ex:toggle").at(0)?.label).toBe("Toggle");
|
|
88
|
+
expect(byName("ds:datePicker").at(0)?.label).toBe("Date Picker");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("A8: an OWL-punned subject emits BOTH its tbox and abox facet", () => {
|
|
92
|
+
// ex:Slider is a class (tbox) AND an ex:Category individual (abox) — both
|
|
93
|
+
// must survive, so its abox membership stays completable.
|
|
94
|
+
const facets = byName("ex:Slider");
|
|
95
|
+
expect(facets).toHaveLength(2);
|
|
96
|
+
expect(facets.map((facet) => facet.box).sort()).toEqual(["abox", "tbox"]);
|
|
97
|
+
expect(facets.find((facet) => facet.box === "tbox")?.type).toBe(
|
|
98
|
+
"owl:Class",
|
|
99
|
+
);
|
|
100
|
+
expect(facets.find((facet) => facet.box === "abox")?.type).toBe(
|
|
101
|
+
"ex:Category",
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The alternative-name property is DECLARED by the distribution, so a pack whose
|
|
108
|
+
* prefix map does not bind its prefix is an ordinary case — a third-party pack
|
|
109
|
+
* outside that namespace — not an error. Resolving the term strictly would throw
|
|
110
|
+
* there and take the whole pack build down with it.
|
|
111
|
+
*/
|
|
112
|
+
describe("buildIndex — the declared alternative-name property", () => {
|
|
113
|
+
let store: Store;
|
|
114
|
+
|
|
115
|
+
beforeAll(async () => {
|
|
116
|
+
store = await createStore({
|
|
117
|
+
sources: [{ content: OWL_EXPORT_TTL, path: "owl-export.ttl" }],
|
|
118
|
+
prefixes: OWL_EXPORT_PREFIXES,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
afterAll(() => {
|
|
123
|
+
store.dispose();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("projects it into altNames when the pack binds its prefix", async () => {
|
|
127
|
+
const index = await buildIndex(store, OWL_EXPORT_PREFIXES, "bound");
|
|
128
|
+
const named = index.entities.filter((entity) => entity.altNames);
|
|
129
|
+
expect(named.length).toBeGreaterThan(0);
|
|
130
|
+
expect(named.at(0)?.altNames?.length).toBeGreaterThan(0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("still builds, carrying no altNames, when the pack binds no such prefix", async () => {
|
|
134
|
+
// Unbind the prefix the DECLARATION uses, whatever it is — the subject here
|
|
135
|
+
// is the declared property, so naming a prefix would couple this case to
|
|
136
|
+
// today's values and quietly stop testing anything if they moved.
|
|
137
|
+
const declared = VOCABULARY.altName.slice(
|
|
138
|
+
0,
|
|
139
|
+
VOCABULARY.altName.indexOf(":"),
|
|
140
|
+
);
|
|
141
|
+
const { [declared]: _unbound, ...withoutDomain } = OWL_EXPORT_PREFIXES;
|
|
142
|
+
const index = await buildIndex(store, withoutDomain, "unbound");
|
|
143
|
+
// The graph is unchanged, so every entity is still indexed…
|
|
144
|
+
expect(index.entities.length).toBeGreaterThan(0);
|
|
145
|
+
// …they simply carry no alternative names, exactly as a pack that never
|
|
146
|
+
// authored the property would.
|
|
147
|
+
expect(index.entities.filter((entity) => entity.altNames)).toEqual([]);
|
|
148
|
+
});
|
|
149
|
+
});
|