first-tree 0.0.5 → 0.0.7
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 +25 -7
- package/dist/cli.js +9 -6
- package/dist/{help-5-WG9QFm.js → help-BRO4mTG6.js} +1 -1
- package/dist/{init-CAq0Uhq6.js → init-BSs0ILp_.js} +49 -9
- package/dist/onboarding-BS8btkG4.js +2 -0
- package/dist/onboarding-D3hnxIie.js +10 -0
- package/dist/{repo-DkR12VUv.js → repo-0z7N9r17.js} +22 -16
- package/dist/source-integration-C2iiN4k_.js +80 -0
- package/dist/{upgrade-DYzuvv1k.js → upgrade-DvBdbph3.js} +26 -5
- package/dist/{verify-C0IUSkMZ.js → verify-DRt5mCqO.js} +7 -3
- package/package.json +2 -2
- package/skills/first-tree/SKILL.md +31 -5
- package/skills/first-tree/agents/openai.yaml +1 -1
- package/skills/first-tree/engine/init.ts +88 -5
- package/skills/first-tree/engine/repo.ts +38 -15
- package/skills/first-tree/engine/runtime/adapters.ts +0 -2
- package/skills/first-tree/engine/runtime/asset-loader.ts +6 -36
- package/skills/first-tree/engine/runtime/installer.ts +0 -2
- package/skills/first-tree/engine/runtime/source-integration.ts +80 -0
- package/skills/first-tree/engine/upgrade.ts +68 -12
- package/skills/first-tree/engine/validators/nodes.ts +2 -11
- package/skills/first-tree/engine/verify.ts +7 -0
- package/skills/first-tree/references/maintainer-architecture.md +4 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +3 -0
- package/skills/first-tree/references/onboarding.md +33 -5
- package/skills/first-tree/references/principles.md +97 -57
- package/skills/first-tree/references/source-map.md +1 -0
- package/skills/first-tree/references/source-workspace-installation.md +64 -0
- package/skills/first-tree/references/upgrade-contract.md +23 -12
- package/skills/first-tree/scripts/check-skill-sync.sh +2 -1
- package/skills/first-tree/tests/asset-loader.test.ts +0 -24
- package/skills/first-tree/tests/helpers.ts +0 -14
- package/skills/first-tree/tests/init.test.ts +67 -0
- package/skills/first-tree/tests/repo.test.ts +20 -25
- package/skills/first-tree/tests/skill-artifacts.test.ts +43 -1
- package/skills/first-tree/tests/thin-cli.test.ts +5 -0
- package/skills/first-tree/tests/upgrade.test.ts +38 -23
- package/skills/first-tree/tests/verify.test.ts +18 -0
- package/dist/installer-UgNasLjl.js +0 -51
- package/dist/onboarding-3zYUeYQb.js +0 -2
- package/dist/onboarding-Dd63N-V1.js +0 -10
|
@@ -28,6 +28,7 @@ repos.
|
|
|
28
28
|
2. Read the user-facing reference that matches the task:
|
|
29
29
|
- `references/onboarding.md`
|
|
30
30
|
- `references/about.md`
|
|
31
|
+
- `references/source-workspace-installation.md`
|
|
31
32
|
- `references/principles.md`
|
|
32
33
|
- `references/ownership-and-naming.md`
|
|
33
34
|
- `references/upgrade-contract.md`
|
|
@@ -58,16 +59,39 @@ repos.
|
|
|
58
59
|
|
|
59
60
|
### Working In A User Tree Repo
|
|
60
61
|
|
|
62
|
+
- When the task is to "install and use first-tree" in an existing
|
|
63
|
+
source/workspace repo, start with
|
|
64
|
+
`references/source-workspace-installation.md` and follow that workflow
|
|
65
|
+
end-to-end before improvising.
|
|
66
|
+
- When a user asks to install first-tree for an existing source/workspace repo,
|
|
67
|
+
the current repo keeps only the installed skill plus a
|
|
68
|
+
`FIRST-TREE-SOURCE-INTEGRATION:` line in `AGENTS.md` and `CLAUDE.md`. Do not
|
|
69
|
+
create `NODE.md`, `members/`, or tree-scoped `AGENTS.md` there.
|
|
61
70
|
- `context-tree init` defaults to creating or reusing a sibling dedicated tree
|
|
62
|
-
repo when invoked from a source/workspace repo.
|
|
63
|
-
the
|
|
71
|
+
repo when invoked from a source/workspace repo. It installs the bundled skill
|
|
72
|
+
into the source/workspace repo and scaffolds tree files only in the
|
|
73
|
+
dedicated tree repo. Use `--here` to initialize the current repo in place
|
|
74
|
+
when you are already inside the tree repo.
|
|
75
|
+
- Never run `context-tree init --here` in a source/workspace repo unless the
|
|
76
|
+
user explicitly wants that repo itself to become the dedicated Context Tree.
|
|
77
|
+
`--here` is for when you have already switched into the `*-context` repo.
|
|
64
78
|
- `context-tree init` installs this skill into the target tree repo and
|
|
65
79
|
scaffolds `.agents/skills/first-tree/`, `.claude/skills/first-tree/`,
|
|
66
80
|
`NODE.md`, `AGENTS.md`, and `members/NODE.md`.
|
|
81
|
+
- The default source/workspace workflow is: create or reuse `<repo>-context`,
|
|
82
|
+
prefer pushing it in the same GitHub organization as the source repo, add it
|
|
83
|
+
back to the source/workspace repo as a git submodule, and open a PR to the
|
|
84
|
+
source/workspace repo's default branch instead of merging automatically.
|
|
85
|
+
- If permissions, auth, or local filesystem constraints block the dedicated
|
|
86
|
+
repo workflow, stop and report the blocker. Do not fall back to in-place tree
|
|
87
|
+
bootstrap in the source/workspace repo.
|
|
67
88
|
- `context-tree upgrade` refreshes the installed skill from the copy bundled
|
|
68
|
-
with the currently running `first-tree` package.
|
|
69
|
-
|
|
70
|
-
|
|
89
|
+
with the currently running `first-tree` package. In a source/workspace repo
|
|
90
|
+
it refreshes only the local skill plus the
|
|
91
|
+
`FIRST-TREE-SOURCE-INTEGRATION:` line; upgrade the dedicated tree repo
|
|
92
|
+
separately with `--tree-path`. To pick up a newer framework, run a newer
|
|
93
|
+
package version first. It also migrates older repos that still use
|
|
94
|
+
`skills/first-tree/`.
|
|
71
95
|
- The user's tree content lives outside the skill; the skill only carries the
|
|
72
96
|
reusable framework payload plus maintenance guidance.
|
|
73
97
|
- The tree still stores decisions, constraints, and ownership; execution detail
|
|
@@ -108,6 +132,8 @@ repos.
|
|
|
108
132
|
- `engine/`: canonical framework and CLI behavior
|
|
109
133
|
- `tests/`: canonical unit and structure validation
|
|
110
134
|
- `references/source-map.md`: canonical reading index
|
|
135
|
+
- `references/source-workspace-installation.md`: source/workspace install
|
|
136
|
+
contract
|
|
111
137
|
- `references/maintainer-architecture.md`: source-repo architecture and
|
|
112
138
|
invariants
|
|
113
139
|
- `references/maintainer-thin-cli.md`: root shell contract
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "First Tree"
|
|
3
3
|
short_description: "Maintain the First Tree skill and thin Context Tree CLI"
|
|
4
|
-
default_prompt: "Use $first-tree to maintain the canonical first-tree skill, its thin context-tree CLI, or its build, packaging, test, eval, and CI wiring."
|
|
4
|
+
default_prompt: "Use $first-tree to maintain the canonical first-tree skill, its thin context-tree CLI, or its build, packaging, test, eval, and CI wiring. When a source/workspace repo installs first-tree, read references/source-workspace-installation.md first, keep that repo limited to local skill integration plus the FIRST-TREE-SOURCE-INTEGRATION marker lines, and keep all NODE.md/tree content only in a dedicated sibling *-context repo. Never choose context-tree init --here in the source/workspace repo unless the user explicitly wants that repo itself to become the tree."
|
|
@@ -19,12 +19,15 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
AGENT_INSTRUCTIONS_FILE,
|
|
21
21
|
AGENT_INSTRUCTIONS_TEMPLATE,
|
|
22
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
22
23
|
FRAMEWORK_ASSET_ROOT,
|
|
23
24
|
FRAMEWORK_VERSION,
|
|
24
25
|
INSTALLED_PROGRESS,
|
|
25
26
|
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
26
27
|
installedSkillRootsDisplay,
|
|
28
|
+
SOURCE_INTEGRATION_MARKER,
|
|
27
29
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
30
|
+
import { upsertSourceIntegrationFiles } from "#skill/engine/runtime/source-integration.js";
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* The interactive prompt tool the agent should use to present choices.
|
|
@@ -34,11 +37,16 @@ import {
|
|
|
34
37
|
export const INTERACTIVE_TOOL = "AskUserQuestion";
|
|
35
38
|
export const INIT_USAGE = `usage: context-tree init [--here] [--tree-name NAME] [--tree-path PATH]
|
|
36
39
|
|
|
37
|
-
By default, running \`context-tree init\` inside a source or workspace repo
|
|
38
|
-
|
|
40
|
+
By default, running \`context-tree init\` inside a source or workspace repo installs
|
|
41
|
+
the first-tree skill in the current repo, updates \`AGENTS.md\` and \`CLAUDE.md\`
|
|
42
|
+
with a \`${SOURCE_INTEGRATION_MARKER}\` line, and creates a sibling dedicated tree
|
|
43
|
+
repo named \`<repo>-context\`.
|
|
44
|
+
|
|
45
|
+
Do not use \`--here\` inside a source/workspace repo unless you explicitly want
|
|
46
|
+
that repo itself to become the Context Tree.
|
|
39
47
|
|
|
40
48
|
Options:
|
|
41
|
-
--here Initialize the current repo in place
|
|
49
|
+
--here Initialize the current repo in place after you are already in the dedicated tree repo
|
|
42
50
|
--tree-name NAME Name the dedicated sibling tree repo to create
|
|
43
51
|
--tree-path PATH Use an explicit tree repo path
|
|
44
52
|
--help Show this help message
|
|
@@ -62,6 +70,7 @@ const TEMPLATE_MAP: TemplateTarget[] = [
|
|
|
62
70
|
|
|
63
71
|
interface TaskListContext {
|
|
64
72
|
sourceRepoPath?: string;
|
|
73
|
+
sourceRepoName?: string;
|
|
65
74
|
dedicatedTreeRepo?: boolean;
|
|
66
75
|
}
|
|
67
76
|
|
|
@@ -106,6 +115,23 @@ export function formatTaskList(
|
|
|
106
115
|
if (context.sourceRepoPath) {
|
|
107
116
|
lines.push(`**Bootstrap source repo:** \`${context.sourceRepoPath}\``, "");
|
|
108
117
|
}
|
|
118
|
+
if (context.sourceRepoName) {
|
|
119
|
+
lines.push(
|
|
120
|
+
`**Source/workspace contract:** Keep \`${context.sourceRepoName}\` limited to the installed skill plus the \`${SOURCE_INTEGRATION_MARKER}\` lines in \`${AGENT_INSTRUCTIONS_FILE}\` and \`${CLAUDE_INSTRUCTIONS_FILE}\`. Never add \`NODE.md\`, \`members/\`, or tree-scoped \`${AGENT_INSTRUCTIONS_FILE}\` there.`,
|
|
121
|
+
"",
|
|
122
|
+
);
|
|
123
|
+
lines.push("## Source Workspace Workflow");
|
|
124
|
+
lines.push(
|
|
125
|
+
`- [ ] Publish this dedicated tree repo to the same GitHub organization as \`${context.sourceRepoName}\``,
|
|
126
|
+
);
|
|
127
|
+
lines.push(
|
|
128
|
+
`- [ ] Add this tree repo back to \`${context.sourceRepoName}\` as a git submodule after the remote exists`,
|
|
129
|
+
);
|
|
130
|
+
lines.push(
|
|
131
|
+
`- [ ] Open a PR against the source/workspace repo's default branch with only the installed skill, the \`${SOURCE_INTEGRATION_MARKER}\` marker lines in \`${AGENT_INSTRUCTIONS_FILE}\` / \`${CLAUDE_INSTRUCTIONS_FILE}\`, and the new submodule pointer`,
|
|
132
|
+
);
|
|
133
|
+
lines.push("");
|
|
134
|
+
}
|
|
109
135
|
lines.push(
|
|
110
136
|
"When you publish this tree repo, keep it in the same GitHub organization" +
|
|
111
137
|
" as the source repo unless you have a reason not to.",
|
|
@@ -179,12 +205,31 @@ export function runInit(repo?: Repo, options?: InitOptions): number {
|
|
|
179
205
|
return 1;
|
|
180
206
|
}
|
|
181
207
|
const r = initTarget.repo;
|
|
208
|
+
if (options?.here && sourceRepo.isLikelySourceRepo() && !sourceRepo.looksLikeTreeRepo()) {
|
|
209
|
+
console.log(
|
|
210
|
+
"Warning: `context-tree init --here` is initializing this source/workspace" +
|
|
211
|
+
" repo in place. This will create `NODE.md`, `members/`, and tree-scoped" +
|
|
212
|
+
` ${AGENT_INSTRUCTIONS_FILE} here. Use plain \`context-tree init\` to create` +
|
|
213
|
+
" a sibling dedicated tree repo instead.",
|
|
214
|
+
);
|
|
215
|
+
console.log();
|
|
216
|
+
}
|
|
182
217
|
const taskListContext = initTarget.dedicatedTreeRepo
|
|
183
218
|
? {
|
|
184
219
|
dedicatedTreeRepo: true,
|
|
220
|
+
sourceRepoName: sourceRepo.repoName(),
|
|
185
221
|
sourceRepoPath: relativePathFrom(r.root, sourceRepo.root),
|
|
186
222
|
}
|
|
187
223
|
: undefined;
|
|
224
|
+
let sourceRoot: string | null = null;
|
|
225
|
+
|
|
226
|
+
const resolveSourceRoot = (): string => {
|
|
227
|
+
if (sourceRoot !== null) {
|
|
228
|
+
return sourceRoot;
|
|
229
|
+
}
|
|
230
|
+
sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
|
|
231
|
+
return sourceRoot;
|
|
232
|
+
};
|
|
188
233
|
|
|
189
234
|
if (initTarget.dedicatedTreeRepo) {
|
|
190
235
|
console.log(
|
|
@@ -196,17 +241,55 @@ export function runInit(repo?: Repo, options?: InitOptions): number {
|
|
|
196
241
|
if (initTarget.createdGitRepo) {
|
|
197
242
|
console.log(" Initialized a new git repo for the tree.");
|
|
198
243
|
}
|
|
244
|
+
console.log(
|
|
245
|
+
" The source/workspace repo should keep only the installed skill and the" +
|
|
246
|
+
` ${SOURCE_INTEGRATION_MARKER} lines in ${AGENT_INSTRUCTIONS_FILE} and ${CLAUDE_INSTRUCTIONS_FILE}.`,
|
|
247
|
+
);
|
|
248
|
+
console.log(
|
|
249
|
+
` Never add NODE.md, members/, or tree-scoped ${AGENT_INSTRUCTIONS_FILE} to the source/workspace repo.`,
|
|
250
|
+
);
|
|
199
251
|
console.log();
|
|
200
252
|
}
|
|
201
253
|
|
|
254
|
+
if (initTarget.dedicatedTreeRepo) {
|
|
255
|
+
try {
|
|
256
|
+
const resolvedSourceRoot = resolveSourceRoot();
|
|
257
|
+
const hadSourceSkill = sourceRepo.hasCurrentInstalledSkill();
|
|
258
|
+
if (!hadSourceSkill) {
|
|
259
|
+
console.log(
|
|
260
|
+
"Installing the first-tree skill into the source/workspace repo...",
|
|
261
|
+
);
|
|
262
|
+
installSkill(resolvedSourceRoot, sourceRepo.root);
|
|
263
|
+
}
|
|
264
|
+
const updates = upsertSourceIntegrationFiles(sourceRepo.root, r.repoName());
|
|
265
|
+
const changedFiles = updates
|
|
266
|
+
.filter((update) => update.action !== "unchanged")
|
|
267
|
+
.map((update) => update.file);
|
|
268
|
+
if (changedFiles.length > 0) {
|
|
269
|
+
console.log(
|
|
270
|
+
` Updated source/workspace instructions in ${changedFiles.map((file) => `\`${file}\``).join(" and ")}`,
|
|
271
|
+
);
|
|
272
|
+
} else {
|
|
273
|
+
console.log(
|
|
274
|
+
` Source/workspace instructions already contain ${SOURCE_INTEGRATION_MARKER}`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
console.log();
|
|
278
|
+
} catch (err) {
|
|
279
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
280
|
+
console.error(`Error: ${message}`);
|
|
281
|
+
return 1;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
202
285
|
if (!r.hasCurrentInstalledSkill()) {
|
|
203
286
|
try {
|
|
204
|
-
const
|
|
287
|
+
const resolvedSourceRoot = resolveSourceRoot();
|
|
205
288
|
console.log(
|
|
206
289
|
"Installing the framework skill bundled with this first-tree package...",
|
|
207
290
|
);
|
|
208
291
|
console.log("Installing skill and scaffolding...");
|
|
209
|
-
installSkill(
|
|
292
|
+
installSkill(resolvedSourceRoot, r.root);
|
|
210
293
|
renderTemplates(r.root);
|
|
211
294
|
console.log();
|
|
212
295
|
} catch (err) {
|
|
@@ -10,8 +10,6 @@ import {
|
|
|
10
10
|
LEGACY_PROGRESS,
|
|
11
11
|
LEGACY_REPO_SKILL_PROGRESS,
|
|
12
12
|
LEGACY_REPO_SKILL_VERSION,
|
|
13
|
-
LEGACY_SKILL_PROGRESS,
|
|
14
|
-
LEGACY_SKILL_VERSION,
|
|
15
13
|
LEGACY_VERSION,
|
|
16
14
|
agentInstructionsFileCandidates,
|
|
17
15
|
installedSkillRoots,
|
|
@@ -20,13 +18,17 @@ import {
|
|
|
20
18
|
frameworkVersionCandidates,
|
|
21
19
|
progressFileCandidates,
|
|
22
20
|
resolveFirstExistingPath,
|
|
21
|
+
SOURCE_INTEGRATION_FILES,
|
|
22
|
+
SOURCE_INTEGRATION_MARKER,
|
|
23
23
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
24
24
|
|
|
25
25
|
const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
|
|
26
26
|
const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
|
|
27
27
|
const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
|
|
28
28
|
const EMPTY_REPO_ENTRY_ALLOWLIST = new Set([
|
|
29
|
+
".agents",
|
|
29
30
|
".DS_Store",
|
|
31
|
+
".claude",
|
|
30
32
|
".editorconfig",
|
|
31
33
|
".gitattributes",
|
|
32
34
|
".github",
|
|
@@ -216,9 +218,6 @@ export class Repo {
|
|
|
216
218
|
if (layout === "legacy") {
|
|
217
219
|
return LEGACY_PROGRESS;
|
|
218
220
|
}
|
|
219
|
-
if (layout === "legacy-skill") {
|
|
220
|
-
return LEGACY_SKILL_PROGRESS;
|
|
221
|
-
}
|
|
222
221
|
if (layout === "legacy-repo-skill") {
|
|
223
222
|
return LEGACY_REPO_SKILL_PROGRESS;
|
|
224
223
|
}
|
|
@@ -233,9 +232,6 @@ export class Repo {
|
|
|
233
232
|
if (layout === "legacy") {
|
|
234
233
|
return LEGACY_VERSION;
|
|
235
234
|
}
|
|
236
|
-
if (layout === "legacy-skill") {
|
|
237
|
-
return LEGACY_SKILL_VERSION;
|
|
238
|
-
}
|
|
239
235
|
if (layout === "legacy-repo-skill") {
|
|
240
236
|
return LEGACY_REPO_SKILL_VERSION;
|
|
241
237
|
}
|
|
@@ -273,6 +269,23 @@ export class Repo {
|
|
|
273
269
|
return text.includes(FRAMEWORK_BEGIN_MARKER) && text.includes(FRAMEWORK_END_MARKER);
|
|
274
270
|
}
|
|
275
271
|
|
|
272
|
+
hasSourceIntegrationFile(relPath: string): boolean {
|
|
273
|
+
return this.fileContains(relPath, SOURCE_INTEGRATION_MARKER);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
hasSourceWorkspaceIntegration(): boolean {
|
|
277
|
+
return SOURCE_INTEGRATION_FILES.some((file) => this.hasSourceIntegrationFile(file));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
hasTreeContent(): boolean {
|
|
281
|
+
return (
|
|
282
|
+
this.progressPath() !== null
|
|
283
|
+
|| this.hasAgentInstructionsMarkers()
|
|
284
|
+
|| this.pathExists("members/NODE.md")
|
|
285
|
+
|| this.frontmatter("NODE.md") !== null
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
276
289
|
hasMembers(): boolean {
|
|
277
290
|
const membersDir = join(this.root, "members");
|
|
278
291
|
try {
|
|
@@ -338,13 +351,19 @@ export class Repo {
|
|
|
338
351
|
return false;
|
|
339
352
|
}
|
|
340
353
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
354
|
+
if (this.hasTreeContent()) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (this.hasFramework() && this.hasSourceWorkspaceIntegration()) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (this.hasFramework()) {
|
|
363
|
+
return !this.hasLikelySourceRepoSignals();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return false;
|
|
348
367
|
}
|
|
349
368
|
|
|
350
369
|
isLikelyEmptyRepo(): boolean {
|
|
@@ -359,6 +378,10 @@ export class Repo {
|
|
|
359
378
|
return false;
|
|
360
379
|
}
|
|
361
380
|
|
|
381
|
+
return this.hasLikelySourceRepoSignals();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private hasLikelySourceRepoSignals(): boolean {
|
|
362
385
|
const entries = this.topLevelEntries().filter(
|
|
363
386
|
(entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry),
|
|
364
387
|
);
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
CLAUDE_FRAMEWORK_HELPERS_DIR,
|
|
5
5
|
FRAMEWORK_EXAMPLES_DIR,
|
|
6
6
|
LEGACY_REPO_SKILL_EXAMPLES_DIR,
|
|
7
|
-
LEGACY_SKILL_EXAMPLES_DIR,
|
|
8
7
|
LEGACY_EXAMPLES_DIR,
|
|
9
8
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
10
9
|
|
|
@@ -16,7 +15,6 @@ export function claudeCodeExampleCandidates(): string[] {
|
|
|
16
15
|
join(CLAUDE_FRAMEWORK_EXAMPLES_DIR, "claude-code"),
|
|
17
16
|
join(FRAMEWORK_EXAMPLES_DIR, "claude-code"),
|
|
18
17
|
join(LEGACY_REPO_SKILL_EXAMPLES_DIR, "claude-code"),
|
|
19
|
-
join(LEGACY_SKILL_EXAMPLES_DIR, "claude-code"),
|
|
20
18
|
join(LEGACY_EXAMPLES_DIR, "claude-code"),
|
|
21
19
|
];
|
|
22
20
|
}
|
|
@@ -21,6 +21,12 @@ export const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
|
|
|
21
21
|
export const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
|
|
22
22
|
export const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
|
|
23
23
|
export const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
|
|
24
|
+
export const CLAUDE_INSTRUCTIONS_FILE = "CLAUDE.md";
|
|
25
|
+
export const SOURCE_INTEGRATION_MARKER = "FIRST-TREE-SOURCE-INTEGRATION:";
|
|
26
|
+
export const SOURCE_INTEGRATION_FILES = [
|
|
27
|
+
AGENT_INSTRUCTIONS_FILE,
|
|
28
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
29
|
+
] as const;
|
|
24
30
|
|
|
25
31
|
export const CLAUDE_SKILL_AGENTS_DIR = join(CLAUDE_SKILL_ROOT, "agents");
|
|
26
32
|
export const CLAUDE_SKILL_REFERENCES_DIR = join(CLAUDE_SKILL_ROOT, "references");
|
|
@@ -109,32 +115,6 @@ export const LEGACY_REPO_SKILL_PROGRESS = join(
|
|
|
109
115
|
"progress.md",
|
|
110
116
|
);
|
|
111
117
|
|
|
112
|
-
export const LEGACY_SKILL_NAME = "first-tree-cli-framework";
|
|
113
|
-
export const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
|
|
114
|
-
export const LEGACY_SKILL_ASSET_ROOT = join(
|
|
115
|
-
LEGACY_SKILL_ROOT,
|
|
116
|
-
"assets",
|
|
117
|
-
"framework",
|
|
118
|
-
);
|
|
119
|
-
export const LEGACY_SKILL_VERSION = join(LEGACY_SKILL_ASSET_ROOT, "VERSION");
|
|
120
|
-
export const LEGACY_SKILL_TEMPLATES_DIR = join(
|
|
121
|
-
LEGACY_SKILL_ASSET_ROOT,
|
|
122
|
-
"templates",
|
|
123
|
-
);
|
|
124
|
-
export const LEGACY_SKILL_WORKFLOWS_DIR = join(
|
|
125
|
-
LEGACY_SKILL_ASSET_ROOT,
|
|
126
|
-
"workflows",
|
|
127
|
-
);
|
|
128
|
-
export const LEGACY_SKILL_PROMPTS_DIR = join(
|
|
129
|
-
LEGACY_SKILL_ASSET_ROOT,
|
|
130
|
-
"prompts",
|
|
131
|
-
);
|
|
132
|
-
export const LEGACY_SKILL_EXAMPLES_DIR = join(
|
|
133
|
-
LEGACY_SKILL_ASSET_ROOT,
|
|
134
|
-
"examples",
|
|
135
|
-
);
|
|
136
|
-
export const LEGACY_SKILL_PROGRESS = join(LEGACY_SKILL_ROOT, "progress.md");
|
|
137
|
-
|
|
138
118
|
export const LEGACY_FRAMEWORK_ROOT = ".context-tree";
|
|
139
119
|
export const LEGACY_VERSION = join(LEGACY_FRAMEWORK_ROOT, "VERSION");
|
|
140
120
|
export const LEGACY_PROGRESS = join(LEGACY_FRAMEWORK_ROOT, "progress.md");
|
|
@@ -147,7 +127,6 @@ export type FrameworkLayout =
|
|
|
147
127
|
| "skill"
|
|
148
128
|
| "claude-skill"
|
|
149
129
|
| "legacy-repo-skill"
|
|
150
|
-
| "legacy-skill"
|
|
151
130
|
| "legacy";
|
|
152
131
|
|
|
153
132
|
function pathExists(root: string, relPath: string): boolean {
|
|
@@ -174,7 +153,6 @@ export function frameworkVersionCandidates(): string[] {
|
|
|
174
153
|
FRAMEWORK_VERSION,
|
|
175
154
|
CLAUDE_FRAMEWORK_VERSION,
|
|
176
155
|
LEGACY_REPO_SKILL_VERSION,
|
|
177
|
-
LEGACY_SKILL_VERSION,
|
|
178
156
|
LEGACY_VERSION,
|
|
179
157
|
];
|
|
180
158
|
}
|
|
@@ -184,7 +162,6 @@ export function progressFileCandidates(): string[] {
|
|
|
184
162
|
INSTALLED_PROGRESS,
|
|
185
163
|
CLAUDE_INSTALLED_PROGRESS,
|
|
186
164
|
LEGACY_REPO_SKILL_PROGRESS,
|
|
187
|
-
LEGACY_SKILL_PROGRESS,
|
|
188
165
|
LEGACY_PROGRESS,
|
|
189
166
|
];
|
|
190
167
|
}
|
|
@@ -198,7 +175,6 @@ export function frameworkTemplateDirCandidates(): string[] {
|
|
|
198
175
|
FRAMEWORK_TEMPLATES_DIR,
|
|
199
176
|
CLAUDE_FRAMEWORK_TEMPLATES_DIR,
|
|
200
177
|
LEGACY_REPO_SKILL_TEMPLATES_DIR,
|
|
201
|
-
LEGACY_SKILL_TEMPLATES_DIR,
|
|
202
178
|
LEGACY_TEMPLATES_DIR,
|
|
203
179
|
];
|
|
204
180
|
}
|
|
@@ -208,7 +184,6 @@ export function frameworkWorkflowDirCandidates(): string[] {
|
|
|
208
184
|
FRAMEWORK_WORKFLOWS_DIR,
|
|
209
185
|
CLAUDE_FRAMEWORK_WORKFLOWS_DIR,
|
|
210
186
|
LEGACY_REPO_SKILL_WORKFLOWS_DIR,
|
|
211
|
-
LEGACY_SKILL_WORKFLOWS_DIR,
|
|
212
187
|
LEGACY_WORKFLOWS_DIR,
|
|
213
188
|
];
|
|
214
189
|
}
|
|
@@ -218,7 +193,6 @@ export function frameworkPromptDirCandidates(): string[] {
|
|
|
218
193
|
FRAMEWORK_PROMPTS_DIR,
|
|
219
194
|
CLAUDE_FRAMEWORK_PROMPTS_DIR,
|
|
220
195
|
LEGACY_REPO_SKILL_PROMPTS_DIR,
|
|
221
|
-
LEGACY_SKILL_PROMPTS_DIR,
|
|
222
196
|
LEGACY_PROMPTS_DIR,
|
|
223
197
|
];
|
|
224
198
|
}
|
|
@@ -228,7 +202,6 @@ export function frameworkExampleDirCandidates(): string[] {
|
|
|
228
202
|
FRAMEWORK_EXAMPLES_DIR,
|
|
229
203
|
CLAUDE_FRAMEWORK_EXAMPLES_DIR,
|
|
230
204
|
LEGACY_REPO_SKILL_EXAMPLES_DIR,
|
|
231
|
-
LEGACY_SKILL_EXAMPLES_DIR,
|
|
232
205
|
LEGACY_EXAMPLES_DIR,
|
|
233
206
|
];
|
|
234
207
|
}
|
|
@@ -255,9 +228,6 @@ export function detectFrameworkLayout(root: string): FrameworkLayout | null {
|
|
|
255
228
|
if (pathExists(root, LEGACY_REPO_SKILL_VERSION)) {
|
|
256
229
|
return "legacy-repo-skill";
|
|
257
230
|
}
|
|
258
|
-
if (pathExists(root, LEGACY_SKILL_VERSION)) {
|
|
259
|
-
return "legacy-skill";
|
|
260
|
-
}
|
|
261
231
|
if (pathExists(root, LEGACY_VERSION)) {
|
|
262
232
|
return "legacy";
|
|
263
233
|
}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
BUNDLED_SKILL_ROOT,
|
|
6
6
|
INSTALLED_SKILL_ROOTS,
|
|
7
7
|
LEGACY_REPO_SKILL_ROOT,
|
|
8
|
-
LEGACY_SKILL_ROOT,
|
|
9
8
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
10
9
|
|
|
11
10
|
export function resolveBundledPackageRoot(startUrl = import.meta.url): string {
|
|
@@ -57,7 +56,6 @@ export function copyCanonicalSkill(sourceRoot: string, targetRoot: string): void
|
|
|
57
56
|
for (const relPath of [
|
|
58
57
|
...INSTALLED_SKILL_ROOTS,
|
|
59
58
|
LEGACY_REPO_SKILL_ROOT,
|
|
60
|
-
LEGACY_SKILL_ROOT,
|
|
61
59
|
]) {
|
|
62
60
|
const fullPath = join(targetRoot, relPath);
|
|
63
61
|
if (existsSync(fullPath)) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
SOURCE_INTEGRATION_FILES,
|
|
5
|
+
SOURCE_INTEGRATION_MARKER,
|
|
6
|
+
} from "#skill/engine/runtime/asset-loader.js";
|
|
7
|
+
|
|
8
|
+
export type SourceIntegrationFile = (typeof SOURCE_INTEGRATION_FILES)[number];
|
|
9
|
+
|
|
10
|
+
export interface SourceIntegrationUpdate {
|
|
11
|
+
action: "created" | "updated" | "unchanged";
|
|
12
|
+
file: SourceIntegrationFile;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildSourceIntegrationLine(treeRepoName: string): string {
|
|
16
|
+
return (
|
|
17
|
+
`${SOURCE_INTEGRATION_MARKER} Use the installed \`first-tree\` skill here;`
|
|
18
|
+
+ ` keep all Context Tree files only in the sibling dedicated`
|
|
19
|
+
+ ` \`${treeRepoName}\` repo/submodule and update that tree when decisions`
|
|
20
|
+
+ " or constraints change."
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function hasSourceIntegrationMarker(text: string | null): boolean {
|
|
25
|
+
if (text === null) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return text
|
|
29
|
+
.replaceAll("\r\n", "\n")
|
|
30
|
+
.split("\n")
|
|
31
|
+
.some((line) => line.startsWith(SOURCE_INTEGRATION_MARKER));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function upsertSourceIntegrationFiles(
|
|
35
|
+
root: string,
|
|
36
|
+
treeRepoName: string,
|
|
37
|
+
): SourceIntegrationUpdate[] {
|
|
38
|
+
return SOURCE_INTEGRATION_FILES.map((file) =>
|
|
39
|
+
upsertSourceIntegrationFile(root, file, treeRepoName),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function upsertSourceIntegrationFile(
|
|
44
|
+
root: string,
|
|
45
|
+
file: SourceIntegrationFile,
|
|
46
|
+
treeRepoName: string,
|
|
47
|
+
): SourceIntegrationUpdate {
|
|
48
|
+
const fullPath = join(root, file);
|
|
49
|
+
const exists = existsSync(fullPath);
|
|
50
|
+
const nextLine = buildSourceIntegrationLine(treeRepoName);
|
|
51
|
+
const current = exists ? readFileSync(fullPath, "utf-8") : null;
|
|
52
|
+
const normalized = current?.replaceAll("\r\n", "\n") ?? "";
|
|
53
|
+
const lines = normalized === "" ? [] : normalized.split("\n");
|
|
54
|
+
const markerIndex = lines.findIndex((line) =>
|
|
55
|
+
line.startsWith(SOURCE_INTEGRATION_MARKER),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (markerIndex >= 0) {
|
|
59
|
+
if (lines[markerIndex] === nextLine) {
|
|
60
|
+
return { action: "unchanged", file };
|
|
61
|
+
}
|
|
62
|
+
lines[markerIndex] = nextLine;
|
|
63
|
+
} else {
|
|
64
|
+
if (lines.length > 0 && lines.at(-1) !== "") {
|
|
65
|
+
lines.push("");
|
|
66
|
+
}
|
|
67
|
+
lines.push(nextLine);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let nextText = lines.join("\n");
|
|
71
|
+
if (nextText !== "" && !nextText.endsWith("\n")) {
|
|
72
|
+
nextText += "\n";
|
|
73
|
+
}
|
|
74
|
+
writeFileSync(fullPath, nextText);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
action: exists ? "updated" : "created",
|
|
78
|
+
file,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -4,6 +4,7 @@ import { Repo } from "#skill/engine/repo.js";
|
|
|
4
4
|
import {
|
|
5
5
|
AGENT_INSTRUCTIONS_FILE,
|
|
6
6
|
AGENT_INSTRUCTIONS_TEMPLATE,
|
|
7
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
7
8
|
CLAUDE_SKILL_ROOT,
|
|
8
9
|
FRAMEWORK_WORKFLOWS_DIR,
|
|
9
10
|
FRAMEWORK_TEMPLATES_DIR,
|
|
@@ -12,8 +13,8 @@ import {
|
|
|
12
13
|
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
13
14
|
LEGACY_FRAMEWORK_ROOT,
|
|
14
15
|
LEGACY_REPO_SKILL_ROOT,
|
|
15
|
-
LEGACY_SKILL_ROOT,
|
|
16
16
|
SKILL_ROOT,
|
|
17
|
+
SOURCE_INTEGRATION_MARKER,
|
|
17
18
|
installedSkillRootsDisplay,
|
|
18
19
|
type FrameworkLayout,
|
|
19
20
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
copyCanonicalSkill,
|
|
22
23
|
resolveBundledPackageRoot,
|
|
23
24
|
} from "#skill/engine/runtime/installer.js";
|
|
25
|
+
import { upsertSourceIntegrationFiles } from "#skill/engine/runtime/source-integration.js";
|
|
24
26
|
import {
|
|
25
27
|
compareFrameworkVersions,
|
|
26
28
|
readSourceVersion,
|
|
@@ -70,12 +72,6 @@ function formatUpgradeTaskList(
|
|
|
70
72
|
);
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
if (layout === "legacy-skill") {
|
|
74
|
-
migrationTasks.push(
|
|
75
|
-
`- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`,
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
75
|
if (repo.hasCanonicalAgentInstructionsFile() && repo.hasLegacyAgentInstructionsFile()) {
|
|
80
76
|
migrationTasks.push(
|
|
81
77
|
`- [ ] Merge any remaining user-authored content from \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` into \`${AGENT_INSTRUCTIONS_FILE}\`, then delete the legacy file`,
|
|
@@ -121,8 +117,10 @@ export interface UpgradeOptions {
|
|
|
121
117
|
|
|
122
118
|
export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
|
|
123
119
|
const workingRepo = repo ?? new Repo();
|
|
120
|
+
const workspaceOnlyIntegration =
|
|
121
|
+
workingRepo.hasSourceWorkspaceIntegration() && !workingRepo.looksLikeTreeRepo();
|
|
124
122
|
|
|
125
|
-
if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo()) {
|
|
123
|
+
if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo() && !workspaceOnlyIntegration) {
|
|
126
124
|
console.error(
|
|
127
125
|
"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.",
|
|
128
126
|
);
|
|
@@ -178,6 +176,68 @@ export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
|
|
|
178
176
|
}
|
|
179
177
|
|
|
180
178
|
const missingInstalledRoots = workingRepo.missingInstalledSkillRoots();
|
|
179
|
+
const sourceRepoTreePathHint = `../${workingRepo.repoName()}-context`;
|
|
180
|
+
|
|
181
|
+
if (workspaceOnlyIntegration) {
|
|
182
|
+
if (
|
|
183
|
+
layout === "skill" &&
|
|
184
|
+
missingInstalledRoots.length === 0 &&
|
|
185
|
+
packagedVersion === localVersion
|
|
186
|
+
) {
|
|
187
|
+
const updates = upsertSourceIntegrationFiles(
|
|
188
|
+
workingRepo.root,
|
|
189
|
+
`${workingRepo.repoName()}-context`,
|
|
190
|
+
);
|
|
191
|
+
const changedFiles = updates
|
|
192
|
+
.filter((update) => update.action !== "unchanged")
|
|
193
|
+
.map((update) => update.file);
|
|
194
|
+
if (changedFiles.length === 0) {
|
|
195
|
+
console.log(
|
|
196
|
+
`Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`,
|
|
197
|
+
);
|
|
198
|
+
console.log(
|
|
199
|
+
`This repo only carries source/workspace integration. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
|
|
200
|
+
);
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
console.log(
|
|
204
|
+
`Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`,
|
|
205
|
+
);
|
|
206
|
+
console.log(
|
|
207
|
+
`Updated the ${SOURCE_INTEGRATION_MARKER} marker lines in ${changedFiles.map((file) => `\`${file}\``).join(" and ")}.`,
|
|
208
|
+
);
|
|
209
|
+
console.log(
|
|
210
|
+
`This repo only carries source/workspace integration. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
|
|
211
|
+
);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
copyCanonicalSkill(sourceRoot, workingRepo.root);
|
|
216
|
+
const updates = upsertSourceIntegrationFiles(
|
|
217
|
+
workingRepo.root,
|
|
218
|
+
`${workingRepo.repoName()}-context`,
|
|
219
|
+
);
|
|
220
|
+
const changedFiles = updates
|
|
221
|
+
.filter((update) => update.action !== "unchanged")
|
|
222
|
+
.map((update) => update.file);
|
|
223
|
+
console.log(
|
|
224
|
+
`Refreshed ${installedSkillRootsDisplay()} in this source/workspace repo.`,
|
|
225
|
+
);
|
|
226
|
+
if (changedFiles.length > 0) {
|
|
227
|
+
console.log(
|
|
228
|
+
`Updated the ${SOURCE_INTEGRATION_MARKER} marker lines in ${changedFiles.map((file) => `\`${file}\``).join(" and ")}.`,
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
console.log(
|
|
232
|
+
`The ${SOURCE_INTEGRATION_MARKER} marker lines in ${AGENT_INSTRUCTIONS_FILE} and ${CLAUDE_INSTRUCTIONS_FILE} were already current.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
console.log(
|
|
236
|
+
`This repo is not the Context Tree. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
|
|
237
|
+
);
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
181
241
|
if (
|
|
182
242
|
layout === "skill" &&
|
|
183
243
|
missingInstalledRoots.length === 0 &&
|
|
@@ -202,10 +262,6 @@ export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
|
|
|
202
262
|
console.log(
|
|
203
263
|
`Migrated legacy ${LEGACY_REPO_SKILL_ROOT}/ layout to ${installedSkillRootsDisplay()}.`,
|
|
204
264
|
);
|
|
205
|
-
} else if (layout === "legacy-skill") {
|
|
206
|
-
console.log(
|
|
207
|
-
`Migrated ${LEGACY_SKILL_ROOT}/ to ${installedSkillRootsDisplay()}.`,
|
|
208
|
-
);
|
|
209
265
|
} else {
|
|
210
266
|
if (missingInstalledRoots.length > 0) {
|
|
211
267
|
console.log(
|