first-tree 0.0.7 → 0.0.8
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 +11 -4
- package/dist/bootstrap-YRjfHJp7.js +28 -0
- package/dist/cli.js +10 -4
- package/dist/{help-BRO4mTG6.js → help-CDfaFrzl.js} +1 -1
- package/dist/{init-BSs0ILp_.js → init-DjSVkUeR.js} +11 -6
- package/dist/onboarding-BiHx2jy5.js +10 -0
- package/dist/onboarding-Ce033qaW.js +2 -0
- package/dist/publish-D0crNDjz.js +504 -0
- package/dist/{repo-0z7N9r17.js → repo-BeVpMoHi.js} +2 -1
- package/dist/{source-integration-C2iiN4k_.js → source-integration-DMxnl8Dw.js} +1 -1
- package/dist/{upgrade-DvBdbph3.js → upgrade-B_NTlNrx.js} +2 -2
- package/dist/{verify-DRt5mCqO.js → verify-Chhm1vOF.js} +1 -1
- package/package.json +1 -1
- package/skills/first-tree/SKILL.md +14 -5
- package/skills/first-tree/assets/framework/VERSION +1 -1
- package/skills/first-tree/engine/commands/publish.ts +5 -0
- package/skills/first-tree/engine/init.ts +11 -5
- package/skills/first-tree/engine/publish.ts +807 -0
- package/skills/first-tree/engine/runtime/asset-loader.ts +1 -0
- package/skills/first-tree/engine/runtime/bootstrap.ts +40 -0
- 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 +12 -9
- package/skills/first-tree/references/source-map.md +3 -1
- package/skills/first-tree/references/source-workspace-installation.md +13 -13
- package/skills/first-tree/references/upgrade-contract.md +11 -0
- package/skills/first-tree/tests/init.test.ts +19 -0
- package/skills/first-tree/tests/publish.test.ts +248 -0
- package/skills/first-tree/tests/skill-artifacts.test.ts +12 -0
- package/skills/first-tree/tests/thin-cli.test.ts +1 -0
- package/dist/onboarding-BS8btkG4.js +0 -2
- package/dist/onboarding-D3hnxIie.js +0 -10
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { C as SKILL_ROOT, c as CLAUDE_INSTRUCTIONS_FILE, l as CLAUDE_SKILL_ROOT, n as Repo, r as AGENT_INSTRUCTIONS_FILE } from "./repo-BeVpMoHi.js";
|
|
2
|
+
import { t as readBootstrapState } from "./bootstrap-YRjfHJp7.js";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
6
|
+
//#region skills/first-tree/engine/publish.ts
|
|
7
|
+
const PUBLISH_USAGE = `usage: context-tree publish [--open-pr] [--tree-path PATH] [--source-repo PATH] [--submodule-path PATH] [--source-remote NAME]
|
|
8
|
+
|
|
9
|
+
Run this from the dedicated tree repo after \`context-tree init\`. The command
|
|
10
|
+
creates or reuses the GitHub \`*-context\` repo, pushes the current tree
|
|
11
|
+
commit, adds that repo back to the source/workspace repo as a git submodule,
|
|
12
|
+
and prepares the source-repo branch.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--open-pr Open a PR in the source/workspace repo after pushing the branch
|
|
16
|
+
--tree-path PATH Publish a tree repo from another working directory
|
|
17
|
+
--source-repo PATH Explicit source/workspace repo path when it cannot be inferred
|
|
18
|
+
--submodule-path PATH Path to use inside the source/workspace repo (default: tree repo name)
|
|
19
|
+
--source-remote NAME Source/workspace repo remote to mirror on GitHub (default: origin)
|
|
20
|
+
--help Show this help message
|
|
21
|
+
`;
|
|
22
|
+
function defaultCommandRunner(command, args, options) {
|
|
23
|
+
try {
|
|
24
|
+
return execFileSync(command, args, {
|
|
25
|
+
cwd: options.cwd,
|
|
26
|
+
encoding: "utf-8",
|
|
27
|
+
env: {
|
|
28
|
+
...process.env,
|
|
29
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
30
|
+
},
|
|
31
|
+
stdio: [
|
|
32
|
+
"ignore",
|
|
33
|
+
"pipe",
|
|
34
|
+
"pipe"
|
|
35
|
+
]
|
|
36
|
+
}).trim();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
39
|
+
throw new Error(`Command failed in ${options.cwd}: ${command} ${args.join(" ")}\n${message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function commandSucceeds(runner, command, args, cwd) {
|
|
43
|
+
try {
|
|
44
|
+
runner(command, args, { cwd });
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function parseGitHubRemote(url) {
|
|
51
|
+
if (url.startsWith("https://") || url.startsWith("http://")) try {
|
|
52
|
+
const parsed = new URL(url);
|
|
53
|
+
if (parsed.hostname !== "github.com") return null;
|
|
54
|
+
const parts = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/, "").split("/");
|
|
55
|
+
if (parts.length !== 2 || parts.some((part) => part.trim() === "")) return null;
|
|
56
|
+
return {
|
|
57
|
+
cloneStyle: "https",
|
|
58
|
+
owner: parts[0],
|
|
59
|
+
repo: parts[1],
|
|
60
|
+
slug: `${parts[0]}/${parts[1]}`
|
|
61
|
+
};
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (url.startsWith("ssh://")) try {
|
|
66
|
+
const parsed = new URL(url);
|
|
67
|
+
if (parsed.hostname !== "github.com") return null;
|
|
68
|
+
const parts = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/, "").split("/");
|
|
69
|
+
if (parts.length !== 2 || parts.some((part) => part.trim() === "")) return null;
|
|
70
|
+
return {
|
|
71
|
+
cloneStyle: "ssh",
|
|
72
|
+
owner: parts[0],
|
|
73
|
+
repo: parts[1],
|
|
74
|
+
slug: `${parts[0]}/${parts[1]}`
|
|
75
|
+
};
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const scpMatch = url.match(/^git@github\.com:(.+?)\/(.+?)(?:\.git)?$/);
|
|
80
|
+
if (scpMatch === null) return null;
|
|
81
|
+
return {
|
|
82
|
+
cloneStyle: "ssh",
|
|
83
|
+
owner: scpMatch[1],
|
|
84
|
+
repo: scpMatch[2],
|
|
85
|
+
slug: `${scpMatch[1]}/${scpMatch[2]}`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function visibilityFlag(visibility) {
|
|
89
|
+
switch (visibility) {
|
|
90
|
+
case "internal": return "--internal";
|
|
91
|
+
case "private": return "--private";
|
|
92
|
+
default: return "--public";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function buildGitHubCloneUrl(slug, cloneStyle) {
|
|
96
|
+
if (cloneStyle === "ssh") return `git@github.com:${slug}.git`;
|
|
97
|
+
return `https://github.com/${slug}.git`;
|
|
98
|
+
}
|
|
99
|
+
function readGitHubRepoMetadata(runner, slug, cwd) {
|
|
100
|
+
const raw = runner("gh", [
|
|
101
|
+
"repo",
|
|
102
|
+
"view",
|
|
103
|
+
slug,
|
|
104
|
+
"--json",
|
|
105
|
+
"defaultBranchRef,nameWithOwner,visibility"
|
|
106
|
+
], { cwd });
|
|
107
|
+
const parsed = JSON.parse(raw);
|
|
108
|
+
const defaultBranch = parsed.defaultBranchRef?.name;
|
|
109
|
+
const nameWithOwner = parsed.nameWithOwner;
|
|
110
|
+
const visibility = parsed.visibility?.toLowerCase();
|
|
111
|
+
if (typeof defaultBranch !== "string" || typeof nameWithOwner !== "string" || visibility !== "internal" && visibility !== "private" && visibility !== "public") throw new Error(`Could not read GitHub metadata for ${slug}.`);
|
|
112
|
+
return {
|
|
113
|
+
defaultBranch,
|
|
114
|
+
nameWithOwner,
|
|
115
|
+
visibility
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function readCurrentBranch(runner, root) {
|
|
119
|
+
return runner("git", ["branch", "--show-current"], { cwd: root }).trim();
|
|
120
|
+
}
|
|
121
|
+
function hasCommit(runner, root) {
|
|
122
|
+
return commandSucceeds(runner, "git", [
|
|
123
|
+
"rev-parse",
|
|
124
|
+
"--verify",
|
|
125
|
+
"HEAD"
|
|
126
|
+
], root);
|
|
127
|
+
}
|
|
128
|
+
function hasIndexedChanges(runner, root, paths) {
|
|
129
|
+
const args = [
|
|
130
|
+
"diff",
|
|
131
|
+
"--cached",
|
|
132
|
+
"--quiet"
|
|
133
|
+
];
|
|
134
|
+
if (paths && paths.length > 0) args.push("--", ...paths);
|
|
135
|
+
return !commandSucceeds(runner, "git", args, root);
|
|
136
|
+
}
|
|
137
|
+
function commitTreeState(runner, treeRepo) {
|
|
138
|
+
const hadCommit = hasCommit(runner, treeRepo.root);
|
|
139
|
+
runner("git", ["add", "-A"], { cwd: treeRepo.root });
|
|
140
|
+
if (!hasIndexedChanges(runner, treeRepo.root)) return false;
|
|
141
|
+
runner("git", [
|
|
142
|
+
"commit",
|
|
143
|
+
"-m",
|
|
144
|
+
hadCommit ? "chore: update context tree" : "chore: bootstrap context tree"
|
|
145
|
+
], { cwd: treeRepo.root });
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
function resolveSourceRepoRoot(treeRepo, options) {
|
|
149
|
+
const cwd = options?.currentCwd ?? process.cwd();
|
|
150
|
+
if (options?.sourceRepoPath) return resolve(cwd, options.sourceRepoPath);
|
|
151
|
+
const bootstrap = readBootstrapState(treeRepo.root);
|
|
152
|
+
if (bootstrap !== null) return resolve(treeRepo.root, bootstrap.sourceRepoPath);
|
|
153
|
+
if (treeRepo.repoName().endsWith("-context")) return join(dirname(treeRepo.root), treeRepo.repoName().slice(0, -8));
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function normalizeSubmodulePath(input) {
|
|
157
|
+
if (input.trim() === "") return null;
|
|
158
|
+
if (isAbsolute(input)) return null;
|
|
159
|
+
const normalized = normalize(input).replaceAll("\\", "/");
|
|
160
|
+
if (normalized === "." || normalized === "" || normalized.startsWith("../") || normalized.includes("/../")) return null;
|
|
161
|
+
return normalized;
|
|
162
|
+
}
|
|
163
|
+
function getGitRemoteUrl(runner, root, remote) {
|
|
164
|
+
try {
|
|
165
|
+
return runner("git", [
|
|
166
|
+
"remote",
|
|
167
|
+
"get-url",
|
|
168
|
+
remote
|
|
169
|
+
], { cwd: root }).trim();
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function localBranchExists(runner, root, branch) {
|
|
175
|
+
return commandSucceeds(runner, "git", [
|
|
176
|
+
"rev-parse",
|
|
177
|
+
"--verify",
|
|
178
|
+
`refs/heads/${branch}`
|
|
179
|
+
], root);
|
|
180
|
+
}
|
|
181
|
+
function remoteTrackingBranchExists(runner, root, remote, branch) {
|
|
182
|
+
return commandSucceeds(runner, "git", [
|
|
183
|
+
"rev-parse",
|
|
184
|
+
"--verify",
|
|
185
|
+
`refs/remotes/${remote}/${branch}`
|
|
186
|
+
], root);
|
|
187
|
+
}
|
|
188
|
+
function buildPublishBranchName(treeRepoName) {
|
|
189
|
+
return `chore/connect-${treeRepoName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")}`;
|
|
190
|
+
}
|
|
191
|
+
function ensureSourceBranch(runner, sourceRepo, sourceRemote, defaultBranch, treeRepoName) {
|
|
192
|
+
const branch = buildPublishBranchName(treeRepoName);
|
|
193
|
+
if (readCurrentBranch(runner, sourceRepo.root) === branch) return branch;
|
|
194
|
+
if (localBranchExists(runner, sourceRepo.root, branch)) {
|
|
195
|
+
runner("git", ["switch", branch], { cwd: sourceRepo.root });
|
|
196
|
+
return branch;
|
|
197
|
+
}
|
|
198
|
+
if (!remoteTrackingBranchExists(runner, sourceRepo.root, sourceRemote, defaultBranch)) runner("git", [
|
|
199
|
+
"fetch",
|
|
200
|
+
sourceRemote,
|
|
201
|
+
defaultBranch
|
|
202
|
+
], { cwd: sourceRepo.root });
|
|
203
|
+
if (remoteTrackingBranchExists(runner, sourceRepo.root, sourceRemote, defaultBranch)) {
|
|
204
|
+
runner("git", [
|
|
205
|
+
"switch",
|
|
206
|
+
"-c",
|
|
207
|
+
branch,
|
|
208
|
+
"--track",
|
|
209
|
+
`${sourceRemote}/${defaultBranch}`
|
|
210
|
+
], { cwd: sourceRepo.root });
|
|
211
|
+
return branch;
|
|
212
|
+
}
|
|
213
|
+
runner("git", [
|
|
214
|
+
"switch",
|
|
215
|
+
"-c",
|
|
216
|
+
branch
|
|
217
|
+
], { cwd: sourceRepo.root });
|
|
218
|
+
return branch;
|
|
219
|
+
}
|
|
220
|
+
function isTrackedSubmodule(runner, sourceRoot, submodulePath) {
|
|
221
|
+
try {
|
|
222
|
+
return runner("git", [
|
|
223
|
+
"ls-files",
|
|
224
|
+
"--stage",
|
|
225
|
+
"--",
|
|
226
|
+
submodulePath
|
|
227
|
+
], { cwd: sourceRoot }).split(/\r?\n/).some((line) => line.startsWith("160000 "));
|
|
228
|
+
} catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function ensureSubmodule(runner, sourceRepo, submodulePath, remoteUrl) {
|
|
233
|
+
if (isTrackedSubmodule(runner, sourceRepo.root, submodulePath)) {
|
|
234
|
+
runner("git", [
|
|
235
|
+
"submodule",
|
|
236
|
+
"set-url",
|
|
237
|
+
"--",
|
|
238
|
+
submodulePath,
|
|
239
|
+
remoteUrl
|
|
240
|
+
], { cwd: sourceRepo.root });
|
|
241
|
+
runner("git", [
|
|
242
|
+
"submodule",
|
|
243
|
+
"sync",
|
|
244
|
+
"--",
|
|
245
|
+
submodulePath
|
|
246
|
+
], { cwd: sourceRepo.root });
|
|
247
|
+
runner("git", [
|
|
248
|
+
"submodule",
|
|
249
|
+
"update",
|
|
250
|
+
"--init",
|
|
251
|
+
"--",
|
|
252
|
+
submodulePath
|
|
253
|
+
], { cwd: sourceRepo.root });
|
|
254
|
+
return "updated";
|
|
255
|
+
}
|
|
256
|
+
if (existsSync(join(sourceRepo.root, submodulePath))) throw new Error(`Cannot add the submodule at ${submodulePath} because that path already exists in the source/workspace repo.`);
|
|
257
|
+
runner("git", [
|
|
258
|
+
"submodule",
|
|
259
|
+
"add",
|
|
260
|
+
remoteUrl,
|
|
261
|
+
submodulePath
|
|
262
|
+
], { cwd: sourceRepo.root });
|
|
263
|
+
return "added";
|
|
264
|
+
}
|
|
265
|
+
function commitSourceIntegration(runner, sourceRepo, submodulePath, treeRepoName) {
|
|
266
|
+
const managedPaths = [
|
|
267
|
+
...[
|
|
268
|
+
SKILL_ROOT,
|
|
269
|
+
CLAUDE_SKILL_ROOT,
|
|
270
|
+
AGENT_INSTRUCTIONS_FILE,
|
|
271
|
+
CLAUDE_INSTRUCTIONS_FILE
|
|
272
|
+
].filter((path) => existsSync(join(sourceRepo.root, path))),
|
|
273
|
+
".gitmodules",
|
|
274
|
+
submodulePath
|
|
275
|
+
].filter((path, index, items) => items.indexOf(path) === index);
|
|
276
|
+
runner("git", [
|
|
277
|
+
"add",
|
|
278
|
+
"--",
|
|
279
|
+
...managedPaths
|
|
280
|
+
], { cwd: sourceRepo.root });
|
|
281
|
+
if (!hasIndexedChanges(runner, sourceRepo.root, managedPaths)) return false;
|
|
282
|
+
runner("git", [
|
|
283
|
+
"commit",
|
|
284
|
+
"-m",
|
|
285
|
+
`chore: connect ${treeRepoName} context tree`,
|
|
286
|
+
"--",
|
|
287
|
+
...managedPaths
|
|
288
|
+
], { cwd: sourceRepo.root });
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
function ensureTreeRemotePublished(runner, treeRepo, treeSlug, sourceCloneStyle, visibility) {
|
|
292
|
+
const existingOrigin = getGitRemoteUrl(runner, treeRepo.root, "origin");
|
|
293
|
+
if (existingOrigin !== null) {
|
|
294
|
+
runner("git", [
|
|
295
|
+
"push",
|
|
296
|
+
"-u",
|
|
297
|
+
"origin",
|
|
298
|
+
"HEAD"
|
|
299
|
+
], { cwd: treeRepo.root });
|
|
300
|
+
return {
|
|
301
|
+
createdRemote: false,
|
|
302
|
+
remoteUrl: existingOrigin
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const desiredCloneUrl = buildGitHubCloneUrl(treeSlug, sourceCloneStyle);
|
|
306
|
+
if (commandSucceeds(runner, "gh", [
|
|
307
|
+
"repo",
|
|
308
|
+
"view",
|
|
309
|
+
treeSlug,
|
|
310
|
+
"--json",
|
|
311
|
+
"nameWithOwner"
|
|
312
|
+
], treeRepo.root)) {
|
|
313
|
+
runner("git", [
|
|
314
|
+
"remote",
|
|
315
|
+
"add",
|
|
316
|
+
"origin",
|
|
317
|
+
desiredCloneUrl
|
|
318
|
+
], { cwd: treeRepo.root });
|
|
319
|
+
runner("git", [
|
|
320
|
+
"push",
|
|
321
|
+
"-u",
|
|
322
|
+
"origin",
|
|
323
|
+
"HEAD"
|
|
324
|
+
], { cwd: treeRepo.root });
|
|
325
|
+
return {
|
|
326
|
+
createdRemote: false,
|
|
327
|
+
remoteUrl: desiredCloneUrl
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
runner("gh", [
|
|
331
|
+
"repo",
|
|
332
|
+
"create",
|
|
333
|
+
treeSlug,
|
|
334
|
+
visibilityFlag(visibility),
|
|
335
|
+
"--source",
|
|
336
|
+
treeRepo.root,
|
|
337
|
+
"--remote",
|
|
338
|
+
"origin",
|
|
339
|
+
"--push"
|
|
340
|
+
], { cwd: treeRepo.root });
|
|
341
|
+
return {
|
|
342
|
+
createdRemote: true,
|
|
343
|
+
remoteUrl: getGitRemoteUrl(runner, treeRepo.root, "origin") ?? desiredCloneUrl
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function buildPrBody(treeRepoName, treeSlug, submodulePath) {
|
|
347
|
+
return [
|
|
348
|
+
`Connect the published \`${treeRepoName}\` Context Tree back into this source/workspace repo.`,
|
|
349
|
+
"",
|
|
350
|
+
`- add \`${submodulePath}\` as the tracked Context Tree submodule`,
|
|
351
|
+
"- keep the local first-tree skill + source integration marker lines in this repo",
|
|
352
|
+
`- use \`${treeSlug}\` as the GitHub home for tree content`
|
|
353
|
+
].join("\n");
|
|
354
|
+
}
|
|
355
|
+
function parsePublishArgs(args) {
|
|
356
|
+
const parsed = {};
|
|
357
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
358
|
+
const arg = args[index];
|
|
359
|
+
switch (arg) {
|
|
360
|
+
case "--open-pr":
|
|
361
|
+
parsed.openPr = true;
|
|
362
|
+
break;
|
|
363
|
+
case "--tree-path": {
|
|
364
|
+
const value = args[index + 1];
|
|
365
|
+
if (!value) return { error: "Missing value for --tree-path" };
|
|
366
|
+
parsed.treePath = value;
|
|
367
|
+
index += 1;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
case "--source-repo": {
|
|
371
|
+
const value = args[index + 1];
|
|
372
|
+
if (!value) return { error: "Missing value for --source-repo" };
|
|
373
|
+
parsed.sourceRepoPath = value;
|
|
374
|
+
index += 1;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
case "--submodule-path": {
|
|
378
|
+
const value = args[index + 1];
|
|
379
|
+
if (!value) return { error: "Missing value for --submodule-path" };
|
|
380
|
+
parsed.submodulePath = value;
|
|
381
|
+
index += 1;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "--source-remote": {
|
|
385
|
+
const value = args[index + 1];
|
|
386
|
+
if (!value) return { error: "Missing value for --source-remote" };
|
|
387
|
+
parsed.sourceRemote = value;
|
|
388
|
+
index += 1;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
default: return { error: `Unknown publish option: ${arg}` };
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return parsed;
|
|
395
|
+
}
|
|
396
|
+
function runPublish(repo, options) {
|
|
397
|
+
const cwd = options?.currentCwd ?? process.cwd();
|
|
398
|
+
const runner = options?.commandRunner ?? defaultCommandRunner;
|
|
399
|
+
const treeRepo = repo ?? new Repo(options?.treePath ? resolve(cwd, options.treePath) : void 0);
|
|
400
|
+
if (treeRepo.hasSourceWorkspaceIntegration() && !treeRepo.looksLikeTreeRepo()) {
|
|
401
|
+
console.error(`Error: this repo only has the first-tree source/workspace integration installed. Run \`context-tree publish --tree-path ../${treeRepo.repoName()}-context\` or switch into the dedicated tree repo first.`);
|
|
402
|
+
return 1;
|
|
403
|
+
}
|
|
404
|
+
if (!treeRepo.hasFramework() || !treeRepo.looksLikeTreeRepo()) {
|
|
405
|
+
console.error("Error: `context-tree publish` must run from a dedicated tree repo (or use `--tree-path` to point at one). Run `context-tree init` first.");
|
|
406
|
+
return 1;
|
|
407
|
+
}
|
|
408
|
+
const sourceRepoRoot = resolveSourceRepoRoot(treeRepo, options);
|
|
409
|
+
if (sourceRepoRoot === null) {
|
|
410
|
+
console.error("Error: could not determine the source/workspace repo for this tree. Re-run `context-tree init` from the source repo first, or pass `--source-repo PATH`.");
|
|
411
|
+
return 1;
|
|
412
|
+
}
|
|
413
|
+
const sourceRepo = new Repo(sourceRepoRoot);
|
|
414
|
+
if (!sourceRepo.isGitRepo()) {
|
|
415
|
+
console.error(`Error: the resolved source/workspace repo is not a git repository: ${sourceRepoRoot}`);
|
|
416
|
+
return 1;
|
|
417
|
+
}
|
|
418
|
+
if (sourceRepo.root === treeRepo.root) {
|
|
419
|
+
console.error("Error: the source/workspace repo and dedicated tree repo resolved to the same path. `context-tree publish` expects two separate repos.");
|
|
420
|
+
return 1;
|
|
421
|
+
}
|
|
422
|
+
if (!sourceRepo.hasCurrentInstalledSkill() || !sourceRepo.hasSourceWorkspaceIntegration()) {
|
|
423
|
+
console.error("Error: the source/workspace repo does not have the first-tree source integration installed. Run `context-tree init` from the source/workspace repo first.");
|
|
424
|
+
return 1;
|
|
425
|
+
}
|
|
426
|
+
const submodulePath = normalizeSubmodulePath(options?.submodulePath ?? treeRepo.repoName());
|
|
427
|
+
if (submodulePath === null) {
|
|
428
|
+
console.error("Error: `--submodule-path` must be a relative path inside the source/workspace repo.");
|
|
429
|
+
return 1;
|
|
430
|
+
}
|
|
431
|
+
const sourceRemoteName = options?.sourceRemote ?? "origin";
|
|
432
|
+
try {
|
|
433
|
+
console.log("Context Tree Publish\n");
|
|
434
|
+
console.log(` Tree repo: ${treeRepo.root}`);
|
|
435
|
+
console.log(` Source repo: ${sourceRepo.root}\n`);
|
|
436
|
+
const sourceRemoteUrl = getGitRemoteUrl(runner, sourceRepo.root, sourceRemoteName);
|
|
437
|
+
if (sourceRemoteUrl === null) throw new Error(`Could not read git remote \`${sourceRemoteName}\` from the source/workspace repo.`);
|
|
438
|
+
const sourceGitHub = parseGitHubRemote(sourceRemoteUrl);
|
|
439
|
+
if (sourceGitHub === null) throw new Error(`The source/workspace remote \`${sourceRemoteName}\` is not a GitHub remote: ${sourceRemoteUrl}`);
|
|
440
|
+
const sourceMetadata = readGitHubRepoMetadata(runner, sourceGitHub.slug, sourceRepo.root);
|
|
441
|
+
const treeSlug = `${sourceGitHub.owner}/${treeRepo.repoName()}`;
|
|
442
|
+
if (commitTreeState(runner, treeRepo)) console.log(" Committed the current tree state.");
|
|
443
|
+
else console.log(" Tree repo already had a committed working state.");
|
|
444
|
+
const treeRemote = ensureTreeRemotePublished(runner, treeRepo, treeSlug, sourceGitHub.cloneStyle, sourceMetadata.visibility);
|
|
445
|
+
if (treeRemote.createdRemote) console.log(` Created and pushed ${treeSlug}.`);
|
|
446
|
+
else console.log(` Pushed the tree repo to ${treeRemote.remoteUrl}.`);
|
|
447
|
+
const sourceBranch = ensureSourceBranch(runner, sourceRepo, sourceRemoteName, sourceMetadata.defaultBranch, treeRepo.repoName());
|
|
448
|
+
console.log(` Working on source/workspace branch \`${sourceBranch}\`.`);
|
|
449
|
+
const submoduleAction = ensureSubmodule(runner, sourceRepo, submodulePath, treeRemote.remoteUrl);
|
|
450
|
+
console.log(submoduleAction === "added" ? ` Added \`${submodulePath}\` as a git submodule.` : ` Updated the \`${submodulePath}\` submodule URL and checkout.`);
|
|
451
|
+
const committedSourceChanges = commitSourceIntegration(runner, sourceRepo, submodulePath, treeRepo.repoName());
|
|
452
|
+
if (committedSourceChanges) console.log(" Committed the source/workspace integration branch.");
|
|
453
|
+
else console.log(" Source/workspace integration was already up to date; no new commit was needed.");
|
|
454
|
+
if (committedSourceChanges || options?.openPr) {
|
|
455
|
+
runner("git", [
|
|
456
|
+
"push",
|
|
457
|
+
"-u",
|
|
458
|
+
sourceRemoteName,
|
|
459
|
+
sourceBranch
|
|
460
|
+
], { cwd: sourceRepo.root });
|
|
461
|
+
console.log(` Pushed \`${sourceBranch}\` to \`${sourceRemoteName}\`.`);
|
|
462
|
+
}
|
|
463
|
+
if (options?.openPr) {
|
|
464
|
+
const prUrl = runner("gh", [
|
|
465
|
+
"pr",
|
|
466
|
+
"create",
|
|
467
|
+
"--repo",
|
|
468
|
+
sourceMetadata.nameWithOwner,
|
|
469
|
+
"--base",
|
|
470
|
+
sourceMetadata.defaultBranch,
|
|
471
|
+
"--head",
|
|
472
|
+
sourceBranch,
|
|
473
|
+
"--title",
|
|
474
|
+
`chore: connect ${treeRepo.repoName()} context tree`,
|
|
475
|
+
"--body",
|
|
476
|
+
buildPrBody(treeRepo.repoName(), treeSlug, submodulePath)
|
|
477
|
+
], { cwd: sourceRepo.root });
|
|
478
|
+
console.log(` Opened PR: ${prUrl}`);
|
|
479
|
+
}
|
|
480
|
+
console.log();
|
|
481
|
+
console.log(`The source/workspace repo's \`${submodulePath}\` checkout is now the canonical local working copy for this tree.`);
|
|
482
|
+
console.log(`You can delete the temporary bootstrap checkout at ${treeRepo.root} once you no longer need it.`);
|
|
483
|
+
return 0;
|
|
484
|
+
} catch (err) {
|
|
485
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
486
|
+
console.error(`Error: ${message}`);
|
|
487
|
+
return 1;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function runPublishCli(args = []) {
|
|
491
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
492
|
+
console.log(PUBLISH_USAGE);
|
|
493
|
+
return 0;
|
|
494
|
+
}
|
|
495
|
+
const parsed = parsePublishArgs(args);
|
|
496
|
+
if ("error" in parsed) {
|
|
497
|
+
console.error(parsed.error);
|
|
498
|
+
console.log(PUBLISH_USAGE);
|
|
499
|
+
return 1;
|
|
500
|
+
}
|
|
501
|
+
return runPublish(void 0, parsed);
|
|
502
|
+
}
|
|
503
|
+
//#endregion
|
|
504
|
+
export { runPublishCli as runPublish };
|
|
@@ -17,6 +17,7 @@ join(FRAMEWORK_ASSET_ROOT, "prompts");
|
|
|
17
17
|
const FRAMEWORK_EXAMPLES_DIR = join(FRAMEWORK_ASSET_ROOT, "examples");
|
|
18
18
|
join(FRAMEWORK_ASSET_ROOT, "helpers");
|
|
19
19
|
const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
|
|
20
|
+
const BOOTSTRAP_STATE = join(SKILL_ROOT, "bootstrap.json");
|
|
20
21
|
const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
|
|
21
22
|
const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
|
|
22
23
|
const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
|
|
@@ -372,4 +373,4 @@ function isDirectory(root, relPath) {
|
|
|
372
373
|
}
|
|
373
374
|
}
|
|
374
375
|
//#endregion
|
|
375
|
-
export {
|
|
376
|
+
export { SKILL_ROOT as C, installedSkillRootsDisplay as E, SKILL_NAME as S, SOURCE_INTEGRATION_MARKER as T, LEGACY_AGENT_INSTRUCTIONS_FILE as _, BOOTSTRAP_STATE as a, LEGACY_REPO_SKILL_EXAMPLES_DIR as b, CLAUDE_INSTRUCTIONS_FILE as c, FRAMEWORK_EXAMPLES_DIR as d, FRAMEWORK_TEMPLATES_DIR as f, INSTALLED_SKILL_ROOTS as g, INSTALLED_PROGRESS as h, AGENT_INSTRUCTIONS_TEMPLATE as i, CLAUDE_SKILL_ROOT as l, FRAMEWORK_WORKFLOWS_DIR as m, Repo as n, BUNDLED_SKILL_ROOT as o, FRAMEWORK_VERSION as p, AGENT_INSTRUCTIONS_FILE as r, CLAUDE_FRAMEWORK_EXAMPLES_DIR as s, FRAMEWORK_END_MARKER as t, FRAMEWORK_ASSET_ROOT as u, LEGACY_EXAMPLES_DIR as v, SOURCE_INTEGRATION_FILES as w, LEGACY_REPO_SKILL_ROOT as x, LEGACY_FRAMEWORK_ROOT as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { T as SOURCE_INTEGRATION_MARKER, g as INSTALLED_SKILL_ROOTS, o as BUNDLED_SKILL_ROOT, w as SOURCE_INTEGRATION_FILES, x as LEGACY_REPO_SKILL_ROOT } from "./repo-BeVpMoHi.js";
|
|
2
2
|
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as resolveCanonicalSkillRoot, i as resolveBundledPackageRoot, n as copyCanonicalSkill, t as upsertSourceIntegrationFiles } from "./source-integration-
|
|
1
|
+
import { C as SKILL_ROOT, E as installedSkillRootsDisplay, T as SOURCE_INTEGRATION_MARKER, _ as LEGACY_AGENT_INSTRUCTIONS_FILE, c as CLAUDE_INSTRUCTIONS_FILE, f as FRAMEWORK_TEMPLATES_DIR, h as INSTALLED_PROGRESS, i as AGENT_INSTRUCTIONS_TEMPLATE, l as CLAUDE_SKILL_ROOT, m as FRAMEWORK_WORKFLOWS_DIR, n as Repo, p as FRAMEWORK_VERSION, r as AGENT_INSTRUCTIONS_FILE, x as LEGACY_REPO_SKILL_ROOT, y as LEGACY_FRAMEWORK_ROOT } from "./repo-BeVpMoHi.js";
|
|
2
|
+
import { a as resolveCanonicalSkillRoot, i as resolveBundledPackageRoot, n as copyCanonicalSkill, t as upsertSourceIntegrationFiles } from "./source-integration-DMxnl8Dw.js";
|
|
3
3
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
//#region skills/first-tree/engine/runtime/upgrader.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as SKILL_ROOT, S as SKILL_NAME, _ as LEGACY_AGENT_INSTRUCTIONS_FILE, n as Repo, r as AGENT_INSTRUCTIONS_FILE } from "./repo-BeVpMoHi.js";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { join, relative, resolve } from "node:path";
|
|
4
4
|
//#region skills/first-tree/engine/validators/members.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: first-tree
|
|
3
|
-
description: Maintain the canonical `first-tree` skill and the thin `context-tree` CLI distributed by the `first-tree` npm package. Use when modifying `context-tree` commands (`init`, `verify`, `upgrade`, `help onboarding`), the installed skill payload under `assets/framework/`, maintainer references, or the build, packaging, test, and CI wiring that supports the framework.
|
|
3
|
+
description: Maintain the canonical `first-tree` skill and the thin `context-tree` CLI distributed by the `first-tree` npm package. Use when modifying `context-tree` commands (`init`, `publish`, `verify`, `upgrade`, `help onboarding`), the installed skill payload under `assets/framework/`, maintainer references, or the build, packaging, test, and CI wiring that supports the framework.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# First Tree
|
|
@@ -72,16 +72,25 @@ repos.
|
|
|
72
72
|
into the source/workspace repo and scaffolds tree files only in the
|
|
73
73
|
dedicated tree repo. Use `--here` to initialize the current repo in place
|
|
74
74
|
when you are already inside the tree repo.
|
|
75
|
+
- `context-tree publish --open-pr` is the default second-stage command after
|
|
76
|
+
`init` for source/workspace installs. Run it from the dedicated tree repo
|
|
77
|
+
once the initial tree version is ready to push.
|
|
75
78
|
- Never run `context-tree init --here` in a source/workspace repo unless the
|
|
76
79
|
user explicitly wants that repo itself to become the dedicated Context Tree.
|
|
77
80
|
`--here` is for when you have already switched into the `*-context` repo.
|
|
78
81
|
- `context-tree init` installs this skill into the target tree repo and
|
|
79
82
|
scaffolds `.agents/skills/first-tree/`, `.claude/skills/first-tree/`,
|
|
80
83
|
`NODE.md`, `AGENTS.md`, and `members/NODE.md`.
|
|
81
|
-
- The default source/workspace workflow is:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
- The default source/workspace workflow is: run `context-tree init` from the
|
|
85
|
+
source repo, draft the first tree version in `<repo>-context`, then run
|
|
86
|
+
`context-tree publish --open-pr` from that dedicated tree repo.
|
|
87
|
+
- After `context-tree publish` succeeds, treat the source/workspace repo's
|
|
88
|
+
submodule checkout as the canonical local working copy for the tree. The
|
|
89
|
+
temporary sibling bootstrap checkout can be deleted when you no longer need
|
|
90
|
+
it.
|
|
91
|
+
- If the dedicated tree repo was initialized manually with `context-tree init --here`
|
|
92
|
+
and does not have bootstrap metadata yet, pass `--source-repo PATH` to
|
|
93
|
+
`context-tree publish`.
|
|
85
94
|
- If permissions, auth, or local filesystem constraints block the dedicated
|
|
86
95
|
repo workflow, stop and report the blocker. Do not fall back to in-place tree
|
|
87
96
|
bootstrap in the source/workspace repo.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.2
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
renderTemplateFile,
|
|
17
17
|
resolveBundledPackageRoot,
|
|
18
18
|
} from "#skill/engine/runtime/installer.js";
|
|
19
|
+
import { writeBootstrapState } from "#skill/engine/runtime/bootstrap.js";
|
|
19
20
|
import {
|
|
20
21
|
AGENT_INSTRUCTIONS_FILE,
|
|
21
22
|
AGENT_INSTRUCTIONS_TEMPLATE,
|
|
@@ -122,13 +123,10 @@ export function formatTaskList(
|
|
|
122
123
|
);
|
|
123
124
|
lines.push("## Source Workspace Workflow");
|
|
124
125
|
lines.push(
|
|
125
|
-
`- [ ]
|
|
126
|
+
`- [ ] When this initial tree version is ready, run \`context-tree publish --open-pr\` from this dedicated tree repo. It will create or reuse the GitHub \`*-context\` repo, add it back to \`${context.sourceRepoName}\` as a git submodule, and open the source/workspace PR.`,
|
|
126
127
|
);
|
|
127
128
|
lines.push(
|
|
128
|
-
`- [ ]
|
|
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`,
|
|
129
|
+
`- [ ] After publish succeeds, treat the source/workspace repo's \`${context.sourceRepoName}\` submodule checkout as the canonical local working copy for this tree. The temporary sibling bootstrap repo can be deleted when you no longer need it.`,
|
|
132
130
|
);
|
|
133
131
|
lines.push("");
|
|
134
132
|
}
|
|
@@ -299,6 +297,14 @@ export function runInit(repo?: Repo, options?: InitOptions): number {
|
|
|
299
297
|
}
|
|
300
298
|
}
|
|
301
299
|
|
|
300
|
+
if (initTarget.dedicatedTreeRepo) {
|
|
301
|
+
writeBootstrapState(r.root, {
|
|
302
|
+
sourceRepoName: sourceRepo.repoName(),
|
|
303
|
+
sourceRepoPath: relativePathFrom(r.root, sourceRepo.root),
|
|
304
|
+
treeRepoName: r.repoName(),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
302
308
|
console.log(ONBOARDING_TEXT);
|
|
303
309
|
console.log("---\n");
|
|
304
310
|
|