@csark0812/skeleton 1.0.0 → 1.1.1

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/dist/cli.js CHANGED
@@ -16536,7 +16536,8 @@ var $visitAsync = visit.visitAsync;
16536
16536
  // src/audit/config/load.ts
16537
16537
  var SCHEMA_CANDIDATES = [
16538
16538
  join(dirname2(fileURLToPath2(import.meta.url)), "../../../schemas/config.schema.json"),
16539
- join(dirname2(fileURLToPath2(import.meta.url)), "../schemas/config.schema.json")
16539
+ join(dirname2(fileURLToPath2(import.meta.url)), "../schemas/config.schema.json"),
16540
+ join(dirname2(fileURLToPath2(import.meta.url)), "../../schemas/config.schema.json")
16540
16541
  ];
16541
16542
  function resolveSchemaPath() {
16542
16543
  for (const candidate of SCHEMA_CANDIDATES) {
@@ -28582,6 +28583,28 @@ var scanRootsRule = { id: "scan-roots", run: runScanRootsRule };
28582
28583
  // src/audit/rules/skill-index.ts
28583
28584
  import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync3 } from "node:fs";
28584
28585
  import { join as join8, relative as relative5 } from "node:path";
28586
+
28587
+ // src/references/constants.ts
28588
+ var CANONICAL_REFS_DIR = ".skeleton/references";
28589
+ var GENERATED_MARKER_START = "<!-- skeleton: generated-reference";
28590
+ var GENERATED_MARKER_RE = /<!-- skeleton: generated-reference\s*\nsource: ([^\n]+)\s*\nredundancy: intentional\s*\n-->\s*\n?/;
28591
+ var SHARED_REF_LINK_RE = /\((?:\.\.\/)+references\/([^)]+)\)/g;
28592
+ function formatGeneratedHeader(sourceRelPath) {
28593
+ return `${GENERATED_MARKER_START}
28594
+ source: ${sourceRelPath}
28595
+ redundancy: intentional
28596
+ -->
28597
+
28598
+ `;
28599
+ }
28600
+ function stripGeneratedHeader(content3) {
28601
+ return content3.replace(GENERATED_MARKER_RE, "");
28602
+ }
28603
+ function isGeneratedReference(content3) {
28604
+ return content3.startsWith(GENERATED_MARKER_START);
28605
+ }
28606
+
28607
+ // src/audit/rules/skill-index.ts
28585
28608
  var NON_PUBLIC_SLUGS = new Set(["align-commands"]);
