@intentius/chant 0.41.19 → 0.42.1

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.
Files changed (150) hide show
  1. package/dist/cli/build-params-cli.d.ts +7 -4
  2. package/dist/cli/build-params-cli.d.ts.map +1 -1
  3. package/dist/cli/commands/build.d.ts.map +1 -1
  4. package/dist/cli/commands/carve-apply.d.ts +9 -0
  5. package/dist/cli/commands/carve-apply.d.ts.map +1 -1
  6. package/dist/cli/commands/carve-bridge.d.ts +7 -0
  7. package/dist/cli/commands/carve-bridge.d.ts.map +1 -1
  8. package/dist/cli/commands/carve-emit.d.ts +7 -0
  9. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  10. package/dist/cli/commands/import.d.ts +16 -0
  11. package/dist/cli/commands/import.d.ts.map +1 -1
  12. package/dist/cli/commands/init.d.ts +2 -0
  13. package/dist/cli/commands/init.d.ts.map +1 -1
  14. package/dist/cli/handlers/carve-apply.d.ts.map +1 -1
  15. package/dist/cli/handlers/components.d.ts.map +1 -1
  16. package/dist/cli/handlers/explain.d.ts +11 -0
  17. package/dist/cli/handlers/explain.d.ts.map +1 -0
  18. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  19. package/dist/cli/handlers/misc.d.ts.map +1 -1
  20. package/dist/cli/main.d.ts.map +1 -1
  21. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  22. package/dist/cli/registry.d.ts +4 -0
  23. package/dist/cli/registry.d.ts.map +1 -1
  24. package/dist/codegen/docs-rule-scanning.d.ts +11 -0
  25. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
  26. package/dist/codegen/okf-lexicon.d.ts +36 -0
  27. package/dist/codegen/okf-lexicon.d.ts.map +1 -0
  28. package/dist/codegen/package.d.ts +2 -1
  29. package/dist/codegen/package.d.ts.map +1 -1
  30. package/dist/components/deploy-units.d.ts.map +1 -1
  31. package/dist/components/verbs/sbom.d.ts +1 -1
  32. package/dist/components/verbs/sbom.d.ts.map +1 -1
  33. package/dist/components/verbs/vuln-gate.d.ts +15 -4
  34. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  35. package/dist/components/verbs/vuln-scan.d.ts +18 -6
  36. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  37. package/dist/config.d.ts +12 -0
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/deep-observation.d.ts +13 -0
  40. package/dist/deep-observation.d.ts.map +1 -1
  41. package/dist/governance.d.ts +37 -0
  42. package/dist/governance.d.ts.map +1 -0
  43. package/dist/lexicon.d.ts +3 -0
  44. package/dist/lexicon.d.ts.map +1 -1
  45. package/dist/lifecycle/observe.d.ts.map +1 -1
  46. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  47. package/dist/okf.d.ts +57 -0
  48. package/dist/okf.d.ts.map +1 -0
  49. package/dist/reconcile.d.ts +15 -0
  50. package/dist/reconcile.d.ts.map +1 -1
  51. package/dist/terraform/__fixtures__/build-graph.d.ts +13 -0
  52. package/dist/terraform/__fixtures__/build-graph.d.ts.map +1 -0
  53. package/dist/terraform/adopt-state.d.ts +33 -1
  54. package/dist/terraform/adopt-state.d.ts.map +1 -1
  55. package/dist/terraform/bridge.d.ts +7 -1
  56. package/dist/terraform/bridge.d.ts.map +1 -1
  57. package/dist/terraform/carve.d.ts +12 -0
  58. package/dist/terraform/carve.d.ts.map +1 -1
  59. package/dist/terraform/excise.d.ts +31 -0
  60. package/dist/terraform/excise.d.ts.map +1 -0
  61. package/dist/terraform/graduate.d.ts +12 -0
  62. package/dist/terraform/graduate.d.ts.map +1 -1
  63. package/dist/terraform/graph.d.ts +35 -5
  64. package/dist/terraform/graph.d.ts.map +1 -1
  65. package/dist/terraform/manifest.d.ts +89 -0
  66. package/dist/terraform/manifest.d.ts.map +1 -0
  67. package/dist/terraform/parse.d.ts +9 -4
  68. package/dist/terraform/parse.d.ts.map +1 -1
  69. package/dist/terraform/types.d.ts +7 -0
  70. package/dist/terraform/types.d.ts.map +1 -1
  71. package/dist/terraform/unified-diff.d.ts +11 -0
  72. package/dist/terraform/unified-diff.d.ts.map +1 -0
  73. package/package.json +1 -1
  74. package/src/__snapshots__/okf.test.ts.snap +381 -0
  75. package/src/cli/build-params-cli.test.ts +47 -3
  76. package/src/cli/build-params-cli.ts +22 -5
  77. package/src/cli/commands/build.test.ts +44 -0
  78. package/src/cli/commands/build.ts +15 -2
  79. package/src/cli/commands/carve-apply.test.ts +94 -2
  80. package/src/cli/commands/carve-apply.ts +62 -11
  81. package/src/cli/commands/carve-bridge.test.ts +82 -4
  82. package/src/cli/commands/carve-bridge.ts +79 -11
  83. package/src/cli/commands/carve-emit-state.test.ts +110 -1
  84. package/src/cli/commands/carve-emit.ts +184 -8
  85. package/src/cli/commands/import.ts +53 -5
  86. package/src/cli/commands/init.ts +1 -1
  87. package/src/cli/handlers/build.test.ts +3 -1
  88. package/src/cli/handlers/carve-apply.ts +1 -0
  89. package/src/cli/handlers/components.ts +4 -2
  90. package/src/cli/handlers/explain.test.ts +93 -0
  91. package/src/cli/handlers/explain.ts +60 -0
  92. package/src/cli/handlers/lifecycle.ts +9 -7
  93. package/src/cli/handlers/misc.ts +40 -1
  94. package/src/cli/handlers/run.test.ts +3 -1
  95. package/src/cli/main.ts +25 -5
  96. package/src/cli/mcp/server.test.ts +17 -0
  97. package/src/cli/mcp/tools/explain.ts +15 -3
  98. package/src/cli/registry.ts +4 -0
  99. package/src/codegen/__snapshots__/okf-lexicon.test.ts.snap +170 -0
  100. package/src/codegen/docs-rule-scanning.ts +35 -14
  101. package/src/codegen/okf-lexicon.test.ts +248 -0
  102. package/src/codegen/okf-lexicon.ts +303 -0
  103. package/src/codegen/package.ts +29 -4
  104. package/src/codegen/publish-order.test.ts +1 -1
  105. package/src/codegen/release-wiring.test.ts +7 -3
  106. package/src/components/config-defaults.test.ts +30 -0
  107. package/src/components/deploy-units.ts +3 -0
  108. package/src/components/verbs/__fixtures__/grype-with-kev-epss.json +1 -0
  109. package/src/components/verbs/__fixtures__/trivy-with-kev-epss.json +631 -0
  110. package/src/components/verbs/exploitability-roundtrip.test.ts +190 -0
  111. package/src/components/verbs/sbom.ts +1 -1
  112. package/src/components/verbs/vuln-gate.test.ts +144 -1
  113. package/src/components/verbs/vuln-gate.ts +61 -10
  114. package/src/components/verbs/vuln-scan.test.ts +94 -0
  115. package/src/components/verbs/vuln-scan.ts +44 -9
  116. package/src/config.test.ts +21 -0
  117. package/src/config.ts +18 -0
  118. package/src/deep-observation.test.ts +19 -0
  119. package/src/deep-observation.ts +32 -13
  120. package/src/governance.test.ts +72 -0
  121. package/src/governance.ts +54 -0
  122. package/src/lexicon.ts +3 -0
  123. package/src/lifecycle/observe.test.ts +18 -0
  124. package/src/lifecycle/observe.ts +10 -3
  125. package/src/lifecycle/snapshot.ts +5 -4
  126. package/src/okf.test.ts +169 -0
  127. package/src/okf.ts +308 -0
  128. package/src/reconcile.ts +32 -1
  129. package/src/terraform/__fixtures__/build-graph.ts +25 -0
  130. package/src/terraform/adopt-state.test.ts +34 -1
  131. package/src/terraform/adopt-state.ts +69 -2
  132. package/src/terraform/bridge.test.ts +38 -14
  133. package/src/terraform/bridge.ts +48 -14
  134. package/src/terraform/carve.test.ts +9 -7
  135. package/src/terraform/carve.ts +0 -0
  136. package/src/terraform/excise.test.ts +92 -0
  137. package/src/terraform/excise.ts +139 -0
  138. package/src/terraform/graduate.test.ts +73 -6
  139. package/src/terraform/graduate.ts +49 -0
  140. package/src/terraform/graph.test.ts +56 -10
  141. package/src/terraform/graph.ts +112 -45
  142. package/src/terraform/manifest.test.ts +105 -0
  143. package/src/terraform/manifest.ts +162 -0
  144. package/src/terraform/parse.test.ts +39 -1
  145. package/src/terraform/parse.ts +36 -9
  146. package/src/terraform/score.test.ts +8 -8
  147. package/src/terraform/state.test.ts +4 -4
  148. package/src/terraform/types.ts +7 -0
  149. package/src/terraform/unified-diff.test.ts +77 -0
  150. package/src/terraform/unified-diff.ts +146 -0
