@intentius/chant 0.124.45 → 0.124.46

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 (190) hide show
  1. package/dist/capabilities/aws.paths +148 -0
  2. package/dist/capabilities/base-mappings.json +122 -0
  3. package/dist/capabilities/catalog.d.ts +89 -0
  4. package/dist/capabilities/catalog.d.ts.map +1 -0
  5. package/dist/capabilities/catalog.js +204 -0
  6. package/dist/capabilities/language.d.ts +103 -0
  7. package/dist/capabilities/language.d.ts.map +1 -0
  8. package/dist/capabilities/language.js +335 -0
  9. package/dist/capabilities/matcher.d.ts +65 -0
  10. package/dist/capabilities/matcher.d.ts.map +1 -0
  11. package/dist/capabilities/matcher.js +299 -0
  12. package/dist/capabilities/view.d.ts +207 -0
  13. package/dist/capabilities/view.d.ts.map +1 -0
  14. package/dist/capabilities/view.js +484 -0
  15. package/dist/cli/commands/lint.d.ts.map +1 -1
  16. package/dist/cli/commands/lint.js +1 -0
  17. package/dist/cli/handlers/declared-graph.d.ts +44 -0
  18. package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
  19. package/dist/cli/handlers/declared-graph.js +87 -0
  20. package/dist/cli/handlers/graph.d.ts +25 -0
  21. package/dist/cli/handlers/graph.d.ts.map +1 -1
  22. package/dist/cli/handlers/graph.js +93 -87
  23. package/dist/cli/handlers/pr-env.d.ts.map +1 -1
  24. package/dist/cli/handlers/pr-env.js +2 -1
  25. package/dist/cli/handlers/search-count.d.ts +89 -0
  26. package/dist/cli/handlers/search-count.d.ts.map +1 -0
  27. package/dist/cli/handlers/search-count.js +132 -0
  28. package/dist/cli/handlers/search.d.ts +358 -6
  29. package/dist/cli/handlers/search.d.ts.map +1 -1
  30. package/dist/cli/handlers/search.js +1065 -155
  31. package/dist/cli/main.d.ts.map +1 -1
  32. package/dist/cli/main.js +23 -3
  33. package/dist/cli/mcp/server.d.ts.map +1 -1
  34. package/dist/cli/mcp/server.js +3 -0
  35. package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
  36. package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
  37. package/dist/cli/mcp/tools/capabilities.js +27 -0
  38. package/dist/cli/mcp/workspace-tools.d.ts +3 -1
  39. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  40. package/dist/cli/mcp/workspace-tools.js +47 -2
  41. package/dist/cli/parse-args.d.ts.map +1 -1
  42. package/dist/cli/parse-args.js +64 -17
  43. package/dist/cli/registry.d.ts +22 -0
  44. package/dist/cli/registry.d.ts.map +1 -1
  45. package/dist/components/pr-env-ports.d.ts +29 -0
  46. package/dist/components/pr-env-ports.d.ts.map +1 -0
  47. package/dist/components/pr-env-ports.js +77 -0
  48. package/dist/components/pr-env-run.d.ts +5 -21
  49. package/dist/components/pr-env-run.d.ts.map +1 -1
  50. package/dist/components/pr-env-run.js +6 -70
  51. package/dist/components/pr-environment.d.ts +2 -1
  52. package/dist/components/pr-environment.d.ts.map +1 -1
  53. package/dist/components/pr-environment.js +1 -1
  54. package/dist/config-environments.d.ts +92 -0
  55. package/dist/config-environments.d.ts.map +1 -0
  56. package/dist/config-environments.js +102 -0
  57. package/dist/config.d.ts +24 -84
  58. package/dist/config.d.ts.map +1 -1
  59. package/dist/config.js +9 -96
  60. package/dist/graph-detail.d.ts.map +1 -1
  61. package/dist/graph-detail.js +15 -3
  62. package/dist/graph-effective.d.ts +33 -2
  63. package/dist/graph-effective.d.ts.map +1 -1
  64. package/dist/graph-effective.js +215 -68
  65. package/dist/graph-ir.d.ts +23 -0
  66. package/dist/graph-ir.d.ts.map +1 -1
  67. package/dist/graph-ir.js +130 -0
  68. package/dist/graph-sub-template.d.ts +11 -0
  69. package/dist/graph-sub-template.d.ts.map +1 -0
  70. package/dist/graph-sub-template.js +19 -0
  71. package/dist/lexicon.d.ts +9 -0
  72. package/dist/lexicon.d.ts.map +1 -1
  73. package/dist/lifecycle/observe.d.ts +28 -0
  74. package/dist/lifecycle/observe.d.ts.map +1 -1
  75. package/dist/lifecycle/observe.js +23 -1
  76. package/dist/lint/rule.d.ts +6 -0
  77. package/dist/lint/rule.d.ts.map +1 -1
  78. package/dist/live-endpoint.d.ts +1 -1
  79. package/dist/live-endpoint.d.ts.map +1 -1
  80. package/dist/live-endpoint.js +1 -1
  81. package/dist/observation.d.ts +48 -1
  82. package/dist/observation.d.ts.map +1 -1
  83. package/dist/observation.js +21 -5
  84. package/dist/op/activities/activity-contracts.d.ts +4 -4
  85. package/dist/wave-runtime.mjs +250 -249
  86. package/dist/workspace/capabilities-contract.d.ts +66 -0
  87. package/dist/workspace/capabilities-contract.d.ts.map +1 -0
  88. package/dist/workspace/capabilities-contract.js +107 -0
  89. package/dist/workspace/capabilities.schema.json +229 -0
  90. package/dist/workspace/compose-graph.d.ts +9 -0
  91. package/dist/workspace/compose-graph.d.ts.map +1 -1
  92. package/dist/workspace/compose-graph.js +3 -1
  93. package/dist/workspace/conformance/index.d.ts +1 -1
  94. package/dist/workspace/conformance/index.d.ts.map +1 -1
  95. package/dist/workspace/conformance/index.js +39 -2
  96. package/dist/workspace/graph-cache.d.ts +8 -0
  97. package/dist/workspace/graph-cache.d.ts.map +1 -1
  98. package/dist/workspace/graph-cache.js +27 -3
  99. package/dist/workspace/graph-cli.d.ts +39 -2
  100. package/dist/workspace/graph-cli.d.ts.map +1 -1
  101. package/dist/workspace/graph-cli.js +66 -24
  102. package/dist/workspace/graph.schema.json +148 -0
  103. package/dist/workspace/kind-readers.d.ts +3 -0
  104. package/dist/workspace/kind-readers.d.ts.map +1 -1
  105. package/dist/workspace/kind-readers.js +7 -1
  106. package/dist/workspace/member-commands.d.ts +10 -1
  107. package/dist/workspace/member-commands.d.ts.map +1 -1
  108. package/dist/workspace/member-commands.js +17 -1
  109. package/dist/workspace/nested-graph.d.ts +2 -0
  110. package/dist/workspace/nested-graph.d.ts.map +1 -1
  111. package/dist/workspace/nested-graph.js +32 -1
  112. package/dist/workspace/reason-codes.d.ts +10 -0
  113. package/dist/workspace/reason-codes.d.ts.map +1 -1
  114. package/dist/workspace/reason-codes.js +12 -0
  115. package/dist/workspace/search-cli.d.ts +204 -0
  116. package/dist/workspace/search-cli.d.ts.map +1 -0
  117. package/dist/workspace/search-cli.js +466 -0
  118. package/dist/workspace/search-contract.d.ts +56 -0
  119. package/dist/workspace/search-contract.d.ts.map +1 -0
  120. package/dist/workspace/search-contract.js +70 -0
  121. package/dist/workspace/search.schema.json +822 -0
  122. package/package.json +1 -1
  123. package/src/__snapshots__/okf.test.ts.snap +10 -0
  124. package/src/capabilities/aws.paths +148 -0
  125. package/src/capabilities/base-mappings.json +122 -0
  126. package/src/capabilities/catalog.ts +230 -0
  127. package/src/capabilities/language.test.ts +191 -0
  128. package/src/capabilities/language.ts +373 -0
  129. package/src/capabilities/matcher.ts +315 -0
  130. package/src/capabilities/view-compose.test.ts +76 -0
  131. package/src/capabilities/view.ts +631 -0
  132. package/src/cli/commands/lint.ts +1 -0
  133. package/src/cli/handlers/declared-graph.ts +99 -0
  134. package/src/cli/handlers/graph.test.ts +94 -3
  135. package/src/cli/handlers/graph.ts +123 -96
  136. package/src/cli/handlers/pr-env.ts +1 -2
  137. package/src/cli/handlers/search-build-roots.test.ts +144 -0
  138. package/src/cli/handlers/search-count.test.ts +191 -0
  139. package/src/cli/handlers/search-count.ts +165 -0
  140. package/src/cli/handlers/search-json.test.ts +333 -0
  141. package/src/cli/handlers/search-live.test.ts +387 -0
  142. package/src/cli/handlers/search-paths.test.ts +235 -0
  143. package/src/cli/handlers/search.ts +1312 -166
  144. package/src/cli/main.test.ts +28 -3
  145. package/src/cli/main.ts +23 -3
  146. package/src/cli/mcp/server.test.ts +3 -3
  147. package/src/cli/mcp/server.ts +3 -0
  148. package/src/cli/mcp/tools/capabilities.ts +32 -0
  149. package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
  150. package/src/cli/mcp/workspace-tools.ts +47 -2
  151. package/src/cli/parse-args.ts +52 -13
  152. package/src/cli/registry.ts +22 -0
  153. package/src/components/pr-env-ports.ts +89 -0
  154. package/src/components/pr-env-run-bundle.test.ts +54 -0
  155. package/src/components/pr-env-run.ts +5 -76
  156. package/src/components/pr-environment.ts +2 -1
  157. package/src/config-environments.ts +133 -0
  158. package/src/config.ts +37 -124
  159. package/src/graph-detail.ts +15 -3
  160. package/src/graph-effective.ts +226 -75
  161. package/src/graph-ir.test.ts +163 -0
  162. package/src/graph-ir.ts +147 -0
  163. package/src/graph-sub-template.ts +22 -0
  164. package/src/lexicon.ts +10 -0
  165. package/src/lifecycle/observe.test.ts +27 -1
  166. package/src/lifecycle/observe.ts +43 -1
  167. package/src/lint/rule.ts +6 -0
  168. package/src/live-endpoint.ts +1 -1
  169. package/src/observation.test.ts +25 -0
  170. package/src/observation.ts +69 -5
  171. package/src/workspace/capabilities-contract.test.ts +88 -0
  172. package/src/workspace/capabilities-contract.ts +132 -0
  173. package/src/workspace/capabilities.schema.json +229 -0
  174. package/src/workspace/compose-graph.ts +10 -1
  175. package/src/workspace/conformance/conformance.e2e.test.ts +14 -1
  176. package/src/workspace/conformance/index.ts +31 -2
  177. package/src/workspace/graph-cache.ts +27 -3
  178. package/src/workspace/graph-cli.ts +96 -24
  179. package/src/workspace/graph.schema.json +148 -0
  180. package/src/workspace/kind-readers.e2e.test.ts +28 -0
  181. package/src/workspace/kind-readers.test.ts +1 -1
  182. package/src/workspace/kind-readers.ts +7 -1
  183. package/src/workspace/member-commands.ts +22 -1
  184. package/src/workspace/nested-graph.ts +32 -1
  185. package/src/workspace/reason-codes.test.ts +7 -0
  186. package/src/workspace/reason-codes.ts +12 -0
  187. package/src/workspace/search-cli.e2e.test.ts +305 -0
  188. package/src/workspace/search-cli.ts +591 -0
  189. package/src/workspace/search-contract.ts +95 -0
  190. package/src/workspace/search.schema.json +822 -0
