@c4a/extract 0.5.41-beta.7 → 0.6.0-alpha.6
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/README.md +41 -17
- package/bin/c4a-extract-code.js +52 -0
- package/index.js +602 -2
- package/package.json +1 -1
- package/wasm/tree-sitter.wasm +0 -0
package/README.md
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# @c4a/extract
|
|
2
2
|
|
|
3
|
-
Code extraction framework for
|
|
3
|
+
Code extraction framework for Context. It owns the language-plugin protocol, repository runner, raw code snapshot contract, digest generation, and shared Tree-sitter parsing utilities.
|
|
4
4
|
|
|
5
5
|
## Role in the Monorepo
|
|
6
6
|
|
|
7
|
-
`@c4a/extract` is the protocol and runner layer
|
|
7
|
+
`@c4a/extract` is the protocol and runner layer used by declared Context
|
|
8
|
+
project extraction phases such as `extractTs({ source, collection: "wikis" })`.
|
|
8
9
|
|
|
9
10
|
- Language plugins implement `ExtractionPlugin` and return `ExtractionResult` v2.
|
|
10
|
-
- The runner loads one or more plugins, scans repository modules, emits progress/module-error/summary events, and can build
|
|
11
|
-
- `@c4a/context-cli`
|
|
12
|
-
-
|
|
11
|
+
- The runner loads one or more plugins, scans repository modules, emits progress/module-error/summary events, and can build an optional low-level code snapshot payload for tooling/tests.
|
|
12
|
+
- `@c4a/context-cli` invokes the runner from `context run extract:<source>:wikis` after the workspace declares an `extractTs` phase.
|
|
13
|
+
- The project flow stages review candidates under `unapproved/entities.jsonl` and keeps runtime candidate artifacts under `.tmp/context-runtime/extract/`; approved Markdown is written only by review/apply.
|
|
13
14
|
|
|
14
15
|
**Depends on:** `@c4a/core`, `web-tree-sitter`, `zod`
|
|
15
16
|
|
|
@@ -67,7 +68,10 @@ Every plugin returns:
|
|
|
67
68
|
|
|
68
69
|
### 3. Repository Runner Protocol
|
|
69
70
|
|
|
70
|
-
The package exposes `c4a-extract-code`, a NDJSON runner
|
|
71
|
+
The package exposes `c4a-extract-code`, a low-level NDJSON runner. The current
|
|
72
|
+
Context project flow normally reaches it through a declared `extractTs` phase
|
|
73
|
+
and `context run extract:<source>:wikis`; agents should not hand-build runner
|
|
74
|
+
input for normal workspace operation.
|
|
71
75
|
|
|
72
76
|
Input is JSON on stdin:
|
|
73
77
|
|
|
@@ -129,7 +133,11 @@ During projection, code-owned Sections receive code `source_ref` values derived
|
|
|
129
133
|
- package rows: `src-N#package:<package>@<hash>`
|
|
130
134
|
- symbol rows: `src-N#symbol:<locator>:<kind>@<hash>`
|
|
131
135
|
|
|
132
|
-
These refs are verified against the raw code snapshot JSONL indexes. They are
|
|
136
|
+
These refs are verified against the raw code snapshot JSONL indexes. They are
|
|
137
|
+
separate from prose evidence refs, because code snapshots use
|
|
138
|
+
`evidence.mode: none` and do not create raw block manifests. Prose/raw evidence
|
|
139
|
+
uses `src-N#block:<locator> L<start>-<end>@<block-hash>` so it can keep line
|
|
140
|
+
ranges for review, diffing, and re-anchoring.
|
|
133
141
|
|
|
134
142
|
## Writing a New Language Plugin
|
|
135
143
|
|
|
@@ -144,7 +152,9 @@ Minimum requirements:
|
|
|
144
152
|
5. Emit `SymbolInfo[]` with stable `name`, `kind`, `visibility`, `file`, `line`, and `endLine`.
|
|
145
153
|
6. Emit `RelationInfo[]` for imports and important type/inheritance/use edges.
|
|
146
154
|
7. Keep paths module-relative inside the plugin; the repository runner prefixes them to repo-relative paths.
|
|
147
|
-
8. Register the plugin in the runner input used by
|
|
155
|
+
8. Register the plugin in the runner input used by the project extraction
|
|
156
|
+
phase. User-facing workspaces should declare the matching phase in
|
|
157
|
+
`src/index.ts` and execute it through `context run <phase-id>`.
|
|
148
158
|
|
|
149
159
|
Example skeleton:
|
|
150
160
|
|
|
@@ -178,15 +188,29 @@ export class PythonPlugin implements ExtractionPlugin {
|
|
|
178
188
|
}
|
|
179
189
|
```
|
|
180
190
|
|
|
181
|
-
## Relationship to
|
|
182
|
-
|
|
183
|
-
`@c4a/extract` is upstream of
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
- `context
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
191
|
+
## Relationship to Context Project Extraction
|
|
192
|
+
|
|
193
|
+
`@c4a/extract` is upstream of the Context project extraction phase; it does not
|
|
194
|
+
render approved knowledge itself.
|
|
195
|
+
|
|
196
|
+
- `context source add repo <name> --local <repo-or-subdir>` registers the code
|
|
197
|
+
source boundary.
|
|
198
|
+
- `src/index.ts` declares `extractTs({ source: source("<name>"), collection: "wikis" })`.
|
|
199
|
+
- `context run extract:<name>:wikis --dry-run` previews resolved modules,
|
|
200
|
+
candidate estimates, knowledge path examples, and module warnings.
|
|
201
|
+
- `context run extract:<name>:wikis` writes draft candidates for review. It
|
|
202
|
+
uses `.tmp/context-runtime/extract/` for candidate snapshots, source
|
|
203
|
+
fingerprints, and symbol indexes; that scratch directory is not committed
|
|
204
|
+
state.
|
|
205
|
+
- `context review html` and `context review apply` own human approval and
|
|
206
|
+
approved Markdown materialization.
|
|
207
|
+
- `context close`, `context verify`, and `context build` own final package
|
|
208
|
+
readiness.
|
|
209
|
+
|
|
210
|
+
That means language plugins affect published knowledge only through the current
|
|
211
|
+
project flow: better symbols/relations produce better draft candidates, stable
|
|
212
|
+
`repo:<source>#symbol:...` source refs, review evidence, approved Markdown, and
|
|
213
|
+
package output.
|
|
190
214
|
|
|
191
215
|
## Development
|
|
192
216
|
|
package/bin/c4a-extract-code.js
CHANGED
|
@@ -14111,6 +14111,29 @@ var readModuleName = async (dirPath, manifestFile) => {
|
|
|
14111
14111
|
}
|
|
14112
14112
|
return null;
|
|
14113
14113
|
};
|
|
14114
|
+
var readModuleVersion = async (dirPath, manifestFile) => {
|
|
14115
|
+
try {
|
|
14116
|
+
const content = await readFile(join(dirPath, manifestFile), "utf-8");
|
|
14117
|
+
if (manifestFile === "package.json") {
|
|
14118
|
+
const parsed = JSON.parse(content);
|
|
14119
|
+
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
|
|
14120
|
+
}
|
|
14121
|
+
if (manifestFile === "pyproject.toml" || manifestFile === "Cargo.toml") {
|
|
14122
|
+
const match = content.match(/^\s*version\s*=\s*["']([^"']+)["']/m);
|
|
14123
|
+
return match?.[1] ?? null;
|
|
14124
|
+
}
|
|
14125
|
+
if (manifestFile === "setup.py") {
|
|
14126
|
+
const match = content.match(/version\s*=\s*["']([^"']+)["']/);
|
|
14127
|
+
return match?.[1] ?? null;
|
|
14128
|
+
}
|
|
14129
|
+
if (manifestFile === "pom.xml") {
|
|
14130
|
+
return content.match(/<version>\s*([^<]+?)\s*<\/version>/i)?.[1] ?? null;
|
|
14131
|
+
}
|
|
14132
|
+
} catch {
|
|
14133
|
+
return null;
|
|
14134
|
+
}
|
|
14135
|
+
return null;
|
|
14136
|
+
};
|
|
14114
14137
|
var MANIFEST_NAMES = new Set(MANIFEST_FILES.map((f) => f.toLowerCase()));
|
|
14115
14138
|
var findSubModuleDirs = async (rootDir, excludeDirs, gitFiles, pathFilter2) => {
|
|
14116
14139
|
const root = resolve(rootDir);
|
|
@@ -14228,6 +14251,35 @@ var detectModules = async (repoPath, ref, pathFilter2) => {
|
|
|
14228
14251
|
}
|
|
14229
14252
|
return modules.sort((a, b) => a.name.localeCompare(b.name));
|
|
14230
14253
|
};
|
|
14254
|
+
var detectModuleBoundaries = async (repoPath, ref, pathFilter2) => {
|
|
14255
|
+
const repoRoot = resolve(repoPath);
|
|
14256
|
+
const gitFiles = await tryGitListFiles(repoRoot, ref);
|
|
14257
|
+
const subModuleDirs = await findSubModuleDirs(repoRoot, undefined, gitFiles, pathFilter2);
|
|
14258
|
+
const boundaries = [];
|
|
14259
|
+
const rootManifest = await findManifestFile(repoRoot);
|
|
14260
|
+
if (rootManifest) {
|
|
14261
|
+
const version2 = await readModuleVersion(repoRoot, rootManifest);
|
|
14262
|
+
boundaries.push({
|
|
14263
|
+
name: await readModuleName(repoRoot, rootManifest) ?? basename(repoRoot),
|
|
14264
|
+
path: ".",
|
|
14265
|
+
manifest: rootManifest,
|
|
14266
|
+
...version2 !== null ? { version: version2 } : {}
|
|
14267
|
+
});
|
|
14268
|
+
}
|
|
14269
|
+
for (const subDir of subModuleDirs) {
|
|
14270
|
+
const manifest = await findManifestFile(subDir);
|
|
14271
|
+
if (!manifest)
|
|
14272
|
+
continue;
|
|
14273
|
+
const version2 = await readModuleVersion(subDir, manifest);
|
|
14274
|
+
boundaries.push({
|
|
14275
|
+
name: await readModuleName(subDir, manifest) ?? basename(subDir),
|
|
14276
|
+
path: toPosixPath(relative(repoRoot, subDir)) || ".",
|
|
14277
|
+
manifest,
|
|
14278
|
+
...version2 !== null ? { version: version2 } : {}
|
|
14279
|
+
});
|
|
14280
|
+
}
|
|
14281
|
+
return boundaries.sort((left, right) => left.path.localeCompare(right.path));
|
|
14282
|
+
};
|
|
14231
14283
|
var detectModuleAt = async (repoPath, modulePath, ref, pathFilter2) => {
|
|
14232
14284
|
const repoRoot = resolve(repoPath);
|
|
14233
14285
|
const moduleDir = modulePath === "." ? repoRoot : resolve(repoRoot, modulePath);
|
package/index.js
CHANGED
|
@@ -8660,7 +8660,7 @@ var require_picomatch2 = __commonJS((exports, module2) => {
|
|
|
8660
8660
|
|
|
8661
8661
|
// ../../node_modules/.bun/web-tree-sitter@0.20.8/node_modules/web-tree-sitter/tree-sitter.js
|
|
8662
8662
|
var require_tree_sitter = __commonJS((exports, module2) => {
|
|
8663
|
-
var __dirname = "/Users/bytedance/github/c4a/node_modules/.bun/web-tree-sitter@0.20.8/node_modules/web-tree-sitter";
|
|
8663
|
+
var __dirname = "/Users/bytedance/github/c4a/context/node_modules/.bun/web-tree-sitter@0.20.8/node_modules/web-tree-sitter";
|
|
8664
8664
|
var Module = Module !== undefined ? Module : {};
|
|
8665
8665
|
var TreeSitter = function() {
|
|
8666
8666
|
var initPromise, document = typeof window == "object" ? { currentScript: window.document.currentScript } : null;
|
|
@@ -15263,7 +15263,9 @@ var symbolInfoSchema = exports_external.lazy(() => exports_external.object({
|
|
|
15263
15263
|
implements: exports_external.array(exports_external.string().min(1)).optional(),
|
|
15264
15264
|
doc: exports_external.string().nullable().optional(),
|
|
15265
15265
|
propsType: exports_external.string().min(1).nullable().optional(),
|
|
15266
|
-
unionValues: exports_external.array(exports_external.string()).optional()
|
|
15266
|
+
unionValues: exports_external.array(exports_external.string()).optional(),
|
|
15267
|
+
initializer: exports_external.string().min(1).nullable().optional(),
|
|
15268
|
+
signature: exports_external.string().min(1).nullable().optional()
|
|
15267
15269
|
}));
|
|
15268
15270
|
var relationInfoSchema = exports_external.object({
|
|
15269
15271
|
type: relationTypeSchema,
|
|
@@ -15463,6 +15465,530 @@ var generateSymbolDiff = (current, previous) => {
|
|
|
15463
15465
|
removed: previousNames.filter((name) => !currentSet.has(name))
|
|
15464
15466
|
};
|
|
15465
15467
|
};
|
|
15468
|
+
// src/documentEvidence.ts
|
|
15469
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
15470
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
15471
|
+
var DOCUMENT_EVIDENCE_NORMALIZER_VERSION = "document-evidence-normalizer.v1";
|
|
15472
|
+
var DOCUMENT_SNAPSHOT_MANIFEST_SCHEMA_VERSION = "document.snapshot.v2";
|
|
15473
|
+
var DEFAULT_SOURCE_SPAN_HASH_LENGTH = 12;
|
|
15474
|
+
var BOM = "\uFEFF";
|
|
15475
|
+
var HASH_ID_RE = /^(?:sha256:)?[a-f0-9]{64}$/u;
|
|
15476
|
+
var SOURCE_SPAN_HASH_RE = /^[a-f0-9]{8,64}$/u;
|
|
15477
|
+
var DOCUMENT_SOURCE_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
15478
|
+
function isRecord(value) {
|
|
15479
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15480
|
+
}
|
|
15481
|
+
function bytesOf(value) {
|
|
15482
|
+
return typeof value === "string" ? Buffer2.from(value, "utf8") : Buffer2.from(value);
|
|
15483
|
+
}
|
|
15484
|
+
function sha256Hex(value) {
|
|
15485
|
+
return createHash2("sha256").update(bytesOf(value)).digest("hex");
|
|
15486
|
+
}
|
|
15487
|
+
function normalizeHashId(value) {
|
|
15488
|
+
const trimmed = value.trim().toLowerCase();
|
|
15489
|
+
const hex = trimmed.startsWith("sha256:") ? trimmed.slice("sha256:".length) : trimmed;
|
|
15490
|
+
if (!/^[a-f0-9]{64}$/u.test(hex)) {
|
|
15491
|
+
throw new TypeError(`invalid sha256 hash id: ${value}`);
|
|
15492
|
+
}
|
|
15493
|
+
return `sha256:${hex}`;
|
|
15494
|
+
}
|
|
15495
|
+
function assertDocumentSourceType(value) {
|
|
15496
|
+
if (value !== "file" && value !== "lark") {
|
|
15497
|
+
throw new TypeError(`document source_type must be file or lark: ${value}`);
|
|
15498
|
+
}
|
|
15499
|
+
}
|
|
15500
|
+
function normalizeDocumentSourceName(name) {
|
|
15501
|
+
const value = name.trim();
|
|
15502
|
+
if (!DOCUMENT_SOURCE_NAME_RE.test(value)) {
|
|
15503
|
+
throw new TypeError(`document source name must be a lowercase path-safe slug: ${name}`);
|
|
15504
|
+
}
|
|
15505
|
+
return value;
|
|
15506
|
+
}
|
|
15507
|
+
function normalizeSnapshotRelativePath(path2) {
|
|
15508
|
+
const value = path2.trim();
|
|
15509
|
+
if (value.length === 0 || value.includes("\x00") || value.startsWith("/") || /^[a-zA-Z]:[\\/]/u.test(value)) {
|
|
15510
|
+
throw new TypeError(`snapshot path must be a POSIX relative path: ${path2}`);
|
|
15511
|
+
}
|
|
15512
|
+
if (value.includes("\\")) {
|
|
15513
|
+
throw new TypeError(`snapshot path must use POSIX separators: ${path2}`);
|
|
15514
|
+
}
|
|
15515
|
+
const segments = value.split("/");
|
|
15516
|
+
if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
15517
|
+
throw new TypeError(`snapshot path must not contain empty, dot, or traversal segments: ${path2}`);
|
|
15518
|
+
}
|
|
15519
|
+
return value;
|
|
15520
|
+
}
|
|
15521
|
+
function encodeSnapshotLocatorPath(path2) {
|
|
15522
|
+
return normalizeSnapshotRelativePath(path2).split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
15523
|
+
}
|
|
15524
|
+
function decodeSnapshotLocatorPath(path2) {
|
|
15525
|
+
try {
|
|
15526
|
+
return normalizeSnapshotRelativePath(path2.split("/").map((segment) => decodeURIComponent(segment)).join("/"));
|
|
15527
|
+
} catch (error) {
|
|
15528
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15529
|
+
throw new TypeError(`invalid encoded snapshot locator path: ${path2}: ${message}`);
|
|
15530
|
+
}
|
|
15531
|
+
}
|
|
15532
|
+
function parseDocumentSourceLocator(source2) {
|
|
15533
|
+
const match = /^(file|lark):([^/]+)\/(.+)$/u.exec(source2);
|
|
15534
|
+
if (match?.[1] === undefined || match[2] === undefined || match[3] === undefined)
|
|
15535
|
+
return null;
|
|
15536
|
+
try {
|
|
15537
|
+
return {
|
|
15538
|
+
sourceType: match[1],
|
|
15539
|
+
sourceName: normalizeDocumentSourceName(match[2]),
|
|
15540
|
+
documentPath: decodeSnapshotLocatorPath(match[3])
|
|
15541
|
+
};
|
|
15542
|
+
} catch {
|
|
15543
|
+
return null;
|
|
15544
|
+
}
|
|
15545
|
+
}
|
|
15546
|
+
function normalizeMarkdownDocument(input) {
|
|
15547
|
+
let text = input.normalize("NFC");
|
|
15548
|
+
if (text.startsWith(BOM)) {
|
|
15549
|
+
text = text.slice(BOM.length);
|
|
15550
|
+
}
|
|
15551
|
+
text = text.replace(/\r\n?/g, `
|
|
15552
|
+
`);
|
|
15553
|
+
text = text.split(`
|
|
15554
|
+
`).map((line) => line.replace(/[ \t]+$/u, "")).join(`
|
|
15555
|
+
`);
|
|
15556
|
+
text = text.replace(/\n{3,}/g, `
|
|
15557
|
+
|
|
15558
|
+
`);
|
|
15559
|
+
text = text.replace(/^(\s*)[*+]([ \t])/gmu, "$1-$2");
|
|
15560
|
+
const fenceCount = (text.match(/^```/gmu) ?? []).length;
|
|
15561
|
+
if (fenceCount % 2 === 1) {
|
|
15562
|
+
if (!text.endsWith(`
|
|
15563
|
+
`))
|
|
15564
|
+
text += `
|
|
15565
|
+
`;
|
|
15566
|
+
text += "```\n";
|
|
15567
|
+
}
|
|
15568
|
+
return text;
|
|
15569
|
+
}
|
|
15570
|
+
function computeDocumentContentHash(bytes) {
|
|
15571
|
+
return `sha256:${sha256Hex(bytes)}`;
|
|
15572
|
+
}
|
|
15573
|
+
function countMarkdownLines(bytes) {
|
|
15574
|
+
const text = bytesOf(bytes).toString("utf8");
|
|
15575
|
+
if (text.length === 0)
|
|
15576
|
+
return 0;
|
|
15577
|
+
const normalized = text.replace(/\r\n?/g, `
|
|
15578
|
+
`);
|
|
15579
|
+
return normalized.endsWith(`
|
|
15580
|
+
`) ? normalized.split(`
|
|
15581
|
+
`).length - 1 : normalized.split(`
|
|
15582
|
+
`).length;
|
|
15583
|
+
}
|
|
15584
|
+
function computeLogicalRawHash(files) {
|
|
15585
|
+
const hash2 = createHash2("sha256");
|
|
15586
|
+
const sorted = [...files].map((file) => ({
|
|
15587
|
+
path: normalizeSnapshotRelativePath(file.path),
|
|
15588
|
+
bytes: bytesOf(file.bytes)
|
|
15589
|
+
})).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
15590
|
+
for (const file of sorted) {
|
|
15591
|
+
hash2.update(Buffer2.from(file.path, "utf8"));
|
|
15592
|
+
hash2.update("\x00");
|
|
15593
|
+
hash2.update(Buffer2.from(String(file.bytes.byteLength), "utf8"));
|
|
15594
|
+
hash2.update("\x00");
|
|
15595
|
+
hash2.update(file.bytes);
|
|
15596
|
+
hash2.update(`
|
|
15597
|
+
`);
|
|
15598
|
+
}
|
|
15599
|
+
return `sha256:${hash2.digest("hex")}`;
|
|
15600
|
+
}
|
|
15601
|
+
function slugSegment(value) {
|
|
15602
|
+
return value.trim().toLowerCase().normalize("NFC").replace(/[`*_~[\]()]/gu, "").replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 64) || "document";
|
|
15603
|
+
}
|
|
15604
|
+
function headingFromLine(line) {
|
|
15605
|
+
const match = /^(#{1,6})\s+(.+?)\s*#*\s*$/u.exec(line);
|
|
15606
|
+
if (!match)
|
|
15607
|
+
return null;
|
|
15608
|
+
const title = (match[2] ?? "").trim();
|
|
15609
|
+
if (title.length === 0)
|
|
15610
|
+
return null;
|
|
15611
|
+
return { level: match[1].length, title };
|
|
15612
|
+
}
|
|
15613
|
+
function fenceMarker(line) {
|
|
15614
|
+
const match = /^\s{0,3}(`{3,}|~{3,})/u.exec(line);
|
|
15615
|
+
if (!match?.[1])
|
|
15616
|
+
return null;
|
|
15617
|
+
return { marker: match[1][0], length: match[1].length };
|
|
15618
|
+
}
|
|
15619
|
+
function truncateSourceSpanHash(hash2, length = DEFAULT_SOURCE_SPAN_HASH_LENGTH) {
|
|
15620
|
+
if (!/^[a-f0-9]{64}$/u.test(hash2)) {
|
|
15621
|
+
throw new TypeError(`source span hash must be a 64-character lowercase sha256 hex digest: ${hash2}`);
|
|
15622
|
+
}
|
|
15623
|
+
if (!Number.isInteger(length) || length < 8 || length > 64) {
|
|
15624
|
+
throw new TypeError(`source span hash length must be an integer between 8 and 64: ${length}`);
|
|
15625
|
+
}
|
|
15626
|
+
return hash2.slice(0, length);
|
|
15627
|
+
}
|
|
15628
|
+
function snapshotTextLines(markdown) {
|
|
15629
|
+
if (markdown.length === 0)
|
|
15630
|
+
return [];
|
|
15631
|
+
const lines = markdown.split(`
|
|
15632
|
+
`);
|
|
15633
|
+
if (markdown.endsWith(`
|
|
15634
|
+
`))
|
|
15635
|
+
lines.pop();
|
|
15636
|
+
return lines;
|
|
15637
|
+
}
|
|
15638
|
+
function headingContextForLine(markdown, lineStart) {
|
|
15639
|
+
const lines = snapshotTextLines(markdown);
|
|
15640
|
+
const headings = [];
|
|
15641
|
+
let fence = null;
|
|
15642
|
+
const maxIndex = Math.min(Math.max(lineStart, 1), lines.length);
|
|
15643
|
+
for (let index = 0;index < maxIndex; index += 1) {
|
|
15644
|
+
const line = lines[index] ?? "";
|
|
15645
|
+
const marker = fenceMarker(line);
|
|
15646
|
+
if (fence !== null) {
|
|
15647
|
+
if (marker !== null && marker.marker === fence.marker && marker.length >= fence.length) {
|
|
15648
|
+
fence = null;
|
|
15649
|
+
}
|
|
15650
|
+
continue;
|
|
15651
|
+
}
|
|
15652
|
+
if (marker !== null) {
|
|
15653
|
+
fence = marker;
|
|
15654
|
+
continue;
|
|
15655
|
+
}
|
|
15656
|
+
const heading = headingFromLine(line);
|
|
15657
|
+
if (heading !== null) {
|
|
15658
|
+
headings.splice(heading.level - 1);
|
|
15659
|
+
headings[heading.level - 1] = {
|
|
15660
|
+
...heading,
|
|
15661
|
+
headingSlug: slugSegment(heading.title)
|
|
15662
|
+
};
|
|
15663
|
+
}
|
|
15664
|
+
}
|
|
15665
|
+
const headingPath = headings.flatMap((heading) => heading === undefined ? [] : [heading.title]);
|
|
15666
|
+
const headingHint = [...headings].reverse().find((heading) => heading !== undefined)?.headingSlug ?? "document";
|
|
15667
|
+
return {
|
|
15668
|
+
heading_hint: headingHint,
|
|
15669
|
+
heading_path: headingPath
|
|
15670
|
+
};
|
|
15671
|
+
}
|
|
15672
|
+
function createDocumentSourceSpan(markdown, input) {
|
|
15673
|
+
const lines = snapshotTextLines(markdown);
|
|
15674
|
+
const lineStart = input.lineStart;
|
|
15675
|
+
const lineEnd = input.lineEnd;
|
|
15676
|
+
if (!Number.isInteger(lineStart) || !Number.isInteger(lineEnd) || lineStart < 1 || lineEnd < lineStart) {
|
|
15677
|
+
throw new TypeError(`source span line range must be positive and ordered: L${lineStart}-${lineEnd}`);
|
|
15678
|
+
}
|
|
15679
|
+
if (lineEnd > lines.length) {
|
|
15680
|
+
throw new TypeError(`source span line range exceeds document line count: L${lineStart}-${lineEnd}`);
|
|
15681
|
+
}
|
|
15682
|
+
const text = lines.slice(lineStart - 1, lineEnd).join(`
|
|
15683
|
+
`);
|
|
15684
|
+
const fullSpanHash = sha256Hex(text);
|
|
15685
|
+
const heading = headingContextForLine(markdown, lineStart);
|
|
15686
|
+
return {
|
|
15687
|
+
heading_hint: heading.heading_hint,
|
|
15688
|
+
heading_path: heading.heading_path,
|
|
15689
|
+
line_start: lineStart,
|
|
15690
|
+
line_end: lineEnd,
|
|
15691
|
+
line_range: `L${lineStart}-${lineEnd}`,
|
|
15692
|
+
span_hash: truncateSourceSpanHash(fullSpanHash, input.hashLength ?? DEFAULT_SOURCE_SPAN_HASH_LENGTH),
|
|
15693
|
+
full_span_hash: fullSpanHash,
|
|
15694
|
+
text,
|
|
15695
|
+
text_preview: text.replace(/\s+/gu, " ").slice(0, 160)
|
|
15696
|
+
};
|
|
15697
|
+
}
|
|
15698
|
+
function formatSpanSourceRef(span, options = {}) {
|
|
15699
|
+
const hash2 = options.hashLength === undefined ? span.span_hash : truncateSourceSpanHash(span.full_span_hash, options.hashLength);
|
|
15700
|
+
const body2 = `#span:${span.heading_hint} L${span.line_start}-${span.line_end}@${hash2}`;
|
|
15701
|
+
return options.locator !== undefined ? `${options.locator}${body2}` : body2;
|
|
15702
|
+
}
|
|
15703
|
+
function formatCanonicalProseSourceRef(input) {
|
|
15704
|
+
const sourceName = normalizeDocumentSourceName(input.sourceName);
|
|
15705
|
+
const documentPath = normalizeSnapshotRelativePath(input.documentPath);
|
|
15706
|
+
return formatSpanSourceRef(input.span, {
|
|
15707
|
+
locator: `${input.sourceType}:${sourceName}/${encodeSnapshotLocatorPath(documentPath)}`,
|
|
15708
|
+
...input.hashLength !== undefined ? { hashLength: input.hashLength } : {}
|
|
15709
|
+
});
|
|
15710
|
+
}
|
|
15711
|
+
function parseSpanSourceRef(value) {
|
|
15712
|
+
const normalized = value.trim();
|
|
15713
|
+
const match = /^(?:(?<locator>[^#\s]+))?#span:(?<heading>[^\s#]+)\s+L(?<start>\d+)-(?<end>\d+)@(?<hash>[a-f0-9]{8,64})$/u.exec(normalized);
|
|
15714
|
+
if (!match?.groups)
|
|
15715
|
+
return null;
|
|
15716
|
+
const lineStart = Number(match.groups.start);
|
|
15717
|
+
const lineEnd = Number(match.groups.end);
|
|
15718
|
+
if (!Number.isInteger(lineStart) || !Number.isInteger(lineEnd) || lineStart < 1 || lineEnd < lineStart) {
|
|
15719
|
+
return null;
|
|
15720
|
+
}
|
|
15721
|
+
return {
|
|
15722
|
+
...match.groups.locator !== undefined ? { locator: match.groups.locator } : {},
|
|
15723
|
+
heading_hint: match.groups.heading,
|
|
15724
|
+
line_start: lineStart,
|
|
15725
|
+
line_end: lineEnd,
|
|
15726
|
+
span_hash: match.groups.hash
|
|
15727
|
+
};
|
|
15728
|
+
}
|
|
15729
|
+
function sourceSpanHashMatches(refHash, fullSpanHash) {
|
|
15730
|
+
return SOURCE_SPAN_HASH_RE.test(refHash) && /^[a-f0-9]{64}$/u.test(fullSpanHash) && fullSpanHash.startsWith(refHash);
|
|
15731
|
+
}
|
|
15732
|
+
function createDocumentSnapshotFileEntry(input) {
|
|
15733
|
+
const path2 = normalizeSnapshotRelativePath(input.path);
|
|
15734
|
+
if ((input.title?.trim().length ?? 0) === 0 && (input.locator?.trim().length ?? 0) === 0) {
|
|
15735
|
+
throw new TypeError(`snapshot file "${path2}" must include title or locator`);
|
|
15736
|
+
}
|
|
15737
|
+
return {
|
|
15738
|
+
path: path2,
|
|
15739
|
+
content_hash: computeDocumentContentHash(input.bytes),
|
|
15740
|
+
line_count: countMarkdownLines(input.bytes),
|
|
15741
|
+
...input.title !== undefined ? { title: input.title.trim() } : {},
|
|
15742
|
+
...input.locator !== undefined ? { locator: input.locator.trim() } : {}
|
|
15743
|
+
};
|
|
15744
|
+
}
|
|
15745
|
+
function createDocumentSnapshotManifest(input) {
|
|
15746
|
+
const files = input.files.map(createDocumentSnapshotFileEntry);
|
|
15747
|
+
const sourceName = normalizeDocumentSourceName(input.sourceName);
|
|
15748
|
+
return parseDocumentSnapshotManifest({
|
|
15749
|
+
schema_version: DOCUMENT_SNAPSHOT_MANIFEST_SCHEMA_VERSION,
|
|
15750
|
+
source_type: input.sourceType,
|
|
15751
|
+
source_name: sourceName,
|
|
15752
|
+
captured_at: input.capturedAt,
|
|
15753
|
+
snapshot_hash: computeLogicalRawHash(input.files),
|
|
15754
|
+
normalizer_version: input.normalizerVersion ?? DOCUMENT_EVIDENCE_NORMALIZER_VERSION,
|
|
15755
|
+
files,
|
|
15756
|
+
...input.assets !== undefined ? { assets: input.assets } : {},
|
|
15757
|
+
...input.metadata !== undefined ? { metadata: input.metadata } : {}
|
|
15758
|
+
});
|
|
15759
|
+
}
|
|
15760
|
+
function parseFileEntry(value, index) {
|
|
15761
|
+
if (!isRecord(value)) {
|
|
15762
|
+
throw new TypeError(`snapshot manifest files[${index}] must be an object`);
|
|
15763
|
+
}
|
|
15764
|
+
if (typeof value.path !== "string") {
|
|
15765
|
+
throw new TypeError(`snapshot manifest files[${index}].path must be a string`);
|
|
15766
|
+
}
|
|
15767
|
+
if (typeof value.content_hash !== "string" || !HASH_ID_RE.test(value.content_hash)) {
|
|
15768
|
+
throw new TypeError(`snapshot manifest files[${index}].content_hash must be sha256:<64 hex>`);
|
|
15769
|
+
}
|
|
15770
|
+
if (!Number.isInteger(value.line_count) || value.line_count < 0) {
|
|
15771
|
+
throw new TypeError(`snapshot manifest files[${index}].line_count must be a non-negative integer`);
|
|
15772
|
+
}
|
|
15773
|
+
const title = typeof value.title === "string" && value.title.trim().length > 0 ? value.title.trim() : undefined;
|
|
15774
|
+
const locator = typeof value.locator === "string" && value.locator.trim().length > 0 ? value.locator.trim() : undefined;
|
|
15775
|
+
if (title === undefined && locator === undefined) {
|
|
15776
|
+
throw new TypeError(`snapshot manifest files[${index}] must include title or locator`);
|
|
15777
|
+
}
|
|
15778
|
+
return {
|
|
15779
|
+
path: normalizeSnapshotRelativePath(value.path),
|
|
15780
|
+
content_hash: normalizeHashId(value.content_hash),
|
|
15781
|
+
line_count: value.line_count,
|
|
15782
|
+
...title !== undefined ? { title } : {},
|
|
15783
|
+
...locator !== undefined ? { locator } : {}
|
|
15784
|
+
};
|
|
15785
|
+
}
|
|
15786
|
+
function parseAssetEntry(value, index) {
|
|
15787
|
+
if (!isRecord(value)) {
|
|
15788
|
+
throw new TypeError(`snapshot manifest assets[${index}] must be an object`);
|
|
15789
|
+
}
|
|
15790
|
+
if (typeof value.path !== "string") {
|
|
15791
|
+
throw new TypeError(`snapshot manifest assets[${index}].path must be a string`);
|
|
15792
|
+
}
|
|
15793
|
+
const contentHash2 = typeof value.content_hash === "string" && HASH_ID_RE.test(value.content_hash) ? normalizeHashId(value.content_hash) : undefined;
|
|
15794
|
+
const mediaType = typeof value.media_type === "string" && value.media_type.trim().length > 0 ? value.media_type.trim() : undefined;
|
|
15795
|
+
const source2 = parseStringRecord(value.source, `snapshot manifest assets[${index}].source`);
|
|
15796
|
+
return {
|
|
15797
|
+
path: normalizeSnapshotRelativePath(value.path),
|
|
15798
|
+
...contentHash2 !== undefined ? { content_hash: contentHash2 } : {},
|
|
15799
|
+
...mediaType !== undefined ? { media_type: mediaType } : {},
|
|
15800
|
+
...source2 !== undefined ? { source: source2 } : {}
|
|
15801
|
+
};
|
|
15802
|
+
}
|
|
15803
|
+
function parseStringRecord(value, field) {
|
|
15804
|
+
if (value === undefined)
|
|
15805
|
+
return;
|
|
15806
|
+
if (!isRecord(value)) {
|
|
15807
|
+
throw new TypeError(`${field} must be an object`);
|
|
15808
|
+
}
|
|
15809
|
+
const result = {};
|
|
15810
|
+
for (const [key, item] of Object.entries(value)) {
|
|
15811
|
+
if (typeof item !== "string") {
|
|
15812
|
+
throw new TypeError(`${field}.${key} must be a string`);
|
|
15813
|
+
}
|
|
15814
|
+
const trimmed = item.trim();
|
|
15815
|
+
if (trimmed.length > 0)
|
|
15816
|
+
result[key] = trimmed;
|
|
15817
|
+
}
|
|
15818
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
15819
|
+
}
|
|
15820
|
+
function optionalMetadataString(value, field) {
|
|
15821
|
+
if (value === undefined)
|
|
15822
|
+
return;
|
|
15823
|
+
if (typeof value !== "string") {
|
|
15824
|
+
throw new TypeError(`${field} must be a string`);
|
|
15825
|
+
}
|
|
15826
|
+
const trimmed = value.trim();
|
|
15827
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
15828
|
+
}
|
|
15829
|
+
function optionalMetadataStringArray(value, field) {
|
|
15830
|
+
if (value === undefined)
|
|
15831
|
+
return;
|
|
15832
|
+
if (!Array.isArray(value)) {
|
|
15833
|
+
throw new TypeError(`${field} must be an array`);
|
|
15834
|
+
}
|
|
15835
|
+
return value.map((item, index) => {
|
|
15836
|
+
if (typeof item !== "string") {
|
|
15837
|
+
throw new TypeError(`${field}[${index}] must be a string`);
|
|
15838
|
+
}
|
|
15839
|
+
const trimmed = item.trim();
|
|
15840
|
+
if (trimmed.length === 0) {
|
|
15841
|
+
throw new TypeError(`${field}[${index}] must be a non-empty string`);
|
|
15842
|
+
}
|
|
15843
|
+
return trimmed;
|
|
15844
|
+
});
|
|
15845
|
+
}
|
|
15846
|
+
function optionalRouteFiles(value, field) {
|
|
15847
|
+
if (value === undefined)
|
|
15848
|
+
return;
|
|
15849
|
+
if (!Array.isArray(value)) {
|
|
15850
|
+
throw new TypeError(`${field} must be an array`);
|
|
15851
|
+
}
|
|
15852
|
+
const result = value.map((item, index) => {
|
|
15853
|
+
if (!isRecord(item)) {
|
|
15854
|
+
throw new TypeError(`${field}[${index}] must be an object`);
|
|
15855
|
+
}
|
|
15856
|
+
const path2 = optionalMetadataString(item.path, `${field}[${index}].path`);
|
|
15857
|
+
if (path2 === undefined) {
|
|
15858
|
+
throw new TypeError(`${field}[${index}].path must be a non-empty string`);
|
|
15859
|
+
}
|
|
15860
|
+
const routes = optionalMetadataStringArray(item.routes, `${field}[${index}].routes`) ?? [];
|
|
15861
|
+
return {
|
|
15862
|
+
path: normalizeSnapshotRelativePath(path2),
|
|
15863
|
+
routes
|
|
15864
|
+
};
|
|
15865
|
+
}).filter((item) => item.routes.length > 0);
|
|
15866
|
+
return result.length > 0 ? result : undefined;
|
|
15867
|
+
}
|
|
15868
|
+
function optionalRouteHints(value, field) {
|
|
15869
|
+
if (value === undefined)
|
|
15870
|
+
return;
|
|
15871
|
+
if (!Array.isArray(value)) {
|
|
15872
|
+
throw new TypeError(`${field} must be an array`);
|
|
15873
|
+
}
|
|
15874
|
+
const result = value.map((item, index) => {
|
|
15875
|
+
if (!isRecord(item)) {
|
|
15876
|
+
throw new TypeError(`${field}[${index}] must be an object`);
|
|
15877
|
+
}
|
|
15878
|
+
const documentPath = optionalMetadataString(item.documentPath, `${field}[${index}].documentPath`);
|
|
15879
|
+
const route = optionalMetadataString(item.route, `${field}[${index}].route`);
|
|
15880
|
+
const metadataPath = optionalMetadataString(item.metadataPath, `${field}[${index}].metadataPath`);
|
|
15881
|
+
if (documentPath === undefined || route === undefined || metadataPath === undefined) {
|
|
15882
|
+
throw new TypeError(`${field}[${index}] must include documentPath, route, and metadataPath`);
|
|
15883
|
+
}
|
|
15884
|
+
return {
|
|
15885
|
+
documentPath: normalizeSnapshotRelativePath(documentPath),
|
|
15886
|
+
route,
|
|
15887
|
+
metadataPath: normalizeSnapshotRelativePath(metadataPath)
|
|
15888
|
+
};
|
|
15889
|
+
});
|
|
15890
|
+
return result.length > 0 ? result : undefined;
|
|
15891
|
+
}
|
|
15892
|
+
function parseManifestMetadata(value) {
|
|
15893
|
+
if (value === undefined)
|
|
15894
|
+
return;
|
|
15895
|
+
if (!isRecord(value)) {
|
|
15896
|
+
throw new TypeError("snapshot manifest metadata must be an object");
|
|
15897
|
+
}
|
|
15898
|
+
let source2;
|
|
15899
|
+
if (value.source !== undefined) {
|
|
15900
|
+
if (!isRecord(value.source)) {
|
|
15901
|
+
throw new TypeError("snapshot manifest metadata.source must be an object");
|
|
15902
|
+
}
|
|
15903
|
+
const url = optionalMetadataString(value.source.url, "snapshot manifest metadata.source.url");
|
|
15904
|
+
const docToken = optionalMetadataString(value.source.docToken, "snapshot manifest metadata.source.docToken");
|
|
15905
|
+
const wikiToken = optionalMetadataString(value.source.wikiToken, "snapshot manifest metadata.source.wikiToken");
|
|
15906
|
+
const title = optionalMetadataString(value.source.title, "snapshot manifest metadata.source.title");
|
|
15907
|
+
const revisionId = optionalMetadataString(value.source.revisionId, "snapshot manifest metadata.source.revisionId");
|
|
15908
|
+
source2 = {
|
|
15909
|
+
...url !== undefined ? { url } : {},
|
|
15910
|
+
...docToken !== undefined ? { docToken } : {},
|
|
15911
|
+
...wikiToken !== undefined ? { wikiToken } : {},
|
|
15912
|
+
...title !== undefined ? { title } : {},
|
|
15913
|
+
...revisionId !== undefined ? { revisionId } : {}
|
|
15914
|
+
};
|
|
15915
|
+
if (Object.keys(source2).length === 0)
|
|
15916
|
+
source2 = undefined;
|
|
15917
|
+
}
|
|
15918
|
+
let capture;
|
|
15919
|
+
if (value.capture !== undefined) {
|
|
15920
|
+
if (!isRecord(value.capture)) {
|
|
15921
|
+
throw new TypeError("snapshot manifest metadata.capture must be an object");
|
|
15922
|
+
}
|
|
15923
|
+
const include = optionalMetadataStringArray(value.capture.include, "snapshot manifest metadata.capture.include");
|
|
15924
|
+
const documentExtensions = optionalMetadataStringArray(value.capture.documentExtensions, "snapshot manifest metadata.capture.documentExtensions");
|
|
15925
|
+
const routeFiles = optionalRouteFiles(value.capture.routeFiles, "snapshot manifest metadata.capture.routeFiles");
|
|
15926
|
+
const routeHints = optionalRouteHints(value.capture.routeHints, "snapshot manifest metadata.capture.routeHints");
|
|
15927
|
+
capture = {
|
|
15928
|
+
...include !== undefined ? { include } : {},
|
|
15929
|
+
...documentExtensions !== undefined ? { documentExtensions } : {},
|
|
15930
|
+
...routeFiles !== undefined ? { routeFiles } : {},
|
|
15931
|
+
...routeHints !== undefined ? { routeHints } : {}
|
|
15932
|
+
};
|
|
15933
|
+
if (Object.keys(capture).length === 0)
|
|
15934
|
+
capture = undefined;
|
|
15935
|
+
}
|
|
15936
|
+
return source2 !== undefined || capture !== undefined ? {
|
|
15937
|
+
...source2 !== undefined ? { source: source2 } : {},
|
|
15938
|
+
...capture !== undefined ? { capture } : {}
|
|
15939
|
+
} : {};
|
|
15940
|
+
}
|
|
15941
|
+
function parseDocumentSnapshotManifest(value) {
|
|
15942
|
+
if (!isRecord(value)) {
|
|
15943
|
+
throw new TypeError("snapshot manifest must be an object");
|
|
15944
|
+
}
|
|
15945
|
+
if (value.schema_version !== DOCUMENT_SNAPSHOT_MANIFEST_SCHEMA_VERSION) {
|
|
15946
|
+
throw new TypeError(`snapshot manifest schema_version must be ${DOCUMENT_SNAPSHOT_MANIFEST_SCHEMA_VERSION}`);
|
|
15947
|
+
}
|
|
15948
|
+
if (typeof value.source_type !== "string") {
|
|
15949
|
+
throw new TypeError("snapshot manifest source_type must be a string");
|
|
15950
|
+
}
|
|
15951
|
+
assertDocumentSourceType(value.source_type);
|
|
15952
|
+
if (typeof value.source_name !== "string" || value.source_name.trim().length === 0) {
|
|
15953
|
+
throw new TypeError("snapshot manifest source_name must be a non-empty string");
|
|
15954
|
+
}
|
|
15955
|
+
const sourceName = normalizeDocumentSourceName(value.source_name);
|
|
15956
|
+
if (typeof value.captured_at !== "string" || value.captured_at.trim().length === 0) {
|
|
15957
|
+
throw new TypeError("snapshot manifest captured_at must be a non-empty string");
|
|
15958
|
+
}
|
|
15959
|
+
if (typeof value.snapshot_hash !== "string" || !HASH_ID_RE.test(value.snapshot_hash)) {
|
|
15960
|
+
throw new TypeError("snapshot manifest snapshot_hash must be sha256:<64 hex>");
|
|
15961
|
+
}
|
|
15962
|
+
if (typeof value.normalizer_version !== "string" || value.normalizer_version.trim().length === 0) {
|
|
15963
|
+
throw new TypeError("snapshot manifest normalizer_version must be a non-empty string");
|
|
15964
|
+
}
|
|
15965
|
+
if (!Array.isArray(value.files)) {
|
|
15966
|
+
throw new TypeError("snapshot manifest files must be an array");
|
|
15967
|
+
}
|
|
15968
|
+
const assets = value.assets === undefined ? undefined : Array.isArray(value.assets) ? value.assets.map(parseAssetEntry) : (() => {
|
|
15969
|
+
throw new TypeError("snapshot manifest assets must be an array");
|
|
15970
|
+
})();
|
|
15971
|
+
const metadata2 = parseManifestMetadata(value.metadata);
|
|
15972
|
+
const files = value.files.map(parseFileEntry);
|
|
15973
|
+
const seenPaths = new Set;
|
|
15974
|
+
for (const file of files) {
|
|
15975
|
+
if (seenPaths.has(file.path)) {
|
|
15976
|
+
throw new TypeError(`snapshot manifest files contains duplicate path: ${file.path}`);
|
|
15977
|
+
}
|
|
15978
|
+
seenPaths.add(file.path);
|
|
15979
|
+
}
|
|
15980
|
+
return {
|
|
15981
|
+
schema_version: DOCUMENT_SNAPSHOT_MANIFEST_SCHEMA_VERSION,
|
|
15982
|
+
source_type: value.source_type,
|
|
15983
|
+
source_name: sourceName,
|
|
15984
|
+
captured_at: value.captured_at,
|
|
15985
|
+
snapshot_hash: normalizeHashId(value.snapshot_hash),
|
|
15986
|
+
normalizer_version: value.normalizer_version,
|
|
15987
|
+
files,
|
|
15988
|
+
...assets !== undefined ? { assets } : {},
|
|
15989
|
+
...metadata2 !== undefined ? { metadata: metadata2 } : {}
|
|
15990
|
+
};
|
|
15991
|
+
}
|
|
15466
15992
|
// src/repository.ts
|
|
15467
15993
|
import { readFile as readFile2, readdir as readdir2, stat } from "node:fs/promises";
|
|
15468
15994
|
import path2 from "node:path";
|
|
@@ -15688,6 +16214,29 @@ var readModuleName = async (dirPath, manifestFile) => {
|
|
|
15688
16214
|
}
|
|
15689
16215
|
return null;
|
|
15690
16216
|
};
|
|
16217
|
+
var readModuleVersion = async (dirPath, manifestFile) => {
|
|
16218
|
+
try {
|
|
16219
|
+
const content = await readFile(join(dirPath, manifestFile), "utf-8");
|
|
16220
|
+
if (manifestFile === "package.json") {
|
|
16221
|
+
const parsed = JSON.parse(content);
|
|
16222
|
+
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
|
|
16223
|
+
}
|
|
16224
|
+
if (manifestFile === "pyproject.toml" || manifestFile === "Cargo.toml") {
|
|
16225
|
+
const match = content.match(/^\s*version\s*=\s*["']([^"']+)["']/m);
|
|
16226
|
+
return match?.[1] ?? null;
|
|
16227
|
+
}
|
|
16228
|
+
if (manifestFile === "setup.py") {
|
|
16229
|
+
const match = content.match(/version\s*=\s*["']([^"']+)["']/);
|
|
16230
|
+
return match?.[1] ?? null;
|
|
16231
|
+
}
|
|
16232
|
+
if (manifestFile === "pom.xml") {
|
|
16233
|
+
return content.match(/<version>\s*([^<]+?)\s*<\/version>/i)?.[1] ?? null;
|
|
16234
|
+
}
|
|
16235
|
+
} catch {
|
|
16236
|
+
return null;
|
|
16237
|
+
}
|
|
16238
|
+
return null;
|
|
16239
|
+
};
|
|
15691
16240
|
var MANIFEST_NAMES = new Set(MANIFEST_FILES.map((f) => f.toLowerCase()));
|
|
15692
16241
|
var findSubModuleDirs = async (rootDir, excludeDirs, gitFiles, pathFilter2) => {
|
|
15693
16242
|
const root = resolve(rootDir);
|
|
@@ -15805,6 +16354,35 @@ var detectModules = async (repoPath, ref, pathFilter2) => {
|
|
|
15805
16354
|
}
|
|
15806
16355
|
return modules.sort((a, b) => a.name.localeCompare(b.name));
|
|
15807
16356
|
};
|
|
16357
|
+
var detectModuleBoundaries = async (repoPath, ref, pathFilter2) => {
|
|
16358
|
+
const repoRoot = resolve(repoPath);
|
|
16359
|
+
const gitFiles = await tryGitListFiles(repoRoot, ref);
|
|
16360
|
+
const subModuleDirs = await findSubModuleDirs(repoRoot, undefined, gitFiles, pathFilter2);
|
|
16361
|
+
const boundaries = [];
|
|
16362
|
+
const rootManifest = await findManifestFile(repoRoot);
|
|
16363
|
+
if (rootManifest) {
|
|
16364
|
+
const version2 = await readModuleVersion(repoRoot, rootManifest);
|
|
16365
|
+
boundaries.push({
|
|
16366
|
+
name: await readModuleName(repoRoot, rootManifest) ?? basename(repoRoot),
|
|
16367
|
+
path: ".",
|
|
16368
|
+
manifest: rootManifest,
|
|
16369
|
+
...version2 !== null ? { version: version2 } : {}
|
|
16370
|
+
});
|
|
16371
|
+
}
|
|
16372
|
+
for (const subDir of subModuleDirs) {
|
|
16373
|
+
const manifest = await findManifestFile(subDir);
|
|
16374
|
+
if (!manifest)
|
|
16375
|
+
continue;
|
|
16376
|
+
const version2 = await readModuleVersion(subDir, manifest);
|
|
16377
|
+
boundaries.push({
|
|
16378
|
+
name: await readModuleName(subDir, manifest) ?? basename(subDir),
|
|
16379
|
+
path: toPosixPath(relative(repoRoot, subDir)) || ".",
|
|
16380
|
+
manifest,
|
|
16381
|
+
...version2 !== null ? { version: version2 } : {}
|
|
16382
|
+
});
|
|
16383
|
+
}
|
|
16384
|
+
return boundaries.sort((left, right) => left.path.localeCompare(right.path));
|
|
16385
|
+
};
|
|
15808
16386
|
var detectModuleAt = async (repoPath, modulePath, ref, pathFilter2) => {
|
|
15809
16387
|
const repoRoot = resolve(repoPath);
|
|
15810
16388
|
const moduleDir = modulePath === "." ? repoRoot : resolve(repoRoot, modulePath);
|
|
@@ -16578,10 +17156,12 @@ var detectTechStack = async (repoPath) => {
|
|
|
16578
17156
|
return labels;
|
|
16579
17157
|
};
|
|
16580
17158
|
export {
|
|
17159
|
+
truncateSourceSpanHash,
|
|
16581
17160
|
symbolParamSchema,
|
|
16582
17161
|
symbolInfoSchema,
|
|
16583
17162
|
symbolDiffSchema,
|
|
16584
17163
|
stableStringify,
|
|
17164
|
+
sourceSpanHashMatches,
|
|
16585
17165
|
sourceInfoSchema,
|
|
16586
17166
|
scanSourceFiles,
|
|
16587
17167
|
runRepositoryExtraction,
|
|
@@ -16596,9 +17176,15 @@ export {
|
|
|
16596
17176
|
prefixExtractionPaths,
|
|
16597
17177
|
prefixEntryDetectionPaths,
|
|
16598
17178
|
patternDetectionResultSchema,
|
|
17179
|
+
parseSpanSourceRef,
|
|
16599
17180
|
parseFile,
|
|
17181
|
+
parseDocumentSourceLocator,
|
|
17182
|
+
parseDocumentSnapshotManifest,
|
|
17183
|
+
normalizeSnapshotRelativePath,
|
|
16600
17184
|
normalizeRelativePath,
|
|
17185
|
+
normalizeMarkdownDocument,
|
|
16601
17186
|
normalizeExtractionPaths,
|
|
17187
|
+
normalizeDocumentSourceName,
|
|
16602
17188
|
metaText,
|
|
16603
17189
|
manifestTypeSchema,
|
|
16604
17190
|
manifestText,
|
|
@@ -16612,6 +17198,8 @@ export {
|
|
|
16612
17198
|
getGitCommitHash,
|
|
16613
17199
|
generateSymbolDiff,
|
|
16614
17200
|
generateDigest,
|
|
17201
|
+
formatSpanSourceRef,
|
|
17202
|
+
formatCanonicalProseSourceRef,
|
|
16615
17203
|
flattenSymbols,
|
|
16616
17204
|
fileInfoSchema,
|
|
16617
17205
|
extractionStatsSchema,
|
|
@@ -16620,12 +17208,21 @@ export {
|
|
|
16620
17208
|
extractionMetaSchema,
|
|
16621
17209
|
entryFileSchema,
|
|
16622
17210
|
entryDetectionResultSchema,
|
|
17211
|
+
encodeSnapshotLocatorPath,
|
|
16623
17212
|
digestStatsSchema,
|
|
16624
17213
|
digestDataSchema,
|
|
16625
17214
|
detectTechStack,
|
|
16626
17215
|
detectModules,
|
|
17216
|
+
detectModuleBoundaries,
|
|
16627
17217
|
detectModuleAt,
|
|
17218
|
+
decodeSnapshotLocatorPath,
|
|
16628
17219
|
createModuleFileSystem,
|
|
17220
|
+
createDocumentSourceSpan,
|
|
17221
|
+
createDocumentSnapshotManifest,
|
|
17222
|
+
createDocumentSnapshotFileEntry,
|
|
17223
|
+
countMarkdownLines,
|
|
17224
|
+
computeLogicalRawHash,
|
|
17225
|
+
computeDocumentContentHash,
|
|
16629
17226
|
codeExtractRunnerInputSchema,
|
|
16630
17227
|
canonicalizeCodeVersionLabel,
|
|
16631
17228
|
buildSourceFileHashId,
|
|
@@ -16637,5 +17234,8 @@ export {
|
|
|
16637
17234
|
SCAN_EXCLUDED_DIRS,
|
|
16638
17235
|
PACKAGE_JSON,
|
|
16639
17236
|
ExtractionPluginRegistry,
|
|
17237
|
+
DOCUMENT_SNAPSHOT_MANIFEST_SCHEMA_VERSION,
|
|
17238
|
+
DOCUMENT_EVIDENCE_NORMALIZER_VERSION,
|
|
17239
|
+
DEFAULT_SOURCE_SPAN_HASH_LENGTH,
|
|
16640
17240
|
CODE_SNAPSHOT_META_SCHEMA_VERSION
|
|
16641
17241
|
};
|
package/package.json
CHANGED
package/wasm/tree-sitter.wasm
DELETED
|
Binary file
|