@csark0812/skeleton 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -15968,8 +15968,7 @@ var require_extend = __commonJS((exports, module) => {
15968
15968
  });
15969
15969
 
15970
15970
  // src/cli.ts
15971
- import { readFileSync as readFileSync22 } from "node:fs";
15972
- import process7 from "node:process";
15971
+ import process5 from "node:process";
15973
15972
 
15974
15973
  // src/audit/config/load.ts
15975
15974
  var import_ajv = __toESM(require_ajv(), 1);
@@ -16903,7 +16902,6 @@ var $visitAsync = visit.visitAsync;
16903
16902
 
16904
16903
  // src/audit/core/shared.ts
16905
16904
  var CATALOG_REL_PATH = ".skeleton/catalog.md";
16906
- var REGISTRY_DIR_REL = ".skeleton";
16907
16905
  var EXTERNAL_LINK_RE = /^(https?:|mailto:|#)/;
16908
16906
  var DOC_META_RE = /<!--\s*doc-meta:\s*owner=[^|]+\|\s*last-reviewed=\d{4}-\d{2}-\d{2}\s*-->/;
16909
16907
  var DOC_META_LAST_REVIEWED_RE = /last-reviewed=(\d{4}-\d{2}-\d{2})/;
@@ -17002,8 +17000,7 @@ function isDraftPlacementAllowed(relPath, draftPathPrefixes) {
17002
17000
  // src/audit/config/load.ts
17003
17001
  var SCHEMA_CANDIDATES = [
17004
17002
  join(dirname(fileURLToPath(import.meta.url)), "../../../schemas/config.schema.json"),
17005
- join(dirname(fileURLToPath(import.meta.url)), "../schemas/config.schema.json"),
17006
- join(dirname(fileURLToPath(import.meta.url)), "../../schemas/config.schema.json")
17003
+ join(dirname(fileURLToPath(import.meta.url)), "../schemas/config.schema.json")
17007
17004
  ];
17008
17005
  var ROOT_CONFIG_TOML = "skeleton.toml";
17009
17006
  var LEGACY_CONFIG_YAML = join(".skeleton", "config.yaml");
@@ -17083,9 +17080,6 @@ function nonPublicSkills(config) {
17083
17080
  return config.scan.nonPublicSkills ?? [];
17084
17081
  }
17085
17082
 
17086
- // src/audit/run.ts
17087
- import process4 from "node:process";
17088
-
17089
17083
  // src/catalog.ts
17090
17084
  import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "node:fs";
17091
17085
  import { dirname as dirname3, join as join5 } from "node:path";
@@ -18229,7 +18223,6 @@ function listOwnedSkillSlugs(index) {
18229
18223
 
18230
18224
  // src/audit/core/collect.ts
18231
18225
  var MARKDOWN_GLOBS = ["**/*.md", "**/*.mdc"];
18232
- var BUILTIN_INCLUDE_PATTERNS = [".skeleton/customize/**"];
18233
18226
  function isMarkdownFile(absPath) {
18234
18227
  return absPath.endsWith(".md") || absPath.endsWith(".mdc");
18235
18228
  }
@@ -18271,7 +18264,7 @@ function expandPatterns(root, patterns, exclude) {
18271
18264
  }
18272
18265
  function collectScanFiles(config, root, skillIndex) {
18273
18266
  const exclude = mergedExcludes(config);
18274
- const includePatterns = [...BUILTIN_INCLUDE_PATTERNS, ...config.scan.include];
18267
+ const includePatterns = [...config.scan.include];
18275
18268
  if (skillIndex) {
18276
18269
  includePatterns.push(...skillCollectAugments(skillIndex));
18277
18270
  }
@@ -18571,19 +18564,11 @@ function writeCatalog(root) {
18571
18564
  writeFileSync(abs, content, "utf8");
18572
18565
  return { path: normalizeRelPath(CATALOG_REL_PATH), entries };
18573
18566
  }
18574
- function catalogAuditWarnings(root) {
18567
+ function refreshLocalCatalog(root) {
18575
18568
  if (process3.env.CI === "true")
18576
- return [];
18577
- const result = checkCatalog(root);
18578
- if (result.missing) {
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 [];
18569
+ return "skipped-ci";
18570
+ writeCatalog(root);
18571
+ return "current";
18587
18572
  }
18588
18573
  function runCatalogCli(options = {}) {
18589
18574
  const root = options.root ?? findRepoRoot();
@@ -18617,8 +18602,7 @@ import { basename as basename2, dirname as dirname4, extname, join as join6 } fr
18617
18602
  import { fileURLToPath as fileURLToPath2 } from "node:url";
18618
18603
  var SCHEMA_CANDIDATES2 = [
18619
18604
  join6(dirname4(fileURLToPath2(import.meta.url)), "../../../schemas/policy-file.schema.json"),
18620
- join6(dirname4(fileURLToPath2(import.meta.url)), "../schemas/policy-file.schema.json"),
18621
- join6(dirname4(fileURLToPath2(import.meta.url)), "../../schemas/policy-file.schema.json")
18605
+ join6(dirname4(fileURLToPath2(import.meta.url)), "../schemas/policy-file.schema.json")
18622
18606
  ];
18623
18607
  function resolvePolicySchemaPath() {
18624
18608
  for (const candidate of SCHEMA_CANDIDATES2) {
@@ -18945,7 +18929,8 @@ function createContext(options = {}) {
18945
18929
  registryHasTableHeader: false,
18946
18930
  skillIndex,
18947
18931
  lockedSkillSlugs: new Set(skillIndex.foreignSlugs),
18948
- policies: options.policies ?? []
18932
+ policies: options.policies ?? [],
18933
+ fileSource: options.fileSource
18949
18934
  };
18950
18935
  }
18951
18936
 
@@ -30900,9 +30885,8 @@ function printWarnings(label, warnings) {
30900
30885
  return;
30901
30886
  console.log(`${label} warnings:
30902
30887
  `);
30903
- for (const i of warnings) {
30904
- const linkPart = i.link ? ` (${i.link})` : "";
30905
- console.log(`- ${i.file}${linkPart}: ${i.message}`);
30888
+ for (const item of warnings) {
30889
+ console.log(`${item.file}: warning: ${item.message}`);
30906
30890
  }
30907
30891
  console.log("");
30908
30892
  }
@@ -30912,12 +30896,65 @@ function printSuccess(label, options, warnings) {
30912
30896
  console.log(`${label} passed${countNote}.`);
30913
30897
  return 0;
30914
30898
  }
30899
+ var REREAD_CODES = new Set([
30900
+ "review-dependency-changed",
30901
+ "impacted-document-review-required",
30902
+ "review-document-changed",
30903
+ "review-dependency-set-changed"
30904
+ ]);
30905
+ function isRereadIssue(item) {
30906
+ return Boolean(item.code && REREAD_CODES.has(item.code));
30907
+ }
30908
+ function rereadTrigger(item) {
30909
+ if (item.code === "review-document-changed")
30910
+ return item.file;
30911
+ if (item.code === "review-dependency-changed" || item.code === "impacted-document-review-required") {
30912
+ return item.link ?? null;
30913
+ }
30914
+ return null;
30915
+ }
30916
+ function orderTriggers(file, triggers) {
30917
+ const rest = [...triggers].filter((path3) => path3 !== file).sort();
30918
+ return triggers.has(file) ? [file, ...rest] : rest;
30919
+ }
30920
+ function collectRereadTriggers(errors2) {
30921
+ const triggers = new Map;
30922
+ const rest = [];
30923
+ for (const item of errors2) {
30924
+ if (!isRereadIssue(item)) {
30925
+ rest.push(item);
30926
+ continue;
30927
+ }
30928
+ const current = triggers.get(item.file) ?? new Set;
30929
+ const trigger = rereadTrigger(item);
30930
+ if (trigger)
30931
+ current.add(trigger);
30932
+ triggers.set(item.file, current);
30933
+ }
30934
+ return { triggers, rest };
30935
+ }
30936
+ function printRereadDiagnostic(file, triggers) {
30937
+ console.log(`${file}: error: review required`);
30938
+ const changed = orderTriggers(file, triggers);
30939
+ if (changed.length > 0) {
30940
+ console.log(` changed: ${changed.join(", ")}`);
30941
+ return;
30942
+ }
30943
+ console.log(" changed: review dependency set");
30944
+ }
30945
+ function printRereadLists(errors2) {
30946
+ const { triggers, rest } = collectRereadTriggers(errors2);
30947
+ for (const file of [...triggers.keys()].sort()) {
30948
+ printRereadDiagnostic(file, triggers.get(file) ?? new Set);
30949
+ }
30950
+ return rest;
30951
+ }
30915
30952
  function printErrors(label, errors2) {
30916
30953
  console.log(`${label} failed:
30917
30954
  `);
30918
- for (const i of errors2) {
30919
- const linkPart = i.link ? ` (${i.link})` : "";
30920
- console.log(`- ${i.file}${linkPart}: ${i.message}`);
30955
+ const rest = printRereadLists(errors2);
30956
+ for (const item of rest) {
30957
+ console.log(`${item.file}: error: ${item.message}`);
30921
30958
  }
30922
30959
  return 1;
30923
30960
  }
@@ -30931,11 +30968,43 @@ function printTextReport(ctx) {
30931
30968
 
30932
30969
  // src/audit/core/review-proof.ts
30933
30970
  import { createHash } from "node:crypto";
30934
- import { existsSync as existsSync13, mkdirSync as mkdirSync2, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
30935
- import { dirname as dirname8, relative as relative6 } from "node:path";
30971
+ import { existsSync as existsSync14, mkdirSync as mkdirSync2, readFileSync as readFileSync12, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
30972
+ import { dirname as dirname8 } from "node:path";
30973
+
30974
+ // src/audit/core/repo-files.ts
30975
+ import { spawnSync as spawnSync2 } from "node:child_process";
30976
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
30977
+ import { join as join11 } from "node:path";
30978
+ function gitShow(root2, spec) {
30979
+ const proc = spawnSync2("git", ["show", spec], {
30980
+ cwd: root2,
30981
+ encoding: "utf8",
30982
+ maxBuffer: 20000000
30983
+ });
30984
+ if (proc.status !== 0)
30985
+ return null;
30986
+ return proc.stdout;
30987
+ }
30988
+ function readRepoText(root2, relPath2, source = "worktree") {
30989
+ const normalized = normalizeRelPath(relPath2);
30990
+ if (source === "index")
30991
+ return gitShow(root2, `:${normalized}`);
30992
+ const abs = join11(root2, normalized);
30993
+ if (!existsSync12(abs))
30994
+ return null;
30995
+ return readFileSync11(abs, "utf8");
30996
+ }
30997
+ function pathDiffersFromHead(root2, relPath2) {
30998
+ const normalized = normalizeRelPath(relPath2);
30999
+ const head = gitShow(root2, `HEAD:${normalized}`);
31000
+ const worktree = readRepoText(root2, normalized, "worktree");
31001
+ if (worktree === null)
31002
+ return head !== null;
31003
+ return worktree !== (head ?? "");
31004
+ }
30936
31005
 
30937
31006
  // src/audit/core/review-deps.ts
30938
- import { existsSync as existsSync12 } from "node:fs";
31007
+ import { existsSync as existsSync13 } from "node:fs";
30939
31008
  var REVIEW_DEPS_RE = /<!--\s*review-deps:\s*([^>]*?)-->/gi;
30940
31009
  var GLOB_MAGIC_RE = /[*?{[]/;
30941
31010
  function parseReviewDepsMarkers(content3) {
@@ -30967,7 +31036,7 @@ function resolveReviewDependencies(root2, patterns) {
30967
31036
  throw new Error(`Invalid review dependency path: ${pattern}`);
30968
31037
  }
30969
31038
  if (!isReviewDependencyGlob(pattern)) {
30970
- if (!existsSync12(`${root2}/${pattern}`)) {
31039
+ if (!existsSync13(`${root2}/${pattern}`)) {
30971
31040
  throw new Error(`Review dependency path is missing: ${pattern}`);
30972
31041
  }
30973
31042
  targets.add(pattern);
@@ -31059,19 +31128,19 @@ function parseLock(content3) {
31059
31128
  return null;
31060
31129
  }
31061
31130
  }
31062
- function loadLock(root2, relPath2) {
31063
- const abs = resolveWritePath(root2, relPath2);
31064
- if (!existsSync13(abs))
31131
+ function loadLock(root2, relPath2, source = "worktree") {
31132
+ const content3 = readRepoText(root2, relPath2, source);
31133
+ if (content3 === null)
31065
31134
  return null;
31066
- return parseLock(readFileSync11(abs, "utf8"));
31135
+ return parseLock(content3);
31067
31136
  }
31068
31137
  function hashDependencies(root2, targets) {
31069
31138
  const out = {};
31070
31139
  for (const target of targets) {
31071
31140
  const abs = resolveWritePath(root2, target);
31072
- if (!existsSync13(abs))
31141
+ if (!existsSync14(abs))
31073
31142
  throw new Error(`Cannot attest missing review dependency: ${target}`);
31074
- out[target] = hash(readFileSync11(abs, "utf8"));
31143
+ out[target] = hash(readFileSync12(abs, "utf8"));
31075
31144
  }
31076
31145
  return out;
31077
31146
  }
@@ -31100,7 +31169,7 @@ function commitWrites(root2, writes) {
31100
31169
  try {
31101
31170
  for (const [relPath2, content3] of writes) {
31102
31171
  const abs = resolveWritePath(root2, relPath2);
31103
- originals.set(relPath2, existsSync13(abs) ? readFileSync11(abs, "utf8") : null);
31172
+ originals.set(relPath2, existsSync14(abs) ? readFileSync12(abs, "utf8") : null);
31104
31173
  mkdirSync2(dirname8(abs), { recursive: true });
31105
31174
  writeFileSync3(abs, content3, "utf8");
31106
31175
  written.push(relPath2);
@@ -31131,7 +31200,7 @@ function attestDocuments(options) {
31131
31200
  if (proofPath) {
31132
31201
  const absProof = resolveWritePath(ctx.root, proofPath);
31133
31202
  lock = loadLock(ctx.root, proofPath);
31134
- if (existsSync13(absProof) && !lock) {
31203
+ if (existsSync14(absProof) && !lock) {
31135
31204
  throw new Error(`Review proof lockfile is malformed: ${proofPath}`);
31136
31205
  }
31137
31206
  lock ??= emptyLock();
@@ -31139,9 +31208,9 @@ function attestDocuments(options) {
31139
31208
  const writes = new Map;
31140
31209
  for (const relPath2 of selected) {
31141
31210
  const abs = resolveWritePath(ctx.root, relPath2);
31142
- if (!existsSync13(abs))
31211
+ if (!existsSync14(abs))
31143
31212
  throw new Error(`Cannot attest missing document: ${relPath2}`);
31144
- const original = readFileSync11(abs, "utf8");
31213
+ const original = readFileSync12(abs, "utf8");
31145
31214
  if (!DOC_META_RE.test(original)) {
31146
31215
  throw new Error(`Cannot attest ${relPath2}: missing doc-meta comment`);
31147
31216
  }
@@ -31191,7 +31260,13 @@ function validateEntry(input) {
31191
31260
  }));
31192
31261
  return issues;
31193
31262
  }
31194
- issues.push(...dependencyHashIssues({ root: ctx.root, relPath: relPath2, targets: currentTargets, entry }));
31263
+ issues.push(...dependencyHashIssues({
31264
+ root: ctx.root,
31265
+ relPath: relPath2,
31266
+ targets: currentTargets,
31267
+ entry,
31268
+ fileSource: ctx.fileSource
31269
+ }));
31195
31270
  return issues;
31196
31271
  }
31197
31272
  function documentProofIssues(relPath2, content3, entry) {
@@ -31225,16 +31300,17 @@ function currentDependencies(input) {
31225
31300
  }
31226
31301
  function dependencyHashIssues(input) {
31227
31302
  const { root: root2, relPath: relPath2, targets, entry } = input;
31303
+ const source = input.fileSource ?? "worktree";
31228
31304
  const issues = [];
31229
31305
  for (const target of targets) {
31230
- const abs = resolveWritePath(root2, target);
31231
- if (!existsSync13(abs)) {
31306
+ const content3 = readRepoText(root2, target, source);
31307
+ if (content3 === null) {
31232
31308
  issues.push(issue("review-proof", relPath2, {
31233
31309
  code: "review-dependency-missing",
31234
31310
  message: `recorded review dependency is missing: ${target}`,
31235
31311
  link: target
31236
31312
  }));
31237
- } else if (entry.reviewDependencies[target] !== hash(readFileSync11(abs, "utf8"))) {
31313
+ } else if (entry.reviewDependencies[target] !== hash(content3)) {
31238
31314
  issues.push(changedDependencyIssue(relPath2, target));
31239
31315
  }
31240
31316
  }
@@ -31244,8 +31320,9 @@ function runReviewProofRule(ctx) {
31244
31320
  if (!ctx.config.reviewProof)
31245
31321
  return [];
31246
31322
  const relLock = lockPath(ctx);
31247
- const absLock = resolveWritePath(ctx.root, relLock);
31248
- if (!existsSync13(absLock)) {
31323
+ const source = ctx.fileSource ?? "worktree";
31324
+ const lockContent = readRepoText(ctx.root, relLock, source);
31325
+ if (lockContent === null) {
31249
31326
  return [
31250
31327
  issue("review-proof", relLock, {
31251
31328
  code: "review-proof-lock-missing",
@@ -31253,7 +31330,7 @@ function runReviewProofRule(ctx) {
31253
31330
  })
31254
31331
  ];
31255
31332
  }
31256
- const lock = parseLock(readFileSync11(absLock, "utf8"));
31333
+ const lock = parseLock(lockContent);
31257
31334
  if (!lock) {
31258
31335
  return [
31259
31336
  issue("review-proof", relLock, {
@@ -31264,11 +31341,10 @@ function runReviewProofRule(ctx) {
31264
31341
  }
31265
31342
  const issues = [];
31266
31343
  for (const relPath2 of ctx.docMetaPaths) {
31267
- const abs = resolveWritePath(ctx.root, relPath2);
31268
- if (!existsSync13(abs))
31344
+ const content3 = readRepoText(ctx.root, relPath2, source);
31345
+ if (content3 === null)
31269
31346
  continue;
31270
- const content3 = readFileSync11(abs, "utf8");
31271
- const entry = lock.documents[normalizeRelPath(relative6(ctx.root, abs))];
31347
+ const entry = lock.documents[normalizeRelPath(relPath2)];
31272
31348
  if (!entry) {
31273
31349
  issues.push(issue("review-proof", relPath2, {
31274
31350
  code: "review-proof-missing",
@@ -31295,8 +31371,8 @@ function runBannedRule(ctx) {
31295
31371
  var bannedRule = { id: "banned", run: runBannedRule };
31296
31372
 
31297
31373
  // src/audit/rules/doc-meta.ts
31298
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "node:fs";
31299
- import { join as join11 } from "node:path";
31374
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
31375
+ import { join as join12 } from "node:path";
31300
31376
  function checkDocMetaBanner(relPath2, content3) {
31301
31377
  if (DOC_META_RE.test(content3))
31302
31378
  return null;
@@ -31351,10 +31427,10 @@ function runDocMetaRule(ctx) {
31351
31427
  const issues = [];
31352
31428
  const today = new Date;
31353
31429
  for (const relPath2 of ctx.docMetaPaths) {
31354
- const abs = join11(ctx.root, relPath2);
31355
- if (!existsSync14(abs))
31430
+ const abs = join12(ctx.root, relPath2);
31431
+ if (!existsSync15(abs))
31356
31432
  continue;
31357
- const content3 = readFileSync12(abs, "utf8");
31433
+ const content3 = readFileSync13(abs, "utf8");
31358
31434
  const banner = checkDocMetaBanner(relPath2, content3);
31359
31435
  if (banner) {
31360
31436
  issues.push(banner);
@@ -31382,7 +31458,7 @@ function runDocMetaRule(ctx) {
31382
31458
  var docMetaRule = { id: "doc-meta", run: runDocMetaRule };
31383
31459
 
31384
31460
  // src/audit/rules/links.ts
31385
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
31461
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
31386
31462
  import { dirname as dirname9, resolve as resolve5 } from "node:path";
31387
31463
  function resolveLink2(sourceFile, target) {
31388
31464
  const withoutAnchor = target.split("#")[0]?.split("?")[0] ?? "";
@@ -31406,13 +31482,13 @@ function checkAgentFile(input, resolved, relSource) {
31406
31482
  return null;
31407
31483
  }
31408
31484
  const agentPath = resolved.endsWith(".md") ? resolved : `${resolved}.md`;
31409
- if (existsSync15(agentPath))
31485
+ if (existsSync16(agentPath))
31410
31486
  return null;
31411
31487
  return issue("links", relSource, { message: "missing agent file", link: input.linkLabel });
31412
31488
  }
31413
31489
  function checkBrokenPath(ctx) {
31414
31490
  const { input, pathPart, resolved, relSource, relTarget } = ctx;
31415
- if (!(pathPart && !existsSync15(resolved)))
31491
+ if (!(pathPart && !existsSync16(resolved)))
31416
31492
  return null;
31417
31493
  return issue("links", relSource, {
31418
31494
  message: `broken link → ${relTarget}`,
@@ -31421,9 +31497,9 @@ function checkBrokenPath(ctx) {
31421
31497
  }
31422
31498
  function checkBrokenAnchor(ctx) {
31423
31499
  const { input, anchor, resolved, relSource, relTarget } = ctx;
31424
- if (!(anchor && existsSync15(resolved)))
31500
+ if (!(anchor && existsSync16(resolved)))
31425
31501
  return null;
31426
- const targetContent = readFileSync13(resolved, "utf8");
31502
+ const targetContent = readFileSync14(resolved, "utf8");
31427
31503
  const slugs = extractHeadingSlugs(targetContent, resolved);
31428
31504
  const anchorSlug = slugifyAnchor(anchor);
31429
31505
  if (slugs.has(anchorSlug))
@@ -31475,8 +31551,8 @@ function runLinksRule(ctx) {
31475
31551
  var linksRule = { id: "links", run: runLinksRule };
31476
31552
 
31477
31553
  // src/audit/rules/near-duplicate.ts
31478
- import { readFileSync as readFileSync14 } from "node:fs";
31479
- import { join as join12 } from "node:path";
31554
+ import { readFileSync as readFileSync15 } from "node:fs";
31555
+ import { join as join13 } from "node:path";
31480
31556
 
31481
31557
  // src/audit/core/ssot-fit.ts
31482
31558
  var DEFAULT_SSOT_OVERLAP_MIN = 0.35;
@@ -31764,7 +31840,7 @@ function runNearDuplicateRule(ctx) {
31764
31840
  const ignored = ignoredPairSet(ctx);
31765
31841
  const entries = eligibleEntries(ctx);
31766
31842
  const fingerprints = entries.map((e) => {
31767
- const content3 = readFileSync14(join12(ctx.root, e.path), "utf8");
31843
+ const content3 = readFileSync15(join13(ctx.root, e.path), "utf8");
31768
31844
  const tokens = tokenize2(bodyWithoutSsotNoise(content3));
31769
31845
  return {
31770
31846
  path: e.path,
@@ -31859,12 +31935,105 @@ function runProsePolicyRule(ctx) {
31859
31935
  }
31860
31936
  var prosePolicyRule = { id: "prose-policy", run: runProsePolicyRule };
31861
31937
 
31938
+ // src/audit/core/review-coverage.ts
31939
+ var DEFAULT_REVIEW_COVERAGE_INCLUDE = [
31940
+ "**/*.{ts,tsx,js,jsx,mjs,cjs,py}",
31941
+ "package.json",
31942
+ "project.json"
31943
+ ];
31944
+ var DEFAULT_REVIEW_COVERAGE_EXCLUDE = [
31945
+ "**/__tests__/**",
31946
+ "**/*.test.*",
31947
+ "**/*.spec.*",
31948
+ "**/fixtures/**",
31949
+ "templates/**",
31950
+ "**/dist/**",
31951
+ "**/node_modules/**",
31952
+ "**/.venv/**",
31953
+ "**/.git/**",
31954
+ ".skeleton/plugins/**"
31955
+ ];
31956
+ function reviewCoveragePatterns(config) {
31957
+ const configured = config.reviewCoverage;
31958
+ if (configured?.include && configured.include.length === 0) {
31959
+ return { include: [], exclude: [] };
31960
+ }
31961
+ return {
31962
+ include: configured?.include && configured.include.length > 0 ? configured.include : DEFAULT_REVIEW_COVERAGE_INCLUDE,
31963
+ exclude: [...DEFAULT_REVIEW_COVERAGE_EXCLUDE, ...configured?.exclude ?? []]
31964
+ };
31965
+ }
31966
+ function pathRequiresReviewCoverage(relPath2, config) {
31967
+ const { include, exclude } = reviewCoveragePatterns(config);
31968
+ if (include.length === 0)
31969
+ return false;
31970
+ const path3 = normalizeRelPath(relPath2);
31971
+ if (exclude.some((pattern) => matchesGlobScope(path3, pattern)))
31972
+ return false;
31973
+ return include.some((pattern) => matchesGlobScope(path3, pattern));
31974
+ }
31975
+ function collectReviewDependencyPatterns(input) {
31976
+ const patterns = new Set;
31977
+ const source = input.fileSource ?? "worktree";
31978
+ for (const abs of collectScanFiles(input.config, input.root, input.skillIndex)) {
31979
+ const rel = relPath(abs, input.root);
31980
+ const content3 = readRepoText(input.root, rel, source);
31981
+ if (content3 === null)
31982
+ continue;
31983
+ for (const pattern of reviewDependencyPatterns(content3))
31984
+ patterns.add(pattern);
31985
+ }
31986
+ return [...patterns].sort();
31987
+ }
31988
+ function pathHasReviewOwner(relPath2, patterns) {
31989
+ return patterns.some((pattern) => reviewDependencyMatchesPath(pattern, relPath2));
31990
+ }
31991
+ function collectReviewCoverageFiles(root2, config) {
31992
+ const { include, exclude } = reviewCoveragePatterns(config);
31993
+ if (include.length === 0)
31994
+ return [];
31995
+ const files = new Set;
31996
+ for (const pattern of include) {
31997
+ for (const match of globSync(pattern, {
31998
+ cwd: root2,
31999
+ onlyFiles: true,
32000
+ dot: true,
32001
+ ignore: exclude
32002
+ })) {
32003
+ const rel = normalizeRelPath(match);
32004
+ if (exclude.some((item) => matchesGlobScope(rel, item)))
32005
+ continue;
32006
+ files.add(rel);
32007
+ }
32008
+ }
32009
+ return [...files].sort();
32010
+ }
32011
+
32012
+ // src/audit/rules/review-coverage.ts
32013
+ function runReviewCoverageRule(ctx) {
32014
+ const patterns = collectReviewDependencyPatterns({
32015
+ root: ctx.root,
32016
+ config: ctx.config,
32017
+ skillIndex: ctx.skillIndex,
32018
+ fileSource: ctx.fileSource
32019
+ });
32020
+ return collectReviewCoverageFiles(ctx.root, ctx.config).filter((path3) => !pathHasReviewOwner(path3, patterns)).map((path3) => issue("review-coverage", path3, {
32021
+ code: "review-coverage-gap",
32022
+ message: "file has no owning document; add a review-deps path or glob on the paper that describes it"
32023
+ }));
32024
+ }
32025
+ var reviewCoverageRule = {
32026
+ id: "review-coverage",
32027
+ global: true,
32028
+ run: runReviewCoverageRule
32029
+ };
32030
+
31862
32031
  // src/audit/rules/review-deps.ts
31863
- import { relative as relative7 } from "node:path";
32032
+ import { relative as relative6 } from "node:path";
31864
32033
  function runReviewDepsRule(ctx) {
31865
32034
  const issues = [];
31866
32035
  for (const abs of collectScanFiles(ctx.config, ctx.root, ctx.skillIndex)) {
31867
- const path3 = normalizeRelPath(relative7(ctx.root, abs));
32036
+ const path3 = normalizeRelPath(relative6(ctx.root, abs));
31868
32037
  for (const marker of parseReviewDepsMarkers(readFileContent(abs))) {
31869
32038
  issues.push(...validateMarker(ctx.root, path3, marker.paths));
31870
32039
  }
@@ -31925,16 +32094,16 @@ function runScanRootsRule(ctx) {
31925
32094
  var scanRootsRule = { id: "scan-roots", run: runScanRootsRule };
31926
32095
 
31927
32096
  // src/audit/rules/skill-index.ts
31928
- import { existsSync as existsSync16, readdirSync as readdirSync2, readFileSync as readFileSync15 } from "node:fs";
31929
- import { join as join13, relative as relative8 } from "node:path";
32097
+ import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync as readFileSync16 } from "node:fs";
32098
+ import { join as join14, relative as relative7 } from "node:path";
31930
32099
  function walkSkillMarkdown(dir) {
31931
32100
  const files = [];
31932
- if (!existsSync16(dir))
32101
+ if (!existsSync17(dir))
31933
32102
  return files;
31934
32103
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
31935
32104
  if (entry.name.startsWith("."))
31936
32105
  continue;
31937
- const fullPath = join13(dir, entry.name);
32106
+ const fullPath = join14(dir, entry.name);
31938
32107
  if (entry.isDirectory()) {
31939
32108
  files.push(...walkSkillMarkdown(fullPath));
31940
32109
  continue;
@@ -31960,8 +32129,8 @@ function parseReadmeTaxonomySlugs(content3) {
31960
32129
  }
31961
32130
  function scanFileForSkillLinks(ctx, filePath, index2) {
31962
32131
  const issues = [];
31963
- const rel = relative8(ctx.root, filePath).replace(/\\/g, "/");
31964
- const content3 = readFileSync15(filePath, "utf8");
32132
+ const rel = relative7(ctx.root, filePath).replace(/\\/g, "/");
32133
+ const content3 = readFileSync16(filePath, "utf8");
31965
32134
  for (const match of content3.matchAll(SKILL_LINK_RE)) {
31966
32135
  const slug2 = match[1];
31967
32136
  if (!slug2)
@@ -31974,14 +32143,14 @@ function scanFileForSkillLinks(ctx, filePath, index2) {
31974
32143
  }
31975
32144
  function taxonomyIssuesForReadme(input) {
31976
32145
  const { ctx, index: index2, skillRoot, diskSlugs, nonPublic } = input;
31977
- const readmePath = join13(ctx.root, skillRoot.relPath, "README.md");
31978
- if (!existsSync16(readmePath))
32146
+ const readmePath = join14(ctx.root, skillRoot.relPath, "README.md");
32147
+ if (!existsSync17(readmePath))
31979
32148
  return [];
31980
- const readme = readFileSync15(readmePath, "utf8");
32149
+ const readme = readFileSync16(readmePath, "utf8");
31981
32150
  if (!readme.includes("## Taxonomy"))
31982
32151
  return [];
31983
32152
  const taxonomySlugs = parseReadmeTaxonomySlugs(readme);
31984
- const nestedSlugs = diskSlugs.filter((slug2) => existsSync16(join13(ctx.root, skillRoot.relPath, slug2, "SKILL.md")));
32153
+ const nestedSlugs = diskSlugs.filter((slug2) => existsSync17(join14(ctx.root, skillRoot.relPath, slug2, "SKILL.md")));
31985
32154
  const foreign = new Set(index2.foreignSlugs);
31986
32155
  const publicSlugs = nestedSlugs.filter((slug2) => !(nonPublic.has(slug2) || foreign.has(slug2)));
31987
32156
  const relReadme = `${skillRoot.relPath}/README.md`;
@@ -32014,10 +32183,10 @@ function slugsForRoot(skillRoot, index2, owned) {
32014
32183
  function auditSkillRoot(input) {
32015
32184
  const { ctx, index: index2, skillRoot, owned } = input;
32016
32185
  const issues = [];
32017
- const base = skillRoot.kind === "nested" ? join13(ctx.root, skillRoot.relPath) : ctx.root;
32186
+ const base = skillRoot.kind === "nested" ? join14(ctx.root, skillRoot.relPath) : ctx.root;
32018
32187
  for (const slug2 of slugsForRoot(skillRoot, index2, owned)) {
32019
- const skillDir = join13(base, slug2);
32020
- if (!existsSync16(skillDir))
32188
+ const skillDir = join14(base, slug2);
32189
+ if (!existsSync17(skillDir))
32021
32190
  continue;
32022
32191
  for (const skillMd of walkSkillMarkdown(skillDir)) {
32023
32192
  issues.push(...scanFileForSkillLinks(ctx, skillMd, index2));
@@ -32067,8 +32236,8 @@ function runSsotRule(ctx) {
32067
32236
  var ssotRule = { id: "ssot", run: runSsotRule };
32068
32237
 
32069
32238
  // src/audit/rules/ssot-summary.ts
32070
- import { readFileSync as readFileSync16 } from "node:fs";
32071
- import { join as join14 } from "node:path";
32239
+ import { readFileSync as readFileSync17 } from "node:fs";
32240
+ import { join as join15 } from "node:path";
32072
32241
  function runSsotSummaryRule(ctx) {
32073
32242
  const overlapMin = ctx.config.docsLint?.ssotOverlapMin ?? DEFAULT_SSOT_OVERLAP_MIN;
32074
32243
  const margin = ctx.config.docsLint?.ssotBetterMatchMargin ?? DEFAULT_BETTER_MATCH_MARGIN;
@@ -32076,7 +32245,7 @@ function runSsotSummaryRule(ctx) {
32076
32245
  const files = ctx.ssotEntries.map((entry) => ({
32077
32246
  path: entry.path,
32078
32247
  summary: entry.summary,
32079
- content: readFileSync16(join14(ctx.root, entry.path), "utf8")
32248
+ content: readFileSync17(join15(ctx.root, entry.path), "utf8")
32080
32249
  }));
32081
32250
  return evaluateSsotFit(files, {
32082
32251
  overlapMin,
@@ -32097,6 +32266,7 @@ var docsRules = [
32097
32266
  { ...nearDuplicateRule, global: true },
32098
32267
  { ...ssotSummaryRule, global: true },
32099
32268
  { ...coverageGapsRule, global: true },
32269
+ { ...reviewCoverageRule, global: true },
32100
32270
  linksRule,
32101
32271
  docMetaRule,
32102
32272
  reviewProofRule,
@@ -32263,14 +32433,7 @@ function labelForSuite(suite) {
32263
32433
  function catalogStatusFor(root2, suite) {
32264
32434
  if (suite !== "docs" && suite !== "self")
32265
32435
  return "not-applicable";
32266
- if (process4.env.CI === "true")
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";
32436
+ return refreshLocalCatalog(root2);
32274
32437
  }
32275
32438
  function buildAuditResult(input) {
32276
32439
  const diagnostics = finalizeIssues(input.issues, input.options.strict);
@@ -32366,7 +32529,8 @@ async function evaluateAudit(options) {
32366
32529
  const base = createContext({
32367
32530
  root: options.root,
32368
32531
  paths: pathScoped ? options.paths : undefined,
32369
- includeExcludedSkillTrees: options.suite === "skills" && !pathScoped
32532
+ includeExcludedSkillTrees: options.suite === "skills" && !pathScoped,
32533
+ fileSource: options.fileSource
32370
32534
  });
32371
32535
  const loaded = await loadPlugins(base.root, base.config);
32372
32536
  const ctx = { ...base, policies: loaded.policies };
@@ -32408,11 +32572,6 @@ async function evaluateAudit(options) {
32408
32572
  for (const rule of executableRules) {
32409
32573
  issues.push(...rule.run(ctx));
32410
32574
  }
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
32575
  const executed = executableRules.map((rule) => rule.id);
32417
32576
  return buildAuditResult({
32418
32577
  options,
@@ -32441,175 +32600,15 @@ async function runAudit(options) {
32441
32600
  return printAuditResult(await evaluateAudit(options), options.json);
32442
32601
  }
32443
32602
 
32444
- // src/customize/resolve.ts
32445
- import { existsSync as existsSync17, readFileSync as readFileSync17 } from "node:fs";
32446
- import { basename as basename3, join as join15, relative as relative9 } from "node:path";
32447
- function customizeDir(root2) {
32448
- return join15(root2, REGISTRY_DIR_REL, "customize");
32449
- }
32450
- function customizePathForSlug(root2, slug2) {
32451
- return join15(customizeDir(root2), `${slug2}.md`);
32452
- }
32453
- function resolveSlugFile(root2, slug2) {
32454
- const direct = customizePathForSlug(root2, slug2);
32455
- if (existsSync17(direct)) {
32456
- return {
32457
- content: readFileSync17(direct, "utf8"),
32458
- path: normalizeRelPath(relative9(root2, direct))
32459
- };
32460
- }
32461
- return { content: null, path: null };
32462
- }
32463
- function alwaysIncludeBasenames(root2) {
32464
- try {
32465
- const config = loadConfig(root2);
32466
- return config.customize?.alwaysInclude ?? [];
32467
- } catch {
32468
- return [];
32469
- }
32470
- }
32471
- function readAlwaysInclude(root2, basenames, skipBasename) {
32472
- const parts = [];
32473
- const paths = [];
32474
- const dir = customizeDir(root2);
32475
- for (const name of basenames) {
32476
- const file = basename3(name);
32477
- if (skipBasename && file === skipBasename)
32478
- continue;
32479
- const abs = join15(dir, file);
32480
- if (!existsSync17(abs))
32481
- continue;
32482
- parts.push(readFileSync17(abs, "utf8").trimEnd());
32483
- paths.push(normalizeRelPath(relative9(root2, abs)));
32484
- }
32485
- return { parts, paths };
32486
- }
32487
- function resolveCustomize(root2, slug2) {
32488
- const slugFile = resolveSlugFile(root2, slug2);
32489
- const alwaysNames = alwaysIncludeBasenames(root2);
32490
- const skip = slugFile.path !== null && slugFile.path !== undefined ? basename3(slugFile.path) : null;
32491
- const always = readAlwaysInclude(root2, alwaysNames, skip);
32492
- const parts = [];
32493
- const included = [];
32494
- if (slugFile.content !== null && slugFile.content !== undefined && slugFile.content.trim().length > 0) {
32495
- parts.push(slugFile.content.trimEnd());
32496
- if (slugFile.path)
32497
- included.push(slugFile.path);
32498
- }
32499
- parts.push(...always.parts);
32500
- included.push(...always.paths);
32501
- if (parts.length === 0) {
32502
- return { slug: slug2, content: null, path: slugFile.path, included: [] };
32503
- }
32504
- return {
32505
- slug: slug2,
32506
- content: `${parts.join(`
32507
-
32508
- ---
32509
-
32510
- `)}
32511
- `,
32512
- path: slugFile.path ?? always.paths[0] ?? null,
32513
- included
32514
- };
32515
- }
32516
- function resolveCustomizeFromRoot(slug2, startDir) {
32517
- const root2 = findRepoRoot(startDir);
32518
- return resolveCustomize(root2, slug2);
32519
- }
32520
-
32521
- // src/hooks/run.ts
32522
- import process5 from "node:process";
32523
- function parsePayload(raw) {
32524
- if (!raw.trim())
32525
- return {};
32526
- return JSON.parse(raw);
32527
- }
32528
- function extractPath(payload) {
32529
- const input = payload.tool_input ?? payload.toolInput ?? {};
32530
- const candidates = [
32531
- input.path,
32532
- input.file_path,
32533
- input.filePath,
32534
- input.target_file,
32535
- input.targetFile
32536
- ];
32537
- for (const value of candidates) {
32538
- if (typeof value === "string" && value.trim())
32539
- return normalizeRelPath(value.trim());
32540
- }
32541
- return null;
32542
- }
32543
- function extractSkillSlug(payload) {
32544
- const tool = payload.tool_name ?? payload.toolName ?? "";
32545
- if (tool === "Skill" || tool === "skill") {
32546
- const input = payload.tool_input ?? payload.toolInput ?? {};
32547
- const slug2 = input.skill ?? input.slug ?? input.name;
32548
- if (typeof slug2 === "string" && slug2.trim())
32549
- return slug2.trim();
32550
- }
32551
- const path3 = extractPath(payload);
32552
- if (path3)
32553
- return slugFromPath(path3, process5.cwd());
32554
- return null;
32555
- }
32556
- function cursorResponse(content3) {
32557
- return JSON.stringify({ additional_context: content3 });
32558
- }
32559
- function claudeResponse(content3) {
32560
- return JSON.stringify({
32561
- hookSpecificOutput: {
32562
- additionalContext: content3
32563
- }
32564
- });
32565
- }
32566
- function codexResponse(content3) {
32567
- return JSON.stringify({ additionalContext: content3 });
32568
- }
32569
- function formatResponse(payload, content3) {
32570
- const tool = payload.tool_name ?? payload.toolName ?? "";
32571
- if (tool === "read_file")
32572
- return codexResponse(content3);
32573
- if (payload.hook_event_name?.toLowerCase().includes("claude"))
32574
- return claudeResponse(content3);
32575
- if (tool === "Read" && payload.hook_event_name === "postToolUse")
32576
- return cursorResponse(content3);
32577
- if (tool === "Read" || tool === "Skill")
32578
- return claudeResponse(content3);
32579
- return cursorResponse(content3);
32580
- }
32581
- function runCustomizeHook(raw) {
32582
- try {
32583
- const payload = parsePayload(raw);
32584
- const slug2 = extractSkillSlug(payload);
32585
- if (!slug2)
32586
- return "{}";
32587
- const resolved = resolveCustomizeFromRoot(slug2);
32588
- if (!resolved.content)
32589
- return "{}";
32590
- const from = resolved.included.length > 0 ? resolved.included.join(", ") : resolved.path ?? ".skeleton/customize";
32591
- const prefix = `
32592
-
32593
- ---
32594
- Customize override for /${slug2} (from ${from}):
32595
-
32596
- `;
32597
- return formatResponse(payload, prefix + resolved.content);
32598
- } catch (error) {
32599
- console.error(`customize hook error: ${error}`);
32600
- return "{}";
32601
- }
32602
- }
32603
-
32604
32603
  // src/init/init.ts
32605
- import { spawnSync as spawnSync2 } from "node:child_process";
32606
- import { copyFileSync, existsSync as existsSync21, mkdirSync as mkdirSync4, readFileSync as readFileSync19 } from "node:fs";
32604
+ import { spawnSync as spawnSync3 } from "node:child_process";
32605
+ import { copyFileSync, existsSync as existsSync21, mkdirSync as mkdirSync3, readFileSync as readFileSync20 } from "node:fs";
32607
32606
  import { join as join19 } from "node:path";
32608
- import process6 from "node:process";
32607
+ import process4 from "node:process";
32609
32608
 
32610
- // src/init/merge-hooks.ts
32611
- import { existsSync as existsSync20, mkdirSync as mkdirSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync4 } from "node:fs";
32612
- import { dirname as dirname12, join as join18 } from "node:path";
32609
+ // src/init/merge-precommit.ts
32610
+ import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync4 } from "node:fs";
32611
+ import { join as join17 } from "node:path";
32613
32612
 
32614
32613
  // src/init/package-paths.ts
32615
32614
  import { existsSync as existsSync18 } from "node:fs";
@@ -32632,279 +32631,45 @@ function resolveTemplatesDir() {
32632
32631
  return dir;
32633
32632
  }
32634
32633
 
32635
- // src/init/resolve-hook-command.ts
32636
- import { existsSync as existsSync19, realpathSync as realpathSync5 } from "node:fs";
32637
- import { createRequire as createRequire3 } from "node:module";
32638
- import { dirname as dirname11, join as join17, relative as relative10, resolve as resolve6 } from "node:path";
32639
- var PACKAGE_NAME = "@csark0812/skeleton";
32640
- var CLI_DIST = "dist/cli.js";
32641
- var PACKAGE_ROOT = resolvePackageRoot();
32642
- var DEV_HOOK_COMMAND = "bun src/cli.ts hook customize";
32643
- var FALLBACK_HOOK_COMMAND = `node node_modules/${PACKAGE_NAME}/${CLI_DIST} hook customize`;
32644
- function safeRealpath2(path3) {
32645
- try {
32646
- return realpathSync5(path3);
32647
- } catch {
32648
- return path3;
32649
- }
32650
- }
32651
- function toRepoRelative(cwd, absPath) {
32652
- const rel = relative10(safeRealpath2(cwd), safeRealpath2(absPath)).replace(/\\/g, "/");
32653
- return rel.startsWith("..") ? absPath.replace(/\\/g, "/") : rel;
32654
- }
32655
- function tryResolvePublishedCli(cwd) {
32656
- try {
32657
- const req = createRequire3(join17(cwd, "package.json"));
32658
- return req.resolve(`${PACKAGE_NAME}/${CLI_DIST}`);
32659
- } catch {
32660
- return null;
32661
- }
32662
- }
32663
- function walkNodeModulesCli(cwd) {
32664
- let dir = cwd;
32665
- while (true) {
32666
- const candidate = join17(dir, "node_modules", PACKAGE_NAME, CLI_DIST);
32667
- if (existsSync19(candidate))
32668
- return candidate;
32669
- const parent = dirname11(dir);
32670
- if (parent === dir)
32671
- break;
32672
- dir = parent;
32673
- }
32674
- return null;
32675
- }
32676
- function isInsidePackageRoot(cwd) {
32677
- const rel = relative10(PACKAGE_ROOT, resolve6(cwd)).replace(/\\/g, "/");
32678
- return rel === "" || !(rel.startsWith("..") || rel.startsWith("/"));
32679
- }
32680
- function nodeCliHookCommand(cliPath) {
32681
- return `node ${cliPath} hook customize`;
32682
- }
32683
- function resolveHookCommand(cwd) {
32684
- if (isInsidePackageRoot(cwd))
32685
- return DEV_HOOK_COMMAND;
32686
- const published = tryResolvePublishedCli(cwd);
32687
- if (published)
32688
- return nodeCliHookCommand(toRepoRelative(cwd, published));
32689
- const hoisted = walkNodeModulesCli(cwd);
32690
- if (hoisted)
32691
- return nodeCliHookCommand(toRepoRelative(cwd, hoisted));
32692
- return FALLBACK_HOOK_COMMAND;
32693
- }
32694
- function isSkeletonHookCommand(command) {
32695
- if (!command)
32696
- return false;
32697
- if (/\bhook\s+customize\b/.test(command))
32698
- return true;
32699
- return /customize-on-skill-read\.(js|ts)\b/.test(command);
32700
- }
32701
-
32702
- // src/init/merge-hooks.ts
32634
+ // src/init/merge-precommit.ts
32703
32635
  var TEMPLATES_DIR = resolveTemplatesDir();
32704
- function identityKey(platform, event, matcher) {
32705
- return `skeleton:customize:${platform}:${event}:${matcher}`;
32706
- }
32707
- function loadFragment(name, hookCommand) {
32708
- const raw = readFileSync18(join18(TEMPLATES_DIR, name), "utf8");
32709
- return JSON.parse(raw.replaceAll("{{HOOK_COMMAND}}", hookCommand));
32710
- }
32711
- function readJson(path3) {
32712
- if (!existsSync20(path3))
32713
- return null;
32714
- try {
32715
- return JSON.parse(readFileSync18(path3, "utf8"));
32716
- } catch (error) {
32717
- throw new Error(`Invalid JSON in ${path3}: ${error}`);
32718
- }
32719
- }
32720
- function writeJson(path3, value) {
32721
- mkdirSync3(dirname12(path3), { recursive: true });
32722
- writeFileSync4(path3, `${JSON.stringify(value, null, 2)}
32723
- `, "utf8");
32724
- }
32725
- function deepEqual(a, b) {
32726
- return JSON.stringify(a) === JSON.stringify(b);
32727
- }
32728
- function mergeExistingCursorHook(input) {
32729
- const { postToolUse, skeletonIdx, canonical, opts } = input;
32730
- const current = postToolUse[skeletonIdx];
32731
- const userEdited = current && !opts.forceHooks && (current.matcher !== canonical.matcher || !isSkeletonHookCommand(current.command));
32732
- if (userEdited) {
32733
- return {
32734
- platform: "cursor",
32735
- action: "conflict",
32736
- message: identityKey("cursor", "postToolUse", String(current.matcher ?? "Read"))
32737
- };
32738
- }
32739
- const extras = Object.fromEntries(Object.entries(current ?? {}).filter(([key]) => !["command", "matcher"].includes(key)));
32740
- const merged = { ...extras, ...canonical };
32741
- if (deepEqual(current, merged))
32742
- return { platform: "cursor", action: "skipped" };
32743
- postToolUse[skeletonIdx] = merged;
32744
- return null;
32745
- }
32746
- function persistCursorHooks(input) {
32747
- const { targetPath, existing, hooks, postToolUse, skeletonIdx } = input;
32748
- const next = {
32749
- ...existing,
32750
- version: existing.version ?? 1,
32751
- hooks: { ...hooks, postToolUse }
32752
- };
32753
- if (deepEqual(existing, next))
32754
- return { platform: "cursor", action: "skipped" };
32755
- writeJson(targetPath, next);
32756
- return { platform: "cursor", action: skeletonIdx >= 0 ? "updated" : "added" };
32757
- }
32758
- function mergeCursorHooks(targetPath, fragment, opts) {
32759
- const existing = readJson(targetPath) ?? {};
32760
- const hooks = existing.hooks ?? {};
32761
- const postToolUse = [...hooks.postToolUse ?? []];
32762
- const incoming = fragment.hooks?.postToolUse?.[0];
32763
- if (!incoming)
32764
- return { platform: "cursor", action: "skipped" };
32765
- const skeletonIdx = postToolUse.findIndex((entry) => isSkeletonHookCommand(entry.command));
32766
- const canonical = { ...incoming, matcher: incoming.matcher ?? "Read" };
32767
- if (skeletonIdx >= 0) {
32768
- const conflict = mergeExistingCursorHook({ postToolUse, skeletonIdx, canonical, opts });
32769
- if (conflict)
32770
- return conflict;
32771
- } else {
32772
- postToolUse.push(canonical);
32773
- }
32774
- return persistCursorHooks({ targetPath, existing, hooks, postToolUse, skeletonIdx });
32775
- }
32776
- function mergeExistingNestedSkeletonHook(input) {
32777
- const { hooks, skeletonIdx, incomingHook, opts, platform, eventName, matcher } = input;
32778
- const current = hooks[skeletonIdx];
32779
- const userEdited = current && !opts.forceHooks && (current.type !== incomingHook.type || !isSkeletonHookCommand(current.command));
32780
- if (userEdited) {
32781
- return {
32782
- changed: false,
32783
- conflict: {
32784
- platform,
32785
- action: "conflict",
32786
- message: identityKey(platform, eventName, matcher)
32787
- }
32788
- };
32789
- }
32790
- const extras = Object.fromEntries(Object.entries(current ?? {}).filter(([key]) => !["type", "command"].includes(key)));
32791
- const merged = { ...extras, ...incomingHook };
32792
- if (!deepEqual(current, merged)) {
32793
- hooks[skeletonIdx] = merged;
32794
- return { changed: true };
32795
- }
32796
- return { changed: false };
32797
- }
32798
- function mergeIncomingHookGroup(args) {
32799
- const { group, incomingHook, eventHooks, opts, platform, eventName } = args;
32800
- const matcher = group.matcher ?? "";
32801
- const groupIdx = eventHooks.findIndex((g) => g.matcher === matcher);
32802
- if (groupIdx < 0) {
32803
- eventHooks.push({ ...group, hooks: [{ ...incomingHook }] });
32804
- return { changed: true };
32805
- }
32806
- const existingGroup = eventHooks[groupIdx];
32807
- const hooks = [...existingGroup?.hooks ?? []];
32808
- const skeletonIdx = hooks.findIndex((entry) => isSkeletonHookCommand(entry.command));
32809
- if (skeletonIdx >= 0) {
32810
- const result = mergeExistingNestedSkeletonHook({
32811
- hooks,
32812
- skeletonIdx,
32813
- incomingHook,
32814
- opts,
32815
- platform,
32816
- eventName,
32817
- matcher
32818
- });
32819
- if (result.conflict)
32820
- return { changed: false, conflict: result.conflict };
32821
- eventHooks[groupIdx] = { ...existingGroup, matcher, hooks };
32822
- return { changed: result.changed };
32823
- }
32824
- hooks.push({ ...incomingHook });
32825
- eventHooks[groupIdx] = { ...existingGroup, matcher, hooks };
32826
- return { changed: true };
32827
- }
32828
- function mergeIncomingGroups(args) {
32829
- let changed = false;
32830
- for (const incomingGroup of args.incomingGroups) {
32831
- const incomingHook = incomingGroup.hooks?.[0];
32832
- if (!incomingHook)
32833
- continue;
32834
- const result = mergeIncomingHookGroup({
32835
- group: incomingGroup,
32836
- incomingHook,
32837
- eventHooks: args.eventHooks,
32838
- opts: args.opts,
32839
- platform: args.platform,
32840
- eventName: args.eventName
32841
- });
32842
- if (result.conflict)
32843
- return { changed: false, conflict: result.conflict };
32844
- if (result.changed)
32845
- changed = true;
32846
- }
32847
- return { changed };
32848
- }
32849
- function mergeNestedHooks(args) {
32850
- const { platform, targetPath, eventName } = args;
32851
- const existing = readJson(targetPath) ?? {};
32852
- const rootHooks = existing.hooks ?? {};
32853
- const eventHooks = [...rootHooks[eventName] ?? []];
32854
- const incomingGroups = args.fragment.hooks?.[eventName] ?? [];
32855
- const result = mergeIncomingGroups({
32856
- incomingGroups,
32857
- eventHooks,
32858
- opts: args.opts,
32859
- platform,
32860
- eventName
32861
- });
32862
- if (result.conflict)
32863
- return result.conflict;
32864
- if (!result.changed)
32865
- return { platform, action: "skipped" };
32866
- const next = {
32867
- ...existing,
32868
- hooks: { ...rootHooks, [eventName]: eventHooks }
32869
- };
32870
- writeJson(targetPath, next);
32871
- return { platform, action: "updated" };
32872
- }
32873
- function mergeHookConfigs(opts) {
32874
- const results = [];
32875
- const cursorPath = join18(opts.cwd, ".cursor/hooks.json");
32876
- const cursorFragment = loadFragment("cursor-hooks.fragment.json", opts.hookCommand);
32877
- results.push(mergeCursorHooks(cursorPath, cursorFragment, opts));
32878
- const claudePath = join18(opts.cwd, ".claude/settings.json");
32879
- const claudeFragment = loadFragment("claude-settings.fragment.json", opts.hookCommand);
32880
- results.push(mergeNestedHooks({
32881
- platform: "claude",
32882
- targetPath: claudePath,
32883
- fragment: claudeFragment,
32884
- eventName: "PostToolUse",
32885
- opts
32886
- }));
32887
- const codexPath = join18(opts.cwd, ".codex/hooks.json");
32888
- if (existsSync20(join18(opts.cwd, ".codex"))) {
32889
- const codexFragment = loadFragment("codex-hooks.fragment.json", opts.hookCommand);
32890
- results.push(mergeNestedHooks({
32891
- platform: "codex",
32892
- targetPath: codexPath,
32893
- fragment: codexFragment,
32894
- eventName: "PostToolUse",
32895
- opts
32896
- }));
32897
- } else {
32898
- results.push({ platform: "codex", action: "skipped", message: "missing .codex directory" });
32899
- }
32900
- return results;
32636
+ var PRECOMMIT_NAME = ".pre-commit-config.yaml";
32637
+ var HOOK_ID = "id: skeleton-validate-staged";
32638
+ var LOCAL_HOOK_BLOCK = ` - repo: local
32639
+ hooks:
32640
+ - id: skeleton-validate-staged
32641
+ name: skeleton validate changed (staged)
32642
+ entry: node node_modules/@csark0812/skeleton/dist/cli.js validate changed --staged
32643
+ language: system
32644
+ pass_filenames: false
32645
+ `;
32646
+ function mergePrecommitConfig(cwd) {
32647
+ const target = join17(cwd, PRECOMMIT_NAME);
32648
+ const template = readFileSync18(join17(TEMPLATES_DIR, "pre-commit-config.yaml"), "utf8");
32649
+ if (!existsSync19(target)) {
32650
+ writeFileSync4(target, template, "utf8");
32651
+ return "added";
32652
+ }
32653
+ const existing = readFileSync18(target, "utf8");
32654
+ if (existing.includes(HOOK_ID))
32655
+ return "skipped";
32656
+ const suffix = existing.includes("repos:") ? `
32657
+ ${LOCAL_HOOK_BLOCK}` : `
32658
+ ${template}`;
32659
+ writeFileSync4(target, `${existing.trimEnd()}${suffix}`, "utf8");
32660
+ return "updated";
32901
32661
  }
32662
+
32663
+ // src/init/merge-scripts.ts
32664
+ import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync5 } from "node:fs";
32665
+ import { join as join18 } from "node:path";
32666
+ var TEMPLATES_DIR2 = resolveTemplatesDir();
32902
32667
  function mergePackageJsonScripts(cwd) {
32903
32668
  const pkgPath = join18(cwd, "package.json");
32904
32669
  if (!existsSync20(pkgPath))
32905
32670
  return "skipped";
32906
- const fragment = JSON.parse(readFileSync18(join18(TEMPLATES_DIR, "package.json.scripts.fragment.json"), "utf8"));
32907
- const pkg = JSON.parse(readFileSync18(pkgPath, "utf8"));
32671
+ const fragment = JSON.parse(readFileSync19(join18(TEMPLATES_DIR2, "package.json.scripts.fragment.json"), "utf8"));
32672
+ const pkg = JSON.parse(readFileSync19(pkgPath, "utf8"));
32908
32673
  pkg.scripts ??= {};
32909
32674
  let changed = false;
32910
32675
  for (const [key, value] of Object.entries(fragment)) {
@@ -32915,7 +32680,7 @@ function mergePackageJsonScripts(cwd) {
32915
32680
  }
32916
32681
  if (!changed)
32917
32682
  return "skipped";
32918
- writeFileSync4(pkgPath, `${JSON.stringify(pkg, null, 2)}
32683
+ writeFileSync5(pkgPath, `${JSON.stringify(pkg, null, 2)}
32919
32684
  `, "utf8");
32920
32685
  return "updated";
32921
32686
  }
@@ -32958,25 +32723,24 @@ function skillsAddArgs(options = {}) {
32958
32723
  }
32959
32724
 
32960
32725
  // src/init/init.ts
32961
- var TEMPLATES_DIR2 = resolveTemplatesDir();
32726
+ var TEMPLATES_DIR3 = resolveTemplatesDir();
32962
32727
  function writeScaffold(cwd) {
32963
32728
  const skeletonDir2 = join19(cwd, ".skeleton");
32964
- mkdirSync4(skeletonDir2, { recursive: true });
32729
+ mkdirSync3(skeletonDir2, { recursive: true });
32965
32730
  let created = false;
32966
32731
  const tomlPath = join19(cwd, "skeleton.toml");
32967
32732
  const legacyYaml = join19(skeletonDir2, "config.yaml");
32968
32733
  if (!(existsSync21(tomlPath) || existsSync21(legacyYaml))) {
32969
- copyFileSync(join19(TEMPLATES_DIR2, "skeleton.toml"), tomlPath);
32734
+ copyFileSync(join19(TEMPLATES_DIR3, "skeleton.toml"), tomlPath);
32970
32735
  created = true;
32971
32736
  }
32972
- mkdirSync4(join19(skeletonDir2, "customize"), { recursive: true });
32973
32737
  return created ? "created" : "skipped";
32974
32738
  }
32975
32739
  function assertPackageResolvable(cwd) {
32976
32740
  const pkgPath = join19(cwd, "package.json");
32977
32741
  if (!existsSync21(pkgPath))
32978
32742
  return;
32979
- const pkg = JSON.parse(readFileSync19(pkgPath, "utf8"));
32743
+ const pkg = JSON.parse(readFileSync20(pkgPath, "utf8"));
32980
32744
  const hasDep = pkg.devDependencies?.["@csark0812/skeleton"] || pkg.dependencies?.["@csark0812/skeleton"];
32981
32745
  if (!hasDep) {
32982
32746
  try {
@@ -32987,23 +32751,13 @@ function assertPackageResolvable(cwd) {
32987
32751
  }
32988
32752
  }
32989
32753
  function runSkillsAdd(args, cwd) {
32990
- const result = spawnSync2("npx", args, {
32754
+ const result = spawnSync3("npx", args, {
32991
32755
  cwd,
32992
32756
  stdio: "inherit",
32993
32757
  shell: false
32994
32758
  });
32995
32759
  return result.status ?? 1;
32996
32760
  }
32997
- function logHookMergeResult(result) {
32998
- if (result.action === "conflict") {
32999
- console.error(`init: skipped ${result.platform} hook (user-edited) — re-run with --force-hooks to restore`);
33000
- return;
33001
- }
33002
- if (result.action === "added")
33003
- console.log(`init: added ${result.platform} customize hook`);
33004
- if (result.action === "updated")
33005
- console.log(`init: updated ${result.platform} customize hook`);
33006
- }
33007
32761
  function installSkillsIfRequested(options, cwd) {
33008
32762
  if (!(options.skills && !options.noSkills))
33009
32763
  return "skipped";
@@ -33016,30 +32770,34 @@ function installSkillsIfRequested(options, cwd) {
33016
32770
  return "installed";
33017
32771
  }
33018
32772
  function runInit(options = {}) {
33019
- const cwd = options.cwd ?? process6.cwd();
32773
+ const cwd = options.cwd ?? process4.cwd();
33020
32774
  assertPackageResolvable(cwd);
33021
32775
  const scaffold = writeScaffold(cwd);
33022
- const hookCommand = resolveHookCommand(cwd);
33023
- const hooks = mergeHookConfigs({ cwd, hookCommand, forceHooks: options.forceHooks });
33024
32776
  const scripts = mergePackageJsonScripts(cwd);
33025
- for (const result of hooks)
33026
- logHookMergeResult(result);
32777
+ const precommit = mergePrecommitConfig(cwd);
33027
32778
  if (scaffold === "created") {
33028
- console.log("init: wrote skeleton.toml (hooks optional — see docs)");
32779
+ console.log("init: wrote skeleton.toml");
33029
32780
  } else {
33030
32781
  console.log("init: skeleton.toml or .skeleton/ already present — skipped scaffold write");
33031
32782
  }
33032
32783
  if (scripts === "updated") {
33033
32784
  console.log("init: merged validate/audit scripts into package.json");
33034
32785
  }
32786
+ if (precommit === "added") {
32787
+ console.log("init: wrote .pre-commit-config.yaml (run pre-commit install once per machine)");
32788
+ } else if (precommit === "updated") {
32789
+ console.log("init: added skeleton validate hook to .pre-commit-config.yaml");
32790
+ }
33035
32791
  const skills = installSkillsIfRequested(options, cwd);
33036
- return { scaffold, hooks, scripts, skills };
32792
+ return { scaffold, scripts, skills, precommit };
33037
32793
  }
33038
32794
 
33039
32795
  // src/init/parse-args.ts
33040
- var SKELETON_INIT_FLAGS = new Set(["--force-hooks", "--skills", "--no-skills"]);
32796
+ var SKELETON_INIT_FLAGS = new Set(["--skills", "--no-skills"]);
33041
32797
  function parseInitArgs(argv) {
33042
- const forceHooks = argv.includes("--force-hooks");
32798
+ if (argv.includes("--force-hooks")) {
32799
+ throw new Error("init: --force-hooks was removed with customize hooks");
32800
+ }
33043
32801
  const noSkills = argv.includes("--no-skills");
33044
32802
  const skills = argv.includes("--skills");
33045
32803
  const skillsFlags = [];
@@ -33054,14 +32812,14 @@ function parseInitArgs(argv) {
33054
32812
  if (skills)
33055
32813
  skillsFlags.push(arg);
33056
32814
  }
33057
- return { forceHooks, skills, noSkills, skillsFlags };
32815
+ return { skills, noSkills, skillsFlags };
33058
32816
  }
33059
32817
 
33060
32818
  // src/plugins/build.ts
33061
- import { spawnSync as spawnSync3 } from "node:child_process";
32819
+ import { spawnSync as spawnSync4 } from "node:child_process";
33062
32820
  import { createHash as createHash2 } from "node:crypto";
33063
- import { existsSync as existsSync22, readFileSync as readFileSync20, writeFileSync as writeFileSync5 } from "node:fs";
33064
- import { basename as basename4, dirname as dirname13, resolve as resolve7 } from "node:path";
32821
+ import { existsSync as existsSync22, readFileSync as readFileSync21, writeFileSync as writeFileSync6 } from "node:fs";
32822
+ import { basename as basename3, dirname as dirname11, resolve as resolve6 } from "node:path";
33065
32823
  function parseBuildPluginArgs(argv) {
33066
32824
  let check = false;
33067
32825
  let entry;
@@ -33086,7 +32844,7 @@ function collectPluginEntries(root2, config, entry) {
33086
32844
  return (config.plugins ?? []).map((e) => resolvePluginTsPath(root2, e));
33087
32845
  }
33088
32846
  function localImportPaths(tsAbs, content3) {
33089
- const dir = dirname13(tsAbs);
32847
+ const dir = dirname11(tsAbs);
33090
32848
  const deps = [];
33091
32849
  const re = /(?:from\s+|import\s*\(\s*|import\s+)["'](\.[^"']+)["']/g;
33092
32850
  for (const match of content3.matchAll(re)) {
@@ -33096,9 +32854,9 @@ function localImportPaths(tsAbs, content3) {
33096
32854
  const candidates = [];
33097
32855
  if (spec.endsWith(".js")) {
33098
32856
  const withoutJs = spec.slice(0, -".js".length);
33099
- candidates.push(resolve7(dir, `${withoutJs}.ts`), resolve7(dir, spec), resolve7(dir, withoutJs, "index.ts"));
32857
+ candidates.push(resolve6(dir, `${withoutJs}.ts`), resolve6(dir, spec), resolve6(dir, withoutJs, "index.ts"));
33100
32858
  } else {
33101
- candidates.push(resolve7(dir, spec), resolve7(dir, `${spec}.ts`), resolve7(dir, `${spec}.js`), resolve7(dir, spec, "index.ts"));
32859
+ candidates.push(resolve6(dir, spec), resolve6(dir, `${spec}.ts`), resolve6(dir, `${spec}.js`), resolve6(dir, spec, "index.ts"));
33102
32860
  }
33103
32861
  for (const candidate of candidates) {
33104
32862
  if (existsSync22(candidate) && candidate.endsWith(".ts")) {
@@ -33118,8 +32876,8 @@ function sourceFingerprint(tsAbs, seen = new Set) {
33118
32876
  if (seen.has(abs))
33119
32877
  return;
33120
32878
  seen.add(abs);
33121
- const content3 = readFileSync20(abs, "utf8");
33122
- hash2.update(basename4(abs));
32879
+ const content3 = readFileSync21(abs, "utf8");
32880
+ hash2.update(basename3(abs));
33123
32881
  hash2.update("\x00");
33124
32882
  hash2.update(content3);
33125
32883
  hash2.update("\x00");
@@ -33131,7 +32889,7 @@ function sourceFingerprint(tsAbs, seen = new Set) {
33131
32889
  return hash2.digest("hex");
33132
32890
  }
33133
32891
  function writeStamp(tsAbs, mjsAbs) {
33134
- writeFileSync5(stampPathForMjs(mjsAbs), `${sourceFingerprint(tsAbs)}
32892
+ writeFileSync6(stampPathForMjs(mjsAbs), `${sourceFingerprint(tsAbs)}
33135
32893
  `, "utf8");
33136
32894
  }
33137
32895
  async function buildOne(tsAbs) {
@@ -33139,7 +32897,7 @@ async function buildOne(tsAbs) {
33139
32897
  if (!existsSync22(tsAbs)) {
33140
32898
  throw new Error(`Plugin source not found: ${tsAbs}`);
33141
32899
  }
33142
- const proc = spawnSync3("bun", ["build", tsAbs, "--target=node", "--format=esm", `--outfile=${mjsAbs}`, "--packages=external"], { encoding: "utf8" });
32900
+ const proc = spawnSync4("bun", ["build", tsAbs, "--target=node", "--format=esm", `--outfile=${mjsAbs}`, "--packages=external"], { encoding: "utf8" });
33143
32901
  if (proc.error) {
33144
32902
  const code3 = proc.error.code;
33145
32903
  if (code3 === "ENOENT") {
@@ -33166,7 +32924,7 @@ function checkOne(tsAbs) {
33166
32924
  if (!existsSync22(stampAbs)) {
33167
32925
  throw new Error(`Plugin stale: ${mjsAbs} has no fingerprint stamp. Run: skeleton build-plugin`);
33168
32926
  }
33169
- const expected = readFileSync20(stampAbs, "utf8").trim();
32927
+ const expected = readFileSync21(stampAbs, "utf8").trim();
33170
32928
  const actual = sourceFingerprint(tsAbs);
33171
32929
  if (expected !== actual) {
33172
32930
  throw new Error(`Plugin stale: ${mjsAbs} does not match ${tsAbs} (or local imports). Run: skeleton build-plugin`);
@@ -33195,15 +32953,15 @@ async function runBuildPlugin(options = {}) {
33195
32953
  }
33196
32954
 
33197
32955
  // src/validate/changed.ts
33198
- import { spawnSync as spawnSync5 } from "node:child_process";
33199
- import { existsSync as existsSync23, readFileSync as readFileSync21 } from "node:fs";
33200
- import { basename as basename5, extname as extname2, join as join20 } from "node:path";
32956
+ import { spawnSync as spawnSync6 } from "node:child_process";
32957
+ import { existsSync as existsSync23, readFileSync as readFileSync22 } from "node:fs";
32958
+ import { basename as basename4, extname as extname2, join as join20 } from "node:path";
33201
32959
 
33202
32960
  // src/validate/git-diff.ts
33203
- import { spawnSync as spawnSync4 } from "node:child_process";
32961
+ import { spawnSync as spawnSync5 } from "node:child_process";
33204
32962
  function gitDiffChangedFiles(options = {}) {
33205
32963
  const root2 = options.root ?? findRepoRoot();
33206
- const proc = spawnSync4("git", gitDiffArgs(options), { cwd: root2, encoding: "utf8" });
32964
+ const proc = spawnSync5("git", gitDiffArgs(options), { cwd: root2, encoding: "utf8" });
33207
32965
  if (proc.status !== 0) {
33208
32966
  throw new Error(proc.stderr?.trim() || "git diff failed");
33209
32967
  }
@@ -33239,6 +32997,32 @@ function addChangedPath(input) {
33239
32997
  });
33240
32998
  }
33241
32999
 
33000
+ // src/validate/staged.ts
33001
+ function stageRequiredIssue(file) {
33002
+ return issue("validate-changed", file, {
33003
+ code: "stage-required",
33004
+ message: "worktree differs from HEAD and is not staged; stage the attested document and review-lock.json when hash mode is on"
33005
+ });
33006
+ }
33007
+ function stageRequiredDiagnostics(input) {
33008
+ if (!input.staged)
33009
+ return [];
33010
+ const staged = new Set(input.stagedPaths.map(normalizeRelPath));
33011
+ const required = [...input.impactedDocuments];
33012
+ if (input.config.reviewProof) {
33013
+ required.push(input.config.reviewProof.lockfile ?? DEFAULT_REVIEW_LOCKFILE);
33014
+ }
33015
+ const issues = [];
33016
+ for (const path3 of [...new Set(required)].sort()) {
33017
+ if (staged.has(path3))
33018
+ continue;
33019
+ if (!pathDiffersFromHead(input.root, path3))
33020
+ continue;
33021
+ issues.push(stageRequiredIssue(path3));
33022
+ }
33023
+ return issues;
33024
+ }
33025
+
33242
33026
  // src/validate/changed.ts
33243
33027
  var DOC_EXTENSIONS = new Set([".md", ".mdc", ".yaml", ".yml"]);
33244
33028
  var POLICY_EXTENSIONS = new Set([".yaml", ".yml"]);
@@ -33251,7 +33035,7 @@ function isSkeletonYamlCandidate(normalized, ext) {
33251
33035
  if (!(normalized.startsWith(".skeleton/") || normalized.startsWith(".skeleton\\"))) {
33252
33036
  return false;
33253
33037
  }
33254
- const name = basename5(normalized).toLowerCase();
33038
+ const name = basename4(normalized).toLowerCase();
33255
33039
  if (name === "config.yaml" || name === "config.yml")
33256
33040
  return false;
33257
33041
  return true;
@@ -33276,7 +33060,7 @@ function bucketForDocPath(normalized, ctx) {
33276
33060
  function bucketFor(relPath2, ctx) {
33277
33061
  const normalized = normalizeRelPath(relPath2);
33278
33062
  const ext = extname2(normalized).toLowerCase();
33279
- const name = basename5(normalized);
33063
+ const name = basename4(normalized);
33280
33064
  if (CODE_EXTENSIONS.has(ext))
33281
33065
  return "code";
33282
33066
  if (COMMAND_CONFIG_NAMES.has(name))
@@ -33314,30 +33098,51 @@ function parseJsonContent(content3) {
33314
33098
  function validationIssue(code3, file, message) {
33315
33099
  return issue("validate-changed", file, { code: code3, message, severity: "error" });
33316
33100
  }
33317
- function validateJson(relPath2, root2) {
33318
- const abs = join20(root2, relPath2);
33101
+ function rereadValidationIssue(file, target) {
33102
+ return issue("validate-changed", file, {
33103
+ code: "impacted-document-review-required",
33104
+ 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",
33105
+ severity: "error",
33106
+ link: target
33107
+ });
33108
+ }
33109
+ function validateJson(relPath2, root2, fileSource) {
33110
+ const content3 = readRepoText(root2, relPath2, fileSource);
33111
+ if (content3 === null)
33112
+ return validationIssue("invalid-json", relPath2, "path not found");
33319
33113
  try {
33320
- parseJsonContent(readFileSync21(abs, "utf8"));
33114
+ parseJsonContent(content3);
33321
33115
  return null;
33322
33116
  } catch (error) {
33323
33117
  return validationIssue("invalid-json", relPath2, `invalid JSON: ${error}`);
33324
33118
  }
33325
33119
  }
33326
- function validatePolicy(relPath2, root2) {
33327
- const abs = join20(root2, relPath2);
33120
+ function validatePolicy(relPath2, root2, fileSource) {
33121
+ const content3 = readRepoText(root2, relPath2, fileSource);
33122
+ if (content3 === null)
33123
+ return validationIssue("invalid-policy", relPath2, "path not found");
33328
33124
  try {
33329
- loadPolicyFile(abs, readFileSync21(abs, "utf8"));
33125
+ loadPolicyFile(join20(root2, relPath2), content3);
33330
33126
  return null;
33331
33127
  } catch (error) {
33332
33128
  return validationIssue("invalid-policy", relPath2, `invalid policy: ${error}`);
33333
33129
  }
33334
33130
  }
33335
- function validateShell(relPath2, root2) {
33131
+ function validateShell(relPath2, root2, fileSource) {
33132
+ if (fileSource === "index") {
33133
+ const content3 = readRepoText(root2, relPath2, fileSource);
33134
+ if (content3 === null)
33135
+ return validationIssue("invalid-shell", relPath2, "path not found");
33136
+ const bash2 = spawnSync6("bash", ["-n"], { input: content3, encoding: "utf8" });
33137
+ if (bash2.status === 0)
33138
+ return null;
33139
+ return validationIssue("invalid-shell", relPath2, `shell syntax check failed: ${bash2.stderr}`);
33140
+ }
33336
33141
  const abs = join20(root2, relPath2);
33337
- const shellcheck = spawnSync5("shellcheck", [abs], { encoding: "utf8" });
33142
+ const shellcheck = spawnSync6("shellcheck", [abs], { encoding: "utf8" });
33338
33143
  if (shellcheck.status === 0)
33339
33144
  return null;
33340
- const bash = spawnSync5("bash", ["-n", abs], { encoding: "utf8" });
33145
+ const bash = spawnSync6("bash", ["-n", abs], { encoding: "utf8" });
33341
33146
  if (bash.status === 0)
33342
33147
  return null;
33343
33148
  return validationIssue("invalid-shell", relPath2, `shell syntax check failed: ${bash.stderr || shellcheck.stderr}`);
@@ -33361,7 +33166,7 @@ function packageManagerFromPackageJson(root2) {
33361
33166
  if (!existsSync23(pkgPath))
33362
33167
  return null;
33363
33168
  try {
33364
- const pkg = JSON.parse(readFileSync21(pkgPath, "utf8"));
33169
+ const pkg = JSON.parse(readFileSync22(pkgPath, "utf8"));
33365
33170
  const raw = pkg.packageManager?.split("@")[0];
33366
33171
  if (raw === "bun" || raw === "npm" || raw === "pnpm" || raw === "yarn")
33367
33172
  return raw;
@@ -33440,20 +33245,20 @@ function classifyPaths(ctx) {
33440
33245
  }
33441
33246
  return state;
33442
33247
  }
33443
- function validateLocalBuckets(buckets, root2) {
33248
+ function validateLocalBuckets(buckets, root2, fileSource) {
33444
33249
  const diagnostics = [];
33445
33250
  for (const relPath2 of buckets.shell) {
33446
- const found = validateShell(relPath2, root2);
33251
+ const found = validateShell(relPath2, root2, fileSource);
33447
33252
  if (found)
33448
33253
  diagnostics.push(found);
33449
33254
  }
33450
33255
  for (const relPath2 of buckets.json) {
33451
- const found = validateJson(relPath2, root2);
33256
+ const found = validateJson(relPath2, root2, fileSource);
33452
33257
  if (found)
33453
33258
  diagnostics.push(found);
33454
33259
  }
33455
33260
  for (const relPath2 of buckets.policy) {
33456
- const found = validatePolicy(relPath2, root2);
33261
+ const found = validatePolicy(relPath2, root2, fileSource);
33457
33262
  if (found)
33458
33263
  diagnostics.push(found);
33459
33264
  }
@@ -33466,16 +33271,24 @@ function discoverImpactedDocuments(input) {
33466
33271
  const changed = new Set(input.relPaths.map(normalizeRelPath));
33467
33272
  const impacted = [];
33468
33273
  for (const abs of collectScanFiles(input.config, input.root, input.skillIndex)) {
33469
- const document4 = impactedDocumentForPath(abs, input.root, changed);
33274
+ const document4 = impactedDocumentForPath({
33275
+ abs,
33276
+ root: input.root,
33277
+ changed,
33278
+ fileSource: input.fileSource
33279
+ });
33470
33280
  if (document4)
33471
33281
  impacted.push(document4);
33472
33282
  }
33473
33283
  return impacted.sort((a, b) => a.path.localeCompare(b.path));
33474
33284
  }
33475
- function impactedDocumentForPath(abs, root2, changed) {
33476
- const path3 = relPath(abs, root2);
33477
- const reviewDependencies = reviewDependencyPatterns(readFileSync21(abs, "utf8"));
33478
- const reasons = impactReasons(path3, reviewDependencies, changed);
33285
+ function impactedDocumentForPath(input) {
33286
+ const path3 = relPath(input.abs, input.root);
33287
+ const content3 = readRepoText(input.root, path3, input.fileSource);
33288
+ if (content3 === null)
33289
+ return null;
33290
+ const reviewDependencies = reviewDependencyPatterns(content3);
33291
+ const reasons = impactReasons(path3, reviewDependencies, input.changed);
33479
33292
  return reasons.length > 0 ? { path: path3, reviewDependencies, reasons } : null;
33480
33293
  }
33481
33294
  function impactReasons(path3, reviewDependencies, changed) {
@@ -33494,18 +33307,35 @@ function dateModeImpactDiagnostics(input) {
33494
33307
  return [];
33495
33308
  const changed = new Set(input.relPaths.map(normalizeRelPath));
33496
33309
  const today = formatLocalReviewDate(new Date);
33497
- const diagnostics = [];
33498
- for (const impacted of input.impactedDocuments) {
33499
- if (!impacted.reasons.some((reason) => reason.kind === "changed-review-dependency"))
33500
- continue;
33501
- const content3 = readFileSync21(join20(input.root, impacted.path), "utf8");
33502
- if (changed.has(impacted.path) && docMetaLastReviewed(content3) === today)
33310
+ return input.impactedDocuments.flatMap((impacted) => dateModeIssuesForDocument({
33311
+ impacted,
33312
+ changed,
33313
+ today,
33314
+ root: input.root,
33315
+ fileSource: input.fileSource
33316
+ }));
33317
+ }
33318
+ function dateModeIssuesForDocument(input) {
33319
+ const content3 = readRepoText(input.root, input.impacted.path, input.fileSource);
33320
+ if (!content3) {
33321
+ return input.impacted.reasons.filter((reason) => reason.kind === "changed-review-dependency" && reason.target).map((reason) => rereadValidationIssue(input.impacted.path, reason.target ?? ""));
33322
+ }
33323
+ if (input.changed.has(input.impacted.path) && docMetaLastReviewed(content3) === input.today) {
33324
+ return [];
33325
+ }
33326
+ const issues = [];
33327
+ for (const reason of input.impacted.reasons) {
33328
+ if (reason.kind !== "changed-review-dependency" || !reason.target)
33503
33329
  continue;
33504
- diagnostics.push(validationIssue("impacted-document-review-required", impacted.path, "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"));
33330
+ issues.push(rereadValidationIssue(input.impacted.path, reason.target));
33505
33331
  }
33506
- return diagnostics;
33332
+ return issues;
33333
+ }
33334
+ function uncoveredChangedPathDiagnostics(relPaths, config, patterns) {
33335
+ 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
33336
  }
33508
- function classificationDiagnostics(classification, root2, base) {
33337
+ function classificationDiagnostics(input) {
33338
+ const { classification, root: root2, base, coverageCandidateCount } = input;
33509
33339
  const diagnostics = [];
33510
33340
  for (const orphan of classification.orphans) {
33511
33341
  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 +33344,7 @@ function classificationDiagnostics(classification, root2, base) {
33514
33344
  diagnostics.push(validationIssue("missing-path", missing, "path not found"));
33515
33345
  }
33516
33346
  const audited = auditedPathCount(classification.buckets);
33517
- if ((classification.skipped.length > 0 || classification.buckets.code.length > 0) && audited === 0 && !base) {
33347
+ if ((classification.skipped.length > 0 || classification.buckets.code.length > 0) && audited === 0 && coverageCandidateCount === 0 && !base) {
33518
33348
  diagnostics.push(validationIssue("all-paths-skipped", ".", `all paths were skipped (code/config). This does not verify application code.
33519
33349
  ${codeValidationHint(root2)}`));
33520
33350
  }
@@ -33545,35 +33375,54 @@ function auditOptions(suite, root2, paths, extra = {}) {
33545
33375
  ...extra
33546
33376
  };
33547
33377
  }
33378
+ async function auditSkillChanges(input) {
33379
+ const sourceOpt = { fileSource: input.fileSource };
33380
+ if (input.skills.length === 0)
33381
+ return [];
33382
+ if (!input.base) {
33383
+ return [await evaluateAudit(auditOptions("skills", input.root, [], sourceOpt))];
33384
+ }
33385
+ return [
33386
+ await evaluateAudit(auditOptions("skills", input.root, input.skills, {
33387
+ pathScopedOnly: true,
33388
+ ...sourceOpt
33389
+ }))
33390
+ ];
33391
+ }
33392
+ async function auditPolicyChanges(input) {
33393
+ const sourceOpt = { fileSource: input.fileSource };
33394
+ const audits = [await evaluateAudit(auditOptions("docs", input.root, [], sourceOpt))];
33395
+ const skillPaths = listSkillMarkdownPaths(input.root, input.skillIndex);
33396
+ if (skillPaths.length === 0)
33397
+ return audits;
33398
+ audits.push(await evaluateAudit(auditOptions("skills", input.root, skillPaths, {
33399
+ pathScopedOnly: true,
33400
+ ...sourceOpt
33401
+ })));
33402
+ return audits;
33403
+ }
33548
33404
  async function evaluateBucketAudits(input) {
33549
- const { classification, root: root2, skillIndex, base } = input;
33405
+ const { classification, root: root2, skillIndex, base, fileSource } = input;
33550
33406
  const audits = [];
33551
- const diagnostics = validateLocalBuckets(classification.buckets, root2);
33407
+ const diagnostics = validateLocalBuckets(classification.buckets, root2, fileSource);
33408
+ const sourceOpt = { fileSource };
33552
33409
  if (base) {
33553
- audits.push(await evaluateAudit(auditOptions("self", root2, [], { globalOnly: true })));
33410
+ audits.push(await evaluateAudit(auditOptions("self", root2, [], { globalOnly: true, ...sourceOpt })));
33554
33411
  }
33555
33412
  if (classification.buckets.docs.length > 0) {
33556
- audits.push(await evaluateAudit(auditOptions("docs", root2, classification.buckets.docs, { pathScopedOnly: true })));
33557
- }
33558
- if (classification.buckets.skills.length > 0) {
33559
- if (!base) {
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
- }
33413
+ audits.push(await evaluateAudit(auditOptions("docs", root2, classification.buckets.docs, {
33414
+ pathScopedOnly: true,
33415
+ ...sourceOpt
33416
+ })));
33566
33417
  }
33567
- if (classification.buckets.policy.length > 0) {
33568
- if (!base) {
33569
- diagnostics.push(validationIssue("full-policy-audit-required", classification.buckets.policy[0] ?? ".", "policy YAML changes need full prose passes; run skeleton audit docs and skeleton audit skills"));
33570
- } else {
33571
- audits.push(await evaluateAudit(auditOptions("docs", root2, [])));
33572
- const skillPaths = listSkillMarkdownPaths(root2, skillIndex);
33573
- if (skillPaths.length > 0) {
33574
- audits.push(await evaluateAudit(auditOptions("skills", root2, skillPaths, { pathScopedOnly: true })));
33575
- }
33576
- }
33418
+ audits.push(...await auditSkillChanges({
33419
+ skills: classification.buckets.skills,
33420
+ root: root2,
33421
+ base,
33422
+ fileSource
33423
+ }));
33424
+ if (classification.buckets.policy.length > 0 && !diagnostics.some((item) => item.code === "invalid-policy")) {
33425
+ audits.push(...await auditPolicyChanges({ root: root2, skillIndex, fileSource }));
33577
33426
  }
33578
33427
  return { audits, diagnostics };
33579
33428
  }
@@ -33611,6 +33460,7 @@ function emptyClassification() {
33611
33460
  }
33612
33461
  async function evaluateValidateChanged(options = {}) {
33613
33462
  const root2 = options.root ?? findRepoRoot();
33463
+ refreshLocalCatalog(root2);
33614
33464
  const resolvedPaths = resolvePaths(options);
33615
33465
  const relPaths = resolvedPaths.paths;
33616
33466
  if (relPaths.length === 0) {
@@ -33618,6 +33468,7 @@ async function evaluateValidateChanged(options = {}) {
33618
33468
  }
33619
33469
  const config = loadConfig(root2);
33620
33470
  const skillIndex = buildSkillIndex(root2, config.skillOwnership);
33471
+ const fileSource = options.staged ? "index" : "worktree";
33621
33472
  let wiredPolicies;
33622
33473
  try {
33623
33474
  wiredPolicies = await collectWiredPolicyRelPaths(root2, config);
@@ -33638,37 +33489,58 @@ async function evaluateValidateChanged(options = {}) {
33638
33489
  skillIndex
33639
33490
  });
33640
33491
  classification.missing = classification.missing.filter((path3) => !resolvedPaths.deleted.has(path3));
33641
- const impactedDocuments = discoverImpactedDocuments({ relPaths, root: root2, config, skillIndex });
33492
+ const impactedDocuments = discoverImpactedDocuments({
33493
+ relPaths,
33494
+ root: root2,
33495
+ config,
33496
+ skillIndex,
33497
+ fileSource
33498
+ });
33642
33499
  for (const impacted of impactedDocuments) {
33643
33500
  if (!classification.buckets.docs.includes(impacted.path)) {
33644
33501
  classification.buckets.docs.push(impacted.path);
33645
33502
  }
33646
33503
  }
33647
33504
  classification.buckets.docs.sort();
33648
- const diagnostics = classificationDiagnostics(classification, root2, options.base);
33649
- if (diagnostics.length > 0) {
33650
- return resultFor({
33651
- options,
33652
- relPaths,
33653
- classification: publicClassification(classification),
33654
- impactedDocuments,
33655
- diagnostics
33656
- });
33657
- }
33505
+ const ownerPatterns = collectReviewDependencyPatterns({
33506
+ root: root2,
33507
+ config,
33508
+ skillIndex,
33509
+ fileSource
33510
+ });
33511
+ const livePaths = relPaths.filter((path3) => !resolvedPaths.deleted.has(path3));
33512
+ const coverageCandidateCount = livePaths.filter((path3) => pathRequiresReviewCoverage(path3, config)).length;
33513
+ const diagnostics = [
33514
+ ...classificationDiagnostics({
33515
+ classification,
33516
+ root: root2,
33517
+ base: options.base,
33518
+ coverageCandidateCount
33519
+ }),
33520
+ ...uncoveredChangedPathDiagnostics(livePaths, config, ownerPatterns),
33521
+ ...stageRequiredDiagnostics({
33522
+ staged: options.staged ?? false,
33523
+ stagedPaths: relPaths,
33524
+ impactedDocuments: impactedDocuments.map((item) => item.path),
33525
+ config,
33526
+ root: root2
33527
+ })
33528
+ ];
33658
33529
  const evaluated = await evaluateBucketAudits({
33659
33530
  classification,
33660
33531
  root: root2,
33661
33532
  skillIndex,
33662
- base: options.base
33533
+ base: options.base,
33534
+ fileSource
33663
33535
  });
33664
- evaluated.diagnostics.push(...dateModeImpactDiagnostics({ config, impactedDocuments, relPaths, root: root2 }));
33536
+ evaluated.diagnostics.push(...dateModeImpactDiagnostics({ config, impactedDocuments, relPaths, root: root2, fileSource }));
33665
33537
  return resultFor({
33666
33538
  options,
33667
33539
  relPaths,
33668
33540
  classification: publicClassification(classification),
33669
33541
  impactedDocuments,
33670
33542
  audits: evaluated.audits,
33671
- diagnostics: evaluated.diagnostics
33543
+ diagnostics: [...diagnostics, ...evaluated.diagnostics]
33672
33544
  });
33673
33545
  }
33674
33546
  function printValidateChangedResult(result) {
@@ -33679,16 +33551,20 @@ function printValidateChangedResult(result) {
33679
33551
  for (const path3 of result.classification.foreignSkills) {
33680
33552
  console.log(`validate changed: skipping foreign skill ${path3} (owned upstream; see skills-lock.json / skillOwnership)`);
33681
33553
  }
33682
- for (const audit of result.audits)
33683
- printAuditResult(audit, false);
33684
- for (const impacted of result.impactedDocuments) {
33685
- for (const reason of impacted.reasons) {
33686
- if (reason.kind !== "changed-review-dependency")
33687
- continue;
33688
- console.log(`validate changed: ${impacted.path} requires review (dependency ${reason.dependency} matched ${reason.target})`);
33554
+ if (result.ok) {
33555
+ for (const audit of result.audits)
33556
+ printAuditResult(audit, false);
33557
+ } else {
33558
+ for (const audit of result.audits.filter((item) => !item.ok)) {
33559
+ printAuditResult(audit, false);
33689
33560
  }
33690
33561
  }
33691
- for (const diagnostic of result.diagnostics) {
33562
+ const rereadDiagnostics = result.diagnostics.filter(isRereadIssue);
33563
+ const otherDiagnostics = result.diagnostics.filter((item) => !isRereadIssue(item));
33564
+ if (rereadDiagnostics.length > 0) {
33565
+ printReport(rereadDiagnostics, { label: "validate changed" });
33566
+ }
33567
+ for (const diagnostic of otherDiagnostics) {
33692
33568
  const path3 = diagnostic.file === "." ? "" : `${diagnostic.file}: `;
33693
33569
  console.error(`validate changed: ${path3}${diagnostic.message}`);
33694
33570
  }
@@ -33716,16 +33592,14 @@ function usage() {
33716
33592
  console.error(`Usage: skeleton <command>
33717
33593
 
33718
33594
  Commands:
33719
- init [--force-hooks] [--skills] [--no-skills] [skills add flags…]
33595
+ init [--skills] [--no-skills] [skills add flags…]
33720
33596
  audit docs|self|skills [--strict] [--json] [--paths=a,b] [--only=rule]
33721
33597
  [--fix[=doc-meta|anchors|ssot]] [--dry-run]
33722
33598
  [--confirm-reviewed (doc-meta only; requires --paths)]
33723
33599
  build-plugin [path] [--check]
33724
33600
  validate changed [paths…] [--staged] [--base <ref>]
33725
33601
  catalog [--check] [--strict] write or check .skeleton/catalog.md (gitignored)
33726
- customize resolve <slug> [--json]
33727
- hook customize (reads a host hook payload on stdin)
33728
- Note: \`register\` was removed — add a source-of-truth marker to the file and run \`skeleton catalog\`.`);
33602
+ Note: \`register\`, \`customize\`, and \`hook\` were removed.`);
33729
33603
  }
33730
33604
  function parseValidateChangedArgs(rest) {
33731
33605
  const paths = [];
@@ -33788,28 +33662,9 @@ function handleCatalog(argv) {
33788
33662
  }
33789
33663
  return runCatalogCli({ check: argv.includes("--check"), strict: argv.includes("--strict") });
33790
33664
  }
33791
- function handleCustomizeResolve(argv) {
33792
- const slug2 = argv[0];
33793
- const json = argv.includes("--json");
33794
- if (!slug2) {
33795
- console.error("customize resolve: slug required");
33796
- return 1;
33797
- }
33798
- const result = resolveCustomizeFromRoot(slug2);
33799
- if (json) {
33800
- console.log(JSON.stringify(result, null, 2));
33801
- } else if (result.content) {
33802
- process7.stdout.write(result.content);
33803
- }
33804
- return 0;
33805
- }
33806
- function handleHook(argv) {
33807
- if (argv[0] !== "customize") {
33808
- usage();
33809
- return 1;
33810
- }
33811
- process7.stdout.write(runCustomizeHook(readFileSync22(0, "utf8")));
33812
- return 0;
33665
+ function handleRemovedOverlay(command) {
33666
+ console.error(`${command}: removed overlay inject is gone. Edit the skill in its owning repo.`);
33667
+ return 1;
33813
33668
  }
33814
33669
  function handleInit(argv) {
33815
33670
  runInit(parseInitArgs(argv));
@@ -33830,9 +33685,8 @@ async function dispatchCommand(argv) {
33830
33685
  case "catalog":
33831
33686
  return handleCatalog(rest);
33832
33687
  case "customize":
33833
- return rest[0] === "resolve" ? handleCustomizeResolve(rest.slice(1)) : null;
33834
33688
  case "hook":
33835
- return handleHook(rest);
33689
+ return handleRemovedOverlay(command);
33836
33690
  case "init":
33837
33691
  return handleInit(rest);
33838
33692
  default:
@@ -33840,22 +33694,22 @@ async function dispatchCommand(argv) {
33840
33694
  }
33841
33695
  }
33842
33696
  async function main() {
33843
- const argv = process7.argv.slice(2);
33697
+ const argv = process5.argv.slice(2);
33844
33698
  const command = argv[0];
33845
33699
  if (!command || command === "--help" || command === "-h") {
33846
33700
  usage();
33847
- process7.exit(command ? 0 : 1);
33701
+ process5.exit(command ? 0 : 1);
33848
33702
  }
33849
33703
  try {
33850
33704
  const exitCode = await dispatchCommand(argv);
33851
33705
  if (exitCode === null) {
33852
33706
  usage();
33853
- process7.exit(1);
33707
+ process5.exit(1);
33854
33708
  }
33855
- process7.exit(exitCode);
33709
+ process5.exit(exitCode);
33856
33710
  } catch (error) {
33857
33711
  console.error(String(error));
33858
- process7.exit(1);
33712
+ process5.exit(1);
33859
33713
  }
33860
33714
  }
33861
33715
  main();