@csark0812/skeleton 1.5.1 → 1.5.3
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 +51 -36
- package/dist/cli.js +605 -293
- package/dist/hooks/customize-on-skill-read.js +11 -14
- package/package.json +1 -1
- package/schemas/config.schema.json +22 -0
- package/templates/skeleton-init/config.yaml +6 -0
package/dist/cli.js
CHANGED
|
@@ -15651,6 +15651,9 @@ var require_extend = __commonJS((exports, module) => {
|
|
|
15651
15651
|
};
|
|
15652
15652
|
});
|
|
15653
15653
|
|
|
15654
|
+
// src/cli.ts
|
|
15655
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
15656
|
+
|
|
15654
15657
|
// src/audit/config/load.ts
|
|
15655
15658
|
var import_ajv = __toESM(require_ajv(), 1);
|
|
15656
15659
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -16984,12 +16987,99 @@ async function loadPlugins(root, config) {
|
|
|
16984
16987
|
}
|
|
16985
16988
|
|
|
16986
16989
|
// src/audit/core/collect.ts
|
|
16987
|
-
import { existsSync as
|
|
16988
|
-
import { join as
|
|
16990
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, realpathSync as realpathSync4, statSync as statSync2 } from "node:fs";
|
|
16991
|
+
import { join as join6, relative as relative4 } from "node:path";
|
|
16992
|
+
|
|
16993
|
+
// src/audit/core/skill-roots.ts
|
|
16994
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readlinkSync, realpathSync as realpathSync3 } from "node:fs";
|
|
16995
|
+
import { join as join5, relative as relative3 } from "node:path";
|
|
16996
|
+
|
|
16997
|
+
// src/audit/core/skill-provenance.ts
|
|
16998
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
16999
|
+
import { join as join4 } from "node:path";
|
|
17000
|
+
var DEFAULT_SKILLS_LOCKFILE = "skills-lock.json";
|
|
17001
|
+
function isForeignLockSourceType(sourceType) {
|
|
17002
|
+
return sourceType !== "local";
|
|
17003
|
+
}
|
|
17004
|
+
function loadSkillsLock(root, lockfileRel = DEFAULT_SKILLS_LOCKFILE) {
|
|
17005
|
+
const warnings = [];
|
|
17006
|
+
const abs = join4(root, lockfileRel);
|
|
17007
|
+
if (!existsSync5(abs)) {
|
|
17008
|
+
return { lockfile: null, entries: {}, warnings };
|
|
17009
|
+
}
|
|
17010
|
+
let raw;
|
|
17011
|
+
try {
|
|
17012
|
+
raw = JSON.parse(readFileSync4(abs, "utf8"));
|
|
17013
|
+
} catch (error) {
|
|
17014
|
+
warnings.push(`malformed ${lockfileRel}: ${error instanceof Error ? error.message : String(error)}`);
|
|
17015
|
+
return { lockfile: lockfileRel, entries: {}, warnings };
|
|
17016
|
+
}
|
|
17017
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
17018
|
+
warnings.push(`${lockfileRel}: expected a JSON object`);
|
|
17019
|
+
return { lockfile: lockfileRel, entries: {}, warnings };
|
|
17020
|
+
}
|
|
17021
|
+
const obj = raw;
|
|
17022
|
+
if (obj.version !== 1) {
|
|
17023
|
+
warnings.push(`${lockfileRel}: unsupported version ${String(obj.version)} (expected 1); ignoring entries`);
|
|
17024
|
+
return { lockfile: lockfileRel, entries: {}, warnings };
|
|
17025
|
+
}
|
|
17026
|
+
const skillsRaw = obj.skills;
|
|
17027
|
+
if (!skillsRaw || typeof skillsRaw !== "object" || Array.isArray(skillsRaw)) {
|
|
17028
|
+
warnings.push(`${lockfileRel}: missing or invalid "skills" object`);
|
|
17029
|
+
return { lockfile: lockfileRel, entries: {}, warnings };
|
|
17030
|
+
}
|
|
17031
|
+
const entries = {};
|
|
17032
|
+
for (const [slug, value] of Object.entries(skillsRaw)) {
|
|
17033
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
17034
|
+
warnings.push(`${lockfileRel}: skill "${slug}" has invalid entry`);
|
|
17035
|
+
continue;
|
|
17036
|
+
}
|
|
17037
|
+
const entry = value;
|
|
17038
|
+
const source = entry.source;
|
|
17039
|
+
const sourceType = entry.sourceType;
|
|
17040
|
+
if (typeof source !== "string" || source.length === 0) {
|
|
17041
|
+
warnings.push(`${lockfileRel}: skill "${slug}" missing source`);
|
|
17042
|
+
continue;
|
|
17043
|
+
}
|
|
17044
|
+
if (typeof sourceType !== "string" || sourceType.length === 0) {
|
|
17045
|
+
warnings.push(`${lockfileRel}: skill "${slug}" missing sourceType`);
|
|
17046
|
+
continue;
|
|
17047
|
+
}
|
|
17048
|
+
const parsed = { source, sourceType };
|
|
17049
|
+
if (typeof entry.skillPath === "string")
|
|
17050
|
+
parsed.skillPath = entry.skillPath;
|
|
17051
|
+
if (typeof entry.computedHash === "string")
|
|
17052
|
+
parsed.computedHash = entry.computedHash;
|
|
17053
|
+
entries[slug] = parsed;
|
|
17054
|
+
}
|
|
17055
|
+
return { lockfile: lockfileRel, entries, warnings };
|
|
17056
|
+
}
|
|
17057
|
+
function classifySkillOwnership(slug, provenance, ownership) {
|
|
17058
|
+
const ownedOverrides = new Set(ownership?.ownedSlugs ?? []);
|
|
17059
|
+
const foreignOverrides = new Set(ownership?.foreignSlugs ?? []);
|
|
17060
|
+
if (ownedOverrides.has(slug))
|
|
17061
|
+
return "owned";
|
|
17062
|
+
if (foreignOverrides.has(slug))
|
|
17063
|
+
return "foreign";
|
|
17064
|
+
const entry = provenance.entries[slug];
|
|
17065
|
+
if (entry && isForeignLockSourceType(entry.sourceType))
|
|
17066
|
+
return "foreign";
|
|
17067
|
+
return "owned";
|
|
17068
|
+
}
|
|
17069
|
+
function resolveOwnershipForSlugs(slugs, provenance, ownership) {
|
|
17070
|
+
const ownedSlugs = [];
|
|
17071
|
+
const foreignSlugs = [];
|
|
17072
|
+
for (const slug of slugs) {
|
|
17073
|
+
if (classifySkillOwnership(slug, provenance, ownership) === "foreign") {
|
|
17074
|
+
foreignSlugs.push(slug);
|
|
17075
|
+
} else {
|
|
17076
|
+
ownedSlugs.push(slug);
|
|
17077
|
+
}
|
|
17078
|
+
}
|
|
17079
|
+
return { ownedSlugs, foreignSlugs };
|
|
17080
|
+
}
|
|
16989
17081
|
|
|
16990
17082
|
// src/audit/core/skill-roots.ts
|
|
16991
|
-
import { existsSync as existsSync5, readdirSync as readdirSync2, readlinkSync, realpathSync as realpathSync3 } from "node:fs";
|
|
16992
|
-
import { join as join4, relative as relative3 } from "node:path";
|
|
16993
17083
|
var NESTED_SKILL_ROOTS = [".claude/skills", ".agents/skills"];
|
|
16994
17084
|
var FLAT_SKILL_DENYLIST = new Set([
|
|
16995
17085
|
".git",
|
|
@@ -17018,10 +17108,10 @@ function safeRealpath(path) {
|
|
|
17018
17108
|
}
|
|
17019
17109
|
}
|
|
17020
17110
|
function listNestedSlugs(root, relRoot) {
|
|
17021
|
-
const absRoot =
|
|
17022
|
-
if (!
|
|
17111
|
+
const absRoot = join5(root, relRoot);
|
|
17112
|
+
if (!existsSync6(absRoot))
|
|
17023
17113
|
return [];
|
|
17024
|
-
return readdirSync2(absRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NESTED_EXCLUDED_DIRS.has(entry.name)).filter((entry) =>
|
|
17114
|
+
return readdirSync2(absRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NESTED_EXCLUDED_DIRS.has(entry.name)).filter((entry) => existsSync6(join5(absRoot, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
|
|
17025
17115
|
}
|
|
17026
17116
|
function listFlatSlugs(root) {
|
|
17027
17117
|
const slugs = [];
|
|
@@ -17030,7 +17120,7 @@ function listFlatSlugs(root) {
|
|
|
17030
17120
|
continue;
|
|
17031
17121
|
if (FLAT_SKILL_DENYLIST.has(entry.name))
|
|
17032
17122
|
continue;
|
|
17033
|
-
if (
|
|
17123
|
+
if (existsSync6(join5(root, entry.name, "SKILL.md"))) {
|
|
17034
17124
|
slugs.push(entry.name);
|
|
17035
17125
|
}
|
|
17036
17126
|
}
|
|
@@ -17040,8 +17130,8 @@ function detectSkillRoots(root) {
|
|
|
17040
17130
|
const roots = [];
|
|
17041
17131
|
let claudeReal = null;
|
|
17042
17132
|
for (const relRoot of NESTED_SKILL_ROOTS) {
|
|
17043
|
-
const abs =
|
|
17044
|
-
if (!
|
|
17133
|
+
const abs = join5(root, relRoot);
|
|
17134
|
+
if (!existsSync6(abs))
|
|
17045
17135
|
continue;
|
|
17046
17136
|
if (relRoot === ".claude/skills") {
|
|
17047
17137
|
claudeReal = safeRealpath(abs);
|
|
@@ -17055,11 +17145,11 @@ function detectSkillRoots(root) {
|
|
|
17055
17145
|
continue;
|
|
17056
17146
|
try {
|
|
17057
17147
|
const link = readlinkSync(abs);
|
|
17058
|
-
if (link && claudeReal && safeRealpath(
|
|
17148
|
+
if (link && claudeReal && safeRealpath(join5(root, link)) === claudeReal)
|
|
17059
17149
|
continue;
|
|
17060
17150
|
} catch {}
|
|
17061
17151
|
}
|
|
17062
|
-
if (listNestedSlugs(root, relRoot).length > 0 ||
|
|
17152
|
+
if (listNestedSlugs(root, relRoot).length > 0 || existsSync6(abs)) {
|
|
17063
17153
|
roots.push({ kind: "nested", relPath: relRoot });
|
|
17064
17154
|
}
|
|
17065
17155
|
}
|
|
@@ -17069,12 +17159,13 @@ function detectSkillRoots(root) {
|
|
|
17069
17159
|
}
|
|
17070
17160
|
return roots;
|
|
17071
17161
|
}
|
|
17072
|
-
function buildSkillIndex(root) {
|
|
17162
|
+
function buildSkillIndex(root, ownership) {
|
|
17073
17163
|
const roots = detectSkillRoots(root);
|
|
17074
17164
|
const slugSet = new Set;
|
|
17075
17165
|
const slugs = [];
|
|
17166
|
+
const flatSlugs = listFlatSlugs(root);
|
|
17076
17167
|
for (const skillRoot of roots) {
|
|
17077
|
-
const rootSlugs = skillRoot.kind === "nested" ? listNestedSlugs(root, skillRoot.relPath) :
|
|
17168
|
+
const rootSlugs = skillRoot.kind === "nested" ? listNestedSlugs(root, skillRoot.relPath) : flatSlugs;
|
|
17078
17169
|
for (const slug of rootSlugs) {
|
|
17079
17170
|
if (!slugSet.has(slug)) {
|
|
17080
17171
|
slugSet.add(slug);
|
|
@@ -17082,40 +17173,94 @@ function buildSkillIndex(root) {
|
|
|
17082
17173
|
}
|
|
17083
17174
|
}
|
|
17084
17175
|
}
|
|
17085
|
-
|
|
17176
|
+
const lockfileRel = ownership?.lockfile ?? DEFAULT_SKILLS_LOCKFILE;
|
|
17177
|
+
const provenance = loadSkillsLock(root, lockfileRel);
|
|
17178
|
+
const { ownedSlugs, foreignSlugs } = resolveOwnershipForSlugs(slugs, provenance, ownership);
|
|
17179
|
+
return { roots, slugs, flatSlugs, ownedSlugs, foreignSlugs, provenance };
|
|
17180
|
+
}
|
|
17181
|
+
function isForeignSkillSlug(index, slug) {
|
|
17182
|
+
return index.foreignSlugs.includes(slug);
|
|
17086
17183
|
}
|
|
17087
17184
|
function resolveSkillPath(index, root, slug) {
|
|
17088
17185
|
for (const skillRoot of index.roots) {
|
|
17089
|
-
const candidate = skillRoot.kind === "nested" ?
|
|
17090
|
-
if (
|
|
17186
|
+
const candidate = skillRoot.kind === "nested" ? join5(root, skillRoot.relPath, slug, "SKILL.md") : join5(root, slug, "SKILL.md");
|
|
17187
|
+
if (existsSync6(candidate)) {
|
|
17091
17188
|
return normalizeRelPath(relative3(root, candidate));
|
|
17092
17189
|
}
|
|
17093
17190
|
}
|
|
17094
17191
|
return null;
|
|
17095
17192
|
}
|
|
17096
17193
|
function isSkillPath(relPath, index) {
|
|
17194
|
+
return skillSlugForPath(relPath, index) !== null;
|
|
17195
|
+
}
|
|
17196
|
+
function skillSlugForPath(relPath, index) {
|
|
17097
17197
|
const normalized = normalizeRelPath(relPath);
|
|
17098
|
-
|
|
17099
|
-
return true;
|
|
17198
|
+
const flat = new Set(index.flatSlugs);
|
|
17100
17199
|
for (const skillRoot of index.roots) {
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
|
|
17105
|
-
const
|
|
17106
|
-
if (
|
|
17107
|
-
return
|
|
17200
|
+
if (skillRoot.kind === "nested") {
|
|
17201
|
+
const prefix = `${skillRoot.relPath}/`;
|
|
17202
|
+
if (!normalized.startsWith(prefix))
|
|
17203
|
+
continue;
|
|
17204
|
+
const slug = normalized.slice(prefix.length).split("/")[0];
|
|
17205
|
+
if (slug && index.slugs.includes(slug))
|
|
17206
|
+
return slug;
|
|
17207
|
+
continue;
|
|
17108
17208
|
}
|
|
17209
|
+
const first = normalized.split("/")[0];
|
|
17210
|
+
if (first && flat.has(first))
|
|
17211
|
+
return first;
|
|
17109
17212
|
}
|
|
17110
|
-
return
|
|
17213
|
+
return null;
|
|
17214
|
+
}
|
|
17215
|
+
function isForeignSkillPath(relPath, index) {
|
|
17216
|
+
const slug = skillSlugForPath(relPath, index);
|
|
17217
|
+
if (!slug)
|
|
17218
|
+
return false;
|
|
17219
|
+
return isForeignSkillSlug(index, slug);
|
|
17220
|
+
}
|
|
17221
|
+
var NESTED_SKILL_SLUG_RE = /(?:^|\/)\.(?:claude|agents)\/skills\/([a-z0-9-]+)\//;
|
|
17222
|
+
var FLAT_SKILL_REFERENCES_RE = /(?:^|\/)([a-z0-9-]+)\/references(?:\/|$)/;
|
|
17223
|
+
function slugFromSkillPath(relPath) {
|
|
17224
|
+
const normalized = normalizeRelPath(relPath);
|
|
17225
|
+
const nested = normalized.match(NESTED_SKILL_SLUG_RE);
|
|
17226
|
+
if (nested?.[1])
|
|
17227
|
+
return nested[1];
|
|
17228
|
+
if (normalized.endsWith("/SKILL.md")) {
|
|
17229
|
+
const parts = normalized.split("/");
|
|
17230
|
+
return parts.at(-2) ?? null;
|
|
17231
|
+
}
|
|
17232
|
+
return null;
|
|
17233
|
+
}
|
|
17234
|
+
function slugFromPath(filePath, workspaceRoot) {
|
|
17235
|
+
const normalized = normalizeRelPath(filePath);
|
|
17236
|
+
const nested = normalized.match(NESTED_SKILL_SLUG_RE);
|
|
17237
|
+
if (nested?.[1])
|
|
17238
|
+
return nested[1];
|
|
17239
|
+
if (normalized.endsWith("/SKILL.md")) {
|
|
17240
|
+
return slugFromSkillPath(normalized);
|
|
17241
|
+
}
|
|
17242
|
+
const flatRef = normalized.match(FLAT_SKILL_REFERENCES_RE);
|
|
17243
|
+
const flatSlug = flatRef?.[1];
|
|
17244
|
+
if (!flatSlug || FLAT_SKILL_DENYLIST.has(flatSlug))
|
|
17245
|
+
return null;
|
|
17246
|
+
if (workspaceRoot) {
|
|
17247
|
+
if (!existsSync6(join5(workspaceRoot, flatSlug, "SKILL.md")))
|
|
17248
|
+
return null;
|
|
17249
|
+
}
|
|
17250
|
+
return flatSlug;
|
|
17111
17251
|
}
|
|
17112
17252
|
function skillCollectAugments(index) {
|
|
17253
|
+
const owned = new Set(index.ownedSlugs);
|
|
17113
17254
|
const patterns = [];
|
|
17114
17255
|
for (const skillRoot of index.roots) {
|
|
17115
17256
|
if (skillRoot.kind === "nested") {
|
|
17116
|
-
|
|
17257
|
+
for (const slug of index.ownedSlugs) {
|
|
17258
|
+
patterns.push(`${skillRoot.relPath}/${slug}/**`);
|
|
17259
|
+
}
|
|
17117
17260
|
} else {
|
|
17118
|
-
for (const slug of index.
|
|
17261
|
+
for (const slug of index.flatSlugs) {
|
|
17262
|
+
if (!owned.has(slug))
|
|
17263
|
+
continue;
|
|
17119
17264
|
patterns.push(`${slug}/**`);
|
|
17120
17265
|
}
|
|
17121
17266
|
}
|
|
@@ -17123,12 +17268,15 @@ function skillCollectAugments(index) {
|
|
|
17123
17268
|
return patterns;
|
|
17124
17269
|
}
|
|
17125
17270
|
function listSkillMarkdownPaths(root, index) {
|
|
17271
|
+
const owned = new Set(index.ownedSlugs);
|
|
17126
17272
|
const paths = new Set;
|
|
17127
17273
|
for (const skillRoot of index.roots) {
|
|
17128
17274
|
const slugs = skillRoot.kind === "nested" ? listNestedSlugs(root, skillRoot.relPath) : listFlatSlugs(root);
|
|
17129
17275
|
for (const slug of slugs) {
|
|
17130
|
-
|
|
17131
|
-
|
|
17276
|
+
if (!owned.has(slug))
|
|
17277
|
+
continue;
|
|
17278
|
+
const absDir = skillRoot.kind === "nested" ? join5(root, skillRoot.relPath, slug) : join5(root, slug);
|
|
17279
|
+
if (!existsSync6(absDir))
|
|
17132
17280
|
continue;
|
|
17133
17281
|
for (const abs of globSync("**/*.{md,mdc}", {
|
|
17134
17282
|
cwd: absDir,
|
|
@@ -17145,6 +17293,9 @@ function listSkillMarkdownPaths(root, index) {
|
|
|
17145
17293
|
function listSkillSlugs(index) {
|
|
17146
17294
|
return index.slugs;
|
|
17147
17295
|
}
|
|
17296
|
+
function listOwnedSkillSlugs(index) {
|
|
17297
|
+
return index.ownedSlugs;
|
|
17298
|
+
}
|
|
17148
17299
|
|
|
17149
17300
|
// src/audit/core/collect.ts
|
|
17150
17301
|
var MARKDOWN_GLOBS = ["**/*.md", "**/*.mdc"];
|
|
@@ -17156,7 +17307,7 @@ function shouldExclude(relPath, exclude) {
|
|
|
17156
17307
|
return exclude.some((pattern) => matchesGlobScope(relPath, pattern));
|
|
17157
17308
|
}
|
|
17158
17309
|
function expandPatterns(root, patterns, exclude) {
|
|
17159
|
-
const
|
|
17310
|
+
const byReal = new Map;
|
|
17160
17311
|
for (const pattern of patterns) {
|
|
17161
17312
|
for (const abs of globSync(pattern, {
|
|
17162
17313
|
cwd: root,
|
|
@@ -17169,10 +17320,19 @@ function expandPatterns(root, patterns, exclude) {
|
|
|
17169
17320
|
const rel = normalizeRelPath(relative4(root, abs));
|
|
17170
17321
|
if (shouldExclude(rel, exclude))
|
|
17171
17322
|
continue;
|
|
17172
|
-
|
|
17323
|
+
let real;
|
|
17324
|
+
try {
|
|
17325
|
+
real = realpathSync4(abs);
|
|
17326
|
+
} catch {
|
|
17327
|
+
real = abs;
|
|
17328
|
+
}
|
|
17329
|
+
const existing = byReal.get(real);
|
|
17330
|
+
if (existing === undefined || abs === real && existing !== real) {
|
|
17331
|
+
byReal.set(real, abs);
|
|
17332
|
+
}
|
|
17173
17333
|
}
|
|
17174
17334
|
}
|
|
17175
|
-
return [...
|
|
17335
|
+
return [...byReal.values()];
|
|
17176
17336
|
}
|
|
17177
17337
|
function collectScanFiles(config, root, skillIndex) {
|
|
17178
17338
|
const exclude = mergedExcludes(config);
|
|
@@ -17180,7 +17340,13 @@ function collectScanFiles(config, root, skillIndex) {
|
|
|
17180
17340
|
if (skillIndex) {
|
|
17181
17341
|
includePatterns.push(...skillCollectAugments(skillIndex));
|
|
17182
17342
|
}
|
|
17183
|
-
|
|
17343
|
+
const files = expandPatterns(root, includePatterns, exclude);
|
|
17344
|
+
if (!skillIndex)
|
|
17345
|
+
return files;
|
|
17346
|
+
return files.filter((abs) => {
|
|
17347
|
+
const rel = normalizeRelPath(relative4(root, abs));
|
|
17348
|
+
return !isForeignSkillPath(rel, skillIndex);
|
|
17349
|
+
});
|
|
17184
17350
|
}
|
|
17185
17351
|
function collectBannedFiles(config, root) {
|
|
17186
17352
|
if (config.scan.banned.length === 0)
|
|
@@ -17226,19 +17392,19 @@ function collectDocMetaPaths(config, root, registryPaths, skillIndex) {
|
|
|
17226
17392
|
}
|
|
17227
17393
|
const extras = ["docs/README.md", ".skeleton/registry.md"];
|
|
17228
17394
|
for (const file of extras) {
|
|
17229
|
-
const abs =
|
|
17230
|
-
if (
|
|
17395
|
+
const abs = join6(root, file);
|
|
17396
|
+
if (existsSync7(abs))
|
|
17231
17397
|
paths.push(normalizeRelPath(file));
|
|
17232
17398
|
}
|
|
17233
17399
|
for (const rel of registryPaths) {
|
|
17234
17400
|
if (!rel.endsWith(".md") && !rel.endsWith(".mdc"))
|
|
17235
17401
|
continue;
|
|
17236
|
-
const abs =
|
|
17237
|
-
if (
|
|
17402
|
+
const abs = join6(root, rel);
|
|
17403
|
+
if (existsSync7(abs))
|
|
17238
17404
|
paths.push(normalizeRelPath(rel));
|
|
17239
17405
|
}
|
|
17240
17406
|
for (const abs of collectScanFiles(config, root, skillIndex)) {
|
|
17241
|
-
const content =
|
|
17407
|
+
const content = readFileSync5(abs, "utf8");
|
|
17242
17408
|
if (/<!--\s*doc-meta:/.test(content)) {
|
|
17243
17409
|
paths.push(normalizeRelPath(relative4(root, abs)));
|
|
17244
17410
|
}
|
|
@@ -17248,7 +17414,7 @@ function collectDocMetaPaths(config, root, registryPaths, skillIndex) {
|
|
|
17248
17414
|
function validateScanRoots(config, root) {
|
|
17249
17415
|
const missing = [];
|
|
17250
17416
|
for (const tree of extractScanRootsFromInclude(config.scan.include)) {
|
|
17251
|
-
if (!
|
|
17417
|
+
if (!existsSync7(join6(root, tree)))
|
|
17252
17418
|
missing.push(tree);
|
|
17253
17419
|
}
|
|
17254
17420
|
return missing;
|
|
@@ -17270,8 +17436,8 @@ function includeExplicitMarkdownPaths(files, paths, root) {
|
|
|
17270
17436
|
const out = new Set(files);
|
|
17271
17437
|
for (const raw of paths) {
|
|
17272
17438
|
const rel = normalizeRelPath(raw);
|
|
17273
|
-
const abs =
|
|
17274
|
-
if (!
|
|
17439
|
+
const abs = join6(root, rel);
|
|
17440
|
+
if (!existsSync7(abs))
|
|
17275
17441
|
continue;
|
|
17276
17442
|
if (isMarkdownFile(rel)) {
|
|
17277
17443
|
out.add(abs);
|
|
@@ -17295,23 +17461,23 @@ function includeExplicitMarkdownPaths(files, paths, root) {
|
|
|
17295
17461
|
return [...out];
|
|
17296
17462
|
}
|
|
17297
17463
|
function readFileContent(absPath) {
|
|
17298
|
-
return
|
|
17464
|
+
return readFileSync5(absPath, "utf8");
|
|
17299
17465
|
}
|
|
17300
17466
|
function relPath(absPath, root) {
|
|
17301
17467
|
return normalizeRelPath(relative4(root, absPath));
|
|
17302
17468
|
}
|
|
17303
17469
|
|
|
17304
17470
|
// src/audit/core/registry.ts
|
|
17305
|
-
import { existsSync as
|
|
17306
|
-
import { join as
|
|
17471
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
|
|
17472
|
+
import { join as join7, relative as relative5, resolve as resolve4 } from "node:path";
|
|
17307
17473
|
var REGISTRY_TABLE_ROW_RE = /^\|\s*[^|]+\|\s*\[[^\]]*\]\(([^)]+)\)\s*\|/;
|
|
17308
17474
|
var REGISTRY_TABLE_HEADER_RE = /\|\s*Topic\s*\|\s*Canonical file\s*\|/i;
|
|
17309
17475
|
function parseRegistry(root) {
|
|
17310
|
-
const abs =
|
|
17311
|
-
if (!
|
|
17476
|
+
const abs = join7(root, REGISTRY_REL_PATH);
|
|
17477
|
+
if (!existsSync8(abs)) {
|
|
17312
17478
|
return { paths: [], hasTableHeader: false };
|
|
17313
17479
|
}
|
|
17314
|
-
const content =
|
|
17480
|
+
const content = readFileSync6(abs, "utf8");
|
|
17315
17481
|
const hasTableHeader = REGISTRY_TABLE_HEADER_RE.test(content);
|
|
17316
17482
|
const paths = [];
|
|
17317
17483
|
for (const line of content.split(`
|
|
@@ -17322,7 +17488,7 @@ function parseRegistry(root) {
|
|
|
17322
17488
|
const linkTarget = match[1].trim();
|
|
17323
17489
|
if (linkTarget.startsWith("#"))
|
|
17324
17490
|
continue;
|
|
17325
|
-
const resolved = resolve4(
|
|
17491
|
+
const resolved = resolve4(join7(root, REGISTRY_DIR_REL), linkTarget);
|
|
17326
17492
|
paths.push(normalizeRelPath(relative5(root, resolved)));
|
|
17327
17493
|
}
|
|
17328
17494
|
return { paths: [...new Set(paths)], hasTableHeader };
|
|
@@ -17335,7 +17501,7 @@ function parseRegistryPaths(root) {
|
|
|
17335
17501
|
function createContext(options = {}) {
|
|
17336
17502
|
const root = options.root ?? findRepoRoot();
|
|
17337
17503
|
const config = loadConfig(root);
|
|
17338
|
-
const skillIndex = buildSkillIndex(root);
|
|
17504
|
+
const skillIndex = buildSkillIndex(root, config.skillOwnership);
|
|
17339
17505
|
let files = collectScanFiles(config, root, skillIndex);
|
|
17340
17506
|
if (options.includeExcludedSkillTrees) {
|
|
17341
17507
|
files = includeExplicitMarkdownPaths(files, listSkillMarkdownPaths(root, skillIndex), root);
|
|
@@ -17355,16 +17521,17 @@ function createContext(options = {}) {
|
|
|
17355
17521
|
registryHasTableHeader: registry.hasTableHeader,
|
|
17356
17522
|
retiredSkills: new Set(retiredSkills(config)),
|
|
17357
17523
|
skillIndex,
|
|
17524
|
+
lockedSkillSlugs: new Set(skillIndex.foreignSlugs),
|
|
17358
17525
|
policies: options.policies ?? []
|
|
17359
17526
|
};
|
|
17360
17527
|
}
|
|
17361
17528
|
|
|
17362
17529
|
// src/audit/core/fix.ts
|
|
17363
|
-
import { existsSync as
|
|
17530
|
+
import { existsSync as existsSync11, realpathSync as realpathSync5, writeFileSync } from "node:fs";
|
|
17364
17531
|
import { dirname as dirname6, resolve as resolve6, sep as sep3 } from "node:path";
|
|
17365
17532
|
|
|
17366
17533
|
// src/audit/fix/anchors.ts
|
|
17367
|
-
import { existsSync as
|
|
17534
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
17368
17535
|
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
17369
17536
|
|
|
17370
17537
|
// node_modules/github-slugger/regex.js
|
|
@@ -25545,7 +25712,7 @@ var handle = {
|
|
|
25545
25712
|
};
|
|
25546
25713
|
|
|
25547
25714
|
// node_modules/mdast-util-to-markdown/lib/join.js
|
|
25548
|
-
var
|
|
25715
|
+
var join8 = [joinDefaults];
|
|
25549
25716
|
function joinDefaults(left, right, parent, state) {
|
|
25550
25717
|
if (right.type === "code" && formatCodeAsIndented(right, state) && (left.type === "list" || left.type === right.type && formatCodeAsIndented(left, state))) {
|
|
25551
25718
|
return false;
|
|
@@ -25928,7 +26095,7 @@ function toMarkdown(tree, options) {
|
|
|
25928
26095
|
handle: undefined,
|
|
25929
26096
|
indentLines,
|
|
25930
26097
|
indexStack: [],
|
|
25931
|
-
join: [...
|
|
26098
|
+
join: [...join8],
|
|
25932
26099
|
options: {},
|
|
25933
26100
|
safe: safeBound,
|
|
25934
26101
|
stack: [],
|
|
@@ -28918,9 +29085,9 @@ function collectAnchorFixes(ctx) {
|
|
|
28918
29085
|
if (!anchor)
|
|
28919
29086
|
continue;
|
|
28920
29087
|
const resolved = resolveLink(filePath, target);
|
|
28921
|
-
if (!
|
|
29088
|
+
if (!existsSync9(resolved))
|
|
28922
29089
|
continue;
|
|
28923
|
-
const targetContent =
|
|
29090
|
+
const targetContent = readFileSync7(resolved, "utf8");
|
|
28924
29091
|
const slugs = extractHeadingSlugs(targetContent, resolved);
|
|
28925
29092
|
const anchorSlug = slugifyAnchor(anchor);
|
|
28926
29093
|
if (slugs.has(anchorSlug))
|
|
@@ -28969,8 +29136,8 @@ function collectAnchorFixes(ctx) {
|
|
|
28969
29136
|
}
|
|
28970
29137
|
|
|
28971
29138
|
// src/audit/fix/doc-meta.ts
|
|
28972
|
-
import { existsSync as
|
|
28973
|
-
import { join as
|
|
29139
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
|
|
29140
|
+
import { join as join9 } from "node:path";
|
|
28974
29141
|
|
|
28975
29142
|
// src/audit/core/git-meta.ts
|
|
28976
29143
|
import { spawnSync } from "node:child_process";
|
|
@@ -29001,10 +29168,10 @@ function bumpDocMetaLastReviewed(content3, gitDate) {
|
|
|
29001
29168
|
function collectDocMetaFixes(ctx) {
|
|
29002
29169
|
const edits = [];
|
|
29003
29170
|
for (const relPath2 of ctx.docMetaPaths) {
|
|
29004
|
-
const abs =
|
|
29005
|
-
if (!
|
|
29171
|
+
const abs = join9(ctx.root, relPath2);
|
|
29172
|
+
if (!existsSync10(abs))
|
|
29006
29173
|
continue;
|
|
29007
|
-
const content3 =
|
|
29174
|
+
const content3 = readFileSync8(abs, "utf8");
|
|
29008
29175
|
if (!DOC_META_RE.test(content3))
|
|
29009
29176
|
continue;
|
|
29010
29177
|
const reviewedStr = docMetaLastReviewed(content3);
|
|
@@ -29071,11 +29238,11 @@ function resolveWritePath(root2, relFile) {
|
|
|
29071
29238
|
if (!underRoot(rootResolved, abs)) {
|
|
29072
29239
|
throw new Error(`Refusing autofix outside repo root: ${relFile}`);
|
|
29073
29240
|
}
|
|
29074
|
-
const rootReal =
|
|
29241
|
+
const rootReal = existsSync11(rootResolved) ? realpathSync5(rootResolved) : rootResolved;
|
|
29075
29242
|
let cursor = abs;
|
|
29076
29243
|
while (true) {
|
|
29077
|
-
if (
|
|
29078
|
-
const real =
|
|
29244
|
+
if (existsSync11(cursor)) {
|
|
29245
|
+
const real = realpathSync5(cursor);
|
|
29079
29246
|
if (!underRoot(rootReal, real)) {
|
|
29080
29247
|
throw new Error(`Refusing autofix outside repo root: ${relFile}`);
|
|
29081
29248
|
}
|
|
@@ -29184,8 +29351,8 @@ function printReport(issues, options) {
|
|
|
29184
29351
|
}
|
|
29185
29352
|
|
|
29186
29353
|
// src/references/check.ts
|
|
29187
|
-
import { existsSync as
|
|
29188
|
-
import { join as
|
|
29354
|
+
import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync10 } from "node:fs";
|
|
29355
|
+
import { join as join11, relative as relative7 } from "node:path";
|
|
29189
29356
|
|
|
29190
29357
|
// src/references/constants.ts
|
|
29191
29358
|
var CANONICAL_REFS_DIR = ".skeleton/references";
|
|
@@ -29208,16 +29375,16 @@ function isGeneratedReference(content3) {
|
|
|
29208
29375
|
}
|
|
29209
29376
|
|
|
29210
29377
|
// src/references/discover.ts
|
|
29211
|
-
import { existsSync as
|
|
29212
|
-
import { join as
|
|
29378
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
|
|
29379
|
+
import { join as join10, relative as relative6 } from "node:path";
|
|
29213
29380
|
function walkMarkdownFiles(dir, root2) {
|
|
29214
29381
|
const files = [];
|
|
29215
|
-
if (!
|
|
29382
|
+
if (!existsSync12(dir))
|
|
29216
29383
|
return files;
|
|
29217
29384
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
29218
29385
|
if (entry.name.startsWith("."))
|
|
29219
29386
|
continue;
|
|
29220
|
-
const fullPath =
|
|
29387
|
+
const fullPath = join10(dir, entry.name);
|
|
29221
29388
|
if (entry.isDirectory()) {
|
|
29222
29389
|
files.push(...walkMarkdownFiles(fullPath, root2));
|
|
29223
29390
|
continue;
|
|
@@ -29229,7 +29396,7 @@ function walkMarkdownFiles(dir, root2) {
|
|
|
29229
29396
|
return files;
|
|
29230
29397
|
}
|
|
29231
29398
|
function canonicalExists(root2, refPath) {
|
|
29232
|
-
return
|
|
29399
|
+
return existsSync12(join10(root2, CANONICAL_REFS_DIR, refPath));
|
|
29233
29400
|
}
|
|
29234
29401
|
function findSharedRefLinks(content3, sourceFile) {
|
|
29235
29402
|
const links = [];
|
|
@@ -29260,7 +29427,7 @@ function findLocalCanonicalLinks(root2, content3, sourceFile) {
|
|
|
29260
29427
|
const raw = normalizeRelPath(match[1] ?? "");
|
|
29261
29428
|
if (!raw)
|
|
29262
29429
|
continue;
|
|
29263
|
-
const refPath = withinDir ? normalizeRelPath(
|
|
29430
|
+
const refPath = withinDir ? normalizeRelPath(join10(withinDir, raw)) : raw;
|
|
29264
29431
|
if (!canonicalExists(root2, refPath))
|
|
29265
29432
|
continue;
|
|
29266
29433
|
links.push({ refPath, sourceFile });
|
|
@@ -29268,17 +29435,17 @@ function findLocalCanonicalLinks(root2, content3, sourceFile) {
|
|
|
29268
29435
|
}
|
|
29269
29436
|
return links;
|
|
29270
29437
|
}
|
|
29271
|
-
function discoverSkillReferencePlans(root2) {
|
|
29272
|
-
const index2 = buildSkillIndex(root2);
|
|
29438
|
+
function discoverSkillReferencePlans(root2, ownership) {
|
|
29439
|
+
const index2 = buildSkillIndex(root2, ownership);
|
|
29273
29440
|
const plans = [];
|
|
29274
|
-
for (const slug2 of index2.
|
|
29275
|
-
const skillDir =
|
|
29276
|
-
if (!
|
|
29441
|
+
for (const slug2 of index2.ownedSlugs) {
|
|
29442
|
+
const skillDir = join10(root2, slug2);
|
|
29443
|
+
if (!existsSync12(join10(skillDir, "SKILL.md")))
|
|
29277
29444
|
continue;
|
|
29278
29445
|
const refPaths = new Set;
|
|
29279
29446
|
const links = [];
|
|
29280
29447
|
for (const relFile of walkMarkdownFiles(skillDir, root2)) {
|
|
29281
|
-
const content3 =
|
|
29448
|
+
const content3 = readFileSync9(join10(root2, relFile), "utf8");
|
|
29282
29449
|
if (isGeneratedReference(content3))
|
|
29283
29450
|
continue;
|
|
29284
29451
|
for (const link2 of findSharedRefLinks(content3, relFile)) {
|
|
@@ -29295,7 +29462,7 @@ function discoverSkillReferencePlans(root2) {
|
|
|
29295
29462
|
const refPath = queue.pop();
|
|
29296
29463
|
if (!refPath || !canonicalExists(root2, refPath))
|
|
29297
29464
|
continue;
|
|
29298
|
-
const canonicalContent =
|
|
29465
|
+
const canonicalContent = readFileSync9(join10(root2, CANONICAL_REFS_DIR, refPath), "utf8");
|
|
29299
29466
|
const syntheticSource = generatedRefPath(slug2, refPath);
|
|
29300
29467
|
for (const link2 of findLocalCanonicalLinks(root2, canonicalContent, syntheticSource)) {
|
|
29301
29468
|
if (refPaths.has(link2.refPath))
|
|
@@ -29312,7 +29479,7 @@ function discoverSkillReferencePlans(root2) {
|
|
|
29312
29479
|
return plans.sort((a, b) => a.skill.localeCompare(b.skill));
|
|
29313
29480
|
}
|
|
29314
29481
|
function generatedRefPath(skill, refPath) {
|
|
29315
|
-
return normalizeRelPath(
|
|
29482
|
+
return normalizeRelPath(join10(skill, "references", refPath));
|
|
29316
29483
|
}
|
|
29317
29484
|
function rewriteSharedRefTarget(sourceFile, skill, refPath) {
|
|
29318
29485
|
const sourceDir = sourceFile.slice(0, sourceFile.lastIndexOf("/"));
|
|
@@ -29341,19 +29508,19 @@ function rewriteSharedRefLinks(content3, sourceFile, skill) {
|
|
|
29341
29508
|
function listAllGeneratedFiles(root2) {
|
|
29342
29509
|
const files = [];
|
|
29343
29510
|
const walk = (dir) => {
|
|
29344
|
-
if (!
|
|
29511
|
+
if (!existsSync13(dir))
|
|
29345
29512
|
return;
|
|
29346
29513
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
29347
29514
|
if (entry.name.startsWith("."))
|
|
29348
29515
|
continue;
|
|
29349
|
-
const fullPath =
|
|
29516
|
+
const fullPath = join11(dir, entry.name);
|
|
29350
29517
|
if (entry.isDirectory()) {
|
|
29351
29518
|
walk(fullPath);
|
|
29352
29519
|
continue;
|
|
29353
29520
|
}
|
|
29354
29521
|
if (!entry.name.endsWith(".md"))
|
|
29355
29522
|
continue;
|
|
29356
|
-
const content3 =
|
|
29523
|
+
const content3 = readFileSync10(fullPath, "utf8");
|
|
29357
29524
|
if (isGeneratedReference(content3)) {
|
|
29358
29525
|
files.push(normalizeRelPath(relative7(root2, fullPath)));
|
|
29359
29526
|
}
|
|
@@ -29362,12 +29529,13 @@ function listAllGeneratedFiles(root2) {
|
|
|
29362
29529
|
walk(root2);
|
|
29363
29530
|
return files;
|
|
29364
29531
|
}
|
|
29365
|
-
function runGeneratedReferencesCheck(root2) {
|
|
29532
|
+
function runGeneratedReferencesCheck(root2, ownership) {
|
|
29366
29533
|
const issues = [];
|
|
29367
|
-
const canonicalDir =
|
|
29368
|
-
if (!
|
|
29534
|
+
const canonicalDir = join11(root2, CANONICAL_REFS_DIR);
|
|
29535
|
+
if (!existsSync13(canonicalDir))
|
|
29369
29536
|
return issues;
|
|
29370
|
-
const
|
|
29537
|
+
const skillIndex = buildSkillIndex(root2, ownership);
|
|
29538
|
+
const plans = discoverSkillReferencePlans(root2, ownership);
|
|
29371
29539
|
const needed = new Set;
|
|
29372
29540
|
for (const plan of plans) {
|
|
29373
29541
|
for (const refPath of plan.refPaths) {
|
|
@@ -29375,42 +29543,44 @@ function runGeneratedReferencesCheck(root2) {
|
|
|
29375
29543
|
}
|
|
29376
29544
|
}
|
|
29377
29545
|
for (const targetRel of needed) {
|
|
29378
|
-
const targetPath =
|
|
29379
|
-
if (!
|
|
29546
|
+
const targetPath = join11(root2, targetRel);
|
|
29547
|
+
if (!existsSync13(targetPath)) {
|
|
29380
29548
|
issues.push(issue("generated-references", targetRel, "missing generated copy — run skeleton references sync"));
|
|
29381
29549
|
continue;
|
|
29382
29550
|
}
|
|
29383
|
-
const generated =
|
|
29551
|
+
const generated = readFileSync10(targetPath, "utf8");
|
|
29384
29552
|
if (!isGeneratedReference(generated)) {
|
|
29385
29553
|
issues.push(issue("generated-references", targetRel, "expected generated-reference provenance header"));
|
|
29386
29554
|
continue;
|
|
29387
29555
|
}
|
|
29388
29556
|
const body = stripGeneratedHeader(generated);
|
|
29389
|
-
const sourceRel = normalizeRelPath(generated.match(/source: ([^\n]+)/)?.[1] ??
|
|
29390
|
-
const canonicalPath =
|
|
29391
|
-
if (!
|
|
29557
|
+
const sourceRel = normalizeRelPath(generated.match(/source: ([^\n]+)/)?.[1] ?? join11(CANONICAL_REFS_DIR, targetRel.split("/references/")[1] ?? ""));
|
|
29558
|
+
const canonicalPath = join11(root2, sourceRel);
|
|
29559
|
+
if (!existsSync13(canonicalPath)) {
|
|
29392
29560
|
issues.push(issue("generated-references", targetRel, `canonical source missing: ${sourceRel}`));
|
|
29393
29561
|
continue;
|
|
29394
29562
|
}
|
|
29395
|
-
const canonical =
|
|
29563
|
+
const canonical = readFileSync10(canonicalPath, "utf8");
|
|
29396
29564
|
if (body !== canonical) {
|
|
29397
29565
|
issues.push(issue("generated-references", targetRel, "stale generated copy — run skeleton references sync"));
|
|
29398
29566
|
}
|
|
29399
29567
|
}
|
|
29400
29568
|
for (const generatedRel of listAllGeneratedFiles(root2)) {
|
|
29569
|
+
if (isForeignSkillPath(generatedRel, skillIndex))
|
|
29570
|
+
continue;
|
|
29401
29571
|
if (!needed.has(generatedRel)) {
|
|
29402
29572
|
issues.push(issue("generated-references", generatedRel, "orphaned generated copy — run skeleton references sync"));
|
|
29403
29573
|
}
|
|
29404
29574
|
}
|
|
29405
29575
|
for (const plan of plans) {
|
|
29406
|
-
const skillDir =
|
|
29407
|
-
if (!
|
|
29576
|
+
const skillDir = join11(root2, plan.skill);
|
|
29577
|
+
if (!existsSync13(skillDir))
|
|
29408
29578
|
continue;
|
|
29409
29579
|
const walk = (dir) => {
|
|
29410
29580
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
29411
29581
|
if (entry.name.startsWith("."))
|
|
29412
29582
|
continue;
|
|
29413
|
-
const fullPath =
|
|
29583
|
+
const fullPath = join11(dir, entry.name);
|
|
29414
29584
|
if (entry.isDirectory()) {
|
|
29415
29585
|
walk(fullPath);
|
|
29416
29586
|
continue;
|
|
@@ -29418,7 +29588,7 @@ function runGeneratedReferencesCheck(root2) {
|
|
|
29418
29588
|
if (!entry.name.endsWith(".md"))
|
|
29419
29589
|
continue;
|
|
29420
29590
|
const relFile = normalizeRelPath(relative7(root2, fullPath));
|
|
29421
|
-
const content3 =
|
|
29591
|
+
const content3 = readFileSync10(fullPath, "utf8");
|
|
29422
29592
|
if (content3.match(SHARED_REF_LINK_RE)) {
|
|
29423
29593
|
issues.push(issue("generated-references", relFile, "still links to shared root references/ — run skeleton references sync"));
|
|
29424
29594
|
}
|
|
@@ -29429,7 +29599,7 @@ function runGeneratedReferencesCheck(root2) {
|
|
|
29429
29599
|
return issues;
|
|
29430
29600
|
}
|
|
29431
29601
|
function runGeneratedReferencesRule(ctx) {
|
|
29432
|
-
return runGeneratedReferencesCheck(ctx.root);
|
|
29602
|
+
return runGeneratedReferencesCheck(ctx.root, ctx.config.skillOwnership);
|
|
29433
29603
|
}
|
|
29434
29604
|
var generatedReferencesRule = {
|
|
29435
29605
|
id: "generated-references",
|
|
@@ -29449,16 +29619,16 @@ function runBannedRule(ctx) {
|
|
|
29449
29619
|
var bannedRule = { id: "banned", run: runBannedRule };
|
|
29450
29620
|
|
|
29451
29621
|
// src/audit/rules/doc-meta.ts
|
|
29452
|
-
import { existsSync as
|
|
29453
|
-
import { join as
|
|
29622
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
|
|
29623
|
+
import { join as join12 } from "node:path";
|
|
29454
29624
|
function runDocMetaRule(ctx) {
|
|
29455
29625
|
const issues = [];
|
|
29456
29626
|
const today = new Date;
|
|
29457
29627
|
for (const relPath2 of ctx.docMetaPaths) {
|
|
29458
|
-
const abs =
|
|
29459
|
-
if (!
|
|
29628
|
+
const abs = join12(ctx.root, relPath2);
|
|
29629
|
+
if (!existsSync14(abs))
|
|
29460
29630
|
continue;
|
|
29461
|
-
const content3 =
|
|
29631
|
+
const content3 = readFileSync11(abs, "utf8");
|
|
29462
29632
|
if (!DOC_META_RE.test(content3)) {
|
|
29463
29633
|
issues.push(issue("doc-meta", relPath2, "missing doc-meta comment (owner + last-reviewed)"));
|
|
29464
29634
|
continue;
|
|
@@ -29473,6 +29643,9 @@ function runDocMetaRule(ctx) {
|
|
|
29473
29643
|
if (ageDays > ctx.config.daysUntilStale) {
|
|
29474
29644
|
issues.push(issue("doc-meta", relPath2, `doc-meta last-reviewed ${reviewedStr} is stale (>${ctx.config.daysUntilStale} days)`, { severity: "warning" }));
|
|
29475
29645
|
}
|
|
29646
|
+
const slug2 = slugFromPath(relPath2, ctx.root);
|
|
29647
|
+
if (slug2 !== null && ctx.lockedSkillSlugs.has(slug2))
|
|
29648
|
+
continue;
|
|
29476
29649
|
const gitDate = lastGitCommitDate(relPath2, ctx.root);
|
|
29477
29650
|
if (!gitDate)
|
|
29478
29651
|
continue;
|
|
@@ -29488,7 +29661,7 @@ function runDocMetaRule(ctx) {
|
|
|
29488
29661
|
var docMetaRule = { id: "doc-meta", run: runDocMetaRule };
|
|
29489
29662
|
|
|
29490
29663
|
// src/audit/rules/links.ts
|
|
29491
|
-
import { existsSync as
|
|
29664
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
|
|
29492
29665
|
import { dirname as dirname7, resolve as resolve7 } from "node:path";
|
|
29493
29666
|
function resolveLink2(sourceFile, target) {
|
|
29494
29667
|
const withoutAnchor = target.split("#")[0]?.split("?")[0] ?? "";
|
|
@@ -29525,19 +29698,19 @@ function validateTarget(ctx, sourceFile, target, linkLabel) {
|
|
|
29525
29698
|
}
|
|
29526
29699
|
if ((target.includes(".claude/agents/") || target.includes(".cursor/agents/")) && target.endsWith(".md")) {
|
|
29527
29700
|
const agentPath = resolved.endsWith(".md") ? resolved : `${resolved}.md`;
|
|
29528
|
-
if (!
|
|
29701
|
+
if (!existsSync15(agentPath)) {
|
|
29529
29702
|
issues.push(issue("links", relSource, "missing agent file", { link: linkLabel }));
|
|
29530
29703
|
}
|
|
29531
29704
|
return issues;
|
|
29532
29705
|
}
|
|
29533
|
-
if (pathPart && !
|
|
29706
|
+
if (pathPart && !existsSync15(resolved)) {
|
|
29534
29707
|
issues.push(issue("links", relSource, `broken link → ${relTarget}`, {
|
|
29535
29708
|
link: linkLabel
|
|
29536
29709
|
}));
|
|
29537
29710
|
return issues;
|
|
29538
29711
|
}
|
|
29539
|
-
if (anchor &&
|
|
29540
|
-
const targetContent =
|
|
29712
|
+
if (anchor && existsSync15(resolved)) {
|
|
29713
|
+
const targetContent = readFileSync12(resolved, "utf8");
|
|
29541
29714
|
const slugs = extractHeadingSlugs(targetContent, resolved);
|
|
29542
29715
|
const anchorSlug = slugifyAnchor(anchor);
|
|
29543
29716
|
if (!slugs.has(anchorSlug)) {
|
|
@@ -29612,8 +29785,8 @@ function runProsePolicyRule(ctx) {
|
|
|
29612
29785
|
var prosePolicyRule = { id: "prose-policy", run: runProsePolicyRule };
|
|
29613
29786
|
|
|
29614
29787
|
// src/audit/rules/registry.ts
|
|
29615
|
-
import { existsSync as
|
|
29616
|
-
import { join as
|
|
29788
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "node:fs";
|
|
29789
|
+
import { join as join13 } from "node:path";
|
|
29617
29790
|
function runRegistryRule(ctx) {
|
|
29618
29791
|
const issues = [];
|
|
29619
29792
|
const registry = new Set(ctx.registryPaths);
|
|
@@ -29621,12 +29794,12 @@ function runRegistryRule(ctx) {
|
|
|
29621
29794
|
issues.push(issue("registry", REGISTRY_REL_PATH, "registry table header found but 0 rows parsed — check | Topic | Canonical file | format and link syntax"));
|
|
29622
29795
|
}
|
|
29623
29796
|
for (const rel of ctx.registryPaths) {
|
|
29624
|
-
const abs =
|
|
29625
|
-
if (!
|
|
29797
|
+
const abs = join13(ctx.root, rel);
|
|
29798
|
+
if (!existsSync16(abs)) {
|
|
29626
29799
|
issues.push(issue("registry", rel, "registry entry file missing"));
|
|
29627
29800
|
continue;
|
|
29628
29801
|
}
|
|
29629
|
-
const content3 =
|
|
29802
|
+
const content3 = readFileSync13(abs, "utf8");
|
|
29630
29803
|
if (!SOURCE_OF_TRUTH_BANNER_RE.test(content3)) {
|
|
29631
29804
|
issues.push(issue("registry", rel, "missing **Source of truth for** banner (required for registry entry)"));
|
|
29632
29805
|
}
|
|
@@ -29637,7 +29810,7 @@ function runRegistryRule(ctx) {
|
|
|
29637
29810
|
continue;
|
|
29638
29811
|
if (!rel.endsWith(".md") && !rel.endsWith(".mdc"))
|
|
29639
29812
|
continue;
|
|
29640
|
-
const content3 =
|
|
29813
|
+
const content3 = readFileSync13(filePath, "utf8");
|
|
29641
29814
|
if (!SOURCE_OF_TRUTH_BANNER_LINE_RE.test(content3))
|
|
29642
29815
|
continue;
|
|
29643
29816
|
if (!registry.has(rel)) {
|
|
@@ -29674,16 +29847,16 @@ function runScanRootsRule(ctx) {
|
|
|
29674
29847
|
var scanRootsRule = { id: "scan-roots", run: runScanRootsRule };
|
|
29675
29848
|
|
|
29676
29849
|
// src/audit/rules/skill-index.ts
|
|
29677
|
-
import { existsSync as
|
|
29678
|
-
import { join as
|
|
29850
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync14 } from "node:fs";
|
|
29851
|
+
import { join as join14, relative as relative8 } from "node:path";
|
|
29679
29852
|
function walkSkillMarkdown(dir) {
|
|
29680
29853
|
const files = [];
|
|
29681
|
-
if (!
|
|
29854
|
+
if (!existsSync17(dir))
|
|
29682
29855
|
return files;
|
|
29683
29856
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
29684
29857
|
if (entry.name.startsWith("."))
|
|
29685
29858
|
continue;
|
|
29686
|
-
const fullPath =
|
|
29859
|
+
const fullPath = join14(dir, entry.name);
|
|
29687
29860
|
if (entry.isDirectory()) {
|
|
29688
29861
|
files.push(...walkSkillMarkdown(fullPath));
|
|
29689
29862
|
continue;
|
|
@@ -29710,7 +29883,7 @@ function parseReadmeTaxonomySlugs(content3) {
|
|
|
29710
29883
|
function scanFileForSkillLinks(ctx, filePath, index2) {
|
|
29711
29884
|
const issues = [];
|
|
29712
29885
|
const rel = relative8(ctx.root, filePath).replace(/\\/g, "/");
|
|
29713
|
-
const content3 =
|
|
29886
|
+
const content3 = readFileSync14(filePath, "utf8");
|
|
29714
29887
|
if (isGeneratedReference(content3))
|
|
29715
29888
|
return issues;
|
|
29716
29889
|
for (const match of content3.matchAll(SKILL_LINK_RE)) {
|
|
@@ -29733,15 +29906,16 @@ function validateReadmeTaxonomy(ctx, index2, diskSlugs) {
|
|
|
29733
29906
|
for (const skillRoot of index2.roots) {
|
|
29734
29907
|
if (skillRoot.kind !== "nested")
|
|
29735
29908
|
continue;
|
|
29736
|
-
const readmePath =
|
|
29737
|
-
if (!
|
|
29909
|
+
const readmePath = join14(ctx.root, skillRoot.relPath, "README.md");
|
|
29910
|
+
if (!existsSync17(readmePath))
|
|
29738
29911
|
continue;
|
|
29739
|
-
const readme =
|
|
29912
|
+
const readme = readFileSync14(readmePath, "utf8");
|
|
29740
29913
|
if (!readme.includes("## Taxonomy"))
|
|
29741
29914
|
continue;
|
|
29742
29915
|
const taxonomySlugs = parseReadmeTaxonomySlugs(readme);
|
|
29743
|
-
const nestedSlugs = diskSlugs.filter((slug2) =>
|
|
29744
|
-
const
|
|
29916
|
+
const nestedSlugs = diskSlugs.filter((slug2) => existsSync17(join14(ctx.root, skillRoot.relPath, slug2, "SKILL.md")));
|
|
29917
|
+
const foreign = new Set(index2.foreignSlugs);
|
|
29918
|
+
const publicSlugs = nestedSlugs.filter((slug2) => !nonPublic.has(slug2) && !foreign.has(slug2));
|
|
29745
29919
|
const relReadme = `${skillRoot.relPath}/README.md`;
|
|
29746
29920
|
for (const slug2 of publicSlugs) {
|
|
29747
29921
|
if (!taxonomySlugs.includes(slug2)) {
|
|
@@ -29760,30 +29934,45 @@ function runSkillIndexRule(ctx) {
|
|
|
29760
29934
|
const issues = [];
|
|
29761
29935
|
const index2 = ctx.skillIndex;
|
|
29762
29936
|
const diskSlugs = listSkillSlugs(index2);
|
|
29937
|
+
for (const warning of index2.provenance.warnings) {
|
|
29938
|
+
issues.push(issue("skill-index", index2.provenance.lockfile ?? "skills-lock.json", `skill provenance: ${warning}`, {
|
|
29939
|
+
severity: "warning"
|
|
29940
|
+
}));
|
|
29941
|
+
}
|
|
29763
29942
|
issues.push(...validateReadmeTaxonomy(ctx, index2, diskSlugs));
|
|
29943
|
+
const owned = new Set(index2.ownedSlugs);
|
|
29764
29944
|
for (const skillRoot of index2.roots) {
|
|
29765
|
-
|
|
29766
|
-
|
|
29767
|
-
|
|
29768
|
-
|
|
29945
|
+
if (skillRoot.kind === "nested") {
|
|
29946
|
+
for (const slug2 of index2.ownedSlugs) {
|
|
29947
|
+
const skillDir = join14(ctx.root, skillRoot.relPath, slug2);
|
|
29948
|
+
if (!existsSync17(skillDir))
|
|
29949
|
+
continue;
|
|
29950
|
+
for (const skillMd of walkSkillMarkdown(skillDir)) {
|
|
29951
|
+
issues.push(...scanFileForSkillLinks(ctx, skillMd, index2));
|
|
29952
|
+
}
|
|
29769
29953
|
}
|
|
29954
|
+
continue;
|
|
29770
29955
|
}
|
|
29771
|
-
|
|
29772
|
-
|
|
29773
|
-
|
|
29774
|
-
|
|
29775
|
-
|
|
29776
|
-
|
|
29777
|
-
|
|
29778
|
-
|
|
29956
|
+
for (const slug2 of index2.flatSlugs) {
|
|
29957
|
+
if (!owned.has(slug2))
|
|
29958
|
+
continue;
|
|
29959
|
+
const skillDir = join14(ctx.root, slug2);
|
|
29960
|
+
if (!existsSync17(skillDir))
|
|
29961
|
+
continue;
|
|
29962
|
+
for (const skillMd of walkSkillMarkdown(skillDir)) {
|
|
29963
|
+
issues.push(...scanFileForSkillLinks(ctx, skillMd, index2));
|
|
29779
29964
|
}
|
|
29780
29965
|
}
|
|
29781
29966
|
}
|
|
29782
29967
|
return issues;
|
|
29783
29968
|
}
|
|
29784
29969
|
var skillIndexRule = { id: "skill-index", run: runSkillIndexRule };
|
|
29785
|
-
function
|
|
29786
|
-
|
|
29970
|
+
function skillAuditSuffix(ctx) {
|
|
29971
|
+
const owned = listOwnedSkillSlugs(ctx.skillIndex).length;
|
|
29972
|
+
const foreign = ctx.skillIndex.foreignSlugs.length;
|
|
29973
|
+
if (foreign === 0)
|
|
29974
|
+
return ` (${owned} skills on disk)`;
|
|
29975
|
+
return ` (${owned} owned skills audited, ${foreign} foreign ignored)`;
|
|
29787
29976
|
}
|
|
29788
29977
|
|
|
29789
29978
|
// src/audit/rules/index.ts
|
|
@@ -29952,24 +30141,24 @@ async function runAudit(options) {
|
|
|
29952
30141
|
json: options.json,
|
|
29953
30142
|
label,
|
|
29954
30143
|
fileCount: options.suite === "docs" || options.suite === "self" ? ctx.files.length : undefined,
|
|
29955
|
-
successSuffix: options.suite === "skills" ?
|
|
30144
|
+
successSuffix: options.suite === "skills" ? skillAuditSuffix(ctx) : undefined
|
|
29956
30145
|
});
|
|
29957
30146
|
}
|
|
29958
30147
|
|
|
29959
30148
|
// src/customize/resolve.ts
|
|
29960
|
-
import { existsSync as
|
|
29961
|
-
import { basename as basename3, join as
|
|
30149
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
|
|
30150
|
+
import { basename as basename3, join as join15, relative as relative9 } from "node:path";
|
|
29962
30151
|
var CUSTOMIZE_PREFIX = "Customize: ";
|
|
29963
30152
|
function customizeDir(root2) {
|
|
29964
|
-
return
|
|
30153
|
+
return join15(root2, REGISTRY_DIR_REL, "customize");
|
|
29965
30154
|
}
|
|
29966
30155
|
function customizePathForSlug(root2, slug2) {
|
|
29967
|
-
return
|
|
30156
|
+
return join15(customizeDir(root2), `${slug2}.md`);
|
|
29968
30157
|
}
|
|
29969
30158
|
function findCustomizeViaRegistry(root2, slug2) {
|
|
29970
30159
|
for (const rel of parseRegistryPaths(root2)) {
|
|
29971
30160
|
const expected = `${REGISTRY_DIR_REL}/customize/${slug2}.md`;
|
|
29972
|
-
if (normalizeRelPath(rel) === expected &&
|
|
30161
|
+
if (normalizeRelPath(rel) === expected && existsSync18(join15(root2, rel))) {
|
|
29973
30162
|
return rel;
|
|
29974
30163
|
}
|
|
29975
30164
|
}
|
|
@@ -29977,17 +30166,17 @@ function findCustomizeViaRegistry(root2, slug2) {
|
|
|
29977
30166
|
}
|
|
29978
30167
|
function resolveSlugFile(root2, slug2) {
|
|
29979
30168
|
const direct = customizePathForSlug(root2, slug2);
|
|
29980
|
-
if (
|
|
30169
|
+
if (existsSync18(direct)) {
|
|
29981
30170
|
return {
|
|
29982
|
-
content:
|
|
30171
|
+
content: readFileSync15(direct, "utf8"),
|
|
29983
30172
|
path: normalizeRelPath(relative9(root2, direct))
|
|
29984
30173
|
};
|
|
29985
30174
|
}
|
|
29986
30175
|
const registryPath = findCustomizeViaRegistry(root2, slug2);
|
|
29987
30176
|
if (registryPath) {
|
|
29988
|
-
const abs =
|
|
30177
|
+
const abs = join15(root2, registryPath);
|
|
29989
30178
|
return {
|
|
29990
|
-
content:
|
|
30179
|
+
content: readFileSync15(abs, "utf8"),
|
|
29991
30180
|
path: registryPath
|
|
29992
30181
|
};
|
|
29993
30182
|
}
|
|
@@ -30009,10 +30198,10 @@ function readAlwaysInclude(root2, basenames, skipBasename) {
|
|
|
30009
30198
|
const file = basename3(name);
|
|
30010
30199
|
if (skipBasename && file === skipBasename)
|
|
30011
30200
|
continue;
|
|
30012
|
-
const abs =
|
|
30013
|
-
if (!
|
|
30201
|
+
const abs = join15(dir, file);
|
|
30202
|
+
if (!existsSync18(abs))
|
|
30014
30203
|
continue;
|
|
30015
|
-
parts.push(
|
|
30204
|
+
parts.push(readFileSync15(abs, "utf8").trimEnd());
|
|
30016
30205
|
paths.push(normalizeRelPath(relative9(root2, abs)));
|
|
30017
30206
|
}
|
|
30018
30207
|
return { parts, paths };
|
|
@@ -30051,61 +30240,151 @@ function resolveCustomizeFromRoot(slug2, startDir) {
|
|
|
30051
30240
|
return resolveCustomize(root2, slug2);
|
|
30052
30241
|
}
|
|
30053
30242
|
|
|
30243
|
+
// src/hooks/run.ts
|
|
30244
|
+
function parsePayload(raw) {
|
|
30245
|
+
if (!raw.trim())
|
|
30246
|
+
return {};
|
|
30247
|
+
return JSON.parse(raw);
|
|
30248
|
+
}
|
|
30249
|
+
function extractPath(payload) {
|
|
30250
|
+
const input = payload.tool_input ?? payload.toolInput ?? {};
|
|
30251
|
+
const candidates = [
|
|
30252
|
+
input.path,
|
|
30253
|
+
input.file_path,
|
|
30254
|
+
input.filePath,
|
|
30255
|
+
input.target_file,
|
|
30256
|
+
input.targetFile
|
|
30257
|
+
];
|
|
30258
|
+
for (const value of candidates) {
|
|
30259
|
+
if (typeof value === "string" && value.trim())
|
|
30260
|
+
return normalizeRelPath(value.trim());
|
|
30261
|
+
}
|
|
30262
|
+
return null;
|
|
30263
|
+
}
|
|
30264
|
+
function extractSkillSlug(payload) {
|
|
30265
|
+
const tool = payload.tool_name ?? payload.toolName ?? "";
|
|
30266
|
+
if (tool === "Skill" || tool === "skill") {
|
|
30267
|
+
const input = payload.tool_input ?? payload.toolInput ?? {};
|
|
30268
|
+
const slug2 = input.skill ?? input.slug ?? input.name;
|
|
30269
|
+
if (typeof slug2 === "string" && slug2.trim())
|
|
30270
|
+
return slug2.trim();
|
|
30271
|
+
}
|
|
30272
|
+
const path2 = extractPath(payload);
|
|
30273
|
+
if (path2)
|
|
30274
|
+
return slugFromPath(path2, process.cwd());
|
|
30275
|
+
return null;
|
|
30276
|
+
}
|
|
30277
|
+
function cursorResponse(content3) {
|
|
30278
|
+
return JSON.stringify({ additional_context: content3 });
|
|
30279
|
+
}
|
|
30280
|
+
function claudeResponse(content3) {
|
|
30281
|
+
return JSON.stringify({
|
|
30282
|
+
hookSpecificOutput: {
|
|
30283
|
+
additionalContext: content3
|
|
30284
|
+
}
|
|
30285
|
+
});
|
|
30286
|
+
}
|
|
30287
|
+
function codexResponse(content3) {
|
|
30288
|
+
return JSON.stringify({ additionalContext: content3 });
|
|
30289
|
+
}
|
|
30290
|
+
function formatResponse(payload, content3) {
|
|
30291
|
+
const tool = payload.tool_name ?? payload.toolName ?? "";
|
|
30292
|
+
if (tool === "read_file")
|
|
30293
|
+
return codexResponse(content3);
|
|
30294
|
+
if (payload.hook_event_name?.toLowerCase().includes("claude"))
|
|
30295
|
+
return claudeResponse(content3);
|
|
30296
|
+
if (tool === "Read" && payload.hook_event_name === "postToolUse")
|
|
30297
|
+
return cursorResponse(content3);
|
|
30298
|
+
if (tool === "Read" || tool === "Skill")
|
|
30299
|
+
return claudeResponse(content3);
|
|
30300
|
+
return cursorResponse(content3);
|
|
30301
|
+
}
|
|
30302
|
+
function runCustomizeHook(raw) {
|
|
30303
|
+
try {
|
|
30304
|
+
const payload = parsePayload(raw);
|
|
30305
|
+
const slug2 = extractSkillSlug(payload);
|
|
30306
|
+
if (!slug2)
|
|
30307
|
+
return "{}";
|
|
30308
|
+
const resolved = resolveCustomizeFromRoot(slug2);
|
|
30309
|
+
if (!resolved.content)
|
|
30310
|
+
return "{}";
|
|
30311
|
+
const from = resolved.included.length > 0 ? resolved.included.join(", ") : resolved.path ?? ".skeleton/customize";
|
|
30312
|
+
const prefix = `
|
|
30313
|
+
|
|
30314
|
+
---
|
|
30315
|
+
Customize override for /${slug2} (from ${from}):
|
|
30316
|
+
|
|
30317
|
+
`;
|
|
30318
|
+
return formatResponse(payload, prefix + resolved.content);
|
|
30319
|
+
} catch (error) {
|
|
30320
|
+
console.error(`customize hook error: ${error}`);
|
|
30321
|
+
return "{}";
|
|
30322
|
+
}
|
|
30323
|
+
}
|
|
30324
|
+
|
|
30054
30325
|
// src/init/init.ts
|
|
30055
30326
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
30056
|
-
import { copyFileSync, existsSync as
|
|
30057
|
-
import { join as
|
|
30327
|
+
import { copyFileSync, existsSync as existsSync22, mkdirSync as mkdirSync2, readFileSync as readFileSync17 } from "node:fs";
|
|
30328
|
+
import { join as join19 } from "node:path";
|
|
30058
30329
|
|
|
30059
30330
|
// src/init/merge-hooks.ts
|
|
30060
|
-
import { existsSync as
|
|
30061
|
-
import { dirname as dirname10, join as
|
|
30331
|
+
import { existsSync as existsSync21, mkdirSync, readFileSync as readFileSync16, writeFileSync as writeFileSync2 } from "node:fs";
|
|
30332
|
+
import { dirname as dirname10, join as join18 } from "node:path";
|
|
30062
30333
|
|
|
30063
30334
|
// src/init/package-paths.ts
|
|
30064
|
-
import { existsSync as
|
|
30065
|
-
import { dirname as dirname8, join as
|
|
30335
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
30336
|
+
import { dirname as dirname8, join as join16 } from "node:path";
|
|
30066
30337
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
30067
30338
|
var MODULE_DIR = dirname8(fileURLToPath5(import.meta.url));
|
|
30068
|
-
var PACKAGE_ROOT_CANDIDATES = [
|
|
30339
|
+
var PACKAGE_ROOT_CANDIDATES = [join16(MODULE_DIR, "../.."), join16(MODULE_DIR, "..")];
|
|
30069
30340
|
function resolvePackageRoot() {
|
|
30070
30341
|
for (const candidate of PACKAGE_ROOT_CANDIDATES) {
|
|
30071
|
-
if (
|
|
30342
|
+
if (existsSync19(join16(candidate, "package.json")))
|
|
30072
30343
|
return candidate;
|
|
30073
30344
|
}
|
|
30074
30345
|
throw new Error("Could not resolve @csark0812/skeleton package root");
|
|
30075
30346
|
}
|
|
30076
30347
|
function resolveTemplatesDir() {
|
|
30077
|
-
const dir =
|
|
30078
|
-
if (!
|
|
30348
|
+
const dir = join16(resolvePackageRoot(), "templates/skeleton-init");
|
|
30349
|
+
if (!existsSync19(dir)) {
|
|
30079
30350
|
throw new Error("Missing templates/skeleton-init in package");
|
|
30080
30351
|
}
|
|
30081
30352
|
return dir;
|
|
30082
30353
|
}
|
|
30083
30354
|
|
|
30084
30355
|
// src/init/resolve-hook-command.ts
|
|
30085
|
-
import { existsSync as
|
|
30356
|
+
import { existsSync as existsSync20, realpathSync as realpathSync6 } from "node:fs";
|
|
30086
30357
|
import { createRequire as createRequire3 } from "node:module";
|
|
30087
|
-
import { dirname as dirname9, join as
|
|
30358
|
+
import { dirname as dirname9, join as join17, relative as relative10, resolve as resolve8 } from "node:path";
|
|
30088
30359
|
var PACKAGE_NAME = "@csark0812/skeleton";
|
|
30089
|
-
var
|
|
30090
|
-
var HOOK_SRC = "src/hooks/customize-on-skill-read.ts";
|
|
30360
|
+
var CLI_DIST = "dist/cli.js";
|
|
30091
30361
|
var PACKAGE_ROOT = resolvePackageRoot();
|
|
30362
|
+
var DEV_HOOK_COMMAND = "bun src/cli.ts hook customize";
|
|
30363
|
+
var FALLBACK_HOOK_COMMAND = `node node_modules/${PACKAGE_NAME}/${CLI_DIST} hook customize`;
|
|
30364
|
+
function safeRealpath2(path2) {
|
|
30365
|
+
try {
|
|
30366
|
+
return realpathSync6(path2);
|
|
30367
|
+
} catch {
|
|
30368
|
+
return path2;
|
|
30369
|
+
}
|
|
30370
|
+
}
|
|
30092
30371
|
function toRepoRelative(cwd, absPath) {
|
|
30093
|
-
const rel = relative10(cwd, absPath).replace(/\\/g, "/");
|
|
30372
|
+
const rel = relative10(safeRealpath2(cwd), safeRealpath2(absPath)).replace(/\\/g, "/");
|
|
30094
30373
|
return rel.startsWith("..") ? absPath.replace(/\\/g, "/") : rel;
|
|
30095
30374
|
}
|
|
30096
|
-
function
|
|
30375
|
+
function tryResolvePublishedCli(cwd) {
|
|
30097
30376
|
try {
|
|
30098
|
-
const req = createRequire3(
|
|
30099
|
-
return req.resolve(`${PACKAGE_NAME}/${
|
|
30377
|
+
const req = createRequire3(join17(cwd, "package.json"));
|
|
30378
|
+
return req.resolve(`${PACKAGE_NAME}/${CLI_DIST}`);
|
|
30100
30379
|
} catch {
|
|
30101
30380
|
return null;
|
|
30102
30381
|
}
|
|
30103
30382
|
}
|
|
30104
|
-
function
|
|
30383
|
+
function walkNodeModulesCli(cwd) {
|
|
30105
30384
|
let dir = cwd;
|
|
30106
30385
|
while (true) {
|
|
30107
|
-
const candidate =
|
|
30108
|
-
if (
|
|
30386
|
+
const candidate = join17(dir, "node_modules", PACKAGE_NAME, CLI_DIST);
|
|
30387
|
+
if (existsSync20(candidate))
|
|
30109
30388
|
return candidate;
|
|
30110
30389
|
const parent = dirname9(dir);
|
|
30111
30390
|
if (parent === dir)
|
|
@@ -30118,28 +30397,25 @@ function isInsidePackageRoot(cwd) {
|
|
|
30118
30397
|
const rel = relative10(PACKAGE_ROOT, resolve8(cwd)).replace(/\\/g, "/");
|
|
30119
30398
|
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
30120
30399
|
}
|
|
30400
|
+
function nodeCliHookCommand(cliPath) {
|
|
30401
|
+
return `node ${cliPath} hook customize`;
|
|
30402
|
+
}
|
|
30121
30403
|
function resolveHookCommand(cwd) {
|
|
30122
|
-
|
|
30404
|
+
if (isInsidePackageRoot(cwd))
|
|
30405
|
+
return DEV_HOOK_COMMAND;
|
|
30406
|
+
const published = tryResolvePublishedCli(cwd);
|
|
30123
30407
|
if (published)
|
|
30124
|
-
return toRepoRelative(cwd, published);
|
|
30125
|
-
const hoisted =
|
|
30408
|
+
return nodeCliHookCommand(toRepoRelative(cwd, published));
|
|
30409
|
+
const hoisted = walkNodeModulesCli(cwd);
|
|
30126
30410
|
if (hoisted)
|
|
30127
|
-
return toRepoRelative(cwd, hoisted);
|
|
30128
|
-
|
|
30129
|
-
const distHook = join16(PACKAGE_ROOT, HOOK_DIST);
|
|
30130
|
-
if (existsSync19(distHook))
|
|
30131
|
-
return toRepoRelative(cwd, distHook);
|
|
30132
|
-
const srcHook = join16(PACKAGE_ROOT, HOOK_SRC);
|
|
30133
|
-
if (existsSync19(srcHook)) {
|
|
30134
|
-
const rel = toRepoRelative(cwd, srcHook);
|
|
30135
|
-
return rel.includes("/") ? `bun ${rel}` : `bun ./${rel}`;
|
|
30136
|
-
}
|
|
30137
|
-
}
|
|
30138
|
-
return `node node_modules/${PACKAGE_NAME}/${HOOK_DIST}`;
|
|
30411
|
+
return nodeCliHookCommand(toRepoRelative(cwd, hoisted));
|
|
30412
|
+
return FALLBACK_HOOK_COMMAND;
|
|
30139
30413
|
}
|
|
30140
30414
|
function isSkeletonHookCommand(command) {
|
|
30141
30415
|
if (!command)
|
|
30142
30416
|
return false;
|
|
30417
|
+
if (/\bhook\s+customize\b/.test(command))
|
|
30418
|
+
return true;
|
|
30143
30419
|
return /customize-on-skill-read\.(js|ts)\b/.test(command);
|
|
30144
30420
|
}
|
|
30145
30421
|
|
|
@@ -30149,14 +30425,14 @@ function identityKey(platform, event, matcher) {
|
|
|
30149
30425
|
return `skeleton:customize:${platform}:${event}:${matcher}`;
|
|
30150
30426
|
}
|
|
30151
30427
|
function loadFragment(name, hookCommand) {
|
|
30152
|
-
const raw =
|
|
30428
|
+
const raw = readFileSync16(join18(TEMPLATES_DIR, name), "utf8");
|
|
30153
30429
|
return JSON.parse(raw.replaceAll("{{HOOK_COMMAND}}", hookCommand));
|
|
30154
30430
|
}
|
|
30155
30431
|
function readJson(path2) {
|
|
30156
|
-
if (!
|
|
30432
|
+
if (!existsSync21(path2))
|
|
30157
30433
|
return null;
|
|
30158
30434
|
try {
|
|
30159
|
-
return JSON.parse(
|
|
30435
|
+
return JSON.parse(readFileSync16(path2, "utf8"));
|
|
30160
30436
|
} catch (error) {
|
|
30161
30437
|
throw new Error(`Invalid JSON in ${path2}: ${error}`);
|
|
30162
30438
|
}
|
|
@@ -30262,14 +30538,14 @@ function mergeNestedHooks(platform, targetPath, fragment, eventName, opts) {
|
|
|
30262
30538
|
}
|
|
30263
30539
|
function mergeHookConfigs(opts) {
|
|
30264
30540
|
const results = [];
|
|
30265
|
-
const cursorPath =
|
|
30541
|
+
const cursorPath = join18(opts.cwd, ".cursor/hooks.json");
|
|
30266
30542
|
const cursorFragment = loadFragment("cursor-hooks.fragment.json", opts.hookCommand);
|
|
30267
30543
|
results.push(mergeCursorHooks(cursorPath, cursorFragment, opts));
|
|
30268
|
-
const claudePath =
|
|
30544
|
+
const claudePath = join18(opts.cwd, ".claude/settings.json");
|
|
30269
30545
|
const claudeFragment = loadFragment("claude-settings.fragment.json", opts.hookCommand);
|
|
30270
30546
|
results.push(mergeNestedHooks("claude", claudePath, claudeFragment, "PostToolUse", opts));
|
|
30271
|
-
const codexPath =
|
|
30272
|
-
if (
|
|
30547
|
+
const codexPath = join18(opts.cwd, ".codex/hooks.json");
|
|
30548
|
+
if (existsSync21(join18(opts.cwd, ".codex"))) {
|
|
30273
30549
|
const codexFragment = loadFragment("codex-hooks.fragment.json", opts.hookCommand);
|
|
30274
30550
|
results.push(mergeNestedHooks("codex", codexPath, codexFragment, "PostToolUse", opts));
|
|
30275
30551
|
} else {
|
|
@@ -30278,11 +30554,11 @@ function mergeHookConfigs(opts) {
|
|
|
30278
30554
|
return results;
|
|
30279
30555
|
}
|
|
30280
30556
|
function mergePackageJsonScripts(cwd) {
|
|
30281
|
-
const pkgPath =
|
|
30282
|
-
if (!
|
|
30557
|
+
const pkgPath = join18(cwd, "package.json");
|
|
30558
|
+
if (!existsSync21(pkgPath))
|
|
30283
30559
|
return "skipped";
|
|
30284
|
-
const fragment = JSON.parse(
|
|
30285
|
-
const pkg = JSON.parse(
|
|
30560
|
+
const fragment = JSON.parse(readFileSync16(join18(TEMPLATES_DIR, "package.json.scripts.fragment.json"), "utf8"));
|
|
30561
|
+
const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
|
|
30286
30562
|
pkg.scripts ??= {};
|
|
30287
30563
|
let changed = false;
|
|
30288
30564
|
for (const [key, value] of Object.entries(fragment)) {
|
|
@@ -30338,27 +30614,27 @@ function skillsAddArgs(options = {}) {
|
|
|
30338
30614
|
// src/init/init.ts
|
|
30339
30615
|
var TEMPLATES_DIR2 = resolveTemplatesDir();
|
|
30340
30616
|
function writeScaffold(cwd) {
|
|
30341
|
-
const skeletonDir2 =
|
|
30617
|
+
const skeletonDir2 = join19(cwd, ".skeleton");
|
|
30342
30618
|
mkdirSync2(skeletonDir2, { recursive: true });
|
|
30343
30619
|
let created = false;
|
|
30344
|
-
const configPath =
|
|
30345
|
-
if (!
|
|
30346
|
-
copyFileSync(
|
|
30620
|
+
const configPath = join19(skeletonDir2, "config.yaml");
|
|
30621
|
+
if (!existsSync22(configPath)) {
|
|
30622
|
+
copyFileSync(join19(TEMPLATES_DIR2, "config.yaml"), configPath);
|
|
30347
30623
|
created = true;
|
|
30348
30624
|
}
|
|
30349
|
-
const registryPath =
|
|
30350
|
-
if (!
|
|
30351
|
-
copyFileSync(
|
|
30625
|
+
const registryPath = join19(skeletonDir2, "registry.md");
|
|
30626
|
+
if (!existsSync22(registryPath)) {
|
|
30627
|
+
copyFileSync(join19(TEMPLATES_DIR2, "registry.md"), registryPath);
|
|
30352
30628
|
created = true;
|
|
30353
30629
|
}
|
|
30354
|
-
mkdirSync2(
|
|
30630
|
+
mkdirSync2(join19(skeletonDir2, "customize"), { recursive: true });
|
|
30355
30631
|
return created ? "created" : "skipped";
|
|
30356
30632
|
}
|
|
30357
30633
|
function assertPackageResolvable(cwd) {
|
|
30358
|
-
const pkgPath =
|
|
30359
|
-
if (!
|
|
30634
|
+
const pkgPath = join19(cwd, "package.json");
|
|
30635
|
+
if (!existsSync22(pkgPath))
|
|
30360
30636
|
return;
|
|
30361
|
-
const pkg = JSON.parse(
|
|
30637
|
+
const pkg = JSON.parse(readFileSync17(pkgPath, "utf8"));
|
|
30362
30638
|
const hasDep = pkg.devDependencies?.["@csark0812/skeleton"] || pkg.dependencies?.["@csark0812/skeleton"];
|
|
30363
30639
|
if (!hasDep) {
|
|
30364
30640
|
try {
|
|
@@ -30442,7 +30718,7 @@ function parseInitArgs(argv) {
|
|
|
30442
30718
|
// src/plugins/build.ts
|
|
30443
30719
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
30444
30720
|
import { createHash } from "node:crypto";
|
|
30445
|
-
import { existsSync as
|
|
30721
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18, writeFileSync as writeFileSync3 } from "node:fs";
|
|
30446
30722
|
import { basename as basename4, dirname as dirname11, resolve as resolve9 } from "node:path";
|
|
30447
30723
|
function parseBuildPluginArgs(argv) {
|
|
30448
30724
|
let check = false;
|
|
@@ -30483,7 +30759,7 @@ function localImportPaths(tsAbs, content3) {
|
|
|
30483
30759
|
candidates.push(resolve9(dir, spec), resolve9(dir, `${spec}.ts`), resolve9(dir, `${spec}.js`), resolve9(dir, spec, "index.ts"));
|
|
30484
30760
|
}
|
|
30485
30761
|
for (const candidate of candidates) {
|
|
30486
|
-
if (
|
|
30762
|
+
if (existsSync23(candidate) && candidate.endsWith(".ts")) {
|
|
30487
30763
|
deps.push(candidate);
|
|
30488
30764
|
break;
|
|
30489
30765
|
}
|
|
@@ -30500,7 +30776,7 @@ function sourceFingerprint(tsAbs, seen = new Set) {
|
|
|
30500
30776
|
if (seen.has(abs))
|
|
30501
30777
|
return;
|
|
30502
30778
|
seen.add(abs);
|
|
30503
|
-
const content3 =
|
|
30779
|
+
const content3 = readFileSync18(abs, "utf8");
|
|
30504
30780
|
hash.update(basename4(abs));
|
|
30505
30781
|
hash.update("\x00");
|
|
30506
30782
|
hash.update(content3);
|
|
@@ -30518,7 +30794,7 @@ function writeStamp(tsAbs, mjsAbs) {
|
|
|
30518
30794
|
}
|
|
30519
30795
|
async function buildOne(tsAbs) {
|
|
30520
30796
|
const mjsAbs = mjsPathForTs(tsAbs);
|
|
30521
|
-
if (!
|
|
30797
|
+
if (!existsSync23(tsAbs)) {
|
|
30522
30798
|
throw new Error(`Plugin source not found: ${tsAbs}`);
|
|
30523
30799
|
}
|
|
30524
30800
|
const proc = spawnSync3("bun", ["build", tsAbs, "--target=node", "--format=esm", `--outfile=${mjsAbs}`, "--packages=external"], { encoding: "utf8" });
|
|
@@ -30538,17 +30814,17 @@ ${proc.stderr || proc.stdout || `exit ${proc.status}`}`);
|
|
|
30538
30814
|
}
|
|
30539
30815
|
function checkOne(tsAbs) {
|
|
30540
30816
|
const mjsAbs = mjsPathForTs(tsAbs);
|
|
30541
|
-
if (!
|
|
30817
|
+
if (!existsSync23(mjsAbs)) {
|
|
30542
30818
|
throw new Error(`Plugin not built: ${tsAbs} (missing ${mjsAbs}). Run: skeleton build-plugin`);
|
|
30543
30819
|
}
|
|
30544
|
-
if (!
|
|
30820
|
+
if (!existsSync23(tsAbs)) {
|
|
30545
30821
|
throw new Error(`Plugin source not found: ${tsAbs}`);
|
|
30546
30822
|
}
|
|
30547
30823
|
const stampAbs = stampPathForMjs(mjsAbs);
|
|
30548
|
-
if (!
|
|
30824
|
+
if (!existsSync23(stampAbs)) {
|
|
30549
30825
|
throw new Error(`Plugin stale: ${mjsAbs} has no fingerprint stamp. Run: skeleton build-plugin`);
|
|
30550
30826
|
}
|
|
30551
|
-
const expected =
|
|
30827
|
+
const expected = readFileSync18(stampAbs, "utf8").trim();
|
|
30552
30828
|
const actual = sourceFingerprint(tsAbs);
|
|
30553
30829
|
if (expected !== actual) {
|
|
30554
30830
|
throw new Error(`Plugin stale: ${mjsAbs} does not match ${tsAbs} (or local imports). Run: skeleton build-plugin`);
|
|
@@ -30578,22 +30854,31 @@ async function runBuildPlugin(options = {}) {
|
|
|
30578
30854
|
|
|
30579
30855
|
// src/references/sync.ts
|
|
30580
30856
|
import {
|
|
30581
|
-
existsSync as
|
|
30857
|
+
existsSync as existsSync24,
|
|
30582
30858
|
mkdirSync as mkdirSync3,
|
|
30583
30859
|
readdirSync as readdirSync6,
|
|
30584
|
-
readFileSync as
|
|
30860
|
+
readFileSync as readFileSync19,
|
|
30585
30861
|
unlinkSync,
|
|
30586
30862
|
writeFileSync as writeFileSync4
|
|
30587
30863
|
} from "node:fs";
|
|
30588
|
-
import { dirname as dirname12, join as
|
|
30864
|
+
import { dirname as dirname12, join as join20, relative as relative11 } from "node:path";
|
|
30865
|
+
function resolveOwnership(root2, override) {
|
|
30866
|
+
if (override !== undefined)
|
|
30867
|
+
return override;
|
|
30868
|
+
try {
|
|
30869
|
+
return loadConfig(root2).skillOwnership;
|
|
30870
|
+
} catch {
|
|
30871
|
+
return;
|
|
30872
|
+
}
|
|
30873
|
+
}
|
|
30589
30874
|
function walkMarkdownFiles2(dir, root2) {
|
|
30590
30875
|
const files = [];
|
|
30591
|
-
if (!
|
|
30876
|
+
if (!existsSync24(dir))
|
|
30592
30877
|
return files;
|
|
30593
30878
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
30594
30879
|
if (entry.name.startsWith("."))
|
|
30595
30880
|
continue;
|
|
30596
|
-
const fullPath =
|
|
30881
|
+
const fullPath = join20(dir, entry.name);
|
|
30597
30882
|
if (entry.isDirectory()) {
|
|
30598
30883
|
files.push(...walkMarkdownFiles2(fullPath, root2));
|
|
30599
30884
|
continue;
|
|
@@ -30605,20 +30890,20 @@ function walkMarkdownFiles2(dir, root2) {
|
|
|
30605
30890
|
return files;
|
|
30606
30891
|
}
|
|
30607
30892
|
function listGeneratedReferenceFiles(skillDir, skill) {
|
|
30608
|
-
const refsDir =
|
|
30609
|
-
if (!
|
|
30893
|
+
const refsDir = join20(skillDir, "references");
|
|
30894
|
+
if (!existsSync24(refsDir))
|
|
30610
30895
|
return [];
|
|
30611
30896
|
const files = [];
|
|
30612
30897
|
const walk = (dir) => {
|
|
30613
30898
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
30614
|
-
const fullPath =
|
|
30899
|
+
const fullPath = join20(dir, entry.name);
|
|
30615
30900
|
if (entry.isDirectory()) {
|
|
30616
30901
|
walk(fullPath);
|
|
30617
30902
|
continue;
|
|
30618
30903
|
}
|
|
30619
30904
|
if (!entry.name.endsWith(".md"))
|
|
30620
30905
|
continue;
|
|
30621
|
-
const content3 =
|
|
30906
|
+
const content3 = readFileSync19(fullPath, "utf8");
|
|
30622
30907
|
if (isGeneratedReference(content3)) {
|
|
30623
30908
|
const refPath = normalizeRelPath(relative11(refsDir, fullPath));
|
|
30624
30909
|
files.push(generatedRefPath(skill, refPath));
|
|
@@ -30630,8 +30915,8 @@ function listGeneratedReferenceFiles(skillDir, skill) {
|
|
|
30630
30915
|
}
|
|
30631
30916
|
function syncReferences(options = {}) {
|
|
30632
30917
|
const root2 = options.root ?? process.cwd();
|
|
30633
|
-
const canonicalDir =
|
|
30634
|
-
if (!
|
|
30918
|
+
const canonicalDir = join20(root2, CANONICAL_REFS_DIR);
|
|
30919
|
+
if (!existsSync24(canonicalDir)) {
|
|
30635
30920
|
throw new Error(`canonical references dir not found: ${CANONICAL_REFS_DIR}`);
|
|
30636
30921
|
}
|
|
30637
30922
|
const result = {
|
|
@@ -30640,23 +30925,23 @@ function syncReferences(options = {}) {
|
|
|
30640
30925
|
removed: [],
|
|
30641
30926
|
skipped: []
|
|
30642
30927
|
};
|
|
30643
|
-
const plans = discoverSkillReferencePlans(root2);
|
|
30928
|
+
const plans = discoverSkillReferencePlans(root2, resolveOwnership(root2, options.ownership));
|
|
30644
30929
|
for (const plan of plans) {
|
|
30645
|
-
const skillDir =
|
|
30930
|
+
const skillDir = join20(root2, plan.skill);
|
|
30646
30931
|
for (const refPath of plan.refPaths) {
|
|
30647
|
-
const sourceRel = normalizeRelPath(
|
|
30648
|
-
const canonicalPath =
|
|
30649
|
-
if (!
|
|
30932
|
+
const sourceRel = normalizeRelPath(join20(CANONICAL_REFS_DIR, refPath));
|
|
30933
|
+
const canonicalPath = join20(root2, sourceRel);
|
|
30934
|
+
if (!existsSync24(canonicalPath)) {
|
|
30650
30935
|
throw new Error(`canonical reference missing: ${sourceRel}`);
|
|
30651
30936
|
}
|
|
30652
30937
|
const targetRel = generatedRefPath(plan.skill, refPath);
|
|
30653
|
-
const targetPath =
|
|
30654
|
-
const canonicalContent =
|
|
30938
|
+
const targetPath = join20(root2, targetRel);
|
|
30939
|
+
const canonicalContent = readFileSync19(canonicalPath, "utf8");
|
|
30655
30940
|
const nextContent = formatGeneratedHeader(sourceRel) + canonicalContent;
|
|
30656
30941
|
if (!options.dryRun) {
|
|
30657
30942
|
mkdirSync3(dirname12(targetPath), { recursive: true });
|
|
30658
30943
|
}
|
|
30659
|
-
const existing =
|
|
30944
|
+
const existing = existsSync24(targetPath) ? readFileSync19(targetPath, "utf8") : null;
|
|
30660
30945
|
if (existing !== nextContent) {
|
|
30661
30946
|
if (!options.dryRun)
|
|
30662
30947
|
writeFileSync4(targetPath, nextContent, "utf8");
|
|
@@ -30667,8 +30952,8 @@ function syncReferences(options = {}) {
|
|
|
30667
30952
|
}
|
|
30668
30953
|
if (options.rewriteLinks !== false) {
|
|
30669
30954
|
for (const relFile of walkMarkdownFiles2(skillDir, root2)) {
|
|
30670
|
-
const filePath =
|
|
30671
|
-
const content3 =
|
|
30955
|
+
const filePath = join20(root2, relFile);
|
|
30956
|
+
const content3 = readFileSync19(filePath, "utf8");
|
|
30672
30957
|
const next = rewriteSharedRefLinks(content3, relFile, plan.skill);
|
|
30673
30958
|
if (next !== content3) {
|
|
30674
30959
|
if (!options.dryRun)
|
|
@@ -30680,7 +30965,7 @@ function syncReferences(options = {}) {
|
|
|
30680
30965
|
for (const generatedRel of listGeneratedReferenceFiles(skillDir, plan.skill)) {
|
|
30681
30966
|
const refPath = generatedRel.slice(`${plan.skill}/references/`.length);
|
|
30682
30967
|
if (!plan.refPaths.has(refPath)) {
|
|
30683
|
-
const fullPath =
|
|
30968
|
+
const fullPath = join20(root2, generatedRel);
|
|
30684
30969
|
if (!options.dryRun)
|
|
30685
30970
|
unlinkSync(fullPath);
|
|
30686
30971
|
result.removed.push(generatedRel);
|
|
@@ -30696,7 +30981,13 @@ function runReferencesSync(options = {}) {
|
|
|
30696
30981
|
}
|
|
30697
30982
|
function runReferencesCheck(options = {}) {
|
|
30698
30983
|
const root2 = options.root ?? process.cwd();
|
|
30699
|
-
|
|
30984
|
+
let ownership;
|
|
30985
|
+
try {
|
|
30986
|
+
ownership = loadConfig(root2).skillOwnership;
|
|
30987
|
+
} catch {
|
|
30988
|
+
ownership = undefined;
|
|
30989
|
+
}
|
|
30990
|
+
const issues = runGeneratedReferencesCheck(root2, ownership);
|
|
30700
30991
|
return printReport(issues, {
|
|
30701
30992
|
strict: options.strict,
|
|
30702
30993
|
json: options.json,
|
|
@@ -30725,8 +31016,8 @@ function printSyncResult(result) {
|
|
|
30725
31016
|
}
|
|
30726
31017
|
|
|
30727
31018
|
// src/register.ts
|
|
30728
|
-
import { existsSync as
|
|
30729
|
-
import { dirname as dirname13, join as
|
|
31019
|
+
import { existsSync as existsSync25, readFileSync as readFileSync20, writeFileSync as writeFileSync5 } from "node:fs";
|
|
31020
|
+
import { dirname as dirname13, join as join21, relative as relative12 } from "node:path";
|
|
30730
31021
|
var REGISTRY_TABLE_ROW_RE2 = /^\|\s*([^|]+)\|\s*\[[^\]]*\]\(([^)]+)\)\s*\|/;
|
|
30731
31022
|
var REGISTRY_TABLE_HEADER = "| Topic | Canonical file |";
|
|
30732
31023
|
function extractTopic(content3) {
|
|
@@ -30734,7 +31025,7 @@ function extractTopic(content3) {
|
|
|
30734
31025
|
return match?.[1]?.trim().replace(/\s+$/, "") ?? null;
|
|
30735
31026
|
}
|
|
30736
31027
|
function toRegistryLink(root2, absPath) {
|
|
30737
|
-
const fromRegistry =
|
|
31028
|
+
const fromRegistry = join21(root2, REGISTRY_DIR_REL);
|
|
30738
31029
|
return normalizeRelPath(relative12(fromRegistry, absPath));
|
|
30739
31030
|
}
|
|
30740
31031
|
function inferSection(registryLink) {
|
|
@@ -30759,11 +31050,11 @@ function parseRegistryRows(content3) {
|
|
|
30759
31050
|
return rows;
|
|
30760
31051
|
}
|
|
30761
31052
|
function pathFromRegistryLink(root2, link2) {
|
|
30762
|
-
return normalizeRelPath(relative12(root2,
|
|
31053
|
+
return normalizeRelPath(relative12(root2, join21(root2, REGISTRY_DIR_REL, link2)));
|
|
30763
31054
|
}
|
|
30764
31055
|
function isOutsideScan(root2, relPath2) {
|
|
30765
31056
|
const config = loadConfig(root2);
|
|
30766
|
-
const skillIndex = buildSkillIndex(root2);
|
|
31057
|
+
const skillIndex = buildSkillIndex(root2, config.skillOwnership);
|
|
30767
31058
|
const scanned = collectScanFiles(config, root2, skillIndex).map((abs) => normalizeRelPath(relative12(root2, abs)));
|
|
30768
31059
|
if (scanned.includes(relPath2))
|
|
30769
31060
|
return false;
|
|
@@ -30826,11 +31117,11 @@ ${newLine}
|
|
|
30826
31117
|
function registerPath(options) {
|
|
30827
31118
|
const root2 = options.root ?? findRepoRoot();
|
|
30828
31119
|
const relPath2 = normalizeRelPath(options.path);
|
|
30829
|
-
const absPath =
|
|
30830
|
-
if (!
|
|
31120
|
+
const absPath = join21(root2, relPath2);
|
|
31121
|
+
if (!existsSync25(absPath)) {
|
|
30831
31122
|
throw new Error(`File not found: ${relPath2}`);
|
|
30832
31123
|
}
|
|
30833
|
-
const content3 =
|
|
31124
|
+
const content3 = readFileSync20(absPath, "utf8");
|
|
30834
31125
|
let topic = options.topic ?? extractTopic(content3);
|
|
30835
31126
|
if (!topic) {
|
|
30836
31127
|
throw new Error(`No **Source of truth for** banner in ${relPath2} — add banner or pass --topic`);
|
|
@@ -30838,9 +31129,9 @@ function registerPath(options) {
|
|
|
30838
31129
|
const registryLink = toRegistryLink(root2, absPath);
|
|
30839
31130
|
topic = ensureCustomizeTopic(topic, registryLink);
|
|
30840
31131
|
const section = inferSection(registryLink);
|
|
30841
|
-
const registryAbs =
|
|
30842
|
-
let registryContent =
|
|
30843
|
-
if (!
|
|
31132
|
+
const registryAbs = join21(root2, REGISTRY_REL_PATH);
|
|
31133
|
+
let registryContent = existsSync25(registryAbs) ? readFileSync20(registryAbs, "utf8") : defaultRegistryContent();
|
|
31134
|
+
if (!existsSync25(registryAbs) && !existsSync25(join21(root2, ".skeleton/config.yaml"))) {
|
|
30844
31135
|
throw new Error("Missing .skeleton/config.yaml — run skeleton init first");
|
|
30845
31136
|
}
|
|
30846
31137
|
const { content: updated, action } = upsertRow(registryContent, topic, registryLink, section, root2);
|
|
@@ -30854,7 +31145,7 @@ function registerPath(options) {
|
|
|
30854
31145
|
};
|
|
30855
31146
|
if (!options.dryRun && action !== "noop") {
|
|
30856
31147
|
const dir = dirname13(registryAbs);
|
|
30857
|
-
if (!
|
|
31148
|
+
if (!existsSync25(dir)) {
|
|
30858
31149
|
throw new Error(`Missing ${REGISTRY_DIR_REL}/ directory`);
|
|
30859
31150
|
}
|
|
30860
31151
|
writeFileSync5(registryAbs, registryContent, "utf8");
|
|
@@ -30876,8 +31167,8 @@ function registerPath(options) {
|
|
|
30876
31167
|
|
|
30877
31168
|
// src/validate/changed.ts
|
|
30878
31169
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
30879
|
-
import { existsSync as
|
|
30880
|
-
import { basename as basename5, extname as extname2, join as
|
|
31170
|
+
import { existsSync as existsSync26, readFileSync as readFileSync21 } from "node:fs";
|
|
31171
|
+
import { basename as basename5, extname as extname2, join as join22 } from "node:path";
|
|
30881
31172
|
|
|
30882
31173
|
// src/validate/git-diff.ts
|
|
30883
31174
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -30916,7 +31207,7 @@ function isSkeletonYamlCandidate(normalized, ext) {
|
|
|
30916
31207
|
return false;
|
|
30917
31208
|
return true;
|
|
30918
31209
|
}
|
|
30919
|
-
function bucketFor(relPath2, root2, wiredPolicies) {
|
|
31210
|
+
function bucketFor(relPath2, root2, wiredPolicies, skillIndex) {
|
|
30920
31211
|
const normalized = normalizeRelPath(relPath2);
|
|
30921
31212
|
const ext = extname2(normalized).toLowerCase();
|
|
30922
31213
|
const name = basename5(normalized);
|
|
@@ -30929,9 +31220,11 @@ function bucketFor(relPath2, root2, wiredPolicies) {
|
|
|
30929
31220
|
return "policy";
|
|
30930
31221
|
return "skip";
|
|
30931
31222
|
}
|
|
30932
|
-
|
|
30933
|
-
|
|
31223
|
+
if (isSkillPath(normalized, skillIndex)) {
|
|
31224
|
+
if (isForeignSkillPath(normalized, skillIndex))
|
|
31225
|
+
return "foreign-skill";
|
|
30934
31226
|
return "skills";
|
|
31227
|
+
}
|
|
30935
31228
|
if (DOC_EXTENSIONS.has(ext)) {
|
|
30936
31229
|
const config = loadConfig(root2);
|
|
30937
31230
|
if (isInScanPerimeter(normalized, config, root2, skillIndex))
|
|
@@ -30960,9 +31253,9 @@ function parseJsonContent(content3) {
|
|
|
30960
31253
|
}
|
|
30961
31254
|
}
|
|
30962
31255
|
function validateJson(relPath2, root2) {
|
|
30963
|
-
const abs =
|
|
31256
|
+
const abs = join22(root2, relPath2);
|
|
30964
31257
|
try {
|
|
30965
|
-
parseJsonContent(
|
|
31258
|
+
parseJsonContent(readFileSync21(abs, "utf8"));
|
|
30966
31259
|
return 0;
|
|
30967
31260
|
} catch (error) {
|
|
30968
31261
|
console.error(`validate changed: invalid JSON in ${relPath2}: ${error}`);
|
|
@@ -30970,9 +31263,9 @@ function validateJson(relPath2, root2) {
|
|
|
30970
31263
|
}
|
|
30971
31264
|
}
|
|
30972
31265
|
function validatePolicy(relPath2, root2) {
|
|
30973
|
-
const abs =
|
|
31266
|
+
const abs = join22(root2, relPath2);
|
|
30974
31267
|
try {
|
|
30975
|
-
loadPolicyFile(abs,
|
|
31268
|
+
loadPolicyFile(abs, readFileSync21(abs, "utf8"));
|
|
30976
31269
|
return 0;
|
|
30977
31270
|
} catch (error) {
|
|
30978
31271
|
console.error(`validate changed: invalid policy ${relPath2}: ${error}`);
|
|
@@ -30980,7 +31273,7 @@ function validatePolicy(relPath2, root2) {
|
|
|
30980
31273
|
}
|
|
30981
31274
|
}
|
|
30982
31275
|
function validateShell(relPath2, root2) {
|
|
30983
|
-
const abs =
|
|
31276
|
+
const abs = join22(root2, relPath2);
|
|
30984
31277
|
const shellcheck = spawnSync5("shellcheck", [abs], { encoding: "utf8" });
|
|
30985
31278
|
if (shellcheck.status === 0)
|
|
30986
31279
|
return 0;
|
|
@@ -31002,23 +31295,23 @@ function resolvePaths(options) {
|
|
|
31002
31295
|
}
|
|
31003
31296
|
function codeValidationHint(root2) {
|
|
31004
31297
|
let pm2 = null;
|
|
31005
|
-
const pkgPath =
|
|
31006
|
-
if (
|
|
31298
|
+
const pkgPath = join22(root2, "package.json");
|
|
31299
|
+
if (existsSync26(pkgPath)) {
|
|
31007
31300
|
try {
|
|
31008
|
-
const pkg = JSON.parse(
|
|
31301
|
+
const pkg = JSON.parse(readFileSync21(pkgPath, "utf8"));
|
|
31009
31302
|
const raw = pkg.packageManager?.split("@")[0];
|
|
31010
31303
|
if (raw === "bun" || raw === "npm" || raw === "pnpm" || raw === "yarn")
|
|
31011
31304
|
pm2 = raw;
|
|
31012
31305
|
} catch {}
|
|
31013
31306
|
}
|
|
31014
31307
|
if (!pm2) {
|
|
31015
|
-
if (
|
|
31308
|
+
if (existsSync26(join22(root2, "bun.lock")) || existsSync26(join22(root2, "bun.lockb")))
|
|
31016
31309
|
pm2 = "bun";
|
|
31017
|
-
else if (
|
|
31310
|
+
else if (existsSync26(join22(root2, "pnpm-lock.yaml")))
|
|
31018
31311
|
pm2 = "pnpm";
|
|
31019
|
-
else if (
|
|
31312
|
+
else if (existsSync26(join22(root2, "yarn.lock")))
|
|
31020
31313
|
pm2 = "yarn";
|
|
31021
|
-
else if (
|
|
31314
|
+
else if (existsSync26(join22(root2, "package-lock.json")))
|
|
31022
31315
|
pm2 = "npm";
|
|
31023
31316
|
}
|
|
31024
31317
|
switch (pm2) {
|
|
@@ -31042,6 +31335,7 @@ async function runValidateChanged(options = {}) {
|
|
|
31042
31335
|
return 0;
|
|
31043
31336
|
}
|
|
31044
31337
|
const config = loadConfig(root2);
|
|
31338
|
+
const skillIndex = buildSkillIndex(root2, config.skillOwnership);
|
|
31045
31339
|
let wiredPolicies;
|
|
31046
31340
|
try {
|
|
31047
31341
|
wiredPolicies = await collectWiredPolicyRelPaths(root2, config);
|
|
@@ -31058,11 +31352,12 @@ async function runValidateChanged(options = {}) {
|
|
|
31058
31352
|
};
|
|
31059
31353
|
let missing = 0;
|
|
31060
31354
|
let skipped = 0;
|
|
31355
|
+
let foreignSkipped = 0;
|
|
31061
31356
|
const orphans = [];
|
|
31062
31357
|
for (const relPath2 of relPaths) {
|
|
31063
31358
|
const normalized = normalizeRelPath(relPath2);
|
|
31064
|
-
const abs =
|
|
31065
|
-
if (!
|
|
31359
|
+
const abs = join22(root2, normalized);
|
|
31360
|
+
if (!existsSync26(abs)) {
|
|
31066
31361
|
missing++;
|
|
31067
31362
|
console.error(`validate changed: path not found: ${relPath2}`);
|
|
31068
31363
|
continue;
|
|
@@ -31072,11 +31367,16 @@ async function runValidateChanged(options = {}) {
|
|
|
31072
31367
|
orphans.push(normalized);
|
|
31073
31368
|
continue;
|
|
31074
31369
|
}
|
|
31075
|
-
const bucket = bucketFor(normalized, root2, wiredPolicies);
|
|
31370
|
+
const bucket = bucketFor(normalized, root2, wiredPolicies, skillIndex);
|
|
31076
31371
|
if (bucket === "skip") {
|
|
31077
31372
|
skipped++;
|
|
31078
31373
|
continue;
|
|
31079
31374
|
}
|
|
31375
|
+
if (bucket === "foreign-skill") {
|
|
31376
|
+
foreignSkipped++;
|
|
31377
|
+
console.log(`validate changed: skipping foreign skill ${normalized} (owned upstream; see skills-lock.json / skillOwnership)`);
|
|
31378
|
+
continue;
|
|
31379
|
+
}
|
|
31080
31380
|
buckets[bucket].push(normalized);
|
|
31081
31381
|
}
|
|
31082
31382
|
if (orphans.length > 0) {
|
|
@@ -31124,24 +31424,24 @@ async function runValidateChanged(options = {}) {
|
|
|
31124
31424
|
exitCode = 1;
|
|
31125
31425
|
}
|
|
31126
31426
|
if (buckets.skills.length > 0) {
|
|
31127
|
-
|
|
31128
|
-
if (skillsOnly && !options.base) {
|
|
31427
|
+
if (!options.base) {
|
|
31129
31428
|
console.error(`validate changed: skill paths need the full skills suite (path-scoped skill rules are empty).
|
|
31130
31429
|
` + ` Run: skeleton audit skills
|
|
31131
31430
|
` + " (audit self covers docs + .skeleton; excluded skill trees still need audit skills)");
|
|
31132
|
-
return 1;
|
|
31133
|
-
}
|
|
31134
|
-
const skillExit = await runAudit({
|
|
31135
|
-
suite: "skills",
|
|
31136
|
-
strict: false,
|
|
31137
|
-
json: false,
|
|
31138
|
-
paths: buckets.skills,
|
|
31139
|
-
only: null,
|
|
31140
|
-
root: root2,
|
|
31141
|
-
pathScopedOnly: true
|
|
31142
|
-
});
|
|
31143
|
-
if (skillExit !== 0)
|
|
31144
31431
|
exitCode = 1;
|
|
31432
|
+
} else {
|
|
31433
|
+
const skillExit = await runAudit({
|
|
31434
|
+
suite: "skills",
|
|
31435
|
+
strict: false,
|
|
31436
|
+
json: false,
|
|
31437
|
+
paths: buckets.skills,
|
|
31438
|
+
only: null,
|
|
31439
|
+
root: root2,
|
|
31440
|
+
pathScopedOnly: true
|
|
31441
|
+
});
|
|
31442
|
+
if (skillExit !== 0)
|
|
31443
|
+
exitCode = 1;
|
|
31444
|
+
}
|
|
31145
31445
|
}
|
|
31146
31446
|
for (const relPath2 of buckets.shell) {
|
|
31147
31447
|
if (validateShell(relPath2, root2) !== 0)
|
|
@@ -31167,7 +31467,7 @@ async function runValidateChanged(options = {}) {
|
|
|
31167
31467
|
});
|
|
31168
31468
|
if (proseExit !== 0)
|
|
31169
31469
|
exitCode = 1;
|
|
31170
|
-
const skillPaths = listSkillMarkdownPaths(root2,
|
|
31470
|
+
const skillPaths = listSkillMarkdownPaths(root2, skillIndex);
|
|
31171
31471
|
if (skillPaths.length > 0) {
|
|
31172
31472
|
const skillProseExit = await runAudit({
|
|
31173
31473
|
suite: "skills",
|
|
@@ -31182,17 +31482,20 @@ async function runValidateChanged(options = {}) {
|
|
|
31182
31482
|
exitCode = 1;
|
|
31183
31483
|
}
|
|
31184
31484
|
} else {
|
|
31185
|
-
|
|
31186
|
-
console.error(`validate changed: policy YAML changes need a full prose-policy pass (path-scoped docs are not enough).
|
|
31485
|
+
console.error(`validate changed: policy YAML changes need a full prose-policy pass (path-scoped docs are not enough).
|
|
31187
31486
|
` + ` Run: skeleton audit docs
|
|
31188
31487
|
` + ` And: skeleton audit skills
|
|
31189
31488
|
` + " (audit self covers docs + .skeleton; excluded skill trees still need audit skills)");
|
|
31190
|
-
}
|
|
31191
31489
|
return 1;
|
|
31192
31490
|
}
|
|
31193
31491
|
}
|
|
31194
31492
|
if (exitCode === 0) {
|
|
31195
|
-
const
|
|
31493
|
+
const parts = [];
|
|
31494
|
+
if (skipped > 0)
|
|
31495
|
+
parts.push(`${skipped} path(s) skipped`);
|
|
31496
|
+
if (foreignSkipped > 0)
|
|
31497
|
+
parts.push(`${foreignSkipped} foreign skill(s) ignored`);
|
|
31498
|
+
const note = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
31196
31499
|
console.log(`validate changed passed${note}.`);
|
|
31197
31500
|
}
|
|
31198
31501
|
return exitCode;
|
|
@@ -31210,6 +31513,7 @@ Commands:
|
|
|
31210
31513
|
validate changed [paths…] [--staged] [--base <ref>]
|
|
31211
31514
|
register <path> [--topic=…] [--dry-run] [--json]
|
|
31212
31515
|
customize resolve <slug> [--json]
|
|
31516
|
+
hook customize (reads a host hook payload on stdin)
|
|
31213
31517
|
references sync [--dry-run] [--no-rewrite-links]
|
|
31214
31518
|
references check [--json] [--strict]`);
|
|
31215
31519
|
}
|
|
@@ -31306,6 +31610,14 @@ async function main() {
|
|
|
31306
31610
|
}
|
|
31307
31611
|
process.exit(0);
|
|
31308
31612
|
}
|
|
31613
|
+
if (command === "hook") {
|
|
31614
|
+
if (argv[1] !== "customize") {
|
|
31615
|
+
usage();
|
|
31616
|
+
process.exit(1);
|
|
31617
|
+
}
|
|
31618
|
+
process.stdout.write(runCustomizeHook(readFileSync22(0, "utf8")));
|
|
31619
|
+
process.exit(0);
|
|
31620
|
+
}
|
|
31309
31621
|
if (command === "init") {
|
|
31310
31622
|
const parsed = parseInitArgs(argv.slice(1));
|
|
31311
31623
|
runInit(parsed);
|