@@ -1,14 +1,14 @@
1
- import { dirname, resolve } from "node:path";
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
2
  import { discoverOps } from "../../op/discover.js";
3
3
  import { discover } from "../../discovery/index.js";
4
- import { partitionByLexicon, computeStackGraph, build, mergeBuildRootEntities } from "../../build.js";
5
- import { applyLexiconGraphEdges, applyLexiconOutputs, buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, GRAPH_IR_VERSION } from "../../graph-ir.js";
4
+ import { partitionByLexicon, computeStackGraph, build } from "../../build.js";
5
+ import { applyLexiconOutputs, buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, GRAPH_IR_VERSION } from "../../graph-ir.js";
6
6
  import { behaviourOverlay } from "../../behaviour-overlay.js";
7
7
  import { applyBehaviourOverlay, behaviourRequestFromIr } from "../../behaviour-graph.js";
8
8
  import { buildDeclaredPerStack } from "../../graph-declared.js";
9
9
  import { mergeProjectOps } from "../../graph-ops.js";
10
10
  import { reconstructEdges, mergeCatalogs, containmentGroups } from "../../graph-refs.js";
11
- import { observeResources } from "../../lifecycle/observe.js";
11
+ import { carryAnnotations, observeResources } from "../../lifecycle/observe.js";
12
12
  import { withLiveReadSession } from "../../live-read-session.js";
