@davesheffer/hunch 1.38.0 → 1.39.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/dist/cli/index.js +255 -9
- package/dist/cli/serve.js +1 -0
- package/dist/client/readOrCompute.d.ts +77 -0
- package/dist/client/readOrCompute.js +85 -0
- package/dist/client/state.d.ts +1 -0
- package/dist/client/state.js +1 -0
- package/dist/constitution/g2.d.ts +1 -0
- package/dist/constitution/service.js +8 -0
- package/dist/constitution/sourceMutation.js +23 -18
- package/dist/core/config.d.ts +16 -0
- package/dist/core/config.js +13 -0
- package/dist/core/machine.d.ts +20 -0
- package/dist/core/machine.js +101 -0
- package/dist/core/taskRecord.js +6 -3
- package/dist/core/taskReport.d.ts +25 -2
- package/dist/core/taskReport.js +92 -18
- package/dist/core/taskReportEvidence.d.ts +4 -1
- package/dist/core/taskReportEvidence.js +5 -2
- package/dist/core/taskReportHook.d.ts +16 -3
- package/dist/core/taskReportHook.js +65 -13
- package/dist/core/taskTouched.d.ts +4 -0
- package/dist/core/taskTouched.js +30 -10
- package/dist/core/types.d.ts +66 -1
- package/dist/core/types.js +3 -0
- package/dist/core/workspace.d.ts +234 -0
- package/dist/core/workspace.js +335 -0
- package/dist/extractors/helm.d.ts +17 -28
- package/dist/extractors/helm.js +12 -12
- package/dist/extractors/indexer.js +171 -7
- package/dist/extractors/k8sManifest.d.ts +59 -0
- package/dist/extractors/k8sManifest.js +507 -0
- package/dist/extractors/workspaces.d.ts +18 -0
- package/dist/extractors/workspaces.js +350 -0
- package/dist/integrations/claudemd.js +1 -0
- package/dist/integrations/hooks.d.ts +2 -0
- package/dist/integrations/hooks.js +25 -0
- package/dist/integrations/scaffold.js +11 -0
- package/dist/integrations/workspaceLedger.d.ts +73 -0
- package/dist/integrations/workspaceLedger.js +201 -0
- package/dist/mcp/server.js +54 -0
- package/dist/mcp/taskReportTools.d.ts +9 -0
- package/dist/mcp/taskReportTools.js +13 -5
- package/dist/serve/app.d.ts +2 -0
- package/dist/serve/app.js +107 -92
- package/dist/serve/mcpHttp.d.ts +27 -0
- package/dist/serve/mcpHttp.js +95 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { readFileSync } from "node:fs";
|
|
12
12
|
import { dirname, join, posix } from "node:path";
|
|
13
|
-
import { parseSource, attributeCalls, attributeRelations } from "./parse.js";
|
|
13
|
+
import { parseSource, attributeCalls, attributeRelations, MAX_BODY_TEXT_CHARS } from "./parse.js";
|
|
14
14
|
import { extractHelmDirectives } from "./helm.js";
|
|
15
|
+
import { extractK8sManifest } from "./k8sManifest.js";
|
|
15
16
|
import { symbolId, componentId, edgeId, sha1 } from "../core/ids.js";
|
|
16
17
|
import { externalImportNodeId, externalPackage } from "../core/externalImports.js";
|
|
17
18
|
import { resolveRelativeImport } from "../core/relativeImports.js";
|
|
@@ -100,6 +101,10 @@ export function scanRepo(store, root, opts = {}) {
|
|
|
100
101
|
const perFileCalls = [];
|
|
101
102
|
const perFileImports = [];
|
|
102
103
|
const perFileRelations = [];
|
|
104
|
+
const k8sResourceIndex = [];
|
|
105
|
+
const k8sReferenceCandidates = [];
|
|
106
|
+
const k8sSelectors = [];
|
|
107
|
+
const k8sWorkloadLabels = [];
|
|
103
108
|
const phpNamespaces = new Map();
|
|
104
109
|
const phpUseDeclarations = new Map();
|
|
105
110
|
// Batched per-file git metrics (churn + last commit) in TWO `git log` spawns
|
|
@@ -113,7 +118,7 @@ export function scanRepo(store, root, opts = {}) {
|
|
|
113
118
|
const chartRoot = chartRootFor(path);
|
|
114
119
|
if (chartRoot === null)
|
|
115
120
|
continue;
|
|
116
|
-
(chartFiles
|
|
121
|
+
pushInto(chartFiles, chartRoot, path);
|
|
117
122
|
}
|
|
118
123
|
const gitMeta = useGit ? fileGitMetrics(root, rels, opts.churn === false ? 0 : 90) : null;
|
|
119
124
|
let skipped = 0;
|
|
@@ -165,8 +170,50 @@ export function scanRepo(store, root, opts = {}) {
|
|
|
165
170
|
// JS-falsy — `if (chartRoot)` would silently skip every repo-root chart.
|
|
166
171
|
if (chartRoot !== null) {
|
|
167
172
|
const helm = extractHelmDirectives(src);
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
// helm.ts's own offsets are named *Char (they're JS char indices, not
|
|
174
|
+
// UTF-8 bytes -- see its module doc comment); mapped here into
|
|
175
|
+
// parsed.symbols/calls's startByte/endByte/atByte fields, which carry
|
|
176
|
+
// the same char-index values under the shared ParsedSymbol/ParsedCall
|
|
177
|
+
// naming this merge target already uses.
|
|
178
|
+
const helmSymbols = helm.symbols.map((s) => ({
|
|
179
|
+
name: s.name, kind: s.kind, startByte: s.startChar, endByte: s.endChar, loc: s.loc, bodyText: s.bodyText,
|
|
180
|
+
}));
|
|
181
|
+
const helmCalls = helm.calls.map((c) => ({
|
|
182
|
+
callee: c.callee, atByte: c.atChar, endByte: c.endChar, member: c.member,
|
|
183
|
+
}));
|
|
184
|
+
parsed.symbols = [...parsed.symbols, ...helmSymbols].sort((a, b) => a.startByte - b.startByte);
|
|
185
|
+
parsed.calls = [...parsed.calls, ...helmCalls];
|
|
186
|
+
}
|
|
187
|
+
// Runs for EVERY yaml file, chart or not (unlike the Helm merge above) --
|
|
188
|
+
// raw manifests with no Chart.yaml are still in scope; what varies below is
|
|
189
|
+
// resolution SCOPE (chartRoot ?? this file), not whether extraction runs.
|
|
190
|
+
let k8sDocs = [];
|
|
191
|
+
// THIS file's K8s resource symbol OBJECTS (identity, not their byte
|
|
192
|
+
// offsets) -- scopes the id lookup below to exactly the symbols this pass
|
|
193
|
+
// creates. Identity, not a Set<number> of startBytes, because a byte
|
|
194
|
+
// value is not a reliable per-symbol key: a Helm `define` symbol that
|
|
195
|
+
// happens to share a startByte with a K8s doc symbol in the same .yaml
|
|
196
|
+
// file (both legitimately synthetic, both can start at byte 0) would
|
|
197
|
+
// otherwise be indistinguishable by offset alone, and the wrong (Helm)
|
|
198
|
+
// symbol id could get recorded instead of the K8s one.
|
|
199
|
+
const k8sSymbolObjects = new Set();
|
|
200
|
+
if (languageFor(rel)?.id === "yaml") {
|
|
201
|
+
k8sDocs = extractK8sManifest(src);
|
|
202
|
+
const k8sSymbols = k8sDocs
|
|
203
|
+
.filter((d) => d.resource !== null)
|
|
204
|
+
// k8sManifest.ts's own offsets are named *Char; mapped here into the
|
|
205
|
+
// shared startByte/endByte fields, same as the Helm merge above.
|
|
206
|
+
.map((d) => ({
|
|
207
|
+
name: `${d.resource.kind}/${displayNameText(d.resource.name)}`,
|
|
208
|
+
kind: "variable",
|
|
209
|
+
startByte: d.resource.startChar,
|
|
210
|
+
endByte: d.resource.endChar,
|
|
211
|
+
loc: src.slice(d.resource.startChar, d.resource.endChar).split("\n").length,
|
|
212
|
+
bodyText: src.slice(d.resource.startChar, d.resource.endChar).slice(0, MAX_BODY_TEXT_CHARS),
|
|
213
|
+
}));
|
|
214
|
+
for (const s of k8sSymbols)
|
|
215
|
+
k8sSymbolObjects.add(s);
|
|
216
|
+
parsed.symbols = [...parsed.symbols, ...k8sSymbols].sort((a, b) => a.startByte - b.startByte);
|
|
170
217
|
}
|
|
171
218
|
const m = gitMeta?.get(rel);
|
|
172
219
|
const churn = opts.churn === false ? (preservedChurn.get(rel) ?? 0) : (m?.churn ?? 0);
|
|
@@ -174,6 +221,7 @@ export function scanRepo(store, root, opts = {}) {
|
|
|
174
221
|
const idsInFile = [];
|
|
175
222
|
const symbolIndexId = new Map();
|
|
176
223
|
const idCounts = new Map(); // disambiguate same (file,name,kind)
|
|
224
|
+
const k8sSymbolIdByStartByte = new Map();
|
|
177
225
|
for (const [index, ps] of parsed.symbols.entries()) {
|
|
178
226
|
const base = symbolId(rel, ps.name, ps.kind);
|
|
179
227
|
const n = idCounts.get(base) ?? 0;
|
|
@@ -182,7 +230,7 @@ export function scanRepo(store, root, opts = {}) {
|
|
|
182
230
|
const id = n === 0 ? base : `${base}_${n}`;
|
|
183
231
|
idsInFile.push(id);
|
|
184
232
|
symbolIndexId.set(index, id);
|
|
185
|
-
(nameIndex
|
|
233
|
+
pushInto(nameIndex, ps.name, id);
|
|
186
234
|
symbols.push({
|
|
187
235
|
id, file: rel, name: ps.name, kind: ps.kind,
|
|
188
236
|
signature_hash: sha1(ps.bodyText).slice(0, 16),
|
|
@@ -190,6 +238,27 @@ export function scanRepo(store, root, opts = {}) {
|
|
|
190
238
|
metrics: { loc: ps.loc, churn_90d: churn, bug_count: 0, fan_in: 0, fan_out: 0 },
|
|
191
239
|
last_changed: last,
|
|
192
240
|
});
|
|
241
|
+
if (k8sSymbolObjects.has(ps))
|
|
242
|
+
k8sSymbolIdByStartByte.set(ps.startByte, id);
|
|
243
|
+
}
|
|
244
|
+
for (const doc of k8sDocs) {
|
|
245
|
+
if (!doc.resource)
|
|
246
|
+
continue;
|
|
247
|
+
const fromId = k8sSymbolIdByStartByte.get(doc.resource.startChar);
|
|
248
|
+
if (!fromId)
|
|
249
|
+
continue;
|
|
250
|
+
const scope = chartRoot ?? rel;
|
|
251
|
+
k8sResourceIndex.push({ symbolId: fromId, scope, kind: doc.resource.kind, nameKey: nameKeyText(doc.resource.name) });
|
|
252
|
+
for (const ref of doc.references) {
|
|
253
|
+
k8sReferenceCandidates.push({
|
|
254
|
+
fromSymbolId: fromId, scope, refKind: ref.refKind, nameKey: nameKeyText(ref.name),
|
|
255
|
+
reason: `${doc.resource.kind}/${displayNameText(doc.resource.name)} references ${ref.refKind}/${displayNameText(ref.name)}`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (doc.selector)
|
|
259
|
+
k8sSelectors.push({ symbolId: fromId, scope, selector: doc.selector });
|
|
260
|
+
if (doc.labels)
|
|
261
|
+
k8sWorkloadLabels.push({ symbolId: fromId, scope, labels: doc.labels });
|
|
193
262
|
}
|
|
194
263
|
fileSymbols.set(rel, idsInFile);
|
|
195
264
|
fileSymbolIndexId.set(rel, symbolIndexId);
|
|
@@ -285,6 +354,77 @@ export function scanRepo(store, root, opts = {}) {
|
|
|
285
354
|
}
|
|
286
355
|
}
|
|
287
356
|
}
|
|
357
|
+
// ---- K8s manifest cross-resource references (Phase 1: name-keyed) --------
|
|
358
|
+
// Own resolver, not resolveName(): resolveName() indexes by bare symbol name
|
|
359
|
+
// only, with no concept of Kubernetes kind -- a ConfigMap and a Secret that
|
|
360
|
+
// happen to share a name would incorrectly conflate. Same ambiguity contract
|
|
361
|
+
// as resolveName() though: 0 matches or 2+ matches -> no edge, never guess.
|
|
362
|
+
const kindNameIndex = new Map();
|
|
363
|
+
for (const r of k8sResourceIndex) {
|
|
364
|
+
const key = `${r.scope}:${r.kind}:${r.nameKey}`;
|
|
365
|
+
pushInto(kindNameIndex, key, r.symbolId);
|
|
366
|
+
}
|
|
367
|
+
for (const ref of k8sReferenceCandidates) {
|
|
368
|
+
const candidates = kindNameIndex.get(`${ref.scope}:${ref.refKind}:${ref.nameKey}`) ?? [];
|
|
369
|
+
if (candidates.length !== 1)
|
|
370
|
+
continue; // 0 or 2+ -> ambiguous or absent, don't guess
|
|
371
|
+
const toId = candidates[0];
|
|
372
|
+
if (toId === ref.fromSymbolId)
|
|
373
|
+
continue;
|
|
374
|
+
addEdge({
|
|
375
|
+
schema: "hunch.edge/1",
|
|
376
|
+
id: edgeId(ref.fromSymbolId, toId, "references"),
|
|
377
|
+
from: ref.fromSymbolId, to: toId, type: "references",
|
|
378
|
+
reason: ref.reason, strength: 0.7,
|
|
379
|
+
provenance: extracted(0.7, [ref.scope]),
|
|
380
|
+
environment: null,
|
|
381
|
+
metadata: {},
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
// ---- K8s manifest cross-resource references (Phase 2: label-selector) ----
|
|
385
|
+
// Structurally different from Phase 1: no name to look up, a SUBSET match
|
|
386
|
+
// between a Service's selector and a workload's pod-template labels, within
|
|
387
|
+
// the same scope. Only ever fires on LITERAL selector/labels (k8sSelectors/
|
|
388
|
+
// k8sWorkloadLabels are already filtered to literal-only by k8sManifest.ts --
|
|
389
|
+
// a block-form templated value is never guessed at).
|
|
390
|
+
//
|
|
391
|
+
// Deliberately NO ambiguity guard here, unlike Phase 1's "0 or 2+ candidates
|
|
392
|
+
// -> no edge": a Service legitimately fronting multiple workloads (blue/green,
|
|
393
|
+
// canary, a shared-label pair of Deployments) is normal, intentional
|
|
394
|
+
// Kubernetes usage, not an ambiguous match to decline -- Phase 1's guard
|
|
395
|
+
// exists because a ConfigMap named X is exactly one resource by definition,
|
|
396
|
+
// which has no analogue here. Fan-out is the correct behavior, not a gap.
|
|
397
|
+
const selectorsByScope = new Map();
|
|
398
|
+
for (const s of k8sSelectors)
|
|
399
|
+
pushInto(selectorsByScope, s.scope, s);
|
|
400
|
+
const labelsByScope = new Map();
|
|
401
|
+
for (const l of k8sWorkloadLabels)
|
|
402
|
+
pushInto(labelsByScope, l.scope, l);
|
|
403
|
+
for (const [scope, selectors] of selectorsByScope) {
|
|
404
|
+
const workloads = labelsByScope.get(scope) ?? [];
|
|
405
|
+
for (const svc of selectors) {
|
|
406
|
+
for (const wl of workloads) {
|
|
407
|
+
// Defensive, not currently reachable: k8sSelectors only ever holds
|
|
408
|
+
// Service symbols and k8sWorkloadLabels only ever holds symbols for
|
|
409
|
+
// kinds in LABELS_PATH_BY_KIND, which excludes Service -- so the two
|
|
410
|
+
// ids can never collide today.
|
|
411
|
+
if (svc.symbolId === wl.symbolId)
|
|
412
|
+
continue;
|
|
413
|
+
const isSubset = Object.entries(svc.selector).every(([k, v]) => wl.labels[k] === v);
|
|
414
|
+
if (!isSubset)
|
|
415
|
+
continue;
|
|
416
|
+
addEdge({
|
|
417
|
+
schema: "hunch.edge/1",
|
|
418
|
+
id: edgeId(svc.symbolId, wl.symbolId, "references"),
|
|
419
|
+
from: svc.symbolId, to: wl.symbolId, type: "references",
|
|
420
|
+
reason: "Service selector matches workload pod-template labels", strength: 0.6,
|
|
421
|
+
provenance: extracted(0.6, [scope]),
|
|
422
|
+
environment: null,
|
|
423
|
+
metadata: {},
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
288
428
|
// PHP's static type relationships use the same symbol graph and conservative
|
|
289
429
|
// resolver as calls. Ambiguous or dynamic targets produce no edge.
|
|
290
430
|
for (const { file, bySym } of perFileRelations) {
|
|
@@ -441,6 +581,16 @@ export function indexRepo(store, root, opts = {}) {
|
|
|
441
581
|
return scan.result;
|
|
442
582
|
}
|
|
443
583
|
// ---- helpers --------------------------------------------------------------
|
|
584
|
+
/** Append `value` to the array at `key`, creating the array on first use.
|
|
585
|
+
* Function declaration (not `const`) so it's usable from pass-1 code above
|
|
586
|
+
* this section via hoisting, without reordering. */
|
|
587
|
+
function pushInto(map, key, value) {
|
|
588
|
+
const bucket = map.get(key);
|
|
589
|
+
if (bucket)
|
|
590
|
+
bucket.push(value);
|
|
591
|
+
else
|
|
592
|
+
map.set(key, [value]);
|
|
593
|
+
}
|
|
444
594
|
/** Nearest-ancestor Chart.yaml lookup, memoized per directory: walks a file's
|
|
445
595
|
* own directory upward through the tracked-file set until it finds
|
|
446
596
|
* `<dir>/Chart.yaml`, or returns null if the file isn't under any chart.
|
|
@@ -449,8 +599,9 @@ export function indexRepo(store, root, opts = {}) {
|
|
|
449
599
|
* approximation, not full Helm semantics: Helm's template namespace is
|
|
450
600
|
* actually release-global, so a parent chart can legitimately include a
|
|
451
601
|
* subchart's define — nearest-ancestor scoping will miss that edge rather
|
|
452
|
-
* than fabricate a wrong one
|
|
453
|
-
*
|
|
602
|
+
* than fabricate a wrong one (test coverage: indexer.test.ts's nested-subchart
|
|
603
|
+
* and subchart-miss cases). Modeling the release-global namespace itself
|
|
604
|
+
* remains open — issue #42. */
|
|
454
605
|
function nearestChartRoot(rels) {
|
|
455
606
|
const tracked = new Set(rels);
|
|
456
607
|
const cache = new Map();
|
|
@@ -466,6 +617,19 @@ function nearestChartRoot(rels) {
|
|
|
466
617
|
};
|
|
467
618
|
return (file) => resolveDir(file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "");
|
|
468
619
|
}
|
|
620
|
+
/** Human-readable text for a name/kind field -- a literal value as-is, or a
|
|
621
|
+
* template's exact raw `{{ }}` source text (never evaluated). Used for
|
|
622
|
+
* display (symbol names, edge reasons); NOT for resolution-key equality --
|
|
623
|
+
* see nameKeyText below for that. */
|
|
624
|
+
function displayNameText(ref) {
|
|
625
|
+
return ref.form === "literal" ? ref.value : ref.sourceText;
|
|
626
|
+
}
|
|
627
|
+
/** Normalized resolution-key text for a name/kind field: a literal value or a
|
|
628
|
+
* template's exact raw source text, EACH PREFIXED so a literal "foo" can
|
|
629
|
+
* never collide with a template whose source text happens to read "foo". */
|
|
630
|
+
function nameKeyText(ref) {
|
|
631
|
+
return ref.form === "literal" ? `L:${ref.value}` : `T:${ref.sourceText}`;
|
|
632
|
+
}
|
|
469
633
|
/** Resolve a callee name to a symbol id: prefer same-file, otherwise require a
|
|
470
634
|
* unique symbol in a statically imported local file. A unique repository-wide
|
|
471
635
|
* name is not evidence of a binding: callback parameters and built-ins often
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic text scan for Kubernetes manifest cross-resource references —
|
|
3
|
+
* NOT a tree-sitter walk. Confirmed directly (not assumed): parsing a realistic
|
|
4
|
+
* templated Deployment with this repo's own tree-sitter-yaml bundle showed a
|
|
5
|
+
* same-line templated scalar (`name: {{ include "x" . }}`) parses cleanly, but
|
|
6
|
+
* a block-form injection on its own line (`labels:\n {{- include ... }}` — the
|
|
7
|
+
* pattern real charts use for metadata.labels and spec.selector) collapses the
|
|
8
|
+
* WHOLE REST OF THE DOCUMENT'S error recovery into a flat, unstructured ERROR
|
|
9
|
+
* node. Real charts place such an injection early (right after metadata.name),
|
|
10
|
+
* so it would poison parsing for every field-path this module needs further
|
|
11
|
+
* down the same document. parseSource() (parse.ts) also never exposes its
|
|
12
|
+
* tree-sitter tree to callers at all -- helm.ts's own text-scan precedent
|
|
13
|
+
* exists for the same reason.
|
|
14
|
+
*
|
|
15
|
+
* This is a line-oriented, indentation-tracking scanner (not flat regex, unlike
|
|
16
|
+
* helm.ts's `{{ }}`-action scan) because K8s field-paths are nested block
|
|
17
|
+
* structure (`spec.template.spec.containers[].env[].valueFrom.secretKeyRef.name`)
|
|
18
|
+
* that a flat scan can't reconstruct. It never interprets `{{ }}` as YAML syntax
|
|
19
|
+
* at all -- a `{{ ... }}` on a line's value side is just that line's raw value
|
|
20
|
+
* text, same-line or block-form alike -- so it is immune to the exact failure
|
|
21
|
+
* mode that broke tree-sitter.
|
|
22
|
+
*/
|
|
23
|
+
export type ManifestNameRef = {
|
|
24
|
+
form: "literal";
|
|
25
|
+
value: string;
|
|
26
|
+
atChar: number;
|
|
27
|
+
endChar: number;
|
|
28
|
+
} | {
|
|
29
|
+
form: "template";
|
|
30
|
+
sourceText: string;
|
|
31
|
+
atChar: number;
|
|
32
|
+
endChar: number;
|
|
33
|
+
};
|
|
34
|
+
interface K8sReferenceCandidate {
|
|
35
|
+
/** Fixed literal for most kinds ("ConfigMap", "Secret", "PersistentVolumeClaim",
|
|
36
|
+
* "Service"); for an ownerReferences entry, the reference's OWN `kind` field
|
|
37
|
+
* value (data-dependent, not statically known). */
|
|
38
|
+
refKind: string;
|
|
39
|
+
name: ManifestNameRef;
|
|
40
|
+
}
|
|
41
|
+
type ManifestLabelMap = Record<string, string>;
|
|
42
|
+
interface K8sResourceDoc {
|
|
43
|
+
kind: string;
|
|
44
|
+
name: ManifestNameRef;
|
|
45
|
+
startChar: number;
|
|
46
|
+
endChar: number;
|
|
47
|
+
}
|
|
48
|
+
export interface K8sManifestDocument {
|
|
49
|
+
resource: K8sResourceDoc | null;
|
|
50
|
+
references: K8sReferenceCandidate[];
|
|
51
|
+
selector: ManifestLabelMap | null;
|
|
52
|
+
labels: ManifestLabelMap | null;
|
|
53
|
+
}
|
|
54
|
+
/** Where a kind's pod spec lives -- factored once so container/volume field
|
|
55
|
+
* paths below aren't hand-duplicated per kind. */
|
|
56
|
+
export declare const POD_SPEC_PATH_BY_KIND: Record<string, string>;
|
|
57
|
+
export declare const LABELS_PATH_BY_KIND: Record<string, string>;
|
|
58
|
+
export declare function extractK8sManifest(source: string): K8sManifestDocument[];
|
|
59
|
+
export {};
|