28586
28609
  function walkSkillMarkdown(dir) {
28587
28610
  const files = [];
@@ -28618,6 +28641,8 @@ function scanFileForSkillLinks(ctx, filePath, index2) {
28618
28641
  const issues = [];
28619
28642
  const rel = relative5(ctx.root, filePath).replace(/\\/g, "/");
28620
28643
  const content3 = readFileSync7(filePath, "utf8");
28644
+ if (isGeneratedReference(content3))
28645
+ return issues;
28621
28646
  for (const match of content3.matchAll(SKILL_LINK_RE)) {
28622
28647
  const slug2 = match[1];
28623
28648
  if (!slug2)
@@ -28690,6 +28715,219 @@ function skillCountOnDisk(ctx) {
28690
28715
  return listSkillSlugs(ctx.skillIndex).length;
28691
28716
  }
28692
28717
 
28718
+ // src/references/check.ts
28719
+ import { existsSync as existsSync10, readFileSync as readFileSync9, readdirSync as readdirSync5 } from "node:fs";
28720
+ import { join as join10, relative as relative7 } from "node:path";
28721
+
28722
+ // src/references/discover.ts
28723
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "node:fs";
28724
+ import { join as join9, relative as relative6 } from "node:path";
28725
+ function walkMarkdownFiles(dir, root2) {
28726
+ const files = [];
28727
+ if (!existsSync9(dir))
28728
+ return files;
28729
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
28730
+ if (entry.name.startsWith("."))
28731
+ continue;
28732
+ const fullPath = join9(dir, entry.name);
28733
+ if (entry.isDirectory()) {
28734
+ files.push(...walkMarkdownFiles(fullPath, root2));
28735
+ continue;
28736
+ }
28737
+ if (entry.name.endsWith(".md")) {
28738
+ files.push(normalizeRelPath(relative6(root2, fullPath)));
28739
+ }
28740
+ }
28741
+ return files;
28742
+ }
28743
+ function canonicalExists(root2, refPath) {
28744
+ return existsSync9(join9(root2, CANONICAL_REFS_DIR, refPath));
28745
+ }
28746
+ function findSharedRefLinks(content3, sourceFile) {
28747
+ const links = [];
28748
+ for (const match of content3.matchAll(SHARED_REF_LINK_RE)) {
28749
+ const refPath = match[1];
28750
+ if (!refPath)
28751
+ continue;
28752
+ links.push({ refPath: normalizeRelPath(refPath), sourceFile });
28753
+ }
28754
+ return links;
28755
+ }
28756
+ function findLocalCanonicalLinks(root2, content3, sourceFile) {
28757
+ const links = [];
28758
+ const localRefRe = /\((?:\.\/)?references\/([^)]+)\)/g;
28759
+ for (const match of content3.matchAll(localRefRe)) {
28760
+ const refPath = normalizeRelPath(match[1] ?? "");
28761
+ if (!refPath || !canonicalExists(root2, refPath))
28762
+ continue;
28763
+ links.push({ refPath, sourceFile });
28764
+ }
28765
+ const inReferencesDir = /\/references\//.test(sourceFile);
28766
+ if (inReferencesDir) {
28767
+ const siblingRe = /\((?!https?:|#|\.\.\/)([a-z0-9./_-]+\.md)\)/gi;
28768
+ for (const match of content3.matchAll(siblingRe)) {
28769
+ const refPath = normalizeRelPath(match[1] ?? "");
28770
+ if (!refPath || !canonicalExists(root2, refPath))
28771
+ continue;
28772
+ links.push({ refPath, sourceFile });
28773
+ }
28774
+ }
28775
+ return links;
28776
+ }
28777
+ function discoverSkillReferencePlans(root2) {
28778
+ const index2 = buildSkillIndex(root2);
28779
+ const plans = [];
28780
+ for (const slug2 of index2.slugs) {
28781
+ const skillDir = join9(root2, slug2);
28782
+ if (!existsSync9(join9(skillDir, "SKILL.md")))
28783
+ continue;
28784
+ const refPaths = new Set;
28785
+ const links = [];
28786
+ for (const relFile of walkMarkdownFiles(skillDir, root2)) {
28787
+ const content3 = readFileSync8(join9(root2, relFile), "utf8");
28788
+ if (isGeneratedReference(content3))
28789
+ continue;
28790
+ for (const link2 of findSharedRefLinks(content3, relFile)) {
28791
+ refPaths.add(link2.refPath);
28792
+ links.push(link2);
28793
+ }
28794
+ for (const link2 of findLocalCanonicalLinks(root2, content3, relFile)) {
28795
+ refPaths.add(link2.refPath);
28796
+ links.push(link2);
28797
+ }
28798
+ }
28799
+ if (refPaths.size > 0) {
28800
+ plans.push({ skill: slug2, refPaths, links });
28801
+ }
28802
+ }
28803
+ return plans.sort((a, b) => a.skill.localeCompare(b.skill));
28804
+ }
28805
+ function generatedRefPath(skill, refPath) {
28806
+ return normalizeRelPath(join9(skill, "references", refPath));
28807
+ }
28808
+ function rewriteSharedRefTarget(sourceFile, skill, refPath) {
28809
+ const sourceDir = sourceFile.slice(0, sourceFile.lastIndexOf("/"));
28810
+ const target = generatedRefPath(skill, refPath);
28811
+ if (!sourceDir)
28812
+ return target;
28813
+ const fromParts = sourceDir.split("/");
28814
+ const toParts = target.split("/");
28815
+ let i = 0;
28816
+ while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
28817
+ i++;
28818
+ }
28819
+ const ups = fromParts.length - i;
28820
+ const down = toParts.slice(i);
28821
+ const rel = [...Array(ups).fill(".."), ...down].join("/");
28822
+ return rel || (toParts.at(-1) ?? refPath);
28823
+ }
28824
+ function rewriteSharedRefLinks(content3, sourceFile, skill) {
28825
+ return content3.replace(SHARED_REF_LINK_RE, (_match, refPath) => {
28826
+ const rewritten = rewriteSharedRefTarget(sourceFile, skill, normalizeRelPath(refPath));
28827
+ return `(${rewritten})`;
28828
+ });
28829
+ }
28830
+
28831
+ // src/references/check.ts
28832
+ function listAllGeneratedFiles(root2) {
28833
+ const files = [];
28834
+ const walk = (dir) => {
28835
+ if (!existsSync10(dir))
28836
+ return;
28837
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
28838
+ if (entry.name.startsWith("."))
28839
+ continue;
28840
+ const fullPath = join10(dir, entry.name);
28841
+ if (entry.isDirectory()) {
28842
+ walk(fullPath);
28843
+ continue;
28844
+ }
28845
+ if (!entry.name.endsWith(".md"))
28846
+ continue;
28847
+ const content3 = readFileSync9(fullPath, "utf8");
28848
+ if (isGeneratedReference(content3)) {
28849
+ files.push(normalizeRelPath(relative7(root2, fullPath)));
28850
+ }
28851
+ }
28852
+ };
28853
+ walk(root2);
28854
+ return files;
28855
+ }
28856
+ function runGeneratedReferencesCheck(root2) {
28857
+ const issues = [];
28858
+ const canonicalDir = join10(root2, CANONICAL_REFS_DIR);
28859
+ if (!existsSync10(canonicalDir))
28860
+ return issues;
28861
+ const plans = discoverSkillReferencePlans(root2);
28862
+ const needed = new Set;
28863
+ for (const plan of plans) {
28864
+ for (const refPath of plan.refPaths) {
28865
+ needed.add(generatedRefPath(plan.skill, refPath));
28866
+ }
28867
+ }
28868
+ for (const targetRel of needed) {
28869
+ const targetPath = join10(root2, targetRel);
28870
+ if (!existsSync10(targetPath)) {
28871
+ issues.push(issue("generated-references", targetRel, "missing generated copy — run skeleton references sync"));
28872
+ continue;
28873
+ }
28874
+ const generated = readFileSync9(targetPath, "utf8");
28875
+ if (!isGeneratedReference(generated)) {
28876
+ issues.push(issue("generated-references", targetRel, "expected generated-reference provenance header"));
28877
+ continue;
28878
+ }
28879
+ const body = stripGeneratedHeader(generated);
28880
+ const sourceRel = normalizeRelPath(generated.match(/source: ([^\n]+)/)?.[1] ?? join10(CANONICAL_REFS_DIR, targetRel.split("/references/")[1] ?? ""));
28881
+ const canonicalPath = join10(root2, sourceRel);
28882
+ if (!existsSync10(canonicalPath)) {
28883
+ issues.push(issue("generated-references", targetRel, `canonical source missing: ${sourceRel}`));
28884
+ continue;
28885
+ }
28886
+ const canonical = readFileSync9(canonicalPath, "utf8");
28887
+ if (body !== canonical) {
28888
+ issues.push(issue("generated-references", targetRel, "stale generated copy — run skeleton references sync"));
28889
+ }
28890
+ }
28891
+ for (const generatedRel of listAllGeneratedFiles(root2)) {
28892
+ if (!needed.has(generatedRel)) {
28893
+ issues.push(issue("generated-references", generatedRel, "orphaned generated copy — run skeleton references sync"));
28894
+ }
28895
+ }
28896
+ for (const plan of plans) {
28897
+ const skillDir = join10(root2, plan.skill);
28898
+ if (!existsSync10(skillDir))
28899
+ continue;
28900
+ const walk = (dir) => {
28901
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
28902
+ if (entry.name.startsWith("."))
28903
+ continue;
28904
+ const fullPath = join10(dir, entry.name);
28905
+ if (entry.isDirectory()) {
28906
+ walk(fullPath);
28907
+ continue;
28908
+ }
28909
+ if (!entry.name.endsWith(".md"))
28910
+ continue;
28911
+ const relFile = normalizeRelPath(relative7(root2, fullPath));
28912
+ const content3 = readFileSync9(fullPath, "utf8");
28913
+ if (content3.match(SHARED_REF_LINK_RE)) {
28914
+ issues.push(issue("generated-references", relFile, "still links to shared root references/ — run skeleton references sync"));
28915
+ }
28916
+ }
28917
+ };
28918
+ walk(skillDir);
28919
+ }
28920
+ return issues;
28921
+ }
28922
+ function runGeneratedReferencesRule(ctx) {
28923
+ return runGeneratedReferencesCheck(ctx.root);
28924
+ }
28925
+ var generatedReferencesRule = {
28926
+ id: "generated-references",
28927
+ global: true,
28928
+ run: runGeneratedReferencesRule
28929
+ };
28930
+
28693
28931
  // src/audit/rules/index.ts
