@csark0812/skeleton 1.0.0 → 1.1.0

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 (3) hide show
  1. package/README.md +134 -3
  2. package/dist/cli.js +483 -75
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,138 @@
1
1
  # Skeleton
2
2
 
3
- **Source of truth for** the Skeleton agent harness package overview.
3
+ Single source of truth (SSOT) linter for agent-enabled repos.
4
4
 
5
- <!-- doc-meta: owner=eng | last-reviewed=2026-07-11 -->
5
+ Agent repos accumulate skills, rules, registries, and cross-linked docs faster than anyone can keep them straight by hand. Code repos solved this decades ago with ESLint — deterministic checks, CI gates, fix what you can before merge. Skeleton does the same job for documentation architecture: what's canonical, what links where, what must not exist, and whether your skill overrides are wired correctly.
6
6
 
7
- Thin SSOT audit CLI for agent-enabled repos. See [authoring conventions](docs/authoring.md).
7
+ Skeleton is **not** a runtime agent harness. It does not execute tools, enforce permissions, or manage agent memory. It is a CLI that audits docs, skills, and registries, and fails CI when invariants break.
8
+
9
+ ## Why
10
+
11
+ Code has linters. Agent repos need the same thing for docs and SSOT.
12
+
13
+ | Code repos | Agent repos |
14
+ | ------------------------------------------------------- | -------------------------------------------------------------------------------------- |
15
+ | ESLint catches broken imports, unused vars, style drift | Skeleton catches broken links, missing registry rows, stale doc-meta, banned artifacts |
16
+ | `eslint --fix` on changed files | `skeleton validate changed` on changed docs and skills |
17
+ | Pre-commit + CI gate | `--staged` pre-commit + `--base` CI gate |
18
+
19
+ Skill linters (skillmark, agentlint, skillscheck) answer: _"Is this SKILL.md well-formed?"_ Skeleton answers: _"Does this repo's documentation system hold together?"_
20
+
21
+ ## Quick start
22
+
23
+ ```bash
24
+ npm install -D @csark0812/skeleton
25
+ npx skeleton init --skills
26
+ ```
27
+
28
+ Init writes `.skeleton/`, merges validate scripts into `package.json`, and wires customize hooks for Cursor, Claude Code, and Codex.
29
+
30
+ Edit `.skeleton/config.yaml` for your repo layout, then verify:
31
+
32
+ ```bash
33
+ npx skeleton audit self
34
+ ```
35
+
36
+ See [install](docs/developer/install.md) for flags and options.
37
+
38
+ ## What it checks
39
+
40
+ - **Registry integrity** — `.skeleton/registry.md` topic → canonical file pointers; banner format on registered docs
41
+ - **Link audit** — broken refs, skill links, anchors in scanned markdown
42
+ - **Skill index** — disk matches taxonomy READMEs in detected skill roots
43
+ - **Banned paths** — session artifacts and other files that must not exist
44
+ - **Coverage gaps** — markdown outside the scan perimeter (warn-only)
45
+ - **Doc meta + stale dates** — owner and `last-reviewed` on index and registry-listed files
46
+ - **Shell / JSON syntax** — lightweight checks on changed `.sh` and `.json` files
47
+
48
+ Code validation (TypeScript, Python, Nx, pytest) stays in your repo. Skeleton handles SSOT-adjacent paths only.
49
+
50
+ ## The `.skeleton/` contract
51
+
52
+ ```
53
+ .skeleton/
54
+ ├── config.yaml # scan perimeter (required)
55
+ ├── registry.md # topic → canonical file (required)
56
+ └── customize/ # project-specific skill overrides (optional)
57
+ └── code-review.md
58
+ ```
59
+
60
+ Every canonical doc carries a banner:
61
+
62
+ ```markdown
63
+ **Source of truth for** Backend API conventions.
64
+ ```
65
+
66
+ Register it:
67
+
68
+ ```bash
69
+ skeleton register docs/developer/api.md
70
+ ```
71
+
72
+ Synced toolbox skills stay pristine. Project overrides live in `.skeleton/customize/<slug>.md` and inject via IDE hooks on skill read — no editing synced `SKILL.md` files.
73
+
74
+ ## Commands
75
+
76
+ ```bash
77
+ skeleton init [--skills] [--force-hooks]
78
+ skeleton register <path> [--topic=…]
79
+ skeleton audit docs|skills|self [--strict] [--paths=a,b]
80
+ skeleton validate changed [--staged | --base <ref>] [paths…]
81
+ skeleton customize resolve <slug>
82
+ ```
83
+
84
+ **Validate changed** routes git diffs to the right audit:
85
+
86
+ | Path | Action |
87
+ | -------------------------------------------- | --------------------------- |
88
+ | Docs/skills in scan perimeter | path-scoped audit |
89
+ | `.sh`, `.bash`, `.zsh` | shellcheck or `bash -n` |
90
+ | Other `.json` | JSONC-tolerant syntax check |
91
+ | `.ts`, `.py`, `package.json`, `project.json` | skip |
92
+
93
+ Pre-commit: `skeleton validate changed --staged` (path-scoped, fast).
94
+ CI: `skeleton validate changed --base origin/main` (global rules first, then changed files).
95
+
96
+ ## Ecosystem
97
+
98
+ Skeleton is the shared validation layer in a three-tier setup:
99
+
100
+ | Repo | Role |
101
+ | -------------------- | -------------------------------------------------- |
102
+ | **skeleton** | SSOT audit CLI (this repo) |
103
+ | **toolbox** | Team skills + public agent preferences |
104
+ | **personal-toolbox** | Private skills + personal preferences |
105
+ | **Consumer apps** | Call skeleton for SSOT; keep code validation local |
106
+
107
+ Skeleton never calls Nx or other task runners — consumers call skeleton for doc and skill paths, then handle code paths themselves.
108
+
109
+ See [tiers](docs/tiers.md).
110
+
111
+ ## Distribution
112
+
113
+ | Channel | Installs |
114
+ | ---------------------------------------------------- | ------------------------------------------------------- |
115
+ | `npm install -D @csark0812/skeleton` | CLI, schemas, audit engine, hook script |
116
+ | `npx skills add csark0812/skeleton --skill skeleton` | `/skeleton` agent skill (ops manual, not the installer) |
117
+
118
+ One command for humans: `npx skeleton init --skills`.
119
+
120
+ ## Docs
121
+
122
+ - [Install](docs/developer/install.md)
123
+ - [Doc system](docs/developer/doc-system.md)
124
+ - [Validation](docs/developer/validation.md)
125
+ - [Audit rules](docs/developer/audit.md)
126
+ - [Customize](docs/developer/customize.md)
127
+ - [Authoring conventions](docs/authoring.md)
128
+
129
+ ## Development
130
+
131
+ Requires Node ≥ 22. Uses Bun for dev and tests.
132
+
133
+ ```bash
134
+ bun install
135
+ bun test
136
+ bun run build
137
+ bun run audit:self
138
+ ```
package/dist/cli.js CHANGED
@@ -28582,6 +28582,28 @@ var scanRootsRule = { id: "scan-roots", run: runScanRootsRule };
28582
28582
  // src/audit/rules/skill-index.ts
