first-tree 0.0.2 → 0.0.4
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 +116 -40
- package/dist/cli.js +46 -17
- package/dist/help-Dtdj91HJ.js +25 -0
- package/dist/init--VepFe6N.js +403 -0
- package/dist/installer-cH7N4RNj.js +47 -0
- package/dist/onboarding-C9cYSE6F.js +2 -0
- package/dist/onboarding-CPP8fF4D.js +10 -0
- package/dist/repo-DY57bMqr.js +318 -0
- package/dist/upgrade-Cgx_K2HM.js +135 -0
- package/dist/{verify-CSRIkuoM.js → verify-mC9ZTd1f.js} +118 -29
- package/package.json +33 -10
- package/skills/first-tree/SKILL.md +113 -0
- package/skills/first-tree/agents/openai.yaml +4 -0
- package/skills/first-tree/assets/framework/VERSION +1 -0
- package/skills/first-tree/assets/framework/examples/claude-code/README.md +14 -0
- package/skills/first-tree/assets/framework/examples/claude-code/settings.json +14 -0
- package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +224 -0
- package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +15 -0
- package/skills/first-tree/assets/framework/helpers/run-review.ts +193 -0
- package/skills/first-tree/assets/framework/manifest.json +11 -0
- package/skills/first-tree/assets/framework/prompts/pr-review.md +38 -0
- package/skills/first-tree/assets/framework/templates/agents.md.template +49 -0
- package/skills/first-tree/assets/framework/templates/member-node.md.template +18 -0
- package/skills/first-tree/assets/framework/templates/members-domain.md.template +45 -0
- package/skills/first-tree/assets/framework/templates/root-node.md.template +41 -0
- package/skills/first-tree/assets/framework/workflows/codeowners.yml +31 -0
- package/skills/first-tree/assets/framework/workflows/pr-review.yml +146 -0
- package/skills/first-tree/assets/framework/workflows/validate.yml +19 -0
- package/skills/first-tree/engine/commands/help.ts +32 -0
- package/skills/first-tree/engine/commands/init.ts +1 -0
- package/skills/first-tree/engine/commands/upgrade.ts +1 -0
- package/skills/first-tree/engine/commands/verify.ts +1 -0
- package/skills/first-tree/engine/init.ts +414 -0
- package/skills/first-tree/engine/onboarding.ts +10 -0
- package/skills/first-tree/engine/repo.ts +360 -0
- package/skills/first-tree/engine/rules/agent-instructions.ts +59 -0
- package/skills/first-tree/engine/rules/agent-integration.ts +19 -0
- package/skills/first-tree/engine/rules/ci-validation.ts +72 -0
- package/skills/first-tree/engine/rules/framework.ts +13 -0
- package/skills/first-tree/engine/rules/index.ts +41 -0
- package/skills/first-tree/engine/rules/members.ts +21 -0
- package/skills/first-tree/engine/rules/populate-tree.ts +36 -0
- package/skills/first-tree/engine/rules/root-node.ts +41 -0
- package/skills/first-tree/engine/runtime/adapters.ts +22 -0
- package/skills/first-tree/engine/runtime/asset-loader.ts +141 -0
- package/skills/first-tree/engine/runtime/installer.ts +82 -0
- package/skills/first-tree/engine/runtime/upgrader.ts +23 -0
- package/skills/first-tree/engine/upgrade.ts +233 -0
- package/skills/first-tree/engine/validators/members.ts +215 -0
- package/skills/first-tree/engine/validators/nodes.ts +559 -0
- package/skills/first-tree/engine/verify.ts +155 -0
- package/skills/first-tree/references/about.md +36 -0
- package/skills/first-tree/references/maintainer-architecture.md +59 -0
- package/skills/first-tree/references/maintainer-build-and-distribution.md +59 -0
- package/skills/first-tree/references/maintainer-testing.md +58 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +38 -0
- package/skills/first-tree/references/onboarding.md +185 -0
- package/skills/first-tree/references/ownership-and-naming.md +94 -0
- package/skills/first-tree/references/principles.md +113 -0
- package/skills/first-tree/references/source-map.md +94 -0
- package/skills/first-tree/references/upgrade-contract.md +94 -0
- package/skills/first-tree/scripts/check-skill-sync.sh +133 -0
- package/skills/first-tree/scripts/quick_validate.py +95 -0
- package/skills/first-tree/scripts/run-local-cli.sh +35 -0
- package/skills/first-tree/tests/asset-loader.test.ts +75 -0
- package/skills/first-tree/tests/generate-codeowners.test.ts +94 -0
- package/skills/first-tree/tests/helpers.ts +169 -0
- package/skills/first-tree/tests/init.test.ts +250 -0
- package/skills/first-tree/tests/repo.test.ts +440 -0
- package/skills/first-tree/tests/rules.test.ts +413 -0
- package/skills/first-tree/tests/run-review.test.ts +155 -0
- package/skills/first-tree/tests/skill-artifacts.test.ts +311 -0
- package/skills/first-tree/tests/thin-cli.test.ts +104 -0
- package/skills/first-tree/tests/upgrade.test.ts +103 -0
- package/skills/first-tree/tests/validate-members.test.ts +224 -0
- package/skills/first-tree/tests/validate-nodes.test.ts +198 -0
- package/skills/first-tree/tests/verify.test.ts +241 -0
- package/dist/init-CE_944sb.js +0 -283
- package/dist/repo-BByc3VvM.js +0 -111
- package/dist/upgrade-Chr7z0CY.js +0 -82
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Context Tree Source Map
|
|
2
|
+
|
|
3
|
+
This is the canonical reading index for the single-skill architecture. If a
|
|
4
|
+
maintainer needs information to safely change the framework or thin CLI, that
|
|
5
|
+
information should be discoverable from this file.
|
|
6
|
+
|
|
7
|
+
## Read First
|
|
8
|
+
|
|
9
|
+
| Path | Why it matters |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `SKILL.md` | Trigger conditions, workflow, and validation contract |
|
|
12
|
+
| `references/about.md` | Product framing for what Context Tree is and is not |
|
|
13
|
+
| `references/onboarding.md` | The onboarding narrative that `help onboarding` and `init` surface |
|
|
14
|
+
| `references/principles.md` | Decision-model reference |
|
|
15
|
+
| `references/ownership-and-naming.md` | Ownership contract |
|
|
16
|
+
| `references/upgrade-contract.md` | Installed layout and upgrade semantics |
|
|
17
|
+
| `references/maintainer-architecture.md` | Source-repo architecture and invariants |
|
|
18
|
+
| `references/maintainer-thin-cli.md` | Root shell contract and anti-duplication rules |
|
|
19
|
+
| `references/maintainer-build-and-distribution.md` | Build, pack, and distribution contract |
|
|
20
|
+
| `references/maintainer-testing.md` | Test workflow and maintainer validation expectations |
|
|
21
|
+
|
|
22
|
+
## Runtime Payload
|
|
23
|
+
|
|
24
|
+
The installed skill payload lives under `assets/framework/`.
|
|
25
|
+
|
|
26
|
+
| Path | Purpose |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `assets/framework/manifest.json` | Runtime asset contract |
|
|
29
|
+
| `assets/framework/VERSION` | Version marker for installed payloads |
|
|
30
|
+
| `assets/framework/templates/` | Generated scaffolds |
|
|
31
|
+
| `assets/framework/workflows/` | CI templates |
|
|
32
|
+
| `assets/framework/prompts/` | Review prompt payload |
|
|
33
|
+
| `assets/framework/examples/` | Agent integration examples |
|
|
34
|
+
| `assets/framework/helpers/` | Shipped helper scripts and TypeScript utilities |
|
|
35
|
+
| `progress.md` | Generated in user repos to track unfinished setup or upgrade tasks |
|
|
36
|
+
|
|
37
|
+
## Framework Engine Surface
|
|
38
|
+
|
|
39
|
+
These skill-owned files implement the framework behavior.
|
|
40
|
+
|
|
41
|
+
| Path | Purpose |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `engine/commands/` | Stable command entrypoints that the thin CLI imports |
|
|
44
|
+
| `engine/init.ts` / `engine/verify.ts` / `engine/upgrade.ts` | Command implementations for install, verify, and upgrade |
|
|
45
|
+
| `engine/onboarding.ts` | Canonical onboarding text loader |
|
|
46
|
+
| `engine/repo.ts` | Repo inspection, source-vs-tree heuristics, and worktree-aware git-root helpers |
|
|
47
|
+
| `engine/rules/` | Situation-aware task generation after `init` |
|
|
48
|
+
| `engine/validators/` | Deterministic tree and member validation |
|
|
49
|
+
| `engine/runtime/asset-loader.ts` | Path constants plus legacy-layout detection |
|
|
50
|
+
| `engine/runtime/installer.ts` | Bundled-package discovery, skill copy, and template-render helpers |
|
|
51
|
+
| `engine/runtime/upgrader.ts` | Packaged-skill version comparison helpers |
|
|
52
|
+
| `engine/runtime/adapters.ts` | Agent-integration path helpers |
|
|
53
|
+
|
|
54
|
+
## Thin CLI Shell Surface
|
|
55
|
+
|
|
56
|
+
These root files are distribution shell code. They should stay thin and should
|
|
57
|
+
not become the only place important maintainer knowledge lives.
|
|
58
|
+
|
|
59
|
+
| Path | Purpose |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `src/cli.ts` | Thin command parser and dispatcher |
|
|
62
|
+
| `src/md.d.ts` | Build-time markdown module typing |
|
|
63
|
+
| `package.json` | Package metadata, import aliases, and scripts |
|
|
64
|
+
| `tsconfig.json` | TypeScript compile boundaries |
|
|
65
|
+
| `tsdown.config.ts` | Build entry and asset handling |
|
|
66
|
+
| `vitest.config.ts` | Unit-test entrypoints |
|
|
67
|
+
| `.github/workflows/ci.yml` | Thin CI shell |
|
|
68
|
+
| `README.md` | Thin distribution overview |
|
|
69
|
+
| `AGENTS.md` | Thin maintainer pointer for agent sessions |
|
|
70
|
+
|
|
71
|
+
## Validation
|
|
72
|
+
|
|
73
|
+
| Path | Coverage |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `tests/init.test.ts` | Init scaffolding behavior |
|
|
76
|
+
| `tests/verify.test.ts` | Verification and progress gating |
|
|
77
|
+
| `tests/rules.test.ts` | Task generation text |
|
|
78
|
+
| `tests/asset-loader.test.ts` | Layout detection and path precedence |
|
|
79
|
+
| `tests/generate-codeowners.test.ts` | Ownership helper behavior |
|
|
80
|
+
| `tests/run-review.test.ts` | Review helper behavior |
|
|
81
|
+
| `tests/skill-artifacts.test.ts` | Skill export and documentation integrity |
|
|
82
|
+
| `tests/thin-cli.test.ts` | Thin CLI entrypoint smoke coverage |
|
|
83
|
+
| `tests/upgrade.test.ts` | Installed-skill upgrade behavior |
|
|
84
|
+
|
|
85
|
+
## Compatibility Notes
|
|
86
|
+
|
|
87
|
+
- The source repo intentionally contains no root `.context-tree/`, `docs/`,
|
|
88
|
+
mirror skills, or bundled repo snapshot.
|
|
89
|
+
- Legacy `.context-tree/...` paths still matter only for migrating existing
|
|
90
|
+
user repos; the compatibility logic lives in
|
|
91
|
+
`engine/runtime/asset-loader.ts` and `engine/upgrade.ts`.
|
|
92
|
+
- Root `README.md` and `AGENTS.md` are intentionally brief. Important
|
|
93
|
+
information must live in the skill references instead.
|
|
94
|
+
- If you change `references/` or `assets/framework/`, run `pnpm validate:skill`.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Upgrade Contract
|
|
2
|
+
|
|
3
|
+
This file describes the current installed-layout contract and the compatibility
|
|
4
|
+
rules we keep for legacy `skills/first-tree-cli-framework/` and
|
|
5
|
+
`.context-tree/` repos.
|
|
6
|
+
|
|
7
|
+
## Canonical Source
|
|
8
|
+
|
|
9
|
+
- `skills/first-tree/` is the only source of truth.
|
|
10
|
+
- `references/` contains explanatory material.
|
|
11
|
+
- `assets/framework/` contains the shipped runtime payload.
|
|
12
|
+
- The distributable `first-tree` package must carry the canonical skill inside
|
|
13
|
+
the package itself.
|
|
14
|
+
- The source repo does not keep a root `.context-tree/`, `docs/`, mirror skill
|
|
15
|
+
directories, or a bundled repo snapshot.
|
|
16
|
+
|
|
17
|
+
## Installed Layout
|
|
18
|
+
|
|
19
|
+
The current installed layout in a user repo is:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
skills/
|
|
23
|
+
first-tree/
|
|
24
|
+
SKILL.md
|
|
25
|
+
progress.md
|
|
26
|
+
references/
|
|
27
|
+
assets/
|
|
28
|
+
framework/
|
|
29
|
+
manifest.json
|
|
30
|
+
VERSION
|
|
31
|
+
templates/
|
|
32
|
+
workflows/
|
|
33
|
+
prompts/
|
|
34
|
+
examples/
|
|
35
|
+
helpers/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The tree content still lives outside the skill:
|
|
39
|
+
|
|
40
|
+
- `NODE.md`
|
|
41
|
+
- `AGENTS.md`
|
|
42
|
+
- `members/`
|
|
43
|
+
|
|
44
|
+
## Command Intent
|
|
45
|
+
|
|
46
|
+
- `context-tree init`
|
|
47
|
+
- when run in a source/workspace repo, creates or reuses a sibling dedicated
|
|
48
|
+
tree repo by default
|
|
49
|
+
- installs the skill into the target tree repo
|
|
50
|
+
- renders top-level tree scaffolding from the skill templates
|
|
51
|
+
- writes progress state to `skills/first-tree/progress.md`
|
|
52
|
+
- `context-tree verify`
|
|
53
|
+
- checks progress state from the installed skill
|
|
54
|
+
- validates root/frontmatter/agent markers
|
|
55
|
+
- runs node and member validators
|
|
56
|
+
- `context-tree upgrade`
|
|
57
|
+
- compares the installed skill payload version to the skill bundled with the
|
|
58
|
+
currently running `first-tree` package
|
|
59
|
+
- refreshes the installed skill payload without overwriting tree content
|
|
60
|
+
- migrates repos that still use the previous
|
|
61
|
+
`skills/first-tree-cli-framework/` path onto `skills/first-tree/`
|
|
62
|
+
- migrates legacy `.context-tree/` repos onto the installed skill layout
|
|
63
|
+
- preserves user-authored sections such as the editable part of `AGENTS.md`
|
|
64
|
+
|
|
65
|
+
## Compatibility Rules For Legacy Trees
|
|
66
|
+
|
|
67
|
+
- `context-tree init` never creates a new `.context-tree/`.
|
|
68
|
+
- `context-tree init --here` preserves the explicit in-place bootstrap path for
|
|
69
|
+
already-created tree repos.
|
|
70
|
+
- Default dedicated-tree-repo creation is local-only. The CLI may create a new
|
|
71
|
+
sibling git repo on disk, but it must not clone the source repo or depend on
|
|
72
|
+
network access.
|
|
73
|
+
- Normal `context-tree init` and `context-tree upgrade` flows do not clone the
|
|
74
|
+
source repo or require network access.
|
|
75
|
+
- `context-tree verify` may still read a legacy
|
|
76
|
+
`skills/first-tree-cli-framework/...` or `.context-tree/...` layout in an
|
|
77
|
+
existing user repo so the repo can be upgraded in place.
|
|
78
|
+
- `context-tree upgrade` must migrate either legacy layout onto
|
|
79
|
+
`skills/first-tree/` and remove the old directory afterward.
|
|
80
|
+
- When both layouts are present, prefer the installed skill layout.
|
|
81
|
+
- Existing repos may still have a legacy `AGENT.md`; `init` and `upgrade`
|
|
82
|
+
must not silently overwrite it, and follow-up tasks should direct users to
|
|
83
|
+
rename it to `AGENTS.md`.
|
|
84
|
+
|
|
85
|
+
## Invariants
|
|
86
|
+
|
|
87
|
+
- Templates, workflows, prompts, helper scripts, and explanatory references
|
|
88
|
+
must stay aligned.
|
|
89
|
+
- If a change affects installed payload contents, bump
|
|
90
|
+
`assets/framework/VERSION` so packaged upgrades can detect it.
|
|
91
|
+
- Ownership behavior must stay identical across layout changes.
|
|
92
|
+
- The tree remains decision-focused; execution detail stays in source systems.
|
|
93
|
+
- A path migration is incomplete if task text, docs, tests, and runtime assets
|
|
94
|
+
disagree about where the framework lives.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
SKILL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
|
|
7
|
+
find_repo_root() {
|
|
8
|
+
local dir="$SKILL_DIR"
|
|
9
|
+
while [[ "$dir" != "/" ]]; do
|
|
10
|
+
if [[ -f "$dir/package.json" ]] && grep -q '"name": "first-tree"' "$dir/package.json"; then
|
|
11
|
+
printf '%s\n' "$dir"
|
|
12
|
+
return 0
|
|
13
|
+
fi
|
|
14
|
+
dir="$(dirname "$dir")"
|
|
15
|
+
done
|
|
16
|
+
return 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
require_file() {
|
|
20
|
+
local path="$1"
|
|
21
|
+
if [[ ! -f "$path" ]]; then
|
|
22
|
+
echo "Missing file: $path" >&2
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
REPO_ROOT="$(find_repo_root || true)"
|
|
28
|
+
SOURCE_DIR=""
|
|
29
|
+
if [[ -n "$REPO_ROOT" ]]; then
|
|
30
|
+
SOURCE_DIR="$REPO_ROOT/skills/first-tree"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [[ -z "$REPO_ROOT" || "$SKILL_DIR" != "$SOURCE_DIR" ]]; then
|
|
34
|
+
echo "Run this script from the source-of-truth skill at skills/first-tree inside a live first-tree checkout." >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
require_file "$SOURCE_DIR/SKILL.md"
|
|
39
|
+
require_file "$SOURCE_DIR/agents/openai.yaml"
|
|
40
|
+
require_file "$SOURCE_DIR/references/about.md"
|
|
41
|
+
require_file "$SOURCE_DIR/references/onboarding.md"
|
|
42
|
+
require_file "$SOURCE_DIR/references/principles.md"
|
|
43
|
+
require_file "$SOURCE_DIR/references/ownership-and-naming.md"
|
|
44
|
+
require_file "$SOURCE_DIR/references/source-map.md"
|
|
45
|
+
require_file "$SOURCE_DIR/references/upgrade-contract.md"
|
|
46
|
+
require_file "$SOURCE_DIR/references/maintainer-architecture.md"
|
|
47
|
+
require_file "$SOURCE_DIR/references/maintainer-thin-cli.md"
|
|
48
|
+
require_file "$SOURCE_DIR/references/maintainer-build-and-distribution.md"
|
|
49
|
+
require_file "$SOURCE_DIR/references/maintainer-testing.md"
|
|
50
|
+
require_file "$SOURCE_DIR/engine/init.ts"
|
|
51
|
+
require_file "$SOURCE_DIR/engine/onboarding.ts"
|
|
52
|
+
require_file "$SOURCE_DIR/engine/repo.ts"
|
|
53
|
+
require_file "$SOURCE_DIR/engine/upgrade.ts"
|
|
54
|
+
require_file "$SOURCE_DIR/engine/verify.ts"
|
|
55
|
+
require_file "$SOURCE_DIR/engine/commands/help.ts"
|
|
56
|
+
require_file "$SOURCE_DIR/engine/commands/init.ts"
|
|
57
|
+
require_file "$SOURCE_DIR/engine/commands/upgrade.ts"
|
|
58
|
+
require_file "$SOURCE_DIR/engine/commands/verify.ts"
|
|
59
|
+
require_file "$SOURCE_DIR/engine/rules/index.ts"
|
|
60
|
+
require_file "$SOURCE_DIR/engine/runtime/asset-loader.ts"
|
|
61
|
+
require_file "$SOURCE_DIR/engine/runtime/installer.ts"
|
|
62
|
+
require_file "$SOURCE_DIR/engine/runtime/upgrader.ts"
|
|
63
|
+
require_file "$SOURCE_DIR/engine/runtime/adapters.ts"
|
|
64
|
+
require_file "$SOURCE_DIR/engine/validators/members.ts"
|
|
65
|
+
require_file "$SOURCE_DIR/engine/validators/nodes.ts"
|
|
66
|
+
require_file "$SOURCE_DIR/tests/init.test.ts"
|
|
67
|
+
require_file "$SOURCE_DIR/tests/verify.test.ts"
|
|
68
|
+
require_file "$SOURCE_DIR/tests/skill-artifacts.test.ts"
|
|
69
|
+
require_file "$SOURCE_DIR/assets/framework/manifest.json"
|
|
70
|
+
require_file "$SOURCE_DIR/assets/framework/VERSION"
|
|
71
|
+
require_file "$SOURCE_DIR/assets/framework/prompts/pr-review.md"
|
|
72
|
+
require_file "$SOURCE_DIR/assets/framework/templates/root-node.md.template"
|
|
73
|
+
require_file "$SOURCE_DIR/assets/framework/templates/agents.md.template"
|
|
74
|
+
require_file "$SOURCE_DIR/assets/framework/templates/members-domain.md.template"
|
|
75
|
+
require_file "$SOURCE_DIR/assets/framework/templates/member-node.md.template"
|
|
76
|
+
require_file "$SOURCE_DIR/assets/framework/workflows/validate.yml"
|
|
77
|
+
require_file "$SOURCE_DIR/assets/framework/workflows/pr-review.yml"
|
|
78
|
+
require_file "$SOURCE_DIR/assets/framework/workflows/codeowners.yml"
|
|
79
|
+
require_file "$SOURCE_DIR/assets/framework/examples/claude-code/README.md"
|
|
80
|
+
require_file "$SOURCE_DIR/assets/framework/examples/claude-code/settings.json"
|
|
81
|
+
require_file "$SOURCE_DIR/assets/framework/helpers/generate-codeowners.ts"
|
|
82
|
+
require_file "$SOURCE_DIR/assets/framework/helpers/run-review.ts"
|
|
83
|
+
require_file "$SOURCE_DIR/assets/framework/helpers/inject-tree-context.sh"
|
|
84
|
+
|
|
85
|
+
# Check for legacy artifacts that should not be committed.
|
|
86
|
+
# Use git ls-files to ignore untracked local files (e.g. .claude/settings.local.json).
|
|
87
|
+
for legacy_path in \
|
|
88
|
+
".agents" \
|
|
89
|
+
".claude" \
|
|
90
|
+
".context-tree" \
|
|
91
|
+
"docs" \
|
|
92
|
+
"skills/first-tree-cli-framework" \
|
|
93
|
+
"tests" \
|
|
94
|
+
"skills/first-tree/references/repo-snapshot"
|
|
95
|
+
do
|
|
96
|
+
if git -C "$REPO_ROOT" ls-files --error-unmatch "$legacy_path" >/dev/null 2>&1; then
|
|
97
|
+
echo "Unexpected legacy artifact tracked in git: $legacy_path" >&2
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
done
|
|
101
|
+
|
|
102
|
+
if [[ -e "$SOURCE_DIR/evals" ]]; then
|
|
103
|
+
echo "Skill should not contain repo-only eval tooling." >&2
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
require_file "$REPO_ROOT/evals/context-tree-eval.test.ts"
|
|
108
|
+
require_file "$REPO_ROOT/evals/README.md"
|
|
109
|
+
require_file "$REPO_ROOT/evals/helpers/case-loader.ts"
|
|
110
|
+
require_file "$REPO_ROOT/evals/scripts/tree-manager.ts"
|
|
111
|
+
require_file "$REPO_ROOT/evals/tests/eval-helpers.test.ts"
|
|
112
|
+
|
|
113
|
+
if grep -q '"#docs/\*"' "$REPO_ROOT/package.json"; then
|
|
114
|
+
echo "package.json still exposes the legacy #docs import alias." >&2
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
if ! grep -q '"#skill/\*"' "$REPO_ROOT/package.json"; then
|
|
119
|
+
echo "package.json is missing the canonical #skill import alias." >&2
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
if ! grep -q '"skills/first-tree"' "$REPO_ROOT/package.json"; then
|
|
124
|
+
echo "package.json is missing the canonical skill in the published files list." >&2
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
if ! grep -q '#skill/engine/commands/init.js' "$REPO_ROOT/src/cli.ts"; then
|
|
129
|
+
echo "src/cli.ts is not dispatching to the skill-owned engine." >&2
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
echo "Canonical skill structure is clean."
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Portable quick validator for a skill directory.
|
|
4
|
+
|
|
5
|
+
This version is intentionally self-contained so it can run in CI and in copied
|
|
6
|
+
skill folders without depending on external Python packages.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
MAX_SKILL_NAME_LENGTH = 64
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def extract_frontmatter(text: str) -> tuple[bool, str]:
|
|
19
|
+
if not text.startswith("---\n"):
|
|
20
|
+
return False, "No YAML frontmatter found"
|
|
21
|
+
match = re.match(r"^---\n(.*?)\n---", text, re.DOTALL)
|
|
22
|
+
if not match:
|
|
23
|
+
return False, "Invalid frontmatter format"
|
|
24
|
+
return True, match.group(1)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_simple_frontmatter(frontmatter: str) -> dict[str, str]:
|
|
28
|
+
data: dict[str, str] = {}
|
|
29
|
+
for line in frontmatter.splitlines():
|
|
30
|
+
stripped = line.strip()
|
|
31
|
+
if not stripped or stripped.startswith("#"):
|
|
32
|
+
continue
|
|
33
|
+
if ":" not in stripped:
|
|
34
|
+
continue
|
|
35
|
+
key, value = stripped.split(":", 1)
|
|
36
|
+
key = key.strip()
|
|
37
|
+
value = value.strip()
|
|
38
|
+
if value.startswith(("'", '"')) and value.endswith(("'", '"')) and len(value) >= 2:
|
|
39
|
+
value = value[1:-1]
|
|
40
|
+
data[key] = value
|
|
41
|
+
return data
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def validate_skill(skill_path: str) -> tuple[bool, str]:
|
|
45
|
+
skill_dir = Path(skill_path)
|
|
46
|
+
skill_md = skill_dir / "SKILL.md"
|
|
47
|
+
if not skill_md.exists():
|
|
48
|
+
return False, "SKILL.md not found"
|
|
49
|
+
|
|
50
|
+
content = skill_md.read_text()
|
|
51
|
+
ok, frontmatter_or_error = extract_frontmatter(content)
|
|
52
|
+
if not ok:
|
|
53
|
+
return False, frontmatter_or_error
|
|
54
|
+
|
|
55
|
+
frontmatter = parse_simple_frontmatter(frontmatter_or_error)
|
|
56
|
+
|
|
57
|
+
unexpected = set(frontmatter.keys()) - {"name", "description"}
|
|
58
|
+
if unexpected:
|
|
59
|
+
return False, f"Unexpected key(s) in frontmatter: {', '.join(sorted(unexpected))}"
|
|
60
|
+
|
|
61
|
+
name = frontmatter.get("name", "").strip()
|
|
62
|
+
if not name:
|
|
63
|
+
return False, "Missing 'name' in frontmatter"
|
|
64
|
+
if not re.match(r"^[a-z0-9-]+$", name):
|
|
65
|
+
return False, f"Name '{name}' should be hyphen-case"
|
|
66
|
+
if name.startswith("-") or name.endswith("-") or "--" in name:
|
|
67
|
+
return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
|
|
68
|
+
if len(name) > MAX_SKILL_NAME_LENGTH:
|
|
69
|
+
return False, f"Name is too long ({len(name)} > {MAX_SKILL_NAME_LENGTH})"
|
|
70
|
+
|
|
71
|
+
description = frontmatter.get("description", "").strip()
|
|
72
|
+
if not description:
|
|
73
|
+
return False, "Missing 'description' in frontmatter"
|
|
74
|
+
if len(description) > 1024:
|
|
75
|
+
return False, f"Description is too long ({len(description)} > 1024)"
|
|
76
|
+
|
|
77
|
+
openai_yaml = skill_dir / "agents" / "openai.yaml"
|
|
78
|
+
if not openai_yaml.exists():
|
|
79
|
+
return False, "agents/openai.yaml not found"
|
|
80
|
+
|
|
81
|
+
return True, "Skill is valid!"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main() -> int:
|
|
85
|
+
if len(sys.argv) != 2:
|
|
86
|
+
print("Usage: quick_validate.py <skill_directory>")
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
valid, message = validate_skill(sys.argv[1])
|
|
90
|
+
print(message)
|
|
91
|
+
return 0 if valid else 1
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
SKILL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
INSTALL_GUIDE="${SKILL_DIR}/references/onboarding.md"
|
|
7
|
+
|
|
8
|
+
find_repo_root() {
|
|
9
|
+
local dir="$SKILL_DIR"
|
|
10
|
+
while [[ "$dir" != "/" ]]; do
|
|
11
|
+
if [[ -f "$dir/package.json" ]] && grep -q '"name": "first-tree"' "$dir/package.json"; then
|
|
12
|
+
printf '%s\n' "$dir"
|
|
13
|
+
return 0
|
|
14
|
+
fi
|
|
15
|
+
dir="$(dirname "$dir")"
|
|
16
|
+
done
|
|
17
|
+
return 1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
REPO_ROOT="$(find_repo_root || true)"
|
|
21
|
+
|
|
22
|
+
if [[ -n "${REPO_ROOT}" ]]; then
|
|
23
|
+
cd "$REPO_ROOT"
|
|
24
|
+
pnpm build >/dev/null
|
|
25
|
+
exec node dist/cli.js "$@"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if command -v context-tree >/dev/null 2>&1; then
|
|
29
|
+
exec context-tree "$@"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo "Could not find a live first-tree checkout or a 'context-tree' binary on PATH." >&2
|
|
33
|
+
echo "Install the npm package 'first-tree' if you want the portable runner to invoke the CLI outside the repo." >&2
|
|
34
|
+
echo "Read the onboarding guide at: ${INSTALL_GUIDE}" >&2
|
|
35
|
+
exit 1
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
FRAMEWORK_VERSION,
|
|
6
|
+
INSTALLED_PROGRESS,
|
|
7
|
+
LEGACY_SKILL_PROGRESS,
|
|
8
|
+
LEGACY_SKILL_VERSION,
|
|
9
|
+
LEGACY_PROGRESS,
|
|
10
|
+
LEGACY_VERSION,
|
|
11
|
+
detectFrameworkLayout,
|
|
12
|
+
frameworkVersionCandidates,
|
|
13
|
+
progressFileCandidates,
|
|
14
|
+
resolveFirstExistingPath,
|
|
15
|
+
} from "#skill/engine/runtime/asset-loader.js";
|
|
16
|
+
import { useTmpDir } from "./helpers.js";
|
|
17
|
+
|
|
18
|
+
describe("asset-loader", () => {
|
|
19
|
+
it("prefers the installed skill layout when both layouts exist", () => {
|
|
20
|
+
const tmp = useTmpDir();
|
|
21
|
+
mkdirSync(join(tmp.path, "skills", "first-tree", "assets", "framework"), {
|
|
22
|
+
recursive: true,
|
|
23
|
+
});
|
|
24
|
+
mkdirSync(join(tmp.path, ".context-tree"), { recursive: true });
|
|
25
|
+
writeFileSync(join(tmp.path, FRAMEWORK_VERSION), "0.2.0\n");
|
|
26
|
+
writeFileSync(join(tmp.path, LEGACY_VERSION), "0.1.0\n");
|
|
27
|
+
|
|
28
|
+
expect(detectFrameworkLayout(tmp.path)).toBe("skill");
|
|
29
|
+
expect(resolveFirstExistingPath(tmp.path, [FRAMEWORK_VERSION, LEGACY_VERSION])).toBe(
|
|
30
|
+
FRAMEWORK_VERSION,
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("falls back to the legacy layout when the skill is not installed", () => {
|
|
35
|
+
const tmp = useTmpDir();
|
|
36
|
+
mkdirSync(join(tmp.path, ".context-tree"), { recursive: true });
|
|
37
|
+
writeFileSync(join(tmp.path, LEGACY_VERSION), "0.1.0\n");
|
|
38
|
+
|
|
39
|
+
expect(detectFrameworkLayout(tmp.path)).toBe("legacy");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("detects the previous installed skill name before the .context-tree layout", () => {
|
|
43
|
+
const tmp = useTmpDir();
|
|
44
|
+
mkdirSync(
|
|
45
|
+
join(tmp.path, "skills", "first-tree-cli-framework", "assets", "framework"),
|
|
46
|
+
{
|
|
47
|
+
recursive: true,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
mkdirSync(join(tmp.path, ".context-tree"), { recursive: true });
|
|
51
|
+
writeFileSync(join(tmp.path, LEGACY_SKILL_VERSION), "0.2.0\n");
|
|
52
|
+
writeFileSync(join(tmp.path, LEGACY_VERSION), "0.1.0\n");
|
|
53
|
+
|
|
54
|
+
expect(detectFrameworkLayout(tmp.path)).toBe("legacy-skill");
|
|
55
|
+
expect(
|
|
56
|
+
resolveFirstExistingPath(tmp.path, frameworkVersionCandidates()),
|
|
57
|
+
).toBe(LEGACY_SKILL_VERSION);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("prefers the installed progress file candidate", () => {
|
|
61
|
+
const tmp = useTmpDir();
|
|
62
|
+
mkdirSync(join(tmp.path, "skills", "first-tree"), { recursive: true });
|
|
63
|
+
mkdirSync(join(tmp.path, "skills", "first-tree-cli-framework"), {
|
|
64
|
+
recursive: true,
|
|
65
|
+
});
|
|
66
|
+
mkdirSync(join(tmp.path, ".context-tree"), { recursive: true });
|
|
67
|
+
writeFileSync(join(tmp.path, INSTALLED_PROGRESS), "new");
|
|
68
|
+
writeFileSync(join(tmp.path, LEGACY_SKILL_PROGRESS), "old-skill");
|
|
69
|
+
writeFileSync(join(tmp.path, LEGACY_PROGRESS), "old");
|
|
70
|
+
|
|
71
|
+
expect(resolveFirstExistingPath(tmp.path, progressFileCandidates())).toBe(
|
|
72
|
+
INSTALLED_PROGRESS,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
parseOwners,
|
|
6
|
+
resolveNodeOwners,
|
|
7
|
+
collectEntries,
|
|
8
|
+
formatOwners,
|
|
9
|
+
} from "../assets/framework/helpers/generate-codeowners.js";
|
|
10
|
+
import { useTmpDir } from "./helpers.js";
|
|
11
|
+
|
|
12
|
+
function write(root: string, relPath: string, content: string): string {
|
|
13
|
+
const p = join(root, relPath);
|
|
14
|
+
mkdirSync(join(p, ".."), { recursive: true });
|
|
15
|
+
writeFileSync(p, content);
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// --- parseOwners ---
|
|
20
|
+
|
|
21
|
+
describe("parseOwners", () => {
|
|
22
|
+
it("parses valid owners", () => {
|
|
23
|
+
const tmp = useTmpDir();
|
|
24
|
+
const p = write(tmp.path, "NODE.md", "---\nowners: [alice, bob]\n---\n");
|
|
25
|
+
expect(parseOwners(p)).toEqual(["alice", "bob"]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("handles empty owners", () => {
|
|
29
|
+
const tmp = useTmpDir();
|
|
30
|
+
const p = write(tmp.path, "NODE.md", "---\nowners: []\n---\n");
|
|
31
|
+
expect(parseOwners(p)).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("handles wildcard", () => {
|
|
35
|
+
const tmp = useTmpDir();
|
|
36
|
+
const p = write(tmp.path, "NODE.md", "---\nowners: [*]\n---\n");
|
|
37
|
+
expect(parseOwners(p)).toEqual(["*"]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns null for no frontmatter", () => {
|
|
41
|
+
const tmp = useTmpDir();
|
|
42
|
+
const p = write(tmp.path, "NODE.md", "# Just a heading\n");
|
|
43
|
+
expect(parseOwners(p)).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// --- resolveNodeOwners ---
|
|
48
|
+
|
|
49
|
+
describe("resolveNodeOwners", () => {
|
|
50
|
+
it("returns direct owners", () => {
|
|
51
|
+
const tmp = useTmpDir();
|
|
52
|
+
write(tmp.path, "NODE.md", "---\nowners: [root-owner]\n---\n");
|
|
53
|
+
write(tmp.path, "domain/NODE.md", "---\nowners: [domain-owner]\n---\n");
|
|
54
|
+
const cache = new Map<string, string[]>();
|
|
55
|
+
const result = resolveNodeOwners(join(tmp.path, "domain"), tmp.path, cache);
|
|
56
|
+
expect(result).toEqual(["domain-owner"]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("inherits from parent", () => {
|
|
60
|
+
const tmp = useTmpDir();
|
|
61
|
+
write(tmp.path, "NODE.md", "---\nowners: [root-owner]\n---\n");
|
|
62
|
+
write(tmp.path, "domain/NODE.md", "---\nowners: []\n---\n");
|
|
63
|
+
const cache = new Map<string, string[]>();
|
|
64
|
+
const result = resolveNodeOwners(join(tmp.path, "domain"), tmp.path, cache);
|
|
65
|
+
expect(result).toEqual(["root-owner"]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// --- collectEntries ---
|
|
70
|
+
|
|
71
|
+
describe("collectEntries", () => {
|
|
72
|
+
it("excludes dot-prefixed dirs", () => {
|
|
73
|
+
const tmp = useTmpDir();
|
|
74
|
+
write(tmp.path, "NODE.md", "---\nowners: [root]\n---\n# Root\n");
|
|
75
|
+
write(tmp.path, "domain/NODE.md", "---\nowners: [alice]\n---\n# Domain\n");
|
|
76
|
+
write(tmp.path, ".hidden/NODE.md", "---\nowners: [secret]\n---\n# Hidden\n");
|
|
77
|
+
const entries = collectEntries(tmp.path);
|
|
78
|
+
const patterns = entries.map(([pat]) => pat);
|
|
79
|
+
expect(patterns.some((p) => p.includes("domain"))).toBe(true);
|
|
80
|
+
expect(patterns.some((p) => p.includes(".hidden"))).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// --- formatOwners ---
|
|
85
|
+
|
|
86
|
+
describe("formatOwners", () => {
|
|
87
|
+
it("deduplicates owners", () => {
|
|
88
|
+
expect(formatOwners(["alice", "bob", "alice"])).toBe("@alice @bob");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("adds @ prefix", () => {
|
|
92
|
+
expect(formatOwners(["alice"])).toBe("@alice");
|
|
93
|
+
});
|
|
94
|
+
});
|