first-tree 0.0.2 → 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 +116 -40
- package/dist/cli.js +46 -17
- package/dist/help-Dtdj91HJ.js +25 -0
- package/dist/init--VepFe6N.js +403 -0
- package/dist/installer-cH7N4RNj.js +47 -0
- package/dist/onboarding-C9cYSE6F.js +2 -0
- package/dist/onboarding-CPP8fF4D.js +10 -0
- package/dist/repo-DY57bMqr.js +318 -0
- package/dist/upgrade-Cgx_K2HM.js +135 -0
- package/dist/{verify-CSRIkuoM.js → verify-mC9ZTd1f.js} +118 -29
- package/package.json +33 -10
- package/skills/first-tree/SKILL.md +113 -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 +193 -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/agents.md.template +49 -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 +41 -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 +414 -0
- package/skills/first-tree/engine/onboarding.ts +10 -0
- package/skills/first-tree/engine/repo.ts +360 -0
- package/skills/first-tree/engine/rules/agent-instructions.ts +59 -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 +141 -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 +233 -0
- package/skills/first-tree/engine/validators/members.ts +215 -0
- package/skills/first-tree/engine/validators/nodes.ts +559 -0
- package/skills/first-tree/engine/verify.ts +155 -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 +59 -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 +185 -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 +94 -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 +169 -0
- package/skills/first-tree/tests/init.test.ts +250 -0
- package/skills/first-tree/tests/repo.test.ts +440 -0
- package/skills/first-tree/tests/rules.test.ts +413 -0
- package/skills/first-tree/tests/run-review.test.ts +155 -0
- package/skills/first-tree/tests/skill-artifacts.test.ts +311 -0
- package/skills/first-tree/tests/thin-cli.test.ts +104 -0
- package/skills/first-tree/tests/upgrade.test.ts +103 -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 +241 -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,403 @@
|
|
|
1
|
+
import { a as FRAMEWORK_ASSET_ROOT, c as FRAMEWORK_VERSION, d as LEGACY_AGENT_INSTRUCTIONS_FILE, g as SKILL_ROOT, i as AGENT_INSTRUCTIONS_TEMPLATE, l as FRAMEWORK_WORKFLOWS_DIR, n as Repo, o as FRAMEWORK_EXAMPLES_DIR, r as AGENT_INSTRUCTIONS_FILE, s as FRAMEWORK_TEMPLATES_DIR, t as FRAMEWORK_END_MARKER, u as INSTALLED_PROGRESS } from "./repo-DY57bMqr.js";
|
|
2
|
+
import { n as onboarding_default } from "./onboarding-CPP8fF4D.js";
|
|
3
|
+
import { n as renderTemplateFile, r as resolveBundledPackageRoot, t as copyCanonicalSkill } from "./installer-cH7N4RNj.js";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region skills/first-tree/engine/rules/agent-instructions.ts
|
|
20
|
+
var agent_instructions_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$6 });
|
|
21
|
+
function evaluate$6(repo) {
|
|
22
|
+
const tasks = [];
|
|
23
|
+
const hasCanonicalInstructions = repo.hasCanonicalAgentInstructionsFile();
|
|
24
|
+
const hasLegacyInstructions = repo.hasLegacyAgentInstructionsFile();
|
|
25
|
+
if (!hasCanonicalInstructions && !hasLegacyInstructions) {
|
|
26
|
+
tasks.push(`${AGENT_INSTRUCTIONS_FILE} is missing — create from \`${FRAMEWORK_TEMPLATES_DIR}/${AGENT_INSTRUCTIONS_TEMPLATE}\``);
|
|
27
|
+
return {
|
|
28
|
+
group: "Agent Instructions",
|
|
29
|
+
order: 3,
|
|
30
|
+
tasks
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (hasCanonicalInstructions && hasLegacyInstructions) tasks.push(`Merge any remaining user-authored content from \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` into \`${AGENT_INSTRUCTIONS_FILE}\`, then delete the legacy file`);
|
|
34
|
+
else if (hasLegacyInstructions) tasks.push(`Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\` to use the canonical agent instructions filename`);
|
|
35
|
+
const instructionsPath = repo.agentInstructionsPath() ?? "AGENTS.md";
|
|
36
|
+
if (!repo.hasAgentInstructionsMarkers()) tasks.push(`\`${instructionsPath}\` exists but is missing framework markers — add \`<!-- BEGIN CONTEXT-TREE FRAMEWORK -->\` and \`<!-- END CONTEXT-TREE FRAMEWORK -->\` sections`);
|
|
37
|
+
else {
|
|
38
|
+
const afterMarker = (repo.readAgentInstructions() ?? "").split(FRAMEWORK_END_MARKER);
|
|
39
|
+
if (afterMarker.length > 1) {
|
|
40
|
+
if (afterMarker[1].trim().split("\n").filter((l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("<!--")).length === 0) tasks.push(`Add your project-specific instructions below the framework markers in ${AGENT_INSTRUCTIONS_FILE}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
group: "Agent Instructions",
|
|
45
|
+
order: 3,
|
|
46
|
+
tasks
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region skills/first-tree/engine/rules/agent-integration.ts
|
|
51
|
+
var agent_integration_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$5 });
|
|
52
|
+
function evaluate$5(repo) {
|
|
53
|
+
const tasks = [];
|
|
54
|
+
if (repo.pathExists(".claude/settings.json")) {
|
|
55
|
+
if (!repo.fileContains(".claude/settings.json", "inject-tree-context")) tasks.push(`Add SessionStart hook to \`.claude/settings.json\` (see \`${FRAMEWORK_EXAMPLES_DIR}/claude-code/\`)`);
|
|
56
|
+
} else if (!repo.anyAgentConfig()) tasks.push(`No agent configuration detected. Configure your agent to load tree context at session start. See \`${FRAMEWORK_EXAMPLES_DIR}/\` for supported agents. You can skip this and set it up later.`);
|
|
57
|
+
return {
|
|
58
|
+
group: "Agent Integration",
|
|
59
|
+
order: 5,
|
|
60
|
+
tasks
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region skills/first-tree/engine/rules/ci-validation.ts
|
|
65
|
+
var ci_validation_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$4 });
|
|
66
|
+
function evaluate$4(repo) {
|
|
67
|
+
const tasks = [];
|
|
68
|
+
let hasValidation = false;
|
|
69
|
+
let hasPrReview = false;
|
|
70
|
+
let hasCodeowners = false;
|
|
71
|
+
const workflowsDir = join(repo.root, ".github", "workflows");
|
|
72
|
+
try {
|
|
73
|
+
if (statSync(workflowsDir).isDirectory()) for (const name of readdirSync(workflowsDir)) {
|
|
74
|
+
if (!name.endsWith(".yml") && !name.endsWith(".yaml")) continue;
|
|
75
|
+
const fullPath = join(workflowsDir, name);
|
|
76
|
+
try {
|
|
77
|
+
if (!statSync(fullPath).isFile()) continue;
|
|
78
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
79
|
+
if (content.includes("validate_nodes") || content.includes("validate_members")) hasValidation = true;
|
|
80
|
+
if (content.includes("run-review")) hasPrReview = true;
|
|
81
|
+
if (content.includes("generate-codeowners")) hasCodeowners = true;
|
|
82
|
+
} catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch {}
|
|
87
|
+
if (!hasValidation) tasks.push(`No validation workflow found — copy \`${FRAMEWORK_WORKFLOWS_DIR}/validate.yml\` to \`.github/workflows/validate.yml\``);
|
|
88
|
+
if (!hasPrReview) {
|
|
89
|
+
tasks.push(`Use ${INTERACTIVE_TOOL} to ask whether the user wants AI-powered PR reviews. Options:\n 1. **OpenRouter** — use an OpenRouter API key
|
|
90
|
+
2. **Claude API** — use a Claude API key directly
|
|
91
|
+
3. **Skip** — do not set up PR reviews
|
|
92
|
+
If (1): copy \`${FRAMEWORK_WORKFLOWS_DIR}/pr-review.yml\` to \`.github/workflows/pr-review.yml\` as-is; the repo secret name is \`OPENROUTER_API_KEY\`. If (2): copy the workflow and replace the \`env\` block with \`ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}\`, remove the \`ANTHROPIC_BASE_URL\`, \`ANTHROPIC_AUTH_TOKEN\`, and \`ANTHROPIC_DEFAULT_SONNET_MODEL\` lines; the repo secret name is \`ANTHROPIC_API_KEY\`. If (3): skip this and the next task.`);
|
|
93
|
+
tasks.push(`Use ${INTERACTIVE_TOOL} to ask how the user wants to configure the API secret. Options:\n 1. **Set it now** — provide the key and the agent will run \`gh secret set <SECRET_NAME> --body <KEY>\`
|
|
94
|
+
2. **I'll do it myself** — the agent will show manual instructions
|
|
95
|
+
If (1): ask the user to provide the key, then run \`gh secret set\` with the secret name from the previous step. If (2): tell the user to go to their repo → Settings → Secrets and variables → Actions → New repository secret, and create the secret with the name from the previous step. Skip this task if the user chose Skip in the previous step.`);
|
|
96
|
+
}
|
|
97
|
+
if (!hasCodeowners) tasks.push(`No CODEOWNERS workflow found — copy \`${FRAMEWORK_WORKFLOWS_DIR}/codeowners.yml\` to \`.github/workflows/codeowners.yml\` to auto-generate CODEOWNERS from tree ownership on every PR.`);
|
|
98
|
+
return {
|
|
99
|
+
group: "CI / Validation",
|
|
100
|
+
order: 6,
|
|
101
|
+
tasks
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region skills/first-tree/engine/rules/framework.ts
|
|
106
|
+
var framework_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$3 });
|
|
107
|
+
function evaluate$3(repo) {
|
|
108
|
+
const tasks = [];
|
|
109
|
+
if (!repo.hasFramework()) tasks.push(`\`${SKILL_ROOT}/\` not found — run \`context-tree init\` to install the framework skill bundled with the current \`first-tree\` package`);
|
|
110
|
+
return {
|
|
111
|
+
group: "Framework",
|
|
112
|
+
order: 1,
|
|
113
|
+
tasks
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region skills/first-tree/engine/rules/members.ts
|
|
118
|
+
var members_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$2 });
|
|
119
|
+
function evaluate$2(repo) {
|
|
120
|
+
const tasks = [];
|
|
121
|
+
if (!repo.pathExists("members")) tasks.push("`members/` directory is missing — create it with a NODE.md");
|
|
122
|
+
else if (!repo.pathExists("members/NODE.md")) tasks.push("`members/NODE.md` is missing — create it from the template");
|
|
123
|
+
if (repo.hasMembers() && repo.memberCount() === 0) tasks.push("Add at least one member node under `members/`. Analyze the user's code repositories (git history, CODEOWNERS, README contributors) to suggest members, then confirm with the user");
|
|
124
|
+
else if (!repo.hasMembers()) tasks.push("Add at least one member node under `members/`. Analyze the user's code repositories (git history, CODEOWNERS, README contributors) to suggest members, then confirm with the user");
|
|
125
|
+
return {
|
|
126
|
+
group: "Members",
|
|
127
|
+
order: 4,
|
|
128
|
+
tasks
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region skills/first-tree/engine/rules/populate-tree.ts
|
|
133
|
+
var populate_tree_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$1 });
|
|
134
|
+
function evaluate$1(repo) {
|
|
135
|
+
const tasks = [];
|
|
136
|
+
tasks.push(`Ask the user whether they want to populate the full context tree now using the **${INTERACTIVE_TOOL}** tool. Present two options: (1) **Yes — populate the full tree**: the agent will analyze source repositories, create sub-domains, and populate NODE.md files for each domain and sub-domain; (2) **No — I'll do it later**: skip deep population and finish init with just the top-level structure. If the user selects No, check off all remaining items in this section and move on.`);
|
|
137
|
+
tasks.push("If the user selected Yes: analyze the codebase (and any additional repositories the user provides) to identify logical sub-domains within each top-level domain. For each sub-domain, create a directory with a NODE.md containing proper frontmatter (title, owners) and a description of the sub-domain's purpose, boundaries, and key decisions. Create deeper sub-domains when a domain is large enough to warrant further decomposition.");
|
|
138
|
+
tasks.push("Use **sub-tasks** (TaskCreate) to parallelize the population work — create one sub-task per top-level domain so each domain can be populated concurrently. Each sub-task should: read the relevant source code, identify sub-domains, create NODE.md files, and establish soft_links between related domains.");
|
|
139
|
+
tasks.push("After all domains are populated, update the root NODE.md to list every top-level domain with a one-line description. Ensure all NODE.md files pass `context-tree verify` — valid frontmatter, no placeholders, and soft_links that resolve correctly.");
|
|
140
|
+
return {
|
|
141
|
+
group: "Populate Tree",
|
|
142
|
+
order: 7,
|
|
143
|
+
tasks
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region skills/first-tree/engine/rules/root-node.ts
|
|
148
|
+
var root_node_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate });
|
|
149
|
+
function evaluate(repo) {
|
|
150
|
+
const tasks = [];
|
|
151
|
+
if (!repo.pathExists("NODE.md")) tasks.push(`NODE.md is missing — create from \`${FRAMEWORK_TEMPLATES_DIR}/root-node.md.template\`. Ask the user for their code repositories or project directories, then analyze the source to determine the project description and domain structure`);
|
|
152
|
+
else {
|
|
153
|
+
const fm = repo.frontmatter("NODE.md");
|
|
154
|
+
if (fm === null) tasks.push("NODE.md exists but has no frontmatter — add frontmatter with title and owners fields");
|
|
155
|
+
else {
|
|
156
|
+
if (!fm.title || fm.title.startsWith("<")) tasks.push("NODE.md has a placeholder title — replace with your organization name");
|
|
157
|
+
if (!fm.owners || fm.owners.length === 0 || fm.owners.length === 1 && fm.owners[0].startsWith("<")) tasks.push("NODE.md has placeholder owners — set owners to your GitHub username(s)");
|
|
158
|
+
}
|
|
159
|
+
if (repo.hasPlaceholderNode()) tasks.push("NODE.md has placeholder content — ask the user for their code repositories or project directories, then analyze the source to fill in the project description and domain structure");
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
group: "Root Node",
|
|
163
|
+
order: 2,
|
|
164
|
+
tasks
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region skills/first-tree/engine/rules/index.ts
|
|
169
|
+
const ALL_RULES = [
|
|
170
|
+
framework_exports,
|
|
171
|
+
root_node_exports,
|
|
172
|
+
agent_instructions_exports,
|
|
173
|
+
members_exports,
|
|
174
|
+
agent_integration_exports,
|
|
175
|
+
ci_validation_exports,
|
|
176
|
+
populate_tree_exports
|
|
177
|
+
];
|
|
178
|
+
function evaluateAll(repo) {
|
|
179
|
+
const results = [];
|
|
180
|
+
for (const rule of ALL_RULES) {
|
|
181
|
+
const result = rule.evaluate(repo);
|
|
182
|
+
if (result.tasks.length > 0) results.push(result);
|
|
183
|
+
}
|
|
184
|
+
return results.sort((a, b) => a.order - b.order);
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region skills/first-tree/engine/init.ts
|
|
188
|
+
/**
|
|
189
|
+
* The interactive prompt tool the agent should use to present choices.
|
|
190
|
+
* Different agents may name this differently — change it here to update
|
|
191
|
+
* all generated task text at once.
|
|
192
|
+
*/
|
|
193
|
+
const INTERACTIVE_TOOL = "AskUserQuestion";
|
|
194
|
+
const INIT_USAGE = `usage: context-tree init [--here] [--tree-name NAME] [--tree-path PATH]
|
|
195
|
+
|
|
196
|
+
By default, running \`context-tree init\` inside a source or workspace repo creates
|
|
197
|
+
a sibling dedicated tree repo named \`<repo>-context\`.
|
|
198
|
+
|
|
199
|
+
Options:
|
|
200
|
+
--here Initialize the current repo in place
|
|
201
|
+
--tree-name NAME Name the dedicated sibling tree repo to create
|
|
202
|
+
--tree-path PATH Use an explicit tree repo path
|
|
203
|
+
--help Show this help message
|
|
204
|
+
`;
|
|
205
|
+
const TEMPLATE_MAP = [
|
|
206
|
+
{
|
|
207
|
+
templateName: "root-node.md.template",
|
|
208
|
+
targetPath: "NODE.md"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
templateName: AGENT_INSTRUCTIONS_TEMPLATE,
|
|
212
|
+
targetPath: AGENT_INSTRUCTIONS_FILE,
|
|
213
|
+
skipIfExists: [AGENT_INSTRUCTIONS_FILE, LEGACY_AGENT_INSTRUCTIONS_FILE]
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
templateName: "members-domain.md.template",
|
|
217
|
+
targetPath: "members/NODE.md"
|
|
218
|
+
}
|
|
219
|
+
];
|
|
220
|
+
function installSkill(source, target) {
|
|
221
|
+
copyCanonicalSkill(source, target);
|
|
222
|
+
console.log(" Installed skills/first-tree/ from the bundled first-tree package");
|
|
223
|
+
}
|
|
224
|
+
function renderTemplates(target) {
|
|
225
|
+
const frameworkDir = join(target, FRAMEWORK_ASSET_ROOT);
|
|
226
|
+
for (const { templateName, targetPath, skipIfExists } of TEMPLATE_MAP) {
|
|
227
|
+
const existingPath = (skipIfExists ?? [targetPath]).find((candidate) => existsSync(join(target, candidate)));
|
|
228
|
+
if (existingPath !== void 0) console.log(` Skipped ${targetPath} (found existing ${existingPath})`);
|
|
229
|
+
else if (renderTemplateFile(frameworkDir, templateName, target, targetPath)) console.log(` Created ${targetPath}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function formatTaskList(groups, context) {
|
|
233
|
+
const lines = ["# Context Tree Init\n"];
|
|
234
|
+
if (context?.dedicatedTreeRepo) {
|
|
235
|
+
lines.push("This repository is the dedicated Context Tree. Keep decisions, rationale, cross-domain relationships, and ownership here; keep execution detail in your source repositories.", "");
|
|
236
|
+
if (context.sourceRepoPath) lines.push(`**Bootstrap source repo:** \`${context.sourceRepoPath}\``, "");
|
|
237
|
+
lines.push("When you publish this tree repo, keep it in the same GitHub organization as the source repo unless you have a reason not to.", "");
|
|
238
|
+
}
|
|
239
|
+
lines.push(`**Agent instructions:** Before starting work, analyze the full task list below and identify all information you need from the user. Ask the user for their code repositories or project directories so you can analyze the source yourself — derive project descriptions, domains, and members from the code instead of asking the user to describe them. Collect everything upfront using the **${INTERACTIVE_TOOL}** tool with structured options — present selectable choices (with label and description) so the user can pick instead of typing free-form answers. You may batch up to 4 questions per ${INTERACTIVE_TOOL} call.\n`);
|
|
240
|
+
for (const group of groups) {
|
|
241
|
+
lines.push(`## ${group.group}`);
|
|
242
|
+
for (const task of group.tasks) lines.push(`- [ ] ${task}`);
|
|
243
|
+
lines.push("");
|
|
244
|
+
}
|
|
245
|
+
lines.push("## Verification");
|
|
246
|
+
lines.push("After completing the tasks above, run `context-tree verify` to confirm:");
|
|
247
|
+
lines.push(`- [ ] \`${FRAMEWORK_VERSION}\` exists`);
|
|
248
|
+
lines.push("- [ ] Root NODE.md has valid frontmatter (title, owners)");
|
|
249
|
+
lines.push(`- [ ] \`${AGENT_INSTRUCTIONS_FILE}\` is the only agent instructions file and has framework markers`);
|
|
250
|
+
lines.push("- [ ] `context-tree verify` passes with no errors");
|
|
251
|
+
lines.push("- [ ] At least one member node exists");
|
|
252
|
+
lines.push("");
|
|
253
|
+
lines.push("---");
|
|
254
|
+
lines.push("");
|
|
255
|
+
lines.push(`**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.`);
|
|
256
|
+
lines.push("");
|
|
257
|
+
return lines.join("\n");
|
|
258
|
+
}
|
|
259
|
+
function writeProgress(repo, content) {
|
|
260
|
+
const progressPath = join(repo.root, repo.preferredProgressPath());
|
|
261
|
+
mkdirSync(dirname(progressPath), { recursive: true });
|
|
262
|
+
writeFileSync(progressPath, content);
|
|
263
|
+
}
|
|
264
|
+
function runInit(repo, options) {
|
|
265
|
+
const sourceRepo = repo ?? new Repo();
|
|
266
|
+
const initTarget = resolveInitTarget(sourceRepo, options);
|
|
267
|
+
if (initTarget.ok === false) {
|
|
268
|
+
console.error(`Error: ${initTarget.message}`);
|
|
269
|
+
return 1;
|
|
270
|
+
}
|
|
271
|
+
const r = initTarget.repo;
|
|
272
|
+
const taskListContext = initTarget.dedicatedTreeRepo ? {
|
|
273
|
+
dedicatedTreeRepo: true,
|
|
274
|
+
sourceRepoPath: relativePathFrom(r.root, sourceRepo.root)
|
|
275
|
+
} : void 0;
|
|
276
|
+
if (initTarget.dedicatedTreeRepo) {
|
|
277
|
+
console.log("Recommended workflow: keep the Context Tree in a dedicated repo separate from your source/workspace repo.");
|
|
278
|
+
console.log(` Source repo: ${sourceRepo.root}`);
|
|
279
|
+
console.log(` Tree repo: ${r.root}`);
|
|
280
|
+
if (initTarget.createdGitRepo) console.log(" Initialized a new git repo for the tree.");
|
|
281
|
+
console.log();
|
|
282
|
+
}
|
|
283
|
+
if (!r.hasFramework()) try {
|
|
284
|
+
const sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
|
|
285
|
+
console.log("Installing the framework skill bundled with this first-tree package...");
|
|
286
|
+
console.log("Installing skill and scaffolding...");
|
|
287
|
+
installSkill(sourceRoot, r.root);
|
|
288
|
+
renderTemplates(r.root);
|
|
289
|
+
console.log();
|
|
290
|
+
} catch (err) {
|
|
291
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
292
|
+
console.error(`Error: ${message}`);
|
|
293
|
+
return 1;
|
|
294
|
+
}
|
|
295
|
+
console.log(onboarding_default);
|
|
296
|
+
console.log("---\n");
|
|
297
|
+
const groups = evaluateAll(r);
|
|
298
|
+
if (groups.length === 0) {
|
|
299
|
+
console.log("All checks passed. Your context tree is set up.");
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
const output = formatTaskList(groups, taskListContext);
|
|
303
|
+
console.log(output);
|
|
304
|
+
writeProgress(r, output);
|
|
305
|
+
console.log(`Progress file written to ${r.preferredProgressPath()}`);
|
|
306
|
+
if (initTarget.dedicatedTreeRepo) console.log(`Continue in ${relativePathFrom(sourceRepo.root, r.root)} and keep your source repos available as additional working directories when you populate the tree.`);
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
function parseInitArgs(args) {
|
|
310
|
+
const parsed = {};
|
|
311
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
312
|
+
const arg = args[index];
|
|
313
|
+
switch (arg) {
|
|
314
|
+
case "--here":
|
|
315
|
+
parsed.here = true;
|
|
316
|
+
break;
|
|
317
|
+
case "--tree-name": {
|
|
318
|
+
const value = args[index + 1];
|
|
319
|
+
if (!value) return { error: "Missing value for --tree-name" };
|
|
320
|
+
parsed.treeName = value;
|
|
321
|
+
index += 1;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case "--tree-path": {
|
|
325
|
+
const value = args[index + 1];
|
|
326
|
+
if (!value) return { error: "Missing value for --tree-path" };
|
|
327
|
+
parsed.treePath = value;
|
|
328
|
+
index += 1;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
default: return { error: `Unknown init option: ${arg}` };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (parsed.here && parsed.treeName) return { error: "Cannot combine --here with --tree-name" };
|
|
335
|
+
if (parsed.here && parsed.treePath) return { error: "Cannot combine --here with --tree-path" };
|
|
336
|
+
if (parsed.treeName && parsed.treePath) return { error: "Cannot combine --tree-name with --tree-path" };
|
|
337
|
+
return parsed;
|
|
338
|
+
}
|
|
339
|
+
function runInitCli(args = []) {
|
|
340
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
341
|
+
console.log(INIT_USAGE);
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
const parsed = parseInitArgs(args);
|
|
345
|
+
if ("error" in parsed) {
|
|
346
|
+
console.error(parsed.error);
|
|
347
|
+
console.log(INIT_USAGE);
|
|
348
|
+
return 1;
|
|
349
|
+
}
|
|
350
|
+
return runInit(void 0, parsed);
|
|
351
|
+
}
|
|
352
|
+
function resolveInitTarget(sourceRepo, options) {
|
|
353
|
+
if (!sourceRepo.isGitRepo()) return {
|
|
354
|
+
ok: false,
|
|
355
|
+
message: "not a git repository. Run this from your source/workspace repo, or create a dedicated tree repo first:\n git init\n context-tree init --here"
|
|
356
|
+
};
|
|
357
|
+
const targetRoot = determineTargetRoot(sourceRepo, options);
|
|
358
|
+
const dedicatedTreeRepo = targetRoot !== sourceRepo.root;
|
|
359
|
+
let createdGitRepo = false;
|
|
360
|
+
try {
|
|
361
|
+
createdGitRepo = ensureGitRepo(targetRoot, options?.gitInitializer);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
return {
|
|
364
|
+
ok: false,
|
|
365
|
+
message: err instanceof Error ? err.message : "unknown error"
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
ok: true,
|
|
370
|
+
createdGitRepo,
|
|
371
|
+
dedicatedTreeRepo,
|
|
372
|
+
repo: new Repo(targetRoot)
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function determineTargetRoot(sourceRepo, options) {
|
|
376
|
+
if (options?.treePath) return resolve(options.currentCwd ?? process.cwd(), options.treePath);
|
|
377
|
+
if (options?.here) return sourceRepo.root;
|
|
378
|
+
if (options?.treeName) return join(dirname(sourceRepo.root), options.treeName);
|
|
379
|
+
if (sourceRepo.looksLikeTreeRepo() || sourceRepo.isLikelyEmptyRepo() || !sourceRepo.isLikelySourceRepo()) return sourceRepo.root;
|
|
380
|
+
return join(dirname(sourceRepo.root), `${sourceRepo.repoName()}-context`);
|
|
381
|
+
}
|
|
382
|
+
function ensureGitRepo(targetRoot, gitInitializer) {
|
|
383
|
+
if (existsSync(targetRoot)) {
|
|
384
|
+
if (!statSync(targetRoot).isDirectory()) throw new Error(`Target path is not a directory: ${targetRoot}`);
|
|
385
|
+
if (new Repo(targetRoot).isGitRepo()) return false;
|
|
386
|
+
if (readdirSync(targetRoot).length !== 0) throw new Error(`Target path exists and is not a git repository: ${targetRoot}. Run \`git init\` there first or choose a different tree path.`);
|
|
387
|
+
} else mkdirSync(targetRoot, { recursive: true });
|
|
388
|
+
(gitInitializer ?? defaultGitInitializer)(targetRoot);
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
function defaultGitInitializer(root) {
|
|
392
|
+
execFileSync("git", ["init"], {
|
|
393
|
+
cwd: root,
|
|
394
|
+
stdio: "ignore"
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function relativePathFrom(from, to) {
|
|
398
|
+
const rel = relative(from, to);
|
|
399
|
+
if (rel === "") return ".";
|
|
400
|
+
return rel.startsWith("..") ? rel : `./${rel}`;
|
|
401
|
+
}
|
|
402
|
+
//#endregion
|
|
403
|
+
export { runInitCli as runInit };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { g as SKILL_ROOT, m as LEGACY_SKILL_ROOT } from "./repo-DY57bMqr.js";
|
|
2
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
//#region skills/first-tree/engine/runtime/installer.ts
|
|
6
|
+
function resolveBundledPackageRoot(startUrl = import.meta.url) {
|
|
7
|
+
let dir = dirname(fileURLToPath(startUrl));
|
|
8
|
+
while (true) {
|
|
9
|
+
if (existsSync(join(dir, "package.json")) && existsSync(join(dir, SKILL_ROOT, "SKILL.md"))) return dir;
|
|
10
|
+
const parent = dirname(dir);
|
|
11
|
+
if (parent === dir) break;
|
|
12
|
+
dir = parent;
|
|
13
|
+
}
|
|
14
|
+
throw new Error("Could not locate the bundled `first-tree` package root. Reinstall the package and try again.");
|
|
15
|
+
}
|
|
16
|
+
function resolveCanonicalSkillRoot(sourceRoot) {
|
|
17
|
+
const directSkillRoot = sourceRoot;
|
|
18
|
+
if (existsSync(join(directSkillRoot, "SKILL.md")) && existsSync(join(directSkillRoot, "assets", "framework", "VERSION"))) return directSkillRoot;
|
|
19
|
+
const nestedSkillRoot = join(sourceRoot, SKILL_ROOT);
|
|
20
|
+
if (existsSync(join(nestedSkillRoot, "SKILL.md")) && existsSync(join(nestedSkillRoot, "assets", "framework", "VERSION"))) return nestedSkillRoot;
|
|
21
|
+
throw new Error(`Canonical skill not found under ${sourceRoot}. Reinstall the \`first-tree\` package and try again.`);
|
|
22
|
+
}
|
|
23
|
+
function copyCanonicalSkill(sourceRoot, targetRoot) {
|
|
24
|
+
const src = resolveCanonicalSkillRoot(sourceRoot);
|
|
25
|
+
const dst = join(targetRoot, SKILL_ROOT);
|
|
26
|
+
const legacyDst = join(targetRoot, LEGACY_SKILL_ROOT);
|
|
27
|
+
if (existsSync(dst)) rmSync(dst, {
|
|
28
|
+
recursive: true,
|
|
29
|
+
force: true
|
|
30
|
+
});
|
|
31
|
+
if (legacyDst !== dst && existsSync(legacyDst)) rmSync(legacyDst, {
|
|
32
|
+
recursive: true,
|
|
33
|
+
force: true
|
|
34
|
+
});
|
|
35
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
36
|
+
cpSync(src, dst, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
function renderTemplateFile(frameworkRoot, templateName, targetRoot, targetPath) {
|
|
39
|
+
const src = join(frameworkRoot, "templates", templateName);
|
|
40
|
+
const dst = join(targetRoot, targetPath);
|
|
41
|
+
if (existsSync(dst) || !existsSync(src)) return false;
|
|
42
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
43
|
+
copyFileSync(src, dst);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { resolveCanonicalSkillRoot as i, renderTemplateFile as n, resolveBundledPackageRoot as r, copyCanonicalSkill as t };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region skills/first-tree/references/onboarding.md
|
|
2
|
+
var onboarding_default = "# Context Tree Onboarding\n\nYou are setting up a **Context Tree** — the living source of truth for an organization. This document tells you what it is and how to bootstrap one.\n\n---\n\n## What Is a Context Tree\n\nA Context Tree is a Git repository where every directory is a **domain** and every file is a **node**. Each node captures decisions, designs, and cross-domain relationships — the knowledge that would otherwise scatter across PRs, documents, and people's heads.\n\nKey properties:\n\n- **Nodes are markdown files.** Each directory has a `NODE.md` that describes the domain. Leaf `.md` files capture specific decisions or designs.\n- **Every node has an owner.** Declared in YAML frontmatter. Owners approve changes to their nodes.\n- **Organized by concern, not by repo or team.** An agent working on \"add SSO\" finds all auth context in one place — not split across 4 repos.\n- **The tree is never a snapshot — it's the current state.** When decisions change, the tree updates. Stale nodes are bugs.\n\n### Frontmatter Format\n\nEvery node has frontmatter:\n\n```yaml\n---\ntitle: \"Auth Architecture\"\nowners: [alice, bob]\nsoft_links: [/infrastructure/deployments]\n---\n```\n\n- `owners` — who can approve changes. `owners: []` inherits from parent. `owners: [*]` means anyone.\n- `soft_links` — cross-references to related nodes in other domains.\n\n### What Belongs in the Tree\n\nInformation an agent needs to **decide** on an approach — not to execute it.\n\n**Yes:** \"Auth spans 4 repos: backend issues JWTs, frontend uses Better Auth, extension uses OAuth popup, desktop uses localhost callback.\"\n\n**No:** The function signature of `auth_service.verify()` — that's in the code.\n\n---\n\n## Four Principles\n\n1. **Source of truth for decisions, not execution.** The tree captures the *what* and *why*. Execution details stay in source systems.\n2. **Agents are first-class participants.** The tree is designed for agents to navigate and update.\n3. **Transparency by default.** Reading is open to all. Writing requires owner approval.\n4. **Git-native.** Nodes are files, domains are directories. History, ownership, and review follow Git conventions.\n\n---\n\n## How to Set Up a Context Tree\n\n### Prerequisites\n\n- A source/workspace Git repository, or an already-created dedicated tree repo\n- Node.js 18+\n- The npm package is `first-tree`, the installed CLI command is\n `context-tree`, and the installed skill directory in the tree is\n `skills/first-tree/`\n- Use `npx first-tree init` for one-off runs, or `npm install -g first-tree`\n to add the `context-tree` command to your PATH\n\n### Step 1: Initialize\n\nRecommended workflow: run `context-tree init` from your source or workspace repo.\nThe CLI will create a sibling dedicated tree repo named `<repo>-context` by\ndefault and install the framework there.\n\n```bash\ncd my-org\ncontext-tree init\ncd ../my-org-context\n```\n\nIf you already created a dedicated tree repo manually, initialize it in place:\n\n```bash\nmkdir my-org-context && cd my-org-context\ngit init\ncontext-tree init --here\n```\n\nEither way, the framework installs into `skills/first-tree/`, renders\nscaffolding (`NODE.md`, `AGENTS.md`, `members/NODE.md`), and generates a task\nlist in `skills/first-tree/progress.md`.\n\nPublishing tip: keep the tree repo in the same GitHub organization as the\nsource repo unless you have a reason not to.\n\n### Step 2: Work Through the Task List\n\nRead `skills/first-tree/progress.md`. It contains a checklist tailored to the current state of the repo. Complete each task:\n\n- Fill in `NODE.md` with your organization name, owners, and domains\n- Add project-specific instructions to `AGENTS.md` below the framework markers\n- Create member nodes under `members/`\n- Optionally configure agent integration (e.g., Claude Code session hooks)\n- Copy validation workflows from `skills/first-tree/assets/framework/workflows/` to `.github/workflows/`\n\nAs you complete each task, check it off in `skills/first-tree/progress.md` by changing `- [ ]` to `- [x]`.\n\n### Step 3: Verify\n\n```bash\ncontext-tree verify\n```\n\nOr, from your source/workspace repo:\n\n```bash\ncontext-tree verify --tree-path ../my-org-context\n```\n\nThis fails if any items in `skills/first-tree/progress.md` remain unchecked, and runs deterministic checks (valid frontmatter, node structure, member nodes exist).\n\n### Step 4: Design Your Domains\n\nCreate top-level directories for your organization's primary concerns. Each needs a `NODE.md`:\n\n```\nmy-org-tree/\n NODE.md # root — lists all domains\n engineering/\n NODE.md # decisions about architecture, infra, tooling\n product/\n NODE.md # strategy, roadmap, user research\n marketing/\n NODE.md # positioning, campaigns\n members/\n NODE.md # team members and agents\n alice/\n NODE.md # individual member node\n```\n\n### Step 5: Populate from Existing Work\n\nFor each domain, extract knowledge from existing repos, docs, and systems:\n\n- Decisions and their rationale\n- Cross-domain relationships and dependencies\n- Constraints that aren't obvious from the code\n\nThe tree doesn't duplicate source code — it captures what connects things and why they were built that way.\n\n---\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `context-tree init` | Create or refresh a dedicated tree repo. By default, running in a source/workspace repo creates a sibling `<repo>-context`; use `--here` to initialize the current repo in place. |\n| `context-tree verify` | Check the installed progress file for unchecked items + run deterministic validation. Use `--tree-path` when invoking from another working directory. |\n| `context-tree upgrade` | Refresh the installed framework skill from the currently running `first-tree` npm package and generate follow-up tasks. Use `--tree-path` when invoking from another working directory. |\n| `context-tree help onboarding` | Print this onboarding guide. |\n\n---\n\n## Upgrading the Framework\n\nWhen the framework updates:\n\n```bash\ncontext-tree upgrade\n```\n\n`context-tree upgrade` refreshes `skills/first-tree/` from the\nskill bundled with the currently running `first-tree` npm package, preserves your\ntree content, and generates follow-up tasks in\n`skills/first-tree/progress.md`.\n\nIf your repo still uses the older `skills/first-tree-cli-framework/` path,\n`context-tree upgrade` will migrate it to `skills/first-tree/` first.\n\nTo pick up a newer framework release, first run a newer package version, for\nexample `npx first-tree@latest upgrade`, or update your global `first-tree`\ninstall before running `context-tree upgrade`.\n\n---\n\n## Further Reading\n\n- `skills/first-tree/references/principles.md` — Core principles with detailed examples\n- `skills/first-tree/references/ownership-and-naming.md` — How nodes are named and owned\n- `AGENTS.md` in your tree — The before/during/after workflow for every task\n";
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region skills/first-tree/engine/onboarding.ts
|
|
5
|
+
function runOnboarding(output = console.log) {
|
|
6
|
+
output(onboarding_default);
|
|
7
|
+
return 0;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { onboarding_default as n, runOnboarding as t };
|