28583
28583
  import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync3 } from "node:fs";
28584
28584
  import { join as join8, relative as relative5 } from "node:path";
28585
+
28586
+ // src/references/constants.ts
28587
+ var CANONICAL_REFS_DIR = ".skeleton/references";
28588
+ var GENERATED_MARKER_START = "<!-- skeleton: generated-reference";
28589
+ var GENERATED_MARKER_RE = /<!-- skeleton: generated-reference\s*\nsource: ([^\n]+)\s*\nredundancy: intentional\s*\n-->\s*\n?/;
28590
+ var SHARED_REF_LINK_RE = /\((?:\.\.\/)+references\/([^)]+)\)/g;
28591
+ function formatGeneratedHeader(sourceRelPath) {
28592
+ return `${GENERATED_MARKER_START}
28593
+ source: ${sourceRelPath}
28594
+ redundancy: intentional
28595
+ -->
28596
+
28597
+ `;
28598
+ }
28599
+ function stripGeneratedHeader(content3) {
28600
+ return content3.replace(GENERATED_MARKER_RE, "");
28601
+ }
28602
+ function isGeneratedReference(content3) {
28603
+ return content3.startsWith(GENERATED_MARKER_START);
28604
+ }
28605
+
28606
+ // src/audit/rules/skill-index.ts
28585
28607
  var NON_PUBLIC_SLUGS = new Set(["align-commands"]);
