@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/README.md +134 -3
- package/dist/cli.js +544 -81
- package/dist/hooks/customize-on-skill-read.js +6385 -13
- package/package.json +1 -1
- package/schemas/config.schema.json +16 -0
- package/templates/skeleton-init/config.yaml +6 -0
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 = [
|
|
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
|
|
28785
|
-
import { join as
|
|
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
|
|
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 &&
|
|
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
|
|
29043
|
+
function resolveSlugFile(root2, slug2) {
|
|
28800
29044
|
const direct = customizePathForSlug(root2, slug2);
|
|
28801
|
-
if (
|
|
29045
|
+
if (existsSync11(direct)) {
|
|
28802
29046
|
return {
|
|
28803
|
-
|
|
28804
|
-
|
|
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 =
|
|
29053
|
+
const abs = join11(root2, registryPath);
|
|
28811
29054
|
return {
|
|
28812
|
-
|
|
28813
|
-
content: readFileSync8(abs, "utf8"),
|
|
29055
|
+
content: readFileSync10(abs, "utf8"),
|
|
28814
29056
|
path: registryPath
|
|
28815
29057
|
};
|
|
28816
29058
|
}
|
|
28817
|
-
return {
|
|
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
|
|
28827
|
-
import { join as
|
|
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
|
|
28831
|
-
import { dirname as dirname6, join as
|
|
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
|
|
29129
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
28835
29130
|
import { createRequire as createRequire3 } from "node:module";
|
|
28836
|
-
import { dirname as dirname5, join as
|
|
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
|
|
28840
|
-
import { dirname as dirname4, join as
|
|
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
|
-
|
|
28845
|
-
|
|
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 (
|
|
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 =
|
|
28856
|
-
if (!
|
|
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 =
|
|
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(
|
|
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 =
|
|
28883
|
-
if (
|
|
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 =
|
|
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 =
|
|
28905
|
-
if (
|
|
29199
|
+
const distHook = join13(PACKAGE_ROOT, HOOK_DIST);
|
|
29200
|
+
if (existsSync13(distHook))
|
|
28906
29201
|
return toRepoRelative(cwd, distHook);
|
|
28907
|
-
const srcHook =
|
|
28908
|
-
if (
|
|
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 =
|
|
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 (!
|
|
29226
|
+
if (!existsSync14(path2))
|
|
28932
29227
|
return null;
|
|
28933
29228
|
try {
|
|
28934
|
-
return JSON.parse(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
29047
|
-
if (
|
|
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 =
|
|
29057
|
-
if (!
|
|
29351
|
+
const pkgPath = join14(cwd, "package.json");
|
|
29352
|
+
if (!existsSync14(pkgPath))
|
|
29058
29353
|
return "skipped";
|
|
29059
|
-
const fragment = JSON.parse(
|
|
29060
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
29411
|
+
const skeletonDir = join15(cwd, ".skeleton");
|
|
29117
29412
|
mkdirSync2(skeletonDir, { recursive: true });
|
|
29118
29413
|
let created = false;
|
|
29119
|
-
const configPath =
|
|
29120
|
-
if (!
|
|
29121
|
-
copyFileSync(
|
|
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 =
|
|
29125
|
-
if (!
|
|
29126
|
-
copyFileSync(
|
|
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(
|
|
29424
|
+
mkdirSync2(join15(skeletonDir, "customize"), { recursive: true });
|
|
29130
29425
|
return created ? "created" : "skipped";
|
|
29131
29426
|
}
|
|
29132
29427
|
function assertPackageResolvable(cwd) {
|
|
29133
|
-
const pkgPath =
|
|
29134
|
-
if (!
|
|
29428
|
+
const pkgPath = join15(cwd, "package.json");
|
|
29429
|
+
if (!existsSync15(pkgPath))
|
|
29135
29430
|
return;
|
|
29136
|
-
const pkg = JSON.parse(
|
|
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
|
|
29223
|
-
import { dirname as dirname7, join as
|
|
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 =
|
|
29232
|
-
return normalizeRelPath(
|
|
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(
|
|
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(
|
|
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 =
|
|
29324
|
-
if (!
|
|
29618
|
+
const absPath = join16(root2, relPath2);
|
|
29619
|
+
if (!existsSync16(absPath)) {
|
|
29325
29620
|
throw new Error(`File not found: ${relPath2}`);
|
|
29326
29621
|
}
|
|
29327
|
-
const content3 =
|
|
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 =
|
|
29336
|
-
let registryContent =
|
|
29337
|
-
if (!
|
|
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 (!
|
|
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
|
|
29373
|
-
import { basename as
|
|
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 =
|
|
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 =
|
|
29735
|
+
const abs = join17(root2, relPath2);
|
|
29441
29736
|
try {
|
|
29442
|
-
parseJsonContent(
|
|
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 =
|
|
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 =
|
|
29486
|
-
if (!
|
|
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) {
|