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.
Files changed (80) hide show
  1. package/README.md +116 -40
  2. package/dist/cli.js +46 -17
  3. package/dist/help-Dtdj91HJ.js +25 -0
  4. package/dist/init--VepFe6N.js +403 -0
  5. package/dist/installer-cH7N4RNj.js +47 -0
  6. package/dist/onboarding-C9cYSE6F.js +2 -0
  7. package/dist/onboarding-CPP8fF4D.js +10 -0
  8. package/dist/repo-DY57bMqr.js +318 -0
  9. package/dist/upgrade-Cgx_K2HM.js +135 -0
  10. package/dist/{verify-CSRIkuoM.js → verify-mC9ZTd1f.js} +118 -29
  11. package/package.json +33 -10
  12. package/skills/first-tree/SKILL.md +113 -0
  13. package/skills/first-tree/agents/openai.yaml +4 -0
  14. package/skills/first-tree/assets/framework/VERSION +1 -0
  15. package/skills/first-tree/assets/framework/examples/claude-code/README.md +14 -0
  16. package/skills/first-tree/assets/framework/examples/claude-code/settings.json +14 -0
  17. package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +224 -0
  18. package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +15 -0
  19. package/skills/first-tree/assets/framework/helpers/run-review.ts +193 -0
  20. package/skills/first-tree/assets/framework/manifest.json +11 -0
  21. package/skills/first-tree/assets/framework/prompts/pr-review.md +38 -0
  22. package/skills/first-tree/assets/framework/templates/agents.md.template +49 -0
  23. package/skills/first-tree/assets/framework/templates/member-node.md.template +18 -0
  24. package/skills/first-tree/assets/framework/templates/members-domain.md.template +45 -0
  25. package/skills/first-tree/assets/framework/templates/root-node.md.template +41 -0
  26. package/skills/first-tree/assets/framework/workflows/codeowners.yml +31 -0
  27. package/skills/first-tree/assets/framework/workflows/pr-review.yml +146 -0
  28. package/skills/first-tree/assets/framework/workflows/validate.yml +19 -0
  29. package/skills/first-tree/engine/commands/help.ts +32 -0
  30. package/skills/first-tree/engine/commands/init.ts +1 -0
  31. package/skills/first-tree/engine/commands/upgrade.ts +1 -0
  32. package/skills/first-tree/engine/commands/verify.ts +1 -0
  33. package/skills/first-tree/engine/init.ts +414 -0
  34. package/skills/first-tree/engine/onboarding.ts +10 -0
  35. package/skills/first-tree/engine/repo.ts +360 -0
  36. package/skills/first-tree/engine/rules/agent-instructions.ts +59 -0
  37. package/skills/first-tree/engine/rules/agent-integration.ts +19 -0
  38. package/skills/first-tree/engine/rules/ci-validation.ts +72 -0
  39. package/skills/first-tree/engine/rules/framework.ts +13 -0
  40. package/skills/first-tree/engine/rules/index.ts +41 -0
  41. package/skills/first-tree/engine/rules/members.ts +21 -0
  42. package/skills/first-tree/engine/rules/populate-tree.ts +36 -0
  43. package/skills/first-tree/engine/rules/root-node.ts +41 -0
  44. package/skills/first-tree/engine/runtime/adapters.ts +22 -0
  45. package/skills/first-tree/engine/runtime/asset-loader.ts +141 -0
  46. package/skills/first-tree/engine/runtime/installer.ts +82 -0
  47. package/skills/first-tree/engine/runtime/upgrader.ts +23 -0
  48. package/skills/first-tree/engine/upgrade.ts +233 -0
  49. package/skills/first-tree/engine/validators/members.ts +215 -0
  50. package/skills/first-tree/engine/validators/nodes.ts +559 -0
  51. package/skills/first-tree/engine/verify.ts +155 -0
  52. package/skills/first-tree/references/about.md +36 -0
  53. package/skills/first-tree/references/maintainer-architecture.md +59 -0
  54. package/skills/first-tree/references/maintainer-build-and-distribution.md +59 -0
  55. package/skills/first-tree/references/maintainer-testing.md +58 -0
  56. package/skills/first-tree/references/maintainer-thin-cli.md +38 -0
  57. package/skills/first-tree/references/onboarding.md +185 -0
  58. package/skills/first-tree/references/ownership-and-naming.md +94 -0
  59. package/skills/first-tree/references/principles.md +113 -0
  60. package/skills/first-tree/references/source-map.md +94 -0
  61. package/skills/first-tree/references/upgrade-contract.md +94 -0
  62. package/skills/first-tree/scripts/check-skill-sync.sh +133 -0
  63. package/skills/first-tree/scripts/quick_validate.py +95 -0
  64. package/skills/first-tree/scripts/run-local-cli.sh +35 -0
  65. package/skills/first-tree/tests/asset-loader.test.ts +75 -0
  66. package/skills/first-tree/tests/generate-codeowners.test.ts +94 -0
  67. package/skills/first-tree/tests/helpers.ts +169 -0
  68. package/skills/first-tree/tests/init.test.ts +250 -0
  69. package/skills/first-tree/tests/repo.test.ts +440 -0
  70. package/skills/first-tree/tests/rules.test.ts +413 -0
  71. package/skills/first-tree/tests/run-review.test.ts +155 -0
  72. package/skills/first-tree/tests/skill-artifacts.test.ts +311 -0
  73. package/skills/first-tree/tests/thin-cli.test.ts +104 -0
  74. package/skills/first-tree/tests/upgrade.test.ts +103 -0
  75. package/skills/first-tree/tests/validate-members.test.ts +224 -0
  76. package/skills/first-tree/tests/validate-nodes.test.ts +198 -0
  77. package/skills/first-tree/tests/verify.test.ts +241 -0
  78. package/dist/init-CE_944sb.js +0 -283
  79. package/dist/repo-BByc3VvM.js +0 -111
  80. 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
+ });