@canonical/pragma-cli 0.27.1-experimental.0 → 0.30.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 +67 -15
- package/dist/pragma +0 -0
- package/package.json +16 -17
- package/src/config/defaultConfig.test.ts +33 -0
- package/src/config/defaultConfig.ts +59 -0
- package/src/config/defaults.json +17 -0
- package/src/config/findProjectConfigPath.test.ts +53 -0
- package/src/config/findProjectConfigPath.ts +36 -0
- package/src/config/index.ts +17 -2
- package/src/config/mergeConfigFileUpdate.ts +58 -0
- package/src/config/parseConfigValues.ts +215 -0
- package/src/config/readConfig.test.ts +104 -13
- package/src/config/readConfig.ts +12 -117
- package/src/config/readConfigLayers.test.ts +107 -0
- package/src/config/readConfigLayers.ts +115 -0
- package/src/config/resolveGlobalConfigPath.ts +13 -0
- package/src/config/resolveWriteConfigPath.test.ts +53 -0
- package/src/config/resolveWriteConfigPath.ts +31 -0
- package/src/config/types.ts +88 -2
- package/src/config/writeConfig.ts +25 -43
- package/src/constants.ts +12 -1
- package/src/domains/block/commands/index.ts +2 -1
- package/src/domains/block/commands/list.ts +5 -90
- package/src/domains/block/commands/lookup.ts +8 -167
- package/src/domains/block/commands/sample.ts +55 -0
- package/src/domains/block/formatters/index.ts +2 -1
- package/src/domains/block/formatters/sample.ts +21 -0
- package/src/domains/block/index.ts +3 -3
- package/src/domains/block/mcp/specs.ts +36 -168
- package/src/domains/block/operations/index.ts +2 -1
- package/src/domains/block/operations/lookup.ts +9 -0
- package/src/domains/block/operations/sample.ts +41 -0
- package/src/domains/block/orchestration/blockEmptyError.test.ts +26 -0
- package/src/domains/block/orchestration/resolveBlockLookup.ts +11 -3
- package/src/domains/block/stories.test.ts +28 -0
- package/src/domains/block/stories.ts +254 -0
- package/src/domains/config/commands/channel.test.ts +14 -3
- package/src/domains/config/commands/channel.ts +11 -7
- package/src/domains/config/commands/configScope.ts +46 -0
- package/src/domains/config/commands/framework.test.ts +105 -0
- package/src/domains/config/commands/framework.ts +91 -0
- package/src/domains/config/commands/index.ts +2 -0
- package/src/domains/config/commands/show.ts +5 -45
- package/src/domains/config/commands/tier.test.ts +13 -2
- package/src/domains/config/commands/tier.ts +11 -7
- package/src/domains/config/commands/trace.test.ts +165 -0
- package/src/domains/config/commands/trace.ts +83 -0
- package/src/domains/config/formatters/channel.test.ts +20 -6
- package/src/domains/config/formatters/channel.ts +14 -11
- package/src/domains/config/formatters/framework.ts +37 -0
- package/src/domains/config/formatters/index.ts +2 -0
- package/src/domains/config/formatters/show.test.ts +27 -0
- package/src/domains/config/formatters/show.ts +27 -4
- package/src/domains/config/formatters/tier.test.ts +26 -10
- package/src/domains/config/formatters/tier.ts +19 -11
- package/src/domains/config/formatters/trace.ts +28 -0
- package/src/domains/config/index.ts +15 -2
- package/src/domains/config/mcp/specs.ts +33 -53
- package/src/domains/config/operations/index.ts +1 -0
- package/src/domains/config/operations/show.test.ts +18 -0
- package/src/domains/config/operations/show.ts +7 -1
- package/src/domains/config/operations/types.ts +7 -0
- package/src/domains/config/operations/validateFramework.ts +24 -0
- package/src/domains/config/stories.ts +42 -0
- package/src/domains/config/tasks/index.ts +3 -0
- package/src/domains/config/tasks/setChannel.ts +13 -22
- package/src/domains/config/tasks/setFramework.ts +21 -0
- package/src/domains/config/tasks/setTier.ts +10 -23
- package/src/domains/config/tasks/setTrace.ts +20 -0
- package/src/domains/config/tasks/writeConfigField.ts +41 -0
- package/src/domains/create/commands/component.ts +5 -5
- package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
- package/src/domains/doctor/operations/checks/checkKeStore.ts +5 -5
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
- package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -48
- package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
- package/src/domains/doctor/operations/checks/index.ts +1 -0
- package/src/domains/doctor/operations/checks.test.ts +162 -8
- package/src/domains/doctor/operations/runChecks.test.ts +85 -4
- package/src/domains/doctor/operations/runChecks.ts +50 -12
- package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
- package/src/domains/graph/helpers/resolveUri.ts +28 -21
- package/src/domains/graphql/commands/build.test.ts +196 -0
- package/src/domains/graphql/commands/build.ts +117 -0
- package/src/domains/graphql/commands/check.test.ts +141 -0
- package/src/domains/graphql/commands/check.ts +81 -0
- package/src/domains/graphql/commands/index.ts +5 -0
- package/src/domains/graphql/commands/serve.test.ts +40 -0
- package/src/domains/graphql/commands/serve.ts +131 -0
- package/src/domains/graphql/constants.ts +10 -0
- package/src/domains/graphql/formatters/index.ts +3 -0
- package/src/domains/graphql/formatters/report.ts +78 -0
- package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
- package/src/domains/graphql/helpers/gatherSources.ts +58 -0
- package/src/domains/graphql/helpers/index.ts +6 -0
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +44 -0
- package/src/domains/graphql/helpers/parsePrefixes.ts +38 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +56 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +45 -0
- package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
- package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
- package/src/domains/graphql/index.ts +24 -0
- package/src/domains/graphql/operations/compileSchema.test.ts +95 -0
- package/src/domains/graphql/operations/compileSchema.ts +107 -0
- package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
- package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
- package/src/domains/graphql/operations/index.ts +12 -0
- package/src/domains/graphql/types.ts +29 -0
- package/src/domains/info/operations/collectInfo.test.ts +16 -5
- package/src/domains/info/operations/collectInfo.ts +16 -19
- package/src/domains/llm/capabilities.test.ts +91 -0
- package/src/domains/llm/commands/capabilities.ts +28 -0
- package/src/domains/llm/commands/index.ts +1 -0
- package/src/domains/llm/data/commandReference.ts +90 -18
- package/src/domains/llm/data/decisionTrees.ts +15 -13
- package/src/domains/llm/data/index.ts +2 -0
- package/src/domains/llm/data/toolCatalog.ts +190 -0
- package/src/domains/llm/formatters/capabilities.ts +50 -0
- package/src/domains/llm/formatters/index.ts +1 -0
- package/src/domains/llm/index.ts +3 -2
- package/src/domains/llm/mcp/index.ts +1 -1
- package/src/domains/llm/mcp/specs.ts +59 -52
- package/src/domains/llm/types.ts +40 -0
- package/src/domains/modifier/commands/index.ts +2 -1
- package/src/domains/modifier/commands/list.ts +5 -38
- package/src/domains/modifier/commands/lookup.ts +8 -79
- package/src/domains/modifier/commands/sample.ts +53 -0
- package/src/domains/modifier/formatters/index.ts +2 -1
- package/src/domains/modifier/formatters/sample.ts +15 -0
- package/src/domains/modifier/index.ts +7 -3
- package/src/domains/modifier/mcp/specs.ts +38 -70
- package/src/domains/modifier/operations/index.ts +2 -1
- package/src/domains/modifier/operations/lookup.ts +9 -0
- package/src/domains/modifier/operations/sample.ts +32 -0
- package/src/domains/modifier/orchestration/modifierEmptyError.test.ts +26 -0
- package/src/domains/modifier/orchestration/modifierEmptyError.ts +7 -2
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +15 -2
- package/src/domains/modifier/stories.test.ts +20 -0
- package/src/domains/modifier/stories.ts +77 -0
- package/src/domains/ontology/commands/list.ts +5 -58
- package/src/domains/ontology/commands/show.ts +6 -58
- package/src/domains/ontology/formatters/show.ts +7 -1
- package/src/domains/ontology/mcp/specs.ts +7 -84
- package/src/domains/ontology/stories.test.ts +36 -0
- package/src/domains/ontology/stories.ts +108 -0
- package/src/domains/refs/operations/index.ts +1 -1
- package/src/domains/refs/operations/paths.ts +29 -1
- package/src/domains/refs/operations/updateRefs.test.ts +6 -3
- package/src/domains/refs/operations/updateRefs.ts +2 -36
- package/src/domains/setup/commands/skills.test.ts +6 -2
- package/src/domains/setup/commands/skills.ts +6 -2
- package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
- package/src/domains/setup/helpers/runSetupTask.ts +62 -70
- package/src/domains/setup/operations/setupMcp.test.ts +6 -1
- package/src/domains/shared/bootStore.ts +92 -50
- package/src/domains/shared/compactUri.test.ts +11 -0
- package/src/domains/shared/compactUri.ts +10 -5
- package/src/domains/shared/context.ts +2 -2
- package/src/domains/shared/contracts.ts +12 -0
- package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
- package/src/domains/shared/createInteractivePromptSession.ts +236 -0
- package/src/domains/shared/emptyErrorRecovery.test.ts +109 -0
- package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
- package/src/domains/shared/loaders/bundledLoader.ts +206 -0
- package/src/domains/shared/loaders/gitLoader.ts +31 -0
- package/src/domains/shared/loaders/index.ts +9 -0
- package/src/domains/shared/loaders/localLoader.ts +87 -0
- package/src/domains/shared/loaders/readPackageDir.ts +147 -0
- package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
- package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
- package/src/domains/shared/lookupMany.test.ts +120 -0
- package/src/domains/shared/lookupMany.ts +43 -3
- package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
- package/src/domains/shared/mergeAndParseRefs.ts +44 -0
- package/src/domains/shared/packages.ts +14 -120
- package/src/domains/shared/pickRandom.ts +22 -0
- package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
- package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
- package/src/domains/shared/runtime.ts +7 -63
- package/src/domains/shared/sampleFormatters.ts +47 -0
- package/src/domains/shared/semanticPackage.ts +112 -0
- package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
- package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
- package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
- package/src/domains/shared/stories/compileLookupTool.ts +96 -0
- package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
- package/src/domains/shared/stories/compileReadCommand.ts +48 -0
- package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
- package/src/domains/shared/stories/compileReadTool.ts +38 -0
- package/src/domains/shared/stories/condense.test.ts +18 -0
- package/src/domains/shared/stories/condense.ts +15 -0
- package/src/domains/shared/stories/index.ts +20 -0
- package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
- package/src/domains/shared/stories/normalizeNames.ts +22 -0
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
- package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
- package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
- package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
- package/src/domains/shared/stories/pack/index.ts +28 -0
- package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
- package/src/domains/shared/stories/pack/types.ts +104 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
- package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
- package/src/domains/shared/stories/projectCliParameters.ts +57 -0
- package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
- package/src/domains/shared/stories/projectToolParams.ts +32 -0
- package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
- package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
- package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
- package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
- package/src/domains/shared/stories/types.ts +171 -0
- package/src/domains/shared/suggestions/detectCrossDomain.test.ts +43 -0
- package/src/domains/shared/suggestions/detectCrossDomain.ts +116 -0
- package/src/domains/shared/suggestions/expandGlob.test.ts +92 -0
- package/src/domains/shared/suggestions/expandGlob.ts +52 -0
- package/src/domains/shared/suggestions/expandLookupQueries.ts +57 -0
- package/src/domains/shared/suggestions/index.ts +7 -0
- package/src/domains/shared/suggestions/listDomainNames.ts +61 -0
- package/src/domains/shared/suggestions/suggestNames.test.ts +70 -0
- package/src/domains/shared/suggestions/suggestNames.ts +130 -0
- package/src/domains/shared/types/index.ts +1 -0
- package/src/domains/shared/types/runtime.ts +4 -1
- package/src/domains/shared/types/sample.ts +17 -0
- package/src/domains/skill/commands/index.ts +3 -1
- package/src/domains/skill/commands/list.test.ts +6 -2
- package/src/domains/skill/commands/list.ts +5 -63
- package/src/domains/skill/commands/lookup.test.ts +118 -0
- package/src/domains/skill/commands/lookup.ts +14 -0
- package/src/domains/skill/formatters/index.ts +1 -0
- package/src/domains/skill/formatters/lookup.test.ts +46 -0
- package/src/domains/skill/formatters/lookup.ts +57 -0
- package/src/domains/skill/helpers/resolveSkillSources.ts +48 -16
- package/src/domains/skill/index.ts +7 -3
- package/src/domains/skill/mcp/specs.ts +10 -29
- package/src/domains/skill/operations/discover.test.ts +61 -30
- package/src/domains/skill/operations/discover.ts +28 -37
- package/src/domains/skill/operations/index.ts +2 -1
- package/src/domains/skill/operations/list.test.ts +15 -7
- package/src/domains/skill/operations/lookup.test.ts +91 -0
- package/src/domains/skill/operations/lookup.ts +70 -0
- package/src/domains/skill/stories.test.ts +43 -0
- package/src/domains/skill/stories.ts +103 -0
- package/src/domains/skill/types.ts +8 -0
- package/src/domains/standard/commands/index.ts +2 -1
- package/src/domains/standard/commands/list.ts +5 -84
- package/src/domains/standard/commands/lookup.ts +8 -93
- package/src/domains/standard/commands/sample.ts +57 -0
- package/src/domains/standard/formatters/index.ts +2 -1
- package/src/domains/standard/formatters/sample.ts +15 -0
- package/src/domains/standard/index.ts +8 -1
- package/src/domains/standard/mcp/specs.ts +44 -152
- package/src/domains/standard/operations/index.ts +2 -1
- package/src/domains/standard/operations/lookup.ts +9 -0
- package/src/domains/standard/operations/sample.ts +35 -0
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +15 -2
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +33 -0
- package/src/domains/standard/orchestration/standardEmptyError.ts +16 -6
- package/src/domains/standard/stories.test.ts +24 -0
- package/src/domains/standard/stories.ts +151 -0
- package/src/domains/tier/commands/list.ts +5 -55
- package/src/domains/tier/mcp/specs.ts +5 -31
- package/src/domains/tier/stories.test.ts +22 -0
- package/src/domains/tier/stories.ts +52 -0
- package/src/domains/token/commands/index.ts +2 -1
- package/src/domains/token/commands/sample.ts +53 -0
- package/src/domains/token/formatters/index.ts +2 -1
- package/src/domains/token/formatters/sample.ts +16 -0
- package/src/domains/token/index.ts +8 -1
- package/src/domains/token/mcp/specs.ts +52 -2
- package/src/domains/token/operations/index.ts +2 -1
- package/src/domains/token/operations/lookup.ts +9 -0
- package/src/domains/token/operations/sample.ts +32 -0
- package/src/domains/token/orchestration/resolveTokenLookup.ts +15 -2
- package/src/domains/token/orchestration/tokenEmptyError.test.ts +33 -0
- package/src/domains/token/orchestration/tokenEmptyError.ts +5 -2
- package/src/domains/trace/commands/clear.ts +71 -0
- package/src/domains/trace/commands/index.ts +5 -0
- package/src/domains/trace/commands/sessions.ts +32 -0
- package/src/domains/trace/commands/show.ts +145 -0
- package/src/domains/trace/formatters/index.ts +9 -0
- package/src/domains/trace/formatters/sessions.ts +80 -0
- package/src/domains/trace/formatters/show.ts +122 -0
- package/src/domains/trace/index.ts +17 -0
- package/src/domains/trace/operations/index.ts +8 -0
- package/src/domains/trace/operations/listSessions.test.ts +55 -0
- package/src/domains/trace/operations/listSessions.ts +52 -0
- package/src/domains/trace/operations/readTraceLog.test.ts +100 -0
- package/src/domains/trace/operations/readTraceLog.ts +104 -0
- package/src/domains/trace/tracePlugin.test.ts +171 -0
- package/src/domains/trace/tracePlugin.ts +159 -0
- package/src/domains/trace/types.ts +65 -0
- package/src/error/PragmaError.ts +15 -2
- package/src/error/types.ts +15 -1
- package/src/mcp/error/serializeErrorPayload.ts +7 -0
- package/src/mcp/error/types.ts +8 -0
- package/src/mcp/registerResources.test.ts +405 -75
- package/src/mcp/registerResources.ts +5 -299
- package/src/mcp/resources/buildGraphIndex.ts +167 -0
- package/src/mcp/resources/buildResourceList.test.ts +97 -0
- package/src/mcp/resources/buildResourceList.ts +176 -0
- package/src/mcp/resources/classifyEntity.test.ts +40 -0
- package/src/mcp/resources/classifyEntity.ts +27 -0
- package/src/mcp/resources/constants.ts +105 -0
- package/src/mcp/resources/displayPredicates.ts +62 -0
- package/src/mcp/resources/pickFirstValue.test.ts +29 -0
- package/src/mcp/resources/pickFirstValue.ts +26 -0
- package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
- package/src/mcp/resources/pickPrimaryType.ts +26 -0
- package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
- package/src/mcp/resources/rankUriCompletions.ts +64 -0
- package/src/mcp/resources/readEntity.ts +169 -0
- package/src/mcp/resources/registerResources.ts +126 -0
- package/src/mcp/resources/types.ts +128 -0
- package/src/mcp/tools/index.ts +9 -0
- package/src/mcp/tools/registerTools.test.ts +84 -12
- package/src/mcp/types.ts +4 -0
- package/src/mcp/utils/wrapTool.test.ts +1 -0
- package/src/pipeline/collectCommands.ts +15 -2
- package/src/pipeline/renderError.ts +15 -0
- package/src/pipeline/resolveCommandKind.ts +8 -1
- package/src/pipeline/runCli.ts +51 -6
- package/src/testing/createTestMcpClient.ts +5 -1
- package/src/testing/graphqlFixtures.ts +72 -0
- package/src/testing/index.ts +17 -0
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
- package/src/testing/integration/executor-execution.test.ts +101 -0
- package/src/testing/integration/executor-golden.test.ts +145 -0
- package/src/testing/integration/mcp-surface.test.ts +11 -5
- package/src/testing/integration/parity.test.ts +17 -12
- package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
- package/src/testing/setupXdgIsolation.ts +15 -0
- package/src/testing/standardParityFixtures.ts +92 -0
- package/src/testing/storyFixtures.ts +63 -0
- package/src/pipeline/runInteractiveCommand.ts +0 -198
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import suggestNames from "./suggestNames.js";
|
|
3
|
+
|
|
4
|
+
describe("suggestNames", () => {
|
|
5
|
+
it("T1-1: suggests edit-distance match for single-char typo", () => {
|
|
6
|
+
expect(suggestNames("Buton", ["Button", "Card", "Input"])).toEqual([
|
|
7
|
+
"Button",
|
|
8
|
+
]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("T1-2: suggests dot-separated names with typo in last segment", () => {
|
|
12
|
+
const candidates = [
|
|
13
|
+
"react.component.props",
|
|
14
|
+
"react.component.props.wrapper",
|
|
15
|
+
"react.component.props.html_rendering",
|
|
16
|
+
"react.component.naming",
|
|
17
|
+
"css.selectors.namespace",
|
|
18
|
+
];
|
|
19
|
+
const result = suggestNames("react.component.prosp", candidates);
|
|
20
|
+
expect(result.at(0)).toBe("react.component.props");
|
|
21
|
+
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("T1-3: matches case-insensitively, preserves original casing", () => {
|
|
25
|
+
expect(
|
|
26
|
+
suggestNames("buton-background", ["Button-Background", "color-primary"]),
|
|
27
|
+
).toEqual(["Button-Background"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("T1-4: returns prefix matches ranked first", () => {
|
|
31
|
+
const result = suggestNames("Accordion", [
|
|
32
|
+
"AccordionItem",
|
|
33
|
+
"AccordionGroup",
|
|
34
|
+
"Button",
|
|
35
|
+
]);
|
|
36
|
+
expect(result).toHaveLength(2);
|
|
37
|
+
expect(result).toContain("AccordionItem");
|
|
38
|
+
expect(result).toContain("AccordionGroup");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("T1-5: returns empty for completely unrelated input", () => {
|
|
42
|
+
expect(suggestNames("zzzzz", ["Button", "Card", "Input"])).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("T1-6: returns empty for empty query", () => {
|
|
46
|
+
expect(suggestNames("", ["Button"])).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("T1-7: excludes exact matches", () => {
|
|
50
|
+
expect(suggestNames("Button", ["Button", "Card"])).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("T1-8: ranks multiple candidates by distance", () => {
|
|
54
|
+
const result = suggestNames("Buttn", ["Button", "Butter", "Bitten"]);
|
|
55
|
+
expect(result).toContain("Button");
|
|
56
|
+
expect(result).toContain("Butter");
|
|
57
|
+
expect(result.indexOf("Button")).toBeLessThan(result.indexOf("Butter"));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("respects maxResults option", () => {
|
|
61
|
+
const candidates = Array.from({ length: 20 }, (_, i) => `Item${i}`);
|
|
62
|
+
const result = suggestNames("Item", candidates, { maxResults: 3 });
|
|
63
|
+
expect(result.length).toBeLessThanOrEqual(3);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("respects threshold option", () => {
|
|
67
|
+
const result = suggestNames("Buton", ["Button"], { threshold: 0.05 });
|
|
68
|
+
expect(result).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rank candidates by similarity to a failed lookup query.
|
|
3
|
+
*
|
|
4
|
+
* Returns up to {@link maxResults} names sorted by relevance:
|
|
5
|
+
* 1. Prefix matches (query is a case-insensitive prefix of candidate)
|
|
6
|
+
* 2. Edit-distance matches (Damerau-Levenshtein, normalized ≤ threshold)
|
|
7
|
+
*
|
|
8
|
+
* Exact matches are excluded — if the query exactly matches a candidate,
|
|
9
|
+
* the caller should have found it and never reached this function.
|
|
10
|
+
*
|
|
11
|
+
* @param query - The user-supplied name that failed lookup.
|
|
12
|
+
* @param candidates - All known names in the domain.
|
|
13
|
+
* @param opts.maxResults - Max suggestions to return (default: 5).
|
|
14
|
+
* @param opts.threshold - Max normalized edit distance (default: 0.4).
|
|
15
|
+
* @returns Ranked suggestions with original casing preserved.
|
|
16
|
+
*/
|
|
17
|
+
export default function suggestNames(
|
|
18
|
+
query: string,
|
|
19
|
+
candidates: readonly string[],
|
|
20
|
+
opts?: { maxResults?: number; threshold?: number },
|
|
21
|
+
): string[] {
|
|
22
|
+
if (query === "") return [];
|
|
23
|
+
|
|
24
|
+
const maxResults = opts?.maxResults ?? 5;
|
|
25
|
+
const threshold = opts?.threshold ?? 0.4;
|
|
26
|
+
const queryLower = query.toLowerCase();
|
|
27
|
+
|
|
28
|
+
type Scored = { name: string; score: number };
|
|
29
|
+
const scored: Scored[] = [];
|
|
30
|
+
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
const candidateLower = candidate.toLowerCase();
|
|
33
|
+
|
|
34
|
+
if (candidateLower === queryLower) continue;
|
|
35
|
+
|
|
36
|
+
if (candidateLower.startsWith(queryLower)) {
|
|
37
|
+
scored.push({ name: candidate, score: 0 });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const distance = damerauLevenshtein(queryLower, candidateLower);
|
|
42
|
+
const maxLen = Math.max(queryLower.length, candidateLower.length);
|
|
43
|
+
const normalized = maxLen === 0 ? 0 : distance / maxLen;
|
|
44
|
+
|
|
45
|
+
if (normalized <= threshold) {
|
|
46
|
+
scored.push({ name: candidate, score: normalized });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
scored.sort((a, b) => a.score - b.score);
|
|
51
|
+
return scored.slice(0, maxResults).map((s) => s.name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Damerau-Levenshtein distance between two strings.
|
|
56
|
+
*
|
|
57
|
+
* Counts insertions, deletions, substitutions, and adjacent transpositions.
|
|
58
|
+
*/
|
|
59
|
+
function damerauLevenshtein(a: string, b: string): number {
|
|
60
|
+
const lenA = a.length;
|
|
61
|
+
const lenB = b.length;
|
|
62
|
+
|
|
63
|
+
if (lenA === 0) return lenB;
|
|
64
|
+
if (lenB === 0) return lenA;
|
|
65
|
+
|
|
66
|
+
// Fully allocated (lenA + 1) × (lenB + 1) grid: every row/column index in
|
|
67
|
+
// [0, lenA] × [0, lenB] below is provably in bounds, so `row.at(j)` never
|
|
68
|
+
// yields undefined for the indices we read.
|
|
69
|
+
const matrix: number[][] = Array.from({ length: lenA + 1 }, () =>
|
|
70
|
+
Array.from<number>({ length: lenB + 1 }).fill(0),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i <= lenA; i++) {
|
|
74
|
+
getRow(matrix, i)[0] = i;
|
|
75
|
+
}
|
|
76
|
+
const firstRow = getRow(matrix, 0);
|
|
77
|
+
for (let j = 0; j <= lenB; j++) {
|
|
78
|
+
firstRow[j] = j;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (let i = 1; i <= lenA; i++) {
|
|
82
|
+
const prevRow = getRow(matrix, i - 1);
|
|
83
|
+
const row = getRow(matrix, i);
|
|
84
|
+
for (let j = 1; j <= lenB; j++) {
|
|
85
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
86
|
+
|
|
87
|
+
row[j] = Math.min(
|
|
88
|
+
getCell(prevRow, j) + 1, // deletion
|
|
89
|
+
getCell(row, j - 1) + 1, // insertion
|
|
90
|
+
getCell(prevRow, j - 1) + cost, // substitution
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
94
|
+
const prevPrevRow = getRow(matrix, i - 2);
|
|
95
|
+
row[j] = Math.min(
|
|
96
|
+
getCell(row, j),
|
|
97
|
+
getCell(prevPrevRow, j - 2) + cost, // transposition
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return getCell(getRow(matrix, lenA), lenB);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Read a matrix row that is structurally guaranteed to exist.
|
|
108
|
+
*
|
|
109
|
+
* @throws if the row is missing, signalling a broken allocation invariant.
|
|
110
|
+
*/
|
|
111
|
+
function getRow(matrix: number[][], index: number): number[] {
|
|
112
|
+
const row = matrix.at(index);
|
|
113
|
+
if (row === undefined) {
|
|
114
|
+
throw new Error(`matrix row ${index} is out of bounds`);
|
|
115
|
+
}
|
|
116
|
+
return row;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Read a matrix cell that is structurally guaranteed to exist.
|
|
121
|
+
*
|
|
122
|
+
* @throws if the cell is missing, signalling a broken allocation invariant.
|
|
123
|
+
*/
|
|
124
|
+
function getCell(row: number[], index: number): number {
|
|
125
|
+
const cell = row.at(index);
|
|
126
|
+
if (cell === undefined) {
|
|
127
|
+
throw new Error(`matrix cell ${index} is out of bounds`);
|
|
128
|
+
}
|
|
129
|
+
return cell;
|
|
130
|
+
}
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Store } from "@canonical/ke";
|
|
4
4
|
import type { PragmaConfig } from "#config";
|
|
5
|
+
import type { SemanticPackage } from "../semanticPackage.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Boot-time state shared by CLI, MCP, and completions server.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* Five members:
|
|
10
11
|
* - `store` — the ke triple store, loaded and ready for SPARQL queries.
|
|
11
12
|
* - `config` — resolved pragma config (tier, channel).
|
|
12
13
|
* - `cwd` — working directory used for config and source resolution.
|
|
14
|
+
* - `packages` — resolved semantic packages (for diagnostics and skills).
|
|
13
15
|
* - `dispose()` — tears down the store and frees WASM memory. Must be
|
|
14
16
|
* called exactly once; calling operations after dispose is undefined.
|
|
15
17
|
*/
|
|
@@ -17,5 +19,6 @@ export interface PragmaRuntime {
|
|
|
17
19
|
readonly store: Store;
|
|
18
20
|
readonly config: PragmaConfig;
|
|
19
21
|
readonly cwd: string;
|
|
22
|
+
readonly packages: readonly SemanticPackage[];
|
|
20
23
|
dispose(): void;
|
|
21
24
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for sample operations across domains.
|
|
3
|
+
*
|
|
4
|
+
* Sample returns 1–5 complete instances as exemplars so agents
|
|
5
|
+
* can learn actual data shapes before writing queries.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Raw result from a sample operation — instances plus total count. */
|
|
9
|
+
export interface SampleResult<T> {
|
|
10
|
+
readonly samples: readonly T[];
|
|
11
|
+
readonly totalCount: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Full sample output including agent guidance. */
|
|
15
|
+
export interface SampleOutput<T> extends SampleResult<T> {
|
|
16
|
+
readonly nextSteps: readonly string[];
|
|
17
|
+
}
|
|
@@ -9,9 +9,13 @@ import buildListCommand from "./list.js";
|
|
|
9
9
|
|
|
10
10
|
const TMP_ROOT = join(tmpdir(), `pragma-skill-cmd-${Date.now()}`);
|
|
11
11
|
|
|
12
|
+
/** One source per skill directory, matching the loader granularity. */
|
|
12
13
|
const TEST_SOURCES = [
|
|
13
14
|
{
|
|
14
|
-
dir: join(
|
|
15
|
+
dir: join(
|
|
16
|
+
TMP_ROOT,
|
|
17
|
+
"node_modules/@canonical/design-system/skills/design-audit",
|
|
18
|
+
),
|
|
15
19
|
packageName: "@canonical/design-system",
|
|
16
20
|
},
|
|
17
21
|
];
|
|
@@ -23,7 +27,7 @@ vi.mock("../helpers/resolveSkillSources.js", () => ({
|
|
|
23
27
|
beforeAll(() => {
|
|
24
28
|
mkdirSync(TMP_ROOT, { recursive: true });
|
|
25
29
|
|
|
26
|
-
const dir =
|
|
30
|
+
const dir = TEST_SOURCES.at(0)?.dir ?? "";
|
|
27
31
|
mkdirSync(dir, { recursive: true });
|
|
28
32
|
writeFileSync(
|
|
29
33
|
join(dir, "SKILL.md"),
|
|
@@ -1,69 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type CommandDefinition,
|
|
3
|
-
type CommandResult,
|
|
4
|
-
createOutputResult,
|
|
5
|
-
} from "@canonical/cli-core";
|
|
6
|
-
import { PragmaError } from "#error";
|
|
1
|
+
import type { CommandDefinition } from "@canonical/cli-core";
|
|
7
2
|
import type { PragmaContext } from "../../shared/context.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import type { SkillListInput } from "../formatters/types.js";
|
|
11
|
-
import { listSkills } from "../operations/index.js";
|
|
3
|
+
import { compileReadCommand } from "../../shared/stories/index.js";
|
|
4
|
+
import { skillListStory } from "../stories.js";
|
|
12
5
|
|
|
13
|
-
/**
|
|
14
|
-
* Build the `pragma skill list` command definition.
|
|
15
|
-
*
|
|
16
|
-
* Discovers skills from installed packages and formats the result.
|
|
17
|
-
* Throws when no skills are found, with a recovery hint.
|
|
18
|
-
*
|
|
19
|
-
* @param ctx - Pragma context providing cwd and formatter selection.
|
|
20
|
-
* @returns The command definition for `pragma skill list`.
|
|
21
|
-
* @note Impure
|
|
22
|
-
*/
|
|
6
|
+
/** Wire the `pragma skill list` CLI command from the skill list story. */
|
|
23
7
|
export default function buildListCommand(
|
|
24
8
|
ctx: PragmaContext,
|
|
25
9
|
): CommandDefinition {
|
|
26
|
-
return
|
|
27
|
-
path: ["skill", "list"],
|
|
28
|
-
description: "List available agent skills from design system packages",
|
|
29
|
-
parameters: [
|
|
30
|
-
{
|
|
31
|
-
name: "detailed",
|
|
32
|
-
description: "Show full metadata for each skill",
|
|
33
|
-
type: "boolean",
|
|
34
|
-
default: false,
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
meta: {
|
|
38
|
-
examples: [
|
|
39
|
-
"pragma skill list",
|
|
40
|
-
"pragma skill list --detailed",
|
|
41
|
-
"pragma skill list --llm",
|
|
42
|
-
"pragma skill list --format json",
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
execute: async (
|
|
46
|
-
params: Record<string, unknown>,
|
|
47
|
-
): Promise<CommandResult> => {
|
|
48
|
-
const detailed = params.detailed === true;
|
|
49
|
-
const { skills, sources } = await listSkills(ctx.cwd);
|
|
50
|
-
|
|
51
|
-
if (skills.length === 0) {
|
|
52
|
-
const allUnavailable = sources.every((s) => !s.available);
|
|
53
|
-
throw PragmaError.emptyResults("skill", {
|
|
54
|
-
recovery: {
|
|
55
|
-
message: allUnavailable
|
|
56
|
-
? "Install @canonical packages first"
|
|
57
|
-
: "No SKILL.md files found in source packages",
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const input: SkillListInput = { skills, sources, detailed };
|
|
63
|
-
|
|
64
|
-
return createOutputResult(input, {
|
|
65
|
-
plain: selectFormatter(ctx, listFormatters),
|
|
66
|
-
});
|
|
67
|
-
},
|
|
68
|
-
};
|
|
10
|
+
return compileReadCommand(ctx, skillListStory);
|
|
69
11
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { CommandDefinition } from "@canonical/cli-core";
|
|
5
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { PragmaError } from "#error";
|
|
7
|
+
import type { PragmaContext } from "../../shared/context.js";
|
|
8
|
+
import buildLookupCommand from "./lookup.js";
|
|
9
|
+
|
|
10
|
+
const TMP_ROOT = join(tmpdir(), `pragma-skill-lookup-cmd-${Date.now()}`);
|
|
11
|
+
|
|
12
|
+
/** One source per skill directory, matching the loader granularity. */
|
|
13
|
+
const TEST_SOURCES = [
|
|
14
|
+
{
|
|
15
|
+
dir: join(
|
|
16
|
+
TMP_ROOT,
|
|
17
|
+
"node_modules/@canonical/design-system/skills/design-auditor",
|
|
18
|
+
),
|
|
19
|
+
packageName: "@canonical/design-system",
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
vi.mock("../helpers/resolveSkillSources.js", () => ({
|
|
24
|
+
default: () => TEST_SOURCES,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
beforeAll(() => {
|
|
28
|
+
const dir = TEST_SOURCES.at(0)?.dir ?? "";
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
writeFileSync(
|
|
31
|
+
join(dir, "SKILL.md"),
|
|
32
|
+
`---
|
|
33
|
+
name: design-auditor
|
|
34
|
+
description: Audit design system coverage
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Design Auditor
|
|
38
|
+
`,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterAll(() => {
|
|
43
|
+
rmSync(TMP_ROOT, { recursive: true, force: true });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function makeCtx(overrides: Partial<PragmaContext> = {}): PragmaContext {
|
|
47
|
+
return {
|
|
48
|
+
cwd: TMP_ROOT,
|
|
49
|
+
globalFlags: { llm: false, format: "text" as const, verbose: false },
|
|
50
|
+
store: {} as PragmaContext["store"],
|
|
51
|
+
config: { tier: undefined, channel: "normal" },
|
|
52
|
+
...overrides,
|
|
53
|
+
} as PragmaContext;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function executeText(
|
|
57
|
+
cmd: CommandDefinition,
|
|
58
|
+
params: Record<string, unknown>,
|
|
59
|
+
ctx: PragmaContext,
|
|
60
|
+
): Promise<string> {
|
|
61
|
+
const result = await cmd.execute(params, ctx);
|
|
62
|
+
expect(result.tag).toBe("output");
|
|
63
|
+
if (result.tag !== "output") throw new Error("Expected output result");
|
|
64
|
+
return result.render.plain(result.value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("buildLookupCommand", () => {
|
|
68
|
+
it("serves the full SKILL.md content", async () => {
|
|
69
|
+
const ctx = makeCtx();
|
|
70
|
+
const text = await executeText(
|
|
71
|
+
buildLookupCommand(ctx),
|
|
72
|
+
{ names: ["design-auditor"] },
|
|
73
|
+
ctx,
|
|
74
|
+
);
|
|
75
|
+
expect(text).toContain("design-auditor");
|
|
76
|
+
expect(text).toContain("# Design Auditor");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("renders JSON with content under --format json", async () => {
|
|
80
|
+
const ctx = makeCtx({
|
|
81
|
+
globalFlags: { llm: false, format: "json", verbose: false },
|
|
82
|
+
});
|
|
83
|
+
const text = await executeText(
|
|
84
|
+
buildLookupCommand(ctx),
|
|
85
|
+
{ names: ["design-auditor"] },
|
|
86
|
+
ctx,
|
|
87
|
+
);
|
|
88
|
+
const data = JSON.parse(text) as { name: string; content: string };
|
|
89
|
+
expect(data.name).toBe("design-auditor");
|
|
90
|
+
expect(data.content).toContain("# Design Auditor");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("reports unknown skills in the errors section", async () => {
|
|
94
|
+
const ctx = makeCtx();
|
|
95
|
+
const text = await executeText(
|
|
96
|
+
buildLookupCommand(ctx),
|
|
97
|
+
{ names: ["nope"] },
|
|
98
|
+
ctx,
|
|
99
|
+
);
|
|
100
|
+
expect(text).toContain("Errors:");
|
|
101
|
+
expect(text).toContain('skill "nope" not found.');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("rejects an empty names list with a recovery hint", async () => {
|
|
105
|
+
const ctx = makeCtx();
|
|
106
|
+
const cmd = buildLookupCommand(ctx);
|
|
107
|
+
await expect(cmd.execute({ names: [] }, ctx)).rejects.toThrow(PragmaError);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("completes skill names from discovery", async () => {
|
|
111
|
+
const ctx = makeCtx();
|
|
112
|
+
const cmd = buildLookupCommand(ctx);
|
|
113
|
+
const names = cmd.parameters.at(0);
|
|
114
|
+
await expect(names?.complete?.("des", ctx)).resolves.toEqual([
|
|
115
|
+
"design-auditor",
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CommandDefinition } from "@canonical/cli-core";
|
|
2
|
+
import type { PragmaContext } from "../../shared/context.js";
|
|
3
|
+
import { compileLookupCommand } from "../../shared/stories/index.js";
|
|
4
|
+
import { skillLookupStory } from "../stories.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wire the `pragma skill lookup <name...>` CLI command from the skill
|
|
8
|
+
* lookup story.
|
|
9
|
+
*/
|
|
10
|
+
export default function buildLookupCommand(
|
|
11
|
+
ctx: PragmaContext,
|
|
12
|
+
): CommandDefinition {
|
|
13
|
+
return compileLookupCommand(ctx, skillLookupStory);
|
|
14
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { SkillDetailed } from "../types.js";
|
|
3
|
+
import formatters from "./lookup.js";
|
|
4
|
+
|
|
5
|
+
const skill: SkillDetailed = {
|
|
6
|
+
name: "design-auditor",
|
|
7
|
+
description: "Audit design system coverage",
|
|
8
|
+
sourcePath: "/pkgs/design-system/skills/design-auditor",
|
|
9
|
+
sourcePackage: "@canonical/design-system",
|
|
10
|
+
folderName: "design-auditor",
|
|
11
|
+
frontmatter: {
|
|
12
|
+
name: "design-auditor",
|
|
13
|
+
description: "Audit design system coverage",
|
|
14
|
+
},
|
|
15
|
+
content: "---\nname: design-auditor\n---\n\n# Design Auditor\n",
|
|
16
|
+
files: ["AUDIT_SPEC.md"],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe("skill lookup formatters", () => {
|
|
20
|
+
it("plain renders a header and the full content", () => {
|
|
21
|
+
const text = formatters.plain(skill);
|
|
22
|
+
expect(text).toContain("design-auditor");
|
|
23
|
+
expect(text).toContain("@canonical/design-system");
|
|
24
|
+
expect(text).toContain("Companion files: AUDIT_SPEC.md");
|
|
25
|
+
expect(text).toContain("# Design Auditor");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("llm renders a markdown header above the content", () => {
|
|
29
|
+
const text = formatters.llm(skill);
|
|
30
|
+
expect(text).toContain("## Skill: design-auditor");
|
|
31
|
+
expect(text).toContain("**Companion files:** AUDIT_SPEC.md");
|
|
32
|
+
expect(text).toContain("# Design Auditor");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("llm omits the companion files line when there are none", () => {
|
|
36
|
+
const text = formatters.llm({ ...skill, files: [] });
|
|
37
|
+
expect(text).not.toContain("Companion files");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("json serializes the skill with content and files", () => {
|
|
41
|
+
const data = JSON.parse(formatters.json(skill)) as Record<string, unknown>;
|
|
42
|
+
expect(data.name).toBe("design-auditor");
|
|
43
|
+
expect(data.files).toEqual(["AUDIT_SPEC.md"]);
|
|
44
|
+
expect(data.content).toContain("# Design Auditor");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { Formatters } from "../../shared/formatters.js";
|
|
3
|
+
import type { SkillDetailed } from "../types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Formatters for `pragma skill lookup` output.
|
|
7
|
+
*
|
|
8
|
+
* - **plain** renders a chalk-styled header (name, source, files) above
|
|
9
|
+
* the full SKILL.md content.
|
|
10
|
+
* - **llm** renders a markdown header above the content — the skill body
|
|
11
|
+
* is already markdown, so it is served verbatim.
|
|
12
|
+
* - **json** serializes the skill with its content and companion files.
|
|
13
|
+
*/
|
|
14
|
+
const formatters: Formatters<SkillDetailed> = {
|
|
15
|
+
plain(skill) {
|
|
16
|
+
const lines: string[] = [
|
|
17
|
+
chalk.bold(skill.name),
|
|
18
|
+
chalk.dim(skill.description),
|
|
19
|
+
chalk.dim(`Source: ${skill.sourcePackage} (${skill.sourcePath})`),
|
|
20
|
+
];
|
|
21
|
+
if (skill.files.length > 0) {
|
|
22
|
+
lines.push(chalk.dim(`Companion files: ${skill.files.join(", ")}`));
|
|
23
|
+
}
|
|
24
|
+
lines.push("", skill.content);
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
llm(skill) {
|
|
29
|
+
const lines: string[] = [
|
|
30
|
+
`## Skill: ${skill.name}`,
|
|
31
|
+
"",
|
|
32
|
+
`**Source:** ${skill.sourcePackage} (${skill.folderName})`,
|
|
33
|
+
];
|
|
34
|
+
if (skill.files.length > 0) {
|
|
35
|
+
lines.push(`**Companion files:** ${skill.files.join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
lines.push("", skill.content);
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
json(skill) {
|
|
42
|
+
return JSON.stringify(
|
|
43
|
+
{
|
|
44
|
+
name: skill.name,
|
|
45
|
+
description: skill.description,
|
|
46
|
+
source: skill.sourcePackage,
|
|
47
|
+
path: skill.sourcePath,
|
|
48
|
+
files: skill.files,
|
|
49
|
+
content: skill.content,
|
|
50
|
+
},
|
|
51
|
+
null,
|
|
52
|
+
2,
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default formatters;
|