13
13
  import { replaySnapshots, hasSnapshot } from "../../lifecycle/replay.js";
14
14
  import { loadChantConfig, environmentNames, matchesDeclaredEnvironment, loadChantConfigUpward } from "../../config.js";
@@ -21,6 +21,8 @@ import { getLayoutEngine, toLayoutInput } from "../../graph-layout.js";
21
21
  import { lintCommand } from "../commands/lint.js";
22
22
  import { reportUninstalledDependencies } from "../dependencies.js";
23
23
  import { loadPlugins, resolveProjectLexicons, collectBuildRootContributors } from "../plugins.js";
24
+ // One copy for graph and search (#4365): the build-root join and the lexicon edges.
25
+ import { declaredBuildRootContributors as graphBuildRootContributors, mergeDeclaredBuildRoots as mergeGraphBuildRoots, withLexiconEdges, } from "./declared-graph.js";
24
26
  import { isNoLexiconDetected } from "../../detectLexicon.js";
25
27
  import { readFileSync } from "node:fs";
26
28
  import { formatError, formatWarning, formatBold } from "../format.js";
@@ -71,59 +73,6 @@ async function graphBuildParams(ctx, projectPath) {
71
73
  }
72
74
  return resolution.provenance;
73
75
  }
74
- /**
75
- * The build-root contributors this invocation's project configures (#1626) —
76
- * the same closures `chant build` and the lifecycle handlers hand to
77
- * `BuildOptions.buildRoots`, bound here for the graph command's
78
- * discover-based paths, which never run `build()` and so never ran the hook:
79
- * a `k8s.kustomize.roots` estate graphed only its typed source while its
80
- * built (and deployed) output carried the rendered manifests too.
81
- *
82
- * `graph` is not `requiresPlugins` (Op/source-graph modes must work without a
83
- * lexicon), so plugins are loaded here when `ctx.plugins` is empty, mirroring
84
- * `runGraphLive`. Rooted at the config's own directory, like
85
- * `cli/commands/build.ts` — a relative kustomize root resolves against where
86
- * it was declared, not against a sourceDir-scoped graph path. A project that
87
- * fails to load plugins degrades to no contributors with a warning rather
88
- * than failing modes that never needed plugins at all.
89
- */
90
- async function graphBuildRootContributors(ctx, projectPath) {
91
- const { config, configPath } = await loadChantConfigUpward(projectPath).catch(() => ({ config: {}, configPath: undefined }));
92
- let plugins = ctx.plugins;
93
- if (plugins.length === 0) {
94
- try {
95
- plugins = await loadPlugins(await resolveProjectLexicons(projectPath));
96
- }
97
- catch (err) {
98
- console.error(formatWarning({
99
- message: `could not load the project's lexicons — build-root entities (kustomize roots) will be missing from the graph (${err instanceof Error ? err.message : String(err)})`,
100
- }));
101
- return [];
102
- }
103
- }
104
- return collectBuildRootContributors(plugins, config, configPath ? dirname(configPath) : projectPath);
105
- }
106
- /**
107
- * Merge build-root contributions into a discovered entity set — the exact
108
- * merge `build()` performs (one implementation: `mergeBuildRootEntities`,
109
- * ../../build.ts), surfaced in the graph command's error style. Returns false
110
- * when a contributor failed or a name collided; the caller stops, matching
111
- * how the discover-based paths treat discovery errors — a graph missing the
112
- * entities the build would refuse over must not be emitted as if complete.
113
- */
114
- async function mergeGraphBuildRoots(entities, contributors) {
115
- if (contributors.length === 0)
116
- return true;
117
- const merged = await mergeBuildRootEntities(entities, contributors);
118
- for (const w of merged.warnings)
119
- console.error(formatWarning({ message: w }));
120
- if (merged.errors.length > 0) {
121
- for (const message of merged.errors)
122
- console.error(formatError({ message }));
123
- return false;
124
- }
125
- return true;
126
- }
127
76
  /** #1675 — join git-root ops into a discovered entity map; op-discovery
128
77
  * errors are warnings here (the lint gate never saw those files). */
