@functional-systems/srcx 0.1.0-dev.3 → 0.1.0-dev.31
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/CHANGELOG.md +137 -1
- package/DESIGN.md +193 -58
- package/README.md +553 -38
- package/RELEASING.md +30 -9
- package/VALIDATION.md +855 -25
- package/dist/build.d.ts +81 -1
- package/dist/build.js +187 -37
- package/dist/build.js.map +1 -1
- package/dist/chunk.d.ts +9 -3
- package/dist/chunk.js +341 -14
- package/dist/chunk.js.map +1 -1
- package/dist/cli.js +103 -19
- package/dist/cli.js.map +1 -1
- package/dist/file-policy.d.ts +20 -0
- package/dist/file-policy.js +96 -0
- package/dist/file-policy.js.map +1 -0
- package/dist/implementation.d.ts +11 -0
- package/dist/implementation.js +191 -0
- package/dist/implementation.js.map +1 -0
- package/dist/publish.d.ts +18 -0
- package/dist/publish.js +158 -34
- package/dist/publish.js.map +1 -1
- package/dist/releases.js +21 -3
- package/dist/releases.js.map +1 -1
- package/dist/remote.d.ts +1 -0
- package/dist/remote.js +50 -9
- package/dist/remote.js.map +1 -1
- package/dist/repository.d.ts +4 -1
- package/dist/repository.js +26 -11
- package/dist/repository.js.map +1 -1
- package/dist/rerank.d.ts +16 -0
- package/dist/rerank.js +87 -0
- package/dist/rerank.js.map +1 -0
- package/dist/search.d.ts +14 -4
- package/dist/search.js +119 -23
- package/dist/search.js.map +1 -1
- package/dist/skills.d.ts +24 -0
- package/dist/skills.js +155 -0
- package/dist/skills.js.map +1 -0
- package/package.json +16 -5
- package/runtime/grammars/README.md +50 -0
- package/runtime/grammars/tree-sitter-bash.LICENSE +21 -0
- package/runtime/grammars/tree-sitter-bash.wasm +0 -0
- package/runtime/grammars/tree-sitter-sql.LICENSE +21 -0
- package/runtime/grammars/tree-sitter-sql.wasm +0 -0
- package/runtime/grammars/tree-sitter-wasm.LICENSE +21 -0
- package/runtime/qwen.py +132 -0
- package/runtime/requirements.txt +34 -0
- package/skills/srcx-search/SKILL.md +85 -0
- package/src/build.ts +258 -47
- package/src/chunk.ts +360 -12
- package/src/cli.ts +212 -24
- package/src/file-policy.ts +157 -0
- package/src/implementation.ts +253 -0
- package/src/publish.ts +226 -34
- package/src/releases.ts +36 -3
- package/src/remote.ts +70 -13
- package/src/repository.ts +40 -11
- package/src/rerank.ts +131 -0
- package/src/search.ts +154 -28
- package/src/skills.ts +205 -0
package/src/build.ts
CHANGED
|
@@ -15,6 +15,26 @@ import {
|
|
|
15
15
|
type Doc,
|
|
16
16
|
optionalJson,
|
|
17
17
|
} from "./common.js";
|
|
18
|
+
import {
|
|
19
|
+
FILE_POLICY,
|
|
20
|
+
normalizeFilePolicy,
|
|
21
|
+
hardExclusion,
|
|
22
|
+
privateKeyContent,
|
|
23
|
+
fileDecision,
|
|
24
|
+
type FilePolicy,
|
|
25
|
+
type FileDecision,
|
|
26
|
+
} from "./file-policy.js";
|
|
27
|
+
export const ANALYZERS = ["english", "japanese", "korean", "standard"] as const;
|
|
28
|
+
export type Analyzer = (typeof ANALYZERS)[number];
|
|
29
|
+
export function normalizeAnalyzers(value: unknown): Analyzer[] {
|
|
30
|
+
invariant(
|
|
31
|
+
Array.isArray(value) &&
|
|
32
|
+
value.length > 0 &&
|
|
33
|
+
value.every((name) => ANALYZERS.includes(name)),
|
|
34
|
+
`Analyzers must be a nonempty list of: ${ANALYZERS.join(", ")}.`,
|
|
35
|
+
);
|
|
36
|
+
return [...new Set(value as Analyzer[])].sort();
|
|
37
|
+
}
|
|
18
38
|
export const INDEX_CONFIGS = {
|
|
19
39
|
kind: { type: "keyword" },
|
|
20
40
|
path: { type: "keyword" },
|
|
@@ -25,18 +45,123 @@ export const INDEX_CONFIGS = {
|
|
|
25
45
|
} as const;
|
|
26
46
|
export type Preset = {
|
|
27
47
|
schemaVersion: 1;
|
|
48
|
+
analyzers: readonly Analyzer[];
|
|
28
49
|
chunker: typeof CHUNKER;
|
|
29
50
|
mode: "syntax" | "window";
|
|
51
|
+
/** Internal evaluation override; the CLI keeps its original enrichment. */
|
|
52
|
+
enrichment?: "path-only-v1";
|
|
30
53
|
maxFileBytes: number;
|
|
31
|
-
|
|
54
|
+
filePolicy: FilePolicy;
|
|
55
|
+
embedding: {
|
|
56
|
+
model: string;
|
|
57
|
+
dimensions: number;
|
|
58
|
+
managed?: true;
|
|
59
|
+
provider?: "openai";
|
|
60
|
+
similarity?: "cosine";
|
|
61
|
+
sourceField?: "embeddingText";
|
|
62
|
+
} | null;
|
|
32
63
|
};
|
|
33
64
|
export const PRESET: Preset = {
|
|
34
65
|
schemaVersion: 1,
|
|
66
|
+
analyzers: ["standard"],
|
|
35
67
|
chunker: CHUNKER,
|
|
36
68
|
mode: "syntax",
|
|
37
69
|
maxFileBytes: 1024 * 1024,
|
|
70
|
+
filePolicy: FILE_POLICY,
|
|
38
71
|
embedding: null,
|
|
39
72
|
};
|
|
73
|
+
/** Embedding variants share the same chunking behavior. */
|
|
74
|
+
export const MANAGED_PRESET: Preset = {
|
|
75
|
+
...PRESET,
|
|
76
|
+
embedding: {
|
|
77
|
+
managed: true,
|
|
78
|
+
provider: "openai",
|
|
79
|
+
model: "text-embedding-3-small",
|
|
80
|
+
dimensions: 1536,
|
|
81
|
+
similarity: "cosine",
|
|
82
|
+
sourceField: "embeddingText",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
export const MANAGED_LARGE_PRESET: Preset = {
|
|
86
|
+
...MANAGED_PRESET,
|
|
87
|
+
embedding: {
|
|
88
|
+
...MANAGED_PRESET.embedding!,
|
|
89
|
+
model: "text-embedding-3-large",
|
|
90
|
+
dimensions: 3072,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
export function presetFor(
|
|
94
|
+
embedding = "none",
|
|
95
|
+
analyzers: readonly string[] = PRESET.analyzers,
|
|
96
|
+
filePolicy: unknown = FILE_POLICY,
|
|
97
|
+
): Preset {
|
|
98
|
+
invariant(
|
|
99
|
+
["none", "text-embedding-3-small", "text-embedding-3-large"].includes(
|
|
100
|
+
embedding,
|
|
101
|
+
),
|
|
102
|
+
"Embedding must be none, text-embedding-3-small or text-embedding-3-large.",
|
|
103
|
+
);
|
|
104
|
+
const base =
|
|
105
|
+
embedding === "none"
|
|
106
|
+
? PRESET
|
|
107
|
+
: embedding === "text-embedding-3-large"
|
|
108
|
+
? MANAGED_LARGE_PRESET
|
|
109
|
+
: MANAGED_PRESET;
|
|
110
|
+
const normalized = normalizeAnalyzers(analyzers);
|
|
111
|
+
const configured = {
|
|
112
|
+
...base,
|
|
113
|
+
analyzers: normalized,
|
|
114
|
+
filePolicy: normalizeFilePolicy(filePolicy),
|
|
115
|
+
};
|
|
116
|
+
return hash(configured) === hash(base) ? base : configured;
|
|
117
|
+
}
|
|
118
|
+
export function supportedPreset(preset: unknown): preset is Preset {
|
|
119
|
+
if (!preset || typeof preset !== "object" || Array.isArray(preset))
|
|
120
|
+
return false;
|
|
121
|
+
try {
|
|
122
|
+
const analyzers = normalizeAnalyzers((preset as Preset).analyzers);
|
|
123
|
+
const filePolicy = normalizeFilePolicy((preset as Preset).filePolicy);
|
|
124
|
+
return [PRESET, MANAGED_PRESET, MANAGED_LARGE_PRESET].some(
|
|
125
|
+
(base) => hash({ ...base, analyzers, filePolicy }) === hash(preset),
|
|
126
|
+
);
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function indexConfigs(preset: Preset = PRESET) {
|
|
132
|
+
const text = { type: "text" as const, analyzers: [...preset.analyzers] };
|
|
133
|
+
const indexes = { ...INDEX_CONFIGS, searchText: text };
|
|
134
|
+
if (!preset.embedding?.managed) return indexes;
|
|
135
|
+
const { managed: _, ...embedding } = preset.embedding;
|
|
136
|
+
return {
|
|
137
|
+
...indexes,
|
|
138
|
+
embeddingText: { ...text, analyzers: [...preset.analyzers] },
|
|
139
|
+
embedding: { type: "vector", managedEmbedding: true, embedding },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Validate server enrichment separately; all other payload fields remain exact. */
|
|
143
|
+
export function recordHash(doc: Doc, preset: Preset): string {
|
|
144
|
+
if (!preset.embedding?.managed) return hash(doc);
|
|
145
|
+
const { embedding, ...source } = doc;
|
|
146
|
+
if (doc.kind === "chunk" && doc.embeddingStatus === "managed") {
|
|
147
|
+
invariant(
|
|
148
|
+
typeof doc.embeddingText === "string" &&
|
|
149
|
+
doc.embeddingText === doc.searchText &&
|
|
150
|
+
doc.embeddingInputHash ===
|
|
151
|
+
hash([preset.embedding, doc.embeddingText]) &&
|
|
152
|
+
Array.isArray(embedding) &&
|
|
153
|
+
embedding.length === preset.embedding.dimensions &&
|
|
154
|
+
embedding.every((v) => typeof v === "number" && Number.isFinite(v)) &&
|
|
155
|
+
embedding.some((v) => v !== 0),
|
|
156
|
+
"Missing or invalid managed embedding.",
|
|
157
|
+
);
|
|
158
|
+
} else
|
|
159
|
+
invariant(
|
|
160
|
+
embedding === undefined && doc.embeddingText === undefined,
|
|
161
|
+
"Unexpected managed embedding on an ineligible record.",
|
|
162
|
+
);
|
|
163
|
+
return hash(source);
|
|
164
|
+
}
|
|
40
165
|
export type InventoryItem = {
|
|
41
166
|
path: string;
|
|
42
167
|
pathBase64: string;
|
|
@@ -45,6 +170,8 @@ export type InventoryItem = {
|
|
|
45
170
|
bytes: number;
|
|
46
171
|
status: "included" | "excluded";
|
|
47
172
|
reason?: string;
|
|
173
|
+
retrieval?: "lexical" | "semantic";
|
|
174
|
+
policyReason?: string;
|
|
48
175
|
targetBase64?: string;
|
|
49
176
|
fileId?: string;
|
|
50
177
|
chunkIds?: string[];
|
|
@@ -52,6 +179,8 @@ export type InventoryItem = {
|
|
|
52
179
|
parseStatus?: string;
|
|
53
180
|
tokens?: number;
|
|
54
181
|
embedded?: number;
|
|
182
|
+
managed?: number;
|
|
183
|
+
managedTokens?: number;
|
|
55
184
|
skipped?: number;
|
|
56
185
|
};
|
|
57
186
|
export type Build = {
|
|
@@ -104,25 +233,10 @@ export async function loadBuild(directory: string): Promise<Build> {
|
|
|
104
233
|
b.directory = resolve(directory);
|
|
105
234
|
return b;
|
|
106
235
|
}
|
|
107
|
-
function exclude(path: string): string | undefined {
|
|
108
|
-
if (
|
|
109
|
-
/(^|\/)(node_modules|vendor|vendored|dist|build|target|coverage|\.git|\.next)(\/|$)/.test(
|
|
110
|
-
path,
|
|
111
|
-
)
|
|
112
|
-
)
|
|
113
|
-
return "dependency-or-build-output";
|
|
114
|
-
if (
|
|
115
|
-
/\.(png|jpe?g|gif|ico|pdf|zip|gz|jar|class|woff2?|mp[34]|exe|dll|so|dylib|wasm)$/i.test(
|
|
116
|
-
path,
|
|
117
|
-
)
|
|
118
|
-
)
|
|
119
|
-
return "binary-extension";
|
|
120
|
-
if (/\.(min\.(js|css)|map)$/.test(path)) return "generated-output";
|
|
121
|
-
return undefined;
|
|
122
|
-
}
|
|
123
236
|
export async function materialize(args: {
|
|
124
237
|
identity: Identity;
|
|
125
238
|
ref: string;
|
|
239
|
+
resolvedCommit?: Awaited<ReturnType<typeof resolveCommit>>;
|
|
126
240
|
output: string;
|
|
127
241
|
previous?: Build;
|
|
128
242
|
preset?: Preset;
|
|
@@ -133,8 +247,12 @@ export async function materialize(args: {
|
|
|
133
247
|
configHash = hash(preset),
|
|
134
248
|
prev = args.previous;
|
|
135
249
|
invariant(
|
|
136
|
-
hash(preset.
|
|
250
|
+
hash(preset.filePolicy) === hash(normalizeFilePolicy(preset.filePolicy)) &&
|
|
251
|
+
hash(preset.analyzers) === hash(normalizeAnalyzers(preset.analyzers)) &&
|
|
252
|
+
hash(preset.chunker) === hash(CHUNKER) &&
|
|
137
253
|
["syntax", "window"].includes(preset.mode) &&
|
|
254
|
+
(preset.enrichment === undefined ||
|
|
255
|
+
preset.enrichment === "path-only-v1") &&
|
|
138
256
|
preset.maxFileBytes === PRESET.maxFileBytes,
|
|
139
257
|
"Unsupported build preset.",
|
|
140
258
|
);
|
|
@@ -147,11 +265,13 @@ export async function materialize(args: {
|
|
|
147
265
|
}
|
|
148
266
|
invariant(
|
|
149
267
|
!preset.embedding ||
|
|
268
|
+
(preset.embedding.managed && supportedPreset(preset)) ||
|
|
150
269
|
(args.embedder?.model === preset.embedding.model &&
|
|
151
270
|
args.embedder.dimensions === preset.embedding.dimensions),
|
|
152
271
|
"Embedding provider must match the pinned preset.",
|
|
153
272
|
);
|
|
154
|
-
const commit =
|
|
273
|
+
const commit =
|
|
274
|
+
args.resolvedCommit ?? (await resolveCommit(args.identity.path, args.ref));
|
|
155
275
|
const directory = resolve(args.output);
|
|
156
276
|
await mkdir(directory, { recursive: false, mode: 0o700 });
|
|
157
277
|
const output = await open(join(directory, "records.jsonl"), "wx", 0o600);
|
|
@@ -200,7 +320,10 @@ export async function materialize(args: {
|
|
|
200
320
|
item.targetBase64 = (await blobs.read(e)).toString("base64");
|
|
201
321
|
continue;
|
|
202
322
|
}
|
|
203
|
-
const
|
|
323
|
+
const pathDecision = fileDecision(e.path, preset.filePolicy);
|
|
324
|
+
const reason =
|
|
325
|
+
hardExclusion(e.path) ??
|
|
326
|
+
(pathDecision.action === "exclude" ? pathDecision.reason : undefined);
|
|
204
327
|
if (reason) {
|
|
205
328
|
item.reason = reason;
|
|
206
329
|
continue;
|
|
@@ -233,7 +356,17 @@ export async function materialize(args: {
|
|
|
233
356
|
item.reason = "lfs-pointer";
|
|
234
357
|
continue;
|
|
235
358
|
}
|
|
236
|
-
|
|
359
|
+
if (privateKeyContent(source)) {
|
|
360
|
+
item.reason = "private-key-content";
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const decision = fileDecision(e.path, preset.filePolicy, source);
|
|
364
|
+
const parsed = await chunk(
|
|
365
|
+
source,
|
|
366
|
+
e.path,
|
|
367
|
+
preset.mode,
|
|
368
|
+
preset.enrichment,
|
|
369
|
+
);
|
|
237
370
|
const contentHash = hash(data);
|
|
238
371
|
const fileId = `f-${hash([e.pathBase64, contentHash])}`;
|
|
239
372
|
const base = { schemaVersion: 1, configHash };
|
|
@@ -256,6 +389,8 @@ export async function materialize(args: {
|
|
|
256
389
|
}
|
|
257
390
|
Object.assign(item, {
|
|
258
391
|
status: "included",
|
|
392
|
+
retrieval: decision.action,
|
|
393
|
+
policyReason: decision.reason,
|
|
259
394
|
contentHash,
|
|
260
395
|
fileId,
|
|
261
396
|
chunkIds: [],
|
|
@@ -282,39 +417,52 @@ export async function materialize(args: {
|
|
|
282
417
|
? "structural-only"
|
|
283
418
|
: "embedding-disabled",
|
|
284
419
|
};
|
|
285
|
-
const eligible =
|
|
420
|
+
const eligible =
|
|
421
|
+
decision.action === "semantic" &&
|
|
422
|
+
!["imports", "structural"].includes(span.chunkKind);
|
|
286
423
|
if (preset.embedding && eligible) {
|
|
287
424
|
const inputHash = hash([preset.embedding, span.searchText]);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
425
|
+
if (preset.embedding.managed) {
|
|
426
|
+
doc.embeddingText = span.searchText;
|
|
427
|
+
doc.embeddingInputHash = inputHash;
|
|
428
|
+
doc.embeddingStatus = "managed";
|
|
429
|
+
delete doc.embeddingSkipReason;
|
|
430
|
+
item.managed = (item.managed ?? 0) + 1;
|
|
431
|
+
item.managedTokens = (item.managedTokens ?? 0) + span.tokenCount;
|
|
432
|
+
} else {
|
|
433
|
+
const cachePath = args.cache
|
|
434
|
+
? join(args.cache, inputHash + ".json")
|
|
435
|
+
: undefined;
|
|
436
|
+
let vector = cachePath
|
|
437
|
+
? await optionalJson<number[]>(cachePath)
|
|
438
|
+
: undefined;
|
|
439
|
+
if (!vector) {
|
|
440
|
+
vector = await args.embedder!.embed(span.searchText);
|
|
441
|
+
invariant(
|
|
442
|
+
vector.length === preset.embedding.dimensions &&
|
|
443
|
+
vector.every(Number.isFinite),
|
|
444
|
+
"Invalid embedding output.",
|
|
445
|
+
);
|
|
446
|
+
if (cachePath) await atomic(cachePath, vector);
|
|
447
|
+
}
|
|
296
448
|
invariant(
|
|
297
449
|
vector.length === preset.embedding.dimensions &&
|
|
298
450
|
vector.every(Number.isFinite),
|
|
299
|
-
"Invalid
|
|
451
|
+
"Invalid cached vector.",
|
|
300
452
|
);
|
|
301
|
-
|
|
453
|
+
doc.embedding = vector;
|
|
454
|
+
doc.embeddingInputHash = inputHash;
|
|
455
|
+
doc.embeddingStatus = "embedded";
|
|
456
|
+
delete doc.embeddingSkipReason;
|
|
457
|
+
item.embedded!++;
|
|
302
458
|
}
|
|
303
|
-
invariant(
|
|
304
|
-
vector.length === preset.embedding.dimensions &&
|
|
305
|
-
vector.every(Number.isFinite),
|
|
306
|
-
"Invalid cached vector.",
|
|
307
|
-
);
|
|
308
|
-
doc.embedding = vector;
|
|
309
|
-
doc.embeddingInputHash = inputHash;
|
|
310
|
-
doc.embeddingStatus = "embedded";
|
|
311
|
-
delete doc.embeddingSkipReason;
|
|
312
|
-
item.embedded!++;
|
|
313
459
|
} else {
|
|
314
460
|
doc.embeddingSkipReason = preset.embedding
|
|
315
|
-
?
|
|
316
|
-
?
|
|
317
|
-
: "
|
|
461
|
+
? decision.action === "lexical"
|
|
462
|
+
? decision.reason
|
|
463
|
+
: span.chunkKind === "imports"
|
|
464
|
+
? "imports-only"
|
|
465
|
+
: "structural-only"
|
|
318
466
|
: "embedding-disabled";
|
|
319
467
|
item.skipped!++;
|
|
320
468
|
}
|
|
@@ -388,6 +536,15 @@ export async function materialize(args: {
|
|
|
388
536
|
excluded: items.filter((e) => e.status === "excluded").length,
|
|
389
537
|
chunks: items.reduce((n, e) => n + (e.chunkIds?.length ?? 0), 0),
|
|
390
538
|
tokens: items.reduce((n, e) => n + (e.tokens ?? 0), 0),
|
|
539
|
+
...(preset.embedding?.managed
|
|
540
|
+
? {
|
|
541
|
+
managed: items.reduce((n, e) => n + (e.managed ?? 0), 0),
|
|
542
|
+
managedTokens: items.reduce(
|
|
543
|
+
(n, e) => n + (e.managedTokens ?? 0),
|
|
544
|
+
0,
|
|
545
|
+
),
|
|
546
|
+
}
|
|
547
|
+
: {}),
|
|
391
548
|
embedded: items.reduce((n, e) => n + (e.embedded ?? 0), 0),
|
|
392
549
|
skipped: items.reduce((n, e) => n + (e.skipped ?? 0), 0),
|
|
393
550
|
},
|
|
@@ -405,7 +562,12 @@ export async function validateBuild(b: Build): Promise<void> {
|
|
|
405
562
|
"Build commit identity mismatch; rebuild from the intended Git commit.",
|
|
406
563
|
);
|
|
407
564
|
invariant(
|
|
408
|
-
hash(b.preset
|
|
565
|
+
hash(b.preset.filePolicy) ===
|
|
566
|
+
hash(normalizeFilePolicy(b.preset.filePolicy)) &&
|
|
567
|
+
hash(b.preset.analyzers) ===
|
|
568
|
+
hash(normalizeAnalyzers(b.preset.analyzers)) &&
|
|
569
|
+
hash(b.preset) === b.configHash &&
|
|
570
|
+
hash(b.inventory) === b.inventoryHash,
|
|
409
571
|
"Build metadata hash mismatch.",
|
|
410
572
|
);
|
|
411
573
|
const seen = new Set<string>();
|
|
@@ -417,6 +579,7 @@ export async function validateBuild(b: Build): Promise<void> {
|
|
|
417
579
|
.filter((e) => e.status === "included")
|
|
418
580
|
.map((e) => [e.fileId!, e]),
|
|
419
581
|
);
|
|
582
|
+
let activeDecision: FileDecision | undefined;
|
|
420
583
|
const completedFiles = new Set<string>();
|
|
421
584
|
const finish = () => {
|
|
422
585
|
if (activeFile) {
|
|
@@ -439,12 +602,36 @@ export async function validateBuild(b: Build): Promise<void> {
|
|
|
439
602
|
doc.configHash === b.configHash && doc.schemaVersion === 1,
|
|
440
603
|
"Mixed configuration.",
|
|
441
604
|
);
|
|
605
|
+
if (b.preset.embedding?.managed) {
|
|
606
|
+
invariant(
|
|
607
|
+
doc.embedding === undefined,
|
|
608
|
+
"Managed artifacts must not contain vectors.",
|
|
609
|
+
);
|
|
610
|
+
if (doc.kind !== "chunk")
|
|
611
|
+
invariant(
|
|
612
|
+
doc.embeddingText === undefined,
|
|
613
|
+
"Only eligible chunks can contain embedding input.",
|
|
614
|
+
);
|
|
615
|
+
}
|
|
442
616
|
if (doc.kind === "file") {
|
|
443
617
|
finish();
|
|
444
618
|
activeFile = doc;
|
|
445
619
|
end = 0;
|
|
446
620
|
count = 0;
|
|
447
621
|
const entry = expectedFile.get(doc.id);
|
|
622
|
+
activeDecision = fileDecision(
|
|
623
|
+
doc.path as string,
|
|
624
|
+
b.preset.filePolicy,
|
|
625
|
+
doc.sourceText as string,
|
|
626
|
+
);
|
|
627
|
+
invariant(
|
|
628
|
+
!hardExclusion(doc.path as string) &&
|
|
629
|
+
!privateKeyContent(doc.sourceText as string) &&
|
|
630
|
+
activeDecision.action !== "exclude" &&
|
|
631
|
+
entry?.retrieval === activeDecision.action &&
|
|
632
|
+
entry?.policyReason === activeDecision.reason,
|
|
633
|
+
"File policy or inventory decision mismatch.",
|
|
634
|
+
);
|
|
448
635
|
const raw = Buffer.from(doc.sourceText as string);
|
|
449
636
|
invariant(
|
|
450
637
|
entry &&
|
|
@@ -494,9 +681,33 @@ export async function validateBuild(b: Build): Promise<void> {
|
|
|
494
681
|
Number(doc.tokenCount) <= CHUNKER.maxTokens,
|
|
495
682
|
"Invalid chunk source/position/token binding.",
|
|
496
683
|
);
|
|
497
|
-
if (
|
|
684
|
+
if (b.preset.embedding?.managed) {
|
|
685
|
+
const eligible =
|
|
686
|
+
activeDecision!.action === "semantic" &&
|
|
687
|
+
!["imports", "structural"].includes(String(doc.chunkKind));
|
|
688
|
+
invariant(
|
|
689
|
+
eligible
|
|
690
|
+
? doc.embeddingStatus === "managed" &&
|
|
691
|
+
doc.embeddingText === doc.searchText &&
|
|
692
|
+
doc.embeddingInputHash ===
|
|
693
|
+
hash([b.preset.embedding, doc.searchText]) &&
|
|
694
|
+
doc.embeddingSkipReason === undefined
|
|
695
|
+
: doc.embeddingStatus === "skipped" &&
|
|
696
|
+
doc.embeddingText === undefined &&
|
|
697
|
+
doc.embeddingInputHash === undefined &&
|
|
698
|
+
doc.embeddingSkipReason ===
|
|
699
|
+
(activeDecision!.action === "lexical"
|
|
700
|
+
? activeDecision!.reason
|
|
701
|
+
: doc.chunkKind === "imports"
|
|
702
|
+
? "imports-only"
|
|
703
|
+
: "structural-only"),
|
|
704
|
+
"Invalid managed embedding input or eligibility.",
|
|
705
|
+
);
|
|
706
|
+
} else if (doc.embeddingStatus === "embedded")
|
|
498
707
|
invariant(
|
|
499
708
|
b.preset.embedding &&
|
|
709
|
+
activeDecision!.action === "semantic" &&
|
|
710
|
+
!["imports", "structural"].includes(String(doc.chunkKind)) &&
|
|
500
711
|
Array.isArray(doc.embedding) &&
|
|
501
712
|
doc.embedding.length === b.preset.embedding.dimensions &&
|
|
502
713
|
doc.embedding.every(Number.isFinite),
|