first-tree 0.0.3 → 0.0.5
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 +78 -27
- package/dist/cli.js +28 -13
- package/dist/{help-xEI-s9iN.js → help-5-WG9QFm.js} +1 -1
- package/dist/{init-DtOjj0wc.js → init-CAq0Uhq6.js} +187 -25
- package/dist/{installer-rcZpGLnM.js → installer-UgNasLjl.js} +20 -16
- package/dist/onboarding-3zYUeYQb.js +2 -0
- package/dist/onboarding-Dd63N-V1.js +10 -0
- package/dist/repo-DkR12VUv.js +369 -0
- package/dist/upgrade-DYzuvv1k.js +140 -0
- package/dist/{verify-CxN6JiV9.js → verify-C0IUSkMZ.js} +66 -6
- package/package.json +12 -10
- package/skills/first-tree/SKILL.md +18 -10
- package/skills/first-tree/assets/framework/VERSION +1 -1
- package/skills/first-tree/assets/framework/examples/claude-code/README.md +2 -2
- package/skills/first-tree/assets/framework/examples/claude-code/settings.json +1 -1
- package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +1 -1
- package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +0 -0
- package/skills/first-tree/assets/framework/helpers/run-review.ts +17 -3
- package/skills/first-tree/assets/framework/templates/{agent.md.template → agents.md.template} +3 -2
- package/skills/first-tree/assets/framework/templates/members-domain.md.template +1 -1
- package/skills/first-tree/assets/framework/templates/root-node.md.template +9 -6
- package/skills/first-tree/assets/framework/workflows/codeowners.yml +1 -1
- package/skills/first-tree/assets/framework/workflows/pr-review.yml +1 -1
- 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 +288 -18
- package/skills/first-tree/engine/repo.ts +220 -11
- package/skills/first-tree/engine/rules/agent-instructions.ts +29 -7
- package/skills/first-tree/engine/rules/agent-integration.ts +3 -1
- package/skills/first-tree/engine/rules/framework.ts +2 -2
- package/skills/first-tree/engine/runtime/adapters.ts +6 -2
- package/skills/first-tree/engine/runtime/asset-loader.ts +143 -4
- package/skills/first-tree/engine/runtime/installer.ts +18 -12
- package/skills/first-tree/engine/upgrade.ts +99 -15
- 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 +57 -24
- package/skills/first-tree/references/source-map.md +3 -3
- package/skills/first-tree/references/upgrade-contract.md +62 -27
- package/skills/first-tree/scripts/check-skill-sync.sh +1 -1
- package/skills/first-tree/scripts/quick_validate.py +0 -0
- package/skills/first-tree/scripts/run-local-cli.sh +0 -0
- package/skills/first-tree/tests/asset-loader.test.ts +23 -1
- package/skills/first-tree/tests/helpers.ts +51 -7
- package/skills/first-tree/tests/init.test.ts +113 -8
- package/skills/first-tree/tests/repo.test.ts +113 -9
- package/skills/first-tree/tests/rules.test.ts +35 -14
- package/skills/first-tree/tests/skill-artifacts.test.ts +10 -0
- package/skills/first-tree/tests/thin-cli.test.ts +52 -7
- package/skills/first-tree/tests/upgrade.test.ts +39 -6
- package/skills/first-tree/tests/verify.test.ts +109 -10
- package/dist/onboarding-6Fr5Gkrk.js +0 -2
- package/dist/onboarding-B9zPGvvG.js +0 -10
- package/dist/repo-BTJG8BU1.js +0 -187
- package/dist/upgrade-COGgI7Rj.js +0 -96
package/README.md
CHANGED
|
@@ -1,56 +1,96 @@
|
|
|
1
1
|
# first-tree
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`first-tree` skill.
|
|
3
|
+
`first-tree` publishes the `context-tree` CLI and bundles the canonical
|
|
4
|
+
`first-tree` skill used to bootstrap and maintain Context Tree repos.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## Install And Run
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
- The installed CLI command is `context-tree`.
|
|
10
|
-
- The installed skill directory inside a user tree is `skills/first-tree/`.
|
|
11
|
-
- When maintainer docs say "the `first-tree` skill", they mean that bundled
|
|
12
|
-
skill directory, not the npm package name.
|
|
13
|
-
- `npx first-tree init` is the quickest one-off entrypoint.
|
|
14
|
-
- `npm install -g first-tree` adds `context-tree` to your PATH for repeated
|
|
15
|
-
use.
|
|
8
|
+
- One-off use without installing globally:
|
|
16
9
|
|
|
17
|
-
|
|
10
|
+
```bash
|
|
11
|
+
npx first-tree init
|
|
12
|
+
```
|
|
18
13
|
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
14
|
+
- Global install:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g first-tree
|
|
18
|
+
context-tree init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- Show the installed CLI version:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
context-tree --version
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- Show the command list:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
context-tree --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Although the npm package is named `first-tree`, the installed CLI command is
|
|
34
|
+
`context-tree`.
|
|
25
35
|
|
|
26
36
|
## Quick Start
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
Recommended workflow: start from your source or workspace repo and let
|
|
39
|
+
`context-tree init` create a sibling dedicated tree repo.
|
|
29
40
|
|
|
30
41
|
```bash
|
|
31
|
-
|
|
32
|
-
git init
|
|
42
|
+
cd my-app
|
|
33
43
|
npx first-tree init
|
|
44
|
+
cd ../my-app-context
|
|
34
45
|
```
|
|
35
46
|
|
|
36
|
-
If you already
|
|
47
|
+
If you already created a dedicated tree repo yourself, initialize it in place:
|
|
37
48
|
|
|
38
49
|
```bash
|
|
39
|
-
context-
|
|
50
|
+
mkdir my-org-context && cd my-org-context
|
|
51
|
+
git init
|
|
52
|
+
context-tree init --here
|
|
40
53
|
```
|
|
41
54
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
- `context-tree init` installs `.agents/skills/first-tree/` and
|
|
56
|
+
`.claude/skills/first-tree/`, creates `NODE.md`, `AGENTS.md`,
|
|
57
|
+
`members/NODE.md`, and writes a checklist to
|
|
58
|
+
`.agents/skills/first-tree/progress.md`.
|
|
59
|
+
- `context-tree verify` checks both the progress checklist and deterministic
|
|
60
|
+
tree validation. It is expected to fail until the required onboarding tasks
|
|
61
|
+
are complete.
|
|
62
|
+
- `context-tree upgrade` refreshes the installed skill from the currently
|
|
63
|
+
running `first-tree` npm package. To force the newest published package for a
|
|
64
|
+
one-off upgrade, run `npx first-tree@latest upgrade`.
|
|
65
|
+
|
|
66
|
+
The package carries the bundled canonical skill, so `init` and `upgrade`
|
|
67
|
+
install from the package payload instead of cloning this source repo at
|
|
68
|
+
runtime.
|
|
45
69
|
|
|
46
70
|
## Commands
|
|
47
71
|
|
|
48
72
|
| Command | What it does |
|
|
49
73
|
| --- | --- |
|
|
50
|
-
| `context-tree init` |
|
|
74
|
+
| `context-tree init` | Create or refresh a dedicated context tree repo; use `--here` to initialize the current repo in place |
|
|
51
75
|
| `context-tree verify` | Run verification checks against the current tree |
|
|
52
76
|
| `context-tree upgrade` | Refresh the installed skill from the current `first-tree` npm package and write follow-up tasks |
|
|
53
77
|
| `context-tree help onboarding` | Print the onboarding guide |
|
|
78
|
+
| `context-tree --help` | Show the available commands |
|
|
79
|
+
| `context-tree --version` | Print the installed CLI version |
|
|
80
|
+
|
|
81
|
+
## Package Name vs Command
|
|
82
|
+
|
|
83
|
+
- The npm package is `first-tree`.
|
|
84
|
+
- The installed CLI command is `context-tree`.
|
|
85
|
+
- The installed skill directories inside a user tree are
|
|
86
|
+
`.agents/skills/first-tree/` and `.claude/skills/first-tree/`.
|
|
87
|
+
- The published package keeps its bundled canonical source under
|
|
88
|
+
`skills/first-tree/`.
|
|
89
|
+
- When maintainer docs say "the `first-tree` skill", they mean that bundled
|
|
90
|
+
skill directory, not the npm package name.
|
|
91
|
+
- `npx first-tree init` is the quickest one-off entrypoint.
|
|
92
|
+
- `npm install -g first-tree` adds `context-tree` to your PATH for repeated
|
|
93
|
+
use.
|
|
54
94
|
|
|
55
95
|
## Runtime And Maintainer Prerequisites
|
|
56
96
|
|
|
@@ -58,6 +98,17 @@ instead of cloning this source repo at runtime.
|
|
|
58
98
|
- This source repo: use Node.js 22 and pnpm 10 to match CI and the checked-in
|
|
59
99
|
package manager version.
|
|
60
100
|
|
|
101
|
+
## What This Repo Ships
|
|
102
|
+
|
|
103
|
+
- `src/` keeps the thin CLI shell that parses commands and dispatches to the
|
|
104
|
+
bundled skill.
|
|
105
|
+
- `skills/first-tree/` is the canonical source for framework behavior, shipped
|
|
106
|
+
templates, maintainer references, and validation logic.
|
|
107
|
+
- `context-tree init` installs that bundled skill into `.agents/skills/first-tree/`
|
|
108
|
+
and `.claude/skills/first-tree/` inside user repos.
|
|
109
|
+
- `evals/` is maintainer-only developer tooling for the source repo. It is
|
|
110
|
+
intentionally not part of the published package.
|
|
111
|
+
|
|
61
112
|
## Developing This Repo
|
|
62
113
|
|
|
63
114
|
Run these commands from the repo root:
|
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
//#region src/cli.ts
|
|
4
5
|
const USAGE = `usage: context-tree <command>
|
|
5
6
|
|
|
6
7
|
New to context-tree? Run \`context-tree help onboarding\` first.
|
|
7
8
|
|
|
8
9
|
Commands:
|
|
9
|
-
init
|
|
10
|
-
verify Run verification checks against
|
|
11
|
-
upgrade Refresh the installed skill
|
|
10
|
+
init Create or refresh a dedicated context tree repo
|
|
11
|
+
verify Run verification checks against a tree repo
|
|
12
|
+
upgrade Refresh the installed skill in a tree repo
|
|
12
13
|
help Show help for a topic (e.g. \`help onboarding\`)
|
|
13
14
|
|
|
14
15
|
Options:
|
|
15
16
|
--help Show this help message
|
|
16
17
|
--version Show version number
|
|
18
|
+
|
|
19
|
+
Common examples:
|
|
20
|
+
context-tree init
|
|
21
|
+
context-tree init --here
|
|
22
|
+
context-tree verify --tree-path ../my-org-context
|
|
23
|
+
context-tree upgrade --tree-path ../my-org-context
|
|
17
24
|
`;
|
|
25
|
+
function isDirectExecution(argv1, metaUrl = import.meta.url) {
|
|
26
|
+
if (argv1 === void 0) return false;
|
|
27
|
+
try {
|
|
28
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(metaUrl));
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
18
33
|
async function runCli(args, output = console.log) {
|
|
19
34
|
const write = (text) => output(text);
|
|
20
35
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
@@ -29,18 +44,18 @@ async function runCli(args, output = console.log) {
|
|
|
29
44
|
const command = args[0];
|
|
30
45
|
switch (command) {
|
|
31
46
|
case "init": {
|
|
32
|
-
const { runInit } = await import("./init-
|
|
33
|
-
return runInit();
|
|
47
|
+
const { runInit } = await import("./init-CAq0Uhq6.js");
|
|
48
|
+
return runInit(args.slice(1));
|
|
34
49
|
}
|
|
35
50
|
case "verify": {
|
|
36
|
-
const { runVerify } = await import("./verify-
|
|
37
|
-
return runVerify();
|
|
51
|
+
const { runVerify } = await import("./verify-C0IUSkMZ.js");
|
|
52
|
+
return runVerify(args.slice(1));
|
|
38
53
|
}
|
|
39
54
|
case "upgrade": {
|
|
40
|
-
const { runUpgrade } = await import("./upgrade-
|
|
41
|
-
return runUpgrade();
|
|
55
|
+
const { runUpgrade } = await import("./upgrade-DYzuvv1k.js");
|
|
56
|
+
return runUpgrade(args.slice(1));
|
|
42
57
|
}
|
|
43
|
-
case "help": return (await import("./help-
|
|
58
|
+
case "help": return (await import("./help-5-WG9QFm.js")).runHelp(args.slice(1), write);
|
|
44
59
|
default:
|
|
45
60
|
write(`Unknown command: ${command}`);
|
|
46
61
|
write(USAGE);
|
|
@@ -50,6 +65,6 @@ async function runCli(args, output = console.log) {
|
|
|
50
65
|
async function main() {
|
|
51
66
|
return runCli(process.argv.slice(2));
|
|
52
67
|
}
|
|
53
|
-
if (
|
|
68
|
+
if (isDirectExecution(process.argv[1])) main().then((code) => process.exit(code));
|
|
54
69
|
//#endregion
|
|
55
|
-
export { USAGE, runCli };
|
|
70
|
+
export { USAGE, isDirectExecution, runCli };
|
|
@@ -12,7 +12,7 @@ async function runHelp(args, output = console.log) {
|
|
|
12
12
|
}
|
|
13
13
|
switch (topic) {
|
|
14
14
|
case "onboarding": {
|
|
15
|
-
const { runOnboarding } = await import("./onboarding-
|
|
15
|
+
const { runOnboarding } = await import("./onboarding-3zYUeYQb.js");
|
|
16
16
|
return runOnboarding(output);
|
|
17
17
|
}
|
|
18
18
|
default:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as onboarding_default } from "./onboarding-
|
|
3
|
-
import { n as renderTemplateFile, r as resolveBundledPackageRoot, t as copyCanonicalSkill } from "./installer-
|
|
1
|
+
import { T as installedSkillRootsDisplay, b as LEGACY_SKILL_EXAMPLES_DIR, c as FRAMEWORK_ASSET_ROOT, d as FRAMEWORK_VERSION, f as FRAMEWORK_WORKFLOWS_DIR, g as LEGACY_EXAMPLES_DIR, h as LEGACY_AGENT_INSTRUCTIONS_FILE, i as AGENT_INSTRUCTIONS_TEMPLATE, l as FRAMEWORK_EXAMPLES_DIR, n as Repo, o as CLAUDE_FRAMEWORK_EXAMPLES_DIR, p as INSTALLED_PROGRESS, r as AGENT_INSTRUCTIONS_FILE, t as FRAMEWORK_END_MARKER, u as FRAMEWORK_TEMPLATES_DIR, v as LEGACY_REPO_SKILL_EXAMPLES_DIR } from "./repo-DkR12VUv.js";
|
|
2
|
+
import { n as onboarding_default } from "./onboarding-Dd63N-V1.js";
|
|
3
|
+
import { n as renderTemplateFile, r as resolveBundledPackageRoot, t as copyCanonicalSkill } from "./installer-UgNasLjl.js";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
-
import {
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
6
7
|
//#region \0rolldown/runtime.js
|
|
7
8
|
var __defProp = Object.defineProperty;
|
|
8
9
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -19,12 +20,24 @@ var __exportAll = (all, no_symbols) => {
|
|
|
19
20
|
var agent_instructions_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$6 });
|
|
20
21
|
function evaluate$6(repo) {
|
|
21
22
|
const tasks = [];
|
|
22
|
-
|
|
23
|
-
|
|
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`);
|
|
24
37
|
else {
|
|
25
|
-
const afterMarker = (repo.
|
|
38
|
+
const afterMarker = (repo.readAgentInstructions() ?? "").split(FRAMEWORK_END_MARKER);
|
|
26
39
|
if (afterMarker.length > 1) {
|
|
27
|
-
if (afterMarker[1].trim().split("\n").filter((l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("<!--")).length === 0) tasks.push(
|
|
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}`);
|
|
28
41
|
}
|
|
29
42
|
}
|
|
30
43
|
return {
|
|
@@ -34,12 +47,24 @@ function evaluate$6(repo) {
|
|
|
34
47
|
};
|
|
35
48
|
}
|
|
36
49
|
//#endregion
|
|
50
|
+
//#region skills/first-tree/engine/runtime/adapters.ts
|
|
51
|
+
function claudeCodeExampleCandidates() {
|
|
52
|
+
return [
|
|
53
|
+
join(CLAUDE_FRAMEWORK_EXAMPLES_DIR, "claude-code"),
|
|
54
|
+
join(FRAMEWORK_EXAMPLES_DIR, "claude-code"),
|
|
55
|
+
join(LEGACY_REPO_SKILL_EXAMPLES_DIR, "claude-code"),
|
|
56
|
+
join(LEGACY_SKILL_EXAMPLES_DIR, "claude-code"),
|
|
57
|
+
join(LEGACY_EXAMPLES_DIR, "claude-code")
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
37
61
|
//#region skills/first-tree/engine/rules/agent-integration.ts
|
|
38
62
|
var agent_integration_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$5 });
|
|
39
63
|
function evaluate$5(repo) {
|
|
40
64
|
const tasks = [];
|
|
65
|
+
const [claudeExamplePath] = claudeCodeExampleCandidates();
|
|
41
66
|
if (repo.pathExists(".claude/settings.json")) {
|
|
42
|
-
if (!repo.fileContains(".claude/settings.json", "inject-tree-context")) tasks.push(`Add SessionStart hook to \`.claude/settings.json\` (see \`${
|
|
67
|
+
if (!repo.fileContains(".claude/settings.json", "inject-tree-context")) tasks.push(`Add SessionStart hook to \`.claude/settings.json\` (see \`${claudeExamplePath}/\`)`);
|
|
43
68
|
} 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.`);
|
|
44
69
|
return {
|
|
45
70
|
group: "Agent Integration",
|
|
@@ -93,7 +118,7 @@ If (1): ask the user to provide the key, then run \`gh secret set\` with the sec
|
|
|
93
118
|
var framework_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$3 });
|
|
94
119
|
function evaluate$3(repo) {
|
|
95
120
|
const tasks = [];
|
|
96
|
-
if (!repo.hasFramework()) tasks.push(
|
|
121
|
+
if (!repo.hasFramework()) tasks.push(`${installedSkillRootsDisplay()} not found — run \`context-tree init\` to install the framework skill bundled with the current \`first-tree\` package`);
|
|
97
122
|
return {
|
|
98
123
|
group: "Framework",
|
|
99
124
|
order: 1,
|
|
@@ -178,22 +203,52 @@ function evaluateAll(repo) {
|
|
|
178
203
|
* all generated task text at once.
|
|
179
204
|
*/
|
|
180
205
|
const INTERACTIVE_TOOL = "AskUserQuestion";
|
|
206
|
+
const INIT_USAGE = `usage: context-tree init [--here] [--tree-name NAME] [--tree-path PATH]
|
|
207
|
+
|
|
208
|
+
By default, running \`context-tree init\` inside a source or workspace repo creates
|
|
209
|
+
a sibling dedicated tree repo named \`<repo>-context\`.
|
|
210
|
+
|
|
211
|
+
Options:
|
|
212
|
+
--here Initialize the current repo in place
|
|
213
|
+
--tree-name NAME Name the dedicated sibling tree repo to create
|
|
214
|
+
--tree-path PATH Use an explicit tree repo path
|
|
215
|
+
--help Show this help message
|
|
216
|
+
`;
|
|
181
217
|
const TEMPLATE_MAP = [
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
218
|
+
{
|
|
219
|
+
templateName: "root-node.md.template",
|
|
220
|
+
targetPath: "NODE.md"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
templateName: AGENT_INSTRUCTIONS_TEMPLATE,
|
|
224
|
+
targetPath: AGENT_INSTRUCTIONS_FILE,
|
|
225
|
+
skipIfExists: [AGENT_INSTRUCTIONS_FILE, LEGACY_AGENT_INSTRUCTIONS_FILE]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
templateName: "members-domain.md.template",
|
|
229
|
+
targetPath: "members/NODE.md"
|
|
230
|
+
}
|
|
185
231
|
];
|
|
186
232
|
function installSkill(source, target) {
|
|
187
233
|
copyCanonicalSkill(source, target);
|
|
188
|
-
console.log(
|
|
234
|
+
console.log(` Installed ${installedSkillRootsDisplay()} from the bundled first-tree package`);
|
|
189
235
|
}
|
|
190
236
|
function renderTemplates(target) {
|
|
191
237
|
const frameworkDir = join(target, FRAMEWORK_ASSET_ROOT);
|
|
192
|
-
for (const
|
|
193
|
-
|
|
238
|
+
for (const { templateName, targetPath, skipIfExists } of TEMPLATE_MAP) {
|
|
239
|
+
const existingPath = (skipIfExists ?? [targetPath]).find((candidate) => existsSync(join(target, candidate)));
|
|
240
|
+
if (existingPath !== void 0) console.log(` Skipped ${targetPath} (found existing ${existingPath})`);
|
|
241
|
+
else if (renderTemplateFile(frameworkDir, templateName, target, targetPath)) console.log(` Created ${targetPath}`);
|
|
242
|
+
}
|
|
194
243
|
}
|
|
195
|
-
function formatTaskList(groups) {
|
|
196
|
-
const lines = ["# Context Tree Init\n"
|
|
244
|
+
function formatTaskList(groups, context) {
|
|
245
|
+
const lines = ["# Context Tree Init\n"];
|
|
246
|
+
if (context?.dedicatedTreeRepo) {
|
|
247
|
+
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.", "");
|
|
248
|
+
if (context.sourceRepoPath) lines.push(`**Bootstrap source repo:** \`${context.sourceRepoPath}\``, "");
|
|
249
|
+
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.", "");
|
|
250
|
+
}
|
|
251
|
+
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`);
|
|
197
252
|
for (const group of groups) {
|
|
198
253
|
lines.push(`## ${group.group}`);
|
|
199
254
|
for (const task of group.tasks) lines.push(`- [ ] ${task}`);
|
|
@@ -203,7 +258,7 @@ function formatTaskList(groups) {
|
|
|
203
258
|
lines.push("After completing the tasks above, run `context-tree verify` to confirm:");
|
|
204
259
|
lines.push(`- [ ] \`${FRAMEWORK_VERSION}\` exists`);
|
|
205
260
|
lines.push("- [ ] Root NODE.md has valid frontmatter (title, owners)");
|
|
206
|
-
lines.push(
|
|
261
|
+
lines.push(`- [ ] \`${AGENT_INSTRUCTIONS_FILE}\` is the only agent instructions file and has framework markers`);
|
|
207
262
|
lines.push("- [ ] `context-tree verify` passes with no errors");
|
|
208
263
|
lines.push("- [ ] At least one member node exists");
|
|
209
264
|
lines.push("");
|
|
@@ -219,12 +274,25 @@ function writeProgress(repo, content) {
|
|
|
219
274
|
writeFileSync(progressPath, content);
|
|
220
275
|
}
|
|
221
276
|
function runInit(repo, options) {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
277
|
+
const sourceRepo = repo ?? new Repo();
|
|
278
|
+
const initTarget = resolveInitTarget(sourceRepo, options);
|
|
279
|
+
if (initTarget.ok === false) {
|
|
280
|
+
console.error(`Error: ${initTarget.message}`);
|
|
225
281
|
return 1;
|
|
226
282
|
}
|
|
227
|
-
|
|
283
|
+
const r = initTarget.repo;
|
|
284
|
+
const taskListContext = initTarget.dedicatedTreeRepo ? {
|
|
285
|
+
dedicatedTreeRepo: true,
|
|
286
|
+
sourceRepoPath: relativePathFrom(r.root, sourceRepo.root)
|
|
287
|
+
} : void 0;
|
|
288
|
+
if (initTarget.dedicatedTreeRepo) {
|
|
289
|
+
console.log("Recommended workflow: keep the Context Tree in a dedicated repo separate from your source/workspace repo.");
|
|
290
|
+
console.log(` Source repo: ${sourceRepo.root}`);
|
|
291
|
+
console.log(` Tree repo: ${r.root}`);
|
|
292
|
+
if (initTarget.createdGitRepo) console.log(" Initialized a new git repo for the tree.");
|
|
293
|
+
console.log();
|
|
294
|
+
}
|
|
295
|
+
if (!r.hasCurrentInstalledSkill()) try {
|
|
228
296
|
const sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
|
|
229
297
|
console.log("Installing the framework skill bundled with this first-tree package...");
|
|
230
298
|
console.log("Installing skill and scaffolding...");
|
|
@@ -243,11 +311,105 @@ function runInit(repo, options) {
|
|
|
243
311
|
console.log("All checks passed. Your context tree is set up.");
|
|
244
312
|
return 0;
|
|
245
313
|
}
|
|
246
|
-
const output = formatTaskList(groups);
|
|
314
|
+
const output = formatTaskList(groups, taskListContext);
|
|
247
315
|
console.log(output);
|
|
248
316
|
writeProgress(r, output);
|
|
249
317
|
console.log(`Progress file written to ${r.preferredProgressPath()}`);
|
|
318
|
+
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.`);
|
|
250
319
|
return 0;
|
|
251
320
|
}
|
|
321
|
+
function parseInitArgs(args) {
|
|
322
|
+
const parsed = {};
|
|
323
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
324
|
+
const arg = args[index];
|
|
325
|
+
switch (arg) {
|
|
326
|
+
case "--here":
|
|
327
|
+
parsed.here = true;
|
|
328
|
+
break;
|
|
329
|
+
case "--tree-name": {
|
|
330
|
+
const value = args[index + 1];
|
|
331
|
+
if (!value) return { error: "Missing value for --tree-name" };
|
|
332
|
+
parsed.treeName = value;
|
|
333
|
+
index += 1;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
case "--tree-path": {
|
|
337
|
+
const value = args[index + 1];
|
|
338
|
+
if (!value) return { error: "Missing value for --tree-path" };
|
|
339
|
+
parsed.treePath = value;
|
|
340
|
+
index += 1;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
default: return { error: `Unknown init option: ${arg}` };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (parsed.here && parsed.treeName) return { error: "Cannot combine --here with --tree-name" };
|
|
347
|
+
if (parsed.here && parsed.treePath) return { error: "Cannot combine --here with --tree-path" };
|
|
348
|
+
if (parsed.treeName && parsed.treePath) return { error: "Cannot combine --tree-name with --tree-path" };
|
|
349
|
+
return parsed;
|
|
350
|
+
}
|
|
351
|
+
function runInitCli(args = []) {
|
|
352
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
353
|
+
console.log(INIT_USAGE);
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
const parsed = parseInitArgs(args);
|
|
357
|
+
if ("error" in parsed) {
|
|
358
|
+
console.error(parsed.error);
|
|
359
|
+
console.log(INIT_USAGE);
|
|
360
|
+
return 1;
|
|
361
|
+
}
|
|
362
|
+
return runInit(void 0, parsed);
|
|
363
|
+
}
|
|
364
|
+
function resolveInitTarget(sourceRepo, options) {
|
|
365
|
+
if (!sourceRepo.isGitRepo()) return {
|
|
366
|
+
ok: false,
|
|
367
|
+
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"
|
|
368
|
+
};
|
|
369
|
+
const targetRoot = determineTargetRoot(sourceRepo, options);
|
|
370
|
+
const dedicatedTreeRepo = targetRoot !== sourceRepo.root;
|
|
371
|
+
let createdGitRepo = false;
|
|
372
|
+
try {
|
|
373
|
+
createdGitRepo = ensureGitRepo(targetRoot, options?.gitInitializer);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
return {
|
|
376
|
+
ok: false,
|
|
377
|
+
message: err instanceof Error ? err.message : "unknown error"
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
ok: true,
|
|
382
|
+
createdGitRepo,
|
|
383
|
+
dedicatedTreeRepo,
|
|
384
|
+
repo: new Repo(targetRoot)
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function determineTargetRoot(sourceRepo, options) {
|
|
388
|
+
if (options?.treePath) return resolve(options.currentCwd ?? process.cwd(), options.treePath);
|
|
389
|
+
if (options?.here) return sourceRepo.root;
|
|
390
|
+
if (options?.treeName) return join(dirname(sourceRepo.root), options.treeName);
|
|
391
|
+
if (sourceRepo.looksLikeTreeRepo() || sourceRepo.isLikelyEmptyRepo() || !sourceRepo.isLikelySourceRepo()) return sourceRepo.root;
|
|
392
|
+
return join(dirname(sourceRepo.root), `${sourceRepo.repoName()}-context`);
|
|
393
|
+
}
|
|
394
|
+
function ensureGitRepo(targetRoot, gitInitializer) {
|
|
395
|
+
if (existsSync(targetRoot)) {
|
|
396
|
+
if (!statSync(targetRoot).isDirectory()) throw new Error(`Target path is not a directory: ${targetRoot}`);
|
|
397
|
+
if (new Repo(targetRoot).isGitRepo()) return false;
|
|
398
|
+
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.`);
|
|
399
|
+
} else mkdirSync(targetRoot, { recursive: true });
|
|
400
|
+
(gitInitializer ?? defaultGitInitializer)(targetRoot);
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
function defaultGitInitializer(root) {
|
|
404
|
+
execFileSync("git", ["init"], {
|
|
405
|
+
cwd: root,
|
|
406
|
+
stdio: "ignore"
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function relativePathFrom(from, to) {
|
|
410
|
+
const rel = relative(from, to);
|
|
411
|
+
if (rel === "") return ".";
|
|
412
|
+
return rel.startsWith("..") ? rel : `./${rel}`;
|
|
413
|
+
}
|
|
252
414
|
//#endregion
|
|
253
|
-
export { runInit };
|
|
415
|
+
export { runInitCli as runInit };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { S as LEGACY_SKILL_ROOT, a as BUNDLED_SKILL_ROOT, m as INSTALLED_SKILL_ROOTS, y as LEGACY_REPO_SKILL_ROOT } from "./repo-DkR12VUv.js";
|
|
3
2
|
import { copyFileSync, cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
//#region skills/first-tree/engine/runtime/installer.ts
|
|
6
6
|
function resolveBundledPackageRoot(startUrl = import.meta.url) {
|
|
7
7
|
let dir = dirname(fileURLToPath(startUrl));
|
|
8
8
|
while (true) {
|
|
9
|
-
if (existsSync(join(dir, "package.json")) && existsSync(join(dir,
|
|
9
|
+
if (existsSync(join(dir, "package.json")) && existsSync(join(dir, BUNDLED_SKILL_ROOT, "SKILL.md"))) return dir;
|
|
10
10
|
const parent = dirname(dir);
|
|
11
11
|
if (parent === dir) break;
|
|
12
12
|
dir = parent;
|
|
@@ -16,24 +16,28 @@ function resolveBundledPackageRoot(startUrl = import.meta.url) {
|
|
|
16
16
|
function resolveCanonicalSkillRoot(sourceRoot) {
|
|
17
17
|
const directSkillRoot = sourceRoot;
|
|
18
18
|
if (existsSync(join(directSkillRoot, "SKILL.md")) && existsSync(join(directSkillRoot, "assets", "framework", "VERSION"))) return directSkillRoot;
|
|
19
|
-
const nestedSkillRoot = join(sourceRoot,
|
|
19
|
+
const nestedSkillRoot = join(sourceRoot, BUNDLED_SKILL_ROOT);
|
|
20
20
|
if (existsSync(join(nestedSkillRoot, "SKILL.md")) && existsSync(join(nestedSkillRoot, "assets", "framework", "VERSION"))) return nestedSkillRoot;
|
|
21
21
|
throw new Error(`Canonical skill not found under ${sourceRoot}. Reinstall the \`first-tree\` package and try again.`);
|
|
22
22
|
}
|
|
23
23
|
function copyCanonicalSkill(sourceRoot, targetRoot) {
|
|
24
24
|
const src = resolveCanonicalSkillRoot(sourceRoot);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
for (const relPath of [
|
|
26
|
+
...INSTALLED_SKILL_ROOTS,
|
|
27
|
+
LEGACY_REPO_SKILL_ROOT,
|
|
28
|
+
LEGACY_SKILL_ROOT
|
|
29
|
+
]) {
|
|
30
|
+
const fullPath = join(targetRoot, relPath);
|
|
31
|
+
if (existsSync(fullPath)) rmSync(fullPath, {
|
|
32
|
+
recursive: true,
|
|
33
|
+
force: true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
for (const relPath of INSTALLED_SKILL_ROOTS) {
|
|
37
|
+
const dst = join(targetRoot, relPath);
|
|
38
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
39
|
+
cpSync(src, dst, { recursive: true });
|
|
40
|
+
}
|
|
37
41
|
}
|
|
38
42
|
function renderTemplateFile(frameworkRoot, templateName, targetRoot, targetPath) {
|
|
39
43
|
const src = join(frameworkRoot, "templates", templateName);
|
|
@@ -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`.\n- `context-tree init` installs the framework skill into\n `.agents/skills/first-tree/` and `.claude/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 `.agents/skills/first-tree/` and\n`.claude/skills/first-tree/`, renders scaffolding (`NODE.md`, `AGENTS.md`,\n`members/NODE.md`), and generates a task list in\n`.agents/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 `.agents/skills/first-tree/progress.md`. It contains a checklist tailored\nto 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 (for Claude Code, the installed hook\n assets live under `.claude/skills/first-tree/`)\n- Copy validation workflows from\n `.agents/skills/first-tree/assets/framework/workflows/` to\n `.github/workflows/`\n\nAs you complete each task, check it off in\n`.agents/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 `.agents/skills/first-tree/progress.md` remain\nunchecked, and runs deterministic checks (valid frontmatter, node structure,\nmember 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 `.agents/skills/first-tree/` and\n`.claude/skills/first-tree/` from the skill bundled with the currently running\n`first-tree` npm package, preserves your tree content, and generates follow-up\ntasks in `.agents/skills/first-tree/progress.md`.\n\nIf your repo still uses the older `skills/first-tree/`,\n`skills/first-tree-cli-framework/`, or `.context-tree/` layouts,\n`context-tree upgrade` will migrate it to the current installed layout 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- `.agents/skills/first-tree/references/principles.md` — Core principles with detailed examples\n- `.agents/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 };
|