129
78
  async function mergeGraphOps(entities, sourceFiles, projectPath) {
@@ -165,6 +114,15 @@ async function runGraphCommand(ctx) {
165
114
  }));
166
115
  return 1;
167
116
  }
117
+ // The capability view (#4378) is built from the declared entity graph's IR,
118
+ // the read `chant workspace graph --capability-view` asks each member for.
119
+ if (ctx.args.capabilityView && (ctx.args.format !== "ir" || ctx.args.components || ctx.args.live || ctx.args.at)) {
120
+ console.error(formatError({
121
+ message: "--capability-view adds the capability view to the declared graph's IR, so it needs --format ir and takes no --components, --live or --at",
122
+ hint: "chant graph --format ir --detail 3 --capability-view",
123
+ }));
124
+ return 1;
125
+ }
168
126
  // `--at` graphs a recorded observation instead of reading the estate (#1279).
169
127
  // Two different observations with no rule for which wins, so not both.
170
128
  if (ctx.args.at && ctx.args.live) {
@@ -300,6 +258,8 @@ async function runGraphLive(ctx, format) {
300
258
  console.error(formatWarning({ message: endpointResult.notice }));
301
259
  let ir;
302
260
  let observations;
261
+ // Facts about absent entities (#4372), carried onto the overlay's nodes.
262
+ let annotations;
303
263
  // A replay is a graph of what was recorded, so it must not reach the provider
304
264
  // at all — not for observation and not for enrichment. Anything that quietly
305
265
  // called out would make `--at` a live read with an older label on it.
@@ -329,6 +289,7 @@ async function runGraphLive(ctx, format) {
329
289
  ...(args.namespace ? { namespace: args.namespace } : {}),
330
290
  });
331
291
  observations = observeResult.observations;
292
+ annotations = observeResult.annotations;
332
293
  const { errors, warnings, notes = [] } = observeResult;
333
294
  for (const e of errors)
334
295
  console.error(formatWarning({ message: e }));
@@ -443,6 +404,10 @@ async function runGraphLive(ctx, format) {
443
404
  : sourceOverlayGraphs(declaredIr, ir, overlayOpts);
444
405
  }
445
406
  }
407
+ // What a read learned about an entity it found absent (#4372), on the
408
+ // declared node beside the overlay's `_status`. A planned create keeps its
409
+ // pending paint and carries the plan's attributes.
410
+ ir = { ...ir, nodes: carryAnnotations(ir.nodes, annotations) };
446
411
  }
447
412
  // Behaviour overlay (#2377, epic #2355): carry a prediction on the graph.
448
413
  // After the drift overlay so it lands on the final node set beside `_status`,
@@ -544,6 +509,55 @@ function refuseLintGate(lint, projectPath) {
544
509
  message: "Refusing to emit graph: source has lint errors. Run `chant lint` and fix them first.",
545
510
  }));
546
511
  }
