@csark0812/skeleton 4.0.0 → 4.0.2
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 +6 -6
- package/dist/audit/config/types.d.ts +8 -0
- package/dist/audit/core/context.d.ts +5 -0
- package/dist/audit/core/repo-files.d.ts +5 -0
- package/dist/audit/core/report.d.ts +1 -0
- package/dist/audit/core/review-coverage.d.ts +18 -0
- package/dist/audit/rules/review-coverage.d.ts +8 -0
- package/dist/cli.js +574 -257
- package/dist/plugin-types.js +6 -0
- package/package.json +2 -2
- package/schemas/config.schema.json +17 -0
- package/templates/skeleton-init/pre-commit-config.yaml +9 -0
- package/templates/skeleton-init/skeleton.toml +7 -4
package/dist/cli.js
CHANGED
|
@@ -15968,8 +15968,8 @@ var require_extend = __commonJS((exports, module) => {
|
|
|
15968
15968
|
});
|
|
15969
15969
|
|
|
15970
15970
|
// src/cli.ts
|
|
15971
|
-
import { readFileSync as
|
|
15972
|
-
import
|
|
15971
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
15972
|
+
import process6 from "node:process";
|
|
15973
15973
|
|
|
15974
15974
|
// src/audit/config/load.ts
|
|
15975
15975
|
var import_ajv = __toESM(require_ajv(), 1);
|
|
@@ -17083,9 +17083,6 @@ function nonPublicSkills(config) {
|
|
|
17083
17083
|
return config.scan.nonPublicSkills ?? [];
|
|
17084
17084
|
}
|
|
17085
17085
|
|
|
17086
|
-
// src/audit/run.ts
|
|
17087
|
-
import process4 from "node:process";
|
|
17088
|
-
|
|
17089
17086
|
// src/catalog.ts
|
|
17090
17087
|
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "node:fs";
|
|
17091
17088
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
@@ -18571,19 +18568,11 @@ function writeCatalog(root) {
|
|
|
18571
18568
|
writeFileSync(abs, content, "utf8");
|
|
18572
18569
|
return { path: normalizeRelPath(CATALOG_REL_PATH), entries };
|
|
18573
18570
|
}
|
|
18574
|
-
function
|
|
18571
|
+
function refreshLocalCatalog(root) {
|
|
18575
18572
|
if (process3.env.CI === "true")
|
|
18576
|
-
return
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
return [
|
|
18580
|
-
`${CATALOG_REL_PATH} missing — run \`skeleton catalog\` so agents can skim SSOT summaries`
|
|
18581
|
-
];
|
|
18582
|
-
}
|
|
18583
|
-
if (result.stale) {
|
|
18584
|
-
return [`${CATALOG_REL_PATH} outdated — run \`skeleton catalog\` to refresh`];
|
|
18585
|
-
}
|
|
18586
|
-
return [];
|
|
18573
|
+
return "skipped-ci";
|
|
18574
|
+
writeCatalog(root);
|
|
18575
|
+
return "current";
|
|
18587
18576
|
}
|
|
18588
18577
|
function runCatalogCli(options = {}) {
|
|
18589
18578
|
const root = options.root ?? findRepoRoot();
|
|
@@ -18945,7 +18934,8 @@ function createContext(options = {}) {
|
|
|
18945
18934
|
registryHasTableHeader: false,
|
|
18946
18935
|
skillIndex,
|
|
18947
18936
|
lockedSkillSlugs: new Set(skillIndex.foreignSlugs),
|
|
18948
|
-
policies: options.policies ?? []
|
|
18937
|
+
policies: options.policies ?? [],
|
|
18938
|
+
fileSource: options.fileSource
|
|
18949
18939
|
};
|
|
18950
18940
|
}
|
|
18951
18941
|
|
|
@@ -30900,9 +30890,8 @@ function printWarnings(label, warnings) {
|
|
|
30900
30890
|
return;
|
|
30901
30891
|
console.log(`${label} warnings:
|
|
30902
30892
|
`);
|
|
30903
|
-
for (const
|
|
30904
|
-
|
|
30905
|
-
console.log(`- ${i.file}${linkPart}: ${i.message}`);
|
|
30893
|
+
for (const item of warnings) {
|
|
30894
|
+
console.log(`${item.file}: warning: ${item.message}`);
|
|
30906
30895
|
}
|
|
30907
30896
|
console.log("");
|
|
30908
30897
|
}
|
|
@@ -30912,12 +30901,65 @@ function printSuccess(label, options, warnings) {
|
|
|
30912
30901
|
console.log(`${label} passed${countNote}.`);
|
|
30913
30902
|
return 0;
|
|
30914
30903
|
}
|
|
30904
|
+
var REREAD_CODES = new Set([
|
|
30905
|
+
"review-dependency-changed",
|
|
30906
|
+
"impacted-document-review-required",
|
|
30907
|
+
"review-document-changed",
|
|
30908
|
+
"review-dependency-set-changed"
|
|
30909
|
+
]);
|
|
30910
|
+
function isRereadIssue(item) {
|
|
30911
|
+
return Boolean(item.code && REREAD_CODES.has(item.code));
|
|
30912
|
+
}
|
|
30913
|
+
function rereadTrigger(item) {
|
|
30914
|
+
if (item.code === "review-document-changed")
|
|
30915
|
+
return item.file;
|
|
30916
|
+
if (item.code === "review-dependency-changed" || item.code === "impacted-document-review-required") {
|
|
30917
|
+
return item.link ?? null;
|
|
30918
|
+
}
|
|
30919
|
+
return null;
|
|
30920
|
+
}
|
|
30921
|
+
function orderTriggers(file, triggers) {
|
|
30922
|
+
const rest = [...triggers].filter((path3) => path3 !== file).sort();
|
|
30923
|
+
return triggers.has(file) ? [file, ...rest] : rest;
|
|
30924
|
+
}
|
|
30925
|
+
function collectRereadTriggers(errors2) {
|
|
30926
|
+
const triggers = new Map;
|
|
30927
|
+
const rest = [];
|
|
30928
|
+
for (const item of errors2) {
|
|
30929
|
+
if (!isRereadIssue(item)) {
|
|
30930
|
+
rest.push(item);
|
|
30931
|
+
continue;
|
|
30932
|
+
}
|
|
30933
|
+
const current = triggers.get(item.file) ?? new Set;
|
|
30934
|
+
const trigger = rereadTrigger(item);
|
|
30935
|
+
if (trigger)
|
|
30936
|
+
current.add(trigger);
|
|
30937
|
+
triggers.set(item.file, current);
|
|
30938
|
+
}
|
|
30939
|
+
return { triggers, rest };
|
|
30940
|
+
}
|
|
30941
|
+
function printRereadDiagnostic(file, triggers) {
|
|
30942
|
+
console.log(`${file}: error: review required`);
|
|
30943
|
+
const changed = orderTriggers(file, triggers);
|
|
30944
|
+
if (changed.length > 0) {
|
|
30945
|
+
console.log(` changed: ${changed.join(", ")}`);
|
|
30946
|
+
return;
|
|
30947
|
+
}
|
|
30948
|
+
console.log(" changed: review dependency set");
|
|
30949
|
+
}
|
|
30950
|
+
function printRereadLists(errors2) {
|
|
30951
|
+
const { triggers, rest } = collectRereadTriggers(errors2);
|
|
30952
|
+
for (const file of [...triggers.keys()].sort()) {
|
|
30953
|
+
printRereadDiagnostic(file, triggers.get(file) ?? new Set);
|
|
30954
|
+
}
|
|
30955
|
+
return rest;
|
|
30956
|
+
}
|
|
30915
30957
|
function printErrors(label, errors2) {
|
|
30916
30958
|
console.log(`${label} failed:
|
|
30917
30959
|
`);
|
|
30918
|
-
|
|
30919
|
-
|
|
30920
|
-
console.log(
|
|
30960
|
+
const rest = printRereadLists(errors2);
|
|
30961
|
+
for (const item of rest) {
|
|
30962
|
+
console.log(`${item.file}: error: ${item.message}`);
|
|
30921
30963
|
}
|
|
30922
30964
|
return 1;
|
|
30923
30965
|
}
|
|
@@ -30931,11 +30973,43 @@ function printTextReport(ctx) {
|
|
|
30931
30973
|
|
|
30932
30974
|
// src/audit/core/review-proof.ts
|
|
30933
30975
|
import { createHash } from "node:crypto";
|
|
30934
|
-
import { existsSync as
|
|
30935
|
-
import { dirname as dirname8
|
|
30976
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync2, readFileSync as readFileSync12, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
30977
|
+
import { dirname as dirname8 } from "node:path";
|
|
30978
|
+
|
|
30979
|
+
// src/audit/core/repo-files.ts
|
|
30980
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
30981
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
|
|
30982
|
+
import { join as join11 } from "node:path";
|
|
30983
|
+
function gitShow(root2, spec) {
|
|
30984
|
+
const proc = spawnSync2("git", ["show", spec], {
|
|
30985
|
+
cwd: root2,
|
|
30986
|
+
encoding: "utf8",
|
|
30987
|
+
maxBuffer: 20000000
|
|
30988
|
+
});
|
|
30989
|
+
if (proc.status !== 0)
|
|
30990
|
+
return null;
|
|
30991
|
+
return proc.stdout;
|
|
30992
|
+
}
|
|
30993
|
+
function readRepoText(root2, relPath2, source = "worktree") {
|
|
30994
|
+
const normalized = normalizeRelPath(relPath2);
|
|
30995
|
+
if (source === "index")
|
|
30996
|
+
return gitShow(root2, `:${normalized}`);
|
|
30997
|
+
const abs = join11(root2, normalized);
|
|
30998
|
+
if (!existsSync12(abs))
|
|
30999
|
+
return null;
|
|
31000
|
+
return readFileSync11(abs, "utf8");
|
|
31001
|
+
}
|
|
31002
|
+
function pathDiffersFromHead(root2, relPath2) {
|
|
31003
|
+
const normalized = normalizeRelPath(relPath2);
|
|
31004
|
+
const head = gitShow(root2, `HEAD:${normalized}`);
|
|
31005
|
+
const worktree = readRepoText(root2, normalized, "worktree");
|
|
31006
|
+
if (worktree === null)
|
|
31007
|
+
return head !== null;
|
|
31008
|
+
return worktree !== (head ?? "");
|
|
31009
|
+
}
|
|
30936
31010
|
|
|
30937
31011
|
// src/audit/core/review-deps.ts
|
|
30938
|
-
import { existsSync as
|
|
31012
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
30939
31013
|
var REVIEW_DEPS_RE = /<!--\s*review-deps:\s*([^>]*?)-->/gi;
|
|
30940
31014
|
var GLOB_MAGIC_RE = /[*?{[]/;
|
|
30941
31015
|
function parseReviewDepsMarkers(content3) {
|
|
@@ -30967,7 +31041,7 @@ function resolveReviewDependencies(root2, patterns) {
|
|
|
30967
31041
|
throw new Error(`Invalid review dependency path: ${pattern}`);
|
|
30968
31042
|
}
|
|
30969
31043
|
if (!isReviewDependencyGlob(pattern)) {
|
|
30970
|
-
if (!
|
|
31044
|
+
if (!existsSync13(`${root2}/${pattern}`)) {
|
|
30971
31045
|
throw new Error(`Review dependency path is missing: ${pattern}`);
|
|
30972
31046
|
}
|
|
30973
31047
|
targets.add(pattern);
|
|
@@ -31059,19 +31133,19 @@ function parseLock(content3) {
|
|
|
31059
31133
|
return null;
|
|
31060
31134
|
}
|
|
31061
31135
|
}
|
|
31062
|
-
function loadLock(root2, relPath2) {
|
|
31063
|
-
const
|
|
31064
|
-
if (
|
|
31136
|
+
function loadLock(root2, relPath2, source = "worktree") {
|
|
31137
|
+
const content3 = readRepoText(root2, relPath2, source);
|
|
31138
|
+
if (content3 === null)
|
|
31065
31139
|
return null;
|
|
31066
|
-
return parseLock(
|
|
31140
|
+
return parseLock(content3);
|
|
31067
31141
|
}
|
|
31068
31142
|
function hashDependencies(root2, targets) {
|
|
31069
31143
|
const out = {};
|
|
31070
31144
|
for (const target of targets) {
|
|
31071
31145
|
const abs = resolveWritePath(root2, target);
|
|
31072
|
-
if (!
|
|
31146
|
+
if (!existsSync14(abs))
|
|
31073
31147
|
throw new Error(`Cannot attest missing review dependency: ${target}`);
|
|
31074
|
-
out[target] = hash(
|
|
31148
|
+
out[target] = hash(readFileSync12(abs, "utf8"));
|
|
31075
31149
|
}
|
|
31076
31150
|
return out;
|
|
31077
31151
|
}
|
|
@@ -31100,7 +31174,7 @@ function commitWrites(root2, writes) {
|
|
|
31100
31174
|
try {
|
|
31101
31175
|
for (const [relPath2, content3] of writes) {
|
|
31102
31176
|
const abs = resolveWritePath(root2, relPath2);
|
|
31103
|
-
originals.set(relPath2,
|
|
31177
|
+
originals.set(relPath2, existsSync14(abs) ? readFileSync12(abs, "utf8") : null);
|
|
31104
31178
|
mkdirSync2(dirname8(abs), { recursive: true });
|
|
31105
31179
|
writeFileSync3(abs, content3, "utf8");
|
|
31106
31180
|
written.push(relPath2);
|
|
@@ -31131,7 +31205,7 @@ function attestDocuments(options) {
|
|
|
31131
31205
|
if (proofPath) {
|
|
31132
31206
|
const absProof = resolveWritePath(ctx.root, proofPath);
|
|
31133
31207
|
lock = loadLock(ctx.root, proofPath);
|
|
31134
|
-
if (
|
|
31208
|
+
if (existsSync14(absProof) && !lock) {
|
|
31135
31209
|
throw new Error(`Review proof lockfile is malformed: ${proofPath}`);
|
|
31136
31210
|
}
|
|
31137
31211
|
lock ??= emptyLock();
|
|
@@ -31139,9 +31213,9 @@ function attestDocuments(options) {
|
|
|
31139
31213
|
const writes = new Map;
|
|
31140
31214
|
for (const relPath2 of selected) {
|
|
31141
31215
|
const abs = resolveWritePath(ctx.root, relPath2);
|
|
31142
|
-
if (!
|
|
31216
|
+
if (!existsSync14(abs))
|
|
31143
31217
|
throw new Error(`Cannot attest missing document: ${relPath2}`);
|
|
31144
|
-
const original =
|
|
31218
|
+
const original = readFileSync12(abs, "utf8");
|
|
31145
31219
|
if (!DOC_META_RE.test(original)) {
|
|
31146
31220
|
throw new Error(`Cannot attest ${relPath2}: missing doc-meta comment`);
|
|
31147
31221
|
}
|
|
@@ -31191,7 +31265,13 @@ function validateEntry(input) {
|
|
|
31191
31265
|
}));
|
|
31192
31266
|
return issues;
|
|
31193
31267
|
}
|
|
31194
|
-
issues.push(...dependencyHashIssues({
|
|
31268
|
+
issues.push(...dependencyHashIssues({
|
|
31269
|
+
root: ctx.root,
|
|
31270
|
+
relPath: relPath2,
|
|
31271
|
+
targets: currentTargets,
|
|
31272
|
+
entry,
|
|
31273
|
+
fileSource: ctx.fileSource
|
|
31274
|
+
}));
|
|
31195
31275
|
return issues;
|
|
31196
31276
|
}
|
|
31197
31277
|
function documentProofIssues(relPath2, content3, entry) {
|
|
@@ -31225,16 +31305,17 @@ function currentDependencies(input) {
|
|
|
31225
31305
|
}
|
|
31226
31306
|
function dependencyHashIssues(input) {
|
|
31227
31307
|
const { root: root2, relPath: relPath2, targets, entry } = input;
|
|
31308
|
+
const source = input.fileSource ?? "worktree";
|
|
31228
31309
|
const issues = [];
|
|
31229
31310
|
for (const target of targets) {
|
|
31230
|
-
const
|
|
31231
|
-
if (
|
|
31311
|
+
const content3 = readRepoText(root2, target, source);
|
|
31312
|
+
if (content3 === null) {
|
|
31232
31313
|
issues.push(issue("review-proof", relPath2, {
|
|
31233
31314
|
code: "review-dependency-missing",
|
|
31234
31315
|
message: `recorded review dependency is missing: ${target}`,
|
|
31235
31316
|
link: target
|
|
31236
31317
|
}));
|
|
31237
|
-
} else if (entry.reviewDependencies[target] !== hash(
|
|
31318
|
+
} else if (entry.reviewDependencies[target] !== hash(content3)) {
|
|
31238
31319
|
issues.push(changedDependencyIssue(relPath2, target));
|
|
31239
31320
|
}
|
|
31240
31321
|
}
|
|
@@ -31244,8 +31325,9 @@ function runReviewProofRule(ctx) {
|
|
|
31244
31325
|
if (!ctx.config.reviewProof)
|
|
31245
31326
|
return [];
|
|
31246
31327
|
const relLock = lockPath(ctx);
|
|
31247
|
-
const
|
|
31248
|
-
|
|
31328
|
+
const source = ctx.fileSource ?? "worktree";
|
|
31329
|
+
const lockContent = readRepoText(ctx.root, relLock, source);
|
|
31330
|
+
if (lockContent === null) {
|
|
31249
31331
|
return [
|
|
31250
31332
|
issue("review-proof", relLock, {
|
|
31251
31333
|
code: "review-proof-lock-missing",
|
|
@@ -31253,7 +31335,7 @@ function runReviewProofRule(ctx) {
|
|
|
31253
31335
|
})
|
|
31254
31336
|
];
|
|
31255
31337
|
}
|
|
31256
|
-
const lock = parseLock(
|
|
31338
|
+
const lock = parseLock(lockContent);
|
|
31257
31339
|
if (!lock) {
|
|
31258
31340
|
return [
|
|
31259
31341
|
issue("review-proof", relLock, {
|
|
@@ -31264,11 +31346,10 @@ function runReviewProofRule(ctx) {
|
|
|
31264
31346
|
}
|
|
31265
31347
|
const issues = [];
|
|
31266
31348
|
for (const relPath2 of ctx.docMetaPaths) {
|
|
31267
|
-
const
|
|
31268
|
-
if (
|
|
31349
|
+
const content3 = readRepoText(ctx.root, relPath2, source);
|
|
31350
|
+
if (content3 === null)
|
|
31269
31351
|
continue;
|
|
31270
|
-
const
|
|
31271
|
-
const entry = lock.documents[normalizeRelPath(relative6(ctx.root, abs))];
|
|
31352
|
+
const entry = lock.documents[normalizeRelPath(relPath2)];
|
|
31272
31353
|
if (!entry) {
|
|
31273
31354
|
issues.push(issue("review-proof", relPath2, {
|
|
31274
31355
|
code: "review-proof-missing",
|
|
@@ -31295,8 +31376,8 @@ function runBannedRule(ctx) {
|
|
|
31295
31376
|
var bannedRule = { id: "banned", run: runBannedRule };
|
|
31296
31377
|
|
|
31297
31378
|
// src/audit/rules/doc-meta.ts
|
|
31298
|
-
import { existsSync as
|
|
31299
|
-
import { join as
|
|
31379
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
|
|
31380
|
+
import { join as join12 } from "node:path";
|
|
31300
31381
|
function checkDocMetaBanner(relPath2, content3) {
|
|
31301
31382
|
if (DOC_META_RE.test(content3))
|
|
31302
31383
|
return null;
|
|
@@ -31351,10 +31432,10 @@ function runDocMetaRule(ctx) {
|
|
|
31351
31432
|
const issues = [];
|
|
31352
31433
|
const today = new Date;
|
|
31353
31434
|
for (const relPath2 of ctx.docMetaPaths) {
|
|
31354
|
-
const abs =
|
|
31355
|
-
if (!
|
|
31435
|
+
const abs = join12(ctx.root, relPath2);
|
|
31436
|
+
if (!existsSync15(abs))
|
|
31356
31437
|
continue;
|
|
31357
|
-
const content3 =
|
|
31438
|
+
const content3 = readFileSync13(abs, "utf8");
|
|
31358
31439
|
const banner = checkDocMetaBanner(relPath2, content3);
|
|
31359
31440
|
if (banner) {
|
|
31360
31441
|
issues.push(banner);
|
|
@@ -31382,7 +31463,7 @@ function runDocMetaRule(ctx) {
|
|
|
31382
31463
|
var docMetaRule = { id: "doc-meta", run: runDocMetaRule };
|
|
31383
31464
|
|
|
31384
31465
|
// src/audit/rules/links.ts
|
|
31385
|
-
import { existsSync as
|
|
31466
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
|
|
31386
31467
|
import { dirname as dirname9, resolve as resolve5 } from "node:path";
|
|
31387
31468
|
function resolveLink2(sourceFile, target) {
|
|
31388
31469
|
const withoutAnchor = target.split("#")[0]?.split("?")[0] ?? "";
|
|
@@ -31406,13 +31487,13 @@ function checkAgentFile(input, resolved, relSource) {
|
|
|
31406
31487
|
return null;
|
|
31407
31488
|
}
|
|
31408
31489
|
const agentPath = resolved.endsWith(".md") ? resolved : `${resolved}.md`;
|
|
31409
|
-
if (
|
|
31490
|
+
if (existsSync16(agentPath))
|
|
31410
31491
|
return null;
|
|
31411
31492
|
return issue("links", relSource, { message: "missing agent file", link: input.linkLabel });
|
|
31412
31493
|
}
|
|
31413
31494
|
function checkBrokenPath(ctx) {
|
|
31414
31495
|
const { input, pathPart, resolved, relSource, relTarget } = ctx;
|
|
31415
|
-
if (!(pathPart && !
|
|
31496
|
+
if (!(pathPart && !existsSync16(resolved)))
|
|
31416
31497
|
return null;
|
|
31417
31498
|
return issue("links", relSource, {
|
|
31418
31499
|
message: `broken link → ${relTarget}`,
|
|
@@ -31421,9 +31502,9 @@ function checkBrokenPath(ctx) {
|
|
|
31421
31502
|
}
|
|
31422
31503
|
function checkBrokenAnchor(ctx) {
|
|
31423
31504
|
const { input, anchor, resolved, relSource, relTarget } = ctx;
|
|
31424
|
-
if (!(anchor &&
|
|
31505
|
+
if (!(anchor && existsSync16(resolved)))
|
|
31425
31506
|
return null;
|
|
31426
|
-
const targetContent =
|
|
31507
|
+
const targetContent = readFileSync14(resolved, "utf8");
|
|
31427
31508
|
const slugs = extractHeadingSlugs(targetContent, resolved);
|
|
31428
31509
|
const anchorSlug = slugifyAnchor(anchor);
|
|
31429
31510
|
if (slugs.has(anchorSlug))
|
|
@@ -31475,8 +31556,8 @@ function runLinksRule(ctx) {
|
|
|
31475
31556
|
var linksRule = { id: "links", run: runLinksRule };
|
|
31476
31557
|
|
|
31477
31558
|
// src/audit/rules/near-duplicate.ts
|
|
31478
|
-
import { readFileSync as
|
|
31479
|
-
import { join as
|
|
31559
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
31560
|
+
import { join as join13 } from "node:path";
|
|
31480
31561
|
|
|
31481
31562
|
// src/audit/core/ssot-fit.ts
|
|
31482
31563
|
var DEFAULT_SSOT_OVERLAP_MIN = 0.35;
|
|
@@ -31764,7 +31845,7 @@ function runNearDuplicateRule(ctx) {
|
|
|
31764
31845
|
const ignored = ignoredPairSet(ctx);
|
|
31765
31846
|
const entries = eligibleEntries(ctx);
|
|
31766
31847
|
const fingerprints = entries.map((e) => {
|
|
31767
|
-
const content3 =
|
|
31848
|
+
const content3 = readFileSync15(join13(ctx.root, e.path), "utf8");
|
|
31768
31849
|
const tokens = tokenize2(bodyWithoutSsotNoise(content3));
|
|
31769
31850
|
return {
|
|
31770
31851
|
path: e.path,
|
|
@@ -31859,12 +31940,104 @@ function runProsePolicyRule(ctx) {
|
|
|
31859
31940
|
}
|
|
31860
31941
|
var prosePolicyRule = { id: "prose-policy", run: runProsePolicyRule };
|
|
31861
31942
|
|
|
31943
|
+
// src/audit/core/review-coverage.ts
|
|
31944
|
+
var DEFAULT_REVIEW_COVERAGE_INCLUDE = [
|
|
31945
|
+
"**/*.{ts,tsx,js,jsx,mjs,cjs,py}",
|
|
31946
|
+
"package.json",
|
|
31947
|
+
"project.json"
|
|
31948
|
+
];
|
|
31949
|
+
var DEFAULT_REVIEW_COVERAGE_EXCLUDE = [
|
|
31950
|
+
"**/__tests__/**",
|
|
31951
|
+
"**/*.test.*",
|
|
31952
|
+
"**/*.spec.*",
|
|
31953
|
+
"**/fixtures/**",
|
|
31954
|
+
"templates/**",
|
|
31955
|
+
"dist/**",
|
|
31956
|
+
"node_modules/**",
|
|
31957
|
+
".git/**",
|
|
31958
|
+
".skeleton/plugins/**"
|
|
31959
|
+
];
|
|
31960
|
+
function reviewCoveragePatterns(config) {
|
|
31961
|
+
const configured = config.reviewCoverage;
|
|
31962
|
+
if (configured?.include && configured.include.length === 0) {
|
|
31963
|
+
return { include: [], exclude: [] };
|
|
31964
|
+
}
|
|
31965
|
+
return {
|
|
31966
|
+
include: configured?.include && configured.include.length > 0 ? configured.include : DEFAULT_REVIEW_COVERAGE_INCLUDE,
|
|
31967
|
+
exclude: [...DEFAULT_REVIEW_COVERAGE_EXCLUDE, ...configured?.exclude ?? []]
|
|
31968
|
+
};
|
|
31969
|
+
}
|
|
31970
|
+
function pathRequiresReviewCoverage(relPath2, config) {
|
|
31971
|
+
const { include, exclude } = reviewCoveragePatterns(config);
|
|
31972
|
+
if (include.length === 0)
|
|
31973
|
+
return false;
|
|
31974
|
+
const path3 = normalizeRelPath(relPath2);
|
|
31975
|
+
if (exclude.some((pattern) => matchesGlobScope(path3, pattern)))
|
|
31976
|
+
return false;
|
|
31977
|
+
return include.some((pattern) => matchesGlobScope(path3, pattern));
|
|
31978
|
+
}
|
|
31979
|
+
function collectReviewDependencyPatterns(input) {
|
|
31980
|
+
const patterns = new Set;
|
|
31981
|
+
const source = input.fileSource ?? "worktree";
|
|
31982
|
+
for (const abs of collectScanFiles(input.config, input.root, input.skillIndex)) {
|
|
31983
|
+
const rel = relPath(abs, input.root);
|
|
31984
|
+
const content3 = readRepoText(input.root, rel, source);
|
|
31985
|
+
if (content3 === null)
|
|
31986
|
+
continue;
|
|
31987
|
+
for (const pattern of reviewDependencyPatterns(content3))
|
|
31988
|
+
patterns.add(pattern);
|
|
31989
|
+
}
|
|
31990
|
+
return [...patterns].sort();
|
|
31991
|
+
}
|
|
31992
|
+
function pathHasReviewOwner(relPath2, patterns) {
|
|
31993
|
+
return patterns.some((pattern) => reviewDependencyMatchesPath(pattern, relPath2));
|
|
31994
|
+
}
|
|
31995
|
+
function collectReviewCoverageFiles(root2, config) {
|
|
31996
|
+
const { include, exclude } = reviewCoveragePatterns(config);
|
|
31997
|
+
if (include.length === 0)
|
|
31998
|
+
return [];
|
|
31999
|
+
const files = new Set;
|
|
32000
|
+
for (const pattern of include) {
|
|
32001
|
+
for (const match of globSync(pattern, {
|
|
32002
|
+
cwd: root2,
|
|
32003
|
+
onlyFiles: true,
|
|
32004
|
+
dot: true,
|
|
32005
|
+
ignore: exclude
|
|
32006
|
+
})) {
|
|
32007
|
+
const rel = normalizeRelPath(match);
|
|
32008
|
+
if (exclude.some((item) => matchesGlobScope(rel, item)))
|
|
32009
|
+
continue;
|
|
32010
|
+
files.add(rel);
|
|
32011
|
+
}
|
|
32012
|
+
}
|
|
32013
|
+
return [...files].sort();
|
|
32014
|
+
}
|
|
32015
|
+
|
|
32016
|
+
// src/audit/rules/review-coverage.ts
|
|
32017
|
+
function runReviewCoverageRule(ctx) {
|
|
32018
|
+
const patterns = collectReviewDependencyPatterns({
|
|
32019
|
+
root: ctx.root,
|
|
32020
|
+
config: ctx.config,
|
|
32021
|
+
skillIndex: ctx.skillIndex,
|
|
32022
|
+
fileSource: ctx.fileSource
|
|
32023
|
+
});
|
|
32024
|
+
return collectReviewCoverageFiles(ctx.root, ctx.config).filter((path3) => !pathHasReviewOwner(path3, patterns)).map((path3) => issue("review-coverage", path3, {
|
|
32025
|
+
code: "review-coverage-gap",
|
|
32026
|
+
message: "file has no owning document; add a review-deps path or glob on the paper that describes it"
|
|
32027
|
+
}));
|
|
32028
|
+
}
|
|
32029
|
+
var reviewCoverageRule = {
|
|
32030
|
+
id: "review-coverage",
|
|
32031
|
+
global: true,
|
|
32032
|
+
run: runReviewCoverageRule
|
|
32033
|
+
};
|
|
32034
|
+
|
|
31862
32035
|
// src/audit/rules/review-deps.ts
|
|
31863
|
-
import { relative as
|
|
32036
|
+
import { relative as relative6 } from "node:path";
|
|
31864
32037
|
function runReviewDepsRule(ctx) {
|
|
31865
32038
|
const issues = [];
|
|
31866
32039
|
for (const abs of collectScanFiles(ctx.config, ctx.root, ctx.skillIndex)) {
|
|
31867
|
-
const path3 = normalizeRelPath(
|
|
32040
|
+
const path3 = normalizeRelPath(relative6(ctx.root, abs));
|
|
31868
32041
|
for (const marker of parseReviewDepsMarkers(readFileContent(abs))) {
|
|
31869
32042
|
issues.push(...validateMarker(ctx.root, path3, marker.paths));
|
|
31870
32043
|
}
|
|
@@ -31925,16 +32098,16 @@ function runScanRootsRule(ctx) {
|
|
|
31925
32098
|
var scanRootsRule = { id: "scan-roots", run: runScanRootsRule };
|
|
31926
32099
|
|
|
31927
32100
|
// src/audit/rules/skill-index.ts
|
|
31928
|
-
import { existsSync as
|
|
31929
|
-
import { join as
|
|
32101
|
+
import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync as readFileSync16 } from "node:fs";
|
|
32102
|
+
import { join as join14, relative as relative7 } from "node:path";
|
|
31930
32103
|
function walkSkillMarkdown(dir) {
|
|
31931
32104
|
const files = [];
|
|
31932
|
-
if (!
|
|
32105
|
+
if (!existsSync17(dir))
|
|
31933
32106
|
return files;
|
|
31934
32107
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
31935
32108
|
if (entry.name.startsWith("."))
|
|
31936
32109
|
continue;
|
|
31937
|
-
const fullPath =
|
|
32110
|
+
const fullPath = join14(dir, entry.name);
|
|
31938
32111
|
if (entry.isDirectory()) {
|
|
31939
32112
|
files.push(...walkSkillMarkdown(fullPath));
|
|
31940
32113
|
continue;
|
|
@@ -31960,8 +32133,8 @@ function parseReadmeTaxonomySlugs(content3) {
|
|
|
31960
32133
|
}
|
|
31961
32134
|
function scanFileForSkillLinks(ctx, filePath, index2) {
|
|
31962
32135
|
const issues = [];
|
|
31963
|
-
const rel =
|
|
31964
|
-
const content3 =
|
|
32136
|
+
const rel = relative7(ctx.root, filePath).replace(/\\/g, "/");
|
|
32137
|
+
const content3 = readFileSync16(filePath, "utf8");
|
|
31965
32138
|
for (const match of content3.matchAll(SKILL_LINK_RE)) {
|
|
31966
32139
|
const slug2 = match[1];
|
|
31967
32140
|
if (!slug2)
|
|
@@ -31974,14 +32147,14 @@ function scanFileForSkillLinks(ctx, filePath, index2) {
|
|
|
31974
32147
|
}
|
|
31975
32148
|
function taxonomyIssuesForReadme(input) {
|
|
31976
32149
|
const { ctx, index: index2, skillRoot, diskSlugs, nonPublic } = input;
|
|
31977
|
-
const readmePath =
|
|
31978
|
-
if (!
|
|
32150
|
+
const readmePath = join14(ctx.root, skillRoot.relPath, "README.md");
|
|
32151
|
+
if (!existsSync17(readmePath))
|
|
31979
32152
|
return [];
|
|
31980
|
-
const readme =
|
|
32153
|
+
const readme = readFileSync16(readmePath, "utf8");
|
|
31981
32154
|
if (!readme.includes("## Taxonomy"))
|
|
31982
32155
|
return [];
|
|
31983
32156
|
const taxonomySlugs = parseReadmeTaxonomySlugs(readme);
|
|
31984
|
-
const nestedSlugs = diskSlugs.filter((slug2) =>
|
|
32157
|
+
const nestedSlugs = diskSlugs.filter((slug2) => existsSync17(join14(ctx.root, skillRoot.relPath, slug2, "SKILL.md")));
|
|
31985
32158
|
const foreign = new Set(index2.foreignSlugs);
|
|
31986
32159
|
const publicSlugs = nestedSlugs.filter((slug2) => !(nonPublic.has(slug2) || foreign.has(slug2)));
|
|
31987
32160
|
const relReadme = `${skillRoot.relPath}/README.md`;
|
|
@@ -32014,10 +32187,10 @@ function slugsForRoot(skillRoot, index2, owned) {
|
|
|
32014
32187
|
function auditSkillRoot(input) {
|
|
32015
32188
|
const { ctx, index: index2, skillRoot, owned } = input;
|
|
32016
32189
|
const issues = [];
|
|
32017
|
-
const base = skillRoot.kind === "nested" ?
|
|
32190
|
+
const base = skillRoot.kind === "nested" ? join14(ctx.root, skillRoot.relPath) : ctx.root;
|
|
32018
32191
|
for (const slug2 of slugsForRoot(skillRoot, index2, owned)) {
|
|
32019
|
-
const skillDir =
|
|
32020
|
-
if (!
|
|
32192
|
+
const skillDir = join14(base, slug2);
|
|
32193
|
+
if (!existsSync17(skillDir))
|
|
32021
32194
|
continue;
|
|
32022
32195
|
for (const skillMd of walkSkillMarkdown(skillDir)) {
|
|
32023
32196
|
issues.push(...scanFileForSkillLinks(ctx, skillMd, index2));
|
|
@@ -32067,8 +32240,8 @@ function runSsotRule(ctx) {
|
|
|
32067
32240
|
var ssotRule = { id: "ssot", run: runSsotRule };
|
|
32068
32241
|
|
|
32069
32242
|
// src/audit/rules/ssot-summary.ts
|
|
32070
|
-
import { readFileSync as
|
|
32071
|
-
import { join as
|
|
32243
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
32244
|
+
import { join as join15 } from "node:path";
|
|
32072
32245
|
function runSsotSummaryRule(ctx) {
|
|
32073
32246
|
const overlapMin = ctx.config.docsLint?.ssotOverlapMin ?? DEFAULT_SSOT_OVERLAP_MIN;
|
|
32074
32247
|
const margin = ctx.config.docsLint?.ssotBetterMatchMargin ?? DEFAULT_BETTER_MATCH_MARGIN;
|
|
@@ -32076,7 +32249,7 @@ function runSsotSummaryRule(ctx) {
|
|
|
32076
32249
|
const files = ctx.ssotEntries.map((entry) => ({
|
|
32077
32250
|
path: entry.path,
|
|
32078
32251
|
summary: entry.summary,
|
|
32079
|
-
content:
|
|
32252
|
+
content: readFileSync17(join15(ctx.root, entry.path), "utf8")
|
|
32080
32253
|
}));
|
|
32081
32254
|
return evaluateSsotFit(files, {
|
|
32082
32255
|
overlapMin,
|
|
@@ -32097,6 +32270,7 @@ var docsRules = [
|
|
|
32097
32270
|
{ ...nearDuplicateRule, global: true },
|
|
32098
32271
|
{ ...ssotSummaryRule, global: true },
|
|
32099
32272
|
{ ...coverageGapsRule, global: true },
|
|
32273
|
+
{ ...reviewCoverageRule, global: true },
|
|
32100
32274
|
linksRule,
|
|
32101
32275
|
docMetaRule,
|
|
32102
32276
|
reviewProofRule,
|
|
@@ -32263,14 +32437,7 @@ function labelForSuite(suite) {
|
|
|
32263
32437
|
function catalogStatusFor(root2, suite) {
|
|
32264
32438
|
if (suite !== "docs" && suite !== "self")
|
|
32265
32439
|
return "not-applicable";
|
|
32266
|
-
|
|
32267
|
-
return "skipped-ci";
|
|
32268
|
-
const result = checkCatalog(root2);
|
|
32269
|
-
if (result.missing)
|
|
32270
|
-
return "missing";
|
|
32271
|
-
if (result.stale)
|
|
32272
|
-
return "stale";
|
|
32273
|
-
return "current";
|
|
32440
|
+
return refreshLocalCatalog(root2);
|
|
32274
32441
|
}
|
|
32275
32442
|
function buildAuditResult(input) {
|
|
32276
32443
|
const diagnostics = finalizeIssues(input.issues, input.options.strict);
|
|
@@ -32366,7 +32533,8 @@ async function evaluateAudit(options) {
|
|
|
32366
32533
|
const base = createContext({
|
|
32367
32534
|
root: options.root,
|
|
32368
32535
|
paths: pathScoped ? options.paths : undefined,
|
|
32369
|
-
includeExcludedSkillTrees: options.suite === "skills" && !pathScoped
|
|
32536
|
+
includeExcludedSkillTrees: options.suite === "skills" && !pathScoped,
|
|
32537
|
+
fileSource: options.fileSource
|
|
32370
32538
|
});
|
|
32371
32539
|
const loaded = await loadPlugins(base.root, base.config);
|
|
32372
32540
|
const ctx = { ...base, policies: loaded.policies };
|
|
@@ -32408,11 +32576,6 @@ async function evaluateAudit(options) {
|
|
|
32408
32576
|
for (const rule of executableRules) {
|
|
32409
32577
|
issues.push(...rule.run(ctx));
|
|
32410
32578
|
}
|
|
32411
|
-
if (options.suite === "docs" || options.suite === "self") {
|
|
32412
|
-
for (const message of catalogAuditWarnings(ctx.root)) {
|
|
32413
|
-
issues.push(issue("catalog", CATALOG_REL_PATH, { message, severity: "warning" }));
|
|
32414
|
-
}
|
|
32415
|
-
}
|
|
32416
32579
|
const executed = executableRules.map((rule) => rule.id);
|
|
32417
32580
|
return buildAuditResult({
|
|
32418
32581
|
options,
|
|
@@ -32442,20 +32605,20 @@ async function runAudit(options) {
|
|
|
32442
32605
|
}
|
|
32443
32606
|
|
|
32444
32607
|
// src/customize/resolve.ts
|
|
32445
|
-
import { existsSync as
|
|
32446
|
-
import { basename as basename3, join as
|
|
32608
|
+
import { existsSync as existsSync18, readFileSync as readFileSync18 } from "node:fs";
|
|
32609
|
+
import { basename as basename3, join as join16, relative as relative8 } from "node:path";
|
|
32447
32610
|
function customizeDir(root2) {
|
|
32448
|
-
return
|
|
32611
|
+
return join16(root2, REGISTRY_DIR_REL, "customize");
|
|
32449
32612
|
}
|
|
32450
32613
|
function customizePathForSlug(root2, slug2) {
|
|
32451
|
-
return
|
|
32614
|
+
return join16(customizeDir(root2), `${slug2}.md`);
|
|
32452
32615
|
}
|
|
32453
32616
|
function resolveSlugFile(root2, slug2) {
|
|
32454
32617
|
const direct = customizePathForSlug(root2, slug2);
|
|
32455
|
-
if (
|
|
32618
|
+
if (existsSync18(direct)) {
|
|
32456
32619
|
return {
|
|
32457
|
-
content:
|
|
32458
|
-
path: normalizeRelPath(
|
|
32620
|
+
content: readFileSync18(direct, "utf8"),
|
|
32621
|
+
path: normalizeRelPath(relative8(root2, direct))
|
|
32459
32622
|
};
|
|
32460
32623
|
}
|
|
32461
32624
|
return { content: null, path: null };
|
|
@@ -32476,11 +32639,11 @@ function readAlwaysInclude(root2, basenames, skipBasename) {
|
|
|
32476
32639
|
const file = basename3(name);
|
|
32477
32640
|
if (skipBasename && file === skipBasename)
|
|
32478
32641
|
continue;
|
|
32479
|
-
const abs =
|
|
32480
|
-
if (!
|
|
32642
|
+
const abs = join16(dir, file);
|
|
32643
|
+
if (!existsSync18(abs))
|
|
32481
32644
|
continue;
|
|
32482
|
-
parts.push(
|
|
32483
|
-
paths.push(normalizeRelPath(
|
|
32645
|
+
parts.push(readFileSync18(abs, "utf8").trimEnd());
|
|
32646
|
+
paths.push(normalizeRelPath(relative8(root2, abs)));
|
|
32484
32647
|
}
|
|
32485
32648
|
return { parts, paths };
|
|
32486
32649
|
}
|
|
@@ -32519,7 +32682,7 @@ function resolveCustomizeFromRoot(slug2, startDir) {
|
|
|
32519
32682
|
}
|
|
32520
32683
|
|
|
32521
32684
|
// src/hooks/run.ts
|
|
32522
|
-
import
|
|
32685
|
+
import process4 from "node:process";
|
|
32523
32686
|
function parsePayload(raw) {
|
|
32524
32687
|
if (!raw.trim())
|
|
32525
32688
|
return {};
|
|
@@ -32550,7 +32713,7 @@ function extractSkillSlug(payload) {
|
|
|
32550
32713
|
}
|
|
32551
32714
|
const path3 = extractPath(payload);
|
|
32552
32715
|
if (path3)
|
|
32553
|
-
return slugFromPath(path3,
|
|
32716
|
+
return slugFromPath(path3, process4.cwd());
|
|
32554
32717
|
return null;
|
|
32555
32718
|
}
|
|
32556
32719
|
function cursorResponse(content3) {
|
|
@@ -32602,40 +32765,40 @@ Customize override for /${slug2} (from ${from}):
|
|
|
32602
32765
|
}
|
|
32603
32766
|
|
|
32604
32767
|
// src/init/init.ts
|
|
32605
|
-
import { spawnSync as
|
|
32606
|
-
import { copyFileSync, existsSync as
|
|
32607
|
-
import { join as
|
|
32608
|
-
import
|
|
32768
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
32769
|
+
import { copyFileSync, existsSync as existsSync23, mkdirSync as mkdirSync4, readFileSync as readFileSync21 } from "node:fs";
|
|
32770
|
+
import { join as join21 } from "node:path";
|
|
32771
|
+
import process5 from "node:process";
|
|
32609
32772
|
|
|
32610
32773
|
// src/init/merge-hooks.ts
|
|
32611
|
-
import { existsSync as
|
|
32612
|
-
import { dirname as dirname12, join as
|
|
32774
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync3, readFileSync as readFileSync19, writeFileSync as writeFileSync4 } from "node:fs";
|
|
32775
|
+
import { dirname as dirname12, join as join19 } from "node:path";
|
|
32613
32776
|
|
|
32614
32777
|
// src/init/package-paths.ts
|
|
32615
|
-
import { existsSync as
|
|
32616
|
-
import { dirname as dirname10, join as
|
|
32778
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
32779
|
+
import { dirname as dirname10, join as join17 } from "node:path";
|
|
32617
32780
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
32618
32781
|
var MODULE_DIR = dirname10(fileURLToPath4(import.meta.url));
|
|
32619
|
-
var PACKAGE_ROOT_CANDIDATES = [
|
|
32782
|
+
var PACKAGE_ROOT_CANDIDATES = [join17(MODULE_DIR, "../.."), join17(MODULE_DIR, "..")];
|
|
32620
32783
|
function resolvePackageRoot() {
|
|
32621
32784
|
for (const candidate of PACKAGE_ROOT_CANDIDATES) {
|
|
32622
|
-
if (
|
|
32785
|
+
if (existsSync19(join17(candidate, "package.json")))
|
|
32623
32786
|
return candidate;
|
|
32624
32787
|
}
|
|
32625
32788
|
throw new Error("Could not resolve @csark0812/skeleton package root");
|
|
32626
32789
|
}
|
|
32627
32790
|
function resolveTemplatesDir() {
|
|
32628
|
-
const dir =
|
|
32629
|
-
if (!
|
|
32791
|
+
const dir = join17(resolvePackageRoot(), "templates/skeleton-init");
|
|
32792
|
+
if (!existsSync19(dir)) {
|
|
32630
32793
|
throw new Error("Missing templates/skeleton-init in package");
|
|
32631
32794
|
}
|
|
32632
32795
|
return dir;
|
|
32633
32796
|
}
|
|
32634
32797
|
|
|
32635
32798
|
// src/init/resolve-hook-command.ts
|
|
32636
|
-
import { existsSync as
|
|
32799
|
+
import { existsSync as existsSync20, realpathSync as realpathSync5 } from "node:fs";
|
|
32637
32800
|
import { createRequire as createRequire3 } from "node:module";
|
|
32638
|
-
import { dirname as dirname11, join as
|
|
32801
|
+
import { dirname as dirname11, join as join18, relative as relative9, resolve as resolve6 } from "node:path";
|
|
32639
32802
|
var PACKAGE_NAME = "@csark0812/skeleton";
|
|
32640
32803
|
var CLI_DIST = "dist/cli.js";
|
|
32641
32804
|
var PACKAGE_ROOT = resolvePackageRoot();
|
|
@@ -32649,12 +32812,12 @@ function safeRealpath2(path3) {
|
|
|
32649
32812
|
}
|
|
32650
32813
|
}
|
|
32651
32814
|
function toRepoRelative(cwd, absPath) {
|
|
32652
|
-
const rel =
|
|
32815
|
+
const rel = relative9(safeRealpath2(cwd), safeRealpath2(absPath)).replace(/\\/g, "/");
|
|
32653
32816
|
return rel.startsWith("..") ? absPath.replace(/\\/g, "/") : rel;
|
|
32654
32817
|
}
|
|
32655
32818
|
function tryResolvePublishedCli(cwd) {
|
|
32656
32819
|
try {
|
|
32657
|
-
const req = createRequire3(
|
|
32820
|
+
const req = createRequire3(join18(cwd, "package.json"));
|
|
32658
32821
|
return req.resolve(`${PACKAGE_NAME}/${CLI_DIST}`);
|
|
32659
32822
|
} catch {
|
|
32660
32823
|
return null;
|
|
@@ -32663,8 +32826,8 @@ function tryResolvePublishedCli(cwd) {
|
|
|
32663
32826
|
function walkNodeModulesCli(cwd) {
|
|
32664
32827
|
let dir = cwd;
|
|
32665
32828
|
while (true) {
|
|
32666
|
-
const candidate =
|
|
32667
|
-
if (
|
|
32829
|
+
const candidate = join18(dir, "node_modules", PACKAGE_NAME, CLI_DIST);
|
|
32830
|
+
if (existsSync20(candidate))
|
|
32668
32831
|
return candidate;
|
|
32669
32832
|
const parent = dirname11(dir);
|
|
32670
32833
|
if (parent === dir)
|
|
@@ -32674,7 +32837,7 @@ function walkNodeModulesCli(cwd) {
|
|
|
32674
32837
|
return null;
|
|
32675
32838
|
}
|
|
32676
32839
|
function isInsidePackageRoot(cwd) {
|
|
32677
|
-
const rel =
|
|
32840
|
+
const rel = relative9(PACKAGE_ROOT, resolve6(cwd)).replace(/\\/g, "/");
|
|
32678
32841
|
return rel === "" || !(rel.startsWith("..") || rel.startsWith("/"));
|
|
32679
32842
|
}
|
|
32680
32843
|
function nodeCliHookCommand(cliPath) {
|
|
@@ -32705,14 +32868,14 @@ function identityKey(platform, event, matcher) {
|
|
|
32705
32868
|
return `skeleton:customize:${platform}:${event}:${matcher}`;
|
|
32706
32869
|
}
|
|
32707
32870
|
function loadFragment(name, hookCommand) {
|
|
32708
|
-
const raw =
|
|
32871
|
+
const raw = readFileSync19(join19(TEMPLATES_DIR, name), "utf8");
|
|
32709
32872
|
return JSON.parse(raw.replaceAll("{{HOOK_COMMAND}}", hookCommand));
|
|
32710
32873
|
}
|
|
32711
32874
|
function readJson(path3) {
|
|
32712
|
-
if (!
|
|
32875
|
+
if (!existsSync21(path3))
|
|
32713
32876
|
return null;
|
|
32714
32877
|
try {
|
|
32715
|
-
return JSON.parse(
|
|
32878
|
+
return JSON.parse(readFileSync19(path3, "utf8"));
|
|
32716
32879
|
} catch (error) {
|
|
32717
32880
|
throw new Error(`Invalid JSON in ${path3}: ${error}`);
|
|
32718
32881
|
}
|
|
@@ -32872,10 +33035,10 @@ function mergeNestedHooks(args) {
|
|
|
32872
33035
|
}
|
|
32873
33036
|
function mergeHookConfigs(opts) {
|
|
32874
33037
|
const results = [];
|
|
32875
|
-
const cursorPath =
|
|
33038
|
+
const cursorPath = join19(opts.cwd, ".cursor/hooks.json");
|
|
32876
33039
|
const cursorFragment = loadFragment("cursor-hooks.fragment.json", opts.hookCommand);
|
|
32877
33040
|
results.push(mergeCursorHooks(cursorPath, cursorFragment, opts));
|
|
32878
|
-
const claudePath =
|
|
33041
|
+
const claudePath = join19(opts.cwd, ".claude/settings.json");
|
|
32879
33042
|
const claudeFragment = loadFragment("claude-settings.fragment.json", opts.hookCommand);
|
|
32880
33043
|
results.push(mergeNestedHooks({
|
|
32881
33044
|
platform: "claude",
|
|
@@ -32884,8 +33047,8 @@ function mergeHookConfigs(opts) {
|
|
|
32884
33047
|
eventName: "PostToolUse",
|
|
32885
33048
|
opts
|
|
32886
33049
|
}));
|
|
32887
|
-
const codexPath =
|
|
32888
|
-
if (
|
|
33050
|
+
const codexPath = join19(opts.cwd, ".codex/hooks.json");
|
|
33051
|
+
if (existsSync21(join19(opts.cwd, ".codex"))) {
|
|
32889
33052
|
const codexFragment = loadFragment("codex-hooks.fragment.json", opts.hookCommand);
|
|
32890
33053
|
results.push(mergeNestedHooks({
|
|
32891
33054
|
platform: "codex",
|
|
@@ -32900,11 +33063,11 @@ function mergeHookConfigs(opts) {
|
|
|
32900
33063
|
return results;
|
|
32901
33064
|
}
|
|
32902
33065
|
function mergePackageJsonScripts(cwd) {
|
|
32903
|
-
const pkgPath =
|
|
32904
|
-
if (!
|
|
33066
|
+
const pkgPath = join19(cwd, "package.json");
|
|
33067
|
+
if (!existsSync21(pkgPath))
|
|
32905
33068
|
return "skipped";
|
|
32906
|
-
const fragment = JSON.parse(
|
|
32907
|
-
const pkg = JSON.parse(
|
|
33069
|
+
const fragment = JSON.parse(readFileSync19(join19(TEMPLATES_DIR, "package.json.scripts.fragment.json"), "utf8"));
|
|
33070
|
+
const pkg = JSON.parse(readFileSync19(pkgPath, "utf8"));
|
|
32908
33071
|
pkg.scripts ??= {};
|
|
32909
33072
|
let changed = false;
|
|
32910
33073
|
for (const [key, value] of Object.entries(fragment)) {
|
|
@@ -32920,6 +33083,37 @@ function mergePackageJsonScripts(cwd) {
|
|
|
32920
33083
|
return "updated";
|
|
32921
33084
|
}
|
|
32922
33085
|
|
|
33086
|
+
// src/init/merge-precommit.ts
|
|
33087
|
+
import { existsSync as existsSync22, readFileSync as readFileSync20, writeFileSync as writeFileSync5 } from "node:fs";
|
|
33088
|
+
import { join as join20 } from "node:path";
|
|
33089
|
+
var TEMPLATES_DIR2 = resolveTemplatesDir();
|
|
33090
|
+
var PRECOMMIT_NAME = ".pre-commit-config.yaml";
|
|
33091
|
+
var HOOK_ID = "id: skeleton-validate-staged";
|
|
33092
|
+
var LOCAL_HOOK_BLOCK = ` - repo: local
|
|
33093
|
+
hooks:
|
|
33094
|
+
- id: skeleton-validate-staged
|
|
33095
|
+
name: skeleton validate changed (staged)
|
|
33096
|
+
entry: node node_modules/@csark0812/skeleton/dist/cli.js validate changed --staged
|
|
33097
|
+
language: system
|
|
33098
|
+
pass_filenames: false
|
|
33099
|
+
`;
|
|
33100
|
+
function mergePrecommitConfig(cwd) {
|
|
33101
|
+
const target = join20(cwd, PRECOMMIT_NAME);
|
|
33102
|
+
const template = readFileSync20(join20(TEMPLATES_DIR2, "pre-commit-config.yaml"), "utf8");
|
|
33103
|
+
if (!existsSync22(target)) {
|
|
33104
|
+
writeFileSync5(target, template, "utf8");
|
|
33105
|
+
return "added";
|
|
33106
|
+
}
|
|
33107
|
+
const existing = readFileSync20(target, "utf8");
|
|
33108
|
+
if (existing.includes(HOOK_ID))
|
|
33109
|
+
return "skipped";
|
|
33110
|
+
const suffix = existing.includes("repos:") ? `
|
|
33111
|
+
${LOCAL_HOOK_BLOCK}` : `
|
|
33112
|
+
${template}`;
|
|
33113
|
+
writeFileSync5(target, `${existing.trimEnd()}${suffix}`, "utf8");
|
|
33114
|
+
return "updated";
|
|
33115
|
+
}
|
|
33116
|
+
|
|
32923
33117
|
// src/init/skills-args.ts
|
|
32924
33118
|
var SKILLS_SOURCE = "csark0812/skeleton";
|
|
32925
33119
|
var DEFAULT_SKILL = "skeleton";
|
|
@@ -32958,25 +33152,25 @@ function skillsAddArgs(options = {}) {
|
|
|
32958
33152
|
}
|
|
32959
33153
|
|
|
32960
33154
|
// src/init/init.ts
|
|
32961
|
-
var
|
|
33155
|
+
var TEMPLATES_DIR3 = resolveTemplatesDir();
|
|
32962
33156
|
function writeScaffold(cwd) {
|
|
32963
|
-
const skeletonDir2 =
|
|
33157
|
+
const skeletonDir2 = join21(cwd, ".skeleton");
|
|
32964
33158
|
mkdirSync4(skeletonDir2, { recursive: true });
|
|
32965
33159
|
let created = false;
|
|
32966
|
-
const tomlPath =
|
|
32967
|
-
const legacyYaml =
|
|
32968
|
-
if (!(
|
|
32969
|
-
copyFileSync(
|
|
33160
|
+
const tomlPath = join21(cwd, "skeleton.toml");
|
|
33161
|
+
const legacyYaml = join21(skeletonDir2, "config.yaml");
|
|
33162
|
+
if (!(existsSync23(tomlPath) || existsSync23(legacyYaml))) {
|
|
33163
|
+
copyFileSync(join21(TEMPLATES_DIR3, "skeleton.toml"), tomlPath);
|
|
32970
33164
|
created = true;
|
|
32971
33165
|
}
|
|
32972
|
-
mkdirSync4(
|
|
33166
|
+
mkdirSync4(join21(skeletonDir2, "customize"), { recursive: true });
|
|
32973
33167
|
return created ? "created" : "skipped";
|
|
32974
33168
|
}
|
|
32975
33169
|
function assertPackageResolvable(cwd) {
|
|
32976
|
-
const pkgPath =
|
|
32977
|
-
if (!
|
|
33170
|
+
const pkgPath = join21(cwd, "package.json");
|
|
33171
|
+
if (!existsSync23(pkgPath))
|
|
32978
33172
|
return;
|
|
32979
|
-
const pkg = JSON.parse(
|
|
33173
|
+
const pkg = JSON.parse(readFileSync21(pkgPath, "utf8"));
|
|
32980
33174
|
const hasDep = pkg.devDependencies?.["@csark0812/skeleton"] || pkg.dependencies?.["@csark0812/skeleton"];
|
|
32981
33175
|
if (!hasDep) {
|
|
32982
33176
|
try {
|
|
@@ -32987,7 +33181,7 @@ function assertPackageResolvable(cwd) {
|
|
|
32987
33181
|
}
|
|
32988
33182
|
}
|
|
32989
33183
|
function runSkillsAdd(args, cwd) {
|
|
32990
|
-
const result =
|
|
33184
|
+
const result = spawnSync3("npx", args, {
|
|
32991
33185
|
cwd,
|
|
32992
33186
|
stdio: "inherit",
|
|
32993
33187
|
shell: false
|
|
@@ -33016,24 +33210,30 @@ function installSkillsIfRequested(options, cwd) {
|
|
|
33016
33210
|
return "installed";
|
|
33017
33211
|
}
|
|
33018
33212
|
function runInit(options = {}) {
|
|
33019
|
-
const cwd = options.cwd ??
|
|
33213
|
+
const cwd = options.cwd ?? process5.cwd();
|
|
33020
33214
|
assertPackageResolvable(cwd);
|
|
33021
33215
|
const scaffold = writeScaffold(cwd);
|
|
33022
33216
|
const hookCommand = resolveHookCommand(cwd);
|
|
33023
33217
|
const hooks = mergeHookConfigs({ cwd, hookCommand, forceHooks: options.forceHooks });
|
|
33024
33218
|
const scripts = mergePackageJsonScripts(cwd);
|
|
33219
|
+
const precommit = mergePrecommitConfig(cwd);
|
|
33025
33220
|
for (const result of hooks)
|
|
33026
33221
|
logHookMergeResult(result);
|
|
33027
33222
|
if (scaffold === "created") {
|
|
33028
|
-
console.log("init: wrote skeleton.toml (hooks optional
|
|
33223
|
+
console.log("init: wrote skeleton.toml (IDE customize hooks optional)");
|
|
33029
33224
|
} else {
|
|
33030
33225
|
console.log("init: skeleton.toml or .skeleton/ already present — skipped scaffold write");
|
|
33031
33226
|
}
|
|
33032
33227
|
if (scripts === "updated") {
|
|
33033
33228
|
console.log("init: merged validate/audit scripts into package.json");
|
|
33034
33229
|
}
|
|
33230
|
+
if (precommit === "added") {
|
|
33231
|
+
console.log("init: wrote .pre-commit-config.yaml (run pre-commit install once per machine)");
|
|
33232
|
+
} else if (precommit === "updated") {
|
|
33233
|
+
console.log("init: added skeleton validate hook to .pre-commit-config.yaml");
|
|
33234
|
+
}
|
|
33035
33235
|
const skills = installSkillsIfRequested(options, cwd);
|
|
33036
|
-
return { scaffold, hooks, scripts, skills };
|
|
33236
|
+
return { scaffold, hooks, scripts, skills, precommit };
|
|
33037
33237
|
}
|
|
33038
33238
|
|
|
33039
33239
|
// src/init/parse-args.ts
|
|
@@ -33058,9 +33258,9 @@ function parseInitArgs(argv) {
|
|
|
33058
33258
|
}
|
|
33059
33259
|
|
|
33060
33260
|
// src/plugins/build.ts
|
|
33061
|
-
import { spawnSync as
|
|
33261
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
33062
33262
|
import { createHash as createHash2 } from "node:crypto";
|
|
33063
|
-
import { existsSync as
|
|
33263
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync6 } from "node:fs";
|
|
33064
33264
|
import { basename as basename4, dirname as dirname13, resolve as resolve7 } from "node:path";
|
|
33065
33265
|
function parseBuildPluginArgs(argv) {
|
|
33066
33266
|
let check = false;
|
|
@@ -33101,7 +33301,7 @@ function localImportPaths(tsAbs, content3) {
|
|
|
33101
33301
|
candidates.push(resolve7(dir, spec), resolve7(dir, `${spec}.ts`), resolve7(dir, `${spec}.js`), resolve7(dir, spec, "index.ts"));
|
|
33102
33302
|
}
|
|
33103
33303
|
for (const candidate of candidates) {
|
|
33104
|
-
if (
|
|
33304
|
+
if (existsSync24(candidate) && candidate.endsWith(".ts")) {
|
|
33105
33305
|
deps.push(candidate);
|
|
33106
33306
|
break;
|
|
33107
33307
|
}
|
|
@@ -33118,7 +33318,7 @@ function sourceFingerprint(tsAbs, seen = new Set) {
|
|
|
33118
33318
|
if (seen.has(abs))
|
|
33119
33319
|
return;
|
|
33120
33320
|
seen.add(abs);
|
|
33121
|
-
const content3 =
|
|
33321
|
+
const content3 = readFileSync22(abs, "utf8");
|
|
33122
33322
|
hash2.update(basename4(abs));
|
|
33123
33323
|
hash2.update("\x00");
|
|
33124
33324
|
hash2.update(content3);
|
|
@@ -33131,15 +33331,15 @@ function sourceFingerprint(tsAbs, seen = new Set) {
|
|
|
33131
33331
|
return hash2.digest("hex");
|
|
33132
33332
|
}
|
|
33133
33333
|
function writeStamp(tsAbs, mjsAbs) {
|
|
33134
|
-
|
|
33334
|
+
writeFileSync6(stampPathForMjs(mjsAbs), `${sourceFingerprint(tsAbs)}
|
|
33135
33335
|
`, "utf8");
|
|
33136
33336
|
}
|
|
33137
33337
|
async function buildOne(tsAbs) {
|
|
33138
33338
|
const mjsAbs = mjsPathForTs(tsAbs);
|
|
33139
|
-
if (!
|
|
33339
|
+
if (!existsSync24(tsAbs)) {
|
|
33140
33340
|
throw new Error(`Plugin source not found: ${tsAbs}`);
|
|
33141
33341
|
}
|
|
33142
|
-
const proc =
|
|
33342
|
+
const proc = spawnSync4("bun", ["build", tsAbs, "--target=node", "--format=esm", `--outfile=${mjsAbs}`, "--packages=external"], { encoding: "utf8" });
|
|
33143
33343
|
if (proc.error) {
|
|
33144
33344
|
const code3 = proc.error.code;
|
|
33145
33345
|
if (code3 === "ENOENT") {
|
|
@@ -33156,17 +33356,17 @@ ${proc.stderr || proc.stdout || `exit ${proc.status}`}`);
|
|
|
33156
33356
|
}
|
|
33157
33357
|
function checkOne(tsAbs) {
|
|
33158
33358
|
const mjsAbs = mjsPathForTs(tsAbs);
|
|
33159
|
-
if (!
|
|
33359
|
+
if (!existsSync24(mjsAbs)) {
|
|
33160
33360
|
throw new Error(`Plugin not built: ${tsAbs} (missing ${mjsAbs}). Run: skeleton build-plugin`);
|
|
33161
33361
|
}
|
|
33162
|
-
if (!
|
|
33362
|
+
if (!existsSync24(tsAbs)) {
|
|
33163
33363
|
throw new Error(`Plugin source not found: ${tsAbs}`);
|
|
33164
33364
|
}
|
|
33165
33365
|
const stampAbs = stampPathForMjs(mjsAbs);
|
|
33166
|
-
if (!
|
|
33366
|
+
if (!existsSync24(stampAbs)) {
|
|
33167
33367
|
throw new Error(`Plugin stale: ${mjsAbs} has no fingerprint stamp. Run: skeleton build-plugin`);
|
|
33168
33368
|
}
|
|
33169
|
-
const expected =
|
|
33369
|
+
const expected = readFileSync22(stampAbs, "utf8").trim();
|
|
33170
33370
|
const actual = sourceFingerprint(tsAbs);
|
|
33171
33371
|
if (expected !== actual) {
|
|
33172
33372
|
throw new Error(`Plugin stale: ${mjsAbs} does not match ${tsAbs} (or local imports). Run: skeleton build-plugin`);
|
|
@@ -33195,15 +33395,15 @@ async function runBuildPlugin(options = {}) {
|
|
|
33195
33395
|
}
|
|
33196
33396
|
|
|
33197
33397
|
// src/validate/changed.ts
|
|
33198
|
-
import { spawnSync as
|
|
33199
|
-
import { existsSync as
|
|
33200
|
-
import { basename as basename5, extname as extname2, join as
|
|
33398
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
33399
|
+
import { existsSync as existsSync25, readFileSync as readFileSync23 } from "node:fs";
|
|
33400
|
+
import { basename as basename5, extname as extname2, join as join22 } from "node:path";
|
|
33201
33401
|
|
|
33202
33402
|
// src/validate/git-diff.ts
|
|
33203
|
-
import { spawnSync as
|
|
33403
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
33204
33404
|
function gitDiffChangedFiles(options = {}) {
|
|
33205
33405
|
const root2 = options.root ?? findRepoRoot();
|
|
33206
|
-
const proc =
|
|
33406
|
+
const proc = spawnSync5("git", gitDiffArgs(options), { cwd: root2, encoding: "utf8" });
|
|
33207
33407
|
if (proc.status !== 0) {
|
|
33208
33408
|
throw new Error(proc.stderr?.trim() || "git diff failed");
|
|
33209
33409
|
}
|
|
@@ -33239,6 +33439,32 @@ function addChangedPath(input) {
|
|
|
33239
33439
|
});
|
|
33240
33440
|
}
|
|
33241
33441
|
|
|
33442
|
+
// src/validate/staged.ts
|
|
33443
|
+
function stageRequiredIssue(file) {
|
|
33444
|
+
return issue("validate-changed", file, {
|
|
33445
|
+
code: "stage-required",
|
|
33446
|
+
message: "worktree differs from HEAD and is not staged; stage the attested document and review-lock.json when hash mode is on"
|
|
33447
|
+
});
|
|
33448
|
+
}
|
|
33449
|
+
function stageRequiredDiagnostics(input) {
|
|
33450
|
+
if (!input.staged)
|
|
33451
|
+
return [];
|
|
33452
|
+
const staged = new Set(input.stagedPaths.map(normalizeRelPath));
|
|
33453
|
+
const required = [...input.impactedDocuments];
|
|
33454
|
+
if (input.config.reviewProof) {
|
|
33455
|
+
required.push(input.config.reviewProof.lockfile ?? DEFAULT_REVIEW_LOCKFILE);
|
|
33456
|
+
}
|
|
33457
|
+
const issues = [];
|
|
33458
|
+
for (const path3 of [...new Set(required)].sort()) {
|
|
33459
|
+
if (staged.has(path3))
|
|
33460
|
+
continue;
|
|
33461
|
+
if (!pathDiffersFromHead(input.root, path3))
|
|
33462
|
+
continue;
|
|
33463
|
+
issues.push(stageRequiredIssue(path3));
|
|
33464
|
+
}
|
|
33465
|
+
return issues;
|
|
33466
|
+
}
|
|
33467
|
+
|
|
33242
33468
|
// src/validate/changed.ts
|
|
33243
33469
|
var DOC_EXTENSIONS = new Set([".md", ".mdc", ".yaml", ".yml"]);
|
|
33244
33470
|
var POLICY_EXTENSIONS = new Set([".yaml", ".yml"]);
|
|
@@ -33314,30 +33540,51 @@ function parseJsonContent(content3) {
|
|
|
33314
33540
|
function validationIssue(code3, file, message) {
|
|
33315
33541
|
return issue("validate-changed", file, { code: code3, message, severity: "error" });
|
|
33316
33542
|
}
|
|
33317
|
-
function
|
|
33318
|
-
|
|
33543
|
+
function rereadValidationIssue(file, target) {
|
|
33544
|
+
return issue("validate-changed", file, {
|
|
33545
|
+
code: "impacted-document-review-required",
|
|
33546
|
+
message: "a linked review dependency changed; re-read the entire document, then attest it with --fix=doc-meta --confirm-reviewed and include the document in validation",
|
|
33547
|
+
severity: "error",
|
|
33548
|
+
link: target
|
|
33549
|
+
});
|
|
33550
|
+
}
|
|
33551
|
+
function validateJson(relPath2, root2, fileSource) {
|
|
33552
|
+
const content3 = readRepoText(root2, relPath2, fileSource);
|
|
33553
|
+
if (content3 === null)
|
|
33554
|
+
return validationIssue("invalid-json", relPath2, "path not found");
|
|
33319
33555
|
try {
|
|
33320
|
-
parseJsonContent(
|
|
33556
|
+
parseJsonContent(content3);
|
|
33321
33557
|
return null;
|
|
33322
33558
|
} catch (error) {
|
|
33323
33559
|
return validationIssue("invalid-json", relPath2, `invalid JSON: ${error}`);
|
|
33324
33560
|
}
|
|
33325
33561
|
}
|
|
33326
|
-
function validatePolicy(relPath2, root2) {
|
|
33327
|
-
const
|
|
33562
|
+
function validatePolicy(relPath2, root2, fileSource) {
|
|
33563
|
+
const content3 = readRepoText(root2, relPath2, fileSource);
|
|
33564
|
+
if (content3 === null)
|
|
33565
|
+
return validationIssue("invalid-policy", relPath2, "path not found");
|
|
33328
33566
|
try {
|
|
33329
|
-
loadPolicyFile(
|
|
33567
|
+
loadPolicyFile(join22(root2, relPath2), content3);
|
|
33330
33568
|
return null;
|
|
33331
33569
|
} catch (error) {
|
|
33332
33570
|
return validationIssue("invalid-policy", relPath2, `invalid policy: ${error}`);
|
|
33333
33571
|
}
|
|
33334
33572
|
}
|
|
33335
|
-
function validateShell(relPath2, root2) {
|
|
33336
|
-
|
|
33337
|
-
|
|
33573
|
+
function validateShell(relPath2, root2, fileSource) {
|
|
33574
|
+
if (fileSource === "index") {
|
|
33575
|
+
const content3 = readRepoText(root2, relPath2, fileSource);
|
|
33576
|
+
if (content3 === null)
|
|
33577
|
+
return validationIssue("invalid-shell", relPath2, "path not found");
|
|
33578
|
+
const bash2 = spawnSync6("bash", ["-n"], { input: content3, encoding: "utf8" });
|
|
33579
|
+
if (bash2.status === 0)
|
|
33580
|
+
return null;
|
|
33581
|
+
return validationIssue("invalid-shell", relPath2, `shell syntax check failed: ${bash2.stderr}`);
|
|
33582
|
+
}
|
|
33583
|
+
const abs = join22(root2, relPath2);
|
|
33584
|
+
const shellcheck = spawnSync6("shellcheck", [abs], { encoding: "utf8" });
|
|
33338
33585
|
if (shellcheck.status === 0)
|
|
33339
33586
|
return null;
|
|
33340
|
-
const bash =
|
|
33587
|
+
const bash = spawnSync6("bash", ["-n", abs], { encoding: "utf8" });
|
|
33341
33588
|
if (bash.status === 0)
|
|
33342
33589
|
return null;
|
|
33343
33590
|
return validationIssue("invalid-shell", relPath2, `shell syntax check failed: ${bash.stderr || shellcheck.stderr}`);
|
|
@@ -33357,11 +33604,11 @@ function resolvePaths(options) {
|
|
|
33357
33604
|
};
|
|
33358
33605
|
}
|
|
33359
33606
|
function packageManagerFromPackageJson(root2) {
|
|
33360
|
-
const pkgPath =
|
|
33361
|
-
if (!
|
|
33607
|
+
const pkgPath = join22(root2, "package.json");
|
|
33608
|
+
if (!existsSync25(pkgPath))
|
|
33362
33609
|
return null;
|
|
33363
33610
|
try {
|
|
33364
|
-
const pkg = JSON.parse(
|
|
33611
|
+
const pkg = JSON.parse(readFileSync23(pkgPath, "utf8"));
|
|
33365
33612
|
const raw = pkg.packageManager?.split("@")[0];
|
|
33366
33613
|
if (raw === "bun" || raw === "npm" || raw === "pnpm" || raw === "yarn")
|
|
33367
33614
|
return raw;
|
|
@@ -33369,13 +33616,13 @@ function packageManagerFromPackageJson(root2) {
|
|
|
33369
33616
|
return null;
|
|
33370
33617
|
}
|
|
33371
33618
|
function packageManagerFromLockfiles(root2) {
|
|
33372
|
-
if (
|
|
33619
|
+
if (existsSync25(join22(root2, "bun.lock")) || existsSync25(join22(root2, "bun.lockb")))
|
|
33373
33620
|
return "bun";
|
|
33374
|
-
if (
|
|
33621
|
+
if (existsSync25(join22(root2, "pnpm-lock.yaml")))
|
|
33375
33622
|
return "pnpm";
|
|
33376
|
-
if (
|
|
33623
|
+
if (existsSync25(join22(root2, "yarn.lock")))
|
|
33377
33624
|
return "yarn";
|
|
33378
|
-
if (
|
|
33625
|
+
if (existsSync25(join22(root2, "package-lock.json")))
|
|
33379
33626
|
return "npm";
|
|
33380
33627
|
return null;
|
|
33381
33628
|
}
|
|
@@ -33401,8 +33648,8 @@ function emptyBuckets() {
|
|
|
33401
33648
|
function classifySinglePath(input) {
|
|
33402
33649
|
const { relPath: relPath2, ctx, state, bucketCtx } = input;
|
|
33403
33650
|
const normalized = normalizeRelPath(relPath2);
|
|
33404
|
-
const abs =
|
|
33405
|
-
if (!
|
|
33651
|
+
const abs = join22(ctx.root, normalized);
|
|
33652
|
+
if (!existsSync25(abs)) {
|
|
33406
33653
|
state.missing.push(normalized);
|
|
33407
33654
|
return;
|
|
33408
33655
|
}
|
|
@@ -33440,20 +33687,20 @@ function classifyPaths(ctx) {
|
|
|
33440
33687
|
}
|
|
33441
33688
|
return state;
|
|
33442
33689
|
}
|
|
33443
|
-
function validateLocalBuckets(buckets, root2) {
|
|
33690
|
+
function validateLocalBuckets(buckets, root2, fileSource) {
|
|
33444
33691
|
const diagnostics = [];
|
|
33445
33692
|
for (const relPath2 of buckets.shell) {
|
|
33446
|
-
const found = validateShell(relPath2, root2);
|
|
33693
|
+
const found = validateShell(relPath2, root2, fileSource);
|
|
33447
33694
|
if (found)
|
|
33448
33695
|
diagnostics.push(found);
|
|
33449
33696
|
}
|
|
33450
33697
|
for (const relPath2 of buckets.json) {
|
|
33451
|
-
const found = validateJson(relPath2, root2);
|
|
33698
|
+
const found = validateJson(relPath2, root2, fileSource);
|
|
33452
33699
|
if (found)
|
|
33453
33700
|
diagnostics.push(found);
|
|
33454
33701
|
}
|
|
33455
33702
|
for (const relPath2 of buckets.policy) {
|
|
33456
|
-
const found = validatePolicy(relPath2, root2);
|
|
33703
|
+
const found = validatePolicy(relPath2, root2, fileSource);
|
|
33457
33704
|
if (found)
|
|
33458
33705
|
diagnostics.push(found);
|
|
33459
33706
|
}
|
|
@@ -33466,16 +33713,24 @@ function discoverImpactedDocuments(input) {
|
|
|
33466
33713
|
const changed = new Set(input.relPaths.map(normalizeRelPath));
|
|
33467
33714
|
const impacted = [];
|
|
33468
33715
|
for (const abs of collectScanFiles(input.config, input.root, input.skillIndex)) {
|
|
33469
|
-
const document4 = impactedDocumentForPath(
|
|
33716
|
+
const document4 = impactedDocumentForPath({
|
|
33717
|
+
abs,
|
|
33718
|
+
root: input.root,
|
|
33719
|
+
changed,
|
|
33720
|
+
fileSource: input.fileSource
|
|
33721
|
+
});
|
|
33470
33722
|
if (document4)
|
|
33471
33723
|
impacted.push(document4);
|
|
33472
33724
|
}
|
|
33473
33725
|
return impacted.sort((a, b) => a.path.localeCompare(b.path));
|
|
33474
33726
|
}
|
|
33475
|
-
function impactedDocumentForPath(
|
|
33476
|
-
const path3 = relPath(abs,
|
|
33477
|
-
const
|
|
33478
|
-
|
|
33727
|
+
function impactedDocumentForPath(input) {
|
|
33728
|
+
const path3 = relPath(input.abs, input.root);
|
|
33729
|
+
const content3 = readRepoText(input.root, path3, input.fileSource);
|
|
33730
|
+
if (content3 === null)
|
|
33731
|
+
return null;
|
|
33732
|
+
const reviewDependencies = reviewDependencyPatterns(content3);
|
|
33733
|
+
const reasons = impactReasons(path3, reviewDependencies, input.changed);
|
|
33479
33734
|
return reasons.length > 0 ? { path: path3, reviewDependencies, reasons } : null;
|
|
33480
33735
|
}
|
|
33481
33736
|
function impactReasons(path3, reviewDependencies, changed) {
|
|
@@ -33494,18 +33749,35 @@ function dateModeImpactDiagnostics(input) {
|
|
|
33494
33749
|
return [];
|
|
33495
33750
|
const changed = new Set(input.relPaths.map(normalizeRelPath));
|
|
33496
33751
|
const today = formatLocalReviewDate(new Date);
|
|
33497
|
-
|
|
33498
|
-
|
|
33499
|
-
|
|
33500
|
-
|
|
33501
|
-
|
|
33502
|
-
|
|
33752
|
+
return input.impactedDocuments.flatMap((impacted) => dateModeIssuesForDocument({
|
|
33753
|
+
impacted,
|
|
33754
|
+
changed,
|
|
33755
|
+
today,
|
|
33756
|
+
root: input.root,
|
|
33757
|
+
fileSource: input.fileSource
|
|
33758
|
+
}));
|
|
33759
|
+
}
|
|
33760
|
+
function dateModeIssuesForDocument(input) {
|
|
33761
|
+
const content3 = readRepoText(input.root, input.impacted.path, input.fileSource);
|
|
33762
|
+
if (!content3) {
|
|
33763
|
+
return input.impacted.reasons.filter((reason) => reason.kind === "changed-review-dependency" && reason.target).map((reason) => rereadValidationIssue(input.impacted.path, reason.target ?? ""));
|
|
33764
|
+
}
|
|
33765
|
+
if (input.changed.has(input.impacted.path) && docMetaLastReviewed(content3) === input.today) {
|
|
33766
|
+
return [];
|
|
33767
|
+
}
|
|
33768
|
+
const issues = [];
|
|
33769
|
+
for (const reason of input.impacted.reasons) {
|
|
33770
|
+
if (reason.kind !== "changed-review-dependency" || !reason.target)
|
|
33503
33771
|
continue;
|
|
33504
|
-
|
|
33772
|
+
issues.push(rereadValidationIssue(input.impacted.path, reason.target));
|
|
33505
33773
|
}
|
|
33506
|
-
return
|
|
33774
|
+
return issues;
|
|
33775
|
+
}
|
|
33776
|
+
function uncoveredChangedPathDiagnostics(relPaths, config, patterns) {
|
|
33777
|
+
return relPaths.map(normalizeRelPath).filter((path3) => pathRequiresReviewCoverage(path3, config)).filter((path3) => !pathHasReviewOwner(path3, patterns)).map((path3) => validationIssue("uncovered-changed-path", path3, "no scanned document claims this path with review-deps. Add a review-deps marker on the owning paper."));
|
|
33507
33778
|
}
|
|
33508
|
-
function classificationDiagnostics(
|
|
33779
|
+
function classificationDiagnostics(input) {
|
|
33780
|
+
const { classification, root: root2, base, coverageCandidateCount } = input;
|
|
33509
33781
|
const diagnostics = [];
|
|
33510
33782
|
for (const orphan of classification.orphans) {
|
|
33511
33783
|
diagnostics.push(validationIssue("orphan-policy", orphan, "file is under .skeleton/ but is not referenced by any plugin policies glob; export it from a plugin policies array or move it"));
|
|
@@ -33514,7 +33786,7 @@ function classificationDiagnostics(classification, root2, base) {
|
|
|
33514
33786
|
diagnostics.push(validationIssue("missing-path", missing, "path not found"));
|
|
33515
33787
|
}
|
|
33516
33788
|
const audited = auditedPathCount(classification.buckets);
|
|
33517
|
-
if ((classification.skipped.length > 0 || classification.buckets.code.length > 0) && audited === 0 && !base) {
|
|
33789
|
+
if ((classification.skipped.length > 0 || classification.buckets.code.length > 0) && audited === 0 && coverageCandidateCount === 0 && !base) {
|
|
33518
33790
|
diagnostics.push(validationIssue("all-paths-skipped", ".", `all paths were skipped (code/config). This does not verify application code.
|
|
33519
33791
|
${codeValidationHint(root2)}`));
|
|
33520
33792
|
}
|
|
@@ -33545,35 +33817,54 @@ function auditOptions(suite, root2, paths, extra = {}) {
|
|
|
33545
33817
|
...extra
|
|
33546
33818
|
};
|
|
33547
33819
|
}
|
|
33820
|
+
async function auditSkillChanges(input) {
|
|
33821
|
+
const sourceOpt = { fileSource: input.fileSource };
|
|
33822
|
+
if (input.skills.length === 0)
|
|
33823
|
+
return [];
|
|
33824
|
+
if (!input.base) {
|
|
33825
|
+
return [await evaluateAudit(auditOptions("skills", input.root, [], sourceOpt))];
|
|
33826
|
+
}
|
|
33827
|
+
return [
|
|
33828
|
+
await evaluateAudit(auditOptions("skills", input.root, input.skills, {
|
|
33829
|
+
pathScopedOnly: true,
|
|
33830
|
+
...sourceOpt
|
|
33831
|
+
}))
|
|
33832
|
+
];
|
|
33833
|
+
}
|
|
33834
|
+
async function auditPolicyChanges(input) {
|
|
33835
|
+
const sourceOpt = { fileSource: input.fileSource };
|
|
33836
|
+
const audits = [await evaluateAudit(auditOptions("docs", input.root, [], sourceOpt))];
|
|
33837
|
+
const skillPaths = listSkillMarkdownPaths(input.root, input.skillIndex);
|
|
33838
|
+
if (skillPaths.length === 0)
|
|
33839
|
+
return audits;
|
|
33840
|
+
audits.push(await evaluateAudit(auditOptions("skills", input.root, skillPaths, {
|
|
33841
|
+
pathScopedOnly: true,
|
|
33842
|
+
...sourceOpt
|
|
33843
|
+
})));
|
|
33844
|
+
return audits;
|
|
33845
|
+
}
|
|
33548
33846
|
async function evaluateBucketAudits(input) {
|
|
33549
|
-
const { classification, root: root2, skillIndex, base } = input;
|
|
33847
|
+
const { classification, root: root2, skillIndex, base, fileSource } = input;
|
|
33550
33848
|
const audits = [];
|
|
33551
|
-
const diagnostics = validateLocalBuckets(classification.buckets, root2);
|
|
33849
|
+
const diagnostics = validateLocalBuckets(classification.buckets, root2, fileSource);
|
|
33850
|
+
const sourceOpt = { fileSource };
|
|
33552
33851
|
if (base) {
|
|
33553
|
-
audits.push(await evaluateAudit(auditOptions("self", root2, [], { globalOnly: true })));
|
|
33852
|
+
audits.push(await evaluateAudit(auditOptions("self", root2, [], { globalOnly: true, ...sourceOpt })));
|
|
33554
33853
|
}
|
|
33555
33854
|
if (classification.buckets.docs.length > 0) {
|
|
33556
|
-
audits.push(await evaluateAudit(auditOptions("docs", root2, classification.buckets.docs, {
|
|
33557
|
-
|
|
33558
|
-
|
|
33559
|
-
|
|
33560
|
-
diagnostics.push(validationIssue("full-skills-audit-required", classification.buckets.skills[0] ?? ".", "skill paths need the full skills suite; run skeleton audit skills (audit self covers docs and .skeleton; excluded skill trees still need audit skills)"));
|
|
33561
|
-
} else {
|
|
33562
|
-
audits.push(await evaluateAudit(auditOptions("skills", root2, classification.buckets.skills, {
|
|
33563
|
-
pathScopedOnly: true
|
|
33564
|
-
})));
|
|
33565
|
-
}
|
|
33855
|
+
audits.push(await evaluateAudit(auditOptions("docs", root2, classification.buckets.docs, {
|
|
33856
|
+
pathScopedOnly: true,
|
|
33857
|
+
...sourceOpt
|
|
33858
|
+
})));
|
|
33566
33859
|
}
|
|
33567
|
-
|
|
33568
|
-
|
|
33569
|
-
|
|
33570
|
-
|
|
33571
|
-
|
|
33572
|
-
|
|
33573
|
-
|
|
33574
|
-
|
|
33575
|
-
}
|
|
33576
|
-
}
|
|
33860
|
+
audits.push(...await auditSkillChanges({
|
|
33861
|
+
skills: classification.buckets.skills,
|
|
33862
|
+
root: root2,
|
|
33863
|
+
base,
|
|
33864
|
+
fileSource
|
|
33865
|
+
}));
|
|
33866
|
+
if (classification.buckets.policy.length > 0 && !diagnostics.some((item) => item.code === "invalid-policy")) {
|
|
33867
|
+
audits.push(...await auditPolicyChanges({ root: root2, skillIndex, fileSource }));
|
|
33577
33868
|
}
|
|
33578
33869
|
return { audits, diagnostics };
|
|
33579
33870
|
}
|
|
@@ -33611,6 +33902,7 @@ function emptyClassification() {
|
|
|
33611
33902
|
}
|
|
33612
33903
|
async function evaluateValidateChanged(options = {}) {
|
|
33613
33904
|
const root2 = options.root ?? findRepoRoot();
|
|
33905
|
+
refreshLocalCatalog(root2);
|
|
33614
33906
|
const resolvedPaths = resolvePaths(options);
|
|
33615
33907
|
const relPaths = resolvedPaths.paths;
|
|
33616
33908
|
if (relPaths.length === 0) {
|
|
@@ -33618,6 +33910,7 @@ async function evaluateValidateChanged(options = {}) {
|
|
|
33618
33910
|
}
|
|
33619
33911
|
const config = loadConfig(root2);
|
|
33620
33912
|
const skillIndex = buildSkillIndex(root2, config.skillOwnership);
|
|
33913
|
+
const fileSource = options.staged ? "index" : "worktree";
|
|
33621
33914
|
let wiredPolicies;
|
|
33622
33915
|
try {
|
|
33623
33916
|
wiredPolicies = await collectWiredPolicyRelPaths(root2, config);
|
|
@@ -33638,37 +33931,57 @@ async function evaluateValidateChanged(options = {}) {
|
|
|
33638
33931
|
skillIndex
|
|
33639
33932
|
});
|
|
33640
33933
|
classification.missing = classification.missing.filter((path3) => !resolvedPaths.deleted.has(path3));
|
|
33641
|
-
const impactedDocuments = discoverImpactedDocuments({
|
|
33934
|
+
const impactedDocuments = discoverImpactedDocuments({
|
|
33935
|
+
relPaths,
|
|
33936
|
+
root: root2,
|
|
33937
|
+
config,
|
|
33938
|
+
skillIndex,
|
|
33939
|
+
fileSource
|
|
33940
|
+
});
|
|
33642
33941
|
for (const impacted of impactedDocuments) {
|
|
33643
33942
|
if (!classification.buckets.docs.includes(impacted.path)) {
|
|
33644
33943
|
classification.buckets.docs.push(impacted.path);
|
|
33645
33944
|
}
|
|
33646
33945
|
}
|
|
33647
33946
|
classification.buckets.docs.sort();
|
|
33648
|
-
const
|
|
33649
|
-
|
|
33650
|
-
|
|
33651
|
-
|
|
33652
|
-
|
|
33653
|
-
|
|
33654
|
-
|
|
33655
|
-
|
|
33656
|
-
|
|
33657
|
-
|
|
33947
|
+
const ownerPatterns = collectReviewDependencyPatterns({
|
|
33948
|
+
root: root2,
|
|
33949
|
+
config,
|
|
33950
|
+
skillIndex,
|
|
33951
|
+
fileSource
|
|
33952
|
+
});
|
|
33953
|
+
const coverageCandidateCount = relPaths.filter((path3) => pathRequiresReviewCoverage(path3, config)).length;
|
|
33954
|
+
const diagnostics = [
|
|
33955
|
+
...classificationDiagnostics({
|
|
33956
|
+
classification,
|
|
33957
|
+
root: root2,
|
|
33958
|
+
base: options.base,
|
|
33959
|
+
coverageCandidateCount
|
|
33960
|
+
}),
|
|
33961
|
+
...uncoveredChangedPathDiagnostics(relPaths, config, ownerPatterns),
|
|
33962
|
+
...stageRequiredDiagnostics({
|
|
33963
|
+
staged: options.staged ?? false,
|
|
33964
|
+
stagedPaths: relPaths,
|
|
33965
|
+
impactedDocuments: impactedDocuments.map((item) => item.path),
|
|
33966
|
+
config,
|
|
33967
|
+
root: root2
|
|
33968
|
+
})
|
|
33969
|
+
];
|
|
33658
33970
|
const evaluated = await evaluateBucketAudits({
|
|
33659
33971
|
classification,
|
|
33660
33972
|
root: root2,
|
|
33661
33973
|
skillIndex,
|
|
33662
|
-
base: options.base
|
|
33974
|
+
base: options.base,
|
|
33975
|
+
fileSource
|
|
33663
33976
|
});
|
|
33664
|
-
evaluated.diagnostics.push(...dateModeImpactDiagnostics({ config, impactedDocuments, relPaths, root: root2 }));
|
|
33977
|
+
evaluated.diagnostics.push(...dateModeImpactDiagnostics({ config, impactedDocuments, relPaths, root: root2, fileSource }));
|
|
33665
33978
|
return resultFor({
|
|
33666
33979
|
options,
|
|
33667
33980
|
relPaths,
|
|
33668
33981
|
classification: publicClassification(classification),
|
|
33669
33982
|
impactedDocuments,
|
|
33670
33983
|
audits: evaluated.audits,
|
|
33671
|
-
diagnostics: evaluated.diagnostics
|
|
33984
|
+
diagnostics: [...diagnostics, ...evaluated.diagnostics]
|
|
33672
33985
|
});
|
|
33673
33986
|
}
|
|
33674
33987
|
function printValidateChangedResult(result) {
|
|
@@ -33679,16 +33992,20 @@ function printValidateChangedResult(result) {
|
|
|
33679
33992
|
for (const path3 of result.classification.foreignSkills) {
|
|
33680
33993
|
console.log(`validate changed: skipping foreign skill ${path3} (owned upstream; see skills-lock.json / skillOwnership)`);
|
|
33681
33994
|
}
|
|
33682
|
-
|
|
33683
|
-
|
|
33684
|
-
|
|
33685
|
-
|
|
33686
|
-
|
|
33687
|
-
|
|
33688
|
-
console.log(`validate changed: ${impacted.path} requires review (dependency ${reason.dependency} matched ${reason.target})`);
|
|
33995
|
+
if (result.ok) {
|
|
33996
|
+
for (const audit of result.audits)
|
|
33997
|
+
printAuditResult(audit, false);
|
|
33998
|
+
} else {
|
|
33999
|
+
for (const audit of result.audits.filter((item) => !item.ok)) {
|
|
34000
|
+
printAuditResult(audit, false);
|
|
33689
34001
|
}
|
|
33690
34002
|
}
|
|
33691
|
-
|
|
34003
|
+
const rereadDiagnostics = result.diagnostics.filter(isRereadIssue);
|
|
34004
|
+
const otherDiagnostics = result.diagnostics.filter((item) => !isRereadIssue(item));
|
|
34005
|
+
if (rereadDiagnostics.length > 0) {
|
|
34006
|
+
printReport(rereadDiagnostics, { label: "validate changed" });
|
|
34007
|
+
}
|
|
34008
|
+
for (const diagnostic of otherDiagnostics) {
|
|
33692
34009
|
const path3 = diagnostic.file === "." ? "" : `${diagnostic.file}: `;
|
|
33693
34010
|
console.error(`validate changed: ${path3}${diagnostic.message}`);
|
|
33694
34011
|
}
|
|
@@ -33799,7 +34116,7 @@ function handleCustomizeResolve(argv) {
|
|
|
33799
34116
|
if (json) {
|
|
33800
34117
|
console.log(JSON.stringify(result, null, 2));
|
|
33801
34118
|
} else if (result.content) {
|
|
33802
|
-
|
|
34119
|
+
process6.stdout.write(result.content);
|
|
33803
34120
|
}
|
|
33804
34121
|
return 0;
|
|
33805
34122
|
}
|
|
@@ -33808,7 +34125,7 @@ function handleHook(argv) {
|
|
|
33808
34125
|
usage();
|
|
33809
34126
|
return 1;
|
|
33810
34127
|
}
|
|
33811
|
-
|
|
34128
|
+
process6.stdout.write(runCustomizeHook(readFileSync24(0, "utf8")));
|
|
33812
34129
|
return 0;
|
|
33813
34130
|
}
|
|
33814
34131
|
function handleInit(argv) {
|
|
@@ -33840,22 +34157,22 @@ async function dispatchCommand(argv) {
|
|
|
33840
34157
|
}
|
|
33841
34158
|
}
|
|
33842
34159
|
async function main() {
|
|
33843
|
-
const argv =
|
|
34160
|
+
const argv = process6.argv.slice(2);
|
|
33844
34161
|
const command = argv[0];
|
|
33845
34162
|
if (!command || command === "--help" || command === "-h") {
|
|
33846
34163
|
usage();
|
|
33847
|
-
|
|
34164
|
+
process6.exit(command ? 0 : 1);
|
|
33848
34165
|
}
|
|
33849
34166
|
try {
|
|
33850
34167
|
const exitCode = await dispatchCommand(argv);
|
|
33851
34168
|
if (exitCode === null) {
|
|
33852
34169
|
usage();
|
|
33853
|
-
|
|
34170
|
+
process6.exit(1);
|
|
33854
34171
|
}
|
|
33855
|
-
|
|
34172
|
+
process6.exit(exitCode);
|
|
33856
34173
|
} catch (error) {
|
|
33857
34174
|
console.error(String(error));
|
|
33858
|
-
|
|
34175
|
+
process6.exit(1);
|
|
33859
34176
|
}
|
|
33860
34177
|
}
|
|
33861
34178
|
main();
|