@untangle-code/context-artifacts 0.0.0-stage → 0.1.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.
- package/LICENSE +661 -0
- package/NOTICE +90 -0
- package/README.md +247 -2
- package/THIRD_PARTY_LICENSES.md +24 -0
- package/dist/adapters/asyncapi.d.ts +20 -0
- package/dist/adapters/asyncapi.d.ts.map +1 -0
- package/dist/adapters/asyncapi.js +95 -0
- package/dist/adapters/asyncapi.js.map +1 -0
- package/dist/adapters/docker.d.ts +43 -0
- package/dist/adapters/docker.d.ts.map +1 -0
- package/dist/adapters/docker.js +97 -0
- package/dist/adapters/docker.js.map +1 -0
- package/dist/adapters/kubernetes.d.ts +22 -0
- package/dist/adapters/kubernetes.d.ts.map +1 -0
- package/dist/adapters/kubernetes.js +56 -0
- package/dist/adapters/kubernetes.js.map +1 -0
- package/dist/adapters/markdown.d.ts +35 -0
- package/dist/adapters/markdown.d.ts.map +1 -0
- package/dist/adapters/markdown.js +36 -0
- package/dist/adapters/markdown.js.map +1 -0
- package/dist/adapters/openapi.d.ts +21 -0
- package/dist/adapters/openapi.d.ts.map +1 -0
- package/dist/adapters/openapi.js +101 -0
- package/dist/adapters/openapi.js.map +1 -0
- package/dist/adapters/protobuf.d.ts +34 -0
- package/dist/adapters/protobuf.d.ts.map +1 -0
- package/dist/adapters/protobuf.js +221 -0
- package/dist/adapters/protobuf.js.map +1 -0
- package/dist/adapters/shared.d.ts +65 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +95 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/sql.d.ts +104 -0
- package/dist/adapters/sql.d.ts.map +1 -0
- package/dist/adapters/sql.js +427 -0
- package/dist/adapters/sql.js.map +1 -0
- package/dist/adapters/terraform.d.ts +40 -0
- package/dist/adapters/terraform.d.ts.map +1 -0
- package/dist/adapters/terraform.js +186 -0
- package/dist/adapters/terraform.js.map +1 -0
- package/dist/adapters/text.d.ts +24 -0
- package/dist/adapters/text.d.ts.map +1 -0
- package/dist/adapters/text.js +31 -0
- package/dist/adapters/text.js.map +1 -0
- package/dist/config-validation.d.ts +18 -0
- package/dist/config-validation.d.ts.map +1 -0
- package/dist/config-validation.js +30 -0
- package/dist/config-validation.js.map +1 -0
- package/dist/coverage.d.ts +114 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +0 -0
- package/dist/coverage.js.map +1 -0
- package/dist/ids.d.ts +12 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +42 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +51 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +86 -0
- package/dist/registry.js.map +1 -0
- package/dist/staleness.d.ts +44 -0
- package/dist/staleness.d.ts.map +1 -0
- package/dist/staleness.js +35 -0
- package/dist/staleness.js.map +1 -0
- package/dist/text/delimiter-balance.d.ts +95 -0
- package/dist/text/delimiter-balance.d.ts.map +1 -0
- package/dist/text/delimiter-balance.js +209 -0
- package/dist/text/delimiter-balance.js.map +1 -0
- package/dist/text/line-index.d.ts +61 -0
- package/dist/text/line-index.d.ts.map +1 -0
- package/dist/text/line-index.js +75 -0
- package/dist/text/line-index.js.map +1 -0
- package/dist/text/yaml-lite.d.ts +73 -0
- package/dist/text/yaml-lite.d.ts.map +1 -0
- package/dist/text/yaml-lite.js +486 -0
- package/dist/text/yaml-lite.js.map +1 -0
- package/package.json +61 -3
- package/src/adapters/__golden__/adr-0001-sections.json +68 -0
- package/src/adapters/__golden__/app-compose-services.json +24 -0
- package/src/adapters/__golden__/app-image-stages.json +24 -0
- package/src/adapters/__golden__/infra-main-sections.json +46 -0
- package/src/adapters/__golden__/orders-events-operations.json +24 -0
- package/src/adapters/__golden__/orders-proto-sections.json +57 -0
- package/src/adapters/__golden__/orders-schema-statements.json +68 -0
- package/src/adapters/__golden__/users-api-operations.json +46 -0
- package/src/adapters/__golden__/web-manifests-resources.json +24 -0
- package/src/adapters/asyncapi.ts +147 -0
- package/src/adapters/docker.ts +170 -0
- package/src/adapters/kubernetes.ts +103 -0
- package/src/adapters/markdown.ts +88 -0
- package/src/adapters/openapi.ts +156 -0
- package/src/adapters/protobuf.ts +286 -0
- package/src/adapters/shared.ts +151 -0
- package/src/adapters/sql.ts +555 -0
- package/src/adapters/terraform.ts +261 -0
- package/src/adapters/text.ts +72 -0
- package/src/config-validation.ts +41 -0
- package/src/coverage.ts +336 -0
- package/src/ids.ts +57 -0
- package/src/index.ts +105 -0
- package/src/registry.ts +155 -0
- package/src/staleness.ts +69 -0
- package/src/text/delimiter-balance.ts +295 -0
- package/src/text/line-index.ts +108 -0
- package/src/text/yaml-lite.ts +565 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terraform/HCL adapter (architecture.md §20: "Ressourcen, Module,
|
|
3
|
+
* Variablen").
|
|
4
|
+
*
|
|
5
|
+
* Same brace-depth line scanning approach as the Protobuf adapter: a
|
|
6
|
+
* top-level `resource "type" "name" {`, `data "type" "name" {`, `module
|
|
7
|
+
* "name" {`, `variable "name" {`, `output "name" {`, `provider "name" {`,
|
|
8
|
+
* or `locals {` block runs to its matching `}`. Whitespace/comments
|
|
9
|
+
* between blocks are not chunked — `buildArtifactSkipLedger` surfaces
|
|
10
|
+
* those gaps with reason `"whitespace_or_comment"`, never silently.
|
|
11
|
+
*
|
|
12
|
+
* The brace counter skips `"..."` string literals, `#`/`//`/`/* *\/`
|
|
13
|
+
* comments and heredoc bodies (`<<EOT` … `EOT`, WP-118) (WP-81 review, same defect class as the Protobuf adapter's own
|
|
14
|
+
* fix: a `{`/`}` inside a multi-line `/* *\/` comment must never move the
|
|
15
|
+
* depth counter, or it desyncs permanently and "runs to EOF", silently
|
|
16
|
+
* swallowing every following top-level block). `scanLine` carries the
|
|
17
|
+
* open-block-comment state across lines through one continuous scan — the
|
|
18
|
+
* SAME scan that finds block boundaries, not a separate look-ahead with its
|
|
19
|
+
* own independent counter, so there is exactly one source of truth for "is
|
|
20
|
+
* this brace really code right now".
|
|
21
|
+
*
|
|
22
|
+
* KNOWN, UNFIXED gap in the same defect class (documented, not silent):
|
|
23
|
+
* `BLOCK_PATTERNS` match against a full physical LINE (`^\s*keyword...`), so
|
|
24
|
+
* two block openers sharing one physical line (`resource "a" "b" {} module
|
|
25
|
+
* "m" {}`) — valid but extremely unidiomatic HCL that `terraform fmt` would
|
|
26
|
+
* never produce — only detects the first; the second's bytes still land
|
|
27
|
+
* inside the first block's `content` (nothing is dropped from the document,
|
|
28
|
+
* only mis-titled), rather than getting its own chunk. Fixing this fully
|
|
29
|
+
* would need the same byte-precise, sub-line splitting the SQL adapter now
|
|
30
|
+
* uses for multiple statements per line; left as an open issue given how
|
|
31
|
+
* rare well-formed HCL makes this shape.
|
|
32
|
+
*/
|
|
33
|
+
import type {
|
|
34
|
+
ArtifactAdapter,
|
|
35
|
+
ArtifactChunk,
|
|
36
|
+
ArtifactDocument,
|
|
37
|
+
ArtifactInput,
|
|
38
|
+
ArtifactLink,
|
|
39
|
+
} from "@untangle-code/domain";
|
|
40
|
+
|
|
41
|
+
import type { DelimiterScanOptions } from "../text/delimiter-balance.js";
|
|
42
|
+
import { describeDelimiterImbalances, findDelimiterImbalances } from "../text/delimiter-balance.js";
|
|
43
|
+
import { buildLineIndex, splitLines } from "../text/line-index.js";
|
|
44
|
+
|
|
45
|
+
import type { ChunkDraft, ChunkPlausibilityCheck, SkipLedgerReporter } from "./shared.js";
|
|
46
|
+
import { buildChunksReportingSkips, buildDocuments } from "./shared.js";
|
|
47
|
+
|
|
48
|
+
const TERRAFORM_EXTENSION = /\.(tf|tf\.json|hcl)$/i;
|
|
49
|
+
|
|
50
|
+
interface BlockPattern {
|
|
51
|
+
kind: string;
|
|
52
|
+
re: RegExp;
|
|
53
|
+
title: (match: RegExpExecArray) => string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const BLOCK_PATTERNS: BlockPattern[] = [
|
|
57
|
+
{
|
|
58
|
+
kind: "terraform_resource",
|
|
59
|
+
re: /^\s*resource\s+"([^"]+)"\s+"([^"]+)"\s*\{/,
|
|
60
|
+
title: (m) => `${m[1]!}.${m[2]!}`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
kind: "terraform_data",
|
|
64
|
+
re: /^\s*data\s+"([^"]+)"\s+"([^"]+)"\s*\{/,
|
|
65
|
+
title: (m) => `${m[1]!}.${m[2]!}`,
|
|
66
|
+
},
|
|
67
|
+
{ kind: "terraform_module", re: /^\s*module\s+"([^"]+)"\s*\{/, title: (m) => m[1]! },
|
|
68
|
+
{ kind: "terraform_variable", re: /^\s*variable\s+"([^"]+)"\s*\{/, title: (m) => m[1]! },
|
|
69
|
+
{ kind: "terraform_output", re: /^\s*output\s+"([^"]+)"\s*\{/, title: (m) => m[1]! },
|
|
70
|
+
{ kind: "terraform_provider", re: /^\s*provider\s+"([^"]+)"\s*\{/, title: (m) => m[1]! },
|
|
71
|
+
{ kind: "terraform_locals", re: /^\s*locals\s*\{/, title: () => "locals" },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Öffner eines Heredocs (`<<EOT` / `<<-EOT`); die Gruppe ist die Endmarke.
|
|
76
|
+
* Sticky, damit er an einer Position geprüft werden kann, ohne die Zeile zu
|
|
77
|
+
* kopieren.
|
|
78
|
+
*/
|
|
79
|
+
const HEREDOC_OPEN = /<<-?([A-Za-z_][A-Za-z0-9_]*)/y;
|
|
80
|
+
|
|
81
|
+
/** Cross-line scanner state: whether the scan is currently inside an open `/* *\/` block comment or heredoc. */
|
|
82
|
+
interface ScanState {
|
|
83
|
+
inBlockComment: boolean;
|
|
84
|
+
/** Endmarke des offenen Heredocs, sonst `undefined`. */
|
|
85
|
+
heredocTag: string | undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Net brace delta for one line, skipping `"..."` string literals and
|
|
90
|
+
* `#`/`//`/`/* *\/` comments. `state.inBlockComment` is mutated for a
|
|
91
|
+
* comment that is still open at the end of this line — the next call for
|
|
92
|
+
* the following line continues from exactly that state. String state is
|
|
93
|
+
* NOT carried across lines: an HCL double-quoted string cannot contain a
|
|
94
|
+
* literal newline (only `\n` escapes), so it is always fully open and
|
|
95
|
+
* closed within one physical line.
|
|
96
|
+
*/
|
|
97
|
+
function scanLine(line: string, state: ScanState): number {
|
|
98
|
+
// WP-118: Ein Heredoc ist ROHTEXT. Ein `{` in einem eingebetteten
|
|
99
|
+
// Shell-Skript (`user_data = <<-EOT ... EOT`) hat den Klammerzähler
|
|
100
|
+
// bisher dauerhaft verschoben — der laufende Block „lief bis Dateiende"
|
|
101
|
+
// und verschluckte jeden folgenden Top-Level-Block, bei lückenloser
|
|
102
|
+
// Byte-Abdeckung und damit ohne einen einzigen Ledger-Eintrag. Genau
|
|
103
|
+
// dieselbe Blindstelle wie die des SQL-Zerlegers, nur mit anderem
|
|
104
|
+
// Auslöser.
|
|
105
|
+
if (state.heredocTag !== undefined) {
|
|
106
|
+
if (line.trim() === state.heredocTag) state.heredocTag = undefined;
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
let delta = 0;
|
|
110
|
+
let inDouble = false;
|
|
111
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
112
|
+
const c = line[i];
|
|
113
|
+
const next = line[i + 1];
|
|
114
|
+
if (state.inBlockComment) {
|
|
115
|
+
if (c === "*" && next === "/") {
|
|
116
|
+
state.inBlockComment = false;
|
|
117
|
+
i += 1;
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (inDouble) {
|
|
122
|
+
if (c === '"' && line[i - 1] !== "\\") inDouble = false;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (c === "#" || (c === "/" && next === "/")) break; // line comment: rest of the line is never code
|
|
126
|
+
if (c === "/" && next === "*") {
|
|
127
|
+
state.inBlockComment = true;
|
|
128
|
+
i += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (c === '"') {
|
|
132
|
+
inDouble = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (c === "<" && next === "<") {
|
|
136
|
+
HEREDOC_OPEN.lastIndex = i;
|
|
137
|
+
const heredoc = HEREDOC_OPEN.exec(line);
|
|
138
|
+
if (heredoc !== null) {
|
|
139
|
+
state.heredocTag = heredoc[1]!;
|
|
140
|
+
return delta; // alles hinter dem Öffner gehört zum Heredoc-Inhalt
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (c === "{") delta += 1;
|
|
144
|
+
else if (c === "}") delta -= 1;
|
|
145
|
+
}
|
|
146
|
+
return delta;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function terraformDrafts(lines: string[]): ChunkDraft[] {
|
|
150
|
+
const drafts: ChunkDraft[] = [];
|
|
151
|
+
const state: ScanState = { inBlockComment: false, heredocTag: undefined };
|
|
152
|
+
let depth = 0;
|
|
153
|
+
let pending: { kind: string; title: string; startLine: number } | undefined;
|
|
154
|
+
|
|
155
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
156
|
+
const line = lines[i] ?? "";
|
|
157
|
+
if (
|
|
158
|
+
depth === 0 &&
|
|
159
|
+
pending === undefined &&
|
|
160
|
+
!state.inBlockComment &&
|
|
161
|
+
state.heredocTag === undefined
|
|
162
|
+
) {
|
|
163
|
+
for (const pattern of BLOCK_PATTERNS) {
|
|
164
|
+
const match = pattern.re.exec(line);
|
|
165
|
+
if (match) {
|
|
166
|
+
pending = { kind: pattern.kind, title: pattern.title(match), startLine: i + 1 };
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
depth += scanLine(line, state);
|
|
172
|
+
if (pending !== undefined && depth === 0) {
|
|
173
|
+
drafts.push({
|
|
174
|
+
kind: pending.kind,
|
|
175
|
+
startLine: pending.startLine,
|
|
176
|
+
endLine: i + 1,
|
|
177
|
+
title: pending.title,
|
|
178
|
+
});
|
|
179
|
+
pending = undefined;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (pending !== undefined) {
|
|
183
|
+
// Unterminated top-level block (malformed source, or a still-open
|
|
184
|
+
// block comment at EOF): degrade to "runs to EOF" rather than dropping
|
|
185
|
+
// it — never silently, see the module doc.
|
|
186
|
+
drafts.push({
|
|
187
|
+
kind: pending.kind,
|
|
188
|
+
startLine: pending.startLine,
|
|
189
|
+
endLine: Math.max(lines.length, pending.startLine),
|
|
190
|
+
title: pending.title,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return drafts;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Trennzeichen-Konventionen für die Plausibilitätsprüfung (WP-118).
|
|
198
|
+
* Heredocs (`<<EOF` / `<<-EOF`) sind Rohtext: eingebettete Skripte
|
|
199
|
+
* enthalten regelmäßig unpaarige Klammern und Anführungszeichen, die kein
|
|
200
|
+
* Befund sind. Der Prüfer überliest sie deshalb vollständig — und meldet
|
|
201
|
+
* dafür ein Heredoc, dessen Endmarke im Fragment fehlt.
|
|
202
|
+
*/
|
|
203
|
+
const TERRAFORM_SCAN_OPTIONS: DelimiterScanOptions = {
|
|
204
|
+
quotes: [
|
|
205
|
+
{ open: '"', close: '"', backslashEscapes: true, doubledEscapes: false, spansLines: false },
|
|
206
|
+
],
|
|
207
|
+
brackets: [
|
|
208
|
+
{ open: "{", close: "}" },
|
|
209
|
+
{ open: "(", close: ")" },
|
|
210
|
+
{ open: "[", close: "]" },
|
|
211
|
+
],
|
|
212
|
+
lineComments: ["#", "//"],
|
|
213
|
+
blockComment: { open: "/*", close: "*/" },
|
|
214
|
+
heredocs: "hcl",
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* WP-118 (dieselbe Blindstelle wie in `sql.ts`): ein unabgeschlossener
|
|
219
|
+
* Top-Level-Block wurde bisher still zu „läuft bis Dateiende" degradiert —
|
|
220
|
+
* lückenlose Byte-Abdeckung, kein leerer Chunk, also kein Ledger-Eintrag,
|
|
221
|
+
* obwohl der Klammerzähler nachweislich aus dem Tritt war. Dasselbe gilt
|
|
222
|
+
* für ein Heredoc, dessen `{` den Zähler verschoben hat (der Zähler dieses
|
|
223
|
+
* Adapters kennt Heredocs nicht). Beides ist jetzt ein sichtbarer Befund.
|
|
224
|
+
*/
|
|
225
|
+
const checkTerraformPlausibility: ChunkPlausibilityCheck = (chunk) => {
|
|
226
|
+
const imbalances = findDelimiterImbalances(chunk.content, TERRAFORM_SCAN_OPTIONS);
|
|
227
|
+
return imbalances.length === 0 ? undefined : describeDelimiterImbalances(imbalances);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export interface TerraformAdapterOptions {
|
|
231
|
+
/** Overrides how a real skip-ledger finding surfaces from `chunk()` (default: a stderr warning) — see `coverage.ts`'s `SkipLedgerReporter`. */
|
|
232
|
+
onSkipLedgerGap?: SkipLedgerReporter;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function createTerraformAdapter(options: TerraformAdapterOptions = {}): ArtifactAdapter {
|
|
236
|
+
return {
|
|
237
|
+
supports(type: string, path: string): boolean {
|
|
238
|
+
return type === "terraform" || (type === "auto" && TERRAFORM_EXTENSION.test(path));
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
async read(input: ArtifactInput): Promise<ArtifactDocument[]> {
|
|
242
|
+
return buildDocuments(input, "terraform");
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
chunk(document: ArtifactDocument): ArtifactChunk[] {
|
|
246
|
+
const { lines } = splitLines(document.content);
|
|
247
|
+
const index = buildLineIndex(document.content);
|
|
248
|
+
return buildChunksReportingSkips(
|
|
249
|
+
document,
|
|
250
|
+
index,
|
|
251
|
+
terraformDrafts(lines),
|
|
252
|
+
options.onSkipLedgerGap,
|
|
253
|
+
checkTerraformPlausibility,
|
|
254
|
+
);
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
links(): ArtifactLink[] {
|
|
258
|
+
return [];
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic text fallback adapter (spec.md §19.2 "sicherer Text-Fallback";
|
|
3
|
+
* architecture.md §22.2 "Parserfehler degradieren auf Textanalyse").
|
|
4
|
+
*
|
|
5
|
+
* `supports()` matches everything the registry hasn't already routed to a
|
|
6
|
+
* more specific adapter — it is always registered LAST, as the registry's
|
|
7
|
+
* catch-all, so no file is ever left unindexable just because its format
|
|
8
|
+
* has no dedicated adapter (rule 4 / E4: nothing is silently skipped, it
|
|
9
|
+
* is degraded to fixed-size line windows instead).
|
|
10
|
+
*
|
|
11
|
+
* Windows are contiguous and non-overlapping, covering the ENTIRE file —
|
|
12
|
+
* `buildArtifactSkipLedger` reports zero gaps for a text-fallback document,
|
|
13
|
+
* by construction.
|
|
14
|
+
*/
|
|
15
|
+
import type {
|
|
16
|
+
ArtifactAdapter,
|
|
17
|
+
ArtifactChunk,
|
|
18
|
+
ArtifactDocument,
|
|
19
|
+
ArtifactInput,
|
|
20
|
+
ArtifactLink,
|
|
21
|
+
} from "@untangle-code/domain";
|
|
22
|
+
|
|
23
|
+
import { buildLineIndex } from "../text/line-index.js";
|
|
24
|
+
|
|
25
|
+
import type { ChunkDraft, SkipLedgerReporter } from "./shared.js";
|
|
26
|
+
import { buildChunksReportingSkips, buildDocuments } from "./shared.js";
|
|
27
|
+
|
|
28
|
+
/** Lines per window — generous enough to keep a chunk's content meaningfully self-contained, small enough to stay embeddable. */
|
|
29
|
+
export const TEXT_WINDOW_LINES = 200;
|
|
30
|
+
|
|
31
|
+
function windowDrafts(lineCount: number): ChunkDraft[] {
|
|
32
|
+
const total = Math.max(lineCount, 1);
|
|
33
|
+
const drafts: ChunkDraft[] = [];
|
|
34
|
+
for (let start = 1; start <= total; start += TEXT_WINDOW_LINES) {
|
|
35
|
+
const end = Math.min(start + TEXT_WINDOW_LINES - 1, total);
|
|
36
|
+
drafts.push({ kind: "text_window", startLine: start, endLine: end });
|
|
37
|
+
}
|
|
38
|
+
return drafts;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TextFallbackAdapterOptions {
|
|
42
|
+
/** Overrides how a real skip-ledger finding surfaces from `chunk()` (default: a stderr warning) — see `coverage.ts`'s `SkipLedgerReporter`. Never actually fires here: windows are gapless by construction (see module doc). */
|
|
43
|
+
onSkipLedgerGap?: SkipLedgerReporter;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createTextFallbackAdapter(
|
|
47
|
+
options: TextFallbackAdapterOptions = {},
|
|
48
|
+
): ArtifactAdapter {
|
|
49
|
+
return {
|
|
50
|
+
supports(): boolean {
|
|
51
|
+
return true; // catch-all; the registry only reaches this adapter once nothing else claimed the file.
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async read(input: ArtifactInput): Promise<ArtifactDocument[]> {
|
|
55
|
+
return buildDocuments(input, "text");
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
chunk(document: ArtifactDocument): ArtifactChunk[] {
|
|
59
|
+
const index = buildLineIndex(document.content);
|
|
60
|
+
return buildChunksReportingSkips(
|
|
61
|
+
document,
|
|
62
|
+
index,
|
|
63
|
+
windowDrafts(index.lineCount),
|
|
64
|
+
options.onSkipLedgerGap,
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
links(): ArtifactLink[] {
|
|
69
|
+
return [];
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-artifact configuration validation (spec.md §19.2: "Name MUSS
|
|
3
|
+
* projektweit eindeutig sein.").
|
|
4
|
+
*/
|
|
5
|
+
import type { ContextArtifactConfig } from "@untangle-code/domain";
|
|
6
|
+
import { configInvalidError } from "@untangle-code/observability";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validates a whole project's `ContextArtifactConfig` list: every `name`
|
|
10
|
+
* must be non-empty and unique across the list (case-sensitive — two
|
|
11
|
+
* artifacts differing only by case are almost certainly a copy/paste bug,
|
|
12
|
+
* and case-insensitive uniqueness would silently reject that legitimate
|
|
13
|
+
* edge case too, so this stays deliberately literal).
|
|
14
|
+
*
|
|
15
|
+
* Throws `UntangleError("CONFIG_INVALID", …)` (rule 8) with every offending
|
|
16
|
+
* name listed in `context.duplicateNames`, not just the first one found, so
|
|
17
|
+
* a single fix-and-rerun cycle can catch everything at once.
|
|
18
|
+
*/
|
|
19
|
+
export function validateArtifactConfigs(configs: readonly ContextArtifactConfig[]): void {
|
|
20
|
+
const seen = new Map<string, number>();
|
|
21
|
+
const emptyNameCount = configs.filter((config) => config.name.trim().length === 0).length;
|
|
22
|
+
if (emptyNameCount > 0) {
|
|
23
|
+
throw configInvalidError(
|
|
24
|
+
`${emptyNameCount} context artifact config(s) have an empty "name" (spec.md §19.2 requires a project-wide unique name)`,
|
|
25
|
+
{ context: { emptyNameCount } },
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
for (const config of configs) {
|
|
29
|
+
seen.set(config.name, (seen.get(config.name) ?? 0) + 1);
|
|
30
|
+
}
|
|
31
|
+
const duplicateNames = [...seen.entries()]
|
|
32
|
+
.filter(([, count]) => count > 1)
|
|
33
|
+
.map(([name]) => name)
|
|
34
|
+
.sort();
|
|
35
|
+
if (duplicateNames.length > 0) {
|
|
36
|
+
throw configInvalidError(
|
|
37
|
+
`context artifact name(s) are not project-wide unique (spec.md §19.2): ${duplicateNames.join(", ")}`,
|
|
38
|
+
{ context: { duplicateNames } },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|