first-tree 0.0.3 → 0.0.4
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 +69 -27
- package/dist/cli.js +28 -13
- package/dist/{help-xEI-s9iN.js → help-Dtdj91HJ.js} +1 -1
- package/dist/{init-DtOjj0wc.js → init--VepFe6N.js} +171 -21
- package/dist/{installer-rcZpGLnM.js → installer-cH7N4RNj.js} +2 -2
- package/dist/onboarding-C9cYSE6F.js +2 -0
- package/dist/onboarding-CPP8fF4D.js +10 -0
- package/dist/{repo-BTJG8BU1.js → repo-DY57bMqr.js} +143 -12
- package/dist/{upgrade-COGgI7Rj.js → upgrade-Cgx_K2HM.js} +46 -7
- package/dist/{verify-CxN6JiV9.js → verify-mC9ZTd1f.js} +66 -6
- package/package.json +1 -1
- package/skills/first-tree/SKILL.md +8 -4
- package/skills/first-tree/assets/framework/VERSION +1 -1
- package/skills/first-tree/assets/framework/helpers/run-review.ts +16 -2
- package/skills/first-tree/assets/framework/templates/{agent.md.template → agents.md.template} +1 -0
- package/skills/first-tree/assets/framework/templates/root-node.md.template +6 -3
- package/skills/first-tree/engine/commands/init.ts +1 -1
- package/skills/first-tree/engine/commands/upgrade.ts +1 -1
- package/skills/first-tree/engine/commands/verify.ts +1 -1
- package/skills/first-tree/engine/init.ts +285 -16
- package/skills/first-tree/engine/repo.ts +185 -9
- package/skills/first-tree/engine/rules/agent-instructions.ts +29 -7
- package/skills/first-tree/engine/runtime/asset-loader.ts +7 -0
- package/skills/first-tree/engine/upgrade.ts +66 -9
- package/skills/first-tree/engine/validators/nodes.ts +48 -3
- package/skills/first-tree/engine/verify.ts +61 -3
- package/skills/first-tree/references/maintainer-architecture.md +1 -1
- package/skills/first-tree/references/maintainer-build-and-distribution.md +3 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +1 -1
- package/skills/first-tree/references/onboarding.md +32 -9
- package/skills/first-tree/references/source-map.md +3 -3
- package/skills/first-tree/references/upgrade-contract.md +14 -5
- package/skills/first-tree/scripts/check-skill-sync.sh +1 -1
- package/skills/first-tree/tests/helpers.ts +24 -4
- package/skills/first-tree/tests/init.test.ts +103 -6
- package/skills/first-tree/tests/repo.test.ts +87 -9
- package/skills/first-tree/tests/rules.test.ts +26 -7
- package/skills/first-tree/tests/skill-artifacts.test.ts +4 -0
- package/skills/first-tree/tests/thin-cli.test.ts +52 -7
- package/skills/first-tree/tests/upgrade.test.ts +19 -5
- package/skills/first-tree/tests/verify.test.ts +106 -7
- package/dist/onboarding-6Fr5Gkrk.js +0 -2
- package/dist/onboarding-B9zPGvvG.js +0 -10
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
-
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
//#region skills/first-tree/engine/runtime/asset-loader.ts
|
|
4
|
+
const SKILL_NAME = "first-tree";
|
|
5
|
+
const SKILL_ROOT = join("skills", SKILL_NAME);
|
|
4
6
|
join(SKILL_ROOT, "agents");
|
|
5
7
|
join(SKILL_ROOT, "references");
|
|
6
8
|
const FRAMEWORK_ASSET_ROOT = join(SKILL_ROOT, "assets", "framework");
|
|
@@ -12,7 +14,11 @@ join(FRAMEWORK_ASSET_ROOT, "prompts");
|
|
|
12
14
|
const FRAMEWORK_EXAMPLES_DIR = join(FRAMEWORK_ASSET_ROOT, "examples");
|
|
13
15
|
join(FRAMEWORK_ASSET_ROOT, "helpers");
|
|
14
16
|
const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
|
|
15
|
-
const
|
|
17
|
+
const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
|
|
18
|
+
const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
|
|
19
|
+
const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
|
|
20
|
+
const LEGACY_SKILL_NAME = "first-tree-cli-framework";
|
|
21
|
+
const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
|
|
16
22
|
const LEGACY_SKILL_ASSET_ROOT = join(LEGACY_SKILL_ROOT, "assets", "framework");
|
|
17
23
|
const LEGACY_SKILL_VERSION = join(LEGACY_SKILL_ASSET_ROOT, "VERSION");
|
|
18
24
|
join(LEGACY_SKILL_ASSET_ROOT, "templates");
|
|
@@ -49,6 +55,9 @@ function progressFileCandidates() {
|
|
|
49
55
|
LEGACY_PROGRESS
|
|
50
56
|
];
|
|
51
57
|
}
|
|
58
|
+
function agentInstructionsFileCandidates() {
|
|
59
|
+
return [AGENT_INSTRUCTIONS_FILE, LEGACY_AGENT_INSTRUCTIONS_FILE];
|
|
60
|
+
}
|
|
52
61
|
function resolveFirstExistingPath(root, candidates) {
|
|
53
62
|
for (const candidate of candidates) if (pathExists(root, candidate)) return candidate;
|
|
54
63
|
return null;
|
|
@@ -64,11 +73,82 @@ function detectFrameworkLayout(root) {
|
|
|
64
73
|
const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
|
|
65
74
|
const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
|
|
66
75
|
const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
|
|
76
|
+
const EMPTY_REPO_ENTRY_ALLOWLIST = new Set([
|
|
77
|
+
".DS_Store",
|
|
78
|
+
".editorconfig",
|
|
79
|
+
".gitattributes",
|
|
80
|
+
".github",
|
|
81
|
+
".gitignore",
|
|
82
|
+
"AGENT.md",
|
|
83
|
+
"AGENTS.md",
|
|
84
|
+
"CLAUDE.md",
|
|
85
|
+
"LICENSE",
|
|
86
|
+
"LICENSE.md",
|
|
87
|
+
"LICENSE.txt",
|
|
88
|
+
"README",
|
|
89
|
+
"README.md",
|
|
90
|
+
"README.txt"
|
|
91
|
+
]);
|
|
92
|
+
const SOURCE_FILE_HINTS = new Set([
|
|
93
|
+
".gitmodules",
|
|
94
|
+
"Cargo.toml",
|
|
95
|
+
"Dockerfile",
|
|
96
|
+
"Gemfile",
|
|
97
|
+
"Makefile",
|
|
98
|
+
"bun.lock",
|
|
99
|
+
"bun.lockb",
|
|
100
|
+
"docker-compose.yml",
|
|
101
|
+
"go.mod",
|
|
102
|
+
"package-lock.json",
|
|
103
|
+
"package.json",
|
|
104
|
+
"pnpm-lock.yaml",
|
|
105
|
+
"pyproject.toml",
|
|
106
|
+
"requirements.txt",
|
|
107
|
+
"tsconfig.json",
|
|
108
|
+
"uv.lock",
|
|
109
|
+
"vite.config.ts",
|
|
110
|
+
"vite.config.js"
|
|
111
|
+
]);
|
|
112
|
+
const SOURCE_DIR_HINTS = new Set([
|
|
113
|
+
"app",
|
|
114
|
+
"apps",
|
|
115
|
+
"backend",
|
|
116
|
+
"cli",
|
|
117
|
+
"client",
|
|
118
|
+
"docs",
|
|
119
|
+
"e2e",
|
|
120
|
+
"frontend",
|
|
121
|
+
"lib",
|
|
122
|
+
"packages",
|
|
123
|
+
"scripts",
|
|
124
|
+
"server",
|
|
125
|
+
"src",
|
|
126
|
+
"test",
|
|
127
|
+
"tests"
|
|
128
|
+
]);
|
|
67
129
|
const FRAMEWORK_END_MARKER = "<!-- END CONTEXT-TREE FRAMEWORK -->";
|
|
130
|
+
function hasGitMetadata(root) {
|
|
131
|
+
try {
|
|
132
|
+
const stat = statSync(join(root, ".git"));
|
|
133
|
+
return stat.isDirectory() || stat.isFile();
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function discoverGitRoot(start) {
|
|
139
|
+
let dir = start;
|
|
140
|
+
while (true) {
|
|
141
|
+
if (hasGitMetadata(dir)) return dir;
|
|
142
|
+
const parent = dirname(dir);
|
|
143
|
+
if (parent === dir) return null;
|
|
144
|
+
dir = parent;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
68
147
|
var Repo = class {
|
|
69
148
|
root;
|
|
70
149
|
constructor(root) {
|
|
71
|
-
|
|
150
|
+
const start = resolve(root ?? process.cwd());
|
|
151
|
+
this.root = root === void 0 ? discoverGitRoot(start) ?? start : start;
|
|
72
152
|
}
|
|
73
153
|
pathExists(relPath) {
|
|
74
154
|
return existsSync(join(this.root, relPath));
|
|
@@ -109,11 +189,7 @@ var Repo = class {
|
|
|
109
189
|
return [".claude/settings.json", ".codex/config.json"].some((c) => this.pathExists(c));
|
|
110
190
|
}
|
|
111
191
|
isGitRepo() {
|
|
112
|
-
|
|
113
|
-
return statSync(join(this.root, ".git")).isDirectory();
|
|
114
|
-
} catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
192
|
+
return hasGitMetadata(this.root);
|
|
117
193
|
}
|
|
118
194
|
hasFramework() {
|
|
119
195
|
return this.frameworkLayout() !== null;
|
|
@@ -142,8 +218,25 @@ var Repo = class {
|
|
|
142
218
|
if (layout === "legacy-skill") return LEGACY_SKILL_VERSION;
|
|
143
219
|
return FRAMEWORK_VERSION;
|
|
144
220
|
}
|
|
145
|
-
|
|
146
|
-
|
|
221
|
+
agentInstructionsPath() {
|
|
222
|
+
return resolveFirstExistingPath(this.root, agentInstructionsFileCandidates());
|
|
223
|
+
}
|
|
224
|
+
hasCanonicalAgentInstructionsFile() {
|
|
225
|
+
return this.pathExists(AGENT_INSTRUCTIONS_FILE);
|
|
226
|
+
}
|
|
227
|
+
hasLegacyAgentInstructionsFile() {
|
|
228
|
+
return this.pathExists(LEGACY_AGENT_INSTRUCTIONS_FILE);
|
|
229
|
+
}
|
|
230
|
+
hasDuplicateAgentInstructionsFiles() {
|
|
231
|
+
return this.hasCanonicalAgentInstructionsFile() && this.hasLegacyAgentInstructionsFile();
|
|
232
|
+
}
|
|
233
|
+
readAgentInstructions() {
|
|
234
|
+
const relPath = this.agentInstructionsPath();
|
|
235
|
+
if (relPath === null) return null;
|
|
236
|
+
return this.readFile(relPath);
|
|
237
|
+
}
|
|
238
|
+
hasAgentInstructionsMarkers() {
|
|
239
|
+
const text = this.readAgentInstructions();
|
|
147
240
|
if (text === null) return false;
|
|
148
241
|
return text.includes("<!-- BEGIN CONTEXT-TREE FRAMEWORK") && text.includes("<!-- END CONTEXT-TREE FRAMEWORK -->");
|
|
149
242
|
}
|
|
@@ -182,6 +275,44 @@ var Repo = class {
|
|
|
182
275
|
hasPlaceholderNode() {
|
|
183
276
|
return this.fileContains("NODE.md", "<!-- PLACEHOLDER");
|
|
184
277
|
}
|
|
278
|
+
repoName() {
|
|
279
|
+
return basename(this.root);
|
|
280
|
+
}
|
|
281
|
+
topLevelEntries() {
|
|
282
|
+
try {
|
|
283
|
+
return readdirSync(this.root).filter((entry) => entry !== ".git");
|
|
284
|
+
} catch {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
looksLikeTreeRepo() {
|
|
289
|
+
if (this.pathExists("package.json") && this.pathExists("src/cli.ts") && this.pathExists("skills/first-tree/SKILL.md") && this.progressPath() === null && this.frontmatter("NODE.md") === null && !this.hasAgentInstructionsMarkers() && !this.pathExists("members/NODE.md")) return false;
|
|
290
|
+
return this.progressPath() !== null || this.hasFramework() || this.hasAgentInstructionsMarkers() || this.pathExists("members/NODE.md") || this.frontmatter("NODE.md") !== null;
|
|
291
|
+
}
|
|
292
|
+
isLikelyEmptyRepo() {
|
|
293
|
+
return this.topLevelEntries().filter((entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry)).length === 0;
|
|
294
|
+
}
|
|
295
|
+
isLikelySourceRepo() {
|
|
296
|
+
if (this.looksLikeTreeRepo()) return false;
|
|
297
|
+
const entries = this.topLevelEntries().filter((entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry));
|
|
298
|
+
if (entries.length === 0) return false;
|
|
299
|
+
let directoryCount = 0;
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
if (SOURCE_FILE_HINTS.has(entry)) return true;
|
|
302
|
+
if (isDirectory(this.root, entry)) {
|
|
303
|
+
directoryCount += 1;
|
|
304
|
+
if (SOURCE_DIR_HINTS.has(entry)) return true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return directoryCount >= 2 || entries.length >= 4;
|
|
308
|
+
}
|
|
185
309
|
};
|
|
310
|
+
function isDirectory(root, relPath) {
|
|
311
|
+
try {
|
|
312
|
+
return statSync(join(root, relPath)).isDirectory();
|
|
313
|
+
} catch {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
186
317
|
//#endregion
|
|
187
|
-
export {
|
|
318
|
+
export { FRAMEWORK_ASSET_ROOT as a, FRAMEWORK_VERSION as c, LEGACY_AGENT_INSTRUCTIONS_FILE as d, LEGACY_FRAMEWORK_ROOT as f, SKILL_ROOT as g, SKILL_NAME as h, AGENT_INSTRUCTIONS_TEMPLATE as i, FRAMEWORK_WORKFLOWS_DIR as l, LEGACY_SKILL_ROOT as m, Repo as n, FRAMEWORK_EXAMPLES_DIR as o, LEGACY_SKILL_NAME as p, AGENT_INSTRUCTIONS_FILE as r, FRAMEWORK_TEMPLATES_DIR as s, FRAMEWORK_END_MARKER as t, INSTALLED_PROGRESS as u };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as resolveCanonicalSkillRoot, r as resolveBundledPackageRoot, t as copyCanonicalSkill } from "./installer-
|
|
1
|
+
import { c as FRAMEWORK_VERSION, d as LEGACY_AGENT_INSTRUCTIONS_FILE, f as LEGACY_FRAMEWORK_ROOT, g as SKILL_ROOT, i as AGENT_INSTRUCTIONS_TEMPLATE, l as FRAMEWORK_WORKFLOWS_DIR, m as LEGACY_SKILL_ROOT, n as Repo, r as AGENT_INSTRUCTIONS_FILE, s as FRAMEWORK_TEMPLATES_DIR, u as INSTALLED_PROGRESS } from "./repo-DY57bMqr.js";
|
|
2
|
+
import { i as resolveCanonicalSkillRoot, r as resolveBundledPackageRoot, t as copyCanonicalSkill } from "./installer-cH7N4RNj.js";
|
|
3
3
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
//#region skills/first-tree/engine/runtime/upgrader.ts
|
|
6
6
|
function compareFrameworkVersions(left, right) {
|
|
7
7
|
const result = left.localeCompare(right, void 0, {
|
|
@@ -22,6 +22,12 @@ function readSourceVersion(sourceRoot) {
|
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region skills/first-tree/engine/upgrade.ts
|
|
25
|
+
const UPGRADE_USAGE = `usage: context-tree upgrade [--tree-path PATH]
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--tree-path PATH Upgrade a tree repo from another working directory
|
|
29
|
+
--help Show this help message
|
|
30
|
+
`;
|
|
25
31
|
function writeProgress(repo, content) {
|
|
26
32
|
const progressPath = join(repo.root, repo.preferredProgressPath());
|
|
27
33
|
mkdirSync(dirname(progressPath), { recursive: true });
|
|
@@ -36,14 +42,22 @@ function formatUpgradeTaskList(repo, localVersion, packagedVersion, layout) {
|
|
|
36
42
|
`- [ ] Re-check any local agent setup that references \`${SKILL_ROOT}/assets/framework/examples/\` or \`${SKILL_ROOT}/assets/framework/helpers/\``,
|
|
37
43
|
""
|
|
38
44
|
];
|
|
39
|
-
|
|
40
|
-
if (layout === "legacy
|
|
41
|
-
if (
|
|
45
|
+
const migrationTasks = [];
|
|
46
|
+
if (layout === "legacy") migrationTasks.push("- [ ] Remove any stale `.context-tree/` references from repo-specific docs, scripts, or workflow files");
|
|
47
|
+
if (layout === "legacy-skill") migrationTasks.push(`- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`);
|
|
48
|
+
if (repo.hasCanonicalAgentInstructionsFile() && repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Merge any remaining user-authored content from \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` into \`${AGENT_INSTRUCTIONS_FILE}\`, then delete the legacy file`);
|
|
49
|
+
else if (repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\` to use the canonical agent instructions filename`);
|
|
50
|
+
if (migrationTasks.length > 0) lines.push("## Migration", ...migrationTasks, "");
|
|
51
|
+
if (repo.hasAgentInstructionsMarkers()) lines.push("## Agent Instructions", `- [ ] Compare the framework section in \`${AGENT_INSTRUCTIONS_FILE}\` with \`${FRAMEWORK_TEMPLATES_DIR}/${AGENT_INSTRUCTIONS_TEMPLATE}\` and update the content between the markers if needed`, "");
|
|
42
52
|
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
53
|
return lines.join("\n");
|
|
44
54
|
}
|
|
45
55
|
function runUpgrade(repo, options) {
|
|
46
56
|
const workingRepo = repo ?? new Repo();
|
|
57
|
+
if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo()) {
|
|
58
|
+
console.error("Error: no installed framework skill found here. This looks like a source/workspace repo. Run `context-tree init` to create a dedicated tree repo, or pass `--tree-path` to upgrade an existing tree repo.");
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
47
61
|
if (!workingRepo.hasFramework()) {
|
|
48
62
|
console.error("Error: no installed framework skill found. Run `context-tree init` first.");
|
|
49
63
|
return 1;
|
|
@@ -92,5 +106,30 @@ function runUpgrade(repo, options) {
|
|
|
92
106
|
console.log(`Progress file written to ${workingRepo.preferredProgressPath()}`);
|
|
93
107
|
return 0;
|
|
94
108
|
}
|
|
109
|
+
function runUpgradeCli(args = []) {
|
|
110
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
111
|
+
console.log(UPGRADE_USAGE);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
let treePath;
|
|
115
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
116
|
+
const arg = args[index];
|
|
117
|
+
if (arg === "--tree-path") {
|
|
118
|
+
const value = args[index + 1];
|
|
119
|
+
if (!value) {
|
|
120
|
+
console.error("Missing value for --tree-path");
|
|
121
|
+
console.log(UPGRADE_USAGE);
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
treePath = value;
|
|
125
|
+
index += 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
console.error(`Unknown upgrade option: ${arg}`);
|
|
129
|
+
console.log(UPGRADE_USAGE);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
return runUpgrade(treePath ? new Repo(resolve(process.cwd(), treePath)) : void 0);
|
|
133
|
+
}
|
|
95
134
|
//#endregion
|
|
96
|
-
export { runUpgrade };
|
|
135
|
+
export { runUpgradeCli as runUpgrade };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as Repo } from "./repo-
|
|
1
|
+
import { d as LEGACY_AGENT_INSTRUCTIONS_FILE, g as SKILL_ROOT, m as LEGACY_SKILL_ROOT, n as Repo, r as AGENT_INSTRUCTIONS_FILE } from "./repo-DY57bMqr.js";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { join, relative } from "node:path";
|
|
3
|
+
import { join, relative, resolve } from "node:path";
|
|
4
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([
|
|
@@ -133,7 +133,11 @@ const SOFT_LINKS_BLOCK_RE = /^soft_links:\s*\n((?:\s+-\s+.+\n?)+)/m;
|
|
|
133
133
|
const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
|
|
134
134
|
const GITHUB_USER_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/;
|
|
135
135
|
const SKIP = new Set(["node_modules", "__pycache__"]);
|
|
136
|
-
const SKIP_FILES = new Set([
|
|
136
|
+
const SKIP_FILES = new Set([
|
|
137
|
+
AGENT_INSTRUCTIONS_FILE,
|
|
138
|
+
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
139
|
+
"CLAUDE.md"
|
|
140
|
+
]);
|
|
137
141
|
const MIN_BODY_LENGTH = 20;
|
|
138
142
|
var Findings = class {
|
|
139
143
|
errors = [];
|
|
@@ -181,8 +185,23 @@ function setTreeRoot(root) {
|
|
|
181
185
|
function rel(path) {
|
|
182
186
|
return relative(treeRoot, path);
|
|
183
187
|
}
|
|
188
|
+
function isInstalledSkillPath(relPath) {
|
|
189
|
+
return relPath === SKILL_ROOT || relPath.startsWith(`${SKILL_ROOT}/`) || relPath === LEGACY_SKILL_ROOT || relPath.startsWith(`${LEGACY_SKILL_ROOT}/`);
|
|
190
|
+
}
|
|
191
|
+
function isFrameworkContainerDir(relPath, fullPath) {
|
|
192
|
+
if (relPath !== "skills") return false;
|
|
193
|
+
try {
|
|
194
|
+
const entries = readdirSync(fullPath).filter((entry) => !entry.startsWith("."));
|
|
195
|
+
if (entries.length === 0) return false;
|
|
196
|
+
return entries.every((entry) => entry === "first-tree" || entry === "first-tree-cli-framework");
|
|
197
|
+
} catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
184
201
|
function shouldSkip(path) {
|
|
185
|
-
|
|
202
|
+
const relPath = relative(treeRoot, path);
|
|
203
|
+
if (relPath.split("/").some((part) => SKIP.has(part) || part.startsWith("."))) return true;
|
|
204
|
+
return isInstalledSkillPath(relPath) || isFrameworkContainerDir(relPath, path);
|
|
186
205
|
}
|
|
187
206
|
function readText(path) {
|
|
188
207
|
if (!textCache.has(path)) try {
|
|
@@ -469,6 +488,12 @@ function runValidateNodes(root) {
|
|
|
469
488
|
//#endregion
|
|
470
489
|
//#region skills/first-tree/engine/verify.ts
|
|
471
490
|
const UNCHECKED_RE = /^- \[ \] (.+)$/gm;
|
|
491
|
+
const VERIFY_USAGE = `usage: context-tree verify [--tree-path PATH]
|
|
492
|
+
|
|
493
|
+
Options:
|
|
494
|
+
--tree-path PATH Verify a tree repo from another working directory
|
|
495
|
+
--help Show this help message
|
|
496
|
+
`;
|
|
472
497
|
function check(label, passed) {
|
|
473
498
|
console.log(` ${passed ? "✓" : "✗"} [${passed ? "PASS" : "FAIL"}] ${label}`);
|
|
474
499
|
return passed;
|
|
@@ -490,6 +515,10 @@ function defaultNodeValidator(root) {
|
|
|
490
515
|
function runVerify(repo, nodeValidator) {
|
|
491
516
|
const r = repo ?? new Repo();
|
|
492
517
|
const validate = nodeValidator ?? defaultNodeValidator;
|
|
518
|
+
if (r.isLikelySourceRepo() && !r.looksLikeTreeRepo()) {
|
|
519
|
+
console.error("Error: no installed framework skill found here. This looks like a source/workspace repo. Run `context-tree init` to create a dedicated tree repo, or pass `--tree-path` to verify an existing tree repo.");
|
|
520
|
+
return 1;
|
|
521
|
+
}
|
|
493
522
|
let allPassed = true;
|
|
494
523
|
const progressPath = r.progressPath() ?? r.preferredProgressPath();
|
|
495
524
|
const frameworkVersionPath = r.frameworkVersionPath();
|
|
@@ -506,7 +535,13 @@ function runVerify(repo, nodeValidator) {
|
|
|
506
535
|
allPassed = check(`${frameworkVersionPath} exists`, r.hasFramework()) && allPassed;
|
|
507
536
|
const fm = r.frontmatter("NODE.md");
|
|
508
537
|
allPassed = check("Root NODE.md has valid frontmatter (title, owners)", fm !== null && fm.title !== void 0 && fm.owners !== void 0) && allPassed;
|
|
509
|
-
|
|
538
|
+
const hasCanonicalAgentInstructions = r.hasCanonicalAgentInstructionsFile();
|
|
539
|
+
const hasLegacyAgentInstructions = r.hasLegacyAgentInstructionsFile();
|
|
540
|
+
if (hasLegacyAgentInstructions) {
|
|
541
|
+
const followUp = hasCanonicalAgentInstructions ? `Remove legacy \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` after confirming its contents are in \`${AGENT_INSTRUCTIONS_FILE}\`.` : `Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\`.`;
|
|
542
|
+
console.log(` Legacy agent instructions detected. ${followUp}\n`);
|
|
543
|
+
}
|
|
544
|
+
allPassed = check(`AGENTS.md is the only agent instructions file and has framework markers`, hasCanonicalAgentInstructions && !hasLegacyAgentInstructions && r.hasAgentInstructionsMarkers()) && allPassed;
|
|
510
545
|
const { exitCode } = validate(r.root);
|
|
511
546
|
allPassed = check("Node validation passes", exitCode === 0) && allPassed;
|
|
512
547
|
allPassed = check("Member validation passes", runValidateMembers(r.root).exitCode === 0) && allPassed;
|
|
@@ -515,5 +550,30 @@ function runVerify(repo, nodeValidator) {
|
|
|
515
550
|
else console.log("Some checks failed. See above for details.");
|
|
516
551
|
return allPassed ? 0 : 1;
|
|
517
552
|
}
|
|
553
|
+
function runVerifyCli(args = []) {
|
|
554
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
555
|
+
console.log(VERIFY_USAGE);
|
|
556
|
+
return 0;
|
|
557
|
+
}
|
|
558
|
+
let treePath;
|
|
559
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
560
|
+
const arg = args[index];
|
|
561
|
+
if (arg === "--tree-path") {
|
|
562
|
+
const value = args[index + 1];
|
|
563
|
+
if (!value) {
|
|
564
|
+
console.error("Missing value for --tree-path");
|
|
565
|
+
console.log(VERIFY_USAGE);
|
|
566
|
+
return 1;
|
|
567
|
+
}
|
|
568
|
+
treePath = value;
|
|
569
|
+
index += 1;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
console.error(`Unknown verify option: ${arg}`);
|
|
573
|
+
console.log(VERIFY_USAGE);
|
|
574
|
+
return 1;
|
|
575
|
+
}
|
|
576
|
+
return runVerify(treePath ? new Repo(resolve(process.cwd(), treePath)) : void 0);
|
|
577
|
+
}
|
|
518
578
|
//#endregion
|
|
519
|
-
export { runVerify };
|
|
579
|
+
export { runVerifyCli as runVerify };
|
package/package.json
CHANGED
|
@@ -51,14 +51,17 @@ Use this skill when the task depends on the exact behavior of the
|
|
|
51
51
|
thin CLI shell, not as a tree repo.
|
|
52
52
|
- Keep command behavior, validator behavior, shipped assets, maintainer
|
|
53
53
|
references, and package shell aligned.
|
|
54
|
-
- If root README/
|
|
54
|
+
- If root README/AGENTS/CI text explains something non-obvious, migrate that
|
|
55
55
|
information into `references/` and trim the root file back down.
|
|
56
56
|
- If you change runtime assets or skill references, run `pnpm validate:skill`.
|
|
57
57
|
|
|
58
58
|
### Working In A User Tree Repo
|
|
59
59
|
|
|
60
|
-
- `context-tree init`
|
|
61
|
-
|
|
60
|
+
- `context-tree init` defaults to creating or reusing a sibling dedicated tree
|
|
61
|
+
repo when invoked from a source/workspace repo. Use `--here` to initialize
|
|
62
|
+
the current repo in place when you are already inside the tree repo.
|
|
63
|
+
- `context-tree init` installs this skill into the target tree repo and
|
|
64
|
+
`NODE.md`, `AGENTS.md`, and `members/NODE.md`.
|
|
62
65
|
- `context-tree upgrade` refreshes the installed skill from the copy bundled
|
|
63
66
|
with the currently running `first-tree` package. To pick up a newer
|
|
64
67
|
framework, run a newer package version first. It also migrates older repos
|
|
@@ -77,7 +80,8 @@ Use this skill when the task depends on the exact behavior of the
|
|
|
77
80
|
- Keep the skill as the only canonical knowledge source. The root CLI/package
|
|
78
81
|
shell must not become a second source of framework semantics.
|
|
79
82
|
- Keep normal `init` / `upgrade` flows self-contained. They must work from the
|
|
80
|
-
skill bundled in the current package without cloning the source repo
|
|
83
|
+
skill bundled in the current package without cloning the source repo or
|
|
84
|
+
relying on network access.
|
|
81
85
|
- Make upgrade behavior explicit. If you change installed paths, update
|
|
82
86
|
`references/upgrade-contract.md`, task text, and tests together.
|
|
83
87
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.2.0
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
|
|
@@ -17,11 +17,25 @@ const MAX_ATTEMPTS = 3;
|
|
|
17
17
|
// Per-invocation budget cap. Worst case is $1.50 total (3 × $0.50),
|
|
18
18
|
// though retries are cheap in practice due to cached context via --continue.
|
|
19
19
|
const MAX_BUDGET_USD = 0.5;
|
|
20
|
+
const AGENT_INSTRUCTIONS_PATHS = ["AGENTS.md", "AGENT.md"] as const;
|
|
21
|
+
|
|
22
|
+
function resolveAgentInstructionsPath(): string {
|
|
23
|
+
for (const candidate of AGENT_INSTRUCTIONS_PATHS) {
|
|
24
|
+
if (existsSync(candidate)) {
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
throw new Error(
|
|
30
|
+
"Missing AGENTS.md in repo root (legacy AGENT.md is also accepted during migration).",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
20
33
|
|
|
21
34
|
function buildPrompt(diffPath: string): string {
|
|
22
35
|
const parts: string[] = [];
|
|
36
|
+
const agentInstructionsPath = resolveAgentInstructionsPath();
|
|
23
37
|
const files: [string, string][] = [
|
|
24
|
-
[
|
|
38
|
+
[agentInstructionsPath, agentInstructionsPath],
|
|
25
39
|
["Root NODE.md", "NODE.md"],
|
|
26
40
|
[
|
|
27
41
|
"Review Instructions",
|
package/skills/first-tree/assets/framework/templates/{agent.md.template → agents.md.template}
RENAMED
|
@@ -28,6 +28,7 @@ Do not skip this. The tree is already a compression of expensive knowledge — c
|
|
|
28
28
|
|
|
29
29
|
- **Decide in the tree, execute in source systems.** If the task involves a decision (not just a bug fix), draft or update the relevant tree node before executing.
|
|
30
30
|
- **The tree is not for execution details.** Function signatures, DB schemas, API endpoints, ad copy — those live in source systems. The tree captures the *why* and *how things connect*.
|
|
31
|
+
- **Bring source repos in as additional working directories.** When you need to inspect or edit code, open the relevant source repositories alongside this tree instead of copying execution detail into the tree.
|
|
31
32
|
- **Respect ownership.** Each node declares owners in its frontmatter. If your changes touch a domain you don't own, flag it — the owner needs to review.
|
|
32
33
|
|
|
33
34
|
## After Every Task
|
|
@@ -5,9 +5,9 @@ owners: [<your-github-username>]
|
|
|
5
5
|
|
|
6
6
|
# <Your Organization>
|
|
7
7
|
|
|
8
|
-
<!-- PLACEHOLDER: Replace this with a description of your organization and what this tree captures. -->
|
|
8
|
+
<!-- PLACEHOLDER: Replace this with a description of your organization and what this tree captures across your source repos and systems. -->
|
|
9
9
|
|
|
10
|
-
The living source of truth for your organization. A structured knowledge base that agents and humans build and maintain together.
|
|
10
|
+
The living source of truth for your organization. A structured knowledge base that agents and humans build and maintain together across one or more source repositories and systems.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -29,7 +29,10 @@ The living source of truth for your organization. A structured knowledge base th
|
|
|
29
29
|
|
|
30
30
|
## Working with the Tree
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Keep decision context here. Keep implementation detail in the source repos this
|
|
33
|
+
tree describes.
|
|
34
|
+
|
|
35
|
+
See [AGENTS.md](AGENTS.md) for agent instructions — the before/during/after workflow, ownership model, and tree maintenance.
|
|
33
36
|
|
|
34
37
|
See [about.md](skills/first-tree/references/about.md) for background — the problem, the idea, and who it's for.
|
|
35
38
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { INIT_USAGE, parseInitArgs, runInitCli as runInit } from "#skill/engine/init.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { UPGRADE_USAGE, runUpgradeCli as runUpgrade } from "#skill/engine/upgrade.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { VERIFY_USAGE, runVerifyCli as runVerify } from "#skill/engine/verify.js";
|