first-tree 0.0.1 → 0.0.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.
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,104 @@
|
|
|
1
1
|
import { n as Repo } from "./repo-BByc3VvM.js";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { join, relative } from "node:path";
|
|
4
|
+
//#region src/validators/members.ts
|
|
5
|
+
const FRONTMATTER_RE$1 = /^---\s*\n(.*?)\n---/s;
|
|
6
|
+
const VALID_TYPES = new Set([
|
|
7
|
+
"human",
|
|
8
|
+
"personal_assistant",
|
|
9
|
+
"autonomous_agent"
|
|
10
|
+
]);
|
|
11
|
+
function rel$1(path, root) {
|
|
12
|
+
return relative(root, path);
|
|
13
|
+
}
|
|
14
|
+
function parseFrontmatter$1(path) {
|
|
15
|
+
try {
|
|
16
|
+
const m = readFileSync(path, "utf-8").match(FRONTMATTER_RE$1);
|
|
17
|
+
return m ? m[1] : null;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function extractScalar(fm, key) {
|
|
23
|
+
const re = new RegExp(`^${key}:\\s*"?([^"\\n]+?)"?\\s*$`, "m");
|
|
24
|
+
const m = fm.match(re);
|
|
25
|
+
return m ? m[1].trim() : null;
|
|
26
|
+
}
|
|
27
|
+
function extractList(fm, key) {
|
|
28
|
+
const inlineRe = new RegExp(`^${key}:\\s*\\[([^\\]]*)\\]`, "m");
|
|
29
|
+
let m = fm.match(inlineRe);
|
|
30
|
+
if (m) {
|
|
31
|
+
const raw = m[1].trim();
|
|
32
|
+
if (!raw) return [];
|
|
33
|
+
return raw.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
const blockRe = new RegExp(`^${key}:\\s*\\n((?:\\s+-\\s+.+\\n?)+)`, "m");
|
|
36
|
+
m = fm.match(blockRe);
|
|
37
|
+
if (m) return m[1].trim().split("\n").filter((line) => line.trim()).map((line) => line.trim().replace(/^-\s*/, "").trim().replace(/^['"]|['"]$/g, ""));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function validateMember(nodePath, treeRoot) {
|
|
41
|
+
const errors = [];
|
|
42
|
+
const loc = rel$1(nodePath, treeRoot);
|
|
43
|
+
const fm = parseFrontmatter$1(nodePath);
|
|
44
|
+
if (fm === null) return [`${loc}: no frontmatter found`];
|
|
45
|
+
if (!extractScalar(fm, "title")) errors.push(`${loc}: missing or empty 'title' field`);
|
|
46
|
+
if (extractList(fm, "owners") === null) errors.push(`${loc}: missing 'owners' field`);
|
|
47
|
+
const memberType = extractScalar(fm, "type");
|
|
48
|
+
if (!memberType) errors.push(`${loc}: missing 'type' field`);
|
|
49
|
+
else if (!VALID_TYPES.has(memberType)) errors.push(`${loc}: invalid type '${memberType}' — must be one of: ${[...VALID_TYPES].sort().join(", ")}`);
|
|
50
|
+
if (!extractScalar(fm, "role")) errors.push(`${loc}: missing or empty 'role' field`);
|
|
51
|
+
const domains = extractList(fm, "domains");
|
|
52
|
+
if (domains === null) errors.push(`${loc}: missing 'domains' field`);
|
|
53
|
+
else if (domains.length === 0) errors.push(`${loc}: 'domains' must contain at least one entry`);
|
|
54
|
+
return errors;
|
|
55
|
+
}
|
|
56
|
+
function runValidateMembers(treeRoot) {
|
|
57
|
+
const membersDir = join(treeRoot, "members");
|
|
58
|
+
if (!existsSync(membersDir) || !statSync(membersDir).isDirectory()) {
|
|
59
|
+
console.log(`Members directory not found: ${membersDir}`);
|
|
60
|
+
return {
|
|
61
|
+
exitCode: 1,
|
|
62
|
+
errors: []
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const allErrors = [];
|
|
66
|
+
let memberCount = 0;
|
|
67
|
+
for (const child of readdirSync(membersDir).sort()) {
|
|
68
|
+
const childPath = join(membersDir, child);
|
|
69
|
+
try {
|
|
70
|
+
const stat = statSync(childPath);
|
|
71
|
+
if (stat.isFile() && child.endsWith(".md") && child !== "NODE.md") {
|
|
72
|
+
allErrors.push(`${rel$1(childPath, treeRoot)}: member must be a directory with NODE.md, not a standalone file — use members/${child.replace(/\.md$/, "")}/NODE.md instead`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (!stat.isDirectory()) continue;
|
|
76
|
+
} catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const nodePath = join(childPath, "NODE.md");
|
|
80
|
+
if (!existsSync(nodePath)) {
|
|
81
|
+
allErrors.push(`${rel$1(childPath, treeRoot)}/: directory exists but missing NODE.md`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
memberCount++;
|
|
85
|
+
allErrors.push(...validateMember(nodePath, treeRoot));
|
|
86
|
+
}
|
|
87
|
+
if (allErrors.length > 0) {
|
|
88
|
+
console.log(`Found ${allErrors.length} member validation error(s):\n`);
|
|
89
|
+
for (const err of allErrors) console.log(` \u2717 ${err}`);
|
|
90
|
+
return {
|
|
91
|
+
exitCode: 1,
|
|
92
|
+
errors: allErrors
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
console.log(`All ${memberCount} member(s) passed validation.`);
|
|
96
|
+
return {
|
|
97
|
+
exitCode: 0,
|
|
98
|
+
errors: allErrors
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
4
102
|
//#region src/validators/nodes.ts
|
|
5
103
|
const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
|
|
6
104
|
const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
|
|
@@ -382,7 +480,7 @@ function runVerify(repo, nodeValidator) {
|
|
|
382
480
|
allPassed = check("AGENT.md exists with framework markers", r.hasAgentMdMarkers()) && allPassed;
|
|
383
481
|
const { exitCode } = validate(r.root);
|
|
384
482
|
allPassed = check("Node validation passes", exitCode === 0) && allPassed;
|
|
385
|
-
allPassed = check("
|
|
483
|
+
allPassed = check("Member validation passes", runValidateMembers(r.root).exitCode === 0) && allPassed;
|
|
386
484
|
console.log();
|
|
387
485
|
if (allPassed) console.log("All checks passed.");
|
|
388
486
|
else console.log("Some checks failed. See above for details.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "first-tree",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "CLI tools for Context Tree — the living source of truth for your organization.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsdown src/cli.ts --format esm --out-dir dist",
|
|
14
|
-
"
|
|
14
|
+
"prepack": "pnpm build",
|
|
15
15
|
"test": "vitest run",
|
|
16
16
|
"typecheck": "tsc --noEmit"
|
|
17
17
|
},
|