@@ -32,6 +32,7 @@ import { affectedStacks } from "../../lifecycle/affected";
32
32
  import { rollbackToRevision } from "../../lifecycle/rollback";
33
33
  import { loadChantConfig, environmentNames } from "../../config";
34
34
  import { applyLiveEndpoint } from "../../live-endpoint";
35
+ import { isResourceDeclarable } from "../../declarable";
35
36
  import { formatError, formatWarning, formatSuccess, formatBold } from "../format";
36
37
  import type { CommandContext } from "../registry";
37
38
  import type { LifecycleSnapshot } from "../../lifecycle/types";
@@ -758,17 +759,18 @@ async function runLifecycleDiffLive(args: LiveDiffArgs): Promise<LiveDiffOutcome
758
759
  continue;
759
760
  }
760
761
 
761
- // Build per-lexicon entity index
762
+ // Build per-lexicon entity index. Resource declarables only — outputs,
763
+ // parameters and serializer directives have no live counterpart, and a
764
+ // declared name the reader can never resolve reads as missing (see
765
+ // lifecycle/observe.ts).
762
766
  const declared = new Set<string>();
763
767
  const entities = new Map<string, { entityType: string; props: Record<string, unknown> }>();
764
768
  for (const [name, entity] of args.buildResult.entities) {
765
- if (entity.lexicon === lexiconName) {
769
+ if (entity.lexicon === lexiconName && isResourceDeclarable(entity)) {
766
770
  declared.add(name);
767
771
  entities.set(name, {
768
772
  entityType: entity.entityType,
769
- props: ("props" in entity && entity.props != null
770
- ? entity.props
771
- : {}) as Record<string, unknown>,
773
+ props: (entity.props != null ? entity.props : {}) as Record<string, unknown>,
772
774
  });
773
775
  }
774
776
  }
@@ -1119,11 +1121,11 @@ export async function runLifecyclePlan(ctx: CommandContext): Promise<number> {
1119
1121
  const declared = new Set<string>();
1120
1122
  const entities = new Map<string, { entityType: string; props: Record<string, unknown> }>();
1121
1123
  for (const [name, entity] of buildResult.entities) {
1122
- if (entity.lexicon === lexiconName) {
1124
+ if (entity.lexicon === lexiconName && isResourceDeclarable(entity)) {
1123
1125
  declared.add(name);
1124
1126
  entities.set(name, {
1125
1127
  entityType: entity.entityType,
1126
- props: ("props" in entity && entity.props != null ? entity.props : {}) as Record<string, unknown>,
1128
+ props: (entity.props != null ? entity.props : {}) as Record<string, unknown>,
1127
1129
  });
1128
1130
  }
1129
1131
  }
@@ -1,6 +1,6 @@
1
1
  import { listCommand, printListResult } from "../commands/list";
2
2
  import { describeCommand, printDescribeResult } from "../commands/describe";
3
- import { importCommand, importFromLive, importFromLiveStacks, printImportResult } from "../commands/import";
3
+ import { importCommand, importFromContent, importFromLive, importFromLiveStacks, printImportResult } from "../commands/import";
4
4
  import { loadChantConfig } from "../../config";
5
5
  import { resolve } from "path";
6
6
  import { auditCommand, printAuditResult, type AuditFormat, type AuditTier, type AuditFailOn } from "../commands/audit";
@@ -171,6 +171,45 @@ export async function runDescribe(ctx: CommandContext): Promise<number> {
171
171
  export async function runImport(ctx: CommandContext): Promise<number> {
172
172
  const { args } = ctx;
173
173
 
174
+ // `--kustomize <dir>` (#1548): render the overlay and import the output
175
+ // through the k8s template parser — the flag NAMES the lexicon, so no JSON
176
+ // detection. `kustomize build`, falling back to kubectl's vendored
177
+ // kustomize when the standalone binary is absent; a big overlay renders
178
+ // megabytes, hence the buffer bound.
179
+ if (args.kustomize) {
180
+ const { exec } = await import("node:child_process");
181
+ const { promisify } = await import("node:util");
182
+ const execAsync = promisify(exec);
183
+ const run = (cmd: string) => execAsync(cmd, { maxBuffer: 64 * 1024 * 1024 });
184
+ const quoted = `'${args.kustomize.replace(/'/g, "'\\''")}'`;
185
+ let rendered: string;
186
+ try {
187
+ ({ stdout: rendered } = await run(`kustomize build ${quoted}`));
188
+ } catch (err) {
189
+ const message = err instanceof Error ? err.message : String(err);
190
+ if (!/ENOENT|not found|command not found|127/.test(message)) {
191
+ console.error(formatError({ message: `kustomize build failed: ${message.split("\n")[0]}` }));
192
+ return 1;
193
+ }
194
+ try {
195
+ ({ stdout: rendered } = await run(`kubectl kustomize ${quoted}`));
196
+ } catch (err2) {
197
+ console.error(formatError({
198
+ message: `neither kustomize nor kubectl could render ${args.kustomize}: ${err2 instanceof Error ? err2.message.split("\n")[0] : String(err2)}`,
199
+ }));
200
+ return 1;
201
+ }
202
+ }
203
+ const result = await importFromContent({
204
+ content: rendered,
205
+ lexicon: "k8s",
206
+ output: args.output,
207
+ force: args.force,
208
+ });
209
+ printImportResult(result);
210
+ return result.success ? 0 : 1;
211
+ }
212
+
174
213
  // `--from <env>` switches import from a template file to a live source.
175
214
  if (args.migrateFrom) {
176
215
  const selector: ResourceSelector | undefined =
@@ -951,7 +951,9 @@ describe("runOpComponents", () => {
951
951
  expect(runComponentsMock).toHaveBeenCalledWith(expect.any(String), "svc", expect.objectContaining({
952
952
  buildParams: [{ name: "tier", value: "light", source: "default" }],
953
953
  }));
954
- expect(stderr.join("\n")).toContain("[param] tier");
954
+ // A parameter at its default collapses into the count line now; the
955
+ // provenance forwarded above is where its value is asserted.
956
+ expect(stderr.join("\n")).toContain("at their defaults");
955
957
  });
956
958
 
957
959
  test("--param overrides a declared default and is threaded through to runComponents", async () => {
package/src/cli/main.ts CHANGED
@@ -26,6 +26,7 @@ import { runCarveApply } from "./handlers/carve-apply";
26
26
  import { runLifecycleSnapshot, runLifecycleShow, runLifecycleDiff, runLifecycleRollback, runLifecyclePlan, runLifecycleAffected, runLifecycleLog, runLifecycleUnknown } from "./handlers/lifecycle";
27
27
  import { runComponentsStatus, runComponentsReleaseRecord, runComponentsUnknown } from "./handlers/components";
28
28
  import { runGraph } from "./handlers/graph";
29
+ import { runExplain } from "./handlers/explain";
29
30
  import { runSearch } from "./handlers/search";
30
31
  import { runOp, runOpList, runOpStatus, runOpSignal, runOpCancel, runOpLog } from "./handlers/run";
31
32
  import { runEmulator } from "./handlers/emulator";
@@ -188,6 +189,10 @@ export function parseArgs(args: string[]): ParsedArgs {
188
189
  // Shared by `migrate --from <lexicon>` and `import --from <env>`; the
189
190
  // two commands never run together, so one field carries both.
190
191
  result.migrateFrom = args[++i];
192
+ } else if (arg === "--kustomize") {
193
+ // `chant import --kustomize <dir>` (#1548): render the overlay, import
194
+ // the output through the k8s template parser.
195
+ result.kustomize = args[++i];
191
196
  } else if (arg === "--type") {
192
197
  result.selectType = args[++i];
193
198
  } else if (arg === "--name") {
@@ -208,6 +213,8 @@ export function parseArgs(args: string[]): ParsedArgs {
208
213
  result.carveStack = args[++i];
209
214
  } else if (arg === "--write") {
210
215
  result.write = true;
216
+ } else if (arg === "--write-source") {
217
+ result.writeSource = true;
211
218
  } else if (arg === "--to") {
212
219
  result.migrateTo = args[++i];
213
220
  } else if (arg === "--emit") {
@@ -381,6 +388,10 @@ Commands:
381
388
  lint Check specifications for issues
382
389
  list List discovered entities
383
390
  describe Show the effective config for one component
391
+ explain Summarize discovered entities (--format markdown|json|okf;
392
+ okf emits an OKF v0.2 knowledge bundle — one markdown
393
+ concept per entity + index.md; -o <dir> writes the
394
+ bundle tree, otherwise JSON path→content on stdout)
384
395
  vendor Pull pinned, checksummed patterns into your repo
385
396
  import Import external template into TypeScript
386
397
  audit [path|url] Audit a repo's CI YAML for security issues
@@ -398,14 +409,22 @@ Commands:
398
409
  --select <addr> (recommended for TF-managed resources); --env
399
410
  --state|--env <env> adopts via live cloud import (--live-name
400
411
  <logical-id> narrows a multi-resource stack).
412
+ Persists a carve manifest bridge/apply compose
413
+ with; scaffolds the output dir into a buildable
414
+ chant project (src/ + config + package.json).
401
415
  carve bridge Generate the surviving-TF patch (data sources + rewired
402
- --from <tf-dir> refs) + deferred inputs + reversible runbook.
403
- --select <addr> Writes proposals for review; --apply-rewrites
404
- edits the .tf in place.
416
+ --from <tf-dir> refs) + deferred inputs + reversible runbook,
417
+ [--select <addr>] plus one git-applyable .patch for the whole
418
+ edit. Writes proposals for review;
419
+ --apply-rewrites edits the .tf in place.
420
+ --select is optional when the output dir
421
+ holds one carve manifest.
405
422
  carve apply Apply graduation: ownership marker + finalized apply
406
423
  --from <tf-dir> runbook (dial-turn observe→apply). BYOL —
407
- --select <addr> no cloud call; --write saves the doc.
408
- --env <env>
424
+ [--select <addr>] no cloud call; --write saves the doc. --select
425
+ --env <env> is optional with a carve manifest present.
426
+ --write-source stamps the ownership marker
427
+ into the emitted chant source.
409
428
 
410
429
  Ops:
411
430
  run <name> Start an Op workflow (spawns worker + submits to Temporal)
@@ -701,6 +720,7 @@ const registry: CommandDef[] = [
701
720
  { name: "lint", handler: runLint },
702
721
  { name: "list", handler: runList },
703
722
  { name: "describe", handler: runDescribe },
723
+ { name: "explain", handler: runExplain },
704
724
  { name: "search", handler: runSearch },
705
725
  { name: "import", handler: runImport },
706
726
  { name: "audit", handler: runAudit },
@@ -327,6 +327,23 @@ describe("McpServer", () => {
327
327
  expect(parsed.sourceFiles).toBeDefined();
328
328
  });
329
329
 
330
+ test("calls explain tool with okf format (#1058)", async () => {
331
+ const response = await server.handleRequest({
332
+ jsonrpc: "2.0",
333
+ id: 1,
334
+ method: "tools/call",
335
+ params: { name: "explain", arguments: { path: testDir, format: "okf" } },
336
+ });
337
+
338
+ expect(response.error).toBeUndefined();
339
+ const result = response.result as { content: Array<{ text: string }> };
340
+ const parsed = JSON.parse(result.content[0].text);
341
+ expect(parsed.okf_version).toBe("0.2");
342
+ // Even an empty project yields a bundle with a root index.md.
343
+ expect(Object.keys(parsed.files)).toEqual(["index.md"]);
344
+ expect(parsed.files["index.md"]).toContain("okf_version: '0.2'");
345
+ });
346
+
330
347
  test("calls scaffold tool with generic fallback", async () => {
331
348
  const response = await server.handleRequest({
332
349
  jsonrpc: "2.0",
@@ -1,5 +1,6 @@
1
1
  import { resolve } from "path";
2
2
  import { discover } from "../../../discovery/index";
3
+ import { buildOkfBundle, OKF_VERSION } from "../../../okf";
3
4
 
4
5
  /**
5
6
  * Explain tool definition for MCP
@@ -16,8 +17,8 @@ export const explainTool = {
16
17
  },
17
18
  format: {
18
19
  type: "string",
19
- enum: ["markdown", "json"],
20
- description: "Output format (default: markdown)",
20
+ enum: ["markdown", "json", "okf"],
21
+ description: "Output format (default: markdown). okf: an OKF v0.2 knowledge bundle — one markdown concept per entity plus an index.md (#1058)",
21
22
  },
22
23
  },
23
24
  required: ["path"],
@@ -29,11 +30,22 @@ export const explainTool = {
29
30
  */
30
31
  export async function handleExplain(params: Record<string, unknown>): Promise<unknown> {
31
32
  const path = params.path as string;
32
- const format = (params.format as "markdown" | "json") ?? "markdown";
33
+ const format = (params.format as "markdown" | "json" | "okf") ?? "markdown";
33
34
 
34
35
  const infraPath = resolve(path);
35
36
  const result = await discover(infraPath);
36
37
 
38
+ // OKF knowledge bundle (#1058): one concept document per entity plus a root
39
+ // index.md, returned as bundle-relative path → content. The CLI path writes
40
+ // the same files to a directory.
41
+ if (format === "okf") {
42
+ return {
43
+ okf_version: OKF_VERSION,
44
+ files: Object.fromEntries(buildOkfBundle(result, infraPath).map((f) => [f.path, f.content])),
45
+ errors: result.errors.map((e) => e.message),
46
+ };
47
+ }
48
+
37
49
  // Group entities by lexicon and kind
38
50
  const byLexicon = new Map<string, { resources: string[]; properties: string[] }>();
39
51
 
@@ -31,6 +31,8 @@ export interface ParsedArgs {
31
31
  live: boolean;
32
32
  /** `chant migrate --from <name>` (default "github") */
33
33
  migrateFrom?: string;
34
+ /** `chant import --kustomize <dir>` — render the kustomization, then import (#1548) */
35
+ kustomize?: string;
34
36
  /** `chant carve advise --state <path>` — opt-in .tfstate for accurate instance counts */
35
37
  statePath?: string;
36
38
  /** `chant carve emit --select <tf-address>` — the Terraform resource to carve */
@@ -43,6 +45,8 @@ export interface ParsedArgs {
43
45
  carveStack?: string;
44
46
  /** `chant carve apply --write` — save the graduation doc */
45
47
  write?: boolean;
48
+ /** `chant carve apply --write-source` — stamp the ownership marker into the emitted source */
49
+ writeSource?: boolean;
46
50
  /** `chant migrate --to <name>` (default "gitlab") */
47
51
  migrateTo?: string;
48
52
  /** `chant migrate --emit yaml|ts` */
@@ -0,0 +1,170 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`buildLexiconOkfBundle > registry-style and dts-style bundles snapshot stably 1`] = `
4
+ [
5
+ {
6
+ "content": "---
7
+ okf_version: '0.2'
8
+ ---
9
+
10
+ # Resource types
11
+
12
+ * [Service](/types/Service.md) - Mock::Compose::Service
13
+ * [Volume](/types/Volume.md) - Mock::Compose::Volume
14
+
15
+ # Rules
16
+
17
+ * [MCK001](/rules/MCK001.md) - Avoid :latest image references on a Service
18
+ * [MCK010](/rules/MCK010.md) - A declared Volume must be mounted somewhere
19
+ ",
20
+ "path": "index.md",
21
+ },
22
+ {
23
+ "content": "---
24
+ type: lint-rule
25
+ title: MCK001
26
+ description: Avoid :latest image references on a Service
27
+ id: MCK001
28
+ severity: warning
29
+ category: correctness
30
+ lexicon: mockdocker
31
+ docs: https://intentius.io/chant/lexicons/mockdocker/rules/
32
+ ---
33
+ Avoid :latest image references on a Service
34
+
35
+ ## Applies to
36
+
37
+ - [Service](/types/Service.md)
38
+ ",
39
+ "path": "rules/MCK001.md",
40
+ },
41
+ {
42
+ "content": "---
43
+ type: post-synth-check
44
+ title: MCK010
45
+ description: A declared Volume must be mounted somewhere
46
+ id: MCK010
47
+ severity: error
48
+ category: post-synth
49
+ lexicon: mockdocker
50
+ docs: https://intentius.io/chant/lexicons/mockdocker/rules/
51
+ ---
52
+ A declared Volume must be mounted somewhere
53
+
54
+ ## Applies to
55
+
56
+ - [Volume](/types/Volume.md)
57
+ ",
58
+ "path": "rules/MCK010.md",
59
+ },
60
+ {
61
+ "content": "---
62
+ type: resource-type
63
+ title: Service
64
+ description: A containerized service definition
65
+ name: Service
66
+ lexicon: mockdocker
67
+ resource_type: Mock::Compose::Service
68
+ ---
69
+ A containerized service definition
70
+
71
+ ## Properties
72
+
73
+ - \`image\` (\`string\`): Container image to use
74
+ - \`ports\` (\`string[]\`): Published ports
75
+
76
+ ## Governed by
77
+
78
+ - [MCK001](/rules/MCK001.md): Avoid :latest image references on a Service
79
+ ",
80
+ "path": "types/Service.md",
81
+ },
82
+ {
83
+ "content": "---
84
+ type: resource-type
85
+ title: Volume
86
+ description: A named volume
87
+ name: Volume
88
+ lexicon: mockdocker
89
+ resource_type: Mock::Compose::Volume
90
+ ---
91
+ A named volume
92
+
93
+ ## Properties
94
+
95
+ - \`driver\` (\`string\`): Volume driver
96
+
97
+ ## Governed by
98
+
99
+ - [MCK010](/rules/MCK010.md): A declared Volume must be mounted somewhere
100
+ ",
101
+ "path": "types/Volume.md",
102
+ },
103
+ ]
104
+ `;
105
+
106
+ exports[`buildLexiconOkfBundle > registry-style and dts-style bundles snapshot stably 2`] = `
107
+ [
108
+ {
109
+ "content": "---
110
+ okf_version: '0.2'
111
+ ---
112
+
113
+ # Resource types
114
+
115
+ * [Bucket](/types/Bucket.md) - Mock::S3::Bucket
116
+
117
+ # Rules
118
+
119
+ * [MAW006](/rules/MAW006.md) - Detects Bucket creation without encryption
120
+ ",
121
+ "path": "index.md",
122
+ },
123
+ {
124
+ "content": "---
125
+ type: lint-rule
126
+ title: MAW006
127
+ description: Detects Bucket creation without encryption
128
+ id: MAW006
129
+ severity: warning
130
+ category: security
131
+ lexicon: mockaws
132
+ docs: https://intentius.io/chant/lexicons/mockaws/rules/
133
+ ---
134
+ Detects Bucket creation without encryption
135
+
136
+ ## Applies to
137
+
138
+ - [Bucket](/types/Bucket.md)
139
+ ",
140
+ "path": "rules/MAW006.md",
141
+ },
142
+ {
143
+ "content": "---
144
+ type: resource-type
145
+ title: Bucket
146
+ description: mockaws resource type Mock::S3::Bucket
147
+ name: Bucket
148
+ lexicon: mockaws
149
+ resource_type: Mock::S3::Bucket
150
+ ---
151
+ \`Mock::S3::Bucket\`, a resource type of the mockaws lexicon.
152
+
153
+ ## Properties
154
+
155
+ - \`BucketName\` (\`string\`, optional): A name for the bucket.
156
+ - \`VersioningConfiguration\` (\`Bucket_VersioningConfiguration\`, optional): The versioning state.
157
+
158
+ ## Attributes
159
+
160
+ - \`Arn\`
161
+ - \`DomainName\`
162
+
163
+ ## Governed by
164
+
165
+ - [MAW006](/rules/MAW006.md): Detects Bucket creation without encryption
166
+ ",
167
+ "path": "types/Bucket.md",
168
+ },
169
+ ]
170
+ `;
@@ -11,12 +11,27 @@ import { join } from "path";
11
11
  import { escapeMdx } from "./docs-file-markers";
12
12
  import type { DocsConfig, RuleMeta } from "./docs-types";
13
13
 
14
+ /** A scanned rule: its extracted metadata plus the source it came from. */
15
+ export interface ScannedRule {
16
+ meta: RuleMeta;
17
+ source: string;
18
+ }
19
+
14
20
  /**
15
21
  * Scan lint rule and post-synth check source files to extract metadata.
16
22
  * Uses regex to find id, severity, category, and description from source.
17
23
  */
18
24
  export function scanRules(srcDir: string): RuleMeta[] {
19
- const rules: RuleMeta[] = [];
25
+ return scanRulesWithSources(srcDir).map((r) => r.meta);
26
+ }
27
+
28
+ /**
29
+ * Like {@link scanRules}, but keeps each rule's source text alongside its
30
+ * metadata — the OKF bundle builder (#1060) reads the source to associate a
31
+ * rule with the resource types it mentions.
32
+ */
33
+ export function scanRulesWithSources(srcDir: string): ScannedRule[] {
34
+ const rules: ScannedRule[] = [];
20
35
 
21
36
  // Scan lint rules
22
37
  scanDir(join(srcDir, "lint", "rules"), "lint", rules);
@@ -27,7 +42,7 @@ export function scanRules(srcDir: string): RuleMeta[] {
27
42
  return rules;
28
43
  }
29
44
 
30
- function scanDir(dir: string, type: "lint" | "post-synth", out: RuleMeta[]): void {
45
+ function scanDir(dir: string, type: "lint" | "post-synth", out: ScannedRule[]): void {
31
46
  let entries: string[];
32
47
  try {
33
48
  entries = readdirSync(dir);
@@ -62,13 +77,16 @@ function scanDir(dir: string, type: "lint" | "post-synth", out: RuleMeta[]): voi
62
77
  );
63
78
 
64
79
  out.push({
65
- id: idMatch[1],
66
- severity: severityMatch?.[1] ?? "warning",
67
- category: categoryMatch?.[1] ?? "general",
68
- description:
69
- descMatch?.[1]?.trim() ??
70
- extractDescriptionFromComment(content, idMatch[1]),
71
- type: "lint",
80
+ meta: {
81
+ id: idMatch[1],
82
+ severity: severityMatch?.[1] ?? "warning",
83
+ category: categoryMatch?.[1] ?? "general",
84
+ description:
85
+ descMatch?.[1]?.trim() ??
86
+ extractDescriptionFromComment(content, idMatch[1]),
87
+ type: "lint",
88
+ },
89
+ source: content,
72
90
  });
73
91
  }
74
92
  } else {
@@ -78,11 +96,14 @@ function scanDir(dir: string, type: "lint" | "post-synth", out: RuleMeta[]): voi
78
96
 
79
97
  if (idMatch) {
80
98
  out.push({
81
- id: idMatch[1],
82
- severity: "error",
83
- category: "post-synth",
84
- description: descMatch?.[1] ?? idMatch[1],
85
- type: "post-synth",
99
+ meta: {
100
+ id: idMatch[1],
101
+ severity: "error",
102
+ category: "post-synth",
103
+ description: descMatch?.[1] ?? idMatch[1],
104
+ type: "post-synth",
105
+ },
106
+ source: content,
86
107
  });
87
108
  }
88
109
  }