first-tree 0.0.6 → 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 +16 -5
- package/dist/bootstrap-YRjfHJp7.js +28 -0
- package/dist/cli.js +14 -5
- package/dist/{help-DV9-AaFp.js → help-CDfaFrzl.js} +1 -1
- package/dist/{init-BgGH2_yC.js → init-DjSVkUeR.js} +19 -8
- package/dist/onboarding-BiHx2jy5.js +10 -0
- package/dist/onboarding-Ce033qaW.js +2 -0
- package/dist/publish-D0crNDjz.js +504 -0
- package/dist/{repo-Cc5U4DWT.js → repo-BeVpMoHi.js} +2 -15
- package/dist/{source-integration-CuKjoheT.js → source-integration-DMxnl8Dw.js} +2 -6
- package/dist/{upgrade-BvA9oKmi.js → upgrade-B_NTlNrx.js} +2 -4
- package/dist/{verify-G8gNXzDX.js → verify-Chhm1vOF.js} +3 -3
- package/package.json +1 -1
- package/skills/first-tree/SKILL.md +25 -6
- package/skills/first-tree/agents/openai.yaml +1 -1
- 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 +24 -6
- package/skills/first-tree/engine/publish.ts +807 -0
- package/skills/first-tree/engine/repo.ts +0 -8
- package/skills/first-tree/engine/runtime/adapters.ts +0 -2
- package/skills/first-tree/engine/runtime/asset-loader.ts +1 -36
- package/skills/first-tree/engine/runtime/bootstrap.ts +40 -0
- package/skills/first-tree/engine/runtime/installer.ts +0 -2
- package/skills/first-tree/engine/upgrade.ts +0 -11
- package/skills/first-tree/engine/validators/nodes.ts +2 -11
- 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 +18 -12
- package/skills/first-tree/references/source-map.md +3 -1
- package/skills/first-tree/references/source-workspace-installation.md +25 -13
- package/skills/first-tree/references/upgrade-contract.md +15 -8
- package/skills/first-tree/scripts/check-skill-sync.sh +0 -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 +25 -0
- package/skills/first-tree/tests/publish.test.ts +248 -0
- package/skills/first-tree/tests/repo.test.ts +0 -25
- package/skills/first-tree/tests/skill-artifacts.test.ts +16 -1
- package/skills/first-tree/tests/thin-cli.test.ts +6 -0
- package/skills/first-tree/tests/upgrade.test.ts +0 -21
- package/dist/onboarding-D7fGGOMN.js +0 -10
- package/dist/onboarding-lASHHmgO.js +0 -2
|
@@ -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";
|
|
@@ -46,15 +47,6 @@ join(LEGACY_REPO_SKILL_ASSET_ROOT, "prompts");
|
|
|
46
47
|
const LEGACY_REPO_SKILL_EXAMPLES_DIR = join(LEGACY_REPO_SKILL_ASSET_ROOT, "examples");
|
|
47
48
|
join(LEGACY_REPO_SKILL_ASSET_ROOT, "helpers");
|
|
48
49
|
const LEGACY_REPO_SKILL_PROGRESS = join(LEGACY_REPO_SKILL_ROOT, "progress.md");
|
|
49
|
-
const LEGACY_SKILL_NAME = "first-tree-cli-framework";
|
|
50
|
-
const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
|
|
51
|
-
const LEGACY_SKILL_ASSET_ROOT = join(LEGACY_SKILL_ROOT, "assets", "framework");
|
|
52
|
-
const LEGACY_SKILL_VERSION = join(LEGACY_SKILL_ASSET_ROOT, "VERSION");
|
|
53
|
-
join(LEGACY_SKILL_ASSET_ROOT, "templates");
|
|
54
|
-
join(LEGACY_SKILL_ASSET_ROOT, "workflows");
|
|
55
|
-
join(LEGACY_SKILL_ASSET_ROOT, "prompts");
|
|
56
|
-
const LEGACY_SKILL_EXAMPLES_DIR = join(LEGACY_SKILL_ASSET_ROOT, "examples");
|
|
57
|
-
const LEGACY_SKILL_PROGRESS = join(LEGACY_SKILL_ROOT, "progress.md");
|
|
58
50
|
const LEGACY_FRAMEWORK_ROOT = ".context-tree";
|
|
59
51
|
const LEGACY_VERSION = join(LEGACY_FRAMEWORK_ROOT, "VERSION");
|
|
60
52
|
const LEGACY_PROGRESS = join(LEGACY_FRAMEWORK_ROOT, "progress.md");
|
|
@@ -81,7 +73,6 @@ function frameworkVersionCandidates() {
|
|
|
81
73
|
FRAMEWORK_VERSION,
|
|
82
74
|
CLAUDE_FRAMEWORK_VERSION,
|
|
83
75
|
LEGACY_REPO_SKILL_VERSION,
|
|
84
|
-
LEGACY_SKILL_VERSION,
|
|
85
76
|
LEGACY_VERSION
|
|
86
77
|
];
|
|
87
78
|
}
|
|
@@ -90,7 +81,6 @@ function progressFileCandidates() {
|
|
|
90
81
|
INSTALLED_PROGRESS,
|
|
91
82
|
CLAUDE_INSTALLED_PROGRESS,
|
|
92
83
|
LEGACY_REPO_SKILL_PROGRESS,
|
|
93
|
-
LEGACY_SKILL_PROGRESS,
|
|
94
84
|
LEGACY_PROGRESS
|
|
95
85
|
];
|
|
96
86
|
}
|
|
@@ -105,7 +95,6 @@ function detectFrameworkLayout(root) {
|
|
|
105
95
|
if (pathExists(root, FRAMEWORK_VERSION)) return "skill";
|
|
106
96
|
if (pathExists(root, CLAUDE_FRAMEWORK_VERSION)) return "claude-skill";
|
|
107
97
|
if (pathExists(root, LEGACY_REPO_SKILL_VERSION)) return "legacy-repo-skill";
|
|
108
|
-
if (pathExists(root, LEGACY_SKILL_VERSION)) return "legacy-skill";
|
|
109
98
|
if (pathExists(root, LEGACY_VERSION)) return "legacy";
|
|
110
99
|
return null;
|
|
111
100
|
}
|
|
@@ -261,7 +250,6 @@ var Repo = class {
|
|
|
261
250
|
preferredProgressPath() {
|
|
262
251
|
const layout = this.frameworkLayout();
|
|
263
252
|
if (layout === "legacy") return LEGACY_PROGRESS;
|
|
264
|
-
if (layout === "legacy-skill") return LEGACY_SKILL_PROGRESS;
|
|
265
253
|
if (layout === "legacy-repo-skill") return LEGACY_REPO_SKILL_PROGRESS;
|
|
266
254
|
if (layout === "claude-skill") return CLAUDE_INSTALLED_PROGRESS;
|
|
267
255
|
return INSTALLED_PROGRESS;
|
|
@@ -269,7 +257,6 @@ var Repo = class {
|
|
|
269
257
|
frameworkVersionPath() {
|
|
270
258
|
const layout = this.frameworkLayout();
|
|
271
259
|
if (layout === "legacy") return LEGACY_VERSION;
|
|
272
|
-
if (layout === "legacy-skill") return LEGACY_SKILL_VERSION;
|
|
273
260
|
if (layout === "legacy-repo-skill") return LEGACY_REPO_SKILL_VERSION;
|
|
274
261
|
if (layout === "claude-skill") return CLAUDE_FRAMEWORK_VERSION;
|
|
275
262
|
return FRAMEWORK_VERSION;
|
|
@@ -386,4 +373,4 @@ function isDirectory(root, relPath) {
|
|
|
386
373
|
}
|
|
387
374
|
}
|
|
388
375
|
//#endregion
|
|
389
|
-
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";
|
|
@@ -22,11 +22,7 @@ function resolveCanonicalSkillRoot(sourceRoot) {
|
|
|
22
22
|
}
|
|
23
23
|
function copyCanonicalSkill(sourceRoot, targetRoot) {
|
|
24
24
|
const src = resolveCanonicalSkillRoot(sourceRoot);
|
|
25
|
-
for (const relPath of [
|
|
26
|
-
...INSTALLED_SKILL_ROOTS,
|
|
27
|
-
LEGACY_REPO_SKILL_ROOT,
|
|
28
|
-
LEGACY_SKILL_ROOT
|
|
29
|
-
]) {
|
|
25
|
+
for (const relPath of [...INSTALLED_SKILL_ROOTS, LEGACY_REPO_SKILL_ROOT]) {
|
|
30
26
|
const fullPath = join(targetRoot, relPath);
|
|
31
27
|
if (existsSync(fullPath)) rmSync(fullPath, {
|
|
32
28
|
recursive: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as
|
|
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
|
|
@@ -46,7 +46,6 @@ function formatUpgradeTaskList(repo, localVersion, packagedVersion, layout) {
|
|
|
46
46
|
const migrationTasks = [];
|
|
47
47
|
if (layout === "legacy") migrationTasks.push("- [ ] Remove any stale `.context-tree/` references from repo-specific docs, scripts, or workflow files");
|
|
48
48
|
if (layout === "legacy-repo-skill") lines.push("## Migration", `- [ ] Remove any stale \`${LEGACY_REPO_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`, "");
|
|
49
|
-
if (layout === "legacy-skill") migrationTasks.push(`- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`);
|
|
50
49
|
if (repo.hasCanonicalAgentInstructionsFile() && repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Merge any remaining user-authored content from \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` into \`${AGENT_INSTRUCTIONS_FILE}\`, then delete the legacy file`);
|
|
51
50
|
else if (repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\` to use the canonical agent instructions filename`);
|
|
52
51
|
if (migrationTasks.length > 0) lines.push("## Migration", ...migrationTasks, "");
|
|
@@ -125,7 +124,6 @@ function runUpgrade(repo, options) {
|
|
|
125
124
|
});
|
|
126
125
|
console.log(`Migrated legacy .context-tree/ layout to ${installedSkillRootsDisplay()}.`);
|
|
127
126
|
} else if (layout === "legacy-repo-skill") console.log(`Migrated legacy ${LEGACY_REPO_SKILL_ROOT}/ layout to ${installedSkillRootsDisplay()}.`);
|
|
128
|
-
else if (layout === "legacy-skill") console.log(`Migrated ${LEGACY_SKILL_ROOT}/ to ${installedSkillRootsDisplay()}.`);
|
|
129
127
|
else if (missingInstalledRoots.length > 0) console.log(`Repaired missing installed skill roots (${missingInstalledRoots.map((root) => `${root}/`).join(", ")}) and refreshed ${installedSkillRootsDisplay()} from the bundled first-tree package.`);
|
|
130
128
|
else console.log(`Refreshed ${installedSkillRootsDisplay()} from the bundled first-tree package.`);
|
|
131
129
|
const output = formatUpgradeTaskList(workingRepo, localVersion, packagedVersion, layout);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
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
|
|
@@ -186,14 +186,14 @@ function rel(path) {
|
|
|
186
186
|
return relative(treeRoot, path);
|
|
187
187
|
}
|
|
188
188
|
function isInstalledSkillPath(relPath) {
|
|
189
|
-
return relPath === SKILL_ROOT || relPath.startsWith(`${SKILL_ROOT}/`)
|
|
189
|
+
return relPath === SKILL_ROOT || relPath.startsWith(`${SKILL_ROOT}/`);
|
|
190
190
|
}
|
|
191
191
|
function isFrameworkContainerDir(relPath, fullPath) {
|
|
192
192
|
if (relPath !== "skills") return false;
|
|
193
193
|
try {
|
|
194
194
|
const entries = readdirSync(fullPath).filter((entry) => !entry.startsWith("."));
|
|
195
195
|
if (entries.length === 0) return false;
|
|
196
|
-
return entries.every((entry) => entry ===
|
|
196
|
+
return entries.every((entry) => entry === SKILL_NAME);
|
|
197
197
|
} catch {
|
|
198
198
|
return false;
|
|
199
199
|
}
|
package/package.json
CHANGED