first-tree 0.0.2 → 0.0.3
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 +73 -39
- package/dist/cli.js +27 -13
- package/dist/help-xEI-s9iN.js +25 -0
- package/dist/init-DtOjj0wc.js +253 -0
- package/dist/installer-rcZpGLnM.js +47 -0
- package/dist/onboarding-6Fr5Gkrk.js +2 -0
- package/dist/onboarding-B9zPGvvG.js +10 -0
- package/dist/repo-BTJG8BU1.js +187 -0
- package/dist/upgrade-COGgI7Rj.js +96 -0
- package/dist/{verify-CSRIkuoM.js → verify-CxN6JiV9.js} +53 -24
- package/package.json +33 -10
- package/skills/first-tree/SKILL.md +109 -0
- package/skills/first-tree/agents/openai.yaml +4 -0
- package/skills/first-tree/assets/framework/VERSION +1 -0
- package/skills/first-tree/assets/framework/examples/claude-code/README.md +14 -0
- package/skills/first-tree/assets/framework/examples/claude-code/settings.json +14 -0
- package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +224 -0
- package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +15 -0
- package/skills/first-tree/assets/framework/helpers/run-review.ts +179 -0
- package/skills/first-tree/assets/framework/manifest.json +11 -0
- package/skills/first-tree/assets/framework/prompts/pr-review.md +38 -0
- package/skills/first-tree/assets/framework/templates/agent.md.template +48 -0
- package/skills/first-tree/assets/framework/templates/member-node.md.template +18 -0
- package/skills/first-tree/assets/framework/templates/members-domain.md.template +45 -0
- package/skills/first-tree/assets/framework/templates/root-node.md.template +38 -0
- package/skills/first-tree/assets/framework/workflows/codeowners.yml +31 -0
- package/skills/first-tree/assets/framework/workflows/pr-review.yml +146 -0
- package/skills/first-tree/assets/framework/workflows/validate.yml +19 -0
- package/skills/first-tree/engine/commands/help.ts +32 -0
- package/skills/first-tree/engine/commands/init.ts +1 -0
- package/skills/first-tree/engine/commands/upgrade.ts +1 -0
- package/skills/first-tree/engine/commands/verify.ts +1 -0
- package/skills/first-tree/engine/init.ts +145 -0
- package/skills/first-tree/engine/onboarding.ts +10 -0
- package/skills/first-tree/engine/repo.ts +184 -0
- package/skills/first-tree/engine/rules/agent-instructions.ts +37 -0
- package/skills/first-tree/engine/rules/agent-integration.ts +19 -0
- package/skills/first-tree/engine/rules/ci-validation.ts +72 -0
- package/skills/first-tree/engine/rules/framework.ts +13 -0
- package/skills/first-tree/engine/rules/index.ts +41 -0
- package/skills/first-tree/engine/rules/members.ts +21 -0
- package/skills/first-tree/engine/rules/populate-tree.ts +36 -0
- package/skills/first-tree/engine/rules/root-node.ts +41 -0
- package/skills/first-tree/engine/runtime/adapters.ts +22 -0
- package/skills/first-tree/engine/runtime/asset-loader.ts +134 -0
- package/skills/first-tree/engine/runtime/installer.ts +82 -0
- package/skills/first-tree/engine/runtime/upgrader.ts +23 -0
- package/skills/first-tree/engine/upgrade.ts +176 -0
- package/skills/first-tree/engine/validators/members.ts +215 -0
- package/skills/first-tree/engine/validators/nodes.ts +514 -0
- package/skills/first-tree/engine/verify.ts +97 -0
- package/skills/first-tree/references/about.md +36 -0
- package/skills/first-tree/references/maintainer-architecture.md +59 -0
- package/skills/first-tree/references/maintainer-build-and-distribution.md +56 -0
- package/skills/first-tree/references/maintainer-testing.md +58 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +38 -0
- package/skills/first-tree/references/onboarding.md +162 -0
- package/skills/first-tree/references/ownership-and-naming.md +94 -0
- package/skills/first-tree/references/principles.md +113 -0
- package/skills/first-tree/references/source-map.md +94 -0
- package/skills/first-tree/references/upgrade-contract.md +85 -0
- package/skills/first-tree/scripts/check-skill-sync.sh +133 -0
- package/skills/first-tree/scripts/quick_validate.py +95 -0
- package/skills/first-tree/scripts/run-local-cli.sh +35 -0
- package/skills/first-tree/tests/asset-loader.test.ts +75 -0
- package/skills/first-tree/tests/generate-codeowners.test.ts +94 -0
- package/skills/first-tree/tests/helpers.ts +149 -0
- package/skills/first-tree/tests/init.test.ts +153 -0
- package/skills/first-tree/tests/repo.test.ts +362 -0
- package/skills/first-tree/tests/rules.test.ts +394 -0
- package/skills/first-tree/tests/run-review.test.ts +155 -0
- package/skills/first-tree/tests/skill-artifacts.test.ts +307 -0
- package/skills/first-tree/tests/thin-cli.test.ts +59 -0
- package/skills/first-tree/tests/upgrade.test.ts +89 -0
- package/skills/first-tree/tests/validate-members.test.ts +224 -0
- package/skills/first-tree/tests/validate-nodes.test.ts +198 -0
- package/skills/first-tree/tests/verify.test.ts +142 -0
- package/dist/init-CE_944sb.js +0 -283
- package/dist/repo-BByc3VvM.js +0 -111
- package/dist/upgrade-Chr7z0CY.js +0 -82
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
const SKILL_ROOT = join("skills", "first-tree");
|
|
4
|
+
join(SKILL_ROOT, "agents");
|
|
5
|
+
join(SKILL_ROOT, "references");
|
|
6
|
+
const FRAMEWORK_ASSET_ROOT = join(SKILL_ROOT, "assets", "framework");
|
|
7
|
+
join(FRAMEWORK_ASSET_ROOT, "manifest.json");
|
|
8
|
+
const FRAMEWORK_VERSION = join(FRAMEWORK_ASSET_ROOT, "VERSION");
|
|
9
|
+
const FRAMEWORK_TEMPLATES_DIR = join(FRAMEWORK_ASSET_ROOT, "templates");
|
|
10
|
+
const FRAMEWORK_WORKFLOWS_DIR = join(FRAMEWORK_ASSET_ROOT, "workflows");
|
|
11
|
+
join(FRAMEWORK_ASSET_ROOT, "prompts");
|
|
12
|
+
const FRAMEWORK_EXAMPLES_DIR = join(FRAMEWORK_ASSET_ROOT, "examples");
|
|
13
|
+
join(FRAMEWORK_ASSET_ROOT, "helpers");
|
|
14
|
+
const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
|
|
15
|
+
const LEGACY_SKILL_ROOT = join("skills", "first-tree-cli-framework");
|
|
16
|
+
const LEGACY_SKILL_ASSET_ROOT = join(LEGACY_SKILL_ROOT, "assets", "framework");
|
|
17
|
+
const LEGACY_SKILL_VERSION = join(LEGACY_SKILL_ASSET_ROOT, "VERSION");
|
|
18
|
+
join(LEGACY_SKILL_ASSET_ROOT, "templates");
|
|
19
|
+
join(LEGACY_SKILL_ASSET_ROOT, "workflows");
|
|
20
|
+
join(LEGACY_SKILL_ASSET_ROOT, "prompts");
|
|
21
|
+
join(LEGACY_SKILL_ASSET_ROOT, "examples");
|
|
22
|
+
const LEGACY_SKILL_PROGRESS = join(LEGACY_SKILL_ROOT, "progress.md");
|
|
23
|
+
const LEGACY_FRAMEWORK_ROOT = ".context-tree";
|
|
24
|
+
const LEGACY_VERSION = join(LEGACY_FRAMEWORK_ROOT, "VERSION");
|
|
25
|
+
const LEGACY_PROGRESS = join(LEGACY_FRAMEWORK_ROOT, "progress.md");
|
|
26
|
+
join(LEGACY_FRAMEWORK_ROOT, "templates");
|
|
27
|
+
join(LEGACY_FRAMEWORK_ROOT, "workflows");
|
|
28
|
+
join(LEGACY_FRAMEWORK_ROOT, "prompts");
|
|
29
|
+
join(LEGACY_FRAMEWORK_ROOT, "examples");
|
|
30
|
+
function pathExists(root, relPath) {
|
|
31
|
+
const fullPath = join(root, relPath);
|
|
32
|
+
try {
|
|
33
|
+
return existsSync(fullPath);
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function frameworkVersionCandidates() {
|
|
39
|
+
return [
|
|
40
|
+
FRAMEWORK_VERSION,
|
|
41
|
+
LEGACY_SKILL_VERSION,
|
|
42
|
+
LEGACY_VERSION
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
function progressFileCandidates() {
|
|
46
|
+
return [
|
|
47
|
+
INSTALLED_PROGRESS,
|
|
48
|
+
LEGACY_SKILL_PROGRESS,
|
|
49
|
+
LEGACY_PROGRESS
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
function resolveFirstExistingPath(root, candidates) {
|
|
53
|
+
for (const candidate of candidates) if (pathExists(root, candidate)) return candidate;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function detectFrameworkLayout(root) {
|
|
57
|
+
if (pathExists(root, FRAMEWORK_VERSION)) return "skill";
|
|
58
|
+
if (pathExists(root, LEGACY_SKILL_VERSION)) return "legacy-skill";
|
|
59
|
+
if (pathExists(root, LEGACY_VERSION)) return "legacy";
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region skills/first-tree/engine/repo.ts
|
|
64
|
+
const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
|
|
65
|
+
const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
|
|
66
|
+
const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
|
|
67
|
+
const FRAMEWORK_END_MARKER = "<!-- END CONTEXT-TREE FRAMEWORK -->";
|
|
68
|
+
var Repo = class {
|
|
69
|
+
root;
|
|
70
|
+
constructor(root) {
|
|
71
|
+
this.root = resolve(root ?? process.cwd());
|
|
72
|
+
}
|
|
73
|
+
pathExists(relPath) {
|
|
74
|
+
return existsSync(join(this.root, relPath));
|
|
75
|
+
}
|
|
76
|
+
fileContains(relPath, text) {
|
|
77
|
+
const fullPath = join(this.root, relPath);
|
|
78
|
+
try {
|
|
79
|
+
if (!statSync(fullPath).isFile()) return false;
|
|
80
|
+
return readFileSync(fullPath, "utf-8").includes(text);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
readFile(relPath) {
|
|
86
|
+
try {
|
|
87
|
+
return readFileSync(join(this.root, relPath), "utf-8");
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
frontmatter(relPath) {
|
|
93
|
+
const text = this.readFile(relPath);
|
|
94
|
+
if (text === null) return null;
|
|
95
|
+
const m = text.match(FRONTMATTER_RE);
|
|
96
|
+
if (!m) return null;
|
|
97
|
+
const fm = m[1];
|
|
98
|
+
const result = {};
|
|
99
|
+
const titleM = fm.match(TITLE_RE);
|
|
100
|
+
if (titleM) result.title = titleM[1].trim();
|
|
101
|
+
const ownersM = fm.match(OWNERS_RE);
|
|
102
|
+
if (ownersM) {
|
|
103
|
+
const raw = ownersM[1].trim();
|
|
104
|
+
result.owners = raw ? raw.split(",").map((o) => o.trim()).filter(Boolean) : [];
|
|
105
|
+
}
|
|
106
|
+
return result.title !== void 0 || result.owners !== void 0 ? result : null;
|
|
107
|
+
}
|
|
108
|
+
anyAgentConfig() {
|
|
109
|
+
return [".claude/settings.json", ".codex/config.json"].some((c) => this.pathExists(c));
|
|
110
|
+
}
|
|
111
|
+
isGitRepo() {
|
|
112
|
+
try {
|
|
113
|
+
return statSync(join(this.root, ".git")).isDirectory();
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
hasFramework() {
|
|
119
|
+
return this.frameworkLayout() !== null;
|
|
120
|
+
}
|
|
121
|
+
frameworkLayout() {
|
|
122
|
+
return detectFrameworkLayout(this.root);
|
|
123
|
+
}
|
|
124
|
+
readVersion() {
|
|
125
|
+
const versionPath = resolveFirstExistingPath(this.root, frameworkVersionCandidates());
|
|
126
|
+
if (versionPath === null) return null;
|
|
127
|
+
const text = this.readFile(versionPath);
|
|
128
|
+
return text ? text.trim() : null;
|
|
129
|
+
}
|
|
130
|
+
progressPath() {
|
|
131
|
+
return resolveFirstExistingPath(this.root, progressFileCandidates());
|
|
132
|
+
}
|
|
133
|
+
preferredProgressPath() {
|
|
134
|
+
const layout = this.frameworkLayout();
|
|
135
|
+
if (layout === "legacy") return LEGACY_PROGRESS;
|
|
136
|
+
if (layout === "legacy-skill") return LEGACY_SKILL_PROGRESS;
|
|
137
|
+
return INSTALLED_PROGRESS;
|
|
138
|
+
}
|
|
139
|
+
frameworkVersionPath() {
|
|
140
|
+
const layout = this.frameworkLayout();
|
|
141
|
+
if (layout === "legacy") return LEGACY_VERSION;
|
|
142
|
+
if (layout === "legacy-skill") return LEGACY_SKILL_VERSION;
|
|
143
|
+
return FRAMEWORK_VERSION;
|
|
144
|
+
}
|
|
145
|
+
hasAgentMdMarkers() {
|
|
146
|
+
const text = this.readFile("AGENT.md");
|
|
147
|
+
if (text === null) return false;
|
|
148
|
+
return text.includes("<!-- BEGIN CONTEXT-TREE FRAMEWORK") && text.includes("<!-- END CONTEXT-TREE FRAMEWORK -->");
|
|
149
|
+
}
|
|
150
|
+
hasMembers() {
|
|
151
|
+
const membersDir = join(this.root, "members");
|
|
152
|
+
try {
|
|
153
|
+
if (!statSync(membersDir).isDirectory()) return false;
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
return existsSync(join(membersDir, "NODE.md"));
|
|
158
|
+
}
|
|
159
|
+
memberCount() {
|
|
160
|
+
const membersDir = join(this.root, "members");
|
|
161
|
+
try {
|
|
162
|
+
if (!statSync(membersDir).isDirectory()) return 0;
|
|
163
|
+
} catch {
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
let count = 0;
|
|
167
|
+
const walk = (dir) => {
|
|
168
|
+
for (const entry of readdirSync(dir)) {
|
|
169
|
+
const childPath = join(dir, entry);
|
|
170
|
+
try {
|
|
171
|
+
if (!statSync(childPath).isDirectory()) continue;
|
|
172
|
+
} catch {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (existsSync(join(childPath, "NODE.md"))) count++;
|
|
176
|
+
walk(childPath);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
walk(membersDir);
|
|
180
|
+
return count;
|
|
181
|
+
}
|
|
182
|
+
hasPlaceholderNode() {
|
|
183
|
+
return this.fileContains("NODE.md", "<!-- PLACEHOLDER");
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
//#endregion
|
|
187
|
+
export { FRAMEWORK_TEMPLATES_DIR as a, INSTALLED_PROGRESS as c, SKILL_ROOT as d, FRAMEWORK_EXAMPLES_DIR as i, LEGACY_FRAMEWORK_ROOT as l, Repo as n, FRAMEWORK_VERSION as o, FRAMEWORK_ASSET_ROOT as r, FRAMEWORK_WORKFLOWS_DIR as s, FRAMEWORK_END_MARKER as t, LEGACY_SKILL_ROOT as u };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { a as FRAMEWORK_TEMPLATES_DIR, c as INSTALLED_PROGRESS, d as SKILL_ROOT, l as LEGACY_FRAMEWORK_ROOT, n as Repo, o as FRAMEWORK_VERSION, s as FRAMEWORK_WORKFLOWS_DIR, u as LEGACY_SKILL_ROOT } from "./repo-BTJG8BU1.js";
|
|
2
|
+
import { i as resolveCanonicalSkillRoot, r as resolveBundledPackageRoot, t as copyCanonicalSkill } from "./installer-rcZpGLnM.js";
|
|
3
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
//#region skills/first-tree/engine/runtime/upgrader.ts
|
|
6
|
+
function compareFrameworkVersions(left, right) {
|
|
7
|
+
const result = left.localeCompare(right, void 0, {
|
|
8
|
+
numeric: true,
|
|
9
|
+
sensitivity: "base"
|
|
10
|
+
});
|
|
11
|
+
if (result < 0) return -1;
|
|
12
|
+
if (result > 0) return 1;
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
function readSourceVersion(sourceRoot) {
|
|
16
|
+
const versionPath = join(resolveCanonicalSkillRoot(sourceRoot), "assets", "framework", "VERSION");
|
|
17
|
+
try {
|
|
18
|
+
return readFileSync(versionPath, "utf-8").trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region skills/first-tree/engine/upgrade.ts
|
|
25
|
+
function writeProgress(repo, content) {
|
|
26
|
+
const progressPath = join(repo.root, repo.preferredProgressPath());
|
|
27
|
+
mkdirSync(dirname(progressPath), { recursive: true });
|
|
28
|
+
writeFileSync(progressPath, content);
|
|
29
|
+
}
|
|
30
|
+
function formatUpgradeTaskList(repo, localVersion, packagedVersion, layout) {
|
|
31
|
+
const lines = [
|
|
32
|
+
`# Context Tree Upgrade — v${localVersion} -> v${packagedVersion}\n`,
|
|
33
|
+
"## Installed Skill",
|
|
34
|
+
`- [ ] Review local customizations under \`${SKILL_ROOT}/\` and reapply them if needed`,
|
|
35
|
+
`- [ ] Re-copy any workflow updates you want from \`${FRAMEWORK_WORKFLOWS_DIR}/\` into \`.github/workflows/\``,
|
|
36
|
+
`- [ ] Re-check any local agent setup that references \`${SKILL_ROOT}/assets/framework/examples/\` or \`${SKILL_ROOT}/assets/framework/helpers/\``,
|
|
37
|
+
""
|
|
38
|
+
];
|
|
39
|
+
if (layout === "legacy") lines.push("## Migration", "- [ ] Remove any stale `.context-tree/` references from repo-specific docs, scripts, or workflow files", "");
|
|
40
|
+
if (layout === "legacy-skill") lines.push("## Migration", `- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`, "");
|
|
41
|
+
if (repo.hasAgentMdMarkers()) lines.push("## Agent Instructions", `- [ ] Compare the framework section in \`AGENT.md\` with \`${FRAMEWORK_TEMPLATES_DIR}/agent.md.template\` and update the content between the markers if needed`, "");
|
|
42
|
+
lines.push("## Verification", `- [ ] \`${FRAMEWORK_VERSION}\` reads \`${packagedVersion}\``, "- [ ] `context-tree verify` passes", "", "---", "", `**Important:** As you complete each task, check it off in \`${INSTALLED_PROGRESS}\` by changing \`- [ ]\` to \`- [x]\`. Run \`context-tree verify\` when done — it will fail if any items remain unchecked.`, "");
|
|
43
|
+
return lines.join("\n");
|
|
44
|
+
}
|
|
45
|
+
function runUpgrade(repo, options) {
|
|
46
|
+
const workingRepo = repo ?? new Repo();
|
|
47
|
+
if (!workingRepo.hasFramework()) {
|
|
48
|
+
console.error("Error: no installed framework skill found. Run `context-tree init` first.");
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
const layout = workingRepo.frameworkLayout();
|
|
52
|
+
if (layout === null) {
|
|
53
|
+
console.error("Error: no installed framework skill found. Run `context-tree init` first.");
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
const localVersion = workingRepo.readVersion() ?? "unknown";
|
|
57
|
+
console.log(`Local framework version: ${localVersion}\n`);
|
|
58
|
+
console.log("Checking the framework skill bundled with this first-tree package...");
|
|
59
|
+
let sourceRoot;
|
|
60
|
+
try {
|
|
61
|
+
sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
64
|
+
console.error(`Error: ${message}`);
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
const packagedVersion = readSourceVersion(sourceRoot);
|
|
68
|
+
if (packagedVersion === null) {
|
|
69
|
+
console.log("Could not read the bundled framework version. Reinstall or update `first-tree` and try again.");
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
if (localVersion !== "unknown" && compareFrameworkVersions(localVersion, packagedVersion) > 0) {
|
|
73
|
+
console.log("The installed framework is newer than the skill bundled with this `first-tree` package. Install a newer package version before running `context-tree upgrade`.");
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
if (layout === "skill" && packagedVersion === localVersion) {
|
|
77
|
+
console.log(`Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
copyCanonicalSkill(sourceRoot, workingRepo.root);
|
|
81
|
+
if (layout === "legacy") {
|
|
82
|
+
rmSync(join(workingRepo.root, LEGACY_FRAMEWORK_ROOT), {
|
|
83
|
+
recursive: true,
|
|
84
|
+
force: true
|
|
85
|
+
});
|
|
86
|
+
console.log("Migrated legacy .context-tree/ layout to skills/first-tree/.");
|
|
87
|
+
} else if (layout === "legacy-skill") console.log("Migrated skills/first-tree-cli-framework/ to skills/first-tree/.");
|
|
88
|
+
else console.log("Refreshed skills/first-tree/ from the bundled first-tree package.");
|
|
89
|
+
const output = formatUpgradeTaskList(workingRepo, localVersion, packagedVersion, layout);
|
|
90
|
+
console.log(`\n${output}`);
|
|
91
|
+
writeProgress(workingRepo, output);
|
|
92
|
+
console.log(`Progress file written to ${workingRepo.preferredProgressPath()}`);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { runUpgrade };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as Repo } from "./repo-
|
|
1
|
+
import { n as Repo } from "./repo-BTJG8BU1.js";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { join, relative } from "node:path";
|
|
4
|
-
//#region
|
|
4
|
+
//#region skills/first-tree/engine/validators/members.ts
|
|
5
5
|
const FRONTMATTER_RE$1 = /^---\s*\n(.*?)\n---/s;
|
|
6
6
|
const VALID_TYPES = new Set([
|
|
7
7
|
"human",
|
|
@@ -64,25 +64,51 @@ function runValidateMembers(treeRoot) {
|
|
|
64
64
|
}
|
|
65
65
|
const allErrors = [];
|
|
66
66
|
let memberCount = 0;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const nameOccurrences = /* @__PURE__ */ new Map();
|
|
68
|
+
const members = [];
|
|
69
|
+
function walk(dir) {
|
|
70
|
+
for (const child of readdirSync(dir).sort()) {
|
|
71
|
+
const childPath = join(dir, child);
|
|
72
|
+
try {
|
|
73
|
+
const stat = statSync(childPath);
|
|
74
|
+
if (stat.isFile() && child.endsWith(".md") && child !== "NODE.md") {
|
|
75
|
+
allErrors.push(`${rel$1(childPath, treeRoot)}: member must be a directory with NODE.md, not a standalone file — use ${rel$1(dir, treeRoot)}/${child.replace(/\.md$/, "")}/NODE.md instead`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!stat.isDirectory()) continue;
|
|
79
|
+
} catch {
|
|
73
80
|
continue;
|
|
74
81
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
const relPath = relative(membersDir, childPath);
|
|
83
|
+
const occurrences = nameOccurrences.get(child) ?? [];
|
|
84
|
+
occurrences.push(relPath);
|
|
85
|
+
nameOccurrences.set(child, occurrences);
|
|
86
|
+
const nodePath = join(childPath, "NODE.md");
|
|
87
|
+
if (!existsSync(nodePath)) {
|
|
88
|
+
allErrors.push(`${rel$1(childPath, treeRoot)}/: directory exists but missing NODE.md`);
|
|
89
|
+
walk(childPath);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
memberCount++;
|
|
93
|
+
allErrors.push(...validateMember(nodePath, treeRoot));
|
|
94
|
+
const fm = parseFrontmatter$1(nodePath);
|
|
95
|
+
if (fm) members.push({
|
|
96
|
+
name: child,
|
|
97
|
+
relPath,
|
|
98
|
+
type: extractScalar(fm, "type"),
|
|
99
|
+
delegateMention: extractScalar(fm, "delegate_mention")
|
|
100
|
+
});
|
|
101
|
+
walk(childPath);
|
|
83
102
|
}
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
}
|
|
104
|
+
walk(membersDir);
|
|
105
|
+
for (const [name, paths] of nameOccurrences) if (paths.length > 1) allErrors.push(`Duplicate member directory name '${name}' found at: ${paths.map((p) => `members/${p}`).join(", ")} — directory names must be unique across all levels under members/`);
|
|
106
|
+
const memberByName = new Map(members.map((m) => [m.name, m]));
|
|
107
|
+
for (const member of members) {
|
|
108
|
+
if (!member.delegateMention) continue;
|
|
109
|
+
const target = memberByName.get(member.delegateMention);
|
|
110
|
+
if (!target) allErrors.push(`members/${member.relPath}/NODE.md: delegate_mention '${member.delegateMention}' references non-existent member — the target must be a directory under members/`);
|
|
111
|
+
else if (target.type !== "personal_assistant") allErrors.push(`members/${member.relPath}/NODE.md: delegate_mention '${member.delegateMention}' must reference a member with type 'personal_assistant', but '${target.name}' has type '${target.type}'`);
|
|
86
112
|
}
|
|
87
113
|
if (allErrors.length > 0) {
|
|
88
114
|
console.log(`Found ${allErrors.length} member validation error(s):\n`);
|
|
@@ -99,7 +125,7 @@ function runValidateMembers(treeRoot) {
|
|
|
99
125
|
};
|
|
100
126
|
}
|
|
101
127
|
//#endregion
|
|
102
|
-
//#region
|
|
128
|
+
//#region skills/first-tree/engine/validators/nodes.ts
|
|
103
129
|
const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
|
|
104
130
|
const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
|
|
105
131
|
const SOFT_LINKS_INLINE_RE = /^soft_links:\s*\[([^\]]*)\]/m;
|
|
@@ -441,14 +467,15 @@ function runValidateNodes(root) {
|
|
|
441
467
|
};
|
|
442
468
|
}
|
|
443
469
|
//#endregion
|
|
444
|
-
//#region
|
|
470
|
+
//#region skills/first-tree/engine/verify.ts
|
|
445
471
|
const UNCHECKED_RE = /^- \[ \] (.+)$/gm;
|
|
446
472
|
function check(label, passed) {
|
|
447
473
|
console.log(` ${passed ? "✓" : "✗"} [${passed ? "PASS" : "FAIL"}] ${label}`);
|
|
448
474
|
return passed;
|
|
449
475
|
}
|
|
450
476
|
function checkProgress(repo) {
|
|
451
|
-
const
|
|
477
|
+
const progressPath = repo.progressPath();
|
|
478
|
+
const text = progressPath === null ? null : repo.readFile(progressPath);
|
|
452
479
|
if (text === null) return [];
|
|
453
480
|
const matches = [];
|
|
454
481
|
let m;
|
|
@@ -464,17 +491,19 @@ function runVerify(repo, nodeValidator) {
|
|
|
464
491
|
const r = repo ?? new Repo();
|
|
465
492
|
const validate = nodeValidator ?? defaultNodeValidator;
|
|
466
493
|
let allPassed = true;
|
|
494
|
+
const progressPath = r.progressPath() ?? r.preferredProgressPath();
|
|
495
|
+
const frameworkVersionPath = r.frameworkVersionPath();
|
|
467
496
|
console.log("Context Tree Verification\n");
|
|
468
497
|
const unchecked = checkProgress(r);
|
|
469
498
|
if (unchecked.length > 0) {
|
|
470
|
-
console.log(
|
|
499
|
+
console.log(` Unchecked items in ${progressPath}:\n`);
|
|
471
500
|
for (const item of unchecked) console.log(` - [ ] ${item}`);
|
|
472
501
|
console.log();
|
|
473
|
-
console.log(
|
|
502
|
+
console.log(` Verify each step above and check it off in ${progressPath} before running verify again.\n`);
|
|
474
503
|
allPassed = false;
|
|
475
504
|
}
|
|
476
505
|
console.log(" Checks:\n");
|
|
477
|
-
allPassed = check(
|
|
506
|
+
allPassed = check(`${frameworkVersionPath} exists`, r.hasFramework()) && allPassed;
|
|
478
507
|
const fm = r.frontmatter("NODE.md");
|
|
479
508
|
allPassed = check("Root NODE.md has valid frontmatter (title, owners)", fm !== null && fm.title !== void 0 && fm.owners !== void 0) && allPassed;
|
|
480
509
|
allPassed = check("AGENT.md exists with framework markers", r.hasAgentMdMarkers()) && allPassed;
|
package/package.json
CHANGED
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "first-tree",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "CLI tools for Context Tree — the living source of truth for your organization.",
|
|
5
|
+
"homepage": "https://github.com/agent-team-foundation/first-tree#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/agent-team-foundation/first-tree/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/agent-team-foundation/first-tree.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"context-tree",
|
|
15
|
+
"cli",
|
|
16
|
+
"agents",
|
|
17
|
+
"knowledge-base",
|
|
18
|
+
"markdown"
|
|
19
|
+
],
|
|
5
20
|
"type": "module",
|
|
6
21
|
"bin": {
|
|
7
22
|
"context-tree": "./dist/cli.js"
|
|
8
23
|
},
|
|
24
|
+
"imports": {
|
|
25
|
+
"#skill/*": "./skills/first-tree/*",
|
|
26
|
+
"#evals/*": "./evals/*",
|
|
27
|
+
"#src/*": "./src/*"
|
|
28
|
+
},
|
|
9
29
|
"files": [
|
|
10
|
-
"dist"
|
|
30
|
+
"dist",
|
|
31
|
+
"skills/first-tree"
|
|
11
32
|
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"build": "tsdown src/cli.ts --format esm --out-dir dist",
|
|
14
|
-
"prepack": "pnpm build",
|
|
15
|
-
"test": "vitest run",
|
|
16
|
-
"typecheck": "tsc --noEmit"
|
|
17
|
-
},
|
|
18
33
|
"license": "Apache-2.0",
|
|
19
34
|
"devDependencies": {
|
|
20
35
|
"@types/node": "^25.5.0",
|
|
21
36
|
"tsdown": "^0.12.0",
|
|
22
37
|
"typescript": "^5.8.0",
|
|
23
|
-
"vitest": "^3.2.0"
|
|
38
|
+
"vitest": "^3.2.0",
|
|
39
|
+
"yaml": "^2.8.3"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsdown",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"eval": "vitest run --config vitest.eval.config.ts",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"validate:skill": "python3 ./skills/first-tree/scripts/quick_validate.py ./skills/first-tree && bash ./skills/first-tree/scripts/check-skill-sync.sh"
|
|
24
47
|
}
|
|
25
|
-
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: first-tree
|
|
3
|
+
description: Maintain the canonical `first-tree` skill and the thin `context-tree` CLI distributed by the `first-tree` npm package. Use when modifying `context-tree` commands (`init`, `verify`, `upgrade`, `help onboarding`), the installed skill payload under `assets/framework/`, maintainer references, or the build, packaging, test, and CI wiring that supports the framework.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# First Tree
|
|
7
|
+
|
|
8
|
+
Use this skill when the task depends on the exact behavior of the
|
|
9
|
+
`context-tree` CLI or the installed `skills/first-tree/` payload that
|
|
10
|
+
`context-tree init` ships to user repos.
|
|
11
|
+
|
|
12
|
+
## Source Of Truth
|
|
13
|
+
|
|
14
|
+
- `skills/first-tree/` is the only canonical copy.
|
|
15
|
+
- `references/` holds the explanatory docs the skill should load on demand.
|
|
16
|
+
- `assets/framework/` holds the runtime payload that gets installed into user
|
|
17
|
+
repos.
|
|
18
|
+
- `engine/` holds the canonical framework and CLI behavior.
|
|
19
|
+
- `scripts/` holds maintenance helpers for validating and running the skill.
|
|
20
|
+
- In maintainer docs, use `context-tree` for the CLI and `skills/first-tree/`
|
|
21
|
+
for the installed skill path so it is not confused with the `first-tree`
|
|
22
|
+
npm package.
|
|
23
|
+
|
|
24
|
+
## When To Read What
|
|
25
|
+
|
|
26
|
+
1. Start with `references/source-map.md` to locate the right files.
|
|
27
|
+
2. Read the user-facing reference that matches the task:
|
|
28
|
+
- `references/onboarding.md`
|
|
29
|
+
- `references/about.md`
|
|
30
|
+
- `references/principles.md`
|
|
31
|
+
- `references/ownership-and-naming.md`
|
|
32
|
+
- `references/upgrade-contract.md`
|
|
33
|
+
3. Read the maintainer reference that matches the shell or validation surface:
|
|
34
|
+
- `references/maintainer-architecture.md`
|
|
35
|
+
- `references/maintainer-thin-cli.md`
|
|
36
|
+
- `references/maintainer-build-and-distribution.md`
|
|
37
|
+
- `references/maintainer-testing.md`
|
|
38
|
+
4. Open `engine/` when changing `init`, `verify`, `upgrade`, command routing,
|
|
39
|
+
repo inspection, rules, runtime helpers, or validators.
|
|
40
|
+
5. Open `assets/framework/` only when the task changes shipped templates,
|
|
41
|
+
workflows, prompts, examples, or helper scripts.
|
|
42
|
+
6. Open `tests/` when changing validation coverage or maintainer workflows.
|
|
43
|
+
7. Use `./scripts/run-local-cli.sh <command>` when you need to exercise the
|
|
44
|
+
live CLI from this repo.
|
|
45
|
+
|
|
46
|
+
## Working Modes
|
|
47
|
+
|
|
48
|
+
### Maintaining `first-tree`
|
|
49
|
+
|
|
50
|
+
- Treat this repo as the distribution source for one canonical skill plus a
|
|
51
|
+
thin CLI shell, not as a tree repo.
|
|
52
|
+
- Keep command behavior, validator behavior, shipped assets, maintainer
|
|
53
|
+
references, and package shell aligned.
|
|
54
|
+
- If root README/AGENT/CI text explains something non-obvious, migrate that
|
|
55
|
+
information into `references/` and trim the root file back down.
|
|
56
|
+
- If you change runtime assets or skill references, run `pnpm validate:skill`.
|
|
57
|
+
|
|
58
|
+
### Working In A User Tree Repo
|
|
59
|
+
|
|
60
|
+
- `context-tree init` installs this skill into the user's repo and scaffolds
|
|
61
|
+
`NODE.md`, `AGENT.md`, and `members/NODE.md`.
|
|
62
|
+
- `context-tree upgrade` refreshes the installed skill from the copy bundled
|
|
63
|
+
with the currently running `first-tree` package. To pick up a newer
|
|
64
|
+
framework, run a newer package version first. It also migrates older repos
|
|
65
|
+
that still use `skills/first-tree-cli-framework/`.
|
|
66
|
+
- The user's tree content lives outside the skill; the skill only carries the
|
|
67
|
+
reusable framework payload plus maintenance guidance.
|
|
68
|
+
- The tree still stores decisions, constraints, and ownership; execution detail
|
|
69
|
+
stays in source systems.
|
|
70
|
+
|
|
71
|
+
## Non-Negotiables
|
|
72
|
+
|
|
73
|
+
- Preserve the CLI contract that it scaffolds, prints task lists, and validates
|
|
74
|
+
state; it does not fully automate tree maintenance.
|
|
75
|
+
- Keep shipped assets generic. They must not contain org-specific content.
|
|
76
|
+
- Keep decision knowledge in the tree and execution detail in source systems.
|
|
77
|
+
- Keep the skill as the only canonical knowledge source. The root CLI/package
|
|
78
|
+
shell must not become a second source of framework semantics.
|
|
79
|
+
- Keep normal `init` / `upgrade` flows self-contained. They must work from the
|
|
80
|
+
skill bundled in the current package without cloning the source repo.
|
|
81
|
+
- Make upgrade behavior explicit. If you change installed paths, update
|
|
82
|
+
`references/upgrade-contract.md`, task text, and tests together.
|
|
83
|
+
|
|
84
|
+
## Validation
|
|
85
|
+
|
|
86
|
+
- Repo checks: `pnpm typecheck`, `pnpm test`, `pnpm build`
|
|
87
|
+
- Packaging check: `pnpm pack` when changing package contents or install/upgrade
|
|
88
|
+
behavior
|
|
89
|
+
- Skill checks:
|
|
90
|
+
- `pnpm validate:skill`
|
|
91
|
+
- `python3 ./skills/first-tree/scripts/quick_validate.py ./skills/first-tree`
|
|
92
|
+
- `bash ./skills/first-tree/scripts/check-skill-sync.sh`
|
|
93
|
+
|
|
94
|
+
## Key Files
|
|
95
|
+
|
|
96
|
+
- `assets/framework/manifest.json`: runtime asset contract
|
|
97
|
+
- `assets/framework/templates/`: generated scaffolds
|
|
98
|
+
- `assets/framework/workflows/`: CI templates
|
|
99
|
+
- `assets/framework/helpers/`: shipped helper scripts and review tooling
|
|
100
|
+
- `engine/`: canonical framework and CLI behavior
|
|
101
|
+
- `tests/`: canonical unit and structure validation
|
|
102
|
+
- `references/source-map.md`: canonical reading index
|
|
103
|
+
- `references/maintainer-architecture.md`: source-repo architecture and
|
|
104
|
+
invariants
|
|
105
|
+
- `references/maintainer-thin-cli.md`: root shell contract
|
|
106
|
+
- `references/maintainer-build-and-distribution.md`: packaging and release
|
|
107
|
+
guidance
|
|
108
|
+
- `references/maintainer-testing.md`: validation workflow
|
|
109
|
+
- `references/upgrade-contract.md`: installed layout and upgrade semantics
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "First Tree"
|
|
3
|
+
short_description: "Maintain the First Tree skill and thin Context Tree CLI"
|
|
4
|
+
default_prompt: "Use $first-tree to maintain the canonical first-tree skill, its thin context-tree CLI, or its build, packaging, test, eval, and CI wiring."
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Claude Code Integration
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
Copy `settings.json` to your tree repo's `.claude/` directory:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
mkdir -p .claude
|
|
9
|
+
cp skills/first-tree/assets/framework/examples/claude-code/settings.json .claude/settings.json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What It Does
|
|
13
|
+
|
|
14
|
+
The `SessionStart` hook runs `./skills/first-tree/assets/framework/helpers/inject-tree-context.sh` when a Claude Code session begins. This injects the root `NODE.md` content as additional context, giving the agent an overview of the tree structure before any task.
|