@first-tree-ai/context-tree 0.1.1 → 0.1.2

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.
@@ -1,619 +0,0 @@
1
- import { C as isRecord, S as parseMarkdownFrontmatter, _ as parseContextTreeRootNode, i as VALIDATION_CODES } from "./schemas-DyQ0V9Z3.mjs";
2
- import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
3
- import { dirname, isAbsolute, join, parse, posix, relative, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { spawnSync } from "node:child_process";
6
- import { fromMarkdown } from "mdast-util-from-markdown";
7
- //#region src/core/internal/packaged-resource.ts
8
- const PACKAGE_NAME = "@first-tree-ai/context-tree";
9
- function isPackageRoot(path) {
10
- const manifestPath = join(path, "package.json");
11
- if (!existsSync(manifestPath)) return false;
12
- try {
13
- const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
14
- return isRecord(manifest) && manifest.name === PACKAGE_NAME;
15
- } catch {
16
- return false;
17
- }
18
- }
19
- function resolvePackagedResource(...segments) {
20
- let candidate = dirname(fileURLToPath(import.meta.url));
21
- const filesystemRoot = parse(candidate).root;
22
- while (true) {
23
- if (isPackageRoot(candidate)) {
24
- const resource = resolve(candidate, ...segments);
25
- if (existsSync(resource)) return resource;
26
- throw new Error(`Packaged resource is missing: ${segments.join("/")}`);
27
- }
28
- if (candidate === filesystemRoot) break;
29
- candidate = dirname(candidate);
30
- }
31
- throw new Error(`Package root is missing while resolving: ${segments.join("/")}`);
32
- }
33
- function readPackageManifest() {
34
- const parsed = JSON.parse(readFileSync(resolvePackagedResource("package.json"), "utf8"));
35
- if (!isRecord(parsed)) throw new Error("Package metadata is invalid.");
36
- return parsed;
37
- }
38
- function readPackageVersion() {
39
- const manifest = readPackageManifest();
40
- if (typeof manifest.version !== "string") throw new Error("Package version is missing or invalid.");
41
- return manifest.version;
42
- }
43
- //#endregion
44
- //#region src/core/policy.ts
45
- function readContextTreePolicy() {
46
- return {
47
- content: readFileSync(resolvePackagedResource("policy", "context-tree-policy.md"), "utf8"),
48
- schemaVersion: 1
49
- };
50
- }
51
- //#endregion
52
- //#region src/core/path.ts
53
- function isPathInside(root, target) {
54
- const path = relative(root, target);
55
- return path === "" || !path.startsWith("..") && !isAbsolute(path);
56
- }
57
- function resolveTreeRoot(path) {
58
- const absolute = resolve(path);
59
- const entry = lstatSync(absolute);
60
- if (entry.isSymbolicLink() || !entry.isDirectory()) throw new Error(`Context Tree root must be a real directory: ${path}`);
61
- return realpathSync(absolute);
62
- }
63
- function toPosixPath(path) {
64
- return path.replace(/\\/gu, "/");
65
- }
66
- //#endregion
67
- //#region src/core/internal/content-class.ts
68
- const GENERATED_DIRECTORY_NAMES = new Set([
69
- "node_modules",
70
- "__pycache__",
71
- "dist",
72
- "build",
73
- ".next",
74
- ".turbo"
75
- ]);
76
- const REPO_INFRA_MARKDOWN_FILES = new Set(["AGENTS.md", "CLAUDE.md"]);
77
- const MANAGED_SYMLINK_PATHS = new Set(["WHITEPAPER.md"]);
78
- function toTreeRelativePosixPath(treeRoot, targetPath) {
79
- return relative(treeRoot, targetPath).replace(/\\/gu, "/");
80
- }
81
- function classifyContextContent(relativePath) {
82
- const parts = relativePath.replace(/\\/gu, "/").replace(/^\.\//u, "").split("/").filter((part) => part.length > 0);
83
- if (parts.some((part) => part.startsWith(".") || GENERATED_DIRECTORY_NAMES.has(part)) || parts[0] === "scripts" || REPO_INFRA_MARKDOWN_FILES.has(parts.at(-1) ?? "")) return "repo-infra";
84
- if (parts[0] === "members") return "member";
85
- return "normal";
86
- }
87
- function emptyContentClassCounts() {
88
- return {
89
- normal: 0,
90
- member: 0,
91
- "repo-infra": 0
92
- };
93
- }
94
- function readDirectoryEntries(path) {
95
- try {
96
- return readdirSync(path, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
97
- } catch {
98
- return [];
99
- }
100
- }
101
- function canonicalTarget$1(realTreeRoot, path) {
102
- try {
103
- const realTarget = realpathSync(path);
104
- if (!isPathInside(realTreeRoot, realTarget)) return { kind: "escaped" };
105
- return {
106
- kind: "resolved",
107
- relativePath: toTreeRelativePosixPath(realTreeRoot, realTarget)
108
- };
109
- } catch {
110
- return { kind: "unresolved" };
111
- }
112
- }
113
- function inspectMarkdownSymlink(realTreeRoot, absolutePath, contentClass) {
114
- let targetStat;
115
- try {
116
- targetStat = statSync(absolutePath);
117
- } catch {
118
- return { kind: "unresolved" };
119
- }
120
- const target = canonicalTarget$1(realTreeRoot, absolutePath);
121
- if (target.kind !== "resolved") return target;
122
- if (!targetStat.isFile()) return { kind: "unsupported" };
123
- if (classifyContextContent(target.relativePath) !== contentClass) return {
124
- kind: "content-class-mismatch",
125
- canonicalRelativePath: target.relativePath
126
- };
127
- return { kind: "regular" };
128
- }
129
- function collectContextMarkdownContent(treeRoot) {
130
- const directories = [];
131
- const directorySymlinks = [];
132
- const files = [];
133
- const realTreeRoot = realpathSync(treeRoot);
134
- function walk(directoryPath) {
135
- for (const entry of readDirectoryEntries(directoryPath)) {
136
- const absolutePath = join(directoryPath, entry.name);
137
- const relativePath = toTreeRelativePosixPath(treeRoot, absolutePath);
138
- const contentClass = classifyContextContent(relativePath);
139
- if (entry.isDirectory()) {
140
- if (contentClass !== "repo-infra") {
141
- directories.push(relativePath);
142
- walk(absolutePath);
143
- }
144
- continue;
145
- }
146
- const symbolicLink = entry.isSymbolicLink();
147
- if (symbolicLink) try {
148
- if (statSync(absolutePath).isDirectory()) {
149
- if (contentClass !== "repo-infra" || entry.name.endsWith(".md")) {
150
- const target = canonicalTarget$1(realTreeRoot, absolutePath);
151
- directorySymlinks.push({
152
- escaped: target.kind === "escaped",
153
- relativePath
154
- });
155
- }
156
- continue;
157
- }
158
- } catch {
159
- if (MANAGED_SYMLINK_PATHS.has(relativePath)) continue;
160
- if (entry.name.endsWith(".md")) files.push({
161
- absolutePath,
162
- contentClass,
163
- inspection: { kind: "unresolved" },
164
- relativePath
165
- });
166
- continue;
167
- }
168
- if (!entry.isFile() && !symbolicLink || !entry.name.endsWith(".md")) continue;
169
- if (symbolicLink && MANAGED_SYMLINK_PATHS.has(relativePath)) continue;
170
- files.push({
171
- absolutePath,
172
- contentClass,
173
- inspection: symbolicLink ? inspectMarkdownSymlink(realTreeRoot, absolutePath, contentClass) : { kind: "regular" },
174
- relativePath
175
- });
176
- }
177
- }
178
- walk(treeRoot);
179
- return {
180
- directories,
181
- directorySymlinks,
182
- files
183
- };
184
- }
185
- //#endregion
186
- //#region src/core/internal/filesystem.ts
187
- function readUtf8File(path) {
188
- return new TextDecoder("utf-8", { fatal: true }).decode(readFileSync(path));
189
- }
190
- //#endregion
191
- //#region src/core/internal/context-document.ts
192
- function readContextDocument(path) {
193
- try {
194
- return parseMarkdownFrontmatter(readUtf8File(path));
195
- } catch (error) {
196
- return {
197
- body: "",
198
- data: null,
199
- error: error instanceof Error ? error.message : String(error),
200
- frontmatter: "invalid"
201
- };
202
- }
203
- }
204
- function readNonEmptyStringField(data, key) {
205
- if (!(key in data)) return {
206
- present: false,
207
- valid: false
208
- };
209
- const value = data[key];
210
- if (typeof value !== "string" || value.trim().length === 0) return {
211
- present: true,
212
- valid: false
213
- };
214
- return {
215
- present: true,
216
- valid: true,
217
- value: value.trim()
218
- };
219
- }
220
- function readNonEmptyStringArrayField(data, key) {
221
- if (!(key in data)) return {
222
- present: false,
223
- valid: false
224
- };
225
- const value = data[key];
226
- if (!Array.isArray(value) || value.length === 0) return {
227
- present: true,
228
- valid: false
229
- };
230
- const items = [];
231
- for (const item of value) {
232
- if (typeof item !== "string" || item.trim().length === 0) return {
233
- present: true,
234
- valid: false
235
- };
236
- items.push(item.trim());
237
- }
238
- return {
239
- present: true,
240
- valid: true,
241
- value: items
242
- };
243
- }
244
- function readNodeDocument(path) {
245
- const document = readContextDocument(path);
246
- if (document.frontmatter !== "valid") return null;
247
- const title = readNonEmptyStringField(document.data, "title");
248
- const description = readNonEmptyStringField(document.data, "description");
249
- if (!title.valid || description.present && !description.valid) return null;
250
- return {
251
- body: document.body,
252
- frontmatter: document.data,
253
- title: title.value,
254
- ...description.valid ? { description: description.value } : {}
255
- };
256
- }
257
- //#endregion
258
- //#region src/core/read.ts
259
- function normalizeTreeTarget(value) {
260
- if (!value || value === ".") return "";
261
- const normalized = posix.normalize(toPosixPath(value).replace(/^\.\//u, ""));
262
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/")) throw new Error(`Read target is outside the Context Tree: ${value}`);
263
- return normalized.replace(/\/$/u, "");
264
- }
265
- function canonicalTarget(root, path) {
266
- const requested = normalizeTreeTarget(path);
267
- const semanticPath = requested === "NODE.md" ? "" : requested.endsWith("/NODE.md") ? dirname(requested) : requested;
268
- if (classifyContextContent(semanticPath) === "repo-infra") throw new Error(`Read target is repository infrastructure: ${requested || "."}`);
269
- const absolutePath = resolve(root, semanticPath);
270
- if (!isPathInside(root, absolutePath)) throw new Error("Read target escapes the Context Tree root.");
271
- const entry = lstatSync(absolutePath);
272
- if (entry.isSymbolicLink() || !entry.isDirectory() && !entry.isFile()) throw new Error(`Read target must be a real file or directory: ${requested || "."}`);
273
- if (realpathSync(absolutePath) !== absolutePath) throw new Error(`Read target must not traverse a symlink: ${requested || "."}`);
274
- const relativePath = toPosixPath(relative(root, absolutePath));
275
- if (entry.isFile() && !absolutePath.endsWith(".md")) throw new Error(`Read target must be a Markdown file or indexed directory: ${requested || "."}`);
276
- return {
277
- absolutePath,
278
- kind: entry.isDirectory() ? "directory" : "file",
279
- relativePath
280
- };
281
- }
282
- function readNode(path, relativePath, kind) {
283
- const documentPath = kind === "directory" ? join(path, "NODE.md") : path;
284
- const entry = lstatSync(documentPath);
285
- if (entry.isSymbolicLink() || !entry.isFile()) throw new Error(`Context Tree document must be a regular file: ${relativePath || "NODE.md"}`);
286
- const document = readNodeDocument(documentPath);
287
- if (document === null) throw new Error(`Context Tree document has invalid or missing metadata: ${relativePath || "."}`);
288
- return {
289
- body: document.body,
290
- contentClass: classifyContextContent(relativePath),
291
- frontmatter: document.frontmatter,
292
- kind,
293
- path: relativePath || "."
294
- };
295
- }
296
- function childSummary(root, parentPath, name) {
297
- const absolutePath = join(parentPath, name);
298
- const relativePath = toPosixPath(relative(root, absolutePath));
299
- const contentClass = classifyContextContent(relativePath);
300
- if (contentClass === "repo-infra") return null;
301
- const entry = lstatSync(absolutePath);
302
- if (entry.isSymbolicLink()) return null;
303
- const kind = entry.isDirectory() ? "directory" : entry.isFile() && name.endsWith(".md") && name !== "NODE.md" ? "file" : null;
304
- if (kind === null) return null;
305
- const document = readNodeDocument(kind === "directory" ? join(absolutePath, "NODE.md") : absolutePath);
306
- if (document === null) throw new Error(`Context Tree child has invalid or missing metadata: ${relativePath}`);
307
- return {
308
- contentClass,
309
- ...document.description === void 0 ? {} : { description: document.description },
310
- kind,
311
- path: relativePath,
312
- title: document.title
313
- };
314
- }
315
- function readTree(treePath, path) {
316
- const root = resolveTreeRoot(treePath);
317
- const target = canonicalTarget(root, path);
318
- const node = readNode(target.absolutePath, target.relativePath, target.kind);
319
- return {
320
- children: target.kind === "file" ? [] : readdirSync(target.absolutePath).map((name) => childSummary(root, target.absolutePath, name)).filter((child) => child !== null).sort((left, right) => left.path.localeCompare(right.path)),
321
- node,
322
- root,
323
- schemaVersion: 1,
324
- target: target.relativePath || "."
325
- };
326
- }
327
- //#endregion
328
- //#region src/core/internal/github-repository.ts
329
- function parseGitHubRepositoryIdentity(repository) {
330
- const repositoryParts = repository.split("/");
331
- const [owner, name] = repositoryParts;
332
- if (repositoryParts.length !== 2 || owner === void 0 || name === void 0 || !/^[A-Za-z\d](?:[A-Za-z\d-]{0,37}[A-Za-z\d])?$/u.test(owner) || !/^[A-Za-z\d._-]{1,100}$/u.test(name) || name === "." || name === ".." || /\.git$/iu.test(name)) throw new Error("Repository must be an explicit GitHub OWNER/REPO identity.");
333
- return name;
334
- }
335
- //#endregion
336
- //#region src/core/internal/context-links.ts
337
- function stripQueryAndFragment(target) {
338
- const indexes = [target.indexOf("?"), target.indexOf("#")].filter((index) => index >= 0);
339
- const end = indexes.length === 0 ? target.length : Math.min(...indexes);
340
- return target.slice(0, end);
341
- }
342
- function decodeTarget(target) {
343
- try {
344
- return decodeURIComponent(target);
345
- } catch {
346
- return target;
347
- }
348
- }
349
- function isWindowsAbsoluteTarget(target) {
350
- return /^[a-z]:[\\/]/iu.test(target) || /^\\/u.test(target);
351
- }
352
- function isTreeLocalTarget(target) {
353
- const trimmed = target.trim();
354
- if (isWindowsAbsoluteTarget(decodeTarget(stripQueryAndFragment(trimmed)))) return true;
355
- return trimmed.length > 0 && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !/^[a-z][a-z\d+.-]*:/iu.test(trimmed);
356
- }
357
- function targetExists(path, softLink) {
358
- try {
359
- const stat = statSync(path);
360
- if (stat.isFile()) return !softLink || path.endsWith(".md");
361
- return stat.isDirectory() && (!softLink || existsSync(resolve(path, "NODE.md")));
362
- } catch {
363
- return false;
364
- }
365
- }
366
- function resolveLocalTreeTarget(options) {
367
- if (!isTreeLocalTarget(options.target)) return null;
368
- const decodedTarget = decodeTarget(stripQueryAndFragment(options.target.trim()));
369
- const withoutSuffix = decodedTarget.replace(/\\/gu, "/");
370
- if (withoutSuffix.length === 0) return null;
371
- if (isWindowsAbsoluteTarget(decodedTarget)) return "escaped-missing";
372
- const sourceDirectory = posix.dirname(options.sourcePath);
373
- const relativePath = posix.normalize(options.softLink || withoutSuffix.startsWith("/") ? withoutSuffix.replace(/^\/+/, "") : posix.join(sourceDirectory, withoutSuffix));
374
- const absoluteRoot = resolve(options.treeRoot);
375
- const absoluteTarget = resolve(absoluteRoot, relativePath);
376
- if (!isPathInside(absoluteRoot, absoluteTarget)) return "escaped-missing";
377
- if (!targetExists(absoluteTarget, options.softLink)) return "missing";
378
- try {
379
- if (!isPathInside(realpathSync(absoluteRoot), realpathSync(absoluteTarget))) return "escaped-existing";
380
- } catch {
381
- return "missing";
382
- }
383
- return "valid";
384
- }
385
- function readMarkdownLinkTargets(markdown) {
386
- const root = fromMarkdown(markdown);
387
- const targets = [];
388
- function visit(node) {
389
- if (!isRecord(node)) return;
390
- if ((node.type === "link" || node.type === "image" || node.type === "definition") && typeof node.url === "string") targets.push(node.url);
391
- if (Array.isArray(node.children)) for (const child of node.children) visit(child);
392
- }
393
- visit(root);
394
- return targets;
395
- }
396
- //#endregion
397
- //#region src/core/internal/validate-nodes.ts
398
- function addFinding(findings, code, path, message, target) {
399
- findings.push({
400
- code,
401
- message,
402
- path,
403
- ...target === void 0 ? {} : { target }
404
- });
405
- }
406
- function validateRequiredNodeMetadata(document, path, findings) {
407
- if (document.frontmatter === "missing") {
408
- addFinding(findings, VALIDATION_CODES.frontmatterMissing, path, "missing frontmatter");
409
- return;
410
- }
411
- if (document.frontmatter === "invalid") {
412
- addFinding(findings, VALIDATION_CODES.frontmatterParse, path, `frontmatter could not be parsed: ${document.error}`);
413
- return;
414
- }
415
- const title = readNonEmptyStringField(document.data, "title");
416
- if (!title.present) addFinding(findings, VALIDATION_CODES.titleMissing, path, "missing 'title' field in frontmatter");
417
- else if (!title.valid) addFinding(findings, VALIDATION_CODES.titleInvalid, path, "'title' must be a non-empty string");
418
- const description = readNonEmptyStringField(document.data, "description");
419
- if (description.present && !description.valid) addFinding(findings, VALIDATION_CODES.descriptionInvalid, path, "'description' must be a non-empty string when present");
420
- }
421
- function validateRootOnlyFields(document, path, findings) {
422
- if (path === "NODE.md" || document.frontmatter !== "valid") return;
423
- const fields = ["schemaVersion", "relatedRepositories"].filter((field) => field in document.data);
424
- if (fields.length > 0) addFinding(findings, VALIDATION_CODES.rootOnlyFields, path, `root-only frontmatter field${fields.length === 1 ? "" : "s"} must appear only in root NODE.md: ${fields.join(", ")}`);
425
- }
426
- function readSoftLinks(document, path, findings) {
427
- if (document.frontmatter !== "valid") return [];
428
- const softLinks = readNonEmptyStringArrayField(document.data, "soft_links");
429
- if (!softLinks.present) return [];
430
- if (!softLinks.valid) {
431
- addFinding(findings, VALIDATION_CODES.softLinksInvalid, path, "'soft_links' must be a non-empty string array when present");
432
- return [];
433
- }
434
- return softLinks.value;
435
- }
436
- function validateSoftLinks(options) {
437
- for (const target of readSoftLinks(options.document, options.path, options.findings)) {
438
- const resolved = resolveLocalTreeTarget({
439
- sourcePath: options.path,
440
- target,
441
- treeRoot: options.treeRoot,
442
- softLink: true
443
- });
444
- if (resolved === null || resolved === "missing" || resolved === "escaped-missing") addFinding(options.findings, VALIDATION_CODES.softLinkBroken, options.path, "broken soft_links target", target);
445
- if (resolved === null) continue;
446
- if (resolved === "escaped-existing" || resolved === "escaped-missing") addFinding(options.findings, VALIDATION_CODES.softLinkPathEscape, options.path, "soft_links target resolves outside the Context Tree root", target);
447
- }
448
- }
449
- function validateMarkdownLinks(document, path, treeRoot, findings) {
450
- for (const target of readMarkdownLinkTargets(document.body)) {
451
- const resolved = resolveLocalTreeTarget({
452
- sourcePath: path,
453
- target,
454
- treeRoot,
455
- softLink: false
456
- });
457
- if (resolved === null) continue;
458
- if (resolved === "escaped-existing" || resolved === "escaped-missing") addFinding(findings, VALIDATION_CODES.markdownPathEscape, path, "Markdown link resolves outside the Context Tree root", target);
459
- }
460
- }
461
- function collectNodeValidationFindings(treeRoot) {
462
- const findings = [];
463
- const scannedByContentClass = emptyContentClassCounts();
464
- const content = collectContextMarkdownContent(treeRoot);
465
- for (const directory of content.directories) {
466
- const nodePath = `${directory}/NODE.md`;
467
- let hasRegularNode = false;
468
- try {
469
- const entry = lstatSync(join(treeRoot, nodePath));
470
- hasRegularNode = entry.isFile() && !entry.isSymbolicLink();
471
- } catch {}
472
- if (!hasRegularNode) addFinding(findings, VALIDATION_CODES.directoryNodeMissing, directory, "Context Tree directory is missing NODE.md");
473
- }
474
- for (const directory of content.directorySymlinks) addFinding(findings, directory.escaped ? VALIDATION_CODES.directorySymlinkPathEscape : VALIDATION_CODES.directorySymlinkUnsupported, directory.relativePath, directory.escaped ? "directory symlink resolves outside the Context Tree root" : "Context Tree domain directories must not be symlinks");
475
- for (const file of content.files) {
476
- scannedByContentClass[file.contentClass] += 1;
477
- if (file.inspection.kind === "unresolved") {
478
- addFinding(findings, VALIDATION_CODES.markdownFileSymlinkBroken, file.relativePath, "Markdown file symlink target cannot be resolved");
479
- continue;
480
- }
481
- if (file.inspection.kind === "escaped") {
482
- addFinding(findings, VALIDATION_CODES.markdownFilePathEscape, file.relativePath, "Markdown file resolves outside the Context Tree root");
483
- continue;
484
- }
485
- if (file.inspection.kind === "unsupported") {
486
- addFinding(findings, VALIDATION_CODES.markdownFileSymlinkUnsupported, file.relativePath, "Markdown file symlink must resolve to a regular file");
487
- continue;
488
- }
489
- if (file.inspection.kind === "content-class-mismatch") {
490
- const canonicalContentClass = classifyContextContent(file.inspection.canonicalRelativePath);
491
- addFinding(findings, VALIDATION_CODES.markdownFileContentClassMismatch, file.relativePath, `Markdown file symlink crosses content-class boundary from ${file.contentClass} to ${canonicalContentClass}`, file.inspection.canonicalRelativePath);
492
- continue;
493
- }
494
- if (file.contentClass === "repo-infra") continue;
495
- const document = readContextDocument(file.absolutePath);
496
- validateRootOnlyFields(document, file.relativePath, findings);
497
- if (file.relativePath !== "NODE.md" || document.frontmatter === "valid") validateRequiredNodeMetadata(document, file.relativePath, findings);
498
- validateSoftLinks({
499
- document,
500
- findings,
501
- path: file.relativePath,
502
- treeRoot
503
- });
504
- validateMarkdownLinks(document, file.relativePath, treeRoot, findings);
505
- }
506
- return {
507
- findings,
508
- scannedByContentClass
509
- };
510
- }
511
- //#endregion
512
- //#region src/core/verify.ts
513
- function rootNodeFindings(root) {
514
- const path = join(root, "NODE.md");
515
- if (!existsSync(path)) return [{
516
- code: VALIDATION_CODES.rootMissing,
517
- message: "root NODE.md is missing",
518
- path: "NODE.md"
519
- }];
520
- try {
521
- const entry = lstatSync(path);
522
- if (entry.isSymbolicLink() || !entry.isFile()) throw new Error("Root NODE.md must be a regular file and must not be a symlink.");
523
- parseContextTreeRootNode(readUtf8File(path));
524
- return [];
525
- } catch (error) {
526
- return [{
527
- code: VALIDATION_CODES.rootNodeInvalid,
528
- message: error instanceof Error ? error.message : String(error),
529
- path: "NODE.md"
530
- }];
531
- }
532
- }
533
- function deduplicate(findings) {
534
- const seen = /* @__PURE__ */ new Set();
535
- return findings.filter((finding) => {
536
- const key = `${finding.code}\0${finding.path}\0${finding.target ?? ""}`;
537
- if (seen.has(key)) return false;
538
- seen.add(key);
539
- return true;
540
- });
541
- }
542
- function verifyTree(treePath) {
543
- const root = resolveTreeRoot(treePath);
544
- const nodeResult = collectNodeValidationFindings(root);
545
- const findings = deduplicate([...rootNodeFindings(root), ...nodeResult.findings]);
546
- return {
547
- findings,
548
- ok: findings.length === 0,
549
- root,
550
- scannedByContentClass: nodeResult.scannedByContentClass,
551
- schemaVersion: 1
552
- };
553
- }
554
- //#endregion
555
- //#region src/core/scaffold.ts
556
- function template(name, values) {
557
- let result = readFileSync(resolvePackagedResource("templates", name), "utf8");
558
- for (const [key, value] of Object.entries(values)) result = result.replaceAll(`{{${key}}}`, value);
559
- return result;
560
- }
561
- function initializeGitRepository(root) {
562
- const initialized = spawnSync("git", [
563
- "init",
564
- "--quiet",
565
- root
566
- ], { stdio: "ignore" });
567
- if (initialized.error !== void 0 || initialized.status !== 0) throw new Error("Failed to initialize Git repository.");
568
- const branch = spawnSync("git", [
569
- "-C",
570
- root,
571
- "symbolic-ref",
572
- "--short",
573
- "HEAD"
574
- ], {
575
- encoding: "utf8",
576
- stdio: [
577
- "ignore",
578
- "pipe",
579
- "ignore"
580
- ]
581
- });
582
- const name = branch.stdout.replace(/\r?\n$/u, "");
583
- if (branch.error !== void 0 || branch.status !== 0 || name.length === 0) throw new Error("Failed to resolve the initial Git branch during repository initialization.");
584
- return name;
585
- }
586
- function scaffoldTree(options) {
587
- const title = parseGitHubRepositoryIdentity(options.repository);
588
- const root = resolve(options.path);
589
- const destination = lstatSync(root, { throwIfNoEntry: false });
590
- if (destination !== void 0) {
591
- if (destination.isSymbolicLink() || !destination.isDirectory()) throw new Error(`Refusing to scaffold into a symlink or non-directory destination: ${root}`);
592
- if (readdirSync(root).length > 0) throw new Error(`Refusing to scaffold into a non-empty directory: ${root}`);
593
- }
594
- const initialBranch = initializeGitRepository(root);
595
- const values = {
596
- branchJson: JSON.stringify(initialBranch),
597
- packageVersion: readPackageVersion(),
598
- title,
599
- titleJson: JSON.stringify(title)
600
- };
601
- const files = [["NODE.md", "root-node.md"], [".github/workflows/validate-context-tree.yml", "validate-context-tree.yml"]];
602
- for (const [relativePath, source] of files) {
603
- const path = join(root, relativePath);
604
- mkdirSync(dirname(path), { recursive: true });
605
- writeFileSync(path, template(source, values), {
606
- encoding: "utf8",
607
- flag: "wx",
608
- mode: 420
609
- });
610
- }
611
- return {
612
- files: files.map(([path]) => path),
613
- root,
614
- schemaVersion: 1,
615
- verification: verifyTree(root)
616
- };
617
- }
618
- //#endregion
619
- export { readContextTreePolicy as a, readTree as i, verifyTree as n, readPackageVersion as o, parseGitHubRepositoryIdentity as r, scaffoldTree as t };