512
+ /** The project's `graph.lint`: `"report"` only when it says so (#919). */
513
+ async function graphLintMode(projectPath) {
514
+ const { config } = await loadChantConfigUpward(projectPath).catch(() => ({ config: {} }));
515
+ return config.graph?.lint === "report" ? "report" : "gate";
516
+ }
517
+ export function graphLintReport(lint, projectPath) {
518
+ const rel = (file) => {
519
+ if (!isAbsolute(file))
520
+ return file;
521
+ const r = relative(projectPath, file);
522
+ return r.startsWith("..") ? file : r;
523
+ };
524
+ const findings = (lint.diagnostics ?? []).map((d) => ({
525
+ ruleId: d.ruleId,
526
+ severity: d.severity,
527
+ message: d.message,
528
+ file: rel(d.file),
529
+ line: d.line,
530
+ column: d.column,
531
+ ...(d.entity ? { entity: d.entity } : {}),
532
+ }));
533
+ return {
534
+ errors: findings.filter((f) => f.severity === "error").length,
535
+ warnings: findings.filter((f) => f.severity === "warning").length,
536
+ findings,
537
+ };
538
+ }
539
+ /**
540
+ * The lint gate for the entity graph. Returns false when the graph must not be
541
+ * emitted, having printed why. A `graph.lint: "report"` project passes with
542
+ * lint errors and says so on stderr, unless an error is an uninstalled
543
+ * dependency: then there is no source to read, and it refuses as the gate does.
544
+ */
545
+ function passLintGate(lint, projectPath, mode) {
546
+ if (lint.success)
547
+ return true;
548
+ if (mode === "gate") {
549
+ refuseLintGate(lint, projectPath);
550
+ return false;
551
+ }
552
+ const errors = (lint.diagnostics ?? []).filter((d) => d.severity === "error").map((d) => d.message);
553
+ if (reportUninstalledDependencies(errors, projectPath))
554
+ return false;
555
+ const n = errors.length;
556
+ console.error(formatWarning({
557
+ message: `Emitting the graph with ${n} lint error${n === 1 ? "" : "s"}: graph.lint is "report", so every finding is on the IR's meta.lint. Run \`chant lint\` to see them.`,
558
+ }));
559
+ return true;
560
+ }
547
561
  /**
548
562
  * `chant graph --components --format ir|mermaid|dot|layout` — the component DAG
549
563
  * projected into the renderable IR (nodes = components, `groups.byWave` = the
@@ -673,7 +687,9 @@ async function buildPipelineProjection(projectPath, lexicon, sandbox) {
673
687
  * `--node-sizes` so spacing fits real node footprints, and defaults to the dagre
674
688
  * engine (no native dependency); `--layout-engine graphviz` opts into `dot`.
675
689
  * Lint-gated: the IR represents valid infra, so we refuse to emit for source that
676
- * does not pass lint. Non-zero on discovery errors or a layout-engine failure.
690
+ * does not pass lint, unless the project's config sets `graph.lint: "report"`
691
+ * (#919): then the graph is emitted and the findings ride on `meta.lint`.
692
+ * Non-zero on discovery errors or a layout-engine failure.
677
693
  */