28694
28932
  var docsRules = [
28695
28933
  { ...scanRootsRule, global: true },
@@ -28699,7 +28937,10 @@ var docsRules = [
28699
28937
  docMetaRule,
28700
28938
  { ...bannedRule, global: true }
28701
28939
  ];
28702
- var skillsRules = [{ ...skillIndexRule, global: true }];
28940
+ var skillsRules = [
28941
+ { ...skillIndexRule, global: true },
28942
+ { ...generatedReferencesRule, global: true }
28943
+ ];
28703
28944
  var allRules = [...docsRules, ...skillsRules];
28704
28945
  function rulesForSuite(suite) {
28705
28946
  switch (suite) {
@@ -28781,40 +29022,94 @@ function runAudit(options) {
28781
29022
  }
28782
29023
 
28783
29024
  // 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";
29025
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
29026
+ import { basename as basename2, join as join11, relative as relative8 } from "node:path";
28786
29027
  var CUSTOMIZE_PREFIX = "Customize: ";
29028
+ function customizeDir(root2) {
29029
+ return join11(root2, REGISTRY_DIR_REL, "customize");
29030
+ }
28787
29031
  function customizePathForSlug(root2, slug2) {
28788
- return join9(root2, REGISTRY_DIR_REL, "customize", `${slug2}.md`);
29032
+ return join11(customizeDir(root2), `${slug2}.md`);
28789
29033
  }
28790
29034
  function findCustomizeViaRegistry(root2, slug2) {
28791
29035
  for (const rel of parseRegistryPaths(root2)) {
28792
29036
  const expected = `${REGISTRY_DIR_REL}/customize/${slug2}.md`;
28793
- if (normalizeRelPath(rel) === expected && existsSync9(join9(root2, rel))) {
29037
+ if (normalizeRelPath(rel) === expected && existsSync11(join11(root2, rel))) {
28794
29038
  return rel;
28795
29039
  }
28796
29040
  }
28797
29041
  return null;
28798
29042
  }
28799
- function resolveCustomize(root2, slug2) {
29043
+ function resolveSlugFile(root2, slug2) {
28800
29044
  const direct = customizePathForSlug(root2, slug2);
28801
- if (existsSync9(direct)) {
29045
+ if (existsSync11(direct)) {
28802
29046
  return {
28803
- slug: slug2,
28804
- content: readFileSync8(direct, "utf8"),
28805
- path: normalizeRelPath(relative6(root2, direct))
29047
+ content: readFileSync10(direct, "utf8"),
29048
+ path: normalizeRelPath(relative8(root2, direct))
28806
29049
  };
28807
29050
  }
28808
29051
  const registryPath = findCustomizeViaRegistry(root2, slug2);
28809
29052
  if (registryPath) {
28810
- const abs = join9(root2, registryPath);
29053
+ const abs = join11(root2, registryPath);
28811
29054
  return {
28812
- slug: slug2,
28813
- content: readFileSync8(abs, "utf8"),
29055
+ content: readFileSync10(abs, "utf8"),
28814
29056
  path: registryPath
28815
29057
  };
28816
29058
  }
28817
- return { slug: slug2, content: null, path: null };
29059
+ return { content: null, path: null };
29060
+ }
29061
+ function alwaysIncludeBasenames(root2) {
29062
+ try {
29063
+ const config = loadConfig(root2);
29064
+ return config.customize?.alwaysInclude ?? [];
29065
+ } catch {
29066
+ return [];
29067
+ }
29068
+ }
29069
+ function readAlwaysInclude(root2, basenames, skipBasename) {
29070
+ const parts = [];
29071
+ const paths = [];
29072
+ const dir = customizeDir(root2);
29073
+ for (const name of basenames) {
29074
+ const file = basename2(name);
29075
+ if (skipBasename && file === skipBasename)
29076
+ continue;
29077
+ const abs = join11(dir, file);
29078
+ if (!existsSync11(abs))
29079
+ continue;
29080
+ parts.push(readFileSync10(abs, "utf8").trimEnd());
29081
+ paths.push(normalizeRelPath(relative8(root2, abs)));
29082
+ }
29083
+ return { parts, paths };
29084
+ }
29085
+ function resolveCustomize(root2, slug2) {
29086
+ const slugFile = resolveSlugFile(root2, slug2);
29087
+ const alwaysNames = alwaysIncludeBasenames(root2);
29088
+ const skip = slugFile.path != null ? basename2(slugFile.path) : null;
29089
+ const always = readAlwaysInclude(root2, alwaysNames, skip);
29090
+ const parts = [];
29091
+ const included = [];
29092
+ if (slugFile.content != null && slugFile.content.trim().length > 0) {
29093
+ parts.push(slugFile.content.trimEnd());
29094
+ if (slugFile.path)
29095
+ included.push(slugFile.path);
29096
+ }
29097
+ parts.push(...always.parts);
29098
+ included.push(...always.paths);
29099
+ if (parts.length === 0) {
29100
+ return { slug: slug2, content: null, path: slugFile.path, included: [] };
29101
+ }
29102
+ return {
29103
+ slug: slug2,
29104
+ content: parts.join(`
29105
+
29106
+ ---
29107
+
29108
+ `) + `
29109
+ `,
29110
+ path: slugFile.path ?? always.paths[0] ?? null,
29111
+ included
29112
+ };
28818
29113
  }
28819
29114
  function resolveCustomizeFromRoot(slug2, startDir) {
28820
29115
  const root2 = findRepoRoot(startDir);
@@ -28823,37 +29118,37 @@ function resolveCustomizeFromRoot(slug2, startDir) {
28823
29118
 
28824
29119
  // src/init/init.ts
28825
29120
  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";
29121
+ import { copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync2, readFileSync as readFileSync12 } from "node:fs";
29122
+ import { join as join15 } from "node:path";
28828
29123
 
28829
29124
  // 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";
29125
+ import { existsSync as existsSync14, mkdirSync, readFileSync as readFileSync11, writeFileSync } from "node:fs";
29126
+ import { dirname as dirname6, join as join14 } from "node:path";
28832
29127
 
28833
29128
  // src/init/resolve-hook-command.ts
28834
- import { existsSync as existsSync11 } from "node:fs";
29129
+ import { existsSync as existsSync13 } from "node:fs";
28835
29130
  import { createRequire as createRequire3 } from "node:module";
28836
- import { dirname as dirname5, join as join11, relative as relative7, resolve as resolve5 } from "node:path";
29131
+ import { dirname as dirname5, join as join13, relative as relative9, resolve as resolve5 } from "node:path";
28837
29132
 
28838
29133
  // src/init/package-paths.ts
28839
- import { existsSync as existsSync10 } from "node:fs";
28840
- import { dirname as dirname4, join as join10 } from "node:path";
29134
+ import { existsSync as existsSync12 } from "node:fs";
29135
+ import { dirname as dirname4, join as join12 } from "node:path";
28841
29136
  import { fileURLToPath as fileURLToPath4 } from "node:url";
28842
29137
  var MODULE_DIR = dirname4(fileURLToPath4(import.meta.url));
28843
29138
  var PACKAGE_ROOT_CANDIDATES = [
28844
- join10(MODULE_DIR, "../.."),
28845
- join10(MODULE_DIR, "..")
29139
+ join12(MODULE_DIR, "../.."),
29140
+ join12(MODULE_DIR, "..")
28846
29141
  ];
28847
29142
  function resolvePackageRoot() {
28848
29143
  for (const candidate of PACKAGE_ROOT_CANDIDATES) {
28849
- if (existsSync10(join10(candidate, "package.json")))
29144
+ if (existsSync12(join12(candidate, "package.json")))
28850
29145
  return candidate;
28851
29146
  }
28852
29147
  throw new Error("Could not resolve @csark0812/skeleton package root");
28853
29148
  }
28854
29149
  function resolveTemplatesDir() {
28855
- const dir = join10(resolvePackageRoot(), "templates/skeleton-init");
28856
- if (!existsSync10(dir)) {
29150
+ const dir = join12(resolvePackageRoot(), "templates/skeleton-init");
29151
+ if (!existsSync12(dir)) {
28857
29152
  throw new Error("Missing templates/skeleton-init in package");
28858
29153
  }
28859
29154
  return dir;
@@ -28865,12 +29160,12 @@ var HOOK_DIST = "dist/hooks/customize-on-skill-read.js";
28865
29160
  var HOOK_SRC = "src/hooks/customize-on-skill-read.ts";
28866
29161
  var PACKAGE_ROOT = resolvePackageRoot();
28867
29162
  function toRepoRelative(cwd, absPath) {
28868
- const rel = relative7(cwd, absPath).replace(/\\/g, "/");
29163
+ const rel = relative9(cwd, absPath).replace(/\\/g, "/");
28869
29164
  return rel.startsWith("..") ? absPath.replace(/\\/g, "/") : rel;
28870
29165
  }
28871
29166
  function tryResolvePublished(cwd) {
28872
29167
  try {
28873
- const req = createRequire3(join11(cwd, "package.json"));
29168
+ const req = createRequire3(join13(cwd, "package.json"));
28874
29169
  return req.resolve(`${PACKAGE_NAME}/${HOOK_DIST}`);
28875
29170
  } catch {
28876
29171
  return null;
@@ -28879,8 +29174,8 @@ function tryResolvePublished(cwd) {
28879
29174
  function walkNodeModules(cwd) {
28880
29175
  let dir = cwd;
28881
29176
  while (true) {
28882
- const candidate = join11(dir, "node_modules", PACKAGE_NAME, HOOK_DIST);
28883
- if (existsSync11(candidate))
29177
+ const candidate = join13(dir, "node_modules", PACKAGE_NAME, HOOK_DIST);
29178
+ if (existsSync13(candidate))
28884
29179
  return candidate;
28885
29180
  const parent = dirname5(dir);
28886
29181
  if (parent === dir)
@@ -28890,7 +29185,7 @@ function walkNodeModules(cwd) {
28890
29185
  return null;
28891
29186
  }
28892
29187
  function isInsidePackageRoot(cwd) {
28893
- const rel = relative7(PACKAGE_ROOT, resolve5(cwd)).replace(/\\/g, "/");
29188
+ const rel = relative9(PACKAGE_ROOT, resolve5(cwd)).replace(/\\/g, "/");
28894
29189
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
28895
29190
  }
28896
29191
  function resolveHookCommand(cwd) {
@@ -28901,11 +29196,11 @@ function resolveHookCommand(cwd) {
28901
29196
  if (hoisted)
28902
29197
  return toRepoRelative(cwd, hoisted);
28903
29198
  if (isInsidePackageRoot(cwd)) {
28904
- const distHook = join11(PACKAGE_ROOT, HOOK_DIST);
28905
- if (existsSync11(distHook))
29199
+ const distHook = join13(PACKAGE_ROOT, HOOK_DIST);
29200
+ if (existsSync13(distHook))
28906
29201
  return toRepoRelative(cwd, distHook);
28907
- const srcHook = join11(PACKAGE_ROOT, HOOK_SRC);
28908
- if (existsSync11(srcHook)) {
29202
+ const srcHook = join13(PACKAGE_ROOT, HOOK_SRC);
29203
+ if (existsSync13(srcHook)) {
28909
29204
  const rel = toRepoRelative(cwd, srcHook);
28910
29205
  return rel.includes("/") ? `bun ${rel}` : `bun ./${rel}`;
28911
29206
  }
@@ -28924,14 +29219,14 @@ function identityKey(platform, event, matcher) {
28924
29219
  return `skeleton:customize:${platform}:${event}:${matcher}`;
28925
29220
  }
28926
29221
  function loadFragment(name, hookCommand) {
28927
- const raw = readFileSync9(join12(TEMPLATES_DIR, name), "utf8");
29222
+ const raw = readFileSync11(join14(TEMPLATES_DIR, name), "utf8");
28928
29223
  return JSON.parse(raw.replaceAll("{{HOOK_COMMAND}}", hookCommand));
28929
29224
  }
28930
29225
  function readJson(path2) {
28931
- if (!existsSync12(path2))
29226
+ if (!existsSync14(path2))
28932
29227
  return null;
28933
29228
  try {
28934
- return JSON.parse(readFileSync9(path2, "utf8"));
29229
+ return JSON.parse(readFileSync11(path2, "utf8"));
28935
29230
  } catch (error) {
28936
29231
  throw new Error(`Invalid JSON in ${path2}: ${error}`);
28937
29232
  }
@@ -29037,14 +29332,14 @@ function mergeNestedHooks(platform, targetPath, fragment, eventName, opts) {
29037
29332
  }
29038
29333
  function mergeHookConfigs(opts) {
29039
29334
  const results = [];
29040
- const cursorPath = join12(opts.cwd, ".cursor/hooks.json");
29335
+ const cursorPath = join14(opts.cwd, ".cursor/hooks.json");
29041
29336
  const cursorFragment = loadFragment("cursor-hooks.fragment.json", opts.hookCommand);
29042
29337
  results.push(mergeCursorHooks(cursorPath, cursorFragment, opts));
29043
- const claudePath = join12(opts.cwd, ".claude/settings.json");
29338
+ const claudePath = join14(opts.cwd, ".claude/settings.json");
29044
29339
  const claudeFragment = loadFragment("claude-settings.fragment.json", opts.hookCommand);
29045
29340
  results.push(mergeNestedHooks("claude", claudePath, claudeFragment, "PostToolUse", opts));
29046
- const codexPath = join12(opts.cwd, ".codex/hooks.json");
29047
- if (existsSync12(join12(opts.cwd, ".codex"))) {
29341
+ const codexPath = join14(opts.cwd, ".codex/hooks.json");
29342
+ if (existsSync14(join14(opts.cwd, ".codex"))) {
29048
29343
  const codexFragment = loadFragment("codex-hooks.fragment.json", opts.hookCommand);
29049
29344
  results.push(mergeNestedHooks("codex", codexPath, codexFragment, "PostToolUse", opts));
29050
29345
  } else {
@@ -29053,11 +29348,11 @@ function mergeHookConfigs(opts) {
29053
29348
  return results;
29054
29349
  }
29055
29350
  function mergePackageJsonScripts(cwd) {
29056
- const pkgPath = join12(cwd, "package.json");
29057
- if (!existsSync12(pkgPath))
29351
+ const pkgPath = join14(cwd, "package.json");
29352
+ if (!existsSync14(pkgPath))
29058
29353
  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"));
29354
+ const fragment = JSON.parse(readFileSync11(join14(TEMPLATES_DIR, "package.json.scripts.fragment.json"), "utf8"));
29355
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
29061
29356
  pkg.scripts ??= {};
29062
29357
  let changed = false;
29063
29358
  for (const [key, value] of Object.entries(fragment)) {
@@ -29113,27 +29408,27 @@ function skillsAddArgs(options = {}) {
29113
29408
  // src/init/init.ts
29114
29409
  var TEMPLATES_DIR2 = resolveTemplatesDir();
29115
29410
  function writeScaffold(cwd) {
29116
- const skeletonDir = join13(cwd, ".skeleton");
29411
+ const skeletonDir = join15(cwd, ".skeleton");
29117
29412
  mkdirSync2(skeletonDir, { recursive: true });
29118
29413
  let created = false;
29119
- const configPath = join13(skeletonDir, "config.yaml");
29120
- if (!existsSync13(configPath)) {
29121
- copyFileSync(join13(TEMPLATES_DIR2, "config.yaml"), configPath);
29414
+ const configPath = join15(skeletonDir, "config.yaml");
29415
+ if (!existsSync15(configPath)) {
29416
+ copyFileSync(join15(TEMPLATES_DIR2, "config.yaml"), configPath);
29122
29417
  created = true;
29123
29418
  }
29124
- const registryPath = join13(skeletonDir, "registry.md");
29125
- if (!existsSync13(registryPath)) {
29126
- copyFileSync(join13(TEMPLATES_DIR2, "registry.md"), registryPath);
29419
+ const registryPath = join15(skeletonDir, "registry.md");
29420
+ if (!existsSync15(registryPath)) {
29421
+ copyFileSync(join15(TEMPLATES_DIR2, "registry.md"), registryPath);
29127
29422
  created = true;
29128
29423
  }
29129
- mkdirSync2(join13(skeletonDir, "customize"), { recursive: true });
29424
+ mkdirSync2(join15(skeletonDir, "customize"), { recursive: true });
29130
29425
  return created ? "created" : "skipped";
29131
29426
  }
29132
29427
  function assertPackageResolvable(cwd) {
29133
- const pkgPath = join13(cwd, "package.json");
29134
- if (!existsSync13(pkgPath))
29428
+ const pkgPath = join15(cwd, "package.json");
29429
+ if (!existsSync15(pkgPath))
29135
29430
  return;
29136
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
29431
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
29137
29432
  const hasDep = pkg.devDependencies?.["@csark0812/skeleton"] || pkg.dependencies?.["@csark0812/skeleton"];
29138
29433
  if (!hasDep) {
29139
29434
  try {
@@ -29219,8 +29514,8 @@ function parseInitArgs(argv) {
29219
29514
  }
29220
29515
 
29221
29516
  // 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";
29517
+ import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "node:fs";
29518
+ import { dirname as dirname7, join as join16, relative as relative10 } from "node:path";
29224
29519
  var REGISTRY_TABLE_ROW_RE2 = /^\|\s*([^|]+)\|\s*\[[^\]]*\]\(([^)]+)\)\s*\|/;
29225
29520
  var REGISTRY_TABLE_HEADER = "| Topic | Canonical file |";
29226
29521
  function extractTopic(content3) {
@@ -29228,8 +29523,8 @@ function extractTopic(content3) {
29228
29523
  return match?.[1]?.trim().replace(/\s+$/, "") ?? null;
29229
29524
  }
29230
29525
  function toRegistryLink(root2, absPath) {
29231
- const fromRegistry = join14(root2, REGISTRY_DIR_REL);
29232
- return normalizeRelPath(relative8(fromRegistry, absPath));
29526
+ const fromRegistry = join16(root2, REGISTRY_DIR_REL);
29527
+ return normalizeRelPath(relative10(fromRegistry, absPath));
29233
29528
  }
29234
29529
  function inferSection(registryLink) {
29235
29530
  return registryLink.startsWith("customize/") ? "Customizations" : "Documentation";
@@ -29253,12 +29548,12 @@ function parseRegistryRows(content3) {
29253
29548
  return rows;
29254
29549
  }
29255
29550
  function pathFromRegistryLink(root2, link2) {
29256
- return normalizeRelPath(relative8(root2, join14(root2, REGISTRY_DIR_REL, link2)));
29551
+ return normalizeRelPath(relative10(root2, join16(root2, REGISTRY_DIR_REL, link2)));
29257
29552
  }
29258
29553
  function isOutsideScan(root2, relPath2) {
29259
29554
  const config = loadConfig(root2);
29260
29555
  const skillIndex = buildSkillIndex(root2);
29261
- const scanned = collectScanFiles(config, root2, skillIndex).map((abs) => normalizeRelPath(relative8(root2, abs)));
29556
+ const scanned = collectScanFiles(config, root2, skillIndex).map((abs) => normalizeRelPath(relative10(root2, abs)));
29262
29557
  if (scanned.includes(relPath2))
29263
29558
  return false;
29264
29559
  return !config.scan.include.some((pattern) => matchesGlobScope(relPath2, pattern));
@@ -29320,11 +29615,11 @@ ${newLine}
29320
29615
  function registerPath(options) {
29321
29616
  const root2 = options.root ?? findRepoRoot();
29322
29617
  const relPath2 = normalizeRelPath(options.path);
29323
- const absPath = join14(root2, relPath2);
29324
- if (!existsSync14(absPath)) {
29618
+ const absPath = join16(root2, relPath2);
29619
+ if (!existsSync16(absPath)) {
29325
29620
  throw new Error(`File not found: ${relPath2}`);
29326
29621
  }
29327
- const content3 = readFileSync11(absPath, "utf8");
29622
+ const content3 = readFileSync13(absPath, "utf8");
29328
29623
  let topic = options.topic ?? extractTopic(content3);
29329
29624
  if (!topic) {
29330
29625
  throw new Error(`No **Source of truth for** banner in ${relPath2} — add banner or pass --topic`);
@@ -29332,9 +29627,9 @@ function registerPath(options) {
29332
29627
  const registryLink = toRegistryLink(root2, absPath);
29333
29628
  topic = ensureCustomizeTopic(topic, registryLink);
29334
29629
  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"))) {
29630
+ const registryAbs = join16(root2, REGISTRY_REL_PATH);
29631
+ let registryContent = existsSync16(registryAbs) ? readFileSync13(registryAbs, "utf8") : defaultRegistryContent();
29632
+ if (!existsSync16(registryAbs) && !existsSync16(join16(root2, ".skeleton/config.yaml"))) {
29338
29633
  throw new Error("Missing .skeleton/config.yaml — run skeleton init first");
29339
29634
  }
29340
29635
  const { content: updated, action } = upsertRow(registryContent, topic, registryLink, section, root2);
@@ -29348,7 +29643,7 @@ function registerPath(options) {
29348
29643
  };
29349
29644
  if (!options.dryRun && action !== "noop") {
29350
29645
  const dir = dirname7(registryAbs);
29351
- if (!existsSync14(dir)) {
29646
+ if (!existsSync16(dir)) {
29352
29647
  throw new Error(`Missing ${REGISTRY_DIR_REL}/ directory`);
29353
29648
  }
29354
29649
  writeFileSync2(registryAbs, registryContent, "utf8");
@@ -29369,8 +29664,8 @@ function registerPath(options) {
29369
29664
  }
29370
29665
 
29371
29666
  // 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";
29667
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
29668
+ import { basename as basename3, extname, join as join17 } from "node:path";
29374
29669
  import { spawnSync as spawnSync4 } from "node:child_process";
29375
29670
 
29376
29671
  // src/validate/git-diff.ts
@@ -29401,7 +29696,7 @@ var COMMAND_CONFIG_NAMES = new Set(["package.json", "project.json"]);
29401
29696
  function bucketFor(relPath2, root2) {
29402
29697
  const normalized = normalizeRelPath(relPath2);
29403
29698
  const ext = extname(normalized).toLowerCase();
29404
- const name = basename2(normalized);
29699
+ const name = basename3(normalized);
29405
29700
  if (SKIP_EXTENSIONS.has(ext))
29406
29701
  return "skip";
29407
29702
  if (COMMAND_CONFIG_NAMES.has(name))
@@ -29437,9 +29732,9 @@ function parseJsonContent(content3) {
29437
29732
  }
29438
29733
  }
29439
29734
  function validateJson(relPath2, root2) {
29440
- const abs = join15(root2, relPath2);
29735
+ const abs = join17(root2, relPath2);
29441
29736
  try {
29442
- parseJsonContent(readFileSync12(abs, "utf8"));
29737
+ parseJsonContent(readFileSync14(abs, "utf8"));
29443
29738
  return 0;
29444
29739
  } catch (error) {
29445
29740
  console.error(`validate changed: invalid JSON in ${relPath2}: ${error}`);
@@ -29447,7 +29742,7 @@ function validateJson(relPath2, root2) {
29447
29742
  }
29448
29743
  }
29449
29744
  function validateShell(relPath2, root2) {
29450
- const abs = join15(root2, relPath2);
29745
+ const abs = join17(root2, relPath2);
29451
29746
  const shellcheck = spawnSync4("shellcheck", [abs], { encoding: "utf8" });
29452
29747
  if (shellcheck.status === 0)
29453
29748
  return 0;
@@ -29482,8 +29777,8 @@ function runValidateChanged(options = {}) {
29482
29777
  };
29483
29778
  let skipped = 0;
29484
29779
  for (const relPath2 of relPaths) {
29485
- const abs = join15(root2, relPath2);
29486
- if (!existsSync15(abs))
29780
+ const abs = join17(root2, relPath2);
29781
+ if (!existsSync17(abs))
29487
29782
  continue;
29488
29783
  const bucket = bucketFor(relPath2, root2);
29489
29784
  if (bucket === "skip") {
@@ -29547,6 +29842,154 @@ function runValidateChanged(options = {}) {
29547
29842
  return exitCode;
29548
29843
  }
29549
29844
 
29845
+ // src/references/sync.ts
29846
+ import {
29847
+ existsSync as existsSync18,
29848
+ mkdirSync as mkdirSync3,
29849
+ readFileSync as readFileSync15,
29850
+ readdirSync as readdirSync6,
29851
+ unlinkSync,
29852
+ writeFileSync as writeFileSync3
29853
+ } from "node:fs";
29854
+ import { dirname as dirname8, join as join18, relative as relative11 } from "node:path";
29855
+ function walkMarkdownFiles2(dir, root2) {
29856
+ const files = [];
29857
+ if (!existsSync18(dir))
29858
+ return files;
29859
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
29860
+ if (entry.name.startsWith("."))
29861
+ continue;
29862
+ const fullPath = join18(dir, entry.name);
29863
+ if (entry.isDirectory()) {
29864
+ files.push(...walkMarkdownFiles2(fullPath, root2));
29865
+ continue;
29866
+ }
29867
+ if (entry.name.endsWith(".md")) {
29868
+ files.push(normalizeRelPath(relative11(root2, fullPath)));
29869
+ }
29870
+ }
29871
+ return files;
29872
+ }
29873
+ function listGeneratedReferenceFiles(skillDir, skill) {
29874
+ const refsDir = join18(skillDir, "references");
29875
+ if (!existsSync18(refsDir))
29876
+ return [];
29877
+ const files = [];
29878
+ const walk = (dir) => {
29879
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
29880
+ const fullPath = join18(dir, entry.name);
29881
+ if (entry.isDirectory()) {
29882
+ walk(fullPath);
29883
+ continue;
29884
+ }
29885
+ if (!entry.name.endsWith(".md"))
29886
+ continue;
29887
+ const content3 = readFileSync15(fullPath, "utf8");
29888
+ if (isGeneratedReference(content3)) {
29889
+ const refPath = normalizeRelPath(relative11(refsDir, fullPath));
29890
+ files.push(generatedRefPath(skill, refPath));
29891
+ }
29892
+ }
29893
+ };
29894
+ walk(refsDir);
29895
+ return files;
29896
+ }
29897
+ function syncReferences(options = {}) {
29898
+ const root2 = options.root ?? process.cwd();
29899
+ const canonicalDir = join18(root2, CANONICAL_REFS_DIR);
29900
+ if (!existsSync18(canonicalDir)) {
29901
+ throw new Error(`canonical references dir not found: ${CANONICAL_REFS_DIR}`);
29902
+ }
29903
+ const result = {
29904
+ written: [],
29905
+ rewritten: [],
29906
+ removed: [],
29907
+ skipped: []
29908
+ };
29909
+ const plans = discoverSkillReferencePlans(root2);
29910
+ for (const plan of plans) {
29911
+ const skillDir = join18(root2, plan.skill);
29912
+ for (const refPath of plan.refPaths) {
29913
+ const sourceRel = normalizeRelPath(join18(CANONICAL_REFS_DIR, refPath));
29914
+ const canonicalPath = join18(root2, sourceRel);
29915
+ if (!existsSync18(canonicalPath)) {
29916
+ throw new Error(`canonical reference missing: ${sourceRel}`);
29917
+ }
29918
+ const targetRel = generatedRefPath(plan.skill, refPath);
29919
+ const targetPath = join18(root2, targetRel);
29920
+ const canonicalContent = readFileSync15(canonicalPath, "utf8");
29921
+ const nextContent = formatGeneratedHeader(sourceRel) + canonicalContent;
29922
+ if (!options.dryRun) {
29923
+ mkdirSync3(dirname8(targetPath), { recursive: true });
29924
+ }
29925
+ const existing = existsSync18(targetPath) ? readFileSync15(targetPath, "utf8") : null;
29926
+ if (existing !== nextContent) {
29927
+ if (!options.dryRun)
29928
+ writeFileSync3(targetPath, nextContent, "utf8");
29929
+ result.written.push(targetRel);
29930
+ } else {
29931
+ result.skipped.push(targetRel);
29932
+ }
29933
+ }
29934
+ if (options.rewriteLinks !== false) {
29935
+ for (const relFile of walkMarkdownFiles2(skillDir, root2)) {
29936
+ const filePath = join18(root2, relFile);
29937
+ const content3 = readFileSync15(filePath, "utf8");
29938
+ const next = rewriteSharedRefLinks(content3, relFile, plan.skill);
29939
+ if (next !== content3) {
29940
+ if (!options.dryRun)
29941
+ writeFileSync3(filePath, next, "utf8");
29942
+ result.rewritten.push(relFile);
29943
+ }
29944
+ }
29945
+ }
29946
+ for (const generatedRel of listGeneratedReferenceFiles(skillDir, plan.skill)) {
29947
+ const refPath = generatedRel.slice(`${plan.skill}/references/`.length);
29948
+ if (!plan.refPaths.has(refPath)) {
29949
+ const fullPath = join18(root2, generatedRel);
29950
+ if (!options.dryRun)
29951
+ unlinkSync(fullPath);
29952
+ result.removed.push(generatedRel);
29953
+ }
29954
+ }
29955
+ }
29956
+ return result;
29957
+ }
29958
+
29959
+ // src/references/run.ts
29960
+ function runReferencesSync(options = {}) {
29961
+ return syncReferences(options);
29962
+ }
29963
+ function runReferencesCheck(options = {}) {
29964
+ const root2 = options.root ?? process.cwd();
29965
+ const issues = runGeneratedReferencesCheck(root2);
29966
+ return printReport(issues, {
29967
+ strict: options.strict,
29968
+ json: options.json,
29969
+ label: "References check"
29970
+ });
29971
+ }
29972
+ function printSyncResult(result) {
29973
+ if (result.written.length > 0) {
29974
+ console.log(`references sync: wrote ${result.written.length} file(s)`);
29975
+ for (const file of result.written)
29976
+ console.log(` + ${file}`);
29977
+ }
29978
+ if (result.rewritten.length > 0) {
29979
+ console.log(`references sync: rewrote links in ${result.rewritten.length} file(s)`);
29980
+ for (const file of result.rewritten)
29981
+ console.log(` ~ ${file}`);
29982
+ }
29983
+ if (result.removed.length > 0) {
29984
+ console.log(`references sync: removed ${result.removed.length} stale file(s)`);
29985
+ for (const file of result.removed)
29986
+ console.log(` - ${file}`);
29987
+ }
29988
+ if (result.written.length === 0 && result.rewritten.length === 0 && result.removed.length === 0) {
29989
+ console.log(`references sync: up to date (${result.skipped.length} file(s) checked)`);
29990
+ }
29991
+ }
29992
+
29550
29993
  // src/cli.ts
29551
29994
  function usage() {
29552
29995
  console.error(`Usage: skeleton <command>
@@ -29556,7 +29999,9 @@ Commands:
29556
29999
  audit docs|self|skills [--strict] [--json] [--paths=a,b] [--only=rule]
29557
30000
  validate changed [paths…] [--staged] [--base <ref>]
29558
30001
  register <path> [--topic=…] [--dry-run] [--json]
29559
- customize resolve <slug> [--json]`);
30002
+ customize resolve <slug> [--json]
30003
+ references sync [--dry-run] [--no-rewrite-links]
30004
+ references check [--json] [--strict]`);
29560
30005
  }
29561
30006
  function parseRegisterArgs(argv) {
29562
30007
  let path2 = null;
@@ -29640,6 +30085,24 @@ function main() {
29640
30085
  runInit(parsed);
29641
30086
  process.exit(0);
29642
30087
  }
30088
+ if (command === "references") {
30089
+ const sub = argv[1];
30090
+ if (sub === "sync") {
30091
+ const dryRun = argv.includes("--dry-run");
30092
+ const rewriteLinks = !argv.includes("--no-rewrite-links");
30093
+ const result = runReferencesSync({ dryRun, rewriteLinks });
30094
+ printSyncResult(result);
30095
+ process.exit(0);
30096
+ }
30097
+ if (sub === "check") {
30098
+ process.exit(runReferencesCheck({
30099
+ json: argv.includes("--json"),
30100
+ strict: argv.includes("--strict")
30101
+ }));
30102
+ }
30103
+ usage();
30104
+ process.exit(1);
30105
+ }
29643
30106
  usage();
29644
30107
  process.exit(1);
29645
30108
  } catch (error) {