28586
28608
  function walkSkillMarkdown(dir) {
28587
28609
  const files = [];
@@ -28618,6 +28640,8 @@ function scanFileForSkillLinks(ctx, filePath, index2) {
28618
28640
  const issues = [];
28619
28641
  const rel = relative5(ctx.root, filePath).replace(/\\/g, "/");
28620
28642
  const content3 = readFileSync7(filePath, "utf8");
28643
+ if (isGeneratedReference(content3))
28644
+ return issues;
28621
28645
  for (const match of content3.matchAll(SKILL_LINK_RE)) {
28622
28646
  const slug2 = match[1];
28623
28647
  if (!slug2)
@@ -28690,6 +28714,219 @@ function skillCountOnDisk(ctx) {
28690
28714
  return listSkillSlugs(ctx.skillIndex).length;
28691
28715
  }
28692
28716
 
28717
+ // src/references/check.ts
28718
+ import { existsSync as existsSync10, readFileSync as readFileSync9, readdirSync as readdirSync5 } from "node:fs";
28719
+ import { join as join10, relative as relative7 } from "node:path";
28720
+
28721
+ // src/references/discover.ts
28722
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "node:fs";
28723
+ import { join as join9, relative as relative6 } from "node:path";
28724
+ function walkMarkdownFiles(dir, root2) {
28725
+ const files = [];
28726
+ if (!existsSync9(dir))
28727
+ return files;
28728
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
28729
+ if (entry.name.startsWith("."))
28730
+ continue;
28731
+ const fullPath = join9(dir, entry.name);
28732
+ if (entry.isDirectory()) {
28733
+ files.push(...walkMarkdownFiles(fullPath, root2));
28734
+ continue;
28735
+ }
28736
+ if (entry.name.endsWith(".md")) {
28737
+ files.push(normalizeRelPath(relative6(root2, fullPath)));
28738
+ }
28739
+ }
28740
+ return files;
28741
+ }
28742
+ function canonicalExists(root2, refPath) {
28743
+ return existsSync9(join9(root2, CANONICAL_REFS_DIR, refPath));
28744
+ }
28745
+ function findSharedRefLinks(content3, sourceFile) {
28746
+ const links = [];
28747
+ for (const match of content3.matchAll(SHARED_REF_LINK_RE)) {
28748
+ const refPath = match[1];
28749
+ if (!refPath)
28750
+ continue;
28751
+ links.push({ refPath: normalizeRelPath(refPath), sourceFile });
28752
+ }
28753
+ return links;
28754
+ }
28755
+ function findLocalCanonicalLinks(root2, content3, sourceFile) {
28756
+ const links = [];
28757
+ const localRefRe = /\((?:\.\/)?references\/([^)]+)\)/g;
28758
+ for (const match of content3.matchAll(localRefRe)) {
28759
+ const refPath = normalizeRelPath(match[1] ?? "");
28760
+ if (!refPath || !canonicalExists(root2, refPath))
28761
+ continue;
28762
+ links.push({ refPath, sourceFile });
28763
+ }
28764
+ const inReferencesDir = /\/references\//.test(sourceFile);
28765
+ if (inReferencesDir) {
28766
+ const siblingRe = /\((?!https?:|#|\.\.\/)([a-z0-9./_-]+\.md)\)/gi;
28767
+ for (const match of content3.matchAll(siblingRe)) {
28768
+ const refPath = normalizeRelPath(match[1] ?? "");
28769
+ if (!refPath || !canonicalExists(root2, refPath))
28770
+ continue;
28771
+ links.push({ refPath, sourceFile });
28772
+ }
28773
+ }
28774
+ return links;
28775
+ }
28776
+ function discoverSkillReferencePlans(root2) {
28777
+ const index2 = buildSkillIndex(root2);
28778
+ const plans = [];
28779
+ for (const slug2 of index2.slugs) {
28780
+ const skillDir = join9(root2, slug2);
28781
+ if (!existsSync9(join9(skillDir, "SKILL.md")))
28782
+ continue;
28783
+ const refPaths = new Set;
28784
+ const links = [];
28785
+ for (const relFile of walkMarkdownFiles(skillDir, root2)) {
28786
+ const content3 = readFileSync8(join9(root2, relFile), "utf8");
28787
+ if (isGeneratedReference(content3))
28788
+ continue;
28789
+ for (const link2 of findSharedRefLinks(content3, relFile)) {
28790
+ refPaths.add(link2.refPath);
28791
+ links.push(link2);
28792
+ }
28793
+ for (const link2 of findLocalCanonicalLinks(root2, content3, relFile)) {
28794
+ refPaths.add(link2.refPath);
28795
+ links.push(link2);
28796
+ }
28797
+ }
28798
+ if (refPaths.size > 0) {
28799
+ plans.push({ skill: slug2, refPaths, links });
28800
+ }
28801
+ }
28802
+ return plans.sort((a, b) => a.skill.localeCompare(b.skill));
28803
+ }
28804
+ function generatedRefPath(skill, refPath) {
28805
+ return normalizeRelPath(join9(skill, "references", refPath));
28806
+ }
28807
+ function rewriteSharedRefTarget(sourceFile, skill, refPath) {
28808
+ const sourceDir = sourceFile.slice(0, sourceFile.lastIndexOf("/"));
28809
+ const target = generatedRefPath(skill, refPath);
28810
+ if (!sourceDir)
28811
+ return target;
28812
+ const fromParts = sourceDir.split("/");
28813
+ const toParts = target.split("/");
28814
+ let i = 0;
28815
+ while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
28816
+ i++;
28817
+ }
28818
+ const ups = fromParts.length - i;
28819
+ const down = toParts.slice(i);
28820
+ const rel = [...Array(ups).fill(".."), ...down].join("/");
28821
+ return rel || (toParts.at(-1) ?? refPath);
28822
+ }
28823
+ function rewriteSharedRefLinks(content3, sourceFile, skill) {
28824
+ return content3.replace(SHARED_REF_LINK_RE, (_match, refPath) => {
28825
+ const rewritten = rewriteSharedRefTarget(sourceFile, skill, normalizeRelPath(refPath));
28826
+ return `(${rewritten})`;
28827
+ });
28828
+ }
28829
+
28830
+ // src/references/check.ts
28831
+ function listAllGeneratedFiles(root2) {
28832
+ const files = [];
28833
+ const walk = (dir) => {
28834
+ if (!existsSync10(dir))
28835
+ return;
28836
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
28837
+ if (entry.name.startsWith("."))
28838
+ continue;
28839
+ const fullPath = join10(dir, entry.name);
28840
+ if (entry.isDirectory()) {
28841
+ walk(fullPath);
28842
+ continue;
28843
+ }
28844
+ if (!entry.name.endsWith(".md"))
28845
+ continue;
28846
+ const content3 = readFileSync9(fullPath, "utf8");
28847
+ if (isGeneratedReference(content3)) {
28848
+ files.push(normalizeRelPath(relative7(root2, fullPath)));
28849
+ }
28850
+ }
28851
+ };
28852
+ walk(root2);
28853
+ return files;
28854
+ }
28855
+ function runGeneratedReferencesCheck(root2) {
28856
+ const issues = [];
28857
+ const canonicalDir = join10(root2, CANONICAL_REFS_DIR);
28858
+ if (!existsSync10(canonicalDir))
28859
+ return issues;
28860
+ const plans = discoverSkillReferencePlans(root2);
28861
+ const needed = new Set;
28862
+ for (const plan of plans) {
28863
+ for (const refPath of plan.refPaths) {
28864
+ needed.add(generatedRefPath(plan.skill, refPath));
28865
+ }
28866
+ }
28867
+ for (const targetRel of needed) {
28868
+ const targetPath = join10(root2, targetRel);
28869
+ if (!existsSync10(targetPath)) {
28870
+ issues.push(issue("generated-references", targetRel, "missing generated copy — run skeleton references sync"));
28871
+ continue;
28872
+ }
28873
+ const generated = readFileSync9(targetPath, "utf8");
28874
+ if (!isGeneratedReference(generated)) {
28875
+ issues.push(issue("generated-references", targetRel, "expected generated-reference provenance header"));
28876
+ continue;
28877
+ }
28878
+ const body = stripGeneratedHeader(generated);
28879
+ const sourceRel = normalizeRelPath(generated.match(/source: ([^\n]+)/)?.[1] ?? join10(CANONICAL_REFS_DIR, targetRel.split("/references/")[1] ?? ""));
28880
+ const canonicalPath = join10(root2, sourceRel);
28881
+ if (!existsSync10(canonicalPath)) {
28882
+ issues.push(issue("generated-references", targetRel, `canonical source missing: ${sourceRel}`));
28883
+ continue;
28884
+ }
28885
+ const canonical = readFileSync9(canonicalPath, "utf8");
28886
+ if (body !== canonical) {
28887
+ issues.push(issue("generated-references", targetRel, "stale generated copy — run skeleton references sync"));
28888
+ }
28889
+ }
28890
+ for (const generatedRel of listAllGeneratedFiles(root2)) {
28891
+ if (!needed.has(generatedRel)) {
28892
+ issues.push(issue("generated-references", generatedRel, "orphaned generated copy — run skeleton references sync"));
28893
+ }
28894
+ }
28895
+ for (const plan of plans) {
28896
+ const skillDir = join10(root2, plan.skill);
28897
+ if (!existsSync10(skillDir))
28898
+ continue;
28899
+ const walk = (dir) => {
28900
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
28901
+ if (entry.name.startsWith("."))
28902
+ continue;
28903
+ const fullPath = join10(dir, entry.name);
28904
+ if (entry.isDirectory()) {
28905
+ walk(fullPath);
28906
+ continue;
28907
+ }
28908
+ if (!entry.name.endsWith(".md"))
28909
+ continue;
28910
+ const relFile = normalizeRelPath(relative7(root2, fullPath));
28911
+ const content3 = readFileSync9(fullPath, "utf8");
28912
+ if (content3.match(SHARED_REF_LINK_RE)) {
28913
+ issues.push(issue("generated-references", relFile, "still links to shared root references/ — run skeleton references sync"));
28914
+ }
28915
+ }
28916
+ };
28917
+ walk(skillDir);
28918
+ }
28919
+ return issues;
28920
+ }
28921
+ function runGeneratedReferencesRule(ctx) {
28922
+ return runGeneratedReferencesCheck(ctx.root);
28923
+ }
28924
+ var generatedReferencesRule = {
28925
+ id: "generated-references",
28926
+ global: true,
28927
+ run: runGeneratedReferencesRule
28928
+ };
28929
+
28693
28930
  // src/audit/rules/index.ts
