@canonical/pragma-cli 0.30.0 → 0.31.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 +64 -35
- package/dist/pragma +0 -0
- package/package.json +15 -13
- package/src/completions/constants.ts +7 -0
- package/src/completions/handleQuery.test.ts +32 -0
- package/src/completions/handleQuery.ts +20 -1
- package/src/completions/queryCompletions.test.ts +5 -0
- package/src/completions/queryCompletions.ts +17 -3
- package/src/completions/querySocket.test.ts +31 -0
- package/src/completions/querySocket.ts +36 -9
- package/src/config/parseConfigValues.ts +2 -0
- package/src/config/readConfigLayers.test.ts +2 -0
- package/src/config/readConfigLayers.ts +3 -0
- package/src/config/types.ts +7 -0
- package/src/constants.ts +3 -1
- package/src/domains/block/commands/sample.ts +2 -1
- package/src/domains/block/formatters/list.test.ts +3 -3
- package/src/domains/block/formatters/lookup.test.ts +5 -5
- package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
- package/src/domains/block/stories.ts +9 -3
- package/src/domains/config/commands/channel.test.ts +16 -0
- package/src/domains/config/commands/channel.ts +14 -1
- package/src/domains/config/commands/framework.test.ts +14 -0
- package/src/domains/config/commands/framework.ts +13 -1
- package/src/domains/config/commands/tier.test.ts +7 -0
- package/src/domains/config/commands/tier.ts +14 -1
- package/src/domains/config/operations/validateTier.test.ts +9 -0
- package/src/domains/config/operations/validateTier.ts +10 -2
- package/src/domains/create/generatorPack/bundled.ts +49 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
- package/src/domains/create/index.ts +13 -7
- package/src/domains/create/mcp/specs.ts +6 -161
- package/src/domains/create/renderGeneratorUi.test.ts +103 -0
- package/src/domains/create/renderGeneratorUi.ts +65 -0
- package/src/domains/doctor/formatters/doctor.test.ts +50 -2
- package/src/domains/doctor/formatters/doctor.ts +90 -38
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +9 -6
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -12
- package/src/domains/doctor/operations/checks.test.ts +6 -4
- package/src/domains/doctor/operations/types.ts +23 -1
- package/src/domains/graph/commands/inspect.test.ts +2 -2
- package/src/domains/graph/commands/query.test.ts +5 -3
- package/src/domains/graph/formatters/inspect.ts +7 -7
- package/src/domains/graph/formatters/query.test.ts +87 -0
- package/src/domains/graph/formatters/query.ts +42 -5
- package/src/domains/graph/operations/inspectUri.test.ts +5 -5
- package/src/domains/graphql/commands/serve.test.ts +12 -6
- package/src/domains/graphql/commands/serve.ts +16 -6
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
- package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
- package/src/domains/graphql/operations/compileSchema.test.ts +2 -2
- package/src/domains/graphql/operations/createSchemaServer.test.ts +2 -2
- package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
- package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
- package/src/domains/graphql/operations/index.ts +5 -0
- package/src/domains/info/formatters/upgrade.test.ts +7 -4
- package/src/domains/info/formatters/upgrade.ts +3 -1
- package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
- package/src/domains/info/operations/collectEntityCounts.ts +123 -0
- package/src/domains/info/operations/index.ts +1 -0
- package/src/domains/info/types.ts +13 -0
- package/src/domains/llm/capabilities.test.ts +23 -3
- package/src/domains/llm/commands/capabilities.ts +12 -3
- package/src/domains/llm/commands/llm.ts +5 -1
- package/src/domains/llm/data/commandReference.ts +20 -11
- package/src/domains/llm/data/decisionTrees.ts +10 -5
- package/src/domains/llm/data/toolCatalog.ts +49 -18
- package/src/domains/llm/formatters/orientation.ts +5 -1
- package/src/domains/llm/llmCommand.test.ts +11 -3
- package/src/domains/llm/mcp/specs.ts +3 -4
- package/src/domains/llm/operations/collectContext.test.ts +64 -0
- package/src/domains/llm/operations/collectContext.ts +7 -17
- package/src/domains/modifier/commands/sample.ts +2 -1
- package/src/domains/modifier/operations/lookup.ts +5 -3
- package/src/domains/modifier/stories.ts +9 -0
- package/src/domains/ontology/commands/list.test.ts +5 -3
- package/src/domains/ontology/commands/show.test.ts +85 -9
- package/src/domains/ontology/formatters/index.ts +1 -0
- package/src/domains/ontology/formatters/list.ts +37 -12
- package/src/domains/ontology/formatters/show.ts +358 -67
- package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
- package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
- package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
- package/src/domains/ontology/helpers/index.ts +14 -0
- package/src/domains/ontology/helpers/queryClasses.ts +32 -16
- package/src/domains/ontology/helpers/queryProperties.ts +39 -6
- package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
- package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
- package/src/domains/ontology/operations/listOntologies.ts +126 -30
- package/src/domains/ontology/operations/showOntology.test.ts +131 -12
- package/src/domains/ontology/operations/showOntology.ts +289 -7
- package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
- package/src/domains/ontology/stories.ts +53 -8
- package/src/domains/refs/operations/updateRefs.ts +0 -1
- package/src/domains/setup/commands/index.ts +1 -0
- package/src/domains/setup/commands/setup.ts +70 -0
- package/src/domains/setup/index.ts +7 -3
- package/src/domains/setup/operations/setupLsp.test.ts +3 -2
- package/src/domains/setup/operations/setupLsp.ts +7 -2
- package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
- package/src/domains/shared/bootStore.test.ts +82 -0
- package/src/domains/shared/bootStore.ts +56 -20
- package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
- package/src/domains/shared/extractLocalName.test.ts +3 -3
- package/src/domains/shared/formatters.ts +23 -2
- package/src/domains/shared/listTiers.ts +40 -0
- package/src/domains/shared/loaders/bundledLoader.ts +48 -0
- package/src/domains/shared/loaders/gitLoader.ts +1 -2
- package/src/domains/shared/loaders/localLoader.ts +2 -18
- package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
- package/src/domains/shared/loaders/readPackageDir.ts +46 -8
- package/src/domains/shared/lookupMany.test.ts +15 -0
- package/src/domains/shared/lookupMany.ts +22 -6
- package/src/domains/shared/namespaces.ts +13 -5
- package/src/domains/shared/parseSampleCount.test.ts +47 -0
- package/src/domains/shared/parseSampleCount.ts +45 -0
- package/src/domains/shared/prefixes.test.ts +211 -0
- package/src/domains/shared/prefixes.ts +182 -15
- package/src/domains/shared/renderers.ts +5 -5
- package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
- package/src/domains/shared/resolveLookupExitCode.ts +42 -0
- package/src/domains/shared/semanticPackage.test.ts +89 -0
- package/src/domains/shared/semanticPackage.ts +17 -2
- package/src/domains/shared/stories/compileLookupCommand.ts +3 -1
- package/src/domains/shared/stories/compileLookupTool.ts +1 -1
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +49 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +46 -11
- package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
- package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +30 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +75 -1
- package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
- package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
- package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +305 -7
- package/src/domains/shared/stories/pack/collectPackStories.ts +90 -8
- package/src/domains/shared/stories/pack/compilePackCommands.ts +17 -7
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +607 -2
- package/src/domains/shared/stories/pack/compilePackStories.ts +544 -73
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +20 -8
- package/src/domains/shared/stories/pack/index.ts +14 -0
- package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
- package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +293 -220
- package/src/domains/shared/stories/pack/types.ts +123 -3
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +502 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +475 -24
- package/src/domains/shared/stories/types.ts +9 -1
- package/src/domains/shared/types/index.ts +5 -9
- package/src/domains/shared/types/ontology.ts +119 -24
- package/src/domains/shared/types/standard.ts +9 -52
- package/src/domains/token/commands/lookup.test.ts +8 -0
- package/src/domains/token/commands/lookup.ts +12 -0
- package/src/domains/token/commands/sample.ts +2 -1
- package/src/domains/token/featureFlag.ts +17 -0
- package/src/domains/token/index.ts +5 -0
- package/src/domains/token/mcp/specs.ts +13 -1
- package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
- package/src/domains/trace/commands/show.ts +30 -4
- package/src/index.ts +1 -13
- package/src/mcp/registerResources.test.ts +2 -2
- package/src/mcp/resources/pickPrimaryType.test.ts +5 -5
- package/src/mcp/tools/index.ts +18 -10
- package/src/mcp/tools/registerTools.test.ts +106 -30
- package/src/pipeline/collectCommands.test.ts +151 -1
- package/src/pipeline/collectCommands.ts +34 -19
- package/src/pipeline/createProgram.test.ts +5 -1
- package/src/pipeline/createProgram.ts +2 -2
- package/src/pipeline/firstRun.test.ts +80 -0
- package/src/pipeline/firstRun.ts +99 -0
- package/src/pipeline/parseGlobalFlags.test.ts +102 -1
- package/src/pipeline/parseGlobalFlags.ts +98 -9
- package/src/pipeline/resolveCommandKind.test.ts +12 -0
- package/src/pipeline/resolveCommandKind.ts +4 -0
- package/src/pipeline/rootHelp.test.ts +87 -0
- package/src/pipeline/rootHelp.ts +265 -0
- package/src/pipeline/runCli.orchestration.test.ts +28 -12
- package/src/pipeline/runCli.test.ts +17 -4
- package/src/pipeline/runCli.ts +162 -19
- package/src/testing/cli.ts +30 -2
- package/src/testing/dsFixtures.ts +21 -4
- package/src/testing/index.ts +1 -4
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +4 -4
- package/src/testing/integration/cli-user-stories.test.ts +63 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +2 -2
- package/src/testing/integration/edge-cases.test.ts +62 -50
- package/src/testing/integration/executor-execution.test.ts +6 -4
- package/src/testing/integration/executor-golden.test.ts +2 -2
- package/src/testing/integration/mcp-surface.test.ts +40 -15
- package/src/testing/integration/parity.test.ts +119 -70
- package/src/testing/setupXdgIsolation.ts +16 -5
- package/src/testing/standardParityFixtures.ts +26 -83
- package/src/testing/store.ts +4 -4
- package/src/domains/create/commands/component.test.ts +0 -166
- package/src/domains/create/commands/component.ts +0 -123
- package/src/domains/create/commands/index.ts +0 -3
- package/src/domains/create/commands/package.test.ts +0 -35
- package/src/domains/create/commands/package.ts +0 -21
- package/src/domains/create/generators.ts +0 -8
- package/src/domains/standard/commands/categories.test.ts +0 -70
- package/src/domains/standard/commands/categories.ts +0 -48
- package/src/domains/standard/commands/index.ts +0 -6
- package/src/domains/standard/commands/list.test.ts +0 -105
- package/src/domains/standard/commands/list.ts +0 -11
- package/src/domains/standard/commands/lookup.test.ts +0 -135
- package/src/domains/standard/commands/lookup.ts +0 -14
- package/src/domains/standard/commands/sample.ts +0 -57
- package/src/domains/standard/formatters/categories.test.ts +0 -44
- package/src/domains/standard/formatters/categories.ts +0 -38
- package/src/domains/standard/formatters/index.ts +0 -10
- package/src/domains/standard/formatters/list.test.ts +0 -169
- package/src/domains/standard/formatters/list.ts +0 -184
- package/src/domains/standard/formatters/lookup.test.ts +0 -95
- package/src/domains/standard/formatters/lookup.ts +0 -101
- package/src/domains/standard/formatters/sample.ts +0 -15
- package/src/domains/standard/formatters/types.ts +0 -31
- package/src/domains/standard/index.ts +0 -40
- package/src/domains/standard/mcp/index.ts +0 -2
- package/src/domains/standard/mcp/specs.ts +0 -89
- package/src/domains/standard/operations/categories.test.ts +0 -61
- package/src/domains/standard/operations/categories.ts +0 -38
- package/src/domains/standard/operations/index.ts +0 -6
- package/src/domains/standard/operations/list.test.ts +0 -75
- package/src/domains/standard/operations/list.ts +0 -64
- package/src/domains/standard/operations/lookup.test.ts +0 -58
- package/src/domains/standard/operations/lookup.ts +0 -151
- package/src/domains/standard/operations/sample.ts +0 -35
- package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
- package/src/domains/standard/orchestration/index.ts +0 -4
- package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
- package/src/domains/standard/standardConfig.ts +0 -42
- package/src/domains/standard/stories.test.ts +0 -24
- package/src/domains/standard/stories.ts +0 -151
- package/src/domains/standard/types.ts +0 -15
- package/src/domains/tier/commands/index.ts +0 -3
- package/src/domains/tier/commands/list.test.ts +0 -67
- package/src/domains/tier/commands/list.ts +0 -9
- package/src/domains/tier/formatters/index.ts +0 -3
- package/src/domains/tier/formatters/list.test.ts +0 -33
- package/src/domains/tier/formatters/list.ts +0 -45
- package/src/domains/tier/index.ts +0 -17
- package/src/domains/tier/mcp/index.ts +0 -2
- package/src/domains/tier/mcp/specs.ts +0 -12
- package/src/domains/tier/operations/index.ts +0 -3
- package/src/domains/tier/operations/list.test.ts +0 -32
- package/src/domains/tier/operations/list.ts +0 -33
- package/src/domains/tier/stories.test.ts +0 -22
- package/src/domains/tier/stories.ts +0 -52
|
@@ -1,59 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Standard-noun parity
|
|
3
|
-
*
|
|
2
|
+
* Standard-noun cutover parity — the bundled `standard` pack against the
|
|
3
|
+
* real @canonical/code-standards graphs, resolved from node_modules
|
|
4
|
+
* through the production loader chain (no fixture TTL).
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* The hand-written standard domain is gone, so parity is SEMANTIC and
|
|
7
|
+
* asserted directly against the graph: every standard the store carries
|
|
8
|
+
* is reachable through the pack's list/lookup/categories/sample stories
|
|
9
|
+
* with the same names and values (category, description, extends,
|
|
10
|
+
* dos/donts code examples), by name, prefixed name, absolute IRI, and
|
|
11
|
+
* glob. The accepted divergences from the deleted built-in are pinned in
|
|
12
|
+
* PARITY_GAPS (`#testing`), not silently absorbed here.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import type { Store } from "@canonical/ke";
|
|
14
16
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
15
|
-
import {
|
|
17
|
+
import { createTestStore, PARITY_GAPS } from "#testing";
|
|
16
18
|
import { parsePackageEntry } from "../../../refs/operations/parseRef.js";
|
|
17
|
-
import type { StandardListOutput } from "../../../standard/formatters/index.js";
|
|
18
|
-
import {
|
|
19
|
-
standardListStory,
|
|
20
|
-
standardLookupStory,
|
|
21
|
-
} from "../../../standard/stories.js";
|
|
22
19
|
import { bootStore } from "../../bootStore.js";
|
|
23
20
|
import compactUri from "../../compactUri.js";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_PREFIX_MAP,
|
|
23
|
+
TRANSITIONAL_DS_PREFIX_MAP,
|
|
24
|
+
} from "../../prefixes.js";
|
|
25
|
+
import { listDomainNames } from "../../suggestions/index.js";
|
|
26
|
+
import type { PragmaRuntime } from "../../types/index.js";
|
|
27
|
+
import { standardPack } from "./bundled/standardPack.js";
|
|
27
28
|
import compilePackStories, {
|
|
28
29
|
type CompiledPackStories,
|
|
29
30
|
} from "./compilePackStories.js";
|
|
31
|
+
import runSelectQuery from "./runSelectQuery.js";
|
|
30
32
|
import validateStoryPackDefinition from "./validateStoryPackDefinition.js";
|
|
31
33
|
|
|
32
|
-
const PACK_SOURCE = "
|
|
33
|
-
|
|
34
|
-
/** The built-in CLI default view: summary lookup, no extra params. */
|
|
35
|
-
const SUMMARY_VIEW: LookupStoryView = {
|
|
36
|
-
surface: "cli",
|
|
37
|
-
detailed: false,
|
|
38
|
-
params: {},
|
|
39
|
-
};
|
|
34
|
+
const PACK_SOURCE = "bundled:standard";
|
|
40
35
|
|
|
41
|
-
/**
|
|
42
|
-
const
|
|
43
|
-
"code/function/purity",
|
|
44
|
-
"react/component/props",
|
|
45
|
-
] as const;
|
|
36
|
+
/** A real standard whose dos AND donts are non-empty on the live package. */
|
|
37
|
+
const LOOKUP_NAME = "react/component/props";
|
|
46
38
|
|
|
47
|
-
/** A real standard with cs:extends — pins the JSON
|
|
39
|
+
/** A real standard with cs:extends — pins the raw-IRI JSON contract. */
|
|
48
40
|
const EXTENDS_LOOKUP_NAME = "react/component/structure/context";
|
|
49
41
|
|
|
50
|
-
/** A near-miss query —
|
|
42
|
+
/** A near-miss query — must produce ranked suggestions. */
|
|
51
43
|
const NEAR_MISS_NAME = "code/function/puriti";
|
|
52
44
|
|
|
53
45
|
let store: Store;
|
|
54
46
|
let rt: PragmaRuntime;
|
|
55
|
-
let
|
|
56
|
-
let packLookup: NonNullable<CompiledPackStories["lookup"]>;
|
|
47
|
+
let compiled: CompiledPackStories;
|
|
57
48
|
|
|
58
49
|
beforeAll(async () => {
|
|
59
50
|
const boot = await bootStore({
|
|
@@ -61,76 +52,39 @@ beforeAll(async () => {
|
|
|
61
52
|
});
|
|
62
53
|
store = boot.store;
|
|
63
54
|
rt = { store } as PragmaRuntime;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
...PREFIX_MAP,
|
|
55
|
+
compiled = compilePackStories(standardPack, PACK_SOURCE, {
|
|
56
|
+
...DEFAULT_PREFIX_MAP,
|
|
67
57
|
});
|
|
68
|
-
packList = compiled.list;
|
|
69
|
-
const lookup = compiled.lookup;
|
|
70
|
-
if (!lookup) {
|
|
71
|
-
throw new Error("STANDARD_PACK_STORY must declare a lookup story");
|
|
72
|
-
}
|
|
73
|
-
packLookup = lookup;
|
|
74
58
|
});
|
|
75
59
|
|
|
76
60
|
afterAll(() => store?.dispose());
|
|
77
61
|
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
const result = await standardLookupStory.resolve(rt, [name], {});
|
|
81
|
-
expect(result.errors).toEqual([]);
|
|
82
|
-
const entity = result.results.at(0);
|
|
83
|
-
if (entity === undefined) {
|
|
84
|
-
throw new Error(`built-in lookup found no result for "${name}"`);
|
|
85
|
-
}
|
|
86
|
-
return entity;
|
|
87
|
-
}
|
|
62
|
+
/** The dos/donts row shape the pack projects for each code example. */
|
|
63
|
+
type ExampleRow = { caption?: string; language?: string; code?: string };
|
|
88
64
|
|
|
89
|
-
/** Resolve one
|
|
90
|
-
async function lookupPack(
|
|
91
|
-
|
|
65
|
+
/** Resolve one lookup query at an optional detail level. */
|
|
66
|
+
async function lookupPack(
|
|
67
|
+
name: string,
|
|
68
|
+
params: Record<string, unknown> = {},
|
|
69
|
+
): Promise<Record<string, string | readonly ExampleRow[]>> {
|
|
70
|
+
const lookup = compiled.lookup;
|
|
71
|
+
if (!lookup) throw new Error("standard pack must declare a lookup story");
|
|
72
|
+
const result = await lookup.resolve(rt, [name], params);
|
|
92
73
|
expect(result.errors).toEqual([]);
|
|
93
74
|
const entity = result.results.at(0);
|
|
94
75
|
if (entity === undefined) {
|
|
95
76
|
throw new Error(`pack lookup found no result for "${name}"`);
|
|
96
77
|
}
|
|
97
|
-
return entity
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Remove the built-in's URI field line — the one v0 lookup rendering
|
|
102
|
-
* gap (see PARITY_GAPS: "lookup uri field"). Asserts the line occurs
|
|
103
|
-
* exactly once so the transformation cannot mask other divergence.
|
|
104
|
-
*/
|
|
105
|
-
function dropUriField(text: string, marker: " URI: " | "- URI: "): string {
|
|
106
|
-
const lines = text.split("\n");
|
|
107
|
-
const remaining = lines.filter((line) => !line.startsWith(marker));
|
|
108
|
-
expect(lines.length - remaining.length).toBe(1);
|
|
109
|
-
return remaining.join("\n");
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Assert every name appears in the text, in order. */
|
|
113
|
-
function expectNamesInOrder(text: string, names: readonly string[]): void {
|
|
114
|
-
let cursor = 0;
|
|
115
|
-
for (const name of names) {
|
|
116
|
-
const index = text.indexOf(name, cursor);
|
|
117
|
-
expect(
|
|
118
|
-
index,
|
|
119
|
-
`expected "${name}" after position ${cursor}`,
|
|
120
|
-
).toBeGreaterThanOrEqual(0);
|
|
121
|
-
cursor = index + name.length;
|
|
122
|
-
}
|
|
78
|
+
return entity as Record<string, string | readonly ExampleRow[]>;
|
|
123
79
|
}
|
|
124
80
|
|
|
125
81
|
describe("standard pack definition", () => {
|
|
126
|
-
it("round-trips as declarative JSON and validates as a
|
|
127
|
-
const raw: unknown = JSON.parse(JSON.stringify(
|
|
128
|
-
expect(validateStoryPackDefinition(raw, PACK_SOURCE)).toEqual(
|
|
129
|
-
STANDARD_PACK_STORY,
|
|
130
|
-
);
|
|
82
|
+
it("round-trips as declarative JSON and validates as a v1 pack", () => {
|
|
83
|
+
const raw: unknown = JSON.parse(JSON.stringify(standardPack));
|
|
84
|
+
expect(validateStoryPackDefinition(raw, PACK_SOURCE)).toEqual(standardPack);
|
|
131
85
|
});
|
|
132
86
|
|
|
133
|
-
it("records every
|
|
87
|
+
it("records every accepted divergence as a distinct entry", () => {
|
|
134
88
|
expect(PARITY_GAPS.length).toBeGreaterThan(0);
|
|
135
89
|
expect(new Set(PARITY_GAPS).size).toBe(PARITY_GAPS.length);
|
|
136
90
|
for (const gap of PARITY_GAPS) {
|
|
@@ -140,169 +94,288 @@ describe("standard pack definition", () => {
|
|
|
140
94
|
});
|
|
141
95
|
|
|
142
96
|
describe("standard list parity", () => {
|
|
143
|
-
let
|
|
144
|
-
let builtinOutput: StandardListOutput;
|
|
145
|
-
let packRows: Record<string, string>[];
|
|
97
|
+
let rows: Record<string, string>[];
|
|
146
98
|
|
|
147
99
|
beforeAll(async () => {
|
|
148
|
-
|
|
149
|
-
builtinOutput = standardListStory.toOutput(builtinResolution, {});
|
|
150
|
-
packRows = await packList.resolve(rt, {});
|
|
100
|
+
rows = await compiled.list.resolve(rt, {});
|
|
151
101
|
});
|
|
152
102
|
|
|
153
|
-
it("
|
|
154
|
-
|
|
155
|
-
|
|
103
|
+
it("lists every named standard in the graph, with the uniform row shape", async () => {
|
|
104
|
+
// The shared suggestions name query is the independent oracle for
|
|
105
|
+
// which standards exist (same store, different query path).
|
|
106
|
+
const allNames = await listDomainNames(store, "standard");
|
|
107
|
+
expect(allNames.length).toBeGreaterThan(0);
|
|
108
|
+
const listed = new Set(rows.map((row) => row.name));
|
|
109
|
+
for (const name of allNames) {
|
|
110
|
+
expect(listed.has(name), `"${name}" missing from standard list`).toBe(
|
|
111
|
+
true,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
for (const row of rows) {
|
|
115
|
+
expect(row.uri).toBeTruthy();
|
|
116
|
+
expect(row.description).toBeTruthy();
|
|
117
|
+
}
|
|
156
118
|
});
|
|
157
119
|
|
|
158
|
-
it("
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
expect(
|
|
120
|
+
it("filters by category (value-free, case-insensitive)", async () => {
|
|
121
|
+
const filtered = await compiled.list.resolve(rt, { category: "REACT" });
|
|
122
|
+
expect(filtered.length).toBeGreaterThan(0);
|
|
123
|
+
expect(filtered).toEqual(rows.filter((row) => row.category === "react"));
|
|
162
124
|
});
|
|
163
125
|
|
|
164
|
-
it("
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
126
|
+
it("returns zero rows (not an error) for an unknown category", async () => {
|
|
127
|
+
// Pinned gap: the old list raised EMPTY_RESULTS here.
|
|
128
|
+
await expect(
|
|
129
|
+
compiled.list.resolve(rt, { category: "nonexistent" }),
|
|
130
|
+
).resolves.toEqual([]);
|
|
168
131
|
});
|
|
169
132
|
|
|
170
|
-
it("
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
133
|
+
it("searches name and description case-insensitively", async () => {
|
|
134
|
+
const found = await compiled.list.resolve(rt, { search: "folder" });
|
|
135
|
+
expect(found.length).toBeGreaterThan(0);
|
|
136
|
+
expect(found).toEqual(
|
|
137
|
+
rows.filter(
|
|
138
|
+
(row) =>
|
|
139
|
+
row.name?.toLowerCase().includes("folder") ||
|
|
140
|
+
row.description?.toLowerCase().includes("folder"),
|
|
141
|
+
),
|
|
175
142
|
);
|
|
176
|
-
expectNamesInOrder(builtinPlain, names);
|
|
177
|
-
expectNamesInOrder(packPlain, names);
|
|
178
|
-
// Layout divergence pinned — see PARITY_GAPS "list plain template".
|
|
179
|
-
expect(packPlain).not.toBe(builtinPlain);
|
|
180
143
|
});
|
|
181
144
|
|
|
182
|
-
it("
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
expect(packLlm).not.toBe(builtinLlm);
|
|
145
|
+
it("combines category and search conjunctively", async () => {
|
|
146
|
+
const found = await compiled.list.resolve(rt, {
|
|
147
|
+
category: "react",
|
|
148
|
+
search: "folder",
|
|
149
|
+
});
|
|
150
|
+
expect(found.length).toBeGreaterThan(0);
|
|
151
|
+
for (const row of found) {
|
|
152
|
+
expect(row.category).toBe("react");
|
|
153
|
+
}
|
|
192
154
|
});
|
|
193
155
|
});
|
|
194
156
|
|
|
195
|
-
describe("standard lookup parity
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
157
|
+
describe("standard lookup parity", () => {
|
|
158
|
+
it("resolves base fields by name at the default (summary) level", async () => {
|
|
159
|
+
const entity = await lookupPack(LOOKUP_NAME);
|
|
160
|
+
expect(entity.name).toBe(LOOKUP_NAME);
|
|
161
|
+
expect(entity.uri).toBeTruthy();
|
|
162
|
+
expect(entity.category).toBe("react");
|
|
163
|
+
expect(typeof entity.description).toBe("string");
|
|
164
|
+
// Summary fetches no expands.
|
|
165
|
+
expect(entity.dos).toBeUndefined();
|
|
166
|
+
expect(entity.donts).toBeUndefined();
|
|
167
|
+
});
|
|
206
168
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
);
|
|
214
|
-
expect(
|
|
215
|
-
}
|
|
169
|
+
it("fetches full dos (code examples) at --detail digest", async () => {
|
|
170
|
+
const entity = await lookupPack(LOOKUP_NAME, { detail: "digest" });
|
|
171
|
+
const dos = entity.dos as readonly ExampleRow[];
|
|
172
|
+
expect(Array.isArray(dos)).toBe(true);
|
|
173
|
+
expect(dos.length).toBeGreaterThan(0);
|
|
174
|
+
for (const example of dos) {
|
|
175
|
+
expect(example.code).toBeTruthy();
|
|
176
|
+
expect(example.language).toBeTruthy();
|
|
177
|
+
}
|
|
178
|
+
expect(entity.donts).toBeUndefined();
|
|
179
|
+
});
|
|
216
180
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
packLookup.toFmtInput(await lookupPack(name), SUMMARY_VIEW),
|
|
223
|
-
);
|
|
224
|
-
expect(packJson).toBe(builtinJson);
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
});
|
|
181
|
+
it("fetches dos AND donts at --detail detailed", async () => {
|
|
182
|
+
const entity = await lookupPack(LOOKUP_NAME, { detail: "detailed" });
|
|
183
|
+
expect((entity.dos as readonly ExampleRow[]).length).toBeGreaterThan(0);
|
|
184
|
+
expect((entity.donts as readonly ExampleRow[]).length).toBeGreaterThan(0);
|
|
185
|
+
});
|
|
228
186
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
187
|
+
it("carries each example's cs:description as its caption", async () => {
|
|
188
|
+
const entity = await lookupPack(LOOKUP_NAME, { detail: "detailed" });
|
|
189
|
+
const uri = entity.uri as string;
|
|
190
|
+
const dos = entity.dos as readonly ExampleRow[];
|
|
191
|
+
const donts = entity.donts as readonly ExampleRow[];
|
|
192
|
+
expect(dos.at(0)?.caption).toBeTruthy();
|
|
193
|
+
expect(donts.at(0)?.caption).toBeTruthy();
|
|
194
|
+
// Oracle: the same store queried directly for the examples' captions.
|
|
195
|
+
const doCaptions = await runSelectQuery(
|
|
196
|
+
store,
|
|
197
|
+
`SELECT ?caption WHERE { <${uri}> cs:do ?ex . ?ex cs:description ?caption }`,
|
|
198
|
+
"test:oracle",
|
|
238
199
|
);
|
|
239
|
-
expect(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
200
|
+
expect(doCaptions.map((row) => row.caption)).toContain(dos.at(0)?.caption);
|
|
201
|
+
const dontCaptions = await runSelectQuery(
|
|
202
|
+
store,
|
|
203
|
+
`SELECT ?caption WHERE { <${uri}> cs:dont ?ex . ?ex cs:description ?caption }`,
|
|
204
|
+
"test:oracle",
|
|
244
205
|
);
|
|
245
|
-
expect(
|
|
246
|
-
|
|
206
|
+
expect(dontCaptions.map((row) => row.caption)).toContain(
|
|
207
|
+
donts.at(0)?.caption,
|
|
247
208
|
);
|
|
248
209
|
});
|
|
249
210
|
|
|
250
|
-
it("
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
211
|
+
it("honors the legacy --detailed alias flag", async () => {
|
|
212
|
+
const aliased = await lookupPack(LOOKUP_NAME, { detailed: true });
|
|
213
|
+
const explicit = await lookupPack(LOOKUP_NAME, { detail: "detailed" });
|
|
214
|
+
expect(aliased).toEqual(explicit);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("keeps cs:extends as the raw IRI in data — a pinned gap", async () => {
|
|
218
|
+
const entity = await lookupPack(EXTENDS_LOOKUP_NAME);
|
|
219
|
+
const extendsIri = entity.extends as string;
|
|
220
|
+
expect(extendsIri).toMatch(/^https?:\/\//);
|
|
221
|
+
// Display-time compaction still yields the prefixed form.
|
|
222
|
+
expect(compactUri(extendsIri, DEFAULT_PREFIX_MAP)).toMatch(/^cs:/);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("resolves the same entity by absolute IRI and prefixed name", async () => {
|
|
226
|
+
const byName = await lookupPack(LOOKUP_NAME);
|
|
227
|
+
const uri = byName.uri as string;
|
|
228
|
+
|
|
229
|
+
const byIri = await lookupPack(uri);
|
|
230
|
+
expect(byIri.name).toBe(LOOKUP_NAME);
|
|
231
|
+
|
|
232
|
+
const prefixed = compactUri(uri, DEFAULT_PREFIX_MAP);
|
|
233
|
+
expect(prefixed).toMatch(/^cs:/);
|
|
234
|
+
const byPrefixed = await lookupPack(prefixed);
|
|
235
|
+
expect(byPrefixed.name).toBe(LOOKUP_NAME);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("expands glob queries against the standard names", async () => {
|
|
239
|
+
const lookup = compiled.lookup;
|
|
240
|
+
if (!lookup) throw new Error("expected lookup story");
|
|
241
|
+
const result = await lookup.resolve(rt, ["react/component/*"], {});
|
|
242
|
+
expect(result.errors).toEqual([]);
|
|
243
|
+
expect(result.results.length).toBeGreaterThan(1);
|
|
244
|
+
for (const entity of result.results) {
|
|
245
|
+
expect(String(entity.name)).toMatch(/^react\/component\//);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("reports misses with ranked suggestions", async () => {
|
|
250
|
+
const lookup = compiled.lookup;
|
|
251
|
+
if (!lookup) throw new Error("expected lookup story");
|
|
252
|
+
const result = await lookup.resolve(rt, [NEAR_MISS_NAME], {});
|
|
253
|
+
expect(result.results).toEqual([]);
|
|
254
|
+
const error = result.errors.at(0);
|
|
255
|
+
expect(error?.code).toBe("ENTITY_NOT_FOUND");
|
|
256
|
+
expect(error?.suggestions).toContain("code/function/purity");
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe("standard categories parity", () => {
|
|
261
|
+
it("lists every category with its standard count (zero-counts kept)", async () => {
|
|
262
|
+
const categories = compiled.verbs.find(
|
|
263
|
+
(story) => story.verb === "categories",
|
|
262
264
|
);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
265
|
+
if (!categories) throw new Error("expected a categories verb");
|
|
266
|
+
const rows = await categories.resolve(rt, {});
|
|
267
|
+
expect(rows.length).toBeGreaterThan(0);
|
|
268
|
+
|
|
269
|
+
// Independent oracle: recount per category from the list story.
|
|
270
|
+
const listRows = await compiled.list.resolve(rt, {});
|
|
271
|
+
const expected = new Map<string, number>();
|
|
272
|
+
for (const row of listRows) {
|
|
273
|
+
if (row.category) {
|
|
274
|
+
expected.set(row.category, (expected.get(row.category) ?? 0) + 1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
for (const [category, count] of expected) {
|
|
278
|
+
const row = rows.find((entry) => entry.name === category);
|
|
279
|
+
expect(row, `category "${category}" missing`).toBeDefined();
|
|
280
|
+
expect(Number(row?.count)).toBe(count);
|
|
281
|
+
}
|
|
282
|
+
// Categories beyond the expected map are zero-count declarations.
|
|
283
|
+
for (const row of rows) {
|
|
284
|
+
if (!expected.has(row.name ?? "")) {
|
|
285
|
+
expect(Number(row.count)).toBe(0);
|
|
286
|
+
}
|
|
271
287
|
}
|
|
272
|
-
// The built-in compacts extends in resolved data; the pack keeps the
|
|
273
|
-
// raw IRI. Compacting the pack value restores byte-level agreement.
|
|
274
|
-
expect({
|
|
275
|
-
...packParsed,
|
|
276
|
-
extends: compactUri(packExtends, PREFIX_MAP),
|
|
277
|
-
}).toEqual(builtinParsed);
|
|
278
|
-
expect(
|
|
279
|
-
JSON.stringify(
|
|
280
|
-
{ ...packParsed, extends: compactUri(packExtends, PREFIX_MAP) },
|
|
281
|
-
null,
|
|
282
|
-
2,
|
|
283
|
-
),
|
|
284
|
-
).toBe(builtinJson);
|
|
285
288
|
});
|
|
286
289
|
});
|
|
287
290
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
291
|
+
// Synthetic fixture: the pinned @canonical/code-standards release names every
|
|
292
|
+
// standard, so the real-graph suites above cannot observe the list query's
|
|
293
|
+
// COALESCE/STRAFTER derived-name fallback or the name-addressable/list-row
|
|
294
|
+
// population split. This tiny store has one NAMED and one UNNAMED standard.
|
|
295
|
+
describe("standard pack on a synthetic graph (derived-name fallback)", () => {
|
|
296
|
+
const CS = TRANSITIONAL_DS_PREFIX_MAP.cs;
|
|
297
|
+
const SYNTHETIC_TTL = `
|
|
298
|
+
@prefix cs: <${CS}> .
|
|
299
|
+
|
|
300
|
+
cs:code.named a cs:CodeStandard ;
|
|
301
|
+
cs:name "code/named" ;
|
|
302
|
+
cs:description "A standard with an explicit cs:name." .
|
|
303
|
+
|
|
304
|
+
<${CS}react.component.unnamed> a cs:CodeStandard ;
|
|
305
|
+
cs:description "A standard without cs:name." .
|
|
306
|
+
`;
|
|
307
|
+
|
|
308
|
+
let syntheticStore: Store;
|
|
309
|
+
let syntheticCleanup: () => void;
|
|
310
|
+
let syntheticRt: PragmaRuntime;
|
|
311
|
+
let synthetic: CompiledPackStories;
|
|
312
|
+
|
|
313
|
+
beforeAll(async () => {
|
|
314
|
+
const result = await createTestStore({ ttl: SYNTHETIC_TTL });
|
|
315
|
+
syntheticStore = result.store;
|
|
316
|
+
syntheticCleanup = result.cleanup;
|
|
317
|
+
syntheticRt = { store: syntheticStore } as PragmaRuntime;
|
|
318
|
+
synthetic = compilePackStories(standardPack, "test:synthetic", {
|
|
319
|
+
...DEFAULT_PREFIX_MAP,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
afterAll(() => syntheticCleanup());
|
|
324
|
+
|
|
325
|
+
it("lists the unnamed standard under its IRI-derived slash-name", async () => {
|
|
326
|
+
const rows = await synthetic.list.resolve(syntheticRt, {});
|
|
327
|
+
expect(rows.map((row) => row.name).sort()).toEqual([
|
|
328
|
+
"code/named",
|
|
329
|
+
"react/component/unnamed",
|
|
330
|
+
]);
|
|
331
|
+
const derived = rows.find((row) => row.name === "react/component/unnamed");
|
|
332
|
+
expect(derived?.uri).toBe(`${CS}react.component.unnamed`);
|
|
333
|
+
expect(derived?.description).toBe("A standard without cs:name.");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("counts only the NAMED standard in the sample population", async () => {
|
|
337
|
+
const sample = synthetic.sample;
|
|
338
|
+
if (!sample) throw new Error("expected a sample story");
|
|
339
|
+
const data = await sample.resolve(syntheticRt, { count: "1" });
|
|
340
|
+
// 2 list rows, but only 1 name-addressable standard (PARITY_GAPS).
|
|
341
|
+
expect(data.totalCount).toBe(1);
|
|
342
|
+
expect(data.samples.map((entity) => entity.name)).toEqual(["code/named"]);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("cannot address the unnamed standard's derived name via lookup", async () => {
|
|
346
|
+
const lookup = synthetic.lookup;
|
|
347
|
+
if (!lookup) throw new Error("expected a lookup story");
|
|
348
|
+
const result = await lookup.resolve(
|
|
349
|
+
syntheticRt,
|
|
350
|
+
["react/component/unnamed"],
|
|
293
351
|
{},
|
|
294
352
|
);
|
|
295
|
-
|
|
296
|
-
expect(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
353
|
+
expect(result.results).toEqual([]);
|
|
354
|
+
expect(result.errors.at(0)?.code).toBe("ENTITY_NOT_FOUND");
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe("standard sample parity", () => {
|
|
359
|
+
it("returns N full exemplars (highest disclosure) with the population size", async () => {
|
|
360
|
+
const sample = compiled.sample;
|
|
361
|
+
if (!sample) throw new Error("expected a sample story");
|
|
362
|
+
const data = await sample.resolve(rt, { count: "2" });
|
|
363
|
+
expect(data.samples).toHaveLength(2);
|
|
364
|
+
// PARITY_GAPS: totalCount counts NAME-ADDRESSABLE standards (the set
|
|
365
|
+
// the sampler can resolve), while the list also carries rows whose
|
|
366
|
+
// name is COALESCE-derived from the IRI — so the list length is only
|
|
367
|
+
// an upper bound. The independent oracle is the shared suggestions
|
|
368
|
+
// name query (cs:name only, different query path, same store).
|
|
369
|
+
const nameAddressable = await listDomainNames(store, "standard");
|
|
370
|
+
expect(nameAddressable.length).toBeGreaterThan(0);
|
|
371
|
+
expect(data.totalCount).toBe(nameAddressable.length);
|
|
372
|
+
const listRows = await compiled.list.resolve(rt, {});
|
|
373
|
+
expect(data.totalCount).toBeLessThanOrEqual(listRows.length);
|
|
374
|
+
for (const entity of data.samples) {
|
|
375
|
+
expect(entity.name).toBeTruthy();
|
|
376
|
+
// Highest level: expands are fetched (arrays, possibly empty).
|
|
377
|
+
expect(Array.isArray(entity.dos)).toBe(true);
|
|
378
|
+
expect(Array.isArray(entity.donts)).toBe(true);
|
|
303
379
|
}
|
|
304
|
-
expect(packError).toEqual(builtinError);
|
|
305
|
-
expect(builtinError.code).toBe("ENTITY_NOT_FOUND");
|
|
306
|
-
expect(builtinError.suggestions).toContain("code/function/purity");
|
|
307
380
|
});
|
|
308
381
|
});
|