@csark0812/skeleton 2.0.0 → 3.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/README.md +7 -8
- package/dist/audit/config/types.d.ts +1 -8
- package/dist/audit/core/review-deps.d.ts +16 -0
- package/dist/audit/rules/index.d.ts +1 -1
- package/dist/audit/rules/review-deps.d.ts +9 -0
- package/dist/cli.js +657 -753
- package/dist/result-types.d.ts +0 -38
- package/package.json +1 -1
- package/schemas/config.schema.json +1 -12
- package/schemas/result.schema.json +4 -110
- package/templates/skeleton-init/skeleton.toml +1 -1
- package/dist/audit/core/code-fit.d.ts +0 -35
- package/dist/audit/rules/code-fit.d.ts +0 -13
package/dist/cli.js
CHANGED
|
@@ -15678,7 +15678,7 @@ var require_extend = __commonJS((exports, module) => {
|
|
|
15678
15678
|
});
|
|
15679
15679
|
|
|
15680
15680
|
// src/cli.ts
|
|
15681
|
-
import { readFileSync as
|
|
15681
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
15682
15682
|
import process9 from "node:process";
|
|
15683
15683
|
|
|
15684
15684
|
// src/audit/config/load.ts
|
|
@@ -30641,438 +30641,61 @@ function printTextReport(ctx) {
|
|
|
30641
30641
|
|
|
30642
30642
|
// src/audit/core/review-proof.ts
|
|
30643
30643
|
import { createHash } from "node:crypto";
|
|
30644
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync2, readFileSync as
|
|
30644
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync2, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
30645
30645
|
import { dirname as dirname8, relative as relative6 } from "node:path";
|
|
30646
30646
|
|
|
30647
|
-
// src/audit/core/
|
|
30648
|
-
import { existsSync as existsSync12
|
|
30649
|
-
|
|
30650
|
-
|
|
30651
|
-
|
|
30652
|
-
var DEFAULT_SSOT_OVERLAP_MIN = 0.35;
|
|
30653
|
-
var DEFAULT_BETTER_MATCH_MARGIN = 0.15;
|
|
30654
|
-
var STOP = new Set([
|
|
30655
|
-
"a",
|
|
30656
|
-
"an",
|
|
30657
|
-
"the",
|
|
30658
|
-
"and",
|
|
30659
|
-
"or",
|
|
30660
|
-
"of",
|
|
30661
|
-
"for",
|
|
30662
|
-
"to",
|
|
30663
|
-
"in",
|
|
30664
|
-
"on",
|
|
30665
|
-
"with",
|
|
30666
|
-
"this",
|
|
30667
|
-
"that",
|
|
30668
|
-
"is",
|
|
30669
|
-
"are",
|
|
30670
|
-
"be",
|
|
30671
|
-
"as",
|
|
30672
|
-
"by",
|
|
30673
|
-
"from",
|
|
30674
|
-
"at",
|
|
30675
|
-
"it",
|
|
30676
|
-
"its"
|
|
30677
|
-
]);
|
|
30678
|
-
var STEM_BLOCKLIST = new Set([
|
|
30679
|
-
"business",
|
|
30680
|
-
"analysis",
|
|
30681
|
-
"status",
|
|
30682
|
-
"process",
|
|
30683
|
-
"access",
|
|
30684
|
-
"address",
|
|
30685
|
-
"series",
|
|
30686
|
-
"species",
|
|
30687
|
-
"news",
|
|
30688
|
-
"means",
|
|
30689
|
-
"cross",
|
|
30690
|
-
"class",
|
|
30691
|
-
"glass",
|
|
30692
|
-
"less",
|
|
30693
|
-
"success",
|
|
30694
|
-
"progress",
|
|
30695
|
-
"express",
|
|
30696
|
-
"discuss",
|
|
30697
|
-
"focus",
|
|
30698
|
-
"bonus",
|
|
30699
|
-
"basis",
|
|
30700
|
-
"crisis",
|
|
30701
|
-
"thesis",
|
|
30702
|
-
"atlas",
|
|
30703
|
-
"canvas",
|
|
30704
|
-
"campus",
|
|
30705
|
-
"virus",
|
|
30706
|
-
"bus",
|
|
30707
|
-
"gas",
|
|
30708
|
-
"plus",
|
|
30709
|
-
"alias",
|
|
30710
|
-
"bias",
|
|
30711
|
-
"circus",
|
|
30712
|
-
"consensus",
|
|
30713
|
-
"census"
|
|
30714
|
-
]);
|
|
30715
|
-
function lightStem(token) {
|
|
30716
|
-
if (token.length < 4)
|
|
30717
|
-
return token;
|
|
30718
|
-
if (STEM_BLOCKLIST.has(token))
|
|
30719
|
-
return token;
|
|
30720
|
-
if (token.endsWith("ies") && token.length > 4) {
|
|
30721
|
-
return `${token.slice(0, -3)}y`;
|
|
30722
|
-
}
|
|
30723
|
-
if (token.endsWith("sses") || token.endsWith("ches") || token.endsWith("shes") || token.endsWith("xes")) {
|
|
30724
|
-
return token.slice(0, -2);
|
|
30725
|
-
}
|
|
30726
|
-
if (token.endsWith("s") && !token.endsWith("ss") && !token.endsWith("us") && !token.endsWith("is")) {
|
|
30727
|
-
return token.slice(0, -1);
|
|
30728
|
-
}
|
|
30729
|
-
return token;
|
|
30730
|
-
}
|
|
30731
|
-
function contentTokens(text5) {
|
|
30732
|
-
return text5.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 1 && !STOP.has(t)).map(lightStem);
|
|
30733
|
-
}
|
|
30734
|
-
function uniqueContentTokens(text5) {
|
|
30735
|
-
return [...new Set(contentTokens(text5))];
|
|
30736
|
-
}
|
|
30737
|
-
function stripCode2(content3) {
|
|
30738
|
-
return content3.replace(/```[\s\S]*?```/g, `
|
|
30739
|
-
`).replace(/`[^`\n]+`/g, " ");
|
|
30740
|
-
}
|
|
30741
|
-
function stripSsotAndMeta(content3) {
|
|
30742
|
-
return content3.replace(/<!--\s*source-of-truth:[\s\S]*?-->/gi, `
|
|
30743
|
-
`).replace(/^\s*source-of-truth:\s*.+$/gim, `
|
|
30744
|
-
`).replace(/^\s*\*\*Source of truth for\*\*\s*.+$/gim, `
|
|
30745
|
-
`).replace(/<!--\s*doc-meta:[\s\S]*?-->/gi, `
|
|
30746
|
-
`);
|
|
30747
|
-
}
|
|
30748
|
-
function extractH1(content3) {
|
|
30749
|
-
const prose = stripCode2(content3);
|
|
30750
|
-
const m = /^#\s+(.+)$/m.exec(prose);
|
|
30751
|
-
return m?.[1]?.trim() ?? "";
|
|
30752
|
-
}
|
|
30753
|
-
function extractLeadParagraph(content3) {
|
|
30754
|
-
const prose = stripSsotAndMeta(stripCode2(content3));
|
|
30755
|
-
const lines = prose.split(/\n/);
|
|
30756
|
-
const chunks = [];
|
|
30757
|
-
let buf = [];
|
|
30758
|
-
const flush = () => {
|
|
30759
|
-
const t = buf.join(" ").trim();
|
|
30760
|
-
if (t)
|
|
30761
|
-
chunks.push(t);
|
|
30762
|
-
buf = [];
|
|
30763
|
-
};
|
|
30764
|
-
for (const line of lines) {
|
|
30765
|
-
const trimmed = line.trim();
|
|
30766
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
30767
|
-
flush();
|
|
30768
|
-
continue;
|
|
30769
|
-
}
|
|
30770
|
-
if (/^[-*|]/.test(trimmed) && chunks.length === 0 && buf.length === 0) {
|
|
30771
|
-
buf.push(trimmed.replace(/^[-*|]+\s*/, ""));
|
|
30772
|
-
continue;
|
|
30773
|
-
}
|
|
30774
|
-
buf.push(trimmed);
|
|
30775
|
-
}
|
|
30776
|
-
flush();
|
|
30777
|
-
return chunks[0] ?? "";
|
|
30778
|
-
}
|
|
30779
|
-
function buildEvidenceText(content3) {
|
|
30780
|
-
const h1 = extractH1(content3);
|
|
30781
|
-
const lead = extractLeadParagraph(content3);
|
|
30782
|
-
const body = stripSsotAndMeta(stripCode2(content3));
|
|
30783
|
-
return [h1, lead, body].filter(Boolean).join(`
|
|
30784
|
-
|
|
30785
|
-
`);
|
|
30786
|
-
}
|
|
30787
|
-
function ssotEvidenceOverlap(summary, evidence) {
|
|
30788
|
-
const st = uniqueContentTokens(summary);
|
|
30789
|
-
if (st.length === 0)
|
|
30790
|
-
return 0;
|
|
30791
|
-
const ev = new Set(contentTokens(evidence));
|
|
30792
|
-
let hit = 0;
|
|
30793
|
-
for (const t of st) {
|
|
30794
|
-
if (ev.has(t))
|
|
30795
|
-
hit++;
|
|
30796
|
-
}
|
|
30797
|
-
return hit / st.length;
|
|
30798
|
-
}
|
|
30799
|
-
function longestSummaryPhrase(summary) {
|
|
30800
|
-
const toks = contentTokens(summary);
|
|
30801
|
-
if (toks.length >= 3)
|
|
30802
|
-
return toks.slice(0, 3);
|
|
30803
|
-
if (toks.length >= 2)
|
|
30804
|
-
return toks.slice(0, 2);
|
|
30805
|
-
return null;
|
|
30806
|
-
}
|
|
30807
|
-
function evidenceHasPhrase(evidence, phrase) {
|
|
30808
|
-
if (phrase.length === 0)
|
|
30809
|
-
return true;
|
|
30810
|
-
const ev = contentTokens(evidence);
|
|
30811
|
-
const needle = phrase.join(" ");
|
|
30812
|
-
for (let i = 0;i <= ev.length - phrase.length; i++) {
|
|
30813
|
-
if (ev.slice(i, i + phrase.length).join(" ") === needle)
|
|
30814
|
-
return true;
|
|
30815
|
-
}
|
|
30816
|
-
return false;
|
|
30817
|
-
}
|
|
30818
|
-
function evaluateSsotFit(files, options = {}) {
|
|
30819
|
-
const overlapMin = options.overlapMin ?? DEFAULT_SSOT_OVERLAP_MIN;
|
|
30820
|
-
const margin = options.betterMatchMargin ?? DEFAULT_BETTER_MATCH_MARGIN;
|
|
30821
|
-
const phraseCheck = options.phraseCheck !== false;
|
|
30822
|
-
const prepared = files.map((f) => {
|
|
30823
|
-
const evidence = buildEvidenceText(f.content);
|
|
30824
|
-
const overlap = ssotEvidenceOverlap(f.summary, evidence);
|
|
30825
|
-
const summaryToks = uniqueContentTokens(f.summary);
|
|
30826
|
-
return { ...f, evidence, overlap, summaryToks };
|
|
30827
|
-
});
|
|
30828
|
-
const issues = [];
|
|
30829
|
-
for (const row of prepared) {
|
|
30830
|
-
if (row.summaryToks.length < 2) {
|
|
30831
|
-
issues.push({
|
|
30832
|
-
kind: "short",
|
|
30833
|
-
path: row.path,
|
|
30834
|
-
message: `source-of-truth summary too short to verify against body ("${row.summary}")`
|
|
30835
|
-
});
|
|
30836
|
-
continue;
|
|
30837
|
-
}
|
|
30838
|
-
if (row.overlap < overlapMin) {
|
|
30839
|
-
let message = `source-of-truth summary weakly matches this paper (token overlap ${row.overlap.toFixed(2)} < ${overlapMin})`;
|
|
30840
|
-
if (phraseCheck) {
|
|
30841
|
-
const phrase = longestSummaryPhrase(row.summary);
|
|
30842
|
-
if (phrase && !evidenceHasPhrase(row.evidence, phrase)) {
|
|
30843
|
-
message += ` — key phrase "${phrase.join(" ")}" not found in H1/lead/body`;
|
|
30844
|
-
}
|
|
30845
|
-
}
|
|
30846
|
-
issues.push({ kind: "weak", path: row.path, message });
|
|
30847
|
-
let best = null;
|
|
30848
|
-
for (const other of prepared) {
|
|
30849
|
-
if (other.path === row.path)
|
|
30850
|
-
continue;
|
|
30851
|
-
const cross = ssotEvidenceOverlap(row.summary, other.evidence);
|
|
30852
|
-
if (cross < overlapMin)
|
|
30853
|
-
continue;
|
|
30854
|
-
if (cross < row.overlap + margin)
|
|
30855
|
-
continue;
|
|
30856
|
-
if (!best || cross > best.overlap)
|
|
30857
|
-
best = { path: other.path, overlap: cross };
|
|
30858
|
-
}
|
|
30859
|
-
if (best) {
|
|
30860
|
-
issues.push({
|
|
30861
|
-
kind: "better-match",
|
|
30862
|
-
path: row.path,
|
|
30863
|
-
otherPath: best.path,
|
|
30864
|
-
message: `source-of-truth fits ${best.path} better (overlap ${best.overlap.toFixed(2)} vs own ${row.overlap.toFixed(2)}). ` + `Try: (1) rewrite this SSOT to match this paper, (2) move/fix the marker onto ${best.path}, ` + `or (3) if these pages are really one topic, consider combining them`
|
|
30865
|
-
});
|
|
30866
|
-
}
|
|
30867
|
-
}
|
|
30868
|
-
}
|
|
30869
|
-
return issues;
|
|
30870
|
-
}
|
|
30871
|
-
|
|
30872
|
-
// src/audit/core/code-fit.ts
|
|
30873
|
-
var DEFAULT_CODE_FIT_OVERLAP_MIN = 0.03;
|
|
30874
|
-
var DEFAULT_CODE_FIT_SURFACE_CAP = 25;
|
|
30875
|
-
var CODE_FIT_RE = /<!--\s*code-fit:\s*([^>]*?)-->/gi;
|
|
30876
|
-
function parseCodeFitMarkers(content3) {
|
|
30647
|
+
// src/audit/core/review-deps.ts
|
|
30648
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
30649
|
+
var REVIEW_DEPS_RE = /<!--\s*review-deps:\s*([^>]*?)-->/gi;
|
|
30650
|
+
var GLOB_MAGIC_RE = /[*?{[]/;
|
|
30651
|
+
function parseReviewDepsMarkers(content3) {
|
|
30877
30652
|
const withoutCode = content3.replace(/```[\s\S]*?```/g, `
|
|
30878
30653
|
`).replace(/`[^`\n]+`/g, " ");
|
|
30879
30654
|
const out = [];
|
|
30880
|
-
for (const match of withoutCode.matchAll(
|
|
30881
|
-
const
|
|
30882
|
-
const
|
|
30883
|
-
|
|
30884
|
-
|
|
30655
|
+
for (const match of withoutCode.matchAll(REVIEW_DEPS_RE)) {
|
|
30656
|
+
const raw = (match[1] ?? "").trim();
|
|
30657
|
+
const pathsMatch = /\bpaths\s*=\s*([^\s]+)/i.exec(raw);
|
|
30658
|
+
const paths = pathsMatch?.[1] ? pathsMatch[1].split(",").map((path3) => normalizeRelPath(path3.trim())).filter(Boolean) : [];
|
|
30659
|
+
out.push({ paths, raw });
|
|
30885
30660
|
}
|
|
30886
30661
|
return out;
|
|
30887
30662
|
}
|
|
30888
|
-
function
|
|
30889
|
-
|
|
30890
|
-
if (!targetsMatch?.[1])
|
|
30891
|
-
return null;
|
|
30892
|
-
const targets = targetsMatch[1].split(",").map((t) => t.trim()).filter(Boolean);
|
|
30893
|
-
if (targets.length === 0)
|
|
30894
|
-
return null;
|
|
30895
|
-
const surfaceMatch = /\bsurface\s*=\s*([^\s]+)/i.exec(body);
|
|
30896
|
-
const surface = surfaceMatch?.[1] ? surfaceMatch[1].split(",").map((s) => s.trim()).filter(Boolean) : null;
|
|
30897
|
-
return { targets, surface, raw: body };
|
|
30663
|
+
function reviewDependencyPatterns(content3) {
|
|
30664
|
+
return [...new Set(parseReviewDepsMarkers(content3).flatMap((marker) => marker.paths))].sort();
|
|
30898
30665
|
}
|
|
30899
|
-
function
|
|
30900
|
-
return
|
|
30901
|
-
`).replace(/\/\/[^\n]*/g, `
|
|
30902
|
-
`).replace(/`(?:\\.|[^`\\])*`/g, " ").replace(/'(?:\\.|[^'\\])*'/g, " ").replace(/"(?:\\.|[^"\\])*"/g, " ");
|
|
30666
|
+
function isReviewDependencyGlob(pattern) {
|
|
30667
|
+
return GLOB_MAGIC_RE.test(pattern);
|
|
30903
30668
|
}
|
|
30904
|
-
function
|
|
30905
|
-
const
|
|
30906
|
-
|
|
30907
|
-
if (m[1])
|
|
30908
|
-
names.add(m[1]);
|
|
30909
|
-
}
|
|
30910
|
-
for (const m of source.matchAll(/\bexport\s+(?:async\s+)?(?:function|class|const|let|var|type|interface|enum)\s+([A-Za-z_$][\w$]*)/g)) {
|
|
30911
|
-
if (m[1])
|
|
30912
|
-
names.add(m[1]);
|
|
30913
|
-
}
|
|
30914
|
-
for (const m of source.matchAll(/\bexport\s+default\s+(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/g)) {
|
|
30915
|
-
if (m[1])
|
|
30916
|
-
names.add(m[1]);
|
|
30917
|
-
}
|
|
30918
|
-
for (const m of source.matchAll(/\bexport\s+default\s+class\s+([A-Za-z_$][\w$]*)/g)) {
|
|
30919
|
-
if (m[1])
|
|
30920
|
-
names.add(m[1]);
|
|
30921
|
-
}
|
|
30922
|
-
collectExportListNames(source, names);
|
|
30923
|
-
return [...names].sort();
|
|
30669
|
+
function isSafeReviewDependencyPath(value) {
|
|
30670
|
+
const normalized = normalizeRelPath(value);
|
|
30671
|
+
return value.length > 0 && value === normalized && normalized !== "." && normalized !== ".." && !normalized.startsWith("/") && !normalized.startsWith("../") && !normalized.includes("/../") && !/^[A-Za-z]:\//.test(normalized);
|
|
30924
30672
|
}
|
|
30925
|
-
function
|
|
30926
|
-
|
|
30927
|
-
|
|
30928
|
-
|
|
30929
|
-
|
|
30673
|
+
function resolveReviewDependencies(root2, patterns) {
|
|
30674
|
+
const targets = new Set;
|
|
30675
|
+
for (const pattern of patterns) {
|
|
30676
|
+
if (!isSafeReviewDependencyPath(pattern)) {
|
|
30677
|
+
throw new Error(`Invalid review dependency path: ${pattern}`);
|
|
30930
30678
|
}
|
|
30931
|
-
|
|
30932
|
-
}
|
|
30933
|
-
|
|
30934
|
-
if (!cleaned || cleaned === "type" || cleaned === "typeof")
|
|
30935
|
-
return;
|
|
30936
|
-
const asMatch = /^([\w$]+)\s+as\s+([\w$]+)$/.exec(cleaned);
|
|
30937
|
-
if (asMatch?.[2]) {
|
|
30938
|
-
names.add(asMatch[2]);
|
|
30939
|
-
return;
|
|
30940
|
-
}
|
|
30941
|
-
const typeAs = /^type\s+([\w$]+)(?:\s+as\s+([\w$]+))?$/.exec(cleaned);
|
|
30942
|
-
if (typeAs?.[1]) {
|
|
30943
|
-
names.add(typeAs[2] ?? typeAs[1]);
|
|
30944
|
-
return;
|
|
30945
|
-
}
|
|
30946
|
-
const id = /^([\w$]+)$/.exec(cleaned);
|
|
30947
|
-
if (id?.[1])
|
|
30948
|
-
names.add(id[1]);
|
|
30949
|
-
}
|
|
30950
|
-
function codeIdentifiers(source) {
|
|
30951
|
-
const stripped = stripCodeNoise(source);
|
|
30952
|
-
return uniqueContentTokens(stripped.replace(/[^A-Za-z0-9_$]+/g, " ").replace(/_/g, " "));
|
|
30953
|
-
}
|
|
30954
|
-
function identifierOverlap(docContent, codeSource) {
|
|
30955
|
-
const codeIds = new Set(codeIdentifiers(codeSource));
|
|
30956
|
-
if (codeIds.size === 0)
|
|
30957
|
-
return 1;
|
|
30958
|
-
const docToks = uniqueContentTokens(docContent);
|
|
30959
|
-
if (docToks.length === 0)
|
|
30960
|
-
return 0;
|
|
30961
|
-
let hit = 0;
|
|
30962
|
-
for (const t of docToks) {
|
|
30963
|
-
if (codeIds.has(t))
|
|
30964
|
-
hit++;
|
|
30965
|
-
}
|
|
30966
|
-
return hit / docToks.length;
|
|
30967
|
-
}
|
|
30968
|
-
function nameInDoc(name, docContent) {
|
|
30969
|
-
const docToks = new Set(contentTokens(docContent));
|
|
30970
|
-
const parts = uniqueContentTokens(name.replace(/_/g, " "));
|
|
30971
|
-
if (parts.length === 0) {
|
|
30972
|
-
return docToks.has(name.toLowerCase());
|
|
30973
|
-
}
|
|
30974
|
-
const asToken = contentTokens(name);
|
|
30975
|
-
if (asToken.some((t) => docToks.has(t)))
|
|
30976
|
-
return true;
|
|
30977
|
-
return parts.every((p) => docToks.has(p));
|
|
30978
|
-
}
|
|
30979
|
-
function nameExistsInTarget(name, autoSurface, source) {
|
|
30980
|
-
if (autoSurface.includes(name))
|
|
30981
|
-
return true;
|
|
30982
|
-
const ids = new Set(extractPublicSurface(source));
|
|
30983
|
-
if (ids.has(name))
|
|
30984
|
-
return true;
|
|
30985
|
-
const re = new RegExp(`\\b${escapeRegExp(name)}\\b`);
|
|
30986
|
-
return re.test(source);
|
|
30987
|
-
}
|
|
30988
|
-
function escapeRegExp(s) {
|
|
30989
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30990
|
-
}
|
|
30991
|
-
function pushCoverageGaps(issues, input, effective) {
|
|
30992
|
-
if (effective.length === 0)
|
|
30993
|
-
return;
|
|
30994
|
-
const missing = effective.filter((n) => !nameInDoc(n, input.docContent));
|
|
30995
|
-
if (missing.length === 0)
|
|
30996
|
-
return;
|
|
30997
|
-
issues.push({
|
|
30998
|
-
path: input.docPath,
|
|
30999
|
-
message: `code-fit coverage: doc does not mention ${missing.map((m) => `"${m}"`).join(", ")} (from ${input.target})`,
|
|
31000
|
-
link: input.target
|
|
31001
|
-
});
|
|
31002
|
-
}
|
|
31003
|
-
function pushLexicalGap(issues, input, source) {
|
|
31004
|
-
const overlapMin = input.options.overlapMin ?? DEFAULT_CODE_FIT_OVERLAP_MIN;
|
|
31005
|
-
const overlap = identifierOverlap(input.docContent, source);
|
|
31006
|
-
if (overlap + 0.000000001 >= overlapMin)
|
|
31007
|
-
return;
|
|
31008
|
-
issues.push({
|
|
31009
|
-
path: input.docPath,
|
|
31010
|
-
message: `code-fit lexical overlap ${(overlap * 100).toFixed(0)}% < ${(overlapMin * 100).toFixed(0)}% vs ${input.target}`,
|
|
31011
|
-
link: input.target
|
|
31012
|
-
});
|
|
31013
|
-
}
|
|
31014
|
-
function evaluateTarget(input) {
|
|
31015
|
-
const issues = [];
|
|
31016
|
-
const abs = join11(input.options.root, input.target);
|
|
31017
|
-
if (!existsSync12(abs)) {
|
|
31018
|
-
issues.push({
|
|
31019
|
-
path: input.docPath,
|
|
31020
|
-
message: `code-fit target missing: ${input.target}`,
|
|
31021
|
-
link: input.target
|
|
31022
|
-
});
|
|
31023
|
-
return issues;
|
|
31024
|
-
}
|
|
31025
|
-
const source = readFileSync11(abs, "utf8");
|
|
31026
|
-
const auto = extractPublicSurface(source);
|
|
31027
|
-
const cap = input.options.surfaceCap ?? DEFAULT_CODE_FIT_SURFACE_CAP;
|
|
31028
|
-
if (input.surfaceOverride === null && auto.length > cap) {
|
|
31029
|
-
issues.push({
|
|
31030
|
-
path: input.docPath,
|
|
31031
|
-
message: `code-fit auto-surface has ${auto.length} names (cap ${cap}); add surface=… to the marker for ${input.target}`,
|
|
31032
|
-
link: input.target
|
|
31033
|
-
});
|
|
31034
|
-
return issues;
|
|
31035
|
-
}
|
|
31036
|
-
if (input.surfaceOverride !== null) {
|
|
31037
|
-
for (const name of input.surfaceOverride) {
|
|
31038
|
-
if (!nameExistsInTarget(name, auto, source)) {
|
|
31039
|
-
issues.push({
|
|
31040
|
-
path: input.docPath,
|
|
31041
|
-
message: `code-fit surface name "${name}" not found in ${input.target}`,
|
|
31042
|
-
link: input.target
|
|
31043
|
-
});
|
|
30679
|
+
if (!isReviewDependencyGlob(pattern)) {
|
|
30680
|
+
if (!existsSync12(`${root2}/${pattern}`)) {
|
|
30681
|
+
throw new Error(`Review dependency path is missing: ${pattern}`);
|
|
31044
30682
|
}
|
|
31045
|
-
|
|
31046
|
-
}
|
|
31047
|
-
const effective = input.surfaceOverride !== null ? input.surfaceOverride : auto;
|
|
31048
|
-
pushCoverageGaps(issues, input, effective);
|
|
31049
|
-
pushLexicalGap(issues, input, source);
|
|
31050
|
-
return issues;
|
|
31051
|
-
}
|
|
31052
|
-
function evaluateCodeFitDoc(docPath, docContent, options) {
|
|
31053
|
-
const markers = parseCodeFitMarkers(docContent);
|
|
31054
|
-
if (markers.length === 0)
|
|
31055
|
-
return [];
|
|
31056
|
-
const issues = [];
|
|
31057
|
-
for (const marker of markers) {
|
|
31058
|
-
if (marker.targets.length === 0) {
|
|
31059
|
-
issues.push({
|
|
31060
|
-
path: docPath,
|
|
31061
|
-
message: "code-fit marker missing targets="
|
|
31062
|
-
});
|
|
30683
|
+
targets.add(pattern);
|
|
31063
30684
|
continue;
|
|
31064
30685
|
}
|
|
31065
|
-
for (const
|
|
31066
|
-
|
|
31067
|
-
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31071
|
-
|
|
31072
|
-
}));
|
|
30686
|
+
for (const match of globSync(pattern, {
|
|
30687
|
+
cwd: root2,
|
|
30688
|
+
onlyFiles: true,
|
|
30689
|
+
dot: true,
|
|
30690
|
+
ignore: [".git/**"]
|
|
30691
|
+
})) {
|
|
30692
|
+
targets.add(normalizeRelPath(match));
|
|
31073
30693
|
}
|
|
31074
30694
|
}
|
|
31075
|
-
return
|
|
30695
|
+
return { patterns: [...new Set(patterns)].sort(), targets: [...targets].sort() };
|
|
30696
|
+
}
|
|
30697
|
+
function reviewDependencyMatchesPath(pattern, relPath2) {
|
|
30698
|
+
return isReviewDependencyGlob(pattern) ? matchesGlobScope(relPath2, pattern) : normalizeRelPath(relPath2) === pattern;
|
|
31076
30699
|
}
|
|
31077
30700
|
|
|
31078
30701
|
// src/audit/core/review-proof.ts
|
|
@@ -31087,7 +30710,7 @@ function hash(content3) {
|
|
|
31087
30710
|
return `sha256:${createHash("sha256").update(content3, "utf8").digest("hex")}`;
|
|
31088
30711
|
}
|
|
31089
30712
|
function emptyLock() {
|
|
31090
|
-
return { version:
|
|
30713
|
+
return { version: 2, documents: {} };
|
|
31091
30714
|
}
|
|
31092
30715
|
function isRecord(value) {
|
|
31093
30716
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -31112,17 +30735,17 @@ function parseEntry(value) {
|
|
|
31112
30735
|
return null;
|
|
31113
30736
|
if (!isHash(value.documentHash))
|
|
31114
30737
|
return null;
|
|
31115
|
-
if (!isRecord(value.
|
|
30738
|
+
if (!isRecord(value.reviewDependencies))
|
|
31116
30739
|
return null;
|
|
31117
|
-
const
|
|
31118
|
-
for (const [target, targetHash] of Object.entries(value.
|
|
30740
|
+
const reviewDependencies = {};
|
|
30741
|
+
for (const [target, targetHash] of Object.entries(value.reviewDependencies)) {
|
|
31119
30742
|
if (!isSafeRepoPath(target))
|
|
31120
30743
|
return null;
|
|
31121
30744
|
if (!isHash(targetHash))
|
|
31122
30745
|
return null;
|
|
31123
|
-
|
|
30746
|
+
reviewDependencies[target] = targetHash;
|
|
31124
30747
|
}
|
|
31125
|
-
return { reviewedAt: value.reviewedAt, documentHash: value.documentHash,
|
|
30748
|
+
return { reviewedAt: value.reviewedAt, documentHash: value.documentHash, reviewDependencies };
|
|
31126
30749
|
}
|
|
31127
30750
|
function lockPath(ctx) {
|
|
31128
30751
|
return normalizeRelPath(ctx.config.reviewProof?.lockfile ?? DEFAULT_REVIEW_LOCKFILE);
|
|
@@ -31130,7 +30753,7 @@ function lockPath(ctx) {
|
|
|
31130
30753
|
function parseLock(content3) {
|
|
31131
30754
|
try {
|
|
31132
30755
|
const parsed = JSON.parse(content3);
|
|
31133
|
-
if (!isRecord(parsed) || parsed.version !==
|
|
30756
|
+
if (!isRecord(parsed) || parsed.version !== 2 || !isRecord(parsed.documents))
|
|
31134
30757
|
return null;
|
|
31135
30758
|
const documents = {};
|
|
31136
30759
|
for (const [path3, value] of Object.entries(parsed.documents)) {
|
|
@@ -31141,7 +30764,7 @@ function parseLock(content3) {
|
|
|
31141
30764
|
return null;
|
|
31142
30765
|
documents[path3] = entry;
|
|
31143
30766
|
}
|
|
31144
|
-
return { version:
|
|
30767
|
+
return { version: 2, documents };
|
|
31145
30768
|
} catch {
|
|
31146
30769
|
return null;
|
|
31147
30770
|
}
|
|
@@ -31150,18 +30773,15 @@ function loadLock(root2, relPath2) {
|
|
|
31150
30773
|
const abs = resolveWritePath(root2, relPath2);
|
|
31151
30774
|
if (!existsSync13(abs))
|
|
31152
30775
|
return null;
|
|
31153
|
-
return parseLock(
|
|
31154
|
-
}
|
|
31155
|
-
function codeTargets(content3) {
|
|
31156
|
-
return [...new Set(parseCodeFitMarkers(content3).flatMap((marker) => marker.targets))].sort();
|
|
30776
|
+
return parseLock(readFileSync11(abs, "utf8"));
|
|
31157
30777
|
}
|
|
31158
|
-
function
|
|
30778
|
+
function hashDependencies(root2, targets) {
|
|
31159
30779
|
const out = {};
|
|
31160
30780
|
for (const target of targets) {
|
|
31161
30781
|
const abs = resolveWritePath(root2, target);
|
|
31162
30782
|
if (!existsSync13(abs))
|
|
31163
|
-
throw new Error(`Cannot attest missing
|
|
31164
|
-
out[target] = hash(
|
|
30783
|
+
throw new Error(`Cannot attest missing review dependency: ${target}`);
|
|
30784
|
+
out[target] = hash(readFileSync11(abs, "utf8"));
|
|
31165
30785
|
}
|
|
31166
30786
|
return out;
|
|
31167
30787
|
}
|
|
@@ -31174,10 +30794,10 @@ function sortedLock(lock) {
|
|
|
31174
30794
|
documents[path3] = {
|
|
31175
30795
|
reviewedAt: entry.reviewedAt,
|
|
31176
30796
|
documentHash: entry.documentHash,
|
|
31177
|
-
|
|
30797
|
+
reviewDependencies: Object.fromEntries(Object.entries(entry.reviewDependencies).sort(([a], [b]) => a.localeCompare(b)))
|
|
31178
30798
|
};
|
|
31179
30799
|
}
|
|
31180
|
-
return { version:
|
|
30800
|
+
return { version: 2, documents };
|
|
31181
30801
|
}
|
|
31182
30802
|
function validateReviewedAt(reviewedAt) {
|
|
31183
30803
|
if (!isCalendarDate(reviewedAt)) {
|
|
@@ -31190,7 +30810,7 @@ function commitWrites(root2, writes) {
|
|
|
31190
30810
|
try {
|
|
31191
30811
|
for (const [relPath2, content3] of writes) {
|
|
31192
30812
|
const abs = resolveWritePath(root2, relPath2);
|
|
31193
|
-
originals.set(relPath2, existsSync13(abs) ?
|
|
30813
|
+
originals.set(relPath2, existsSync13(abs) ? readFileSync11(abs, "utf8") : null);
|
|
31194
30814
|
mkdirSync2(dirname8(abs), { recursive: true });
|
|
31195
30815
|
writeFileSync3(abs, content3, "utf8");
|
|
31196
30816
|
written.push(relPath2);
|
|
@@ -31231,7 +30851,7 @@ function attestDocuments(options) {
|
|
|
31231
30851
|
const abs = resolveWritePath(ctx.root, relPath2);
|
|
31232
30852
|
if (!existsSync13(abs))
|
|
31233
30853
|
throw new Error(`Cannot attest missing document: ${relPath2}`);
|
|
31234
|
-
const original =
|
|
30854
|
+
const original = readFileSync11(abs, "utf8");
|
|
31235
30855
|
if (!DOC_META_RE.test(original)) {
|
|
31236
30856
|
throw new Error(`Cannot attest ${relPath2}: missing doc-meta comment`);
|
|
31237
30857
|
}
|
|
@@ -31241,7 +30861,7 @@ function attestDocuments(options) {
|
|
|
31241
30861
|
lock.documents[relPath2] = {
|
|
31242
30862
|
reviewedAt,
|
|
31243
30863
|
documentHash: hash(updated),
|
|
31244
|
-
|
|
30864
|
+
reviewDependencies: hashDependencies(ctx.root, resolveReviewDependencies(ctx.root, reviewDependencyPatterns(updated)).targets)
|
|
31245
30865
|
};
|
|
31246
30866
|
}
|
|
31247
30867
|
}
|
|
@@ -31259,16 +30879,32 @@ function attestDocuments(options) {
|
|
|
31259
30879
|
modifiedFiles: options.dryRun ? [] : [...writes.keys()]
|
|
31260
30880
|
};
|
|
31261
30881
|
}
|
|
31262
|
-
function
|
|
30882
|
+
function changedDependencyIssue(relPath2, target) {
|
|
31263
30883
|
return issue("review-proof", relPath2, {
|
|
31264
|
-
code: "review-
|
|
31265
|
-
message: `
|
|
30884
|
+
code: "review-dependency-changed",
|
|
30885
|
+
message: `review dependency changed after the recorded review: ${target}`,
|
|
31266
30886
|
link: target,
|
|
31267
|
-
remediation: "Re-read the entire document against the current
|
|
30887
|
+
remediation: "Re-read the entire document against the current dependencies, then attest it again."
|
|
31268
30888
|
});
|
|
31269
30889
|
}
|
|
31270
|
-
function validateEntry(input) {
|
|
31271
|
-
const { ctx, relPath: relPath2, content: content3, entry } = input;
|
|
30890
|
+
function validateEntry(input) {
|
|
30891
|
+
const { ctx, relPath: relPath2, content: content3, entry } = input;
|
|
30892
|
+
const issues = documentProofIssues(relPath2, content3, entry);
|
|
30893
|
+
const currentTargets = currentDependencies({ root: ctx.root, relPath: relPath2, content: content3, issues });
|
|
30894
|
+
if (!currentTargets)
|
|
30895
|
+
return issues;
|
|
30896
|
+
const recordedTargets = Object.keys(entry.reviewDependencies).sort();
|
|
30897
|
+
if (JSON.stringify(currentTargets) !== JSON.stringify(recordedTargets)) {
|
|
30898
|
+
issues.push(issue("review-proof", relPath2, {
|
|
30899
|
+
code: "review-dependency-set-changed",
|
|
30900
|
+
message: "review dependency set changed after the recorded review"
|
|
30901
|
+
}));
|
|
30902
|
+
return issues;
|
|
30903
|
+
}
|
|
30904
|
+
issues.push(...dependencyHashIssues({ root: ctx.root, relPath: relPath2, targets: currentTargets, entry }));
|
|
30905
|
+
return issues;
|
|
30906
|
+
}
|
|
30907
|
+
function documentProofIssues(relPath2, content3, entry) {
|
|
31272
30908
|
const issues = [];
|
|
31273
30909
|
if (entry.reviewedAt !== docMetaLastReviewed(content3)) {
|
|
31274
30910
|
issues.push(issue("review-proof", relPath2, {
|
|
@@ -31283,27 +30919,33 @@ function validateEntry(input) {
|
|
|
31283
30919
|
remediation: "Re-read the entire document, then attest it again."
|
|
31284
30920
|
}));
|
|
31285
30921
|
}
|
|
31286
|
-
|
|
31287
|
-
|
|
31288
|
-
|
|
30922
|
+
return issues;
|
|
30923
|
+
}
|
|
30924
|
+
function currentDependencies(input) {
|
|
30925
|
+
const { root: root2, relPath: relPath2, content: content3, issues } = input;
|
|
30926
|
+
try {
|
|
30927
|
+
return resolveReviewDependencies(root2, reviewDependencyPatterns(content3)).targets;
|
|
30928
|
+
} catch (error) {
|
|
31289
30929
|
issues.push(issue("review-proof", relPath2, {
|
|
31290
|
-
code: "review-
|
|
31291
|
-
message:
|
|
30930
|
+
code: "review-dependency-invalid",
|
|
30931
|
+
message: error instanceof Error ? error.message : String(error)
|
|
31292
30932
|
}));
|
|
31293
|
-
return
|
|
30933
|
+
return null;
|
|
31294
30934
|
}
|
|
31295
|
-
|
|
31296
|
-
|
|
30935
|
+
}
|
|
30936
|
+
function dependencyHashIssues(input) {
|
|
30937
|
+
const { root: root2, relPath: relPath2, targets, entry } = input;
|
|
30938
|
+
const issues = [];
|
|
30939
|
+
for (const target of targets) {
|
|
30940
|
+
const abs = resolveWritePath(root2, target);
|
|
31297
30941
|
if (!existsSync13(abs)) {
|
|
31298
30942
|
issues.push(issue("review-proof", relPath2, {
|
|
31299
|
-
code: "review-
|
|
31300
|
-
message: `recorded
|
|
30943
|
+
code: "review-dependency-missing",
|
|
30944
|
+
message: `recorded review dependency is missing: ${target}`,
|
|
31301
30945
|
link: target
|
|
31302
30946
|
}));
|
|
31303
|
-
|
|
31304
|
-
|
|
31305
|
-
if (entry.codeTargets[target] !== hash(readFileSync12(abs, "utf8"))) {
|
|
31306
|
-
issues.push(changedTargetIssue(relPath2, target));
|
|
30947
|
+
} else if (entry.reviewDependencies[target] !== hash(readFileSync11(abs, "utf8"))) {
|
|
30948
|
+
issues.push(changedDependencyIssue(relPath2, target));
|
|
31307
30949
|
}
|
|
31308
30950
|
}
|
|
31309
30951
|
return issues;
|
|
@@ -31321,7 +30963,7 @@ function runReviewProofRule(ctx) {
|
|
|
31321
30963
|
})
|
|
31322
30964
|
];
|
|
31323
30965
|
}
|
|
31324
|
-
const lock = parseLock(
|
|
30966
|
+
const lock = parseLock(readFileSync11(absLock, "utf8"));
|
|
31325
30967
|
if (!lock) {
|
|
31326
30968
|
return [
|
|
31327
30969
|
issue("review-proof", relLock, {
|
|
@@ -31335,7 +30977,7 @@ function runReviewProofRule(ctx) {
|
|
|
31335
30977
|
const abs = resolveWritePath(ctx.root, relPath2);
|
|
31336
30978
|
if (!existsSync13(abs))
|
|
31337
30979
|
continue;
|
|
31338
|
-
const content3 =
|
|
30980
|
+
const content3 = readFileSync11(abs, "utf8");
|
|
31339
30981
|
const entry = lock.documents[normalizeRelPath(relative6(ctx.root, abs))];
|
|
31340
30982
|
if (!entry) {
|
|
31341
30983
|
issues.push(issue("review-proof", relPath2, {
|
|
@@ -31352,8 +30994,8 @@ function runReviewProofRule(ctx) {
|
|
|
31352
30994
|
var reviewProofRule = { id: "review-proof", run: runReviewProofRule };
|
|
31353
30995
|
|
|
31354
30996
|
// src/references/check.ts
|
|
31355
|
-
import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as
|
|
31356
|
-
import { join as
|
|
30997
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync13 } from "node:fs";
|
|
30998
|
+
import { join as join12, relative as relative8 } from "node:path";
|
|
31357
30999
|
|
|
31358
31000
|
// src/references/constants.ts
|
|
31359
31001
|
var CANONICAL_REFS_DIR = ".skeleton/references";
|
|
@@ -31376,8 +31018,8 @@ function isGeneratedReference(content3) {
|
|
|
31376
31018
|
}
|
|
31377
31019
|
|
|
31378
31020
|
// src/references/discover.ts
|
|
31379
|
-
import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as
|
|
31380
|
-
import { join as
|
|
31021
|
+
import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync12 } from "node:fs";
|
|
31022
|
+
import { join as join11, relative as relative7 } from "node:path";
|
|
31381
31023
|
function walkMarkdownFiles(dir, root2) {
|
|
31382
31024
|
const files = [];
|
|
31383
31025
|
if (!existsSync14(dir))
|
|
@@ -31385,7 +31027,7 @@ function walkMarkdownFiles(dir, root2) {
|
|
|
31385
31027
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
31386
31028
|
if (entry.name.startsWith("."))
|
|
31387
31029
|
continue;
|
|
31388
|
-
const fullPath =
|
|
31030
|
+
const fullPath = join11(dir, entry.name);
|
|
31389
31031
|
if (entry.isDirectory()) {
|
|
31390
31032
|
files.push(...walkMarkdownFiles(fullPath, root2));
|
|
31391
31033
|
continue;
|
|
@@ -31397,7 +31039,7 @@ function walkMarkdownFiles(dir, root2) {
|
|
|
31397
31039
|
return files;
|
|
31398
31040
|
}
|
|
31399
31041
|
function canonicalExists(root2, refPath) {
|
|
31400
|
-
return existsSync14(
|
|
31042
|
+
return existsSync14(join11(root2, CANONICAL_REFS_DIR, refPath));
|
|
31401
31043
|
}
|
|
31402
31044
|
function findSharedRefLinks(content3, sourceFile) {
|
|
31403
31045
|
const links = [];
|
|
@@ -31421,7 +31063,7 @@ function findSiblingRefLinks(root2, content3, sourceFile) {
|
|
|
31421
31063
|
const raw = normalizeRelPath(match[1] ?? "");
|
|
31422
31064
|
if (!raw)
|
|
31423
31065
|
continue;
|
|
31424
|
-
const refPath = withinDir ? normalizeRelPath(
|
|
31066
|
+
const refPath = withinDir ? normalizeRelPath(join11(withinDir, raw)) : raw;
|
|
31425
31067
|
if (!canonicalExists(root2, refPath))
|
|
31426
31068
|
continue;
|
|
31427
31069
|
links.push({ refPath, sourceFile });
|
|
@@ -31441,7 +31083,7 @@ function findLocalCanonicalLinks(root2, content3, sourceFile) {
|
|
|
31441
31083
|
return links;
|
|
31442
31084
|
}
|
|
31443
31085
|
function collectLinksForFile(root2, relFile) {
|
|
31444
|
-
const content3 =
|
|
31086
|
+
const content3 = readFileSync12(join11(root2, relFile), "utf8");
|
|
31445
31087
|
if (isGeneratedReference(content3))
|
|
31446
31088
|
return [];
|
|
31447
31089
|
return [
|
|
@@ -31456,7 +31098,7 @@ function expandTransitiveRefs(input) {
|
|
|
31456
31098
|
const refPath = queue.pop();
|
|
31457
31099
|
if (!(refPath && canonicalExists(root2, refPath)))
|
|
31458
31100
|
continue;
|
|
31459
|
-
const canonicalContent =
|
|
31101
|
+
const canonicalContent = readFileSync12(join11(root2, CANONICAL_REFS_DIR, refPath), "utf8");
|
|
31460
31102
|
const syntheticSource = generatedRefPath(skillDir, refPath);
|
|
31461
31103
|
for (const link2 of findLocalCanonicalLinks(root2, canonicalContent, syntheticSource)) {
|
|
31462
31104
|
if (refPaths.has(link2.refPath))
|
|
@@ -31468,8 +31110,8 @@ function expandTransitiveRefs(input) {
|
|
|
31468
31110
|
}
|
|
31469
31111
|
}
|
|
31470
31112
|
function planForSkill(root2, slug2, skillDir) {
|
|
31471
|
-
const absSkillDir =
|
|
31472
|
-
if (!existsSync14(
|
|
31113
|
+
const absSkillDir = join11(root2, skillDir);
|
|
31114
|
+
if (!existsSync14(join11(absSkillDir, "SKILL.md")))
|
|
31473
31115
|
return null;
|
|
31474
31116
|
const refPaths = new Set;
|
|
31475
31117
|
const links = [];
|
|
@@ -31485,8 +31127,8 @@ function planForSkill(root2, slug2, skillDir) {
|
|
|
31485
31127
|
function concreteSkillDirs(root2, index2, slug2) {
|
|
31486
31128
|
const dirs = [];
|
|
31487
31129
|
for (const skillRoot of index2.roots) {
|
|
31488
|
-
const rel = normalizeRelPath(skillRoot.kind === "nested" ?
|
|
31489
|
-
if (existsSync14(
|
|
31130
|
+
const rel = normalizeRelPath(skillRoot.kind === "nested" ? join11(skillRoot.relPath, slug2) : slug2);
|
|
31131
|
+
if (existsSync14(join11(root2, rel, "SKILL.md")))
|
|
31490
31132
|
dirs.push(rel);
|
|
31491
31133
|
}
|
|
31492
31134
|
return [...new Set(dirs)];
|
|
@@ -31504,7 +31146,7 @@ function discoverSkillReferencePlans(root2, ownership) {
|
|
|
31504
31146
|
return plans.sort((a, b) => a.skillDir.localeCompare(b.skillDir));
|
|
31505
31147
|
}
|
|
31506
31148
|
function generatedRefPath(skillDir, refPath) {
|
|
31507
|
-
return normalizeRelPath(
|
|
31149
|
+
return normalizeRelPath(join11(skillDir, "references", refPath));
|
|
31508
31150
|
}
|
|
31509
31151
|
function rewriteSharedRefTarget(sourceFile, skillDir, refPath) {
|
|
31510
31152
|
const sourceDir = sourceFile.slice(0, sourceFile.lastIndexOf("/"));
|
|
@@ -31536,7 +31178,7 @@ function walkMarkdown(dir, onFile) {
|
|
|
31536
31178
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
31537
31179
|
if (entry.name.startsWith("."))
|
|
31538
31180
|
continue;
|
|
31539
|
-
const fullPath =
|
|
31181
|
+
const fullPath = join12(dir, entry.name);
|
|
31540
31182
|
if (entry.isDirectory()) {
|
|
31541
31183
|
walkMarkdown(fullPath, onFile);
|
|
31542
31184
|
continue;
|
|
@@ -31549,7 +31191,7 @@ function walkMarkdown(dir, onFile) {
|
|
|
31549
31191
|
function listAllGeneratedFiles(root2) {
|
|
31550
31192
|
const files = [];
|
|
31551
31193
|
walkMarkdown(root2, (fullPath) => {
|
|
31552
|
-
const content3 =
|
|
31194
|
+
const content3 = readFileSync13(fullPath, "utf8");
|
|
31553
31195
|
if (isGeneratedReference(content3)) {
|
|
31554
31196
|
files.push(normalizeRelPath(relative8(root2, fullPath)));
|
|
31555
31197
|
}
|
|
@@ -31557,21 +31199,21 @@ function listAllGeneratedFiles(root2) {
|
|
|
31557
31199
|
return files;
|
|
31558
31200
|
}
|
|
31559
31201
|
function checkNeededCopy(root2, targetRel) {
|
|
31560
|
-
const targetPath =
|
|
31202
|
+
const targetPath = join12(root2, targetRel);
|
|
31561
31203
|
if (!existsSync15(targetPath)) {
|
|
31562
31204
|
return issue("generated-references", targetRel, "missing generated copy — run skeleton references sync");
|
|
31563
31205
|
}
|
|
31564
|
-
const generated =
|
|
31206
|
+
const generated = readFileSync13(targetPath, "utf8");
|
|
31565
31207
|
if (!isGeneratedReference(generated)) {
|
|
31566
31208
|
return issue("generated-references", targetRel, "expected generated-reference provenance header");
|
|
31567
31209
|
}
|
|
31568
31210
|
const body = stripGeneratedHeader(generated);
|
|
31569
|
-
const sourceRel = normalizeRelPath(generated.match(/source: ([^\n]+)/)?.[1] ??
|
|
31570
|
-
const canonicalPath =
|
|
31211
|
+
const sourceRel = normalizeRelPath(generated.match(/source: ([^\n]+)/)?.[1] ?? join12(CANONICAL_REFS_DIR, targetRel.split("/references/")[1] ?? ""));
|
|
31212
|
+
const canonicalPath = join12(root2, sourceRel);
|
|
31571
31213
|
if (!existsSync15(canonicalPath)) {
|
|
31572
31214
|
return issue("generated-references", targetRel, `canonical source missing: ${sourceRel}`);
|
|
31573
31215
|
}
|
|
31574
|
-
const canonical =
|
|
31216
|
+
const canonical = readFileSync13(canonicalPath, "utf8");
|
|
31575
31217
|
if (body !== canonical) {
|
|
31576
31218
|
return issue("generated-references", targetRel, "stale generated copy — run skeleton references sync");
|
|
31577
31219
|
}
|
|
@@ -31592,7 +31234,7 @@ function checkStaleSharedLinks(root2, skillDir) {
|
|
|
31592
31234
|
const issues = [];
|
|
31593
31235
|
walkMarkdown(skillDir, (fullPath) => {
|
|
31594
31236
|
const relFile = normalizeRelPath(relative8(root2, fullPath));
|
|
31595
|
-
const content3 =
|
|
31237
|
+
const content3 = readFileSync13(fullPath, "utf8");
|
|
31596
31238
|
if (!content3.match(SHARED_REF_LINK_RE))
|
|
31597
31239
|
return;
|
|
31598
31240
|
issues.push(issue("generated-references", relFile, "still links to shared root references/ — run skeleton references sync"));
|
|
@@ -31601,7 +31243,7 @@ function checkStaleSharedLinks(root2, skillDir) {
|
|
|
31601
31243
|
}
|
|
31602
31244
|
function runGeneratedReferencesCheck(root2, ownership) {
|
|
31603
31245
|
const issues = [];
|
|
31604
|
-
const canonicalDir =
|
|
31246
|
+
const canonicalDir = join12(root2, CANONICAL_REFS_DIR);
|
|
31605
31247
|
if (!existsSync15(canonicalDir))
|
|
31606
31248
|
return issues;
|
|
31607
31249
|
const skillIndex = buildSkillIndex(root2, ownership);
|
|
@@ -31619,7 +31261,7 @@ function runGeneratedReferencesCheck(root2, ownership) {
|
|
|
31619
31261
|
}
|
|
31620
31262
|
issues.push(...checkOrphanedCopies(root2, needed, skillIndex));
|
|
31621
31263
|
for (const plan of plans) {
|
|
31622
|
-
issues.push(...checkStaleSharedLinks(root2,
|
|
31264
|
+
issues.push(...checkStaleSharedLinks(root2, join12(root2, plan.skillDir)));
|
|
31623
31265
|
}
|
|
31624
31266
|
return issues;
|
|
31625
31267
|
}
|
|
@@ -31643,38 +31285,9 @@ function runBannedRule(ctx) {
|
|
|
31643
31285
|
}
|
|
31644
31286
|
var bannedRule = { id: "banned", run: runBannedRule };
|
|
31645
31287
|
|
|
31646
|
-
// src/audit/rules/code-fit.ts
|
|
31647
|
-
import { relative as relative9 } from "node:path";
|
|
31648
|
-
function runCodeFitRule(ctx) {
|
|
31649
|
-
const overlapMin = ctx.config.docsLint?.codeFitOverlapMin ?? DEFAULT_CODE_FIT_OVERLAP_MIN;
|
|
31650
|
-
const surfaceCap = ctx.config.docsLint?.codeFitSurfaceCap ?? DEFAULT_CODE_FIT_SURFACE_CAP;
|
|
31651
|
-
const options = { root: ctx.root, overlapMin, surfaceCap };
|
|
31652
|
-
const corpus = collectScanFiles(ctx.config, ctx.root, ctx.skillIndex);
|
|
31653
|
-
const issues = [];
|
|
31654
|
-
for (const abs of corpus) {
|
|
31655
|
-
const content3 = readFileContent(abs);
|
|
31656
|
-
if (!parseCodeFitMarkers(content3).length)
|
|
31657
|
-
continue;
|
|
31658
|
-
const rel = normalizeRelPath(relative9(ctx.root, abs));
|
|
31659
|
-
for (const fit of evaluateCodeFitDoc(rel, content3, options)) {
|
|
31660
|
-
issues.push(issue("code-fit", fit.path, {
|
|
31661
|
-
message: fit.message,
|
|
31662
|
-
link: fit.link,
|
|
31663
|
-
severity: "error"
|
|
31664
|
-
}));
|
|
31665
|
-
}
|
|
31666
|
-
}
|
|
31667
|
-
return issues;
|
|
31668
|
-
}
|
|
31669
|
-
var codeFitRule = {
|
|
31670
|
-
id: "code-fit",
|
|
31671
|
-
alwaysRun: true,
|
|
31672
|
-
run: runCodeFitRule
|
|
31673
|
-
};
|
|
31674
|
-
|
|
31675
31288
|
// src/audit/rules/doc-meta.ts
|
|
31676
|
-
import { existsSync as existsSync16, readFileSync as
|
|
31677
|
-
import { join as
|
|
31289
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
|
|
31290
|
+
import { join as join13 } from "node:path";
|
|
31678
31291
|
function checkDocMetaBanner(relPath2, content3) {
|
|
31679
31292
|
if (DOC_META_RE.test(content3))
|
|
31680
31293
|
return null;
|
|
@@ -31725,136 +31338,359 @@ function checkGitFreshness(input) {
|
|
|
31725
31338
|
remediation: "Re-read the entire document, then use explicit review attestation for that path."
|
|
31726
31339
|
});
|
|
31727
31340
|
}
|
|
31728
|
-
function runDocMetaRule(ctx) {
|
|
31729
|
-
const issues = [];
|
|
31730
|
-
const today = new Date;
|
|
31731
|
-
for (const relPath2 of ctx.docMetaPaths) {
|
|
31732
|
-
const abs =
|
|
31733
|
-
if (!existsSync16(abs))
|
|
31341
|
+
function runDocMetaRule(ctx) {
|
|
31342
|
+
const issues = [];
|
|
31343
|
+
const today = new Date;
|
|
31344
|
+
for (const relPath2 of ctx.docMetaPaths) {
|
|
31345
|
+
const abs = join13(ctx.root, relPath2);
|
|
31346
|
+
if (!existsSync16(abs))
|
|
31347
|
+
continue;
|
|
31348
|
+
const content3 = readFileSync14(abs, "utf8");
|
|
31349
|
+
const banner = checkDocMetaBanner(relPath2, content3);
|
|
31350
|
+
if (banner) {
|
|
31351
|
+
issues.push(banner);
|
|
31352
|
+
continue;
|
|
31353
|
+
}
|
|
31354
|
+
const stale = checkStaleReview({
|
|
31355
|
+
relPath: relPath2,
|
|
31356
|
+
content: content3,
|
|
31357
|
+
today,
|
|
31358
|
+
staleDays: ctx.config.daysUntilStale
|
|
31359
|
+
});
|
|
31360
|
+
if (stale)
|
|
31361
|
+
issues.push(stale);
|
|
31362
|
+
const git = checkGitFreshness({
|
|
31363
|
+
relPath: relPath2,
|
|
31364
|
+
content: content3,
|
|
31365
|
+
root: ctx.root,
|
|
31366
|
+
lockedSkillSlugs: ctx.lockedSkillSlugs
|
|
31367
|
+
});
|
|
31368
|
+
if (git)
|
|
31369
|
+
issues.push(git);
|
|
31370
|
+
}
|
|
31371
|
+
return issues;
|
|
31372
|
+
}
|
|
31373
|
+
var docMetaRule = { id: "doc-meta", run: runDocMetaRule };
|
|
31374
|
+
|
|
31375
|
+
// src/audit/rules/links.ts
|
|
31376
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
|
|
31377
|
+
import { dirname as dirname9, resolve as resolve5 } from "node:path";
|
|
31378
|
+
function resolveLink2(sourceFile, target) {
|
|
31379
|
+
const withoutAnchor = target.split("#")[0]?.split("?")[0] ?? "";
|
|
31380
|
+
if (!withoutAnchor)
|
|
31381
|
+
return sourceFile;
|
|
31382
|
+
return resolve5(dirname9(sourceFile), withoutAnchor);
|
|
31383
|
+
}
|
|
31384
|
+
function checkMissingSkill(input, relSource) {
|
|
31385
|
+
if (!input.target.includes("/SKILL.md"))
|
|
31386
|
+
return null;
|
|
31387
|
+
const slug2 = SKILL_LINK_IN_TARGET_RE.exec(input.target)?.[1];
|
|
31388
|
+
if (!(slug2 && !resolveSkillPath(input.ctx.skillIndex, input.ctx.root, slug2)))
|
|
31389
|
+
return null;
|
|
31390
|
+
return issue("links", relSource, {
|
|
31391
|
+
message: `missing skill "${slug2}/SKILL.md"`,
|
|
31392
|
+
link: input.linkLabel
|
|
31393
|
+
});
|
|
31394
|
+
}
|
|
31395
|
+
function checkAgentFile(input, resolved, relSource) {
|
|
31396
|
+
if (!((input.target.includes(".claude/agents/") || input.target.includes(".cursor/agents/")) && input.target.endsWith(".md"))) {
|
|
31397
|
+
return null;
|
|
31398
|
+
}
|
|
31399
|
+
const agentPath = resolved.endsWith(".md") ? resolved : `${resolved}.md`;
|
|
31400
|
+
if (existsSync17(agentPath))
|
|
31401
|
+
return null;
|
|
31402
|
+
return issue("links", relSource, { message: "missing agent file", link: input.linkLabel });
|
|
31403
|
+
}
|
|
31404
|
+
function checkBrokenPath(ctx) {
|
|
31405
|
+
const { input, pathPart, resolved, relSource, relTarget } = ctx;
|
|
31406
|
+
if (!(pathPart && !existsSync17(resolved)))
|
|
31407
|
+
return null;
|
|
31408
|
+
return issue("links", relSource, {
|
|
31409
|
+
message: `broken link → ${relTarget}`,
|
|
31410
|
+
link: input.linkLabel
|
|
31411
|
+
});
|
|
31412
|
+
}
|
|
31413
|
+
function checkBrokenAnchor(ctx) {
|
|
31414
|
+
const { input, anchor, resolved, relSource, relTarget } = ctx;
|
|
31415
|
+
if (!(anchor && existsSync17(resolved)))
|
|
31416
|
+
return null;
|
|
31417
|
+
const targetContent = readFileSync15(resolved, "utf8");
|
|
31418
|
+
const slugs = extractHeadingSlugs(targetContent, resolved);
|
|
31419
|
+
const anchorSlug = slugifyAnchor(anchor);
|
|
31420
|
+
if (slugs.has(anchorSlug))
|
|
31421
|
+
return null;
|
|
31422
|
+
return issue("links", relSource, {
|
|
31423
|
+
message: `broken anchor → #${anchor} in ${relTarget}`,
|
|
31424
|
+
link: input.linkLabel
|
|
31425
|
+
});
|
|
31426
|
+
}
|
|
31427
|
+
function resolveTargetParts(sourceFile, target, root2) {
|
|
31428
|
+
const relSource = relPath(sourceFile, root2);
|
|
31429
|
+
const anchor = target.includes("#") ? target.split("#")[1]?.split("?")[0] ?? "" : "";
|
|
31430
|
+
const pathPart = target.split("#")[0]?.split("?")[0] ?? "";
|
|
31431
|
+
const resolved = resolveLink2(sourceFile, target);
|
|
31432
|
+
const relTarget = relPath(resolved, root2);
|
|
31433
|
+
return { relSource, anchor, pathPart, resolved, relTarget };
|
|
31434
|
+
}
|
|
31435
|
+
function validateTarget(input) {
|
|
31436
|
+
const { ctx, sourceFile, target } = input;
|
|
31437
|
+
if (isExternalLink(target) && !target.startsWith("#"))
|
|
31438
|
+
return [];
|
|
31439
|
+
if (isPlaceholderLink(target))
|
|
31440
|
+
return [];
|
|
31441
|
+
const parts = resolveTargetParts(sourceFile, target, ctx.root);
|
|
31442
|
+
const missingSkill = checkMissingSkill(input, parts.relSource);
|
|
31443
|
+
if (missingSkill)
|
|
31444
|
+
return [missingSkill];
|
|
31445
|
+
const agent = checkAgentFile(input, parts.resolved, parts.relSource);
|
|
31446
|
+
if (agent)
|
|
31447
|
+
return [agent];
|
|
31448
|
+
const brokenPath = checkBrokenPath({ input, ...parts });
|
|
31449
|
+
if (brokenPath)
|
|
31450
|
+
return [brokenPath];
|
|
31451
|
+
const brokenAnchor = checkBrokenAnchor({ input, ...parts });
|
|
31452
|
+
return brokenAnchor ? [brokenAnchor] : [];
|
|
31453
|
+
}
|
|
31454
|
+
function runLinksRule(ctx) {
|
|
31455
|
+
const issues = [];
|
|
31456
|
+
for (const filePath of ctx.files) {
|
|
31457
|
+
const content3 = readFileContent(filePath);
|
|
31458
|
+
const links = extractLinksFromMarkdown(content3, filePath);
|
|
31459
|
+
for (const { target, line } of links) {
|
|
31460
|
+
const linkLabel = line ? `line ${line}` : target;
|
|
31461
|
+
issues.push(...validateTarget({ ctx, sourceFile: filePath, target, linkLabel }));
|
|
31462
|
+
}
|
|
31463
|
+
}
|
|
31464
|
+
return issues;
|
|
31465
|
+
}
|
|
31466
|
+
var linksRule = { id: "links", run: runLinksRule };
|
|
31467
|
+
|
|
31468
|
+
// src/audit/rules/near-duplicate.ts
|
|
31469
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
31470
|
+
import { join as join14 } from "node:path";
|
|
31471
|
+
|
|
31472
|
+
// src/audit/core/ssot-fit.ts
|
|
31473
|
+
var DEFAULT_SSOT_OVERLAP_MIN = 0.35;
|
|
31474
|
+
var DEFAULT_BETTER_MATCH_MARGIN = 0.15;
|
|
31475
|
+
var STOP = new Set([
|
|
31476
|
+
"a",
|
|
31477
|
+
"an",
|
|
31478
|
+
"the",
|
|
31479
|
+
"and",
|
|
31480
|
+
"or",
|
|
31481
|
+
"of",
|
|
31482
|
+
"for",
|
|
31483
|
+
"to",
|
|
31484
|
+
"in",
|
|
31485
|
+
"on",
|
|
31486
|
+
"with",
|
|
31487
|
+
"this",
|
|
31488
|
+
"that",
|
|
31489
|
+
"is",
|
|
31490
|
+
"are",
|
|
31491
|
+
"be",
|
|
31492
|
+
"as",
|
|
31493
|
+
"by",
|
|
31494
|
+
"from",
|
|
31495
|
+
"at",
|
|
31496
|
+
"it",
|
|
31497
|
+
"its"
|
|
31498
|
+
]);
|
|
31499
|
+
var STEM_BLOCKLIST = new Set([
|
|
31500
|
+
"business",
|
|
31501
|
+
"analysis",
|
|
31502
|
+
"status",
|
|
31503
|
+
"process",
|
|
31504
|
+
"access",
|
|
31505
|
+
"address",
|
|
31506
|
+
"series",
|
|
31507
|
+
"species",
|
|
31508
|
+
"news",
|
|
31509
|
+
"means",
|
|
31510
|
+
"cross",
|
|
31511
|
+
"class",
|
|
31512
|
+
"glass",
|
|
31513
|
+
"less",
|
|
31514
|
+
"success",
|
|
31515
|
+
"progress",
|
|
31516
|
+
"express",
|
|
31517
|
+
"discuss",
|
|
31518
|
+
"focus",
|
|
31519
|
+
"bonus",
|
|
31520
|
+
"basis",
|
|
31521
|
+
"crisis",
|
|
31522
|
+
"thesis",
|
|
31523
|
+
"atlas",
|
|
31524
|
+
"canvas",
|
|
31525
|
+
"campus",
|
|
31526
|
+
"virus",
|
|
31527
|
+
"bus",
|
|
31528
|
+
"gas",
|
|
31529
|
+
"plus",
|
|
31530
|
+
"alias",
|
|
31531
|
+
"bias",
|
|
31532
|
+
"circus",
|
|
31533
|
+
"consensus",
|
|
31534
|
+
"census"
|
|
31535
|
+
]);
|
|
31536
|
+
function lightStem(token) {
|
|
31537
|
+
if (token.length < 4)
|
|
31538
|
+
return token;
|
|
31539
|
+
if (STEM_BLOCKLIST.has(token))
|
|
31540
|
+
return token;
|
|
31541
|
+
if (token.endsWith("ies") && token.length > 4) {
|
|
31542
|
+
return `${token.slice(0, -3)}y`;
|
|
31543
|
+
}
|
|
31544
|
+
if (token.endsWith("sses") || token.endsWith("ches") || token.endsWith("shes") || token.endsWith("xes")) {
|
|
31545
|
+
return token.slice(0, -2);
|
|
31546
|
+
}
|
|
31547
|
+
if (token.endsWith("s") && !token.endsWith("ss") && !token.endsWith("us") && !token.endsWith("is")) {
|
|
31548
|
+
return token.slice(0, -1);
|
|
31549
|
+
}
|
|
31550
|
+
return token;
|
|
31551
|
+
}
|
|
31552
|
+
function contentTokens(text5) {
|
|
31553
|
+
return text5.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 1 && !STOP.has(t)).map(lightStem);
|
|
31554
|
+
}
|
|
31555
|
+
function uniqueContentTokens(text5) {
|
|
31556
|
+
return [...new Set(contentTokens(text5))];
|
|
31557
|
+
}
|
|
31558
|
+
function stripCode2(content3) {
|
|
31559
|
+
return content3.replace(/```[\s\S]*?```/g, `
|
|
31560
|
+
`).replace(/`[^`\n]+`/g, " ");
|
|
31561
|
+
}
|
|
31562
|
+
function stripSsotAndMeta(content3) {
|
|
31563
|
+
return content3.replace(/<!--\s*source-of-truth:[\s\S]*?-->/gi, `
|
|
31564
|
+
`).replace(/^\s*source-of-truth:\s*.+$/gim, `
|
|
31565
|
+
`).replace(/^\s*\*\*Source of truth for\*\*\s*.+$/gim, `
|
|
31566
|
+
`).replace(/<!--\s*doc-meta:[\s\S]*?-->/gi, `
|
|
31567
|
+
`);
|
|
31568
|
+
}
|
|
31569
|
+
function extractH1(content3) {
|
|
31570
|
+
const prose = stripCode2(content3);
|
|
31571
|
+
const m = /^#\s+(.+)$/m.exec(prose);
|
|
31572
|
+
return m?.[1]?.trim() ?? "";
|
|
31573
|
+
}
|
|
31574
|
+
function extractLeadParagraph(content3) {
|
|
31575
|
+
const prose = stripSsotAndMeta(stripCode2(content3));
|
|
31576
|
+
const lines = prose.split(/\n/);
|
|
31577
|
+
const chunks = [];
|
|
31578
|
+
let buf = [];
|
|
31579
|
+
const flush = () => {
|
|
31580
|
+
const t = buf.join(" ").trim();
|
|
31581
|
+
if (t)
|
|
31582
|
+
chunks.push(t);
|
|
31583
|
+
buf = [];
|
|
31584
|
+
};
|
|
31585
|
+
for (const line of lines) {
|
|
31586
|
+
const trimmed = line.trim();
|
|
31587
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
31588
|
+
flush();
|
|
31734
31589
|
continue;
|
|
31735
|
-
|
|
31736
|
-
|
|
31737
|
-
|
|
31738
|
-
issues.push(banner);
|
|
31590
|
+
}
|
|
31591
|
+
if (/^[-*|]/.test(trimmed) && chunks.length === 0 && buf.length === 0) {
|
|
31592
|
+
buf.push(trimmed.replace(/^[-*|]+\s*/, ""));
|
|
31739
31593
|
continue;
|
|
31740
31594
|
}
|
|
31741
|
-
|
|
31742
|
-
relPath: relPath2,
|
|
31743
|
-
content: content3,
|
|
31744
|
-
today,
|
|
31745
|
-
staleDays: ctx.config.daysUntilStale
|
|
31746
|
-
});
|
|
31747
|
-
if (stale)
|
|
31748
|
-
issues.push(stale);
|
|
31749
|
-
const git = checkGitFreshness({
|
|
31750
|
-
relPath: relPath2,
|
|
31751
|
-
content: content3,
|
|
31752
|
-
root: ctx.root,
|
|
31753
|
-
lockedSkillSlugs: ctx.lockedSkillSlugs
|
|
31754
|
-
});
|
|
31755
|
-
if (git)
|
|
31756
|
-
issues.push(git);
|
|
31595
|
+
buf.push(trimmed);
|
|
31757
31596
|
}
|
|
31758
|
-
|
|
31597
|
+
flush();
|
|
31598
|
+
return chunks[0] ?? "";
|
|
31759
31599
|
}
|
|
31760
|
-
|
|
31600
|
+
function buildEvidenceText(content3) {
|
|
31601
|
+
const h1 = extractH1(content3);
|
|
31602
|
+
const lead = extractLeadParagraph(content3);
|
|
31603
|
+
const body = stripSsotAndMeta(stripCode2(content3));
|
|
31604
|
+
return [h1, lead, body].filter(Boolean).join(`
|
|
31761
31605
|
|
|
31762
|
-
|
|
31763
|
-
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "node:fs";
|
|
31764
|
-
import { dirname as dirname9, resolve as resolve5 } from "node:path";
|
|
31765
|
-
function resolveLink2(sourceFile, target) {
|
|
31766
|
-
const withoutAnchor = target.split("#")[0]?.split("?")[0] ?? "";
|
|
31767
|
-
if (!withoutAnchor)
|
|
31768
|
-
return sourceFile;
|
|
31769
|
-
return resolve5(dirname9(sourceFile), withoutAnchor);
|
|
31770
|
-
}
|
|
31771
|
-
function checkMissingSkill(input, relSource) {
|
|
31772
|
-
if (!input.target.includes("/SKILL.md"))
|
|
31773
|
-
return null;
|
|
31774
|
-
const slug2 = SKILL_LINK_IN_TARGET_RE.exec(input.target)?.[1];
|
|
31775
|
-
if (!(slug2 && !resolveSkillPath(input.ctx.skillIndex, input.ctx.root, slug2)))
|
|
31776
|
-
return null;
|
|
31777
|
-
return issue("links", relSource, {
|
|
31778
|
-
message: `missing skill "${slug2}/SKILL.md"`,
|
|
31779
|
-
link: input.linkLabel
|
|
31780
|
-
});
|
|
31606
|
+
`);
|
|
31781
31607
|
}
|
|
31782
|
-
function
|
|
31783
|
-
|
|
31784
|
-
|
|
31608
|
+
function ssotEvidenceOverlap(summary, evidence) {
|
|
31609
|
+
const st = uniqueContentTokens(summary);
|
|
31610
|
+
if (st.length === 0)
|
|
31611
|
+
return 0;
|
|
31612
|
+
const ev = new Set(contentTokens(evidence));
|
|
31613
|
+
let hit = 0;
|
|
31614
|
+
for (const t of st) {
|
|
31615
|
+
if (ev.has(t))
|
|
31616
|
+
hit++;
|
|
31785
31617
|
}
|
|
31786
|
-
|
|
31787
|
-
if (existsSync17(agentPath))
|
|
31788
|
-
return null;
|
|
31789
|
-
return issue("links", relSource, { message: "missing agent file", link: input.linkLabel });
|
|
31790
|
-
}
|
|
31791
|
-
function checkBrokenPath(ctx) {
|
|
31792
|
-
const { input, pathPart, resolved, relSource, relTarget } = ctx;
|
|
31793
|
-
if (!(pathPart && !existsSync17(resolved)))
|
|
31794
|
-
return null;
|
|
31795
|
-
return issue("links", relSource, {
|
|
31796
|
-
message: `broken link → ${relTarget}`,
|
|
31797
|
-
link: input.linkLabel
|
|
31798
|
-
});
|
|
31799
|
-
}
|
|
31800
|
-
function checkBrokenAnchor(ctx) {
|
|
31801
|
-
const { input, anchor, resolved, relSource, relTarget } = ctx;
|
|
31802
|
-
if (!(anchor && existsSync17(resolved)))
|
|
31803
|
-
return null;
|
|
31804
|
-
const targetContent = readFileSync16(resolved, "utf8");
|
|
31805
|
-
const slugs = extractHeadingSlugs(targetContent, resolved);
|
|
31806
|
-
const anchorSlug = slugifyAnchor(anchor);
|
|
31807
|
-
if (slugs.has(anchorSlug))
|
|
31808
|
-
return null;
|
|
31809
|
-
return issue("links", relSource, {
|
|
31810
|
-
message: `broken anchor → #${anchor} in ${relTarget}`,
|
|
31811
|
-
link: input.linkLabel
|
|
31812
|
-
});
|
|
31618
|
+
return hit / st.length;
|
|
31813
31619
|
}
|
|
31814
|
-
function
|
|
31815
|
-
const
|
|
31816
|
-
|
|
31817
|
-
|
|
31818
|
-
|
|
31819
|
-
|
|
31820
|
-
return
|
|
31620
|
+
function longestSummaryPhrase(summary) {
|
|
31621
|
+
const toks = contentTokens(summary);
|
|
31622
|
+
if (toks.length >= 3)
|
|
31623
|
+
return toks.slice(0, 3);
|
|
31624
|
+
if (toks.length >= 2)
|
|
31625
|
+
return toks.slice(0, 2);
|
|
31626
|
+
return null;
|
|
31821
31627
|
}
|
|
31822
|
-
function
|
|
31823
|
-
|
|
31824
|
-
|
|
31825
|
-
|
|
31826
|
-
|
|
31827
|
-
|
|
31828
|
-
|
|
31829
|
-
|
|
31830
|
-
|
|
31831
|
-
|
|
31832
|
-
const agent = checkAgentFile(input, parts.resolved, parts.relSource);
|
|
31833
|
-
if (agent)
|
|
31834
|
-
return [agent];
|
|
31835
|
-
const brokenPath = checkBrokenPath({ input, ...parts });
|
|
31836
|
-
if (brokenPath)
|
|
31837
|
-
return [brokenPath];
|
|
31838
|
-
const brokenAnchor = checkBrokenAnchor({ input, ...parts });
|
|
31839
|
-
return brokenAnchor ? [brokenAnchor] : [];
|
|
31628
|
+
function evidenceHasPhrase(evidence, phrase) {
|
|
31629
|
+
if (phrase.length === 0)
|
|
31630
|
+
return true;
|
|
31631
|
+
const ev = contentTokens(evidence);
|
|
31632
|
+
const needle = phrase.join(" ");
|
|
31633
|
+
for (let i = 0;i <= ev.length - phrase.length; i++) {
|
|
31634
|
+
if (ev.slice(i, i + phrase.length).join(" ") === needle)
|
|
31635
|
+
return true;
|
|
31636
|
+
}
|
|
31637
|
+
return false;
|
|
31840
31638
|
}
|
|
31841
|
-
function
|
|
31639
|
+
function evaluateSsotFit(files, options = {}) {
|
|
31640
|
+
const overlapMin = options.overlapMin ?? DEFAULT_SSOT_OVERLAP_MIN;
|
|
31641
|
+
const margin = options.betterMatchMargin ?? DEFAULT_BETTER_MATCH_MARGIN;
|
|
31642
|
+
const phraseCheck = options.phraseCheck !== false;
|
|
31643
|
+
const prepared = files.map((f) => {
|
|
31644
|
+
const evidence = buildEvidenceText(f.content);
|
|
31645
|
+
const overlap = ssotEvidenceOverlap(f.summary, evidence);
|
|
31646
|
+
const summaryToks = uniqueContentTokens(f.summary);
|
|
31647
|
+
return { ...f, evidence, overlap, summaryToks };
|
|
31648
|
+
});
|
|
31842
31649
|
const issues = [];
|
|
31843
|
-
for (const
|
|
31844
|
-
|
|
31845
|
-
|
|
31846
|
-
|
|
31847
|
-
|
|
31848
|
-
|
|
31650
|
+
for (const row of prepared) {
|
|
31651
|
+
if (row.summaryToks.length < 2) {
|
|
31652
|
+
issues.push({
|
|
31653
|
+
kind: "short",
|
|
31654
|
+
path: row.path,
|
|
31655
|
+
message: `source-of-truth summary too short to verify against body ("${row.summary}")`
|
|
31656
|
+
});
|
|
31657
|
+
continue;
|
|
31658
|
+
}
|
|
31659
|
+
if (row.overlap < overlapMin) {
|
|
31660
|
+
let message = `source-of-truth summary weakly matches this paper (token overlap ${row.overlap.toFixed(2)} < ${overlapMin})`;
|
|
31661
|
+
if (phraseCheck) {
|
|
31662
|
+
const phrase = longestSummaryPhrase(row.summary);
|
|
31663
|
+
if (phrase && !evidenceHasPhrase(row.evidence, phrase)) {
|
|
31664
|
+
message += ` — key phrase "${phrase.join(" ")}" not found in H1/lead/body`;
|
|
31665
|
+
}
|
|
31666
|
+
}
|
|
31667
|
+
issues.push({ kind: "weak", path: row.path, message });
|
|
31668
|
+
let best = null;
|
|
31669
|
+
for (const other of prepared) {
|
|
31670
|
+
if (other.path === row.path)
|
|
31671
|
+
continue;
|
|
31672
|
+
const cross = ssotEvidenceOverlap(row.summary, other.evidence);
|
|
31673
|
+
if (cross < overlapMin)
|
|
31674
|
+
continue;
|
|
31675
|
+
if (cross < row.overlap + margin)
|
|
31676
|
+
continue;
|
|
31677
|
+
if (!best || cross > best.overlap)
|
|
31678
|
+
best = { path: other.path, overlap: cross };
|
|
31679
|
+
}
|
|
31680
|
+
if (best) {
|
|
31681
|
+
issues.push({
|
|
31682
|
+
kind: "better-match",
|
|
31683
|
+
path: row.path,
|
|
31684
|
+
otherPath: best.path,
|
|
31685
|
+
message: `source-of-truth fits ${best.path} better (overlap ${best.overlap.toFixed(2)} vs own ${row.overlap.toFixed(2)}). ` + `Try: (1) rewrite this SSOT to match this paper, (2) move/fix the marker onto ${best.path}, ` + `or (3) if these pages are really one topic, consider combining them`
|
|
31686
|
+
});
|
|
31687
|
+
}
|
|
31849
31688
|
}
|
|
31850
31689
|
}
|
|
31851
31690
|
return issues;
|
|
31852
31691
|
}
|
|
31853
|
-
var linksRule = { id: "links", run: runLinksRule };
|
|
31854
31692
|
|
|
31855
31693
|
// src/audit/rules/near-duplicate.ts
|
|
31856
|
-
import { readFileSync as readFileSync17 } from "node:fs";
|
|
31857
|
-
import { join as join15 } from "node:path";
|
|
31858
31694
|
var DEFAULT_THRESHOLD = 0.72;
|
|
31859
31695
|
var SHINGLE_N = 3;
|
|
31860
31696
|
function tokenize2(text5) {
|
|
@@ -31919,7 +31755,7 @@ function runNearDuplicateRule(ctx) {
|
|
|
31919
31755
|
const ignored = ignoredPairSet(ctx);
|
|
31920
31756
|
const entries = eligibleEntries(ctx);
|
|
31921
31757
|
const fingerprints = entries.map((e) => {
|
|
31922
|
-
const content3 =
|
|
31758
|
+
const content3 = readFileSync16(join14(ctx.root, e.path), "utf8");
|
|
31923
31759
|
const tokens = tokenize2(bodyWithoutSsotNoise(content3));
|
|
31924
31760
|
return {
|
|
31925
31761
|
path: e.path,
|
|
@@ -32013,6 +31849,44 @@ function runProsePolicyRule(ctx) {
|
|
|
32013
31849
|
return issues;
|
|
32014
31850
|
}
|
|
32015
31851
|
var prosePolicyRule = { id: "prose-policy", run: runProsePolicyRule };
|
|
31852
|
+
|
|
31853
|
+
// src/audit/rules/review-deps.ts
|
|
31854
|
+
import { relative as relative9 } from "node:path";
|
|
31855
|
+
function runReviewDepsRule(ctx) {
|
|
31856
|
+
const issues = [];
|
|
31857
|
+
for (const abs of collectScanFiles(ctx.config, ctx.root, ctx.skillIndex)) {
|
|
31858
|
+
const path3 = normalizeRelPath(relative9(ctx.root, abs));
|
|
31859
|
+
for (const marker of parseReviewDepsMarkers(readFileContent(abs))) {
|
|
31860
|
+
issues.push(...validateMarker(ctx.root, path3, marker.paths));
|
|
31861
|
+
}
|
|
31862
|
+
}
|
|
31863
|
+
return issues;
|
|
31864
|
+
}
|
|
31865
|
+
function validateMarker(root2, path3, dependencies) {
|
|
31866
|
+
if (dependencies.length === 0)
|
|
31867
|
+
return [issue("review-deps", path3, "review-deps marker missing paths=")];
|
|
31868
|
+
return dependencies.flatMap((dependency) => validateDependency(root2, path3, dependency));
|
|
31869
|
+
}
|
|
31870
|
+
function validateDependency(root2, path3, dependency) {
|
|
31871
|
+
if (!isSafeReviewDependencyPath(dependency)) {
|
|
31872
|
+
return [issue("review-deps", path3, `invalid review dependency path: ${dependency}`)];
|
|
31873
|
+
}
|
|
31874
|
+
try {
|
|
31875
|
+
const resolved = resolveReviewDependencies(root2, [dependency]);
|
|
31876
|
+
if (!isReviewDependencyGlob(dependency) || resolved.targets.length > 0)
|
|
31877
|
+
return [];
|
|
31878
|
+
return [
|
|
31879
|
+
issue("review-deps", path3, {
|
|
31880
|
+
code: "review-dependency-glob-empty",
|
|
31881
|
+
message: `review dependency glob matches no files: ${dependency}`,
|
|
31882
|
+
severity: "warning"
|
|
31883
|
+
})
|
|
31884
|
+
];
|
|
31885
|
+
} catch (error) {
|
|
31886
|
+
return [issue("review-deps", path3, error instanceof Error ? error.message : String(error))];
|
|
31887
|
+
}
|
|
31888
|
+
}
|
|
31889
|
+
var reviewDepsRule = { id: "review-deps", alwaysRun: true, run: runReviewDepsRule };
|
|
32016
31890
|
// src/audit/rules/scan-gaps.ts
|
|
32017
31891
|
function runCoverageGapsRule(ctx) {
|
|
32018
31892
|
const exclude = [...COVERAGE_BUILTIN_EXCLUDES, ...ctx.config.scan.exclude];
|
|
@@ -32042,8 +31916,8 @@ function runScanRootsRule(ctx) {
|
|
|
32042
31916
|
var scanRootsRule = { id: "scan-roots", run: runScanRootsRule };
|
|
32043
31917
|
|
|
32044
31918
|
// src/audit/rules/skill-index.ts
|
|
32045
|
-
import { existsSync as existsSync18, readdirSync as readdirSync4, readFileSync as
|
|
32046
|
-
import { join as
|
|
31919
|
+
import { existsSync as existsSync18, readdirSync as readdirSync4, readFileSync as readFileSync17 } from "node:fs";
|
|
31920
|
+
import { join as join15, relative as relative10 } from "node:path";
|
|
32047
31921
|
function walkSkillMarkdown(dir) {
|
|
32048
31922
|
const files = [];
|
|
32049
31923
|
if (!existsSync18(dir))
|
|
@@ -32051,7 +31925,7 @@ function walkSkillMarkdown(dir) {
|
|
|
32051
31925
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
32052
31926
|
if (entry.name.startsWith("."))
|
|
32053
31927
|
continue;
|
|
32054
|
-
const fullPath =
|
|
31928
|
+
const fullPath = join15(dir, entry.name);
|
|
32055
31929
|
if (entry.isDirectory()) {
|
|
32056
31930
|
files.push(...walkSkillMarkdown(fullPath));
|
|
32057
31931
|
continue;
|
|
@@ -32078,7 +31952,7 @@ function parseReadmeTaxonomySlugs(content3) {
|
|
|
32078
31952
|
function scanFileForSkillLinks(ctx, filePath, index2) {
|
|
32079
31953
|
const issues = [];
|
|
32080
31954
|
const rel = relative10(ctx.root, filePath).replace(/\\/g, "/");
|
|
32081
|
-
const content3 =
|
|
31955
|
+
const content3 = readFileSync17(filePath, "utf8");
|
|
32082
31956
|
if (isGeneratedReference(content3))
|
|
32083
31957
|
return issues;
|
|
32084
31958
|
for (const match of content3.matchAll(SKILL_LINK_RE)) {
|
|
@@ -32093,14 +31967,14 @@ function scanFileForSkillLinks(ctx, filePath, index2) {
|
|
|
32093
31967
|
}
|
|
32094
31968
|
function taxonomyIssuesForReadme(input) {
|
|
32095
31969
|
const { ctx, index: index2, skillRoot, diskSlugs, nonPublic } = input;
|
|
32096
|
-
const readmePath =
|
|
31970
|
+
const readmePath = join15(ctx.root, skillRoot.relPath, "README.md");
|
|
32097
31971
|
if (!existsSync18(readmePath))
|
|
32098
31972
|
return [];
|
|
32099
|
-
const readme =
|
|
31973
|
+
const readme = readFileSync17(readmePath, "utf8");
|
|
32100
31974
|
if (!readme.includes("## Taxonomy"))
|
|
32101
31975
|
return [];
|
|
32102
31976
|
const taxonomySlugs = parseReadmeTaxonomySlugs(readme);
|
|
32103
|
-
const nestedSlugs = diskSlugs.filter((slug2) => existsSync18(
|
|
31977
|
+
const nestedSlugs = diskSlugs.filter((slug2) => existsSync18(join15(ctx.root, skillRoot.relPath, slug2, "SKILL.md")));
|
|
32104
31978
|
const foreign = new Set(index2.foreignSlugs);
|
|
32105
31979
|
const publicSlugs = nestedSlugs.filter((slug2) => !(nonPublic.has(slug2) || foreign.has(slug2)));
|
|
32106
31980
|
const relReadme = `${skillRoot.relPath}/README.md`;
|
|
@@ -32133,9 +32007,9 @@ function slugsForRoot(skillRoot, index2, owned) {
|
|
|
32133
32007
|
function auditSkillRoot(input) {
|
|
32134
32008
|
const { ctx, index: index2, skillRoot, owned } = input;
|
|
32135
32009
|
const issues = [];
|
|
32136
|
-
const base = skillRoot.kind === "nested" ?
|
|
32010
|
+
const base = skillRoot.kind === "nested" ? join15(ctx.root, skillRoot.relPath) : ctx.root;
|
|
32137
32011
|
for (const slug2 of slugsForRoot(skillRoot, index2, owned)) {
|
|
32138
|
-
const skillDir =
|
|
32012
|
+
const skillDir = join15(base, slug2);
|
|
32139
32013
|
if (!existsSync18(skillDir))
|
|
32140
32014
|
continue;
|
|
32141
32015
|
for (const skillMd of walkSkillMarkdown(skillDir)) {
|
|
@@ -32186,8 +32060,8 @@ function runSsotRule(ctx) {
|
|
|
32186
32060
|
var ssotRule = { id: "ssot", run: runSsotRule };
|
|
32187
32061
|
|
|
32188
32062
|
// src/audit/rules/ssot-summary.ts
|
|
32189
|
-
import { readFileSync as
|
|
32190
|
-
import { join as
|
|
32063
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
32064
|
+
import { join as join16 } from "node:path";
|
|
32191
32065
|
function runSsotSummaryRule(ctx) {
|
|
32192
32066
|
const overlapMin = ctx.config.docsLint?.ssotOverlapMin ?? DEFAULT_SSOT_OVERLAP_MIN;
|
|
32193
32067
|
const margin = ctx.config.docsLint?.ssotBetterMatchMargin ?? DEFAULT_BETTER_MATCH_MARGIN;
|
|
@@ -32195,7 +32069,7 @@ function runSsotSummaryRule(ctx) {
|
|
|
32195
32069
|
const files = ctx.ssotEntries.map((entry) => ({
|
|
32196
32070
|
path: entry.path,
|
|
32197
32071
|
summary: entry.summary,
|
|
32198
|
-
content:
|
|
32072
|
+
content: readFileSync18(join16(ctx.root, entry.path), "utf8")
|
|
32199
32073
|
}));
|
|
32200
32074
|
return evaluateSsotFit(files, {
|
|
32201
32075
|
overlapMin,
|
|
@@ -32219,7 +32093,7 @@ var docsRules = [
|
|
|
32219
32093
|
linksRule,
|
|
32220
32094
|
docMetaRule,
|
|
32221
32095
|
reviewProofRule,
|
|
32222
|
-
|
|
32096
|
+
reviewDepsRule,
|
|
32223
32097
|
{ ...bannedRule, global: true },
|
|
32224
32098
|
prosePolicyRule
|
|
32225
32099
|
];
|
|
@@ -32562,19 +32436,19 @@ async function runAudit(options) {
|
|
|
32562
32436
|
}
|
|
32563
32437
|
|
|
32564
32438
|
// src/customize/resolve.ts
|
|
32565
|
-
import { existsSync as existsSync19, readFileSync as
|
|
32566
|
-
import { basename as basename3, join as
|
|
32439
|
+
import { existsSync as existsSync19, readFileSync as readFileSync19 } from "node:fs";
|
|
32440
|
+
import { basename as basename3, join as join17, relative as relative11 } from "node:path";
|
|
32567
32441
|
function customizeDir(root2) {
|
|
32568
|
-
return
|
|
32442
|
+
return join17(root2, REGISTRY_DIR_REL, "customize");
|
|
32569
32443
|
}
|
|
32570
32444
|
function customizePathForSlug(root2, slug2) {
|
|
32571
|
-
return
|
|
32445
|
+
return join17(customizeDir(root2), `${slug2}.md`);
|
|
32572
32446
|
}
|
|
32573
32447
|
function resolveSlugFile(root2, slug2) {
|
|
32574
32448
|
const direct = customizePathForSlug(root2, slug2);
|
|
32575
32449
|
if (existsSync19(direct)) {
|
|
32576
32450
|
return {
|
|
32577
|
-
content:
|
|
32451
|
+
content: readFileSync19(direct, "utf8"),
|
|
32578
32452
|
path: normalizeRelPath(relative11(root2, direct))
|
|
32579
32453
|
};
|
|
32580
32454
|
}
|
|
@@ -32596,10 +32470,10 @@ function readAlwaysInclude(root2, basenames, skipBasename) {
|
|
|
32596
32470
|
const file = basename3(name);
|
|
32597
32471
|
if (skipBasename && file === skipBasename)
|
|
32598
32472
|
continue;
|
|
32599
|
-
const abs =
|
|
32473
|
+
const abs = join17(dir, file);
|
|
32600
32474
|
if (!existsSync19(abs))
|
|
32601
32475
|
continue;
|
|
32602
|
-
parts.push(
|
|
32476
|
+
parts.push(readFileSync19(abs, "utf8").trimEnd());
|
|
32603
32477
|
paths.push(normalizeRelPath(relative11(root2, abs)));
|
|
32604
32478
|
}
|
|
32605
32479
|
return { parts, paths };
|
|
@@ -32723,29 +32597,29 @@ Customize override for /${slug2} (from ${from}):
|
|
|
32723
32597
|
|
|
32724
32598
|
// src/init/init.ts
|
|
32725
32599
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
32726
|
-
import { copyFileSync, existsSync as existsSync23, mkdirSync as mkdirSync4, readFileSync as
|
|
32727
|
-
import { join as
|
|
32600
|
+
import { copyFileSync, existsSync as existsSync23, mkdirSync as mkdirSync4, readFileSync as readFileSync21 } from "node:fs";
|
|
32601
|
+
import { join as join21 } from "node:path";
|
|
32728
32602
|
import process6 from "node:process";
|
|
32729
32603
|
|
|
32730
32604
|
// src/init/merge-hooks.ts
|
|
32731
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync3, readFileSync as
|
|
32732
|
-
import { dirname as dirname12, join as
|
|
32605
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync3, readFileSync as readFileSync20, writeFileSync as writeFileSync4 } from "node:fs";
|
|
32606
|
+
import { dirname as dirname12, join as join20 } from "node:path";
|
|
32733
32607
|
|
|
32734
32608
|
// src/init/package-paths.ts
|
|
32735
32609
|
import { existsSync as existsSync20 } from "node:fs";
|
|
32736
|
-
import { dirname as dirname10, join as
|
|
32610
|
+
import { dirname as dirname10, join as join18 } from "node:path";
|
|
32737
32611
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
32738
32612
|
var MODULE_DIR = dirname10(fileURLToPath4(import.meta.url));
|
|
32739
|
-
var PACKAGE_ROOT_CANDIDATES = [
|
|
32613
|
+
var PACKAGE_ROOT_CANDIDATES = [join18(MODULE_DIR, "../.."), join18(MODULE_DIR, "..")];
|
|
32740
32614
|
function resolvePackageRoot() {
|
|
32741
32615
|
for (const candidate of PACKAGE_ROOT_CANDIDATES) {
|
|
32742
|
-
if (existsSync20(
|
|
32616
|
+
if (existsSync20(join18(candidate, "package.json")))
|
|
32743
32617
|
return candidate;
|
|
32744
32618
|
}
|
|
32745
32619
|
throw new Error("Could not resolve @csark0812/skeleton package root");
|
|
32746
32620
|
}
|
|
32747
32621
|
function resolveTemplatesDir() {
|
|
32748
|
-
const dir =
|
|
32622
|
+
const dir = join18(resolvePackageRoot(), "templates/skeleton-init");
|
|
32749
32623
|
if (!existsSync20(dir)) {
|
|
32750
32624
|
throw new Error("Missing templates/skeleton-init in package");
|
|
32751
32625
|
}
|
|
@@ -32755,7 +32629,7 @@ function resolveTemplatesDir() {
|
|
|
32755
32629
|
// src/init/resolve-hook-command.ts
|
|
32756
32630
|
import { existsSync as existsSync21, realpathSync as realpathSync5 } from "node:fs";
|
|
32757
32631
|
import { createRequire as createRequire3 } from "node:module";
|
|
32758
|
-
import { dirname as dirname11, join as
|
|
32632
|
+
import { dirname as dirname11, join as join19, relative as relative12, resolve as resolve6 } from "node:path";
|
|
32759
32633
|
var PACKAGE_NAME = "@csark0812/skeleton";
|
|
32760
32634
|
var CLI_DIST = "dist/cli.js";
|
|
32761
32635
|
var PACKAGE_ROOT = resolvePackageRoot();
|
|
@@ -32774,7 +32648,7 @@ function toRepoRelative(cwd, absPath) {
|
|
|
32774
32648
|
}
|
|
32775
32649
|
function tryResolvePublishedCli(cwd) {
|
|
32776
32650
|
try {
|
|
32777
|
-
const req = createRequire3(
|
|
32651
|
+
const req = createRequire3(join19(cwd, "package.json"));
|
|
32778
32652
|
return req.resolve(`${PACKAGE_NAME}/${CLI_DIST}`);
|
|
32779
32653
|
} catch {
|
|
32780
32654
|
return null;
|
|
@@ -32783,7 +32657,7 @@ function tryResolvePublishedCli(cwd) {
|
|
|
32783
32657
|
function walkNodeModulesCli(cwd) {
|
|
32784
32658
|
let dir = cwd;
|
|
32785
32659
|
while (true) {
|
|
32786
|
-
const candidate =
|
|
32660
|
+
const candidate = join19(dir, "node_modules", PACKAGE_NAME, CLI_DIST);
|
|
32787
32661
|
if (existsSync21(candidate))
|
|
32788
32662
|
return candidate;
|
|
32789
32663
|
const parent = dirname11(dir);
|
|
@@ -32825,14 +32699,14 @@ function identityKey(platform, event, matcher) {
|
|
|
32825
32699
|
return `skeleton:customize:${platform}:${event}:${matcher}`;
|
|
32826
32700
|
}
|
|
32827
32701
|
function loadFragment(name, hookCommand) {
|
|
32828
|
-
const raw =
|
|
32702
|
+
const raw = readFileSync20(join20(TEMPLATES_DIR, name), "utf8");
|
|
32829
32703
|
return JSON.parse(raw.replaceAll("{{HOOK_COMMAND}}", hookCommand));
|
|
32830
32704
|
}
|
|
32831
32705
|
function readJson(path3) {
|
|
32832
32706
|
if (!existsSync22(path3))
|
|
32833
32707
|
return null;
|
|
32834
32708
|
try {
|
|
32835
|
-
return JSON.parse(
|
|
32709
|
+
return JSON.parse(readFileSync20(path3, "utf8"));
|
|
32836
32710
|
} catch (error) {
|
|
32837
32711
|
throw new Error(`Invalid JSON in ${path3}: ${error}`);
|
|
32838
32712
|
}
|
|
@@ -32992,10 +32866,10 @@ function mergeNestedHooks(args) {
|
|
|
32992
32866
|
}
|
|
32993
32867
|
function mergeHookConfigs(opts) {
|
|
32994
32868
|
const results = [];
|
|
32995
|
-
const cursorPath =
|
|
32869
|
+
const cursorPath = join20(opts.cwd, ".cursor/hooks.json");
|
|
32996
32870
|
const cursorFragment = loadFragment("cursor-hooks.fragment.json", opts.hookCommand);
|
|
32997
32871
|
results.push(mergeCursorHooks(cursorPath, cursorFragment, opts));
|
|
32998
|
-
const claudePath =
|
|
32872
|
+
const claudePath = join20(opts.cwd, ".claude/settings.json");
|
|
32999
32873
|
const claudeFragment = loadFragment("claude-settings.fragment.json", opts.hookCommand);
|
|
33000
32874
|
results.push(mergeNestedHooks({
|
|
33001
32875
|
platform: "claude",
|
|
@@ -33004,8 +32878,8 @@ function mergeHookConfigs(opts) {
|
|
|
33004
32878
|
eventName: "PostToolUse",
|
|
33005
32879
|
opts
|
|
33006
32880
|
}));
|
|
33007
|
-
const codexPath =
|
|
33008
|
-
if (existsSync22(
|
|
32881
|
+
const codexPath = join20(opts.cwd, ".codex/hooks.json");
|
|
32882
|
+
if (existsSync22(join20(opts.cwd, ".codex"))) {
|
|
33009
32883
|
const codexFragment = loadFragment("codex-hooks.fragment.json", opts.hookCommand);
|
|
33010
32884
|
results.push(mergeNestedHooks({
|
|
33011
32885
|
platform: "codex",
|
|
@@ -33020,11 +32894,11 @@ function mergeHookConfigs(opts) {
|
|
|
33020
32894
|
return results;
|
|
33021
32895
|
}
|
|
33022
32896
|
function mergePackageJsonScripts(cwd) {
|
|
33023
|
-
const pkgPath =
|
|
32897
|
+
const pkgPath = join20(cwd, "package.json");
|
|
33024
32898
|
if (!existsSync22(pkgPath))
|
|
33025
32899
|
return "skipped";
|
|
33026
|
-
const fragment = JSON.parse(
|
|
33027
|
-
const pkg = JSON.parse(
|
|
32900
|
+
const fragment = JSON.parse(readFileSync20(join20(TEMPLATES_DIR, "package.json.scripts.fragment.json"), "utf8"));
|
|
32901
|
+
const pkg = JSON.parse(readFileSync20(pkgPath, "utf8"));
|
|
33028
32902
|
pkg.scripts ??= {};
|
|
33029
32903
|
let changed = false;
|
|
33030
32904
|
for (const [key, value] of Object.entries(fragment)) {
|
|
@@ -33080,23 +32954,23 @@ function skillsAddArgs(options = {}) {
|
|
|
33080
32954
|
// src/init/init.ts
|
|
33081
32955
|
var TEMPLATES_DIR2 = resolveTemplatesDir();
|
|
33082
32956
|
function writeScaffold(cwd) {
|
|
33083
|
-
const skeletonDir2 =
|
|
32957
|
+
const skeletonDir2 = join21(cwd, ".skeleton");
|
|
33084
32958
|
mkdirSync4(skeletonDir2, { recursive: true });
|
|
33085
32959
|
let created = false;
|
|
33086
|
-
const tomlPath =
|
|
33087
|
-
const legacyYaml =
|
|
32960
|
+
const tomlPath = join21(cwd, "skeleton.toml");
|
|
32961
|
+
const legacyYaml = join21(skeletonDir2, "config.yaml");
|
|
33088
32962
|
if (!(existsSync23(tomlPath) || existsSync23(legacyYaml))) {
|
|
33089
|
-
copyFileSync(
|
|
32963
|
+
copyFileSync(join21(TEMPLATES_DIR2, "skeleton.toml"), tomlPath);
|
|
33090
32964
|
created = true;
|
|
33091
32965
|
}
|
|
33092
|
-
mkdirSync4(
|
|
32966
|
+
mkdirSync4(join21(skeletonDir2, "customize"), { recursive: true });
|
|
33093
32967
|
return created ? "created" : "skipped";
|
|
33094
32968
|
}
|
|
33095
32969
|
function assertPackageResolvable(cwd) {
|
|
33096
|
-
const pkgPath =
|
|
32970
|
+
const pkgPath = join21(cwd, "package.json");
|
|
33097
32971
|
if (!existsSync23(pkgPath))
|
|
33098
32972
|
return;
|
|
33099
|
-
const pkg = JSON.parse(
|
|
32973
|
+
const pkg = JSON.parse(readFileSync21(pkgPath, "utf8"));
|
|
33100
32974
|
const hasDep = pkg.devDependencies?.["@csark0812/skeleton"] || pkg.dependencies?.["@csark0812/skeleton"];
|
|
33101
32975
|
if (!hasDep) {
|
|
33102
32976
|
try {
|
|
@@ -33180,7 +33054,7 @@ function parseInitArgs(argv) {
|
|
|
33180
33054
|
// src/plugins/build.ts
|
|
33181
33055
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
33182
33056
|
import { createHash as createHash2 } from "node:crypto";
|
|
33183
|
-
import { existsSync as existsSync24, readFileSync as
|
|
33057
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync5 } from "node:fs";
|
|
33184
33058
|
import { basename as basename4, dirname as dirname13, resolve as resolve7 } from "node:path";
|
|
33185
33059
|
function parseBuildPluginArgs(argv) {
|
|
33186
33060
|
let check = false;
|
|
@@ -33238,7 +33112,7 @@ function sourceFingerprint(tsAbs, seen = new Set) {
|
|
|
33238
33112
|
if (seen.has(abs))
|
|
33239
33113
|
return;
|
|
33240
33114
|
seen.add(abs);
|
|
33241
|
-
const content3 =
|
|
33115
|
+
const content3 = readFileSync22(abs, "utf8");
|
|
33242
33116
|
hash2.update(basename4(abs));
|
|
33243
33117
|
hash2.update("\x00");
|
|
33244
33118
|
hash2.update(content3);
|
|
@@ -33286,7 +33160,7 @@ function checkOne(tsAbs) {
|
|
|
33286
33160
|
if (!existsSync24(stampAbs)) {
|
|
33287
33161
|
throw new Error(`Plugin stale: ${mjsAbs} has no fingerprint stamp. Run: skeleton build-plugin`);
|
|
33288
33162
|
}
|
|
33289
|
-
const expected =
|
|
33163
|
+
const expected = readFileSync22(stampAbs, "utf8").trim();
|
|
33290
33164
|
const actual = sourceFingerprint(tsAbs);
|
|
33291
33165
|
if (expected !== actual) {
|
|
33292
33166
|
throw new Error(`Plugin stale: ${mjsAbs} does not match ${tsAbs} (or local imports). Run: skeleton build-plugin`);
|
|
@@ -33322,11 +33196,11 @@ import {
|
|
|
33322
33196
|
existsSync as existsSync25,
|
|
33323
33197
|
mkdirSync as mkdirSync5,
|
|
33324
33198
|
readdirSync as readdirSync5,
|
|
33325
|
-
readFileSync as
|
|
33199
|
+
readFileSync as readFileSync23,
|
|
33326
33200
|
unlinkSync as unlinkSync2,
|
|
33327
33201
|
writeFileSync as writeFileSync6
|
|
33328
33202
|
} from "node:fs";
|
|
33329
|
-
import { dirname as dirname14, join as
|
|
33203
|
+
import { dirname as dirname14, join as join22, relative as relative13 } from "node:path";
|
|
33330
33204
|
import process7 from "node:process";
|
|
33331
33205
|
function resolveOwnership(root2, override) {
|
|
33332
33206
|
if (override !== undefined)
|
|
@@ -33342,7 +33216,7 @@ function walkMarkdownFiles2(dir, root2) {
|
|
|
33342
33216
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
33343
33217
|
if (entry.name.startsWith("."))
|
|
33344
33218
|
continue;
|
|
33345
|
-
const fullPath =
|
|
33219
|
+
const fullPath = join22(dir, entry.name);
|
|
33346
33220
|
if (entry.isDirectory()) {
|
|
33347
33221
|
files.push(...walkMarkdownFiles2(fullPath, root2));
|
|
33348
33222
|
continue;
|
|
@@ -33356,14 +33230,14 @@ function walkMarkdownFiles2(dir, root2) {
|
|
|
33356
33230
|
function collectGeneratedInDir(input) {
|
|
33357
33231
|
const { dir, refsDir, skillDir, files } = input;
|
|
33358
33232
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
33359
|
-
const fullPath =
|
|
33233
|
+
const fullPath = join22(dir, entry.name);
|
|
33360
33234
|
if (entry.isDirectory()) {
|
|
33361
33235
|
collectGeneratedInDir({ dir: fullPath, refsDir, skillDir, files });
|
|
33362
33236
|
continue;
|
|
33363
33237
|
}
|
|
33364
33238
|
if (!entry.name.endsWith(".md"))
|
|
33365
33239
|
continue;
|
|
33366
|
-
const content3 =
|
|
33240
|
+
const content3 = readFileSync23(fullPath, "utf8");
|
|
33367
33241
|
if (!isGeneratedReference(content3))
|
|
33368
33242
|
continue;
|
|
33369
33243
|
const refPath = normalizeRelPath(relative13(refsDir, fullPath));
|
|
@@ -33371,7 +33245,7 @@ function collectGeneratedInDir(input) {
|
|
|
33371
33245
|
}
|
|
33372
33246
|
}
|
|
33373
33247
|
function listGeneratedReferenceFiles(root2, skillDir) {
|
|
33374
|
-
const refsDir =
|
|
33248
|
+
const refsDir = join22(root2, skillDir, "references");
|
|
33375
33249
|
if (!existsSync25(refsDir))
|
|
33376
33250
|
return [];
|
|
33377
33251
|
const files = [];
|
|
@@ -33380,18 +33254,18 @@ function listGeneratedReferenceFiles(root2, skillDir) {
|
|
|
33380
33254
|
}
|
|
33381
33255
|
function syncGeneratedCopy(ctx, refPath) {
|
|
33382
33256
|
const { root: root2, plan, options, result } = ctx;
|
|
33383
|
-
const sourceRel = normalizeRelPath(
|
|
33384
|
-
const canonicalPath =
|
|
33257
|
+
const sourceRel = normalizeRelPath(join22(CANONICAL_REFS_DIR, refPath));
|
|
33258
|
+
const canonicalPath = join22(root2, sourceRel);
|
|
33385
33259
|
if (!existsSync25(canonicalPath)) {
|
|
33386
33260
|
throw new Error(`canonical reference missing: ${sourceRel}`);
|
|
33387
33261
|
}
|
|
33388
33262
|
const targetRel = generatedRefPath(plan.skillDir, refPath);
|
|
33389
|
-
const targetPath =
|
|
33390
|
-
const canonicalContent =
|
|
33263
|
+
const targetPath = join22(root2, targetRel);
|
|
33264
|
+
const canonicalContent = readFileSync23(canonicalPath, "utf8");
|
|
33391
33265
|
const nextContent = formatGeneratedHeader(sourceRel) + canonicalContent;
|
|
33392
33266
|
if (!options.dryRun)
|
|
33393
33267
|
mkdirSync5(dirname14(targetPath), { recursive: true });
|
|
33394
|
-
const existing = existsSync25(targetPath) ?
|
|
33268
|
+
const existing = existsSync25(targetPath) ? readFileSync23(targetPath, "utf8") : null;
|
|
33395
33269
|
if (existing !== nextContent) {
|
|
33396
33270
|
if (!options.dryRun)
|
|
33397
33271
|
writeFileSync6(targetPath, nextContent, "utf8");
|
|
@@ -33405,8 +33279,8 @@ function rewritePlanLinks(ctx, skillDir) {
|
|
|
33405
33279
|
if (options.rewriteLinks === false)
|
|
33406
33280
|
return;
|
|
33407
33281
|
for (const relFile of walkMarkdownFiles2(skillDir, root2)) {
|
|
33408
|
-
const filePath =
|
|
33409
|
-
const content3 =
|
|
33282
|
+
const filePath = join22(root2, relFile);
|
|
33283
|
+
const content3 = readFileSync23(filePath, "utf8");
|
|
33410
33284
|
const next = rewriteSharedRefLinks(content3, relFile, plan.skillDir);
|
|
33411
33285
|
if (next === content3)
|
|
33412
33286
|
continue;
|
|
@@ -33422,12 +33296,12 @@ function removeStaleGenerated(ctx) {
|
|
|
33422
33296
|
if (plan.refPaths.has(refPath))
|
|
33423
33297
|
continue;
|
|
33424
33298
|
if (!options.dryRun)
|
|
33425
|
-
unlinkSync2(
|
|
33299
|
+
unlinkSync2(join22(root2, generatedRel));
|
|
33426
33300
|
result.removed.push(generatedRel);
|
|
33427
33301
|
}
|
|
33428
33302
|
}
|
|
33429
33303
|
function syncPlan(ctx) {
|
|
33430
|
-
const skillDir =
|
|
33304
|
+
const skillDir = join22(ctx.root, ctx.plan.skillDir);
|
|
33431
33305
|
for (const refPath of ctx.plan.refPaths) {
|
|
33432
33306
|
syncGeneratedCopy(ctx, refPath);
|
|
33433
33307
|
}
|
|
@@ -33436,7 +33310,7 @@ function syncPlan(ctx) {
|
|
|
33436
33310
|
}
|
|
33437
33311
|
function syncReferences(options = {}) {
|
|
33438
33312
|
const root2 = options.root ?? process7.cwd();
|
|
33439
|
-
const canonicalDir =
|
|
33313
|
+
const canonicalDir = join22(root2, CANONICAL_REFS_DIR);
|
|
33440
33314
|
if (!existsSync25(canonicalDir)) {
|
|
33441
33315
|
throw new Error(`canonical references dir not found: ${CANONICAL_REFS_DIR}`);
|
|
33442
33316
|
}
|
|
@@ -33484,27 +33358,47 @@ function printSyncResult(result) {
|
|
|
33484
33358
|
|
|
33485
33359
|
// src/validate/changed.ts
|
|
33486
33360
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
33487
|
-
import { existsSync as existsSync26, readFileSync as
|
|
33488
|
-
import { basename as basename5, extname as extname2, join as
|
|
33361
|
+
import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
|
|
33362
|
+
import { basename as basename5, extname as extname2, join as join23 } from "node:path";
|
|
33489
33363
|
|
|
33490
33364
|
// src/validate/git-diff.ts
|
|
33491
33365
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
33492
33366
|
function gitDiffChangedFiles(options = {}) {
|
|
33493
33367
|
const root2 = options.root ?? findRepoRoot();
|
|
33494
|
-
|
|
33495
|
-
if (options.staged) {
|
|
33496
|
-
args = ["diff", "--cached", "--name-only", "--diff-filter=ACMR"];
|
|
33497
|
-
} else if (options.base) {
|
|
33498
|
-
args = ["diff", `${options.base}...HEAD`, "--name-only", "--diff-filter=ACMR"];
|
|
33499
|
-
} else {
|
|
33500
|
-
args = ["diff", "HEAD", "--name-only", "--diff-filter=ACMR"];
|
|
33501
|
-
}
|
|
33502
|
-
const proc = spawnSync4("git", args, { cwd: root2, encoding: "utf8" });
|
|
33368
|
+
const proc = spawnSync4("git", gitDiffArgs(options), { cwd: root2, encoding: "utf8" });
|
|
33503
33369
|
if (proc.status !== 0) {
|
|
33504
33370
|
throw new Error(proc.stderr?.trim() || "git diff failed");
|
|
33505
33371
|
}
|
|
33506
|
-
return proc.stdout
|
|
33507
|
-
|
|
33372
|
+
return parseChangedPaths(proc.stdout);
|
|
33373
|
+
}
|
|
33374
|
+
function gitDiffArgs(options) {
|
|
33375
|
+
const prefix = options.staged ? ["diff", "--cached"] : ["diff", options.base ? `${options.base}...HEAD` : "HEAD"];
|
|
33376
|
+
return [...prefix, "--name-status", "--find-renames", "--diff-filter=ACMRD"];
|
|
33377
|
+
}
|
|
33378
|
+
function parseChangedPaths(output) {
|
|
33379
|
+
const changed = new Map;
|
|
33380
|
+
for (const line of output.split(`
|
|
33381
|
+
`))
|
|
33382
|
+
addChangedLine(changed, line);
|
|
33383
|
+
return [...changed.values()];
|
|
33384
|
+
}
|
|
33385
|
+
function addChangedLine(changed, line) {
|
|
33386
|
+
const [status = "", first = "", second] = line.split("\t");
|
|
33387
|
+
if (!first)
|
|
33388
|
+
return;
|
|
33389
|
+
const paths = status.startsWith("R") || status.startsWith("C") ? [first, second] : [first];
|
|
33390
|
+
for (const raw of paths)
|
|
33391
|
+
addChangedPath({ changed, status, first, raw });
|
|
33392
|
+
}
|
|
33393
|
+
function addChangedPath(input) {
|
|
33394
|
+
const { changed, status, first, raw } = input;
|
|
33395
|
+
if (!raw)
|
|
33396
|
+
return;
|
|
33397
|
+
const path3 = normalizeRelPath(raw);
|
|
33398
|
+
changed.set(path3, {
|
|
33399
|
+
path: path3,
|
|
33400
|
+
deleted: status.startsWith("D") || status.startsWith("R") && raw === first
|
|
33401
|
+
});
|
|
33508
33402
|
}
|
|
33509
33403
|
|
|
33510
33404
|
// src/validate/changed.ts
|
|
@@ -33583,25 +33477,25 @@ function validationIssue(code3, file, message) {
|
|
|
33583
33477
|
return issue("validate-changed", file, { code: code3, message, severity: "error" });
|
|
33584
33478
|
}
|
|
33585
33479
|
function validateJson(relPath2, root2) {
|
|
33586
|
-
const abs =
|
|
33480
|
+
const abs = join23(root2, relPath2);
|
|
33587
33481
|
try {
|
|
33588
|
-
parseJsonContent(
|
|
33482
|
+
parseJsonContent(readFileSync24(abs, "utf8"));
|
|
33589
33483
|
return null;
|
|
33590
33484
|
} catch (error) {
|
|
33591
33485
|
return validationIssue("invalid-json", relPath2, `invalid JSON: ${error}`);
|
|
33592
33486
|
}
|
|
33593
33487
|
}
|
|
33594
33488
|
function validatePolicy(relPath2, root2) {
|
|
33595
|
-
const abs =
|
|
33489
|
+
const abs = join23(root2, relPath2);
|
|
33596
33490
|
try {
|
|
33597
|
-
loadPolicyFile(abs,
|
|
33491
|
+
loadPolicyFile(abs, readFileSync24(abs, "utf8"));
|
|
33598
33492
|
return null;
|
|
33599
33493
|
} catch (error) {
|
|
33600
33494
|
return validationIssue("invalid-policy", relPath2, `invalid policy: ${error}`);
|
|
33601
33495
|
}
|
|
33602
33496
|
}
|
|
33603
33497
|
function validateShell(relPath2, root2) {
|
|
33604
|
-
const abs =
|
|
33498
|
+
const abs = join23(root2, relPath2);
|
|
33605
33499
|
const shellcheck = spawnSync5("shellcheck", [abs], { encoding: "utf8" });
|
|
33606
33500
|
if (shellcheck.status === 0)
|
|
33607
33501
|
return null;
|
|
@@ -33612,20 +33506,24 @@ function validateShell(relPath2, root2) {
|
|
|
33612
33506
|
}
|
|
33613
33507
|
function resolvePaths(options) {
|
|
33614
33508
|
if (options.paths && options.paths.length > 0) {
|
|
33615
|
-
return options.paths.map((p) => normalizeRelPath(p));
|
|
33509
|
+
return { paths: options.paths.map((p) => normalizeRelPath(p)), deleted: new Set };
|
|
33616
33510
|
}
|
|
33617
|
-
|
|
33511
|
+
const changed = gitDiffChangedFiles({
|
|
33618
33512
|
staged: options.staged,
|
|
33619
33513
|
base: options.base,
|
|
33620
33514
|
root: options.root
|
|
33621
33515
|
});
|
|
33516
|
+
return {
|
|
33517
|
+
paths: changed.map((entry) => entry.path),
|
|
33518
|
+
deleted: new Set(changed.filter((entry) => entry.deleted).map((entry) => entry.path))
|
|
33519
|
+
};
|
|
33622
33520
|
}
|
|
33623
33521
|
function packageManagerFromPackageJson(root2) {
|
|
33624
|
-
const pkgPath =
|
|
33522
|
+
const pkgPath = join23(root2, "package.json");
|
|
33625
33523
|
if (!existsSync26(pkgPath))
|
|
33626
33524
|
return null;
|
|
33627
33525
|
try {
|
|
33628
|
-
const pkg = JSON.parse(
|
|
33526
|
+
const pkg = JSON.parse(readFileSync24(pkgPath, "utf8"));
|
|
33629
33527
|
const raw = pkg.packageManager?.split("@")[0];
|
|
33630
33528
|
if (raw === "bun" || raw === "npm" || raw === "pnpm" || raw === "yarn")
|
|
33631
33529
|
return raw;
|
|
@@ -33633,13 +33531,13 @@ function packageManagerFromPackageJson(root2) {
|
|
|
33633
33531
|
return null;
|
|
33634
33532
|
}
|
|
33635
33533
|
function packageManagerFromLockfiles(root2) {
|
|
33636
|
-
if (existsSync26(
|
|
33534
|
+
if (existsSync26(join23(root2, "bun.lock")) || existsSync26(join23(root2, "bun.lockb")))
|
|
33637
33535
|
return "bun";
|
|
33638
|
-
if (existsSync26(
|
|
33536
|
+
if (existsSync26(join23(root2, "pnpm-lock.yaml")))
|
|
33639
33537
|
return "pnpm";
|
|
33640
|
-
if (existsSync26(
|
|
33538
|
+
if (existsSync26(join23(root2, "yarn.lock")))
|
|
33641
33539
|
return "yarn";
|
|
33642
|
-
if (existsSync26(
|
|
33540
|
+
if (existsSync26(join23(root2, "package-lock.json")))
|
|
33643
33541
|
return "npm";
|
|
33644
33542
|
return null;
|
|
33645
33543
|
}
|
|
@@ -33665,7 +33563,7 @@ function emptyBuckets() {
|
|
|
33665
33563
|
function classifySinglePath(input) {
|
|
33666
33564
|
const { relPath: relPath2, ctx, state, bucketCtx } = input;
|
|
33667
33565
|
const normalized = normalizeRelPath(relPath2);
|
|
33668
|
-
const abs =
|
|
33566
|
+
const abs = join23(ctx.root, normalized);
|
|
33669
33567
|
if (!existsSync26(abs)) {
|
|
33670
33568
|
state.missing.push(normalized);
|
|
33671
33569
|
return;
|
|
@@ -33730,23 +33628,29 @@ function discoverImpactedDocuments(input) {
|
|
|
33730
33628
|
const changed = new Set(input.relPaths.map(normalizeRelPath));
|
|
33731
33629
|
const impacted = [];
|
|
33732
33630
|
for (const abs of collectScanFiles(input.config, input.root, input.skillIndex)) {
|
|
33733
|
-
const
|
|
33734
|
-
|
|
33735
|
-
|
|
33736
|
-
...new Set(parseCodeFitMarkers(content3).flatMap((marker) => marker.targets.map(normalizeRelPath)))
|
|
33737
|
-
].sort();
|
|
33738
|
-
const reasons = [];
|
|
33739
|
-
if (changed.has(path3))
|
|
33740
|
-
reasons.push({ kind: "changed-document" });
|
|
33741
|
-
for (const target of codeTargets2) {
|
|
33742
|
-
if (changed.has(target))
|
|
33743
|
-
reasons.push({ kind: "changed-code-target", target });
|
|
33744
|
-
}
|
|
33745
|
-
if (reasons.length > 0)
|
|
33746
|
-
impacted.push({ path: path3, codeTargets: codeTargets2, reasons });
|
|
33631
|
+
const document4 = impactedDocumentForPath(abs, input.root, changed);
|
|
33632
|
+
if (document4)
|
|
33633
|
+
impacted.push(document4);
|
|
33747
33634
|
}
|
|
33748
33635
|
return impacted.sort((a, b) => a.path.localeCompare(b.path));
|
|
33749
33636
|
}
|
|
33637
|
+
function impactedDocumentForPath(abs, root2, changed) {
|
|
33638
|
+
const path3 = relPath(abs, root2);
|
|
33639
|
+
const reviewDependencies = reviewDependencyPatterns(readFileSync24(abs, "utf8"));
|
|
33640
|
+
const reasons = impactReasons(path3, reviewDependencies, changed);
|
|
33641
|
+
return reasons.length > 0 ? { path: path3, reviewDependencies, reasons } : null;
|
|
33642
|
+
}
|
|
33643
|
+
function impactReasons(path3, reviewDependencies, changed) {
|
|
33644
|
+
const reasons = changed.has(path3) ? [{ kind: "changed-document" }] : [];
|
|
33645
|
+
for (const dependency of reviewDependencies) {
|
|
33646
|
+
for (const target of changed) {
|
|
33647
|
+
if (reviewDependencyMatchesPath(dependency, target)) {
|
|
33648
|
+
reasons.push({ kind: "changed-review-dependency", dependency, target });
|
|
33649
|
+
}
|
|
33650
|
+
}
|
|
33651
|
+
}
|
|
33652
|
+
return reasons;
|
|
33653
|
+
}
|
|
33750
33654
|
function dateModeImpactDiagnostics(input) {
|
|
33751
33655
|
if (input.config.reviewProof)
|
|
33752
33656
|
return [];
|
|
@@ -33754,12 +33658,12 @@ function dateModeImpactDiagnostics(input) {
|
|
|
33754
33658
|
const today = formatLocalReviewDate(new Date);
|
|
33755
33659
|
const diagnostics = [];
|
|
33756
33660
|
for (const impacted of input.impactedDocuments) {
|
|
33757
|
-
if (!impacted.reasons.some((reason) => reason.kind === "changed-
|
|
33661
|
+
if (!impacted.reasons.some((reason) => reason.kind === "changed-review-dependency"))
|
|
33758
33662
|
continue;
|
|
33759
|
-
const content3 =
|
|
33663
|
+
const content3 = readFileSync24(join23(input.root, impacted.path), "utf8");
|
|
33760
33664
|
if (changed.has(impacted.path) && docMetaLastReviewed(content3) === today)
|
|
33761
33665
|
continue;
|
|
33762
|
-
diagnostics.push(validationIssue("impacted-document-review-required", impacted.path, "a linked
|
|
33666
|
+
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"));
|
|
33763
33667
|
}
|
|
33764
33668
|
return diagnostics;
|
|
33765
33669
|
}
|
|
@@ -33840,8 +33744,6 @@ function resultFor(input) {
|
|
|
33840
33744
|
const diagnostics = input.diagnostics ?? [];
|
|
33841
33745
|
const failed = diagnostics.some((item) => item.severity === "error") || audits.some((audit) => audit.exitCode !== 0);
|
|
33842
33746
|
return {
|
|
33843
|
-
schemaVersion: 1,
|
|
33844
|
-
command: "validate-changed",
|
|
33845
33747
|
ok: !failed,
|
|
33846
33748
|
exitCode: failed ? 1 : 0,
|
|
33847
33749
|
input: {
|
|
@@ -33871,7 +33773,8 @@ function emptyClassification() {
|
|
|
33871
33773
|
}
|
|
33872
33774
|
async function evaluateValidateChanged(options = {}) {
|
|
33873
33775
|
const root2 = options.root ?? findRepoRoot();
|
|
33874
|
-
const
|
|
33776
|
+
const resolvedPaths = resolvePaths(options);
|
|
33777
|
+
const relPaths = resolvedPaths.paths;
|
|
33875
33778
|
if (relPaths.length === 0) {
|
|
33876
33779
|
return resultFor({ options, relPaths, classification: emptyClassification() });
|
|
33877
33780
|
}
|
|
@@ -33896,6 +33799,7 @@ async function evaluateValidateChanged(options = {}) {
|
|
|
33896
33799
|
wiredPolicies,
|
|
33897
33800
|
skillIndex
|
|
33898
33801
|
});
|
|
33802
|
+
classification.missing = classification.missing.filter((path3) => !resolvedPaths.deleted.has(path3));
|
|
33899
33803
|
const impactedDocuments = discoverImpactedDocuments({ relPaths, root: root2, config, skillIndex });
|
|
33900
33804
|
for (const impacted of impactedDocuments) {
|
|
33901
33805
|
if (!classification.buckets.docs.includes(impacted.path)) {
|
|
@@ -33929,11 +33833,7 @@ async function evaluateValidateChanged(options = {}) {
|
|
|
33929
33833
|
diagnostics: evaluated.diagnostics
|
|
33930
33834
|
});
|
|
33931
33835
|
}
|
|
33932
|
-
function printValidateChangedResult(result
|
|
33933
|
-
if (json) {
|
|
33934
|
-
console.log(JSON.stringify(result, null, 2));
|
|
33935
|
-
return result.exitCode;
|
|
33936
|
-
}
|
|
33836
|
+
function printValidateChangedResult(result) {
|
|
33937
33837
|
if (result.input.paths.length === 0) {
|
|
33938
33838
|
console.log("validate changed: no changed files.");
|
|
33939
33839
|
return result.exitCode;
|
|
@@ -33943,6 +33843,13 @@ function printValidateChangedResult(result, json) {
|
|
|
33943
33843
|
}
|
|
33944
33844
|
for (const audit of result.audits)
|
|
33945
33845
|
printAuditResult(audit, false);
|
|
33846
|
+
for (const impacted of result.impactedDocuments) {
|
|
33847
|
+
for (const reason of impacted.reasons) {
|
|
33848
|
+
if (reason.kind !== "changed-review-dependency")
|
|
33849
|
+
continue;
|
|
33850
|
+
console.log(`validate changed: ${impacted.path} requires review (dependency ${reason.dependency} matched ${reason.target})`);
|
|
33851
|
+
}
|
|
33852
|
+
}
|
|
33946
33853
|
for (const diagnostic of result.diagnostics) {
|
|
33947
33854
|
const path3 = diagnostic.file === "." ? "" : `${diagnostic.file}: `;
|
|
33948
33855
|
console.error(`validate changed: ${path3}${diagnostic.message}`);
|
|
@@ -33963,7 +33870,7 @@ function printValidateChangedResult(result, json) {
|
|
|
33963
33870
|
return result.exitCode;
|
|
33964
33871
|
}
|
|
33965
33872
|
async function runValidateChanged(options = {}) {
|
|
33966
|
-
return printValidateChangedResult(await evaluateValidateChanged(options)
|
|
33873
|
+
return printValidateChangedResult(await evaluateValidateChanged(options));
|
|
33967
33874
|
}
|
|
33968
33875
|
|
|
33969
33876
|
// src/cli.ts
|
|
@@ -33976,7 +33883,7 @@ Commands:
|
|
|
33976
33883
|
[--fix[=doc-meta|anchors|ssot]] [--dry-run]
|
|
33977
33884
|
[--confirm-reviewed (doc-meta only; requires --paths)]
|
|
33978
33885
|
build-plugin [path] [--check]
|
|
33979
|
-
validate changed [paths…] [--staged] [--base <ref>]
|
|
33886
|
+
validate changed [paths…] [--staged] [--base <ref>]
|
|
33980
33887
|
catalog [--check] [--strict] write or check .skeleton/catalog.md (gitignored)
|
|
33981
33888
|
customize resolve <slug> [--json]
|
|
33982
33889
|
hook customize (reads a host hook payload on stdin)
|
|
@@ -33989,13 +33896,10 @@ function parseValidateChangedArgs(rest) {
|
|
|
33989
33896
|
const paths = [];
|
|
33990
33897
|
let staged = false;
|
|
33991
33898
|
let base;
|
|
33992
|
-
let json = false;
|
|
33993
33899
|
for (let i = 0;i < rest.length; i++) {
|
|
33994
33900
|
const arg = rest[i];
|
|
33995
33901
|
if (arg === "--staged")
|
|
33996
33902
|
staged = true;
|
|
33997
|
-
else if (arg === "--json")
|
|
33998
|
-
json = true;
|
|
33999
33903
|
else if (arg === "--base") {
|
|
34000
33904
|
const value = rest[++i];
|
|
34001
33905
|
if (!value || value.startsWith("-"))
|
|
@@ -34010,7 +33914,7 @@ function parseValidateChangedArgs(rest) {
|
|
|
34010
33914
|
else if (arg && !arg.startsWith("-"))
|
|
34011
33915
|
paths.push(arg);
|
|
34012
33916
|
}
|
|
34013
|
-
return { paths, staged, base
|
|
33917
|
+
return { paths, staged, base };
|
|
34014
33918
|
}
|
|
34015
33919
|
async function handleAudit(argv) {
|
|
34016
33920
|
const sub = argv[0];
|
|
@@ -34034,8 +33938,8 @@ async function handleBuildPlugin(argv) {
|
|
|
34034
33938
|
return 0;
|
|
34035
33939
|
}
|
|
34036
33940
|
async function handleValidateChanged(argv) {
|
|
34037
|
-
const { paths, staged, base
|
|
34038
|
-
return runValidateChanged({ paths, staged, base
|
|
33941
|
+
const { paths, staged, base } = parseValidateChangedArgs(argv);
|
|
33942
|
+
return runValidateChanged({ paths, staged, base });
|
|
34039
33943
|
}
|
|
34040
33944
|
function handleRegister() {
|
|
34041
33945
|
console.error("register: removed — add `<!-- source-of-truth: … -->` (or visible `source-of-truth:`) to the file, then run `skeleton catalog`.");
|
|
@@ -34069,7 +33973,7 @@ function handleHook(argv) {
|
|
|
34069
33973
|
usage();
|
|
34070
33974
|
return 1;
|
|
34071
33975
|
}
|
|
34072
|
-
process9.stdout.write(runCustomizeHook(
|
|
33976
|
+
process9.stdout.write(runCustomizeHook(readFileSync25(0, "utf8")));
|
|
34073
33977
|
return 0;
|
|
34074
33978
|
}
|
|
34075
33979
|
function handleInit(argv) {
|