28694
28931
  var docsRules = [
28695
28932
  { ...scanRootsRule, global: true },
@@ -28699,7 +28936,10 @@ var docsRules = [
28699
28936
  docMetaRule,
28700
28937
  { ...bannedRule, global: true }
28701
28938
  ];
28702
- var skillsRules = [{ ...skillIndexRule, global: true }];
28939
+ var skillsRules = [
28940
+ { ...skillIndexRule, global: true },
28941
+ { ...generatedReferencesRule, global: true }
28942
+ ];
28703
28943
  var allRules = [...docsRules, ...skillsRules];
28704
28944
  function rulesForSuite(suite) {
28705
28945
  switch (suite) {
@@ -28781,16 +29021,16 @@ function runAudit(options) {
28781
29021
  }
28782
29022
 
28783
29023
  // src/customize/resolve.ts
28784
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
28785
- import { join as join9, relative as relative6 } from "node:path";
29024
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
29025
+ import { join as join11, relative as relative8 } from "node:path";
28786
29026
  var CUSTOMIZE_PREFIX = "Customize: ";
28787
29027
  function customizePathForSlug(root2, slug2) {
28788
- return join9(root2, REGISTRY_DIR_REL, "customize", `${slug2}.md`);
29028
+ return join11(root2, REGISTRY_DIR_REL, "customize", `${slug2}.md`);
28789
29029
  }
28790
29030
  function findCustomizeViaRegistry(root2, slug2) {
28791
29031
  for (const rel of parseRegistryPaths(root2)) {
28792
29032
  const expected = `${REGISTRY_DIR_REL}/customize/${slug2}.md`;
28793
- if (normalizeRelPath(rel) === expected && existsSync9(join9(root2, rel))) {
29033
+ if (normalizeRelPath(rel) === expected && existsSync11(join11(root2, rel))) {
28794
29034
  return rel;
28795
29035
  }
28796
29036
  }
@@ -28798,19 +29038,19 @@ function findCustomizeViaRegistry(root2, slug2) {
28798
29038
  }
28799
29039
  function resolveCustomize(root2, slug2) {
28800
29040
  const direct = customizePathForSlug(root2, slug2);
28801
- if (existsSync9(direct)) {
29041
+ if (existsSync11(direct)) {
28802
29042
  return {
28803
29043
  slug: slug2,
28804
- content: readFileSync8(direct, "utf8"),
28805
- path: normalizeRelPath(relative6(root2, direct))
29044
+ content: readFileSync10(direct, "utf8"),
29045
+ path: normalizeRelPath(relative8(root2, direct))
28806
29046
  };
28807
29047
  }
28808
29048
  const registryPath = findCustomizeViaRegistry(root2, slug2);
28809
29049
  if (registryPath) {
28810
- const abs = join9(root2, registryPath);
29050
+ const abs = join11(root2, registryPath);
28811
29051
  return {
28812
29052
  slug: slug2,
28813
- content: readFileSync8(abs, "utf8"),
29053
+ content: readFileSync10(abs, "utf8"),
28814
29054
  path: registryPath
28815
29055
  };
28816
29056
  }
@@ -28823,37 +29063,37 @@ function resolveCustomizeFromRoot(slug2, startDir) {
28823
29063
 
28824
29064
  // src/init/init.ts
28825
29065
  import { spawnSync as spawnSync2 } from "node:child_process";
28826
- import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync2, readFileSync as readFileSync10 } from "node:fs";
28827
- import { join as join13 } from "node:path";
29066
+ import { copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync2, readFileSync as readFileSync12 } from "node:fs";
29067
+ import { join as join15 } from "node:path";
28828
29068
 
28829
29069
  // src/init/merge-hooks.ts
28830
- import { existsSync as existsSync12, mkdirSync, readFileSync as readFileSync9, writeFileSync } from "node:fs";
28831
- import { dirname as dirname6, join as join12 } from "node:path";
29070
+ import { existsSync as existsSync14, mkdirSync, readFileSync as readFileSync11, writeFileSync } from "node:fs";
29071
+ import { dirname as dirname6, join as join14 } from "node:path";
28832
29072
 
28833
29073
  // src/init/resolve-hook-command.ts
28834
- import { existsSync as existsSync11 } from "node:fs";
29074
+ import { existsSync as existsSync13 } from "node:fs";
28835
29075
  import { createRequire as createRequire3 } from "node:module";
28836
- import { dirname as dirname5, join as join11, relative as relative7, resolve as resolve5 } from "node:path";
29076
+ import { dirname as dirname5, join as join13, relative as relative9, resolve as resolve5 } from "node:path";
28837
29077
 
28838
29078
  // src/init/package-paths.ts
28839
- import { existsSync as existsSync10 } from "node:fs";
28840
- import { dirname as dirname4, join as join10 } from "node:path";
29079
+ import { existsSync as existsSync12 } from "node:fs";
29080
+ import { dirname as dirname4, join as join12 } from "node:path";
28841
29081
  import { fileURLToPath as fileURLToPath4 } from "node:url";
28842
29082
  var MODULE_DIR = dirname4(fileURLToPath4(import.meta.url));
28843
29083
  var PACKAGE_ROOT_CANDIDATES = [
28844
- join10(MODULE_DIR, "../.."),
28845
- join10(MODULE_DIR, "..")
29084
+ join12(MODULE_DIR, "../.."),
29085
+ join12(MODULE_DIR, "..")
28846
29086
  ];
28847
29087
  function resolvePackageRoot() {
28848
29088
  for (const candidate of PACKAGE_ROOT_CANDIDATES) {
28849
- if (existsSync10(join10(candidate, "package.json")))
29089
+ if (existsSync12(join12(candidate, "package.json")))
28850
29090
  return candidate;
28851
29091
  }
28852
29092
  throw new Error("Could not resolve @csark0812/skeleton package root");
28853
29093
  }
28854
29094
  function resolveTemplatesDir() {
28855
- const dir = join10(resolvePackageRoot(), "templates/skeleton-init");
28856
- if (!existsSync10(dir)) {
29095
+ const dir = join12(resolvePackageRoot(), "templates/skeleton-init");
29096
+ if (!existsSync12(dir)) {
28857
29097
  throw new Error("Missing templates/skeleton-init in package");
28858
29098
  }
28859
29099
  return dir;
@@ -28865,12 +29105,12 @@ var HOOK_DIST = "dist/hooks/customize-on-skill-read.js";
28865
29105
  var HOOK_SRC = "src/hooks/customize-on-skill-read.ts";
28866
29106
  var PACKAGE_ROOT = resolvePackageRoot();
28867
29107
  function toRepoRelative(cwd, absPath) {
28868
- const rel = relative7(cwd, absPath).replace(/\\/g, "/");
29108
+ const rel = relative9(cwd, absPath).replace(/\\/g, "/");
28869
29109
  return rel.startsWith("..") ? absPath.replace(/\\/g, "/") : rel;
28870
29110
  }
28871
29111
  function tryResolvePublished(cwd) {
28872
29112
  try {
28873
- const req = createRequire3(join11(cwd, "package.json"));
29113
+ const req = createRequire3(join13(cwd, "package.json"));
28874
29114
  return req.resolve(`${PACKAGE_NAME}/${HOOK_DIST}`);
28875
29115
  } catch {
28876
29116
  return null;
@@ -28879,8 +29119,8 @@ function tryResolvePublished(cwd) {
28879
29119
  function walkNodeModules(cwd) {
28880
29120
  let dir = cwd;
28881
29121
  while (true) {
28882
- const candidate = join11(dir, "node_modules", PACKAGE_NAME, HOOK_DIST);
28883
- if (existsSync11(candidate))
29122
+ const candidate = join13(dir, "node_modules", PACKAGE_NAME, HOOK_DIST);
29123
+ if (existsSync13(candidate))
28884
29124
  return candidate;
28885
29125
  const parent = dirname5(dir);
28886
29126
  if (parent === dir)
@@ -28890,7 +29130,7 @@ function walkNodeModules(cwd) {
28890
29130
  return null;
28891
29131
  }
28892
29132
  function isInsidePackageRoot(cwd) {
28893
- const rel = relative7(PACKAGE_ROOT, resolve5(cwd)).replace(/\\/g, "/");
29133
+ const rel = relative9(PACKAGE_ROOT, resolve5(cwd)).replace(/\\/g, "/");
28894
29134
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
28895
29135
  }
28896
29136
  function resolveHookCommand(cwd) {
@@ -28901,11 +29141,11 @@ function resolveHookCommand(cwd) {
28901
29141
  if (hoisted)
28902
29142
  return toRepoRelative(cwd, hoisted);
28903
29143
  if (isInsidePackageRoot(cwd)) {
28904
- const distHook = join11(PACKAGE_ROOT, HOOK_DIST);
28905
- if (existsSync11(distHook))
29144
+ const distHook = join13(PACKAGE_ROOT, HOOK_DIST);
29145
+ if (existsSync13(distHook))
28906
29146
  return toRepoRelative(cwd, distHook);
28907
- const srcHook = join11(PACKAGE_ROOT, HOOK_SRC);
28908
- if (existsSync11(srcHook)) {
29147
+ const srcHook = join13(PACKAGE_ROOT, HOOK_SRC);
29148
+ if (existsSync13(srcHook)) {
28909
29149
  const rel = toRepoRelative(cwd, srcHook);
28910
29150
  return rel.includes("/") ? `bun ${rel}` : `bun ./${rel}`;
28911
29151
  }
@@ -28924,14 +29164,14 @@ function identityKey(platform, event, matcher) {
28924
29164
  return `skeleton:customize:${platform}:${event}:${matcher}`;
28925
29165
  }
28926
29166
  function loadFragment(name, hookCommand) {
28927
- const raw = readFileSync9(join12(TEMPLATES_DIR, name), "utf8");
29167
+ const raw = readFileSync11(join14(TEMPLATES_DIR, name), "utf8");
28928
29168
  return JSON.parse(raw.replaceAll("{{HOOK_COMMAND}}", hookCommand));
28929
29169
  }
28930
29170
  function readJson(path2) {
28931
- if (!existsSync12(path2))
29171
+ if (!existsSync14(path2))
28932
29172
  return null;
28933
29173
  try {
28934
- return JSON.parse(readFileSync9(path2, "utf8"));
29174
+ return JSON.parse(readFileSync11(path2, "utf8"));
28935
29175
  } catch (error) {
28936
29176
  throw new Error(`Invalid JSON in ${path2}: ${error}`);
28937
29177
  }
@@ -29037,14 +29277,14 @@ function mergeNestedHooks(platform, targetPath, fragment, eventName, opts) {
29037
29277
  }
29038
29278
  function mergeHookConfigs(opts) {
29039
29279
  const results = [];
29040
- const cursorPath = join12(opts.cwd, ".cursor/hooks.json");
29280
+ const cursorPath = join14(opts.cwd, ".cursor/hooks.json");
29041
29281
  const cursorFragment = loadFragment("cursor-hooks.fragment.json", opts.hookCommand);
29042
29282
  results.push(mergeCursorHooks(cursorPath, cursorFragment, opts));
29043
- const claudePath = join12(opts.cwd, ".claude/settings.json");
29283
+ const claudePath = join14(opts.cwd, ".claude/settings.json");
29044
29284
  const claudeFragment = loadFragment("claude-settings.fragment.json", opts.hookCommand);
29045
29285
  results.push(mergeNestedHooks("claude", claudePath, claudeFragment, "PostToolUse", opts));
29046
- const codexPath = join12(opts.cwd, ".codex/hooks.json");
29047
- if (existsSync12(join12(opts.cwd, ".codex"))) {
29286
+ const codexPath = join14(opts.cwd, ".codex/hooks.json");
29287
+ if (existsSync14(join14(opts.cwd, ".codex"))) {
29048
29288
  const codexFragment = loadFragment("codex-hooks.fragment.json", opts.hookCommand);
29049
29289
  results.push(mergeNestedHooks("codex", codexPath, codexFragment, "PostToolUse", opts));
29050
29290
  } else {
@@ -29053,11 +29293,11 @@ function mergeHookConfigs(opts) {
29053
29293
  return results;
29054
29294
  }
29055
29295
  function mergePackageJsonScripts(cwd) {
29056
- const pkgPath = join12(cwd, "package.json");
29057
- if (!existsSync12(pkgPath))
29296
+ const pkgPath = join14(cwd, "package.json");
29297
+ if (!existsSync14(pkgPath))
29058
29298
  return "skipped";
29059
- const fragment = JSON.parse(readFileSync9(join12(TEMPLATES_DIR, "package.json.scripts.fragment.json"), "utf8"));
29060
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
29299
+ const fragment = JSON.parse(readFileSync11(join14(TEMPLATES_DIR, "package.json.scripts.fragment.json"), "utf8"));
29300
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
29061
29301
  pkg.scripts ??= {};
29062
29302
  let changed = false;
29063
29303
  for (const [key, value] of Object.entries(fragment)) {
@@ -29113,27 +29353,27 @@ function skillsAddArgs(options = {}) {
29113
29353
  // src/init/init.ts
29114
29354
  var TEMPLATES_DIR2 = resolveTemplatesDir();
29115
29355
  function writeScaffold(cwd) {
29116
- const skeletonDir = join13(cwd, ".skeleton");
29356
+ const skeletonDir = join15(cwd, ".skeleton");
29117
29357
  mkdirSync2(skeletonDir, { recursive: true });
29118
29358
  let created = false;
29119
- const configPath = join13(skeletonDir, "config.yaml");
29120
- if (!existsSync13(configPath)) {
29121
- copyFileSync(join13(TEMPLATES_DIR2, "config.yaml"), configPath);
29359
+ const configPath = join15(skeletonDir, "config.yaml");
29360
+ if (!existsSync15(configPath)) {
29361
+ copyFileSync(join15(TEMPLATES_DIR2, "config.yaml"), configPath);
29122
29362
  created = true;
29123
29363
  }
29124
- const registryPath = join13(skeletonDir, "registry.md");
29125
- if (!existsSync13(registryPath)) {
29126
- copyFileSync(join13(TEMPLATES_DIR2, "registry.md"), registryPath);
29364
+ const registryPath = join15(skeletonDir, "registry.md");
29365
+ if (!existsSync15(registryPath)) {
29366
+ copyFileSync(join15(TEMPLATES_DIR2, "registry.md"), registryPath);
29127
29367
  created = true;
29128
29368
  }
29129
- mkdirSync2(join13(skeletonDir, "customize"), { recursive: true });
29369
+ mkdirSync2(join15(skeletonDir, "customize"), { recursive: true });
29130
29370
  return created ? "created" : "skipped";
29131
29371
  }
29132
29372
  function assertPackageResolvable(cwd) {
29133
- const pkgPath = join13(cwd, "package.json");
29134
- if (!existsSync13(pkgPath))
29373
+ const pkgPath = join15(cwd, "package.json");
29374
+ if (!existsSync15(pkgPath))
29135
29375
  return;
29136
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
29376
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
29137
29377
  const hasDep = pkg.devDependencies?.["@csark0812/skeleton"] || pkg.dependencies?.["@csark0812/skeleton"];
29138
29378
  if (!hasDep) {
29139
29379
  try {
@@ -29219,8 +29459,8 @@ function parseInitArgs(argv) {
29219
29459
  }
29220
29460
 
29221
29461
  // src/register.ts
29222
- import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync2 } from "node:fs";
29223
- import { dirname as dirname7, join as join14, relative as relative8 } from "node:path";
29462
+ import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "node:fs";
29463
+ import { dirname as dirname7, join as join16, relative as relative10 } from "node:path";
29224
29464
  var REGISTRY_TABLE_ROW_RE2 = /^\|\s*([^|]+)\|\s*\[[^\]]*\]\(([^)]+)\)\s*\|/;
29225
29465
  var REGISTRY_TABLE_HEADER = "| Topic | Canonical file |";
29226
29466
  function extractTopic(content3) {
@@ -29228,8 +29468,8 @@ function extractTopic(content3) {
29228
29468
  return match?.[1]?.trim().replace(/\s+$/, "") ?? null;
29229
29469
  }
29230
29470
  function toRegistryLink(root2, absPath) {
29231
- const fromRegistry = join14(root2, REGISTRY_DIR_REL);
29232
- return normalizeRelPath(relative8(fromRegistry, absPath));
29471
+ const fromRegistry = join16(root2, REGISTRY_DIR_REL);
29472
+ return normalizeRelPath(relative10(fromRegistry, absPath));
29233
29473
  }
29234
29474
  function inferSection(registryLink) {
29235
29475
  return registryLink.startsWith("customize/") ? "Customizations" : "Documentation";
@@ -29253,12 +29493,12 @@ function parseRegistryRows(content3) {
29253
29493
  return rows;
29254
29494
  }
29255
29495
  function pathFromRegistryLink(root2, link2) {
29256
- return normalizeRelPath(relative8(root2, join14(root2, REGISTRY_DIR_REL, link2)));
29496
+ return normalizeRelPath(relative10(root2, join16(root2, REGISTRY_DIR_REL, link2)));
29257
29497
  }
29258
29498
  function isOutsideScan(root2, relPath2) {
29259
29499
  const config = loadConfig(root2);
29260
29500
  const skillIndex = buildSkillIndex(root2);
29261
- const scanned = collectScanFiles(config, root2, skillIndex).map((abs) => normalizeRelPath(relative8(root2, abs)));
29501
+ const scanned = collectScanFiles(config, root2, skillIndex).map((abs) => normalizeRelPath(relative10(root2, abs)));
29262
29502
  if (scanned.includes(relPath2))
29263
29503
  return false;
29264
29504
  return !config.scan.include.some((pattern) => matchesGlobScope(relPath2, pattern));
@@ -29320,11 +29560,11 @@ ${newLine}
29320
29560
  function registerPath(options) {
29321
29561
  const root2 = options.root ?? findRepoRoot();
29322
29562
  const relPath2 = normalizeRelPath(options.path);
29323
- const absPath = join14(root2, relPath2);
29324
- if (!existsSync14(absPath)) {
29563
+ const absPath = join16(root2, relPath2);
29564
+ if (!existsSync16(absPath)) {
29325
29565
  throw new Error(`File not found: ${relPath2}`);
29326
29566
  }
29327
- const content3 = readFileSync11(absPath, "utf8");
29567
+ const content3 = readFileSync13(absPath, "utf8");
29328
29568
  let topic = options.topic ?? extractTopic(content3);
29329
29569
  if (!topic) {
29330
29570
  throw new Error(`No **Source of truth for** banner in ${relPath2} — add banner or pass --topic`);
@@ -29332,9 +29572,9 @@ function registerPath(options) {
29332
29572
  const registryLink = toRegistryLink(root2, absPath);
29333
29573
  topic = ensureCustomizeTopic(topic, registryLink);
29334
29574
  const section = inferSection(registryLink);
29335
- const registryAbs = join14(root2, REGISTRY_REL_PATH);
29336
- let registryContent = existsSync14(registryAbs) ? readFileSync11(registryAbs, "utf8") : defaultRegistryContent();
29337
- if (!existsSync14(registryAbs) && !existsSync14(join14(root2, ".skeleton/config.yaml"))) {
29575
+ const registryAbs = join16(root2, REGISTRY_REL_PATH);
29576
+ let registryContent = existsSync16(registryAbs) ? readFileSync13(registryAbs, "utf8") : defaultRegistryContent();
29577
+ if (!existsSync16(registryAbs) && !existsSync16(join16(root2, ".skeleton/config.yaml"))) {
29338
29578
  throw new Error("Missing .skeleton/config.yaml — run skeleton init first");
29339
29579
  }
29340
29580
  const { content: updated, action } = upsertRow(registryContent, topic, registryLink, section, root2);
@@ -29348,7 +29588,7 @@ function registerPath(options) {
29348
29588
  };
29349
29589
  if (!options.dryRun && action !== "noop") {
29350
29590
  const dir = dirname7(registryAbs);
29351
- if (!existsSync14(dir)) {
29591
+ if (!existsSync16(dir)) {
29352
29592
  throw new Error(`Missing ${REGISTRY_DIR_REL}/ directory`);
29353
29593
  }
29354
29594
  writeFileSync2(registryAbs, registryContent, "utf8");
@@ -29369,8 +29609,8 @@ function registerPath(options) {
29369
29609
  }
29370
29610
 
29371
29611
  // src/validate/changed.ts
29372
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
29373
- import { basename as basename2, extname, join as join15 } from "node:path";
29612
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
29613
+ import { basename as basename2, extname, join as join17 } from "node:path";
29374
29614
  import { spawnSync as spawnSync4 } from "node:child_process";
29375
29615
 
29376
29616
  // src/validate/git-diff.ts
@@ -29437,9 +29677,9 @@ function parseJsonContent(content3) {
29437
29677
  }
29438
29678
  }
29439
29679
  function validateJson(relPath2, root2) {
29440
- const abs = join15(root2, relPath2);
29680
+ const abs = join17(root2, relPath2);
29441
29681
  try {
29442
- parseJsonContent(readFileSync12(abs, "utf8"));
29682
+ parseJsonContent(readFileSync14(abs, "utf8"));
29443
29683
  return 0;
29444
29684
  } catch (error) {
29445
29685
  console.error(`validate changed: invalid JSON in ${relPath2}: ${error}`);
@@ -29447,7 +29687,7 @@ function validateJson(relPath2, root2) {
29447
29687
  }
29448
29688
  }
29449
29689
  function validateShell(relPath2, root2) {
29450
- const abs = join15(root2, relPath2);
29690
+ const abs = join17(root2, relPath2);
29451
29691
  const shellcheck = spawnSync4("shellcheck", [abs], { encoding: "utf8" });
29452
29692
  if (shellcheck.status === 0)
29453
29693
  return 0;
@@ -29482,8 +29722,8 @@ function runValidateChanged(options = {}) {
29482
29722
  };
29483
29723
  let skipped = 0;
29484
29724
  for (const relPath2 of relPaths) {
29485
- const abs = join15(root2, relPath2);
29486
- if (!existsSync15(abs))
29725
+ const abs = join17(root2, relPath2);
29726
+ if (!existsSync17(abs))
29487
29727
  continue;
29488
29728
  const bucket = bucketFor(relPath2, root2);
29489
29729
  if (bucket === "skip") {
@@ -29547,6 +29787,154 @@ function runValidateChanged(options = {}) {
29547
29787
  return exitCode;
29548
29788
  }
29549
29789
 
29790
+ // src/references/sync.ts
29791
+ import {
29792
+ existsSync as existsSync18,
29793
+ mkdirSync as mkdirSync3,
29794
+ readFileSync as readFileSync15,
29795
+ readdirSync as readdirSync6,
29796
+ unlinkSync,
29797
+ writeFileSync as writeFileSync3
29798
+ } from "node:fs";
29799
+ import { dirname as dirname8, join as join18, relative as relative11 } from "node:path";
29800
+ function walkMarkdownFiles2(dir, root2) {
29801
+ const files = [];
29802
+ if (!existsSync18(dir))
29803
+ return files;
29804
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
29805
+ if (entry.name.startsWith("."))
29806
+ continue;
29807
+ const fullPath = join18(dir, entry.name);
29808
+ if (entry.isDirectory()) {
29809
+ files.push(...walkMarkdownFiles2(fullPath, root2));
29810
+ continue;
29811
+ }
29812
+ if (entry.name.endsWith(".md")) {
29813
+ files.push(normalizeRelPath(relative11(root2, fullPath)));
29814
+ }
29815
+ }
29816
+ return files;
29817
+ }
29818
+ function listGeneratedReferenceFiles(skillDir, skill) {
29819
+ const refsDir = join18(skillDir, "references");
29820
+ if (!existsSync18(refsDir))
29821
+ return [];
29822
+ const files = [];
29823
+ const walk = (dir) => {
29824
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
29825
+ const fullPath = join18(dir, entry.name);
29826
+ if (entry.isDirectory()) {
29827
+ walk(fullPath);
29828
+ continue;
29829
+ }
29830
+ if (!entry.name.endsWith(".md"))
29831
+ continue;
29832
+ const content3 = readFileSync15(fullPath, "utf8");
29833
+ if (isGeneratedReference(content3)) {
29834
+ const refPath = normalizeRelPath(relative11(refsDir, fullPath));
29835
+ files.push(generatedRefPath(skill, refPath));
29836
+ }
29837
+ }
29838
+ };
29839
+ walk(refsDir);
29840
+ return files;
29841
+ }
29842
+ function syncReferences(options = {}) {
29843
+ const root2 = options.root ?? process.cwd();
29844
+ const canonicalDir = join18(root2, CANONICAL_REFS_DIR);
29845
+ if (!existsSync18(canonicalDir)) {
29846
+ throw new Error(`canonical references dir not found: ${CANONICAL_REFS_DIR}`);
29847
+ }
29848
+ const result = {
29849
+ written: [],
29850
+ rewritten: [],
29851
+ removed: [],
29852
+ skipped: []
29853
+ };
29854
+ const plans = discoverSkillReferencePlans(root2);
29855
+ for (const plan of plans) {
29856
+ const skillDir = join18(root2, plan.skill);
29857
+ for (const refPath of plan.refPaths) {
29858
+ const sourceRel = normalizeRelPath(join18(CANONICAL_REFS_DIR, refPath));
29859
+ const canonicalPath = join18(root2, sourceRel);
29860
+ if (!existsSync18(canonicalPath)) {
29861
+ throw new Error(`canonical reference missing: ${sourceRel}`);
29862
+ }
29863
+ const targetRel = generatedRefPath(plan.skill, refPath);
29864
+ const targetPath = join18(root2, targetRel);
29865
+ const canonicalContent = readFileSync15(canonicalPath, "utf8");
29866
+ const nextContent = formatGeneratedHeader(sourceRel) + canonicalContent;
29867
+ if (!options.dryRun) {
29868
+ mkdirSync3(dirname8(targetPath), { recursive: true });
29869
+ }
29870
+ const existing = existsSync18(targetPath) ? readFileSync15(targetPath, "utf8") : null;
29871
+ if (existing !== nextContent) {
29872
+ if (!options.dryRun)
29873
+ writeFileSync3(targetPath, nextContent, "utf8");
29874
+ result.written.push(targetRel);
29875
+ } else {
29876
+ result.skipped.push(targetRel);
29877
+ }
29878
+ }
29879
+ if (options.rewriteLinks !== false) {
29880
+ for (const relFile of walkMarkdownFiles2(skillDir, root2)) {
29881
+ const filePath = join18(root2, relFile);
29882
+ const content3 = readFileSync15(filePath, "utf8");
29883
+ const next = rewriteSharedRefLinks(content3, relFile, plan.skill);
29884
+ if (next !== content3) {
29885
+ if (!options.dryRun)
29886
+ writeFileSync3(filePath, next, "utf8");
29887
+ result.rewritten.push(relFile);
29888
+ }
29889
+ }
29890
+ }
29891
+ for (const generatedRel of listGeneratedReferenceFiles(skillDir, plan.skill)) {
29892
+ const refPath = generatedRel.slice(`${plan.skill}/references/`.length);
29893
+ if (!plan.refPaths.has(refPath)) {
29894
+ const fullPath = join18(root2, generatedRel);
29895
+ if (!options.dryRun)
29896
+ unlinkSync(fullPath);
29897
+ result.removed.push(generatedRel);
29898
+ }
29899
+ }
29900
+ }
29901
+ return result;
29902
+ }
29903
+
29904
+ // src/references/run.ts
29905
+ function runReferencesSync(options = {}) {
29906
+ return syncReferences(options);
29907
+ }
29908
+ function runReferencesCheck(options = {}) {
29909
+ const root2 = options.root ?? process.cwd();
29910
+ const issues = runGeneratedReferencesCheck(root2);
29911
+ return printReport(issues, {
29912
+ strict: options.strict,
29913
+ json: options.json,
29914
+ label: "References check"
29915
+ });
29916
+ }
29917
+ function printSyncResult(result) {
29918
+ if (result.written.length > 0) {
29919
+ console.log(`references sync: wrote ${result.written.length} file(s)`);
29920
+ for (const file of result.written)
29921
+ console.log(` + ${file}`);
29922
+ }
29923
+ if (result.rewritten.length > 0) {
29924
+ console.log(`references sync: rewrote links in ${result.rewritten.length} file(s)`);
29925
+ for (const file of result.rewritten)
29926
+ console.log(` ~ ${file}`);
29927
+ }
29928
+ if (result.removed.length > 0) {
29929
+ console.log(`references sync: removed ${result.removed.length} stale file(s)`);
29930
+ for (const file of result.removed)
29931
+ console.log(` - ${file}`);
29932
+ }
29933
+ if (result.written.length === 0 && result.rewritten.length === 0 && result.removed.length === 0) {
29934
+ console.log(`references sync: up to date (${result.skipped.length} file(s) checked)`);
29935
+ }
29936
+ }
29937
+
29550
29938
  // src/cli.ts
29551
29939
  function usage() {
29552
29940
  console.error(`Usage: skeleton <command>
@@ -29556,7 +29944,9 @@ Commands:
29556
29944
  audit docs|self|skills [--strict] [--json] [--paths=a,b] [--only=rule]
29557
29945
  validate changed [paths…] [--staged] [--base <ref>]
29558
29946
  register <path> [--topic=…] [--dry-run] [--json]
29559
- customize resolve <slug> [--json]`);
29947
+ customize resolve <slug> [--json]
29948
+ references sync [--dry-run] [--no-rewrite-links]
29949
+ references check [--json] [--strict]`);
29560
29950
  }
29561
29951
  function parseRegisterArgs(argv) {
29562
29952
  let path2 = null;
@@ -29640,6 +30030,24 @@ function main() {
29640
30030
  runInit(parsed);
29641
30031
  process.exit(0);
29642
30032
  }
30033
+ if (command === "references") {
30034
+ const sub = argv[1];
30035
+ if (sub === "sync") {
30036
+ const dryRun = argv.includes("--dry-run");
30037
+ const rewriteLinks = !argv.includes("--no-rewrite-links");
30038
+ const result = runReferencesSync({ dryRun, rewriteLinks });
30039
+ printSyncResult(result);
30040
+ process.exit(0);
30041
+ }
30042
+ if (sub === "check") {
30043
+ process.exit(runReferencesCheck({
30044
+ json: argv.includes("--json"),
30045
+ strict: argv.includes("--strict")
30046
+ }));
30047
+ }
30048
+ usage();
30049
+ process.exit(1);
30050
+ }
29643
30051
  usage();
29644
30052
  process.exit(1);
29645
30053
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csark0812/skeleton",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "SSOT audit CLI for agent harness repos",
5
5
  "type": "module",
6
6
  "bin": {