678
694
  /**
679
695
  * Add what the lexicons' `graphMeta` hooks report to `ir.meta` (#2559). A
@@ -696,28 +712,6 @@ function withGraphMeta(ir, plugins, entities) {
696
712
  }
697
713
  return Object.keys(added).length === 0 ? ir : { ...ir, meta: { ...(ir.meta ?? {}), ...added } };
698
714
  }
699
- /**
700
- * Add the edges the lexicons' `graphEdges` hooks resolve (#3910): CI
701
- * dependencies written as strings (`needs`, `uses:`, `trigger:`, `include:`,
702
- * `extends`), which the AttrRef walk in `buildGraphIr` cannot see. A hook that
703
- * throws costs its own edges and a warning, not the graph.
704
- */
705
- function withLexiconEdges(ir, plugins, entities) {
706
- const reports = [];
707
- for (const p of plugins) {
708
- if (!p.graphEdges)
709
- continue;
710
- try {
711
- const report = p.graphEdges(entities);
712
- if (report)
713
- reports.push(report);
714
- }
715
- catch (err) {
716
- console.error(formatWarning({ message: `the ${p.name} lexicon's graphEdges failed (${err instanceof Error ? err.message : String(err)}); its edges are left out` }));
717
- }
718
- }
719
- return reports.length === 0 ? ir : applyLexiconGraphEdges(ir, reports);
720
- }
721
715
  /**
722
716
  * Add the outputs the lexicons' `workspaceOutputs` hooks report (#3931) to
723
717
  * the IR's `exports`: a sql table and each of its columns, which `chant
@@ -821,17 +815,17 @@ async function runGraphView(ctx, format) {
821
815
  const buildParams = await graphBuildParams(ctx, projectPath);
822
816
  if (!buildParams)
823
817
  return 1;
824
- // Gate: only emit for lint-clean source.
818
+ // Gate: only emit for lint-clean source, unless the project reads source it
819
+ // did not author and asks for the findings instead (`graph.lint`, #919).
825
820
  const lint = await lintCommand({
826
821
  path: ctx.args.path,
827
822
  format: "stylish",
828
823
  sandbox: ctx.args.sandbox,
829
824
  buildParams,
830
825
  });
831
- if (!lint.success) {
832
- refuseLintGate(lint, projectPath);
826
+ const lintMode = await graphLintMode(projectPath);
827
+ if (!passLintGate(lint, projectPath, lintMode))
833
828
  return 1;
834
- }
835
829
  const result = await discover(projectPath, { buildParams });
836
830
  if (result.errors.length > 0) {
837
831
  for (const e of result.errors)
@@ -854,10 +848,10 @@ async function runGraphView(ctx, format) {
854
848
  // The declared half of the epic's delta (#2355): this path predicts the
855
849
  // file, the live path predicts the account. Same helper, so the two shapes
856
850
  // reach the engine by one route and stay comparable.
851
+ const plugins = ctx.plugins.length > 0
852
+ ? ctx.plugins
853
+ : await loadPlugins(await projectLexiconsOrNone(projectPath));
857
854
  {
858
- const plugins = ctx.plugins.length > 0
859
- ? ctx.plugins
860
- : await loadPlugins(await projectLexiconsOrNone(projectPath));
861
855
  ir = withLexiconEdges(ir, plugins, result.entities);
862
856
  ir = withLexiconOutputs(ir, plugins, result.entities);
863
857
  const predicted = await predictOntoIr(ir, plugins, ctx.args, {
@@ -894,6 +888,18 @@ async function runGraphView(ctx, format) {
894
888
  console.error(formatWarning({ message: notice }));
895
889
  }
896
890
  }
891
+ // Last, so no lens or detail pass can drop it: the findings are about the
892
+ // whole source read, not about the nodes this view kept.
893
+ if (lintMode === "report")
894
+ ir = { ...ir, meta: { ...ir.meta, lint: graphLintReport(lint, projectPath) } };
895
+ // The view capability rules read (#4378), mapped by this project's own
896
+ // lexicons, so a workspace's rules read each member as its chant maps it.
897
+ if (ctx.args.capabilityView) {
898
+ const { buildView, serializeView } = await import("../../capabilities/view.js");
899
+ const { resolvePacks } = await import("../../capabilities/catalog.js");
900
+ const packs = plugins.map((p) => p.capabilityPack).filter((p) => !!p);
901
+ ir = { ...ir, capabilityView: serializeView(buildView(ir, resolvePacks(packs))) };
902
+ }
897
903
  return emitIr(ir, ctx, format);
898
904
  }
899
905
  /** Emit a built IR in the requested view format — shared by the source
@@ -1 +1 @@
1
- {"version":3,"file":"pr-env.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/pr-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AA2CH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAUlD,8CAA8C;AAC9C,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7E"}
1
+ {"version":3,"file":"pr-env.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/pr-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AA0CH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAUlD,8CAA8C;AAC9C,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7E"}
@@ -40,7 +40,8 @@ import { loadChantConfig } from "../../config.js";
40
40
  import { ENV_VAR } from "../../env.js";
41
41
  import { isEmptyChangeSet } from "../../change-set.js";
42
42
  import { PR_ENV_EXIT, PR_ENV_GATE, PR_ENV_RECORD_FILE, PR_ENV_REPORT_DIR, acceptHandedRecord, handedPrEnvReport, PR_ENV_TEMPLATE_VAR, derivePrEnvironment, prEnvApproveCommand, prEnvStatusState, prEnvironmentName, prEnvironmentSite, renderPrEnvNote, ttlMs, } from "../../components/pr-environment.js";
43
- import { commandTeardownAdapter, forgePrEnvPublisher, lifecyclePrEnvPorts, prEnvDown as prEnvDownRun, prEnvOp, prEnvSweep as prEnvSweepRun, prEnvUp as prEnvUpRun, } from "../../components/pr-env-run.js";
43
+ import { commandTeardownAdapter, prEnvDown as prEnvDownRun, prEnvOp, prEnvSweep as prEnvSweepRun, prEnvUp as prEnvUpRun, } from "../../components/pr-env-run.js";
44
+ import { forgePrEnvPublisher, lifecyclePrEnvPorts } from "../../components/pr-env-ports.js";
44
45
  import { applyPrSet } from "../../components/pr-run.js";
45
46
  import { prOp } from "../../pr-loop.js";
46
47
  import { FORGE_KINDS, forgeFromEnv, forgePrincipalOf } from "../../pr-forge.js";
@@ -0,0 +1,89 @@
1
+ import type { GraphIR, IRNode } from "../../graph-ir.js";
2
+ import { terraformType } from "../../graph-effective.js";
3
+ export { terraformType };
4
+ /**
5
+ * `chant search --count-by <kind|module|attr:<name>>` (#4360): grouped counts
6
+ * over the matched set, one line per group, instead of one row per match.
7
+ *
8
+ * An agent asking "how many resources of each type does each module own" used
9
+ * to run one search per value, or read every row and count them itself. The
10
+ * second is the token cost `search` exists to avoid.
11
+ *
12
+ * Every matched node lands in exactly one group. A node with no value for the
13
+ * grouped dimension is counted under `(none)`, never dropped, so the groups
14
+ * always add up to the total line.
15
+ */
16
+ export type CountBy = {
17
+ by: "kind";
18
+ } | {
19
+ by: "module";
20
+ } | {
21
+ by: "attr";
22
+ name: string;
23
+ };
24
+ /** The group a node with no value for the grouped dimension is counted under. */
25
+ export declare const NONE_GROUP = "(none)";
26
+ /** The module group of a Terraform block declared in the root module itself. */
27
+ export declare const ROOT_MODULE_GROUP = "(root)";
28
+ /** Parse a `--count-by` value, or say what the accepted spellings are. */
29
+ export declare function parseCountBy(raw: string): CountBy | {
30
+ error: string;
31
+ hint: string;
32
+ };
33
+ /**
34
+ * The group one node belongs to.
35
+ *
36
+ * `module` on a Terraform node is its call chain. When the graph holds more
37
+ * than one Terraform root, the chain is prefixed with the root (`app/(root)`,
38
+ * `net/module.vpc`): `module.vpc` in two roots is two modules, applied
39
+ * separately, and counting them as one would add up resources that never sit
40
+ * in the same state. A node of another lexicon groups under the composite that
41
+ * expanded it, chant's own unit of reuse, or `(none)`.
42
+ */
43
+ export declare function groupOf(n: IRNode, countBy: CountBy, multiRoot?: boolean): string;
44
+ /** Whether the graph holds blocks from more than one Terraform root. */
45
+ export declare function hasManyTerraformRoots(ir: GraphIR): boolean;
46
+ /** Count `matches` per group, sorted by count (highest first) and then by name. */
47
+ export declare function countGroups(matches: IRNode[], countBy: CountBy, multiRoot?: boolean): Array<{
48
+ group: string;
49
+ count: number;
50
+ }>;
51
+ /**
52
+ * The groups with the ids of the matches in each, in match order (#4382), in
53
+ * the order {@link countGroups} gives. `count` is always `ids.length`. A
54
+ * reader that draws the groups (behold#922) lights each group's nodes from
55
+ * `ids`, so it never has to read a node's group itself.
56
+ */
57
+ export declare function groupMembers(matches: IRNode[], countBy: CountBy, multiRoot?: boolean): Array<{
58
+ group: string;
59
+ count: number;
60
+ ids: string[];
61
+ }>;
62
+ /** `--count-by` with `--json`: the groups, in printed order, each with its matched ids, and the total they add up to. */
63
+ export interface CountsJson {
64
+ query: string;
65
+ source: unknown;
66
+ countBy: string;
67
+ groups: Array<{
68
+ group: string;
69
+ count: number;
70
+ ids: string[];
71
+ }>;
72
+ total: number;
73
+ liveFailures?: string[];
74
+ }
75
+ export declare function countsJson(query: string, source: unknown, groups: Array<{
76
+ group: string;
77
+ count: number;
78
+ ids: string[];
79
+ }>, countBy: CountBy, liveFailures?: string[]): CountsJson;
80
+ /**
81
+ * Render grouped counts: one `<count> <group>` line per group, counts right
82
+ * aligned, then the total. The total is the number of matches, which is the
83
+ * sum of the group counts by construction.
84
+ */
85
+ export declare function formatCounts(groups: Array<{
86
+ group: string;
87
+ count: number;
88
+ }>, countBy: CountBy): string[];
89
+ //# sourceMappingURL=search-count.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-count.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/search-count.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,OAAO,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvF,iFAAiF;AACjF,eAAO,MAAM,UAAU,WAAW,CAAC;AACnC,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAUnF;AAiCD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,UAAQ,GAAG,MAAM,CAQ9E;AAED,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAI1D;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,UAAQ,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAE3H;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,UAAQ,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAW3I;AAED,yHAAyH;AACzH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,EAC9D,OAAO,EAAE,OAAO,EAChB,YAAY,GAAE,MAAM,EAAO,GAC1B,UAAU,CASZ;AAOD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAMxG"}
@@ -0,0 +1,132 @@
1
+ import { terraformType } from "../../graph-effective.js";
2
+ // The one reading of a terraform node's type lives with the folds that also
3
+ // select on it, so `--count-by kind` and `kind:` cannot disagree (#4366).
4
+ export { terraformType };
5
+ /** The group a node with no value for the grouped dimension is counted under. */
6
+ export const NONE_GROUP = "(none)";
7
+ /** The module group of a Terraform block declared in the root module itself. */
8
+ export const ROOT_MODULE_GROUP = "(root)";
9
+ /** Parse a `--count-by` value, or say what the accepted spellings are. */
10
+ export function parseCountBy(raw) {
11
+ const value = raw.trim();
12
+ if (value === "kind")
13
+ return { by: "kind" };
14
+ if (value === "module")
15
+ return { by: "module" };
16
+ const attr = /^attr:(.+)$/.exec(value);
17
+ if (attr && attr[1].trim())
18
+ return { by: "attr", name: attr[1].trim() };
19
+ return {
20
+ error: `--count-by "${raw}" is not a grouping`,
21
+ hint: "group by one of: --count-by kind, --count-by module, --count-by attr:<name>",
22
+ };
23
+ }
24
+ /** A Terraform node's root name, from its attrs or the first segment of its id. */
25
+ function terraformRoot(n) {
26
+ const attrs = (n.attrs ?? {});
27
+ if (typeof attrs.root === "string")
28
+ return attrs.root;
29
+ const slash = n.id.indexOf("/");
30
+ return slash === -1 ? "" : n.id.slice(0, slash);
31
+ }
32
+ /**
33
+ * The module a Terraform node sits in: its call chain as Terraform writes it
34
+ * (`module.cdn`, `module.cdn.module.bucket`), or `(root)` for a block of the
35
+ * root module itself. The chain is on `attrs.callers`; an entity key
36
+ * `<root>/module.cdn/<address>` carries the same chain when it is absent.
37
+ */
38
+ function terraformModule(n) {
39
+ const attrs = (n.attrs ?? {});
40
+ const callers = Array.isArray(attrs.callers)
41
+ ? attrs.callers.filter((c) => typeof c === "string")
42
+ : n.id.split("/").slice(1, -1);
43
+ return callers.length === 0 ? ROOT_MODULE_GROUP : callers.join(".");
44
+ }
45
+ /** An attribute's value as a group name, matched by exact name and then case-insensitively, as `--show` does. */
46
+ function attrGroup(n, name) {
47
+ const attrs = (n.attrs ?? {});
48
+ const key = name in attrs ? name : Object.keys(attrs).find((k) => k.toLowerCase() === name.toLowerCase());
49
+ const v = key === undefined ? undefined : attrs[key];
50
+ if (v === undefined || v === null || v === "")
51
+ return NONE_GROUP;
52
+ return typeof v === "object" ? JSON.stringify(v) : String(v);
53
+ }
54
+ /**
55
+ * The group one node belongs to.
56
+ *
57
+ * `module` on a Terraform node is its call chain. When the graph holds more
58
+ * than one Terraform root, the chain is prefixed with the root (`app/(root)`,
59
+ * `net/module.vpc`): `module.vpc` in two roots is two modules, applied
60
+ * separately, and counting them as one would add up resources that never sit
61
+ * in the same state. A node of another lexicon groups under the composite that
62
+ * expanded it, chant's own unit of reuse, or `(none)`.
63
+ */
64
+ export function groupOf(n, countBy, multiRoot = false) {
65
+ if (countBy.by === "kind")
66
+ return terraformType(n) ?? (n.kind || NONE_GROUP);
67
+ if (countBy.by === "attr")
68
+ return attrGroup(n, countBy.name);
69
+ if (n.lexicon === "terraform") {
70
+ const module = terraformModule(n);
71
+ return multiRoot ? `${terraformRoot(n) || NONE_GROUP}/${module}` : module;
72
+ }
73
+ return n.compositeParent || NONE_GROUP;
74
+ }
75
+ /** Whether the graph holds blocks from more than one Terraform root. */
76
+ export function hasManyTerraformRoots(ir) {
77
+ const roots = new Set();
78
+ for (const n of ir.nodes)
79
+ if (n.lexicon === "terraform")
80
+ roots.add(terraformRoot(n));
81
+ return roots.size > 1;
82
+ }
83
+ /** Count `matches` per group, sorted by count (highest first) and then by name. */
84
+ export function countGroups(matches, countBy, multiRoot = false) {
85
+ return groupMembers(matches, countBy, multiRoot).map(({ group, count }) => ({ group, count }));
86
+ }
87
+ /**
88
+ * The groups with the ids of the matches in each, in match order (#4382), in
89
+ * the order {@link countGroups} gives. `count` is always `ids.length`. A
90
+ * reader that draws the groups (behold#922) lights each group's nodes from
91
+ * `ids`, so it never has to read a node's group itself.
92
+ */
93
+ export function groupMembers(matches, countBy, multiRoot = false) {
94
+ const ids = new Map();
95
+ for (const n of matches) {
96
+ const g = groupOf(n, countBy, multiRoot);
97
+ const list = ids.get(g);
98
+ if (list)
99
+ list.push(n.id);
100
+ else
101
+ ids.set(g, [n.id]);
102
+ }
103
+ return [...ids]
104
+ .map(([group, members]) => ({ group, count: members.length, ids: members }))
105
+ .sort((a, b) => b.count - a.count || (a.group < b.group ? -1 : a.group > b.group ? 1 : 0));
106
+ }
107
+ export function countsJson(query, source, groups, countBy, liveFailures = []) {
108
+ return {
109
+ query,
110
+ source,
111
+ countBy: countByLabel(countBy),
112
+ groups,
113
+ total: groups.reduce((sum, g) => sum + g.count, 0),
114
+ ...(liveFailures.length > 0 ? { liveFailures } : {}),
115
+ };
116
+ }
117
+ /** The label a `--count-by` value is printed with on the total line. */
118
+ function countByLabel(countBy) {
119
+ return countBy.by === "attr" ? `attr:${countBy.name}` : countBy.by;
120
+ }
121
+ /**
122
+ * Render grouped counts: one `<count> <group>` line per group, counts right
123
+ * aligned, then the total. The total is the number of matches, which is the
124
+ * sum of the group counts by construction.
125
+ */
126
+ export function formatCounts(groups, countBy) {
127
+ const total = groups.reduce((sum, g) => sum + g.count, 0);
128
+ const width = String(groups[0]?.count ?? 0).length;
129
+ const lines = groups.map((g) => `${String(g.count).padStart(width)} ${g.group}`);
130
+ lines.push(`— ${total} total · ${groups.length} group${groups.length === 1 ? "" : "s"} by ${countByLabel(countBy)}`);
131
+ return lines;
132
+ }