first-tree 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -27
- package/dist/cli.js +28 -13
- package/dist/{help-xEI-s9iN.js → help-5-WG9QFm.js} +1 -1
- package/dist/{init-DtOjj0wc.js → init-CAq0Uhq6.js} +187 -25
- package/dist/{installer-rcZpGLnM.js → installer-UgNasLjl.js} +20 -16
- package/dist/onboarding-3zYUeYQb.js +2 -0
- package/dist/onboarding-Dd63N-V1.js +10 -0
- package/dist/repo-DkR12VUv.js +369 -0
- package/dist/upgrade-DYzuvv1k.js +140 -0
- package/dist/{verify-CxN6JiV9.js → verify-C0IUSkMZ.js} +66 -6
- package/package.json +12 -10
- package/skills/first-tree/SKILL.md +18 -10
- package/skills/first-tree/assets/framework/VERSION +1 -1
- package/skills/first-tree/assets/framework/examples/claude-code/README.md +2 -2
- package/skills/first-tree/assets/framework/examples/claude-code/settings.json +1 -1
- package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +1 -1
- package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +0 -0
- package/skills/first-tree/assets/framework/helpers/run-review.ts +17 -3
- package/skills/first-tree/assets/framework/templates/{agent.md.template → agents.md.template} +3 -2
- package/skills/first-tree/assets/framework/templates/members-domain.md.template +1 -1
- package/skills/first-tree/assets/framework/templates/root-node.md.template +9 -6
- package/skills/first-tree/assets/framework/workflows/codeowners.yml +1 -1
- package/skills/first-tree/assets/framework/workflows/pr-review.yml +1 -1
- package/skills/first-tree/engine/commands/init.ts +1 -1
- package/skills/first-tree/engine/commands/upgrade.ts +1 -1
- package/skills/first-tree/engine/commands/verify.ts +1 -1
- package/skills/first-tree/engine/init.ts +288 -18
- package/skills/first-tree/engine/repo.ts +220 -11
- package/skills/first-tree/engine/rules/agent-instructions.ts +29 -7
- package/skills/first-tree/engine/rules/agent-integration.ts +3 -1
- package/skills/first-tree/engine/rules/framework.ts +2 -2
- package/skills/first-tree/engine/runtime/adapters.ts +6 -2
- package/skills/first-tree/engine/runtime/asset-loader.ts +143 -4
- package/skills/first-tree/engine/runtime/installer.ts +18 -12
- package/skills/first-tree/engine/upgrade.ts +99 -15
- package/skills/first-tree/engine/validators/nodes.ts +48 -3
- package/skills/first-tree/engine/verify.ts +61 -3
- package/skills/first-tree/references/maintainer-architecture.md +1 -1
- package/skills/first-tree/references/maintainer-build-and-distribution.md +3 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +1 -1
- package/skills/first-tree/references/onboarding.md +57 -24
- package/skills/first-tree/references/source-map.md +3 -3
- package/skills/first-tree/references/upgrade-contract.md +62 -27
- package/skills/first-tree/scripts/check-skill-sync.sh +1 -1
- package/skills/first-tree/scripts/quick_validate.py +0 -0
- package/skills/first-tree/scripts/run-local-cli.sh +0 -0
- package/skills/first-tree/tests/asset-loader.test.ts +23 -1
- package/skills/first-tree/tests/helpers.ts +51 -7
- package/skills/first-tree/tests/init.test.ts +113 -8
- package/skills/first-tree/tests/repo.test.ts +113 -9
- package/skills/first-tree/tests/rules.test.ts +35 -14
- package/skills/first-tree/tests/skill-artifacts.test.ts +10 -0
- package/skills/first-tree/tests/thin-cli.test.ts +52 -7
- package/skills/first-tree/tests/upgrade.test.ts +39 -6
- package/skills/first-tree/tests/verify.test.ts +109 -10
- package/dist/onboarding-6Fr5Gkrk.js +0 -2
- package/dist/onboarding-B9zPGvvG.js +0 -10
- package/dist/repo-BTJG8BU1.js +0 -187
- package/dist/upgrade-COGgI7Rj.js +0 -96
|
@@ -6,8 +6,9 @@ description: Maintain the canonical `first-tree` skill and the thin `context-tre
|
|
|
6
6
|
# First Tree
|
|
7
7
|
|
|
8
8
|
Use this skill when the task depends on the exact behavior of the
|
|
9
|
-
`context-tree` CLI or the installed
|
|
10
|
-
`context-tree init` ships to user
|
|
9
|
+
`context-tree` CLI or the installed `.agents/skills/first-tree/` and
|
|
10
|
+
`.claude/skills/first-tree/` payloads that `context-tree init` ships to user
|
|
11
|
+
repos.
|
|
11
12
|
|
|
12
13
|
## Source Of Truth
|
|
13
14
|
|
|
@@ -17,9 +18,9 @@ Use this skill when the task depends on the exact behavior of the
|
|
|
17
18
|
repos.
|
|
18
19
|
- `engine/` holds the canonical framework and CLI behavior.
|
|
19
20
|
- `scripts/` holds maintenance helpers for validating and running the skill.
|
|
20
|
-
- In maintainer docs, use `context-tree` for the CLI
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
- In maintainer docs, use `context-tree` for the CLI, `skills/first-tree/` for
|
|
22
|
+
the bundled source path, and `.agents/skills/first-tree/` /
|
|
23
|
+
`.claude/skills/first-tree/` for installed user-repo paths.
|
|
23
24
|
|
|
24
25
|
## When To Read What
|
|
25
26
|
|
|
@@ -51,18 +52,22 @@ Use this skill when the task depends on the exact behavior of the
|
|
|
51
52
|
thin CLI shell, not as a tree repo.
|
|
52
53
|
- Keep command behavior, validator behavior, shipped assets, maintainer
|
|
53
54
|
references, and package shell aligned.
|
|
54
|
-
- If root README/
|
|
55
|
+
- If root README/AGENTS/CI text explains something non-obvious, migrate that
|
|
55
56
|
information into `references/` and trim the root file back down.
|
|
56
57
|
- If you change runtime assets or skill references, run `pnpm validate:skill`.
|
|
57
58
|
|
|
58
59
|
### Working In A User Tree Repo
|
|
59
60
|
|
|
60
|
-
- `context-tree init`
|
|
61
|
-
|
|
61
|
+
- `context-tree init` defaults to creating or reusing a sibling dedicated tree
|
|
62
|
+
repo when invoked from a source/workspace repo. Use `--here` to initialize
|
|
63
|
+
the current repo in place when you are already inside the tree repo.
|
|
64
|
+
- `context-tree init` installs this skill into the target tree repo and
|
|
65
|
+
scaffolds `.agents/skills/first-tree/`, `.claude/skills/first-tree/`,
|
|
66
|
+
`NODE.md`, `AGENTS.md`, and `members/NODE.md`.
|
|
62
67
|
- `context-tree upgrade` refreshes the installed skill from the copy bundled
|
|
63
68
|
with the currently running `first-tree` package. To pick up a newer
|
|
64
69
|
framework, run a newer package version first. It also migrates older repos
|
|
65
|
-
that still use `skills/first-tree-cli-framework/`.
|
|
70
|
+
that still use `skills/first-tree/` or `skills/first-tree-cli-framework/`.
|
|
66
71
|
- The user's tree content lives outside the skill; the skill only carries the
|
|
67
72
|
reusable framework payload plus maintenance guidance.
|
|
68
73
|
- The tree still stores decisions, constraints, and ownership; execution detail
|
|
@@ -76,8 +81,11 @@ Use this skill when the task depends on the exact behavior of the
|
|
|
76
81
|
- Keep decision knowledge in the tree and execution detail in source systems.
|
|
77
82
|
- Keep the skill as the only canonical knowledge source. The root CLI/package
|
|
78
83
|
shell must not become a second source of framework semantics.
|
|
84
|
+
- Keep the CLI name written as `context-tree` in maintainer and user-facing
|
|
85
|
+
docs so it is not confused with the `first-tree` package that ships it.
|
|
79
86
|
- Keep normal `init` / `upgrade` flows self-contained. They must work from the
|
|
80
|
-
skill bundled in the current package without cloning the source repo
|
|
87
|
+
skill bundled in the current package without cloning the source repo or
|
|
88
|
+
relying on network access.
|
|
81
89
|
- Make upgrade behavior explicit. If you change installed paths, update
|
|
82
90
|
`references/upgrade-contract.md`, task text, and tests together.
|
|
83
91
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1
|
|
1
|
+
0.2.1
|
|
@@ -6,9 +6,9 @@ Copy `settings.json` to your tree repo's `.claude/` directory:
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
mkdir -p .claude
|
|
9
|
-
cp skills/first-tree/assets/framework/examples/claude-code/settings.json .claude/settings.json
|
|
9
|
+
cp .claude/skills/first-tree/assets/framework/examples/claude-code/settings.json .claude/settings.json
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
## What It Does
|
|
13
13
|
|
|
14
|
-
The `SessionStart` hook runs
|
|
14
|
+
The `SessionStart` hook runs `./.claude/skills/first-tree/assets/framework/helpers/inject-tree-context.sh` when a Claude Code session begins. This injects the root `NODE.md` content as additional context, giving the agent an overview of the tree structure before any task.
|
|
@@ -205,7 +205,7 @@ export function generate(
|
|
|
205
205
|
return 0;
|
|
206
206
|
}
|
|
207
207
|
console.log(
|
|
208
|
-
"CODEOWNERS is out-of-date. Run: npx tsx skills/first-tree/assets/framework/helpers/generate-codeowners.ts",
|
|
208
|
+
"CODEOWNERS is out-of-date. Run: npx tsx .agents/skills/first-tree/assets/framework/helpers/generate-codeowners.ts",
|
|
209
209
|
);
|
|
210
210
|
return 1;
|
|
211
211
|
}
|
|
File without changes
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
|
|
@@ -17,15 +17,29 @@ const MAX_ATTEMPTS = 3;
|
|
|
17
17
|
// Per-invocation budget cap. Worst case is $1.50 total (3 × $0.50),
|
|
18
18
|
// though retries are cheap in practice due to cached context via --continue.
|
|
19
19
|
const MAX_BUDGET_USD = 0.5;
|
|
20
|
+
const AGENT_INSTRUCTIONS_PATHS = ["AGENTS.md", "AGENT.md"] as const;
|
|
21
|
+
|
|
22
|
+
function resolveAgentInstructionsPath(): string {
|
|
23
|
+
for (const candidate of AGENT_INSTRUCTIONS_PATHS) {
|
|
24
|
+
if (existsSync(candidate)) {
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
throw new Error(
|
|
30
|
+
"Missing AGENTS.md in repo root (legacy AGENT.md is also accepted during migration).",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
20
33
|
|
|
21
34
|
function buildPrompt(diffPath: string): string {
|
|
22
35
|
const parts: string[] = [];
|
|
36
|
+
const agentInstructionsPath = resolveAgentInstructionsPath();
|
|
23
37
|
const files: [string, string][] = [
|
|
24
|
-
[
|
|
38
|
+
[agentInstructionsPath, agentInstructionsPath],
|
|
25
39
|
["Root NODE.md", "NODE.md"],
|
|
26
40
|
[
|
|
27
41
|
"Review Instructions",
|
|
28
|
-
"skills/first-tree/assets/framework/prompts/pr-review.md",
|
|
42
|
+
".agents/skills/first-tree/assets/framework/prompts/pr-review.md",
|
|
29
43
|
],
|
|
30
44
|
];
|
|
31
45
|
for (const [heading, path] of files) {
|
package/skills/first-tree/assets/framework/templates/{agent.md.template → agents.md.template}
RENAMED
|
@@ -13,7 +13,7 @@ You are working in a **Context Tree** — the living source of truth for decisio
|
|
|
13
13
|
|
|
14
14
|
4. **Git-native tree structure.** Each node is a file; each domain is a directory. Soft links allow cross-references without the complexity of a full graph. History, ownership, and review follow Git conventions.
|
|
15
15
|
|
|
16
|
-
See
|
|
16
|
+
See `.agents/skills/first-tree/references/principles.md` for detailed explanations and examples.
|
|
17
17
|
|
|
18
18
|
## Before Every Task
|
|
19
19
|
|
|
@@ -28,6 +28,7 @@ Do not skip this. The tree is already a compression of expensive knowledge — c
|
|
|
28
28
|
|
|
29
29
|
- **Decide in the tree, execute in source systems.** If the task involves a decision (not just a bug fix), draft or update the relevant tree node before executing.
|
|
30
30
|
- **The tree is not for execution details.** Function signatures, DB schemas, API endpoints, ad copy — those live in source systems. The tree captures the *why* and *how things connect*.
|
|
31
|
+
- **Bring source repos in as additional working directories.** When you need to inspect or edit code, open the relevant source repositories alongside this tree instead of copying execution detail into the tree.
|
|
31
32
|
- **Respect ownership.** Each node declares owners in its frontmatter. If your changes touch a domain you don't own, flag it — the owner needs to review.
|
|
32
33
|
|
|
33
34
|
## After Every Task
|
|
@@ -40,7 +41,7 @@ Ask yourself: **Does the tree need updating?**
|
|
|
40
41
|
|
|
41
42
|
## Reference
|
|
42
43
|
|
|
43
|
-
For ownership rules, tree structure, and key files, see [NODE.md](NODE.md) and
|
|
44
|
+
For ownership rules, tree structure, and key files, see [NODE.md](NODE.md) and `.agents/skills/first-tree/references/ownership-and-naming.md`.
|
|
44
45
|
<!-- END CONTEXT-TREE FRAMEWORK -->
|
|
45
46
|
|
|
46
47
|
# Project-Specific Instructions
|
|
@@ -5,9 +5,9 @@ owners: [<your-github-username>]
|
|
|
5
5
|
|
|
6
6
|
# <Your Organization>
|
|
7
7
|
|
|
8
|
-
<!-- PLACEHOLDER: Replace this with a description of your organization and what this tree captures. -->
|
|
8
|
+
<!-- PLACEHOLDER: Replace this with a description of your organization and what this tree captures across your source repos and systems. -->
|
|
9
9
|
|
|
10
|
-
The living source of truth for your organization. A structured knowledge base that agents and humans build and maintain together.
|
|
10
|
+
The living source of truth for your organization. A structured knowledge base that agents and humans build and maintain together across one or more source repositories and systems.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -29,10 +29,13 @@ The living source of truth for your organization. A structured knowledge base th
|
|
|
29
29
|
|
|
30
30
|
## Working with the Tree
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Keep decision context here. Keep implementation detail in the source repos this
|
|
33
|
+
tree describes.
|
|
33
34
|
|
|
34
|
-
See [
|
|
35
|
+
See [AGENTS.md](AGENTS.md) for agent instructions — the before/during/after workflow, ownership model, and tree maintenance.
|
|
36
|
+
|
|
37
|
+
See [about.md](.agents/skills/first-tree/references/about.md) for background — the problem, the idea, and who it's for.
|
|
35
38
|
|
|
36
39
|
See the installed framework documentation:
|
|
37
|
-
- [principles.md](skills/first-tree/references/principles.md) — core principles with examples
|
|
38
|
-
- [ownership-and-naming.md](skills/first-tree/references/ownership-and-naming.md) — node naming and ownership model
|
|
40
|
+
- [principles.md](.agents/skills/first-tree/references/principles.md) — core principles with examples
|
|
41
|
+
- [ownership-and-naming.md](.agents/skills/first-tree/references/ownership-and-naming.md) — node naming and ownership model
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
gh pr view "$PR_NUMBER" --json headRefOid -q .headRefOid > /tmp/pr-head-sha.txt
|
|
46
46
|
|
|
47
47
|
- name: Run Claude review
|
|
48
|
-
run: npx tsx skills/first-tree/assets/framework/helpers/run-review.ts
|
|
48
|
+
run: npx tsx .agents/skills/first-tree/assets/framework/helpers/run-review.ts
|
|
49
49
|
|
|
50
50
|
- name: Parse and post review
|
|
51
51
|
run: |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { INIT_USAGE, parseInitArgs, runInitCli as runInit } from "#skill/engine/init.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { UPGRADE_USAGE, runUpgradeCli as runUpgrade } from "#skill/engine/upgrade.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { VERIFY_USAGE, runVerifyCli as runVerify } from "#skill/engine/verify.js";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import {
|
|
2
3
|
existsSync,
|
|
3
4
|
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
statSync,
|
|
4
7
|
writeFileSync,
|
|
5
8
|
} from "node:fs";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
9
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
7
10
|
import { Repo } from "#skill/engine/repo.js";
|
|
8
11
|
import { ONBOARDING_TEXT } from "#skill/engine/onboarding.js";
|
|
9
12
|
import { evaluateAll } from "#skill/engine/rules/index.js";
|
|
@@ -14,9 +17,13 @@ import {
|
|
|
14
17
|
resolveBundledPackageRoot,
|
|
15
18
|
} from "#skill/engine/runtime/installer.js";
|
|
16
19
|
import {
|
|
20
|
+
AGENT_INSTRUCTIONS_FILE,
|
|
21
|
+
AGENT_INSTRUCTIONS_TEMPLATE,
|
|
17
22
|
FRAMEWORK_ASSET_ROOT,
|
|
18
23
|
FRAMEWORK_VERSION,
|
|
19
24
|
INSTALLED_PROGRESS,
|
|
25
|
+
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
26
|
+
installedSkillRootsDisplay,
|
|
20
27
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
21
28
|
|
|
22
29
|
/**
|
|
@@ -25,34 +32,88 @@ import {
|
|
|
25
32
|
* all generated task text at once.
|
|
26
33
|
*/
|
|
27
34
|
export const INTERACTIVE_TOOL = "AskUserQuestion";
|
|
35
|
+
export const INIT_USAGE = `usage: context-tree init [--here] [--tree-name NAME] [--tree-path PATH]
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
By default, running \`context-tree init\` inside a source or workspace repo creates
|
|
38
|
+
a sibling dedicated tree repo named \`<repo>-context\`.
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
--here Initialize the current repo in place
|
|
42
|
+
--tree-name NAME Name the dedicated sibling tree repo to create
|
|
43
|
+
--tree-path PATH Use an explicit tree repo path
|
|
44
|
+
--help Show this help message
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
interface TemplateTarget {
|
|
48
|
+
templateName: string;
|
|
49
|
+
targetPath: string;
|
|
50
|
+
skipIfExists?: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const TEMPLATE_MAP: TemplateTarget[] = [
|
|
54
|
+
{ templateName: "root-node.md.template", targetPath: "NODE.md" },
|
|
55
|
+
{
|
|
56
|
+
templateName: AGENT_INSTRUCTIONS_TEMPLATE,
|
|
57
|
+
targetPath: AGENT_INSTRUCTIONS_FILE,
|
|
58
|
+
skipIfExists: [AGENT_INSTRUCTIONS_FILE, LEGACY_AGENT_INSTRUCTIONS_FILE],
|
|
59
|
+
},
|
|
60
|
+
{ templateName: "members-domain.md.template", targetPath: "members/NODE.md" },
|
|
33
61
|
];
|
|
34
62
|
|
|
63
|
+
interface TaskListContext {
|
|
64
|
+
sourceRepoPath?: string;
|
|
65
|
+
dedicatedTreeRepo?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
35
68
|
function installSkill(source: string, target: string): void {
|
|
36
69
|
copyCanonicalSkill(source, target);
|
|
37
70
|
console.log(
|
|
38
|
-
|
|
71
|
+
` Installed ${installedSkillRootsDisplay()} from the bundled first-tree package`,
|
|
39
72
|
);
|
|
40
73
|
}
|
|
41
74
|
|
|
42
75
|
function renderTemplates(target: string): void {
|
|
43
76
|
const frameworkDir = join(target, FRAMEWORK_ASSET_ROOT);
|
|
44
|
-
for (const
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
for (const { templateName, targetPath, skipIfExists } of TEMPLATE_MAP) {
|
|
78
|
+
const existingPaths = skipIfExists ?? [targetPath];
|
|
79
|
+
const existingPath = existingPaths.find((candidate) =>
|
|
80
|
+
existsSync(join(target, candidate)),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (existingPath !== undefined) {
|
|
84
|
+
console.log(` Skipped ${targetPath} (found existing ${existingPath})`);
|
|
47
85
|
} else if (renderTemplateFile(frameworkDir, templateName, target, targetPath)) {
|
|
48
86
|
console.log(` Created ${targetPath}`);
|
|
49
87
|
}
|
|
50
88
|
}
|
|
51
89
|
}
|
|
52
90
|
|
|
53
|
-
export function formatTaskList(
|
|
91
|
+
export function formatTaskList(
|
|
92
|
+
groups: RuleResult[],
|
|
93
|
+
context?: TaskListContext,
|
|
94
|
+
): string {
|
|
54
95
|
const lines: string[] = [
|
|
55
96
|
"# Context Tree Init\n",
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
if (context?.dedicatedTreeRepo) {
|
|
100
|
+
lines.push(
|
|
101
|
+
"This repository is the dedicated Context Tree. Keep decisions, rationale," +
|
|
102
|
+
" cross-domain relationships, and ownership here; keep execution detail" +
|
|
103
|
+
" in your source repositories.",
|
|
104
|
+
"",
|
|
105
|
+
);
|
|
106
|
+
if (context.sourceRepoPath) {
|
|
107
|
+
lines.push(`**Bootstrap source repo:** \`${context.sourceRepoPath}\``, "");
|
|
108
|
+
}
|
|
109
|
+
lines.push(
|
|
110
|
+
"When you publish this tree repo, keep it in the same GitHub organization" +
|
|
111
|
+
" as the source repo unless you have a reason not to.",
|
|
112
|
+
"",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
lines.push(
|
|
56
117
|
"**Agent instructions:** Before starting work, analyze the full task list below and" +
|
|
57
118
|
" identify all information you need from the user. Ask the user for their code" +
|
|
58
119
|
" repositories or project directories so you can analyze the source yourself —" +
|
|
@@ -61,7 +122,7 @@ export function formatTaskList(groups: RuleResult[]): string {
|
|
|
61
122
|
` **${INTERACTIVE_TOOL}** tool with structured options — present selectable choices` +
|
|
62
123
|
" (with label and description) so the user can pick instead of typing free-form" +
|
|
63
124
|
` answers. You may batch up to 4 questions per ${INTERACTIVE_TOOL} call.\n`,
|
|
64
|
-
|
|
125
|
+
);
|
|
65
126
|
for (const group of groups) {
|
|
66
127
|
lines.push(`## ${group.group}`);
|
|
67
128
|
for (const task of group.tasks) {
|
|
@@ -75,7 +136,9 @@ export function formatTaskList(groups: RuleResult[]): string {
|
|
|
75
136
|
);
|
|
76
137
|
lines.push(`- [ ] \`${FRAMEWORK_VERSION}\` exists`);
|
|
77
138
|
lines.push("- [ ] Root NODE.md has valid frontmatter (title, owners)");
|
|
78
|
-
lines.push(
|
|
139
|
+
lines.push(
|
|
140
|
+
`- [ ] \`${AGENT_INSTRUCTIONS_FILE}\` is the only agent instructions file and has framework markers`,
|
|
141
|
+
);
|
|
79
142
|
lines.push("- [ ] `context-tree verify` passes with no errors");
|
|
80
143
|
lines.push("- [ ] At least one member node exists");
|
|
81
144
|
lines.push("");
|
|
@@ -99,19 +162,44 @@ export function writeProgress(repo: Repo, content: string): void {
|
|
|
99
162
|
|
|
100
163
|
export interface InitOptions {
|
|
101
164
|
sourceRoot?: string;
|
|
165
|
+
here?: boolean;
|
|
166
|
+
treeName?: string;
|
|
167
|
+
treePath?: string;
|
|
168
|
+
currentCwd?: string;
|
|
169
|
+
gitInitializer?: (root: string) => void;
|
|
102
170
|
}
|
|
103
171
|
|
|
104
172
|
export function runInit(repo?: Repo, options?: InitOptions): number {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
if (
|
|
173
|
+
const sourceRepo = repo ?? new Repo();
|
|
174
|
+
const initTarget = resolveInitTarget(sourceRepo, options);
|
|
175
|
+
if (initTarget.ok === false) {
|
|
108
176
|
console.error(
|
|
109
|
-
|
|
177
|
+
`Error: ${initTarget.message}`,
|
|
110
178
|
);
|
|
111
179
|
return 1;
|
|
112
180
|
}
|
|
181
|
+
const r = initTarget.repo;
|
|
182
|
+
const taskListContext = initTarget.dedicatedTreeRepo
|
|
183
|
+
? {
|
|
184
|
+
dedicatedTreeRepo: true,
|
|
185
|
+
sourceRepoPath: relativePathFrom(r.root, sourceRepo.root),
|
|
186
|
+
}
|
|
187
|
+
: undefined;
|
|
188
|
+
|
|
189
|
+
if (initTarget.dedicatedTreeRepo) {
|
|
190
|
+
console.log(
|
|
191
|
+
"Recommended workflow: keep the Context Tree in a dedicated repo separate" +
|
|
192
|
+
" from your source/workspace repo.",
|
|
193
|
+
);
|
|
194
|
+
console.log(` Source repo: ${sourceRepo.root}`);
|
|
195
|
+
console.log(` Tree repo: ${r.root}`);
|
|
196
|
+
if (initTarget.createdGitRepo) {
|
|
197
|
+
console.log(" Initialized a new git repo for the tree.");
|
|
198
|
+
}
|
|
199
|
+
console.log();
|
|
200
|
+
}
|
|
113
201
|
|
|
114
|
-
if (!r.
|
|
202
|
+
if (!r.hasCurrentInstalledSkill()) {
|
|
115
203
|
try {
|
|
116
204
|
const sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
|
|
117
205
|
console.log(
|
|
@@ -137,9 +225,191 @@ export function runInit(repo?: Repo, options?: InitOptions): number {
|
|
|
137
225
|
return 0;
|
|
138
226
|
}
|
|
139
227
|
|
|
140
|
-
const output = formatTaskList(groups);
|
|
228
|
+
const output = formatTaskList(groups, taskListContext);
|
|
141
229
|
console.log(output);
|
|
142
230
|
writeProgress(r, output);
|
|
143
231
|
console.log(`Progress file written to ${r.preferredProgressPath()}`);
|
|
232
|
+
if (initTarget.dedicatedTreeRepo) {
|
|
233
|
+
console.log(
|
|
234
|
+
`Continue in ${relativePathFrom(sourceRepo.root, r.root)} and keep your source repos available as additional working directories when you populate the tree.`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
144
237
|
return 0;
|
|
145
238
|
}
|
|
239
|
+
|
|
240
|
+
export interface ParsedInitArgs {
|
|
241
|
+
here?: boolean;
|
|
242
|
+
treeName?: string;
|
|
243
|
+
treePath?: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function parseInitArgs(
|
|
247
|
+
args: string[],
|
|
248
|
+
): ParsedInitArgs | { error: string } {
|
|
249
|
+
const parsed: ParsedInitArgs = {};
|
|
250
|
+
|
|
251
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
252
|
+
const arg = args[index];
|
|
253
|
+
switch (arg) {
|
|
254
|
+
case "--here":
|
|
255
|
+
parsed.here = true;
|
|
256
|
+
break;
|
|
257
|
+
case "--tree-name": {
|
|
258
|
+
const value = args[index + 1];
|
|
259
|
+
if (!value) {
|
|
260
|
+
return { error: "Missing value for --tree-name" };
|
|
261
|
+
}
|
|
262
|
+
parsed.treeName = value;
|
|
263
|
+
index += 1;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case "--tree-path": {
|
|
267
|
+
const value = args[index + 1];
|
|
268
|
+
if (!value) {
|
|
269
|
+
return { error: "Missing value for --tree-path" };
|
|
270
|
+
}
|
|
271
|
+
parsed.treePath = value;
|
|
272
|
+
index += 1;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
default:
|
|
276
|
+
return { error: `Unknown init option: ${arg}` };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (parsed.here && parsed.treeName) {
|
|
281
|
+
return { error: "Cannot combine --here with --tree-name" };
|
|
282
|
+
}
|
|
283
|
+
if (parsed.here && parsed.treePath) {
|
|
284
|
+
return { error: "Cannot combine --here with --tree-path" };
|
|
285
|
+
}
|
|
286
|
+
if (parsed.treeName && parsed.treePath) {
|
|
287
|
+
return { error: "Cannot combine --tree-name with --tree-path" };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return parsed;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function runInitCli(args: string[] = []): number {
|
|
294
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
295
|
+
console.log(INIT_USAGE);
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const parsed = parseInitArgs(args);
|
|
300
|
+
if ("error" in parsed) {
|
|
301
|
+
console.error(parsed.error);
|
|
302
|
+
console.log(INIT_USAGE);
|
|
303
|
+
return 1;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return runInit(undefined, parsed);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
interface ResolvedInitTarget {
|
|
310
|
+
ok: true;
|
|
311
|
+
createdGitRepo: boolean;
|
|
312
|
+
dedicatedTreeRepo: boolean;
|
|
313
|
+
repo: Repo;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
interface FailedInitTarget {
|
|
317
|
+
message: string;
|
|
318
|
+
ok: false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function resolveInitTarget(
|
|
322
|
+
sourceRepo: Repo,
|
|
323
|
+
options?: InitOptions,
|
|
324
|
+
): FailedInitTarget | ResolvedInitTarget {
|
|
325
|
+
if (!sourceRepo.isGitRepo()) {
|
|
326
|
+
return {
|
|
327
|
+
ok: false,
|
|
328
|
+
message:
|
|
329
|
+
"not a git repository. Run this from your source/workspace repo, or create a dedicated tree repo first:\n git init\n context-tree init --here",
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const targetRoot = determineTargetRoot(sourceRepo, options);
|
|
334
|
+
const dedicatedTreeRepo = targetRoot !== sourceRepo.root;
|
|
335
|
+
let createdGitRepo = false;
|
|
336
|
+
try {
|
|
337
|
+
createdGitRepo = ensureGitRepo(targetRoot, options?.gitInitializer);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
340
|
+
return {
|
|
341
|
+
ok: false,
|
|
342
|
+
message,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
ok: true,
|
|
348
|
+
createdGitRepo,
|
|
349
|
+
dedicatedTreeRepo,
|
|
350
|
+
repo: new Repo(targetRoot),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function determineTargetRoot(sourceRepo: Repo, options?: InitOptions): string {
|
|
355
|
+
if (options?.treePath) {
|
|
356
|
+
return resolve(options.currentCwd ?? process.cwd(), options.treePath);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (options?.here) {
|
|
360
|
+
return sourceRepo.root;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (options?.treeName) {
|
|
364
|
+
return join(dirname(sourceRepo.root), options.treeName);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (
|
|
368
|
+
sourceRepo.looksLikeTreeRepo()
|
|
369
|
+
|| sourceRepo.isLikelyEmptyRepo()
|
|
370
|
+
|| !sourceRepo.isLikelySourceRepo()
|
|
371
|
+
) {
|
|
372
|
+
return sourceRepo.root;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return join(dirname(sourceRepo.root), `${sourceRepo.repoName()}-context`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function ensureGitRepo(
|
|
379
|
+
targetRoot: string,
|
|
380
|
+
gitInitializer?: (root: string) => void,
|
|
381
|
+
): boolean {
|
|
382
|
+
if (existsSync(targetRoot)) {
|
|
383
|
+
if (!statSync(targetRoot).isDirectory()) {
|
|
384
|
+
throw new Error(`Target path is not a directory: ${targetRoot}`);
|
|
385
|
+
}
|
|
386
|
+
if (new Repo(targetRoot).isGitRepo()) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
if (readdirSync(targetRoot).length !== 0) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
`Target path exists and is not a git repository: ${targetRoot}. Run \`git init\` there first or choose a different tree path.`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
(gitInitializer ?? defaultGitInitializer)(targetRoot);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function defaultGitInitializer(root: string): void {
|
|
403
|
+
execFileSync("git", ["init"], {
|
|
404
|
+
cwd: root,
|
|
405
|
+
stdio: "ignore",
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function relativePathFrom(from: string, to: string): string {
|
|
410
|
+
const rel = relative(from, to);
|
|
411
|
+
if (rel === "") {
|
|
412
|
+
return ".";
|
|
413
|
+
}
|
|
414
|
+
return rel.startsWith("..") ? rel : `./${rel}`;
|
|
415
|
+
}
|