@csark0812/skeleton 1.1.3 → 1.5.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 +5 -1
- package/dist/cli.js +2070 -909
- package/dist/hooks/customize-on-skill-read.js +27 -2
- package/dist/plugin-types.d.ts +117 -0
- package/dist/plugin-types.js +41 -0
- package/package.json +14 -2
- package/schemas/config.schema.json +7 -2
- package/schemas/policy-file.schema.json +41 -0
- package/templates/skeleton-init/config.yaml +4 -0
package/dist/cli.js
CHANGED
|
@@ -15703,6 +15703,106 @@ var $stringify = publicApi.stringify;
|
|
|
15703
15703
|
var $visit = visit.visit;
|
|
15704
15704
|
var $visitAsync = visit.visitAsync;
|
|
15705
15705
|
|
|
15706
|
+
// src/audit/core/shared.ts
|
|
15707
|
+
var REGISTRY_REL_PATH = ".skeleton/registry.md";
|
|
15708
|
+
var REGISTRY_DIR_REL = ".skeleton";
|
|
15709
|
+
var EXTERNAL_LINK_RE = /^(https?:|mailto:|#)/;
|
|
15710
|
+
var SOURCE_OF_TRUTH_BANNER_RE = /\*\*Source of truth for\*\*/;
|
|
15711
|
+
var SOURCE_OF_TRUTH_BANNER_LINE_RE = /^\s*\*\*Source of truth for\*\*/m;
|
|
15712
|
+
var DOC_META_RE = /<!--\s*doc-meta:\s*owner=[^|]+\|\s*last-reviewed=\d{4}-\d{2}-\d{2}\s*-->/;
|
|
15713
|
+
var DOC_META_LAST_REVIEWED_RE = /last-reviewed=(\d{4}-\d{2}-\d{2})/;
|
|
15714
|
+
function docMetaLastReviewed(content) {
|
|
15715
|
+
const meta = DOC_META_RE.exec(content);
|
|
15716
|
+
if (!meta?.[0])
|
|
15717
|
+
return null;
|
|
15718
|
+
const match = DOC_META_LAST_REVIEWED_RE.exec(meta[0]);
|
|
15719
|
+
return match?.[1] ?? null;
|
|
15720
|
+
}
|
|
15721
|
+
function replaceDocMetaLastReviewed(content, date) {
|
|
15722
|
+
const meta = DOC_META_RE.exec(content);
|
|
15723
|
+
if (!meta?.[0] || meta.index === undefined)
|
|
15724
|
+
return null;
|
|
15725
|
+
const updatedComment = meta[0].replace(DOC_META_LAST_REVIEWED_RE, `last-reviewed=${date}`);
|
|
15726
|
+
if (updatedComment === meta[0])
|
|
15727
|
+
return null;
|
|
15728
|
+
return content.slice(0, meta.index) + updatedComment + content.slice(meta.index + meta[0].length);
|
|
15729
|
+
}
|
|
15730
|
+
var SKILL_LINK_IN_TARGET_RE = /(?:\.claude\/skills\/|\.agents\/skills\/|(?:\.\.\/)+)([a-z0-9-]+)\/SKILL\.md/;
|
|
15731
|
+
var SKILL_LINK_RE = /(?:\.claude\/skills\/|\.agents\/skills\/|\.\.\/|\.\/)?([a-z0-9-]+)\/SKILL\.md/g;
|
|
15732
|
+
function normalizeRelPath(p) {
|
|
15733
|
+
let out = p.replace(/\\/g, "/");
|
|
15734
|
+
if (out.startsWith("/"))
|
|
15735
|
+
return out;
|
|
15736
|
+
while (out.startsWith("./")) {
|
|
15737
|
+
out = out.slice(2);
|
|
15738
|
+
}
|
|
15739
|
+
return out;
|
|
15740
|
+
}
|
|
15741
|
+
function isExternalLink(target) {
|
|
15742
|
+
return EXTERNAL_LINK_RE.test(target);
|
|
15743
|
+
}
|
|
15744
|
+
function isPlaceholderLink(target) {
|
|
15745
|
+
return !target.includes("/") && !target.includes(".") && !target.startsWith("#");
|
|
15746
|
+
}
|
|
15747
|
+
function escapeRegexLiteral(s) {
|
|
15748
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15749
|
+
}
|
|
15750
|
+
function globToRegex(glob) {
|
|
15751
|
+
let pattern = glob.replace(/\\/g, "/");
|
|
15752
|
+
pattern = pattern.replace(/\{([^}]+)\}/g, (_, inner) => {
|
|
15753
|
+
const parts = inner.split(",").map((p) => escapeRegexLiteral(p.trim()));
|
|
15754
|
+
return `(${parts.join("|")})`;
|
|
15755
|
+
});
|
|
15756
|
+
pattern = pattern.replace(/\*\*/g, "§§").replace(/\*/g, "[^/]*").replace(/§§/g, ".*").replace(/\?/g, "[^/]");
|
|
15757
|
+
return new RegExp(`^${pattern}$`);
|
|
15758
|
+
}
|
|
15759
|
+
function matchesGlobScope(relPath, scope) {
|
|
15760
|
+
if (!scope)
|
|
15761
|
+
return true;
|
|
15762
|
+
return globToRegex(scope).test(normalizeRelPath(relPath));
|
|
15763
|
+
}
|
|
15764
|
+
function extractScanRootsFromInclude(include) {
|
|
15765
|
+
const roots = new Set;
|
|
15766
|
+
for (const pattern of include) {
|
|
15767
|
+
const normalized = normalizeRelPath(pattern);
|
|
15768
|
+
const globIdx = normalized.search(/[*?[{]/);
|
|
15769
|
+
if (globIdx === -1) {
|
|
15770
|
+
if (/\.[a-z0-9]+$/i.test(normalized))
|
|
15771
|
+
continue;
|
|
15772
|
+
roots.add(normalized.replace(/\/$/, ""));
|
|
15773
|
+
continue;
|
|
15774
|
+
}
|
|
15775
|
+
const root = normalized.slice(0, globIdx).replace(/\/$/, "");
|
|
15776
|
+
if (root)
|
|
15777
|
+
roots.add(root);
|
|
15778
|
+
}
|
|
15779
|
+
return [...roots];
|
|
15780
|
+
}
|
|
15781
|
+
|
|
15782
|
+
// src/audit/core/draft.ts
|
|
15783
|
+
var DRAFT_FILENAME_RE = /(^|\/)_draft-[^/]+\.md$/i;
|
|
15784
|
+
function normalizeDraftPrefix(prefix) {
|
|
15785
|
+
const normalized = normalizeRelPath(prefix);
|
|
15786
|
+
const withSlash = normalized.endsWith("/") ? normalized : `${normalized}/`;
|
|
15787
|
+
if (withSlash === "/" || withSlash === "./") {
|
|
15788
|
+
throw new Error(`Invalid draftPathPrefixes entry ${JSON.stringify(prefix)}: must be a repo-relative directory (e.g. drafts/), not ${JSON.stringify(prefix)}`);
|
|
15789
|
+
}
|
|
15790
|
+
return withSlash;
|
|
15791
|
+
}
|
|
15792
|
+
function validateDraftPathPrefixes(prefixes) {
|
|
15793
|
+
if (!prefixes)
|
|
15794
|
+
return;
|
|
15795
|
+
for (const prefix of prefixes) {
|
|
15796
|
+
normalizeDraftPrefix(prefix);
|
|
15797
|
+
}
|
|
15798
|
+
}
|
|
15799
|
+
function isDraftPlacementAllowed(relPath, draftPathPrefixes) {
|
|
15800
|
+
const normalized = normalizeRelPath(relPath);
|
|
15801
|
+
if (DRAFT_FILENAME_RE.test(normalized))
|
|
15802
|
+
return true;
|
|
15803
|
+
return draftPathPrefixes.some((prefix) => normalized.startsWith(normalizeDraftPrefix(prefix)));
|
|
15804
|
+
}
|
|
15805
|
+
|
|
15706
15806
|
// src/audit/config/load.ts
|
|
15707
15807
|
var SCHEMA_CANDIDATES = [
|
|
15708
15808
|
join(dirname(fileURLToPath(import.meta.url)), "../../../schemas/config.schema.json"),
|
|
@@ -15743,7 +15843,9 @@ function validateConfig(raw) {
|
|
|
15743
15843
|
const detail = validate.errors?.map((e) => `${e.instancePath || "/"} ${e.message}`).join("; ");
|
|
15744
15844
|
throw new Error(`Invalid .skeleton/config.yaml: ${detail ?? "schema validation failed"}`);
|
|
15745
15845
|
}
|
|
15746
|
-
|
|
15846
|
+
const config = raw;
|
|
15847
|
+
validateDraftPathPrefixes(config.draftPathPrefixes);
|
|
15848
|
+
return config;
|
|
15747
15849
|
}
|
|
15748
15850
|
function loadConfig(root) {
|
|
15749
15851
|
const configPath = join(root, ".skeleton", "config.yaml");
|
|
@@ -15763,9 +15865,10 @@ function nonPublicSkills(config) {
|
|
|
15763
15865
|
return config.scan.nonPublicSkills ?? [];
|
|
15764
15866
|
}
|
|
15765
15867
|
|
|
15766
|
-
// src/
|
|
15767
|
-
import { existsSync as
|
|
15768
|
-
import {
|
|
15868
|
+
// src/plugins/load.ts
|
|
15869
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
15870
|
+
import { relative as relative2 } from "node:path";
|
|
15871
|
+
import { pathToFileURL } from "node:url";
|
|
15769
15872
|
|
|
15770
15873
|
// node_modules/tinyglobby/dist/index.mjs
|
|
15771
15874
|
import { readdir, readdirSync, realpath, realpathSync, stat, statSync } from "fs";
|
|
@@ -16593,63 +16696,300 @@ function globSync(globInput, options) {
|
|
|
16593
16696
|
return crawler ? formatPaths(crawler.sync(), relative2) : [];
|
|
16594
16697
|
}
|
|
16595
16698
|
|
|
16596
|
-
// src/audit/
|
|
16597
|
-
var
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
var
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
function
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16699
|
+
// src/audit/policies/load.ts
|
|
16700
|
+
var import_ajv2 = __toESM(require_ajv(), 1);
|
|
16701
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
16702
|
+
import { basename as basename2, dirname as dirname3, extname, join as join2 } from "node:path";
|
|
16703
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
16704
|
+
var SCHEMA_CANDIDATES2 = [
|
|
16705
|
+
join2(dirname3(fileURLToPath3(import.meta.url)), "../../../schemas/policy-file.schema.json"),
|
|
16706
|
+
join2(dirname3(fileURLToPath3(import.meta.url)), "../schemas/policy-file.schema.json"),
|
|
16707
|
+
join2(dirname3(fileURLToPath3(import.meta.url)), "../../schemas/policy-file.schema.json")
|
|
16708
|
+
];
|
|
16709
|
+
function resolvePolicySchemaPath() {
|
|
16710
|
+
for (const candidate of SCHEMA_CANDIDATES2) {
|
|
16711
|
+
if (existsSync2(candidate))
|
|
16712
|
+
return candidate;
|
|
16713
|
+
}
|
|
16714
|
+
throw new Error("Missing schemas/policy-file.schema.json in package");
|
|
16611
16715
|
}
|
|
16612
|
-
function
|
|
16613
|
-
|
|
16716
|
+
function validatePolicyYaml(raw, label) {
|
|
16717
|
+
const schema = JSON.parse(readFileSync2(resolvePolicySchemaPath(), "utf8"));
|
|
16718
|
+
const ajv = new import_ajv2.default({ allErrors: true, strict: false });
|
|
16719
|
+
const validate = ajv.compile(schema);
|
|
16720
|
+
if (!validate(raw)) {
|
|
16721
|
+
const detail = validate.errors?.map((e) => `${e.instancePath || "/"} ${e.message}`).join("; ");
|
|
16722
|
+
throw new Error(`Invalid policy ${label}: ${detail ?? "schema validation failed"}`);
|
|
16723
|
+
}
|
|
16724
|
+
return raw;
|
|
16614
16725
|
}
|
|
16615
|
-
function
|
|
16616
|
-
|
|
16726
|
+
function parsePolicyYaml(content, fileStem) {
|
|
16727
|
+
const parsed = $parse(content);
|
|
16728
|
+
if (Array.isArray(parsed)) {
|
|
16729
|
+
throw new Error(`Policy ${fileStem}.yaml must use Policy File shape (name + entries) — see schemas/policy-file.schema.json`);
|
|
16730
|
+
}
|
|
16731
|
+
if (!parsed || typeof parsed !== "object") {
|
|
16732
|
+
throw new Error(`Policy ${fileStem}.yaml missing required 'entries' array`);
|
|
16733
|
+
}
|
|
16734
|
+
return validatePolicyYaml(parsed, `${fileStem}.yaml`);
|
|
16617
16735
|
}
|
|
16618
|
-
function
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
const
|
|
16622
|
-
|
|
16736
|
+
function compilePolicy(name, raw) {
|
|
16737
|
+
const caseInsensitive = name !== "skill-hub-duplication";
|
|
16738
|
+
const entries = raw.map((entry) => {
|
|
16739
|
+
const mode = entry.mode ?? "pattern";
|
|
16740
|
+
if (mode === "fingerprint") {
|
|
16741
|
+
return { ...entry, mode, regex: null };
|
|
16742
|
+
}
|
|
16743
|
+
if (!entry.pattern) {
|
|
16744
|
+
throw new Error(`Policy ${name} entry ${entry.id} requires pattern when mode is pattern`);
|
|
16745
|
+
}
|
|
16746
|
+
let regex;
|
|
16747
|
+
try {
|
|
16748
|
+
const flags = entry.pattern.startsWith("^") || !caseInsensitive ? "" : "i";
|
|
16749
|
+
regex = new RegExp(entry.pattern, flags);
|
|
16750
|
+
} catch (err) {
|
|
16751
|
+
throw new Error(`Invalid regex in policy ${name} entry ${entry.id}: ${entry.pattern} — ${err}`);
|
|
16752
|
+
}
|
|
16753
|
+
return { ...entry, mode, regex };
|
|
16623
16754
|
});
|
|
16624
|
-
|
|
16625
|
-
return new RegExp(`^${pattern}$`);
|
|
16755
|
+
return { name, entries };
|
|
16626
16756
|
}
|
|
16627
|
-
function
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
return
|
|
16757
|
+
function loadPolicyFile(absPath, content) {
|
|
16758
|
+
const stem = basename2(absPath, extname(absPath));
|
|
16759
|
+
const { name, entries } = parsePolicyYaml(content, stem);
|
|
16760
|
+
return compilePolicy(name || stem, entries);
|
|
16631
16761
|
}
|
|
16632
|
-
function
|
|
16633
|
-
const
|
|
16634
|
-
for (const
|
|
16635
|
-
const
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16762
|
+
function policiesForFile(policies, relPath) {
|
|
16763
|
+
const matched = [];
|
|
16764
|
+
for (const policy of policies) {
|
|
16765
|
+
for (const entry of policy.entries) {
|
|
16766
|
+
if (matchesGlobScope(relPath, entry.scope)) {
|
|
16767
|
+
matched.push({ ...entry, policyName: policy.name });
|
|
16768
|
+
}
|
|
16769
|
+
}
|
|
16770
|
+
}
|
|
16771
|
+
return matched;
|
|
16772
|
+
}
|
|
16773
|
+
|
|
16774
|
+
// src/plugins/paths.ts
|
|
16775
|
+
import { existsSync as existsSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
16776
|
+
import { dirname as dirname4, join as join3, resolve as resolve3, sep as sep2 } from "node:path";
|
|
16777
|
+
function skeletonDir(root) {
|
|
16778
|
+
return join3(root, ".skeleton");
|
|
16779
|
+
}
|
|
16780
|
+
function mjsPathForTs(tsPath) {
|
|
16781
|
+
if (tsPath.endsWith(".mjs"))
|
|
16782
|
+
return tsPath;
|
|
16783
|
+
if (tsPath.endsWith(".ts"))
|
|
16784
|
+
return `${tsPath.slice(0, -3)}.mjs`;
|
|
16785
|
+
return `${tsPath}.mjs`;
|
|
16786
|
+
}
|
|
16787
|
+
function underBase(pathAbs, baseAbs) {
|
|
16788
|
+
return pathAbs !== baseAbs && pathAbs.startsWith(baseAbs + sep2);
|
|
16789
|
+
}
|
|
16790
|
+
function assertUnderBase(abs, base, label) {
|
|
16791
|
+
const baseAbs = resolve3(base);
|
|
16792
|
+
const pathAbs = resolve3(abs);
|
|
16793
|
+
if (!underBase(pathAbs, baseAbs)) {
|
|
16794
|
+
throw new Error(`${label} must stay under .skeleton/: ${abs}`);
|
|
16795
|
+
}
|
|
16796
|
+
}
|
|
16797
|
+
function assertRealUnderBase(abs, baseReal, label) {
|
|
16798
|
+
const pathAbs = resolve3(abs);
|
|
16799
|
+
let cursor = pathAbs;
|
|
16800
|
+
while (true) {
|
|
16801
|
+
if (existsSync3(cursor)) {
|
|
16802
|
+
const real = realpathSync2(cursor);
|
|
16803
|
+
if (real !== baseReal && !underBase(real, baseReal)) {
|
|
16804
|
+
throw new Error(`${label} must stay under .skeleton/: ${abs}`);
|
|
16805
|
+
}
|
|
16806
|
+
return;
|
|
16807
|
+
}
|
|
16808
|
+
const parent = dirname4(cursor);
|
|
16809
|
+
if (parent === cursor) {
|
|
16810
|
+
throw new Error(`${label} must stay under .skeleton/: ${abs}`);
|
|
16811
|
+
}
|
|
16812
|
+
cursor = parent;
|
|
16813
|
+
}
|
|
16814
|
+
}
|
|
16815
|
+
function skeletonRealPath(root) {
|
|
16816
|
+
const base = resolve3(skeletonDir(root));
|
|
16817
|
+
return existsSync3(base) ? realpathSync2(base) : base;
|
|
16818
|
+
}
|
|
16819
|
+
function resolvePluginTsPath(root, entry) {
|
|
16820
|
+
if (entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry)) {
|
|
16821
|
+
throw new Error(`Plugin path must be relative to .skeleton/: ${entry}`);
|
|
16822
|
+
}
|
|
16823
|
+
const cleaned = entry.replace(/^\.skeleton\//, "").replace(/^\.\//, "");
|
|
16824
|
+
if (!cleaned || cleaned === ".") {
|
|
16825
|
+
throw new Error(`Plugin path must stay under .skeleton/: ${entry}`);
|
|
16826
|
+
}
|
|
16827
|
+
if (cleaned.split(/[/\\]/).includes("..")) {
|
|
16828
|
+
throw new Error(`Plugin path must stay under .skeleton/: ${entry}`);
|
|
16829
|
+
}
|
|
16830
|
+
const base = resolve3(skeletonDir(root));
|
|
16831
|
+
const abs = resolve3(base, cleaned);
|
|
16832
|
+
return assertPluginTsUnderSkeleton(root, abs, entry);
|
|
16833
|
+
}
|
|
16834
|
+
function resolveAbsolutePluginTsPath(root, absEntry) {
|
|
16835
|
+
if (!(absEntry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(absEntry))) {
|
|
16836
|
+
throw new Error(`Expected absolute plugin path: ${absEntry}`);
|
|
16837
|
+
}
|
|
16838
|
+
const abs = resolve3(absEntry);
|
|
16839
|
+
return assertPluginTsUnderSkeleton(root, abs, absEntry);
|
|
16840
|
+
}
|
|
16841
|
+
function assertPluginTsUnderSkeleton(root, abs, label) {
|
|
16842
|
+
const base = resolve3(skeletonDir(root));
|
|
16843
|
+
assertUnderBase(abs, base, "Plugin path");
|
|
16844
|
+
if (!abs.endsWith(".ts")) {
|
|
16845
|
+
throw new Error(`Plugin path must be a .ts file under .skeleton/: ${label}`);
|
|
16846
|
+
}
|
|
16847
|
+
const mjsAbs = mjsPathForTs(abs);
|
|
16848
|
+
assertUnderBase(mjsAbs, base, "Plugin output");
|
|
16849
|
+
const baseReal = skeletonRealPath(root);
|
|
16850
|
+
assertRealUnderBase(abs, baseReal, "Plugin path");
|
|
16851
|
+
assertRealUnderBase(mjsAbs, baseReal, "Plugin output");
|
|
16852
|
+
return abs;
|
|
16853
|
+
}
|
|
16854
|
+
function assertUnderSkeleton(root, absPath) {
|
|
16855
|
+
const base = resolve3(skeletonDir(root));
|
|
16856
|
+
assertUnderBase(absPath, base, "Policy file");
|
|
16857
|
+
assertRealUnderBase(absPath, skeletonRealPath(root), "Policy file");
|
|
16858
|
+
}
|
|
16859
|
+
|
|
16860
|
+
// src/plugins/load.ts
|
|
16861
|
+
function normalizeExport(mod) {
|
|
16862
|
+
if (!mod || typeof mod !== "object") {
|
|
16863
|
+
throw new Error("Plugin module must export { rules: AuditRule[]; policies?: string[] }");
|
|
16864
|
+
}
|
|
16865
|
+
const record = mod;
|
|
16866
|
+
const namedRules = Array.isArray(record.rules) ? record.rules : undefined;
|
|
16867
|
+
const namedPoliciesRaw = "policies" in record ? record.policies : undefined;
|
|
16868
|
+
const def = "default" in record && record.default && typeof record.default === "object" ? record.default : null;
|
|
16869
|
+
const defaultRules = def && Array.isArray(def.rules) ? def.rules : undefined;
|
|
16870
|
+
const defaultPoliciesRaw = def && "policies" in def ? def.policies : undefined;
|
|
16871
|
+
let rules;
|
|
16872
|
+
if (defaultRules !== undefined && namedRules !== undefined) {
|
|
16873
|
+
if (defaultRules.length !== namedRules.length || defaultRules.some((rule, i) => rule.id !== namedRules[i]?.id || rule.run !== namedRules[i]?.run)) {
|
|
16874
|
+
throw new Error("Plugin exports disagree on rules: default and named `rules` must match when both are set");
|
|
16875
|
+
}
|
|
16876
|
+
rules = defaultRules;
|
|
16877
|
+
} else {
|
|
16878
|
+
rules = defaultRules ?? namedRules;
|
|
16879
|
+
}
|
|
16880
|
+
let policies;
|
|
16881
|
+
if (defaultPoliciesRaw !== undefined && namedPoliciesRaw !== undefined) {
|
|
16882
|
+
if (!Array.isArray(defaultPoliciesRaw) || !Array.isArray(namedPoliciesRaw) || defaultPoliciesRaw.length !== namedPoliciesRaw.length || defaultPoliciesRaw.some((p, i) => p !== namedPoliciesRaw[i])) {
|
|
16883
|
+
throw new Error("Plugin exports disagree on policies: default and named `policies` must match when both are set");
|
|
16884
|
+
}
|
|
16885
|
+
policies = defaultPoliciesRaw;
|
|
16886
|
+
} else {
|
|
16887
|
+
policies = defaultPoliciesRaw ?? namedPoliciesRaw;
|
|
16888
|
+
}
|
|
16889
|
+
if (!Array.isArray(rules)) {
|
|
16890
|
+
throw new Error("Plugin module must export { rules: AuditRule[]; policies?: string[] }");
|
|
16891
|
+
}
|
|
16892
|
+
for (const rule of rules) {
|
|
16893
|
+
if (!rule || typeof rule.id !== "string" || typeof rule.run !== "function") {
|
|
16894
|
+
throw new Error("Plugin rules must each have string id and run()");
|
|
16895
|
+
}
|
|
16896
|
+
}
|
|
16897
|
+
if (policies !== undefined) {
|
|
16898
|
+
if (!Array.isArray(policies) || policies.some((p) => typeof p !== "string")) {
|
|
16899
|
+
throw new Error("Plugin policies must be string[] (globs relative to .skeleton/) — got non-array");
|
|
16900
|
+
}
|
|
16901
|
+
}
|
|
16902
|
+
return {
|
|
16903
|
+
rules,
|
|
16904
|
+
policies
|
|
16905
|
+
};
|
|
16906
|
+
}
|
|
16907
|
+
function expandPolicyGlobs(root, globs) {
|
|
16908
|
+
const base = skeletonDir(root);
|
|
16909
|
+
const files = new Set;
|
|
16910
|
+
for (const pattern of globs) {
|
|
16911
|
+
if (pattern.split(/[/\\]/).includes("..")) {
|
|
16912
|
+
throw new Error(`Policy glob must stay under .skeleton/: ${pattern}`);
|
|
16913
|
+
}
|
|
16914
|
+
const matches = globSync(pattern, {
|
|
16915
|
+
cwd: base,
|
|
16916
|
+
absolute: true,
|
|
16917
|
+
onlyFiles: true
|
|
16918
|
+
});
|
|
16919
|
+
for (const match of matches) {
|
|
16920
|
+
assertUnderSkeleton(root, match);
|
|
16921
|
+
if (match.endsWith(".yaml") || match.endsWith(".yml")) {
|
|
16922
|
+
files.add(match);
|
|
16923
|
+
}
|
|
16924
|
+
}
|
|
16925
|
+
}
|
|
16926
|
+
return [...files].sort();
|
|
16927
|
+
}
|
|
16928
|
+
function loadPoliciesFromGlobs(root, globs) {
|
|
16929
|
+
const files = expandPolicyGlobs(root, globs);
|
|
16930
|
+
const policies = [];
|
|
16931
|
+
for (const abs of files) {
|
|
16932
|
+
assertUnderSkeleton(root, abs);
|
|
16933
|
+
policies.push(loadPolicyFile(abs, readFileSync3(abs, "utf8")));
|
|
16934
|
+
}
|
|
16935
|
+
if (globs.length > 0 && policies.length === 0) {
|
|
16936
|
+
throw new Error(`Plugin policies matched no YAML under .skeleton/: ${globs.join(", ")}. Fix the glob or add policy files.`);
|
|
16937
|
+
}
|
|
16938
|
+
return policies;
|
|
16939
|
+
}
|
|
16940
|
+
async function collectWiredPolicyRelPaths(root, config) {
|
|
16941
|
+
const entries = config.plugins ?? [];
|
|
16942
|
+
const wired = new Set;
|
|
16943
|
+
if (entries.length === 0)
|
|
16944
|
+
return wired;
|
|
16945
|
+
for (const entry of entries) {
|
|
16946
|
+
const tsAbs = resolvePluginTsPath(root, entry);
|
|
16947
|
+
const mjsAbs = mjsPathForTs(tsAbs);
|
|
16948
|
+
if (!existsSync4(mjsAbs)) {
|
|
16949
|
+
const rel = relative2(skeletonDir(root), tsAbs) || entry;
|
|
16950
|
+
throw new Error(`Plugin not built: ${rel} (missing ${relative2(root, mjsAbs) || mjsAbs}). Run: skeleton build-plugin`);
|
|
16951
|
+
}
|
|
16952
|
+
const mod = await import(pathToFileURL(mjsAbs).href);
|
|
16953
|
+
const normalized = normalizeExport(mod);
|
|
16954
|
+
if (!normalized.policies?.length)
|
|
16641
16955
|
continue;
|
|
16956
|
+
for (const abs of expandPolicyGlobs(root, normalized.policies)) {
|
|
16957
|
+
wired.add(normalizeRelPath(relative2(root, abs)));
|
|
16642
16958
|
}
|
|
16643
|
-
const root = normalized.slice(0, globIdx).replace(/\/$/, "");
|
|
16644
|
-
if (root)
|
|
16645
|
-
roots.add(root);
|
|
16646
16959
|
}
|
|
16647
|
-
return
|
|
16960
|
+
return wired;
|
|
16961
|
+
}
|
|
16962
|
+
async function loadPlugins(root, config) {
|
|
16963
|
+
const entries = config.plugins ?? [];
|
|
16964
|
+
if (entries.length === 0) {
|
|
16965
|
+
return { rules: [], policies: [] };
|
|
16966
|
+
}
|
|
16967
|
+
const rules = [];
|
|
16968
|
+
const policies = [];
|
|
16969
|
+
for (const entry of entries) {
|
|
16970
|
+
const tsAbs = resolvePluginTsPath(root, entry);
|
|
16971
|
+
const mjsAbs = mjsPathForTs(tsAbs);
|
|
16972
|
+
if (!existsSync4(mjsAbs)) {
|
|
16973
|
+
const rel = relative2(skeletonDir(root), tsAbs) || entry;
|
|
16974
|
+
throw new Error(`Plugin not built: ${rel} (missing ${relative2(root, mjsAbs) || mjsAbs}). Run: skeleton build-plugin`);
|
|
16975
|
+
}
|
|
16976
|
+
const mod = await import(pathToFileURL(mjsAbs).href);
|
|
16977
|
+
const normalized = normalizeExport(mod);
|
|
16978
|
+
rules.push(...normalized.rules);
|
|
16979
|
+
if (normalized.policies?.length) {
|
|
16980
|
+
policies.push(...loadPoliciesFromGlobs(root, normalized.policies));
|
|
16981
|
+
}
|
|
16982
|
+
}
|
|
16983
|
+
return { rules, policies };
|
|
16648
16984
|
}
|
|
16649
16985
|
|
|
16986
|
+
// src/audit/core/collect.ts
|
|
16987
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
16988
|
+
import { join as join5, relative as relative4 } from "node:path";
|
|
16989
|
+
|
|
16650
16990
|
// src/audit/core/skill-roots.ts
|
|
16651
|
-
import { existsSync as
|
|
16652
|
-
import { join as
|
|
16991
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readlinkSync, realpathSync as realpathSync3 } from "node:fs";
|
|
16992
|
+
import { join as join4, relative as relative3 } from "node:path";
|
|
16653
16993
|
var NESTED_SKILL_ROOTS = [".claude/skills", ".agents/skills"];
|
|
16654
16994
|
var FLAT_SKILL_DENYLIST = new Set([
|
|
16655
16995
|
".git",
|
|
@@ -16672,16 +17012,16 @@ var FLAT_SKILL_DENYLIST = new Set([
|
|
|
16672
17012
|
var NESTED_EXCLUDED_DIRS = new Set(["references", "_shared"]);
|
|
16673
17013
|
function safeRealpath(path) {
|
|
16674
17014
|
try {
|
|
16675
|
-
return
|
|
17015
|
+
return realpathSync3(path);
|
|
16676
17016
|
} catch {
|
|
16677
17017
|
return null;
|
|
16678
17018
|
}
|
|
16679
17019
|
}
|
|
16680
17020
|
function listNestedSlugs(root, relRoot) {
|
|
16681
|
-
const absRoot =
|
|
16682
|
-
if (!
|
|
17021
|
+
const absRoot = join4(root, relRoot);
|
|
17022
|
+
if (!existsSync5(absRoot))
|
|
16683
17023
|
return [];
|
|
16684
|
-
return readdirSync2(absRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NESTED_EXCLUDED_DIRS.has(entry.name)).filter((entry) =>
|
|
17024
|
+
return readdirSync2(absRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NESTED_EXCLUDED_DIRS.has(entry.name)).filter((entry) => existsSync5(join4(absRoot, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
|
|
16685
17025
|
}
|
|
16686
17026
|
function listFlatSlugs(root) {
|
|
16687
17027
|
const slugs = [];
|
|
@@ -16690,7 +17030,7 @@ function listFlatSlugs(root) {
|
|
|
16690
17030
|
continue;
|
|
16691
17031
|
if (FLAT_SKILL_DENYLIST.has(entry.name))
|
|
16692
17032
|
continue;
|
|
16693
|
-
if (
|
|
17033
|
+
if (existsSync5(join4(root, entry.name, "SKILL.md"))) {
|
|
16694
17034
|
slugs.push(entry.name);
|
|
16695
17035
|
}
|
|
16696
17036
|
}
|
|
@@ -16700,8 +17040,8 @@ function detectSkillRoots(root) {
|
|
|
16700
17040
|
const roots = [];
|
|
16701
17041
|
let claudeReal = null;
|
|
16702
17042
|
for (const relRoot of NESTED_SKILL_ROOTS) {
|
|
16703
|
-
const abs =
|
|
16704
|
-
if (!
|
|
17043
|
+
const abs = join4(root, relRoot);
|
|
17044
|
+
if (!existsSync5(abs))
|
|
16705
17045
|
continue;
|
|
16706
17046
|
if (relRoot === ".claude/skills") {
|
|
16707
17047
|
claudeReal = safeRealpath(abs);
|
|
@@ -16715,11 +17055,11 @@ function detectSkillRoots(root) {
|
|
|
16715
17055
|
continue;
|
|
16716
17056
|
try {
|
|
16717
17057
|
const link = readlinkSync(abs);
|
|
16718
|
-
if (link && claudeReal && safeRealpath(
|
|
17058
|
+
if (link && claudeReal && safeRealpath(join4(root, link)) === claudeReal)
|
|
16719
17059
|
continue;
|
|
16720
17060
|
} catch {}
|
|
16721
17061
|
}
|
|
16722
|
-
if (listNestedSlugs(root, relRoot).length > 0 ||
|
|
17062
|
+
if (listNestedSlugs(root, relRoot).length > 0 || existsSync5(abs)) {
|
|
16723
17063
|
roots.push({ kind: "nested", relPath: relRoot });
|
|
16724
17064
|
}
|
|
16725
17065
|
}
|
|
@@ -16746,9 +17086,9 @@ function buildSkillIndex(root) {
|
|
|
16746
17086
|
}
|
|
16747
17087
|
function resolveSkillPath(index, root, slug) {
|
|
16748
17088
|
for (const skillRoot of index.roots) {
|
|
16749
|
-
const candidate = skillRoot.kind === "nested" ?
|
|
16750
|
-
if (
|
|
16751
|
-
return normalizeRelPath(
|
|
17089
|
+
const candidate = skillRoot.kind === "nested" ? join4(root, skillRoot.relPath, slug, "SKILL.md") : join4(root, slug, "SKILL.md");
|
|
17090
|
+
if (existsSync5(candidate)) {
|
|
17091
|
+
return normalizeRelPath(relative3(root, candidate));
|
|
16752
17092
|
}
|
|
16753
17093
|
}
|
|
16754
17094
|
return null;
|
|
@@ -16782,12 +17122,33 @@ function skillCollectAugments(index) {
|
|
|
16782
17122
|
}
|
|
16783
17123
|
return patterns;
|
|
16784
17124
|
}
|
|
17125
|
+
function listSkillMarkdownPaths(root, index) {
|
|
17126
|
+
const paths = new Set;
|
|
17127
|
+
for (const skillRoot of index.roots) {
|
|
17128
|
+
const slugs = skillRoot.kind === "nested" ? listNestedSlugs(root, skillRoot.relPath) : listFlatSlugs(root);
|
|
17129
|
+
for (const slug of slugs) {
|
|
17130
|
+
const absDir = skillRoot.kind === "nested" ? join4(root, skillRoot.relPath, slug) : join4(root, slug);
|
|
17131
|
+
if (!existsSync5(absDir))
|
|
17132
|
+
continue;
|
|
17133
|
+
for (const abs of globSync("**/*.{md,mdc}", {
|
|
17134
|
+
cwd: absDir,
|
|
17135
|
+
absolute: true,
|
|
17136
|
+
onlyFiles: true,
|
|
17137
|
+
dot: true
|
|
17138
|
+
})) {
|
|
17139
|
+
paths.add(normalizeRelPath(relative3(root, abs)));
|
|
17140
|
+
}
|
|
17141
|
+
}
|
|
17142
|
+
}
|
|
17143
|
+
return [...paths].sort();
|
|
17144
|
+
}
|
|
16785
17145
|
function listSkillSlugs(index) {
|
|
16786
17146
|
return index.slugs;
|
|
16787
17147
|
}
|
|
16788
17148
|
|
|
16789
17149
|
// src/audit/core/collect.ts
|
|
16790
17150
|
var MARKDOWN_GLOBS = ["**/*.md", "**/*.mdc"];
|
|
17151
|
+
var BUILTIN_INCLUDE_PATTERNS = [".skeleton/customize/**"];
|
|
16791
17152
|
function isMarkdownFile(absPath) {
|
|
16792
17153
|
return absPath.endsWith(".md") || absPath.endsWith(".mdc");
|
|
16793
17154
|
}
|
|
@@ -16805,7 +17166,7 @@ function expandPatterns(root, patterns, exclude) {
|
|
|
16805
17166
|
})) {
|
|
16806
17167
|
if (!isMarkdownFile(abs))
|
|
16807
17168
|
continue;
|
|
16808
|
-
const rel = normalizeRelPath(
|
|
17169
|
+
const rel = normalizeRelPath(relative4(root, abs));
|
|
16809
17170
|
if (shouldExclude(rel, exclude))
|
|
16810
17171
|
continue;
|
|
16811
17172
|
files.add(abs);
|
|
@@ -16815,7 +17176,7 @@ function expandPatterns(root, patterns, exclude) {
|
|
|
16815
17176
|
}
|
|
16816
17177
|
function collectScanFiles(config, root, skillIndex) {
|
|
16817
17178
|
const exclude = mergedExcludes(config);
|
|
16818
|
-
const includePatterns = [...config.scan.include];
|
|
17179
|
+
const includePatterns = [...BUILTIN_INCLUDE_PATTERNS, ...config.scan.include];
|
|
16819
17180
|
if (skillIndex) {
|
|
16820
17181
|
includePatterns.push(...skillCollectAugments(skillIndex));
|
|
16821
17182
|
}
|
|
@@ -16833,7 +17194,7 @@ function collectBannedFiles(config, root) {
|
|
|
16833
17194
|
onlyFiles: true,
|
|
16834
17195
|
dot: false
|
|
16835
17196
|
})) {
|
|
16836
|
-
const rel = normalizeRelPath(
|
|
17197
|
+
const rel = normalizeRelPath(relative4(root, abs));
|
|
16837
17198
|
if (shouldExclude(rel, exclude))
|
|
16838
17199
|
continue;
|
|
16839
17200
|
files.add(abs);
|
|
@@ -16850,7 +17211,7 @@ function collectCoverageCandidateFiles(root, exclude) {
|
|
|
16850
17211
|
onlyFiles: true,
|
|
16851
17212
|
dot: false
|
|
16852
17213
|
})) {
|
|
16853
|
-
const rel = normalizeRelPath(
|
|
17214
|
+
const rel = normalizeRelPath(relative4(root, abs));
|
|
16854
17215
|
if (shouldExclude(rel, exclude))
|
|
16855
17216
|
continue;
|
|
16856
17217
|
files.add(rel);
|
|
@@ -16861,25 +17222,25 @@ function collectCoverageCandidateFiles(root, exclude) {
|
|
|
16861
17222
|
function collectDocMetaPaths(config, root, registryPaths, skillIndex) {
|
|
16862
17223
|
const paths = [];
|
|
16863
17224
|
for (const abs of expandPatterns(root, ["docs/*/README.md"], mergedExcludes(config))) {
|
|
16864
|
-
paths.push(normalizeRelPath(
|
|
17225
|
+
paths.push(normalizeRelPath(relative4(root, abs)));
|
|
16865
17226
|
}
|
|
16866
17227
|
const extras = ["docs/README.md", ".skeleton/registry.md"];
|
|
16867
17228
|
for (const file of extras) {
|
|
16868
|
-
const abs =
|
|
16869
|
-
if (
|
|
17229
|
+
const abs = join5(root, file);
|
|
17230
|
+
if (existsSync6(abs))
|
|
16870
17231
|
paths.push(normalizeRelPath(file));
|
|
16871
17232
|
}
|
|
16872
17233
|
for (const rel of registryPaths) {
|
|
16873
17234
|
if (!rel.endsWith(".md") && !rel.endsWith(".mdc"))
|
|
16874
17235
|
continue;
|
|
16875
|
-
const abs =
|
|
16876
|
-
if (
|
|
17236
|
+
const abs = join5(root, rel);
|
|
17237
|
+
if (existsSync6(abs))
|
|
16877
17238
|
paths.push(normalizeRelPath(rel));
|
|
16878
17239
|
}
|
|
16879
17240
|
for (const abs of collectScanFiles(config, root, skillIndex)) {
|
|
16880
|
-
const content =
|
|
17241
|
+
const content = readFileSync4(abs, "utf8");
|
|
16881
17242
|
if (/<!--\s*doc-meta:/.test(content)) {
|
|
16882
|
-
paths.push(normalizeRelPath(
|
|
17243
|
+
paths.push(normalizeRelPath(relative4(root, abs)));
|
|
16883
17244
|
}
|
|
16884
17245
|
}
|
|
16885
17246
|
return [...new Set(paths)];
|
|
@@ -16887,7 +17248,7 @@ function collectDocMetaPaths(config, root, registryPaths, skillIndex) {
|
|
|
16887
17248
|
function validateScanRoots(config, root) {
|
|
16888
17249
|
const missing = [];
|
|
16889
17250
|
for (const tree of extractScanRootsFromInclude(config.scan.include)) {
|
|
16890
|
-
if (!
|
|
17251
|
+
if (!existsSync6(join5(root, tree)))
|
|
16891
17252
|
missing.push(tree);
|
|
16892
17253
|
}
|
|
16893
17254
|
return missing;
|
|
@@ -16901,28 +17262,56 @@ function filterDocMetaPaths(docMetaPaths, paths) {
|
|
|
16901
17262
|
function filterToPaths(files, paths, root) {
|
|
16902
17263
|
const normalizedPaths = paths.map((path) => normalizeRelPath(path));
|
|
16903
17264
|
return files.filter((abs) => {
|
|
16904
|
-
const rel = normalizeRelPath(
|
|
17265
|
+
const rel = normalizeRelPath(relative4(root, abs));
|
|
16905
17266
|
return normalizedPaths.some((path) => rel === path || rel.startsWith(`${path}/`));
|
|
16906
17267
|
});
|
|
16907
17268
|
}
|
|
17269
|
+
function includeExplicitMarkdownPaths(files, paths, root) {
|
|
17270
|
+
const out = new Set(files);
|
|
17271
|
+
for (const raw of paths) {
|
|
17272
|
+
const rel = normalizeRelPath(raw);
|
|
17273
|
+
const abs = join5(root, rel);
|
|
17274
|
+
if (!existsSync6(abs))
|
|
17275
|
+
continue;
|
|
17276
|
+
if (isMarkdownFile(rel)) {
|
|
17277
|
+
out.add(abs);
|
|
17278
|
+
continue;
|
|
17279
|
+
}
|
|
17280
|
+
try {
|
|
17281
|
+
if (!statSync2(abs).isDirectory())
|
|
17282
|
+
continue;
|
|
17283
|
+
} catch {
|
|
17284
|
+
continue;
|
|
17285
|
+
}
|
|
17286
|
+
for (const md of globSync("**/*.{md,mdc}", {
|
|
17287
|
+
cwd: abs,
|
|
17288
|
+
absolute: true,
|
|
17289
|
+
onlyFiles: true,
|
|
17290
|
+
dot: true
|
|
17291
|
+
})) {
|
|
17292
|
+
out.add(md);
|
|
17293
|
+
}
|
|
17294
|
+
}
|
|
17295
|
+
return [...out];
|
|
17296
|
+
}
|
|
16908
17297
|
function readFileContent(absPath) {
|
|
16909
|
-
return
|
|
17298
|
+
return readFileSync4(absPath, "utf8");
|
|
16910
17299
|
}
|
|
16911
17300
|
function relPath(absPath, root) {
|
|
16912
|
-
return normalizeRelPath(
|
|
17301
|
+
return normalizeRelPath(relative4(root, absPath));
|
|
16913
17302
|
}
|
|
16914
17303
|
|
|
16915
17304
|
// src/audit/core/registry.ts
|
|
16916
|
-
import { existsSync as
|
|
16917
|
-
import { join as
|
|
17305
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
17306
|
+
import { join as join6, relative as relative5, resolve as resolve4 } from "node:path";
|
|
16918
17307
|
var REGISTRY_TABLE_ROW_RE = /^\|\s*[^|]+\|\s*\[[^\]]*\]\(([^)]+)\)\s*\|/;
|
|
16919
17308
|
var REGISTRY_TABLE_HEADER_RE = /\|\s*Topic\s*\|\s*Canonical file\s*\|/i;
|
|
16920
17309
|
function parseRegistry(root) {
|
|
16921
|
-
const abs =
|
|
16922
|
-
if (!
|
|
17310
|
+
const abs = join6(root, REGISTRY_REL_PATH);
|
|
17311
|
+
if (!existsSync7(abs)) {
|
|
16923
17312
|
return { paths: [], hasTableHeader: false };
|
|
16924
17313
|
}
|
|
16925
|
-
const content =
|
|
17314
|
+
const content = readFileSync5(abs, "utf8");
|
|
16926
17315
|
const hasTableHeader = REGISTRY_TABLE_HEADER_RE.test(content);
|
|
16927
17316
|
const paths = [];
|
|
16928
17317
|
for (const line of content.split(`
|
|
@@ -16933,8 +17322,8 @@ function parseRegistry(root) {
|
|
|
16933
17322
|
const linkTarget = match[1].trim();
|
|
16934
17323
|
if (linkTarget.startsWith("#"))
|
|
16935
17324
|
continue;
|
|
16936
|
-
const resolved =
|
|
16937
|
-
paths.push(normalizeRelPath(
|
|
17325
|
+
const resolved = resolve4(join6(root, REGISTRY_DIR_REL), linkTarget);
|
|
17326
|
+
paths.push(normalizeRelPath(relative5(root, resolved)));
|
|
16938
17327
|
}
|
|
16939
17328
|
return { paths: [...new Set(paths)], hasTableHeader };
|
|
16940
17329
|
}
|
|
@@ -16948,7 +17337,11 @@ function createContext(options = {}) {
|
|
|
16948
17337
|
const config = loadConfig(root);
|
|
16949
17338
|
const skillIndex = buildSkillIndex(root);
|
|
16950
17339
|
let files = collectScanFiles(config, root, skillIndex);
|
|
17340
|
+
if (options.includeExcludedSkillTrees) {
|
|
17341
|
+
files = includeExplicitMarkdownPaths(files, listSkillMarkdownPaths(root, skillIndex), root);
|
|
17342
|
+
}
|
|
16951
17343
|
if (options.paths && options.paths.length > 0) {
|
|
17344
|
+
files = includeExplicitMarkdownPaths(files, options.paths, root);
|
|
16952
17345
|
files = filterToPaths(files, options.paths, root);
|
|
16953
17346
|
}
|
|
16954
17347
|
const registry = parseRegistry(root);
|
|
@@ -16961,479 +17354,111 @@ function createContext(options = {}) {
|
|
|
16961
17354
|
registryPaths: registry.paths,
|
|
16962
17355
|
registryHasTableHeader: registry.hasTableHeader,
|
|
16963
17356
|
retiredSkills: new Set(retiredSkills(config)),
|
|
16964
|
-
skillIndex
|
|
17357
|
+
skillIndex,
|
|
17358
|
+
policies: options.policies ?? []
|
|
16965
17359
|
};
|
|
16966
17360
|
}
|
|
16967
17361
|
|
|
16968
|
-
// src/audit/core/
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
17362
|
+
// src/audit/core/fix.ts
|
|
17363
|
+
import { existsSync as existsSync10, realpathSync as realpathSync4, writeFileSync } from "node:fs";
|
|
17364
|
+
import { dirname as dirname6, resolve as resolve6, sep as sep3 } from "node:path";
|
|
17365
|
+
|
|
17366
|
+
// src/audit/fix/anchors.ts
|
|
17367
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
|
|
17368
|
+
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
17369
|
+
|
|
17370
|
+
// node_modules/github-slugger/regex.js
|
|
17371
|
+
var regex = /[\0-\x1F!-,\.\/:-@\[-\^`\{-\xA9\xAB-\xB4\xB6-\xB9\xBB-\xBF\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0378\u0379\u037E\u0380-\u0385\u0387\u038B\u038D\u03A2\u03F6\u0482\u0530\u0557\u0558\u055A-\u055F\u0589-\u0590\u05BE\u05C0\u05C3\u05C6\u05C8-\u05CF\u05EB-\u05EE\u05F3-\u060F\u061B-\u061F\u066A-\u066D\u06D4\u06DD\u06DE\u06E9\u06FD\u06FE\u0700-\u070F\u074B\u074C\u07B2-\u07BF\u07F6-\u07F9\u07FB\u07FC\u07FE\u07FF\u082E-\u083F\u085C-\u085F\u086B-\u089F\u08B5\u08C8-\u08D2\u08E2\u0964\u0965\u0970\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09F2-\u09FB\u09FD\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF0-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B70\u0B72-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BF0-\u0BFF\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C7F\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D4F-\u0D53\u0D58-\u0D5E\u0D64\u0D65\u0D70-\u0D79\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF4-\u0E00\u0E3B-\u0E3F\u0E4F\u0E5A-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F01-\u0F17\u0F1A-\u0F1F\u0F2A-\u0F34\u0F36\u0F38\u0F3A-\u0F3D\u0F48\u0F6D-\u0F70\u0F85\u0F98\u0FBD-\u0FC5\u0FC7-\u0FFF\u104A-\u104F\u109E\u109F\u10C6\u10C8-\u10CC\u10CE\u10CF\u10FB\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u1360-\u137F\u1390-\u139F\u13F6\u13F7\u13FE-\u1400\u166D\u166E\u1680\u169B-\u169F\u16EB-\u16ED\u16F9-\u16FF\u170D\u1715-\u171F\u1735-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17D4-\u17D6\u17D8-\u17DB\u17DE\u17DF\u17EA-\u180A\u180E\u180F\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u1945\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DA-\u19FF\u1A1C-\u1A1F\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1AA6\u1AA8-\u1AAF\u1AC1-\u1AFF\u1B4C-\u1B4F\u1B5A-\u1B6A\u1B74-\u1B7F\u1BF4-\u1BFF\u1C38-\u1C3F\u1C4A-\u1C4C\u1C7E\u1C7F\u1C89-\u1C8F\u1CBB\u1CBC\u1CC0-\u1CCF\u1CD3\u1CFB-\u1CFF\u1DFA\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FBD\u1FBF-\u1FC1\u1FC5\u1FCD-\u1FCF\u1FD4\u1FD5\u1FDC-\u1FDF\u1FED-\u1FF1\u1FF5\u1FFD-\u203E\u2041-\u2053\u2055-\u2070\u2072-\u207E\u2080-\u208F\u209D-\u20CF\u20F1-\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F-\u215F\u2189-\u24B5\u24EA-\u2BFF\u2C2F\u2C5F\u2CE5-\u2CEA\u2CF4-\u2CFF\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D70-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E00-\u2E2E\u2E30-\u3004\u3008-\u3020\u3030\u3036\u3037\u303D-\u3040\u3097\u3098\u309B\u309C\u30A0\u30FB\u3100-\u3104\u3130\u318F-\u319F\u31C0-\u31EF\u3200-\u33FF\u4DC0-\u4DFF\u9FFD-\u9FFF\uA48D-\uA4CF\uA4FE\uA4FF\uA60D-\uA60F\uA62C-\uA63F\uA673\uA67E\uA6F2-\uA716\uA720\uA721\uA789\uA78A\uA7C0\uA7C1\uA7CB-\uA7F4\uA828-\uA82B\uA82D-\uA83F\uA874-\uA87F\uA8C6-\uA8CF\uA8DA-\uA8DF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA954-\uA95F\uA97D-\uA97F\uA9C1-\uA9CE\uA9DA-\uA9DF\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A-\uAA5F\uAA77-\uAA79\uAAC3-\uAADA\uAADE\uAADF\uAAF0\uAAF1\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB5B\uAB6A-\uAB6F\uABEB\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB29\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBB2-\uFBD2\uFD3E-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFC-\uFDFF\uFE10-\uFE1F\uFE30-\uFE32\uFE35-\uFE4C\uFE50-\uFE6F\uFE75\uFEFD-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF3E\uFF40\uFF5B-\uFF65\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFFF]|\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDD3F\uDD75-\uDDFC\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEE1-\uDEFF\uDF20-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDF9F\uDFC4-\uDFC7\uDFD0\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56-\uDC5F\uDC77-\uDC7F\uDC9F-\uDCDF\uDCF3\uDCF6-\uDCFF\uDD16-\uDD1F\uDD3A-\uDD7F\uDDB8-\uDDBD\uDDC0-\uDDFF\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE40-\uDE5F\uDE7D-\uDE7F\uDE9D-\uDEBF\uDEC8\uDEE7-\uDEFF\uDF36-\uDF3F\uDF56-\uDF5F\uDF73-\uDF7F\uDF92-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCFF\uDD28-\uDD2F\uDD3A-\uDE7F\uDEAA\uDEAD-\uDEAF\uDEB2-\uDEFF\uDF1D-\uDF26\uDF28-\uDF2F\uDF51-\uDFAF\uDFC5-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC47-\uDC65\uDC70-\uDC7E\uDCBB-\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD40-\uDD43\uDD48-\uDD4F\uDD74\uDD75\uDD77-\uDD7F\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDFF\uDE12\uDE38-\uDE3D\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEA9-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC4B-\uDC4F\uDC5A-\uDC5D\uDC62-\uDC7F\uDCC6\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDC1-\uDDD7\uDDDE-\uDDFF\uDE41-\uDE43\uDE45-\uDE4F\uDE5A-\uDE7F\uDEB9-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF3A-\uDFFF]|\uD806[\uDC3B-\uDC9F\uDCEA-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD44-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE2\uDDE5-\uDDFF\uDE3F-\uDE46\uDE48-\uDE4F\uDE9A-\uDE9C\uDE9E-\uDEBF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC41-\uDC4F\uDC5A-\uDC71\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF7-\uDFAF\uDFB1-\uDFFF]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80B\uD80E-\uD810\uD812-\uD819\uD824-\uD82B\uD82D\uD82E\uD830-\uD833\uD837\uD839\uD83D\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDBFF][\uDC00-\uDFFF]|\uD80D[\uDC2F-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDECF\uDEEE\uDEEF\uDEF5-\uDEFF\uDF37-\uDF3F\uDF44-\uDF4F\uDF5A-\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE80-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE2\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82C[\uDD1F-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A-\uDC9C\uDC9F-\uDFFF]|\uD834[\uDC00-\uDD64\uDD6A-\uDD6C\uDD73-\uDD7A\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDE41\uDE45-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3\uDFCC\uDFCD]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD4F-\uDEBF\uDEFA-\uDFFF]|\uD83A[\uDCC5-\uDCCF\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDFFF]|\uD83B[\uDC00-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDFFF]|\uD83C[\uDC00-\uDD2F\uDD4A-\uDD4F\uDD6A-\uDD6F\uDD8A-\uDFFF]|\uD83E[\uDC00-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEDE-\uDEFF]|\uD86D[\uDF35-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00-\uDCFF\uDDF0-\uDFFF]/g;
|
|
17372
|
+
|
|
17373
|
+
// node_modules/github-slugger/index.js
|
|
17374
|
+
var own = Object.hasOwnProperty;
|
|
17375
|
+
|
|
17376
|
+
class BananaSlug {
|
|
17377
|
+
constructor() {
|
|
17378
|
+
this.occurrences;
|
|
17379
|
+
this.reset();
|
|
17380
|
+
}
|
|
17381
|
+
slug(value, maintainCase) {
|
|
17382
|
+
const self = this;
|
|
17383
|
+
let result = slug(value, maintainCase === true);
|
|
17384
|
+
const originalSlug = result;
|
|
17385
|
+
while (own.call(self.occurrences, result)) {
|
|
17386
|
+
self.occurrences[originalSlug]++;
|
|
17387
|
+
result = originalSlug + "-" + self.occurrences[originalSlug];
|
|
17388
|
+
}
|
|
17389
|
+
self.occurrences[result] = 0;
|
|
17390
|
+
return result;
|
|
17391
|
+
}
|
|
17392
|
+
reset() {
|
|
17393
|
+
this.occurrences = Object.create(null);
|
|
17394
|
+
}
|
|
16977
17395
|
}
|
|
16978
|
-
function
|
|
16979
|
-
if (
|
|
16980
|
-
return
|
|
16981
|
-
|
|
17396
|
+
function slug(value, maintainCase) {
|
|
17397
|
+
if (typeof value !== "string")
|
|
17398
|
+
return "";
|
|
17399
|
+
if (!maintainCase)
|
|
17400
|
+
value = value.toLowerCase();
|
|
17401
|
+
return value.replace(regex, "").replace(/ /g, "-");
|
|
16982
17402
|
}
|
|
16983
|
-
function printReport(issues, options) {
|
|
16984
|
-
const finalized = finalizeIssues(issues, options.strict ?? false);
|
|
16985
|
-
const errors2 = finalized.filter((i) => i.severity === "error");
|
|
16986
|
-
const warnings = finalized.filter((i) => i.severity === "warning");
|
|
16987
|
-
const label = options.label ?? "Audit";
|
|
16988
|
-
if (options.json) {
|
|
16989
|
-
console.log(JSON.stringify({
|
|
16990
|
-
label,
|
|
16991
|
-
fileCount: options.fileCount,
|
|
16992
|
-
errors: errors2.length,
|
|
16993
|
-
warnings: warnings.length,
|
|
16994
|
-
issues: finalized
|
|
16995
|
-
}, null, 2));
|
|
16996
|
-
return errors2.length > 0 ? 1 : 0;
|
|
16997
|
-
}
|
|
16998
|
-
if (warnings.length > 0) {
|
|
16999
|
-
console.log(`${label} warnings:
|
|
17000
|
-
`);
|
|
17001
|
-
for (const i of warnings) {
|
|
17002
|
-
const linkPart = i.link ? ` (${i.link})` : "";
|
|
17003
|
-
console.log(`- ${i.file}${linkPart}: ${i.message}`);
|
|
17004
|
-
}
|
|
17005
|
-
console.log("");
|
|
17006
|
-
}
|
|
17007
|
-
if (errors2.length === 0) {
|
|
17008
|
-
const warnNote = warnings.length > 0 ? `, ${warnings.length} warning(s)` : "";
|
|
17009
|
-
const countNote = options.successSuffix ?? (options.fileCount !== undefined ? ` (${options.fileCount} files scanned${warnNote})` : "");
|
|
17010
|
-
console.log(`${label} passed${countNote}.`);
|
|
17011
|
-
return 0;
|
|
17012
|
-
}
|
|
17013
|
-
console.log(`${label} failed:
|
|
17014
|
-
`);
|
|
17015
|
-
for (const i of errors2) {
|
|
17016
|
-
const linkPart = i.link ? ` (${i.link})` : "";
|
|
17017
|
-
console.log(`- ${i.file}${linkPart}: ${i.message}`);
|
|
17018
|
-
}
|
|
17019
|
-
return 1;
|
|
17020
|
-
}
|
|
17021
|
-
|
|
17022
|
-
// src/references/check.ts
|
|
17023
|
-
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
17024
|
-
import { join as join6, relative as relative6 } from "node:path";
|
|
17025
|
-
|
|
17026
|
-
// src/references/constants.ts
|
|
17027
|
-
var CANONICAL_REFS_DIR = ".skeleton/references";
|
|
17028
|
-
var GENERATED_MARKER_START = "<!-- skeleton: generated-reference";
|
|
17029
|
-
var GENERATED_MARKER_RE = /<!-- skeleton: generated-reference\s*\nsource: ([^\n]+)\s*\nredundancy: intentional\s*\n-->\s*\n?/;
|
|
17030
|
-
var SHARED_REF_LINK_RE = /\((?:\.\.\/)+references\/([^)]+)\)/g;
|
|
17031
|
-
function formatGeneratedHeader(sourceRelPath) {
|
|
17032
|
-
return `${GENERATED_MARKER_START}
|
|
17033
|
-
source: ${sourceRelPath}
|
|
17034
|
-
redundancy: intentional
|
|
17035
|
-
-->
|
|
17036
17403
|
|
|
17037
|
-
|
|
17038
|
-
|
|
17039
|
-
function stripGeneratedHeader(content) {
|
|
17040
|
-
return content.replace(GENERATED_MARKER_RE, "");
|
|
17041
|
-
}
|
|
17042
|
-
function isGeneratedReference(content) {
|
|
17043
|
-
return content.startsWith(GENERATED_MARKER_START);
|
|
17044
|
-
}
|
|
17404
|
+
// node_modules/devlop/lib/development.js
|
|
17405
|
+
var codesWarned = new Set;
|
|
17045
17406
|
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
|
|
17053
|
-
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
17054
|
-
if (entry.name.startsWith("."))
|
|
17055
|
-
continue;
|
|
17056
|
-
const fullPath = join5(dir, entry.name);
|
|
17057
|
-
if (entry.isDirectory()) {
|
|
17058
|
-
files.push(...walkMarkdownFiles(fullPath, root));
|
|
17059
|
-
continue;
|
|
17060
|
-
}
|
|
17061
|
-
if (entry.name.endsWith(".md")) {
|
|
17062
|
-
files.push(normalizeRelPath(relative5(root, fullPath)));
|
|
17063
|
-
}
|
|
17064
|
-
}
|
|
17065
|
-
return files;
|
|
17066
|
-
}
|
|
17067
|
-
function canonicalExists(root, refPath) {
|
|
17068
|
-
return existsSync5(join5(root, CANONICAL_REFS_DIR, refPath));
|
|
17069
|
-
}
|
|
17070
|
-
function findSharedRefLinks(content, sourceFile) {
|
|
17071
|
-
const links = [];
|
|
17072
|
-
for (const match of content.matchAll(SHARED_REF_LINK_RE)) {
|
|
17073
|
-
const refPath = match[1];
|
|
17074
|
-
if (!refPath)
|
|
17075
|
-
continue;
|
|
17076
|
-
links.push({ refPath: normalizeRelPath(refPath), sourceFile });
|
|
17077
|
-
}
|
|
17078
|
-
return links;
|
|
17079
|
-
}
|
|
17080
|
-
function findLocalCanonicalLinks(root, content, sourceFile) {
|
|
17081
|
-
const links = [];
|
|
17082
|
-
const localRefRe = /\((?:\.\/)?references\/([^)]+)\)/g;
|
|
17083
|
-
for (const match of content.matchAll(localRefRe)) {
|
|
17084
|
-
const refPath = normalizeRelPath(match[1] ?? "");
|
|
17085
|
-
if (!refPath || !canonicalExists(root, refPath))
|
|
17086
|
-
continue;
|
|
17087
|
-
links.push({ refPath, sourceFile });
|
|
17088
|
-
}
|
|
17089
|
-
const inReferencesDir = /\/references\//.test(sourceFile);
|
|
17090
|
-
if (inReferencesDir) {
|
|
17091
|
-
const refsIdx = sourceFile.lastIndexOf("/references/");
|
|
17092
|
-
const withinRefs = sourceFile.slice(refsIdx + "/references/".length);
|
|
17093
|
-
const withinDir = withinRefs.includes("/") ? withinRefs.slice(0, withinRefs.lastIndexOf("/")) : "";
|
|
17094
|
-
const siblingRe = /\((?!https?:|#|\.\.\/)([a-z0-9./_-]+\.md)\)/gi;
|
|
17095
|
-
for (const match of content.matchAll(siblingRe)) {
|
|
17096
|
-
const raw = normalizeRelPath(match[1] ?? "");
|
|
17097
|
-
if (!raw)
|
|
17098
|
-
continue;
|
|
17099
|
-
const refPath = withinDir ? normalizeRelPath(join5(withinDir, raw)) : raw;
|
|
17100
|
-
if (!canonicalExists(root, refPath))
|
|
17101
|
-
continue;
|
|
17102
|
-
links.push({ refPath, sourceFile });
|
|
17103
|
-
}
|
|
17104
|
-
}
|
|
17105
|
-
return links;
|
|
17106
|
-
}
|
|
17107
|
-
function discoverSkillReferencePlans(root) {
|
|
17108
|
-
const index = buildSkillIndex(root);
|
|
17109
|
-
const plans = [];
|
|
17110
|
-
for (const slug of index.slugs) {
|
|
17111
|
-
const skillDir = join5(root, slug);
|
|
17112
|
-
if (!existsSync5(join5(skillDir, "SKILL.md")))
|
|
17113
|
-
continue;
|
|
17114
|
-
const refPaths = new Set;
|
|
17115
|
-
const links = [];
|
|
17116
|
-
for (const relFile of walkMarkdownFiles(skillDir, root)) {
|
|
17117
|
-
const content = readFileSync4(join5(root, relFile), "utf8");
|
|
17118
|
-
if (isGeneratedReference(content))
|
|
17119
|
-
continue;
|
|
17120
|
-
for (const link of findSharedRefLinks(content, relFile)) {
|
|
17121
|
-
refPaths.add(link.refPath);
|
|
17122
|
-
links.push(link);
|
|
17123
|
-
}
|
|
17124
|
-
for (const link of findLocalCanonicalLinks(root, content, relFile)) {
|
|
17125
|
-
refPaths.add(link.refPath);
|
|
17126
|
-
links.push(link);
|
|
17127
|
-
}
|
|
17128
|
-
}
|
|
17129
|
-
const queue = [...refPaths];
|
|
17130
|
-
while (queue.length > 0) {
|
|
17131
|
-
const refPath = queue.pop();
|
|
17132
|
-
if (!refPath || !canonicalExists(root, refPath))
|
|
17133
|
-
continue;
|
|
17134
|
-
const canonicalContent = readFileSync4(join5(root, CANONICAL_REFS_DIR, refPath), "utf8");
|
|
17135
|
-
const syntheticSource = generatedRefPath(slug, refPath);
|
|
17136
|
-
for (const link of findLocalCanonicalLinks(root, canonicalContent, syntheticSource)) {
|
|
17137
|
-
if (refPaths.has(link.refPath))
|
|
17138
|
-
continue;
|
|
17139
|
-
refPaths.add(link.refPath);
|
|
17140
|
-
links.push(link);
|
|
17141
|
-
queue.push(link.refPath);
|
|
17142
|
-
}
|
|
17143
|
-
}
|
|
17144
|
-
if (refPaths.size > 0) {
|
|
17145
|
-
plans.push({ skill: slug, refPaths, links });
|
|
17407
|
+
class AssertionError extends Error {
|
|
17408
|
+
name = "Assertion";
|
|
17409
|
+
code = "ERR_ASSERTION";
|
|
17410
|
+
constructor(message, actual, expected, operator, generated) {
|
|
17411
|
+
super(message);
|
|
17412
|
+
if (Error.captureStackTrace) {
|
|
17413
|
+
Error.captureStackTrace(this, this.constructor);
|
|
17146
17414
|
}
|
|
17415
|
+
this.actual = actual;
|
|
17416
|
+
this.expected = expected;
|
|
17417
|
+
this.generated = generated;
|
|
17418
|
+
this.operator = operator;
|
|
17147
17419
|
}
|
|
17148
|
-
return plans.sort((a, b) => a.skill.localeCompare(b.skill));
|
|
17149
17420
|
}
|
|
17150
|
-
function
|
|
17151
|
-
|
|
17421
|
+
function ok(value, message) {
|
|
17422
|
+
assert(Boolean(value), false, true, "ok", "Expected value to be truthy", message);
|
|
17152
17423
|
}
|
|
17153
|
-
function
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
if (!sourceDir)
|
|
17157
|
-
return target;
|
|
17158
|
-
const fromParts = sourceDir.split("/");
|
|
17159
|
-
const toParts = target.split("/");
|
|
17160
|
-
let i = 0;
|
|
17161
|
-
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
|
|
17162
|
-
i++;
|
|
17424
|
+
function assert(bool, actual, expected, operator, defaultMessage, userMessage) {
|
|
17425
|
+
if (!bool) {
|
|
17426
|
+
throw userMessage instanceof Error ? userMessage : new AssertionError(userMessage || defaultMessage, actual, expected, operator, !userMessage);
|
|
17163
17427
|
}
|
|
17164
|
-
const ups = fromParts.length - i;
|
|
17165
|
-
const down = toParts.slice(i);
|
|
17166
|
-
const rel = [...Array(ups).fill(".."), ...down].join("/");
|
|
17167
|
-
return rel || (toParts.at(-1) ?? refPath);
|
|
17168
|
-
}
|
|
17169
|
-
function rewriteSharedRefLinks(content, sourceFile, skill) {
|
|
17170
|
-
return content.replace(SHARED_REF_LINK_RE, (_match, refPath) => {
|
|
17171
|
-
const rewritten = rewriteSharedRefTarget(sourceFile, skill, normalizeRelPath(refPath));
|
|
17172
|
-
return `(${rewritten})`;
|
|
17173
|
-
});
|
|
17174
17428
|
}
|
|
17175
17429
|
|
|
17176
|
-
//
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
const
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
if (entry.name.startsWith("."))
|
|
17184
|
-
continue;
|
|
17185
|
-
const fullPath = join6(dir, entry.name);
|
|
17186
|
-
if (entry.isDirectory()) {
|
|
17187
|
-
walk(fullPath);
|
|
17188
|
-
continue;
|
|
17189
|
-
}
|
|
17190
|
-
if (!entry.name.endsWith(".md"))
|
|
17191
|
-
continue;
|
|
17192
|
-
const content = readFileSync5(fullPath, "utf8");
|
|
17193
|
-
if (isGeneratedReference(content)) {
|
|
17194
|
-
files.push(normalizeRelPath(relative6(root, fullPath)));
|
|
17195
|
-
}
|
|
17196
|
-
}
|
|
17197
|
-
};
|
|
17198
|
-
walk(root);
|
|
17199
|
-
return files;
|
|
17430
|
+
// node_modules/mdast-util-to-string/lib/index.js
|
|
17431
|
+
var emptyOptions = {};
|
|
17432
|
+
function toString(value, options) {
|
|
17433
|
+
const settings = options || emptyOptions;
|
|
17434
|
+
const includeImageAlt = typeof settings.includeImageAlt === "boolean" ? settings.includeImageAlt : true;
|
|
17435
|
+
const includeHtml = typeof settings.includeHtml === "boolean" ? settings.includeHtml : true;
|
|
17436
|
+
return one(value, includeImageAlt, includeHtml);
|
|
17200
17437
|
}
|
|
17201
|
-
function
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
return issues;
|
|
17206
|
-
const plans = discoverSkillReferencePlans(root);
|
|
17207
|
-
const needed = new Set;
|
|
17208
|
-
for (const plan of plans) {
|
|
17209
|
-
for (const refPath of plan.refPaths) {
|
|
17210
|
-
needed.add(generatedRefPath(plan.skill, refPath));
|
|
17211
|
-
}
|
|
17212
|
-
}
|
|
17213
|
-
for (const targetRel of needed) {
|
|
17214
|
-
const targetPath = join6(root, targetRel);
|
|
17215
|
-
if (!existsSync6(targetPath)) {
|
|
17216
|
-
issues.push(issue("generated-references", targetRel, "missing generated copy — run skeleton references sync"));
|
|
17217
|
-
continue;
|
|
17218
|
-
}
|
|
17219
|
-
const generated = readFileSync5(targetPath, "utf8");
|
|
17220
|
-
if (!isGeneratedReference(generated)) {
|
|
17221
|
-
issues.push(issue("generated-references", targetRel, "expected generated-reference provenance header"));
|
|
17222
|
-
continue;
|
|
17223
|
-
}
|
|
17224
|
-
const body = stripGeneratedHeader(generated);
|
|
17225
|
-
const sourceRel = normalizeRelPath(generated.match(/source: ([^\n]+)/)?.[1] ?? join6(CANONICAL_REFS_DIR, targetRel.split("/references/")[1] ?? ""));
|
|
17226
|
-
const canonicalPath = join6(root, sourceRel);
|
|
17227
|
-
if (!existsSync6(canonicalPath)) {
|
|
17228
|
-
issues.push(issue("generated-references", targetRel, `canonical source missing: ${sourceRel}`));
|
|
17229
|
-
continue;
|
|
17438
|
+
function one(value, includeImageAlt, includeHtml) {
|
|
17439
|
+
if (node(value)) {
|
|
17440
|
+
if ("value" in value) {
|
|
17441
|
+
return value.type === "html" && !includeHtml ? "" : value.value;
|
|
17230
17442
|
}
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
issues.push(issue("generated-references", targetRel, "stale generated copy — run skeleton references sync"));
|
|
17443
|
+
if (includeImageAlt && "alt" in value && value.alt) {
|
|
17444
|
+
return value.alt;
|
|
17234
17445
|
}
|
|
17235
|
-
|
|
17236
|
-
|
|
17237
|
-
if (!needed.has(generatedRel)) {
|
|
17238
|
-
issues.push(issue("generated-references", generatedRel, "orphaned generated copy — run skeleton references sync"));
|
|
17446
|
+
if ("children" in value) {
|
|
17447
|
+
return all(value.children, includeImageAlt, includeHtml);
|
|
17239
17448
|
}
|
|
17240
17449
|
}
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
if (!existsSync6(skillDir))
|
|
17244
|
-
continue;
|
|
17245
|
-
const walk = (dir) => {
|
|
17246
|
-
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
17247
|
-
if (entry.name.startsWith("."))
|
|
17248
|
-
continue;
|
|
17249
|
-
const fullPath = join6(dir, entry.name);
|
|
17250
|
-
if (entry.isDirectory()) {
|
|
17251
|
-
walk(fullPath);
|
|
17252
|
-
continue;
|
|
17253
|
-
}
|
|
17254
|
-
if (!entry.name.endsWith(".md"))
|
|
17255
|
-
continue;
|
|
17256
|
-
const relFile = normalizeRelPath(relative6(root, fullPath));
|
|
17257
|
-
const content = readFileSync5(fullPath, "utf8");
|
|
17258
|
-
if (content.match(SHARED_REF_LINK_RE)) {
|
|
17259
|
-
issues.push(issue("generated-references", relFile, "still links to shared root references/ — run skeleton references sync"));
|
|
17260
|
-
}
|
|
17261
|
-
}
|
|
17262
|
-
};
|
|
17263
|
-
walk(skillDir);
|
|
17450
|
+
if (Array.isArray(value)) {
|
|
17451
|
+
return all(value, includeImageAlt, includeHtml);
|
|
17264
17452
|
}
|
|
17265
|
-
return
|
|
17266
|
-
}
|
|
17267
|
-
function runGeneratedReferencesRule(ctx) {
|
|
17268
|
-
return runGeneratedReferencesCheck(ctx.root);
|
|
17453
|
+
return "";
|
|
17269
17454
|
}
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
// src/audit/rules/banned.ts
|
|
17277
|
-
function runBannedRule(ctx) {
|
|
17278
|
-
const issues = [];
|
|
17279
|
-
for (const abs of collectBannedFiles(ctx.config, ctx.root)) {
|
|
17280
|
-
const rel = relPath(abs, ctx.root);
|
|
17281
|
-
issues.push(issue("banned", rel, "file matches scan.banned — must not exist in repo"));
|
|
17455
|
+
function all(values, includeImageAlt, includeHtml) {
|
|
17456
|
+
const result = [];
|
|
17457
|
+
let index = -1;
|
|
17458
|
+
while (++index < values.length) {
|
|
17459
|
+
result[index] = one(values[index], includeImageAlt, includeHtml);
|
|
17282
17460
|
}
|
|
17283
|
-
return
|
|
17284
|
-
}
|
|
17285
|
-
var bannedRule = { id: "banned", run: runBannedRule };
|
|
17286
|
-
|
|
17287
|
-
// src/audit/rules/doc-meta.ts
|
|
17288
|
-
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
17289
|
-
import { join as join7 } from "node:path";
|
|
17290
|
-
|
|
17291
|
-
// src/audit/core/git-meta.ts
|
|
17292
|
-
import { spawnSync } from "node:child_process";
|
|
17293
|
-
function lastGitCommitDate(relPath2, root) {
|
|
17294
|
-
const proc = spawnSync("git", ["log", "-1", "--format=%cs", "--", relPath2], {
|
|
17295
|
-
cwd: root,
|
|
17296
|
-
encoding: "utf8"
|
|
17297
|
-
});
|
|
17298
|
-
if (proc.status !== 0)
|
|
17299
|
-
return null;
|
|
17300
|
-
const date = proc.stdout.trim();
|
|
17301
|
-
return date || null;
|
|
17302
|
-
}
|
|
17303
|
-
|
|
17304
|
-
// src/audit/rules/doc-meta.ts
|
|
17305
|
-
function runDocMetaRule(ctx) {
|
|
17306
|
-
const issues = [];
|
|
17307
|
-
const today = new Date;
|
|
17308
|
-
for (const relPath2 of ctx.docMetaPaths) {
|
|
17309
|
-
const abs = join7(ctx.root, relPath2);
|
|
17310
|
-
if (!existsSync7(abs))
|
|
17311
|
-
continue;
|
|
17312
|
-
const content = readFileSync6(abs, "utf8");
|
|
17313
|
-
if (!DOC_META_RE.test(content)) {
|
|
17314
|
-
issues.push(issue("doc-meta", relPath2, "missing doc-meta comment (owner + last-reviewed)"));
|
|
17315
|
-
continue;
|
|
17316
|
-
}
|
|
17317
|
-
const match = DOC_META_LAST_REVIEWED_RE.exec(content);
|
|
17318
|
-
if (!match?.[1])
|
|
17319
|
-
continue;
|
|
17320
|
-
const reviewed = new Date(`${match[1]}T00:00:00Z`);
|
|
17321
|
-
if (Number.isNaN(reviewed.getTime()))
|
|
17322
|
-
continue;
|
|
17323
|
-
const ageDays = (today.getTime() - reviewed.getTime()) / 86400000;
|
|
17324
|
-
if (ageDays > ctx.config.daysUntilStale) {
|
|
17325
|
-
issues.push(issue("doc-meta", relPath2, `doc-meta last-reviewed ${match[1]} is stale (>${ctx.config.daysUntilStale} days)`, { severity: "warning" }));
|
|
17326
|
-
}
|
|
17327
|
-
const gitDate = lastGitCommitDate(relPath2, ctx.root);
|
|
17328
|
-
if (!gitDate)
|
|
17329
|
-
continue;
|
|
17330
|
-
const committed = new Date(`${gitDate}T00:00:00Z`);
|
|
17331
|
-
if (Number.isNaN(committed.getTime()))
|
|
17332
|
-
continue;
|
|
17333
|
-
if (committed.getTime() > reviewed.getTime()) {
|
|
17334
|
-
issues.push(issue("doc-meta", relPath2, `content changed after last-reviewed ${match[1]} (git: ${gitDate}) — bump last-reviewed or confirm review`, { severity: "warning" }));
|
|
17335
|
-
}
|
|
17336
|
-
}
|
|
17337
|
-
return issues;
|
|
17338
|
-
}
|
|
17339
|
-
var docMetaRule = { id: "doc-meta", run: runDocMetaRule };
|
|
17340
|
-
|
|
17341
|
-
// src/audit/rules/links.ts
|
|
17342
|
-
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
17343
|
-
import { dirname as dirname3, resolve as resolve4 } from "node:path";
|
|
17344
|
-
|
|
17345
|
-
// node_modules/github-slugger/regex.js
|
|
17346
|
-
var regex = /[\0-\x1F!-,\.\/:-@\[-\^`\{-\xA9\xAB-\xB4\xB6-\xB9\xBB-\xBF\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0378\u0379\u037E\u0380-\u0385\u0387\u038B\u038D\u03A2\u03F6\u0482\u0530\u0557\u0558\u055A-\u055F\u0589-\u0590\u05BE\u05C0\u05C3\u05C6\u05C8-\u05CF\u05EB-\u05EE\u05F3-\u060F\u061B-\u061F\u066A-\u066D\u06D4\u06DD\u06DE\u06E9\u06FD\u06FE\u0700-\u070F\u074B\u074C\u07B2-\u07BF\u07F6-\u07F9\u07FB\u07FC\u07FE\u07FF\u082E-\u083F\u085C-\u085F\u086B-\u089F\u08B5\u08C8-\u08D2\u08E2\u0964\u0965\u0970\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09F2-\u09FB\u09FD\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF0-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B70\u0B72-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BF0-\u0BFF\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C7F\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D4F-\u0D53\u0D58-\u0D5E\u0D64\u0D65\u0D70-\u0D79\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF4-\u0E00\u0E3B-\u0E3F\u0E4F\u0E5A-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F01-\u0F17\u0F1A-\u0F1F\u0F2A-\u0F34\u0F36\u0F38\u0F3A-\u0F3D\u0F48\u0F6D-\u0F70\u0F85\u0F98\u0FBD-\u0FC5\u0FC7-\u0FFF\u104A-\u104F\u109E\u109F\u10C6\u10C8-\u10CC\u10CE\u10CF\u10FB\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u1360-\u137F\u1390-\u139F\u13F6\u13F7\u13FE-\u1400\u166D\u166E\u1680\u169B-\u169F\u16EB-\u16ED\u16F9-\u16FF\u170D\u1715-\u171F\u1735-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17D4-\u17D6\u17D8-\u17DB\u17DE\u17DF\u17EA-\u180A\u180E\u180F\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u1945\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DA-\u19FF\u1A1C-\u1A1F\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1AA6\u1AA8-\u1AAF\u1AC1-\u1AFF\u1B4C-\u1B4F\u1B5A-\u1B6A\u1B74-\u1B7F\u1BF4-\u1BFF\u1C38-\u1C3F\u1C4A-\u1C4C\u1C7E\u1C7F\u1C89-\u1C8F\u1CBB\u1CBC\u1CC0-\u1CCF\u1CD3\u1CFB-\u1CFF\u1DFA\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FBD\u1FBF-\u1FC1\u1FC5\u1FCD-\u1FCF\u1FD4\u1FD5\u1FDC-\u1FDF\u1FED-\u1FF1\u1FF5\u1FFD-\u203E\u2041-\u2053\u2055-\u2070\u2072-\u207E\u2080-\u208F\u209D-\u20CF\u20F1-\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F-\u215F\u2189-\u24B5\u24EA-\u2BFF\u2C2F\u2C5F\u2CE5-\u2CEA\u2CF4-\u2CFF\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D70-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E00-\u2E2E\u2E30-\u3004\u3008-\u3020\u3030\u3036\u3037\u303D-\u3040\u3097\u3098\u309B\u309C\u30A0\u30FB\u3100-\u3104\u3130\u318F-\u319F\u31C0-\u31EF\u3200-\u33FF\u4DC0-\u4DFF\u9FFD-\u9FFF\uA48D-\uA4CF\uA4FE\uA4FF\uA60D-\uA60F\uA62C-\uA63F\uA673\uA67E\uA6F2-\uA716\uA720\uA721\uA789\uA78A\uA7C0\uA7C1\uA7CB-\uA7F4\uA828-\uA82B\uA82D-\uA83F\uA874-\uA87F\uA8C6-\uA8CF\uA8DA-\uA8DF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA954-\uA95F\uA97D-\uA97F\uA9C1-\uA9CE\uA9DA-\uA9DF\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A-\uAA5F\uAA77-\uAA79\uAAC3-\uAADA\uAADE\uAADF\uAAF0\uAAF1\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB5B\uAB6A-\uAB6F\uABEB\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB29\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBB2-\uFBD2\uFD3E-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFC-\uFDFF\uFE10-\uFE1F\uFE30-\uFE32\uFE35-\uFE4C\uFE50-\uFE6F\uFE75\uFEFD-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF3E\uFF40\uFF5B-\uFF65\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFFF]|\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDD3F\uDD75-\uDDFC\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEE1-\uDEFF\uDF20-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDF9F\uDFC4-\uDFC7\uDFD0\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56-\uDC5F\uDC77-\uDC7F\uDC9F-\uDCDF\uDCF3\uDCF6-\uDCFF\uDD16-\uDD1F\uDD3A-\uDD7F\uDDB8-\uDDBD\uDDC0-\uDDFF\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE40-\uDE5F\uDE7D-\uDE7F\uDE9D-\uDEBF\uDEC8\uDEE7-\uDEFF\uDF36-\uDF3F\uDF56-\uDF5F\uDF73-\uDF7F\uDF92-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCFF\uDD28-\uDD2F\uDD3A-\uDE7F\uDEAA\uDEAD-\uDEAF\uDEB2-\uDEFF\uDF1D-\uDF26\uDF28-\uDF2F\uDF51-\uDFAF\uDFC5-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC47-\uDC65\uDC70-\uDC7E\uDCBB-\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD40-\uDD43\uDD48-\uDD4F\uDD74\uDD75\uDD77-\uDD7F\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDFF\uDE12\uDE38-\uDE3D\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEA9-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC4B-\uDC4F\uDC5A-\uDC5D\uDC62-\uDC7F\uDCC6\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDC1-\uDDD7\uDDDE-\uDDFF\uDE41-\uDE43\uDE45-\uDE4F\uDE5A-\uDE7F\uDEB9-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF3A-\uDFFF]|\uD806[\uDC3B-\uDC9F\uDCEA-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD44-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE2\uDDE5-\uDDFF\uDE3F-\uDE46\uDE48-\uDE4F\uDE9A-\uDE9C\uDE9E-\uDEBF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC41-\uDC4F\uDC5A-\uDC71\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF7-\uDFAF\uDFB1-\uDFFF]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80B\uD80E-\uD810\uD812-\uD819\uD824-\uD82B\uD82D\uD82E\uD830-\uD833\uD837\uD839\uD83D\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDBFF][\uDC00-\uDFFF]|\uD80D[\uDC2F-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDECF\uDEEE\uDEEF\uDEF5-\uDEFF\uDF37-\uDF3F\uDF44-\uDF4F\uDF5A-\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE80-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE2\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82C[\uDD1F-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A-\uDC9C\uDC9F-\uDFFF]|\uD834[\uDC00-\uDD64\uDD6A-\uDD6C\uDD73-\uDD7A\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDE41\uDE45-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3\uDFCC\uDFCD]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD4F-\uDEBF\uDEFA-\uDFFF]|\uD83A[\uDCC5-\uDCCF\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDFFF]|\uD83B[\uDC00-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDFFF]|\uD83C[\uDC00-\uDD2F\uDD4A-\uDD4F\uDD6A-\uDD6F\uDD8A-\uDFFF]|\uD83E[\uDC00-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEDE-\uDEFF]|\uD86D[\uDF35-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00-\uDCFF\uDDF0-\uDFFF]/g;
|
|
17347
|
-
|
|
17348
|
-
// node_modules/github-slugger/index.js
|
|
17349
|
-
var own = Object.hasOwnProperty;
|
|
17350
|
-
|
|
17351
|
-
class BananaSlug {
|
|
17352
|
-
constructor() {
|
|
17353
|
-
this.occurrences;
|
|
17354
|
-
this.reset();
|
|
17355
|
-
}
|
|
17356
|
-
slug(value, maintainCase) {
|
|
17357
|
-
const self = this;
|
|
17358
|
-
let result = slug(value, maintainCase === true);
|
|
17359
|
-
const originalSlug = result;
|
|
17360
|
-
while (own.call(self.occurrences, result)) {
|
|
17361
|
-
self.occurrences[originalSlug]++;
|
|
17362
|
-
result = originalSlug + "-" + self.occurrences[originalSlug];
|
|
17363
|
-
}
|
|
17364
|
-
self.occurrences[result] = 0;
|
|
17365
|
-
return result;
|
|
17366
|
-
}
|
|
17367
|
-
reset() {
|
|
17368
|
-
this.occurrences = Object.create(null);
|
|
17369
|
-
}
|
|
17370
|
-
}
|
|
17371
|
-
function slug(value, maintainCase) {
|
|
17372
|
-
if (typeof value !== "string")
|
|
17373
|
-
return "";
|
|
17374
|
-
if (!maintainCase)
|
|
17375
|
-
value = value.toLowerCase();
|
|
17376
|
-
return value.replace(regex, "").replace(/ /g, "-");
|
|
17377
|
-
}
|
|
17378
|
-
|
|
17379
|
-
// node_modules/devlop/lib/development.js
|
|
17380
|
-
var codesWarned = new Set;
|
|
17381
|
-
|
|
17382
|
-
class AssertionError extends Error {
|
|
17383
|
-
name = "Assertion";
|
|
17384
|
-
code = "ERR_ASSERTION";
|
|
17385
|
-
constructor(message, actual, expected, operator, generated) {
|
|
17386
|
-
super(message);
|
|
17387
|
-
if (Error.captureStackTrace) {
|
|
17388
|
-
Error.captureStackTrace(this, this.constructor);
|
|
17389
|
-
}
|
|
17390
|
-
this.actual = actual;
|
|
17391
|
-
this.expected = expected;
|
|
17392
|
-
this.generated = generated;
|
|
17393
|
-
this.operator = operator;
|
|
17394
|
-
}
|
|
17395
|
-
}
|
|
17396
|
-
function ok(value, message) {
|
|
17397
|
-
assert(Boolean(value), false, true, "ok", "Expected value to be truthy", message);
|
|
17398
|
-
}
|
|
17399
|
-
function assert(bool, actual, expected, operator, defaultMessage, userMessage) {
|
|
17400
|
-
if (!bool) {
|
|
17401
|
-
throw userMessage instanceof Error ? userMessage : new AssertionError(userMessage || defaultMessage, actual, expected, operator, !userMessage);
|
|
17402
|
-
}
|
|
17403
|
-
}
|
|
17404
|
-
|
|
17405
|
-
// node_modules/mdast-util-to-string/lib/index.js
|
|
17406
|
-
var emptyOptions = {};
|
|
17407
|
-
function toString(value, options) {
|
|
17408
|
-
const settings = options || emptyOptions;
|
|
17409
|
-
const includeImageAlt = typeof settings.includeImageAlt === "boolean" ? settings.includeImageAlt : true;
|
|
17410
|
-
const includeHtml = typeof settings.includeHtml === "boolean" ? settings.includeHtml : true;
|
|
17411
|
-
return one(value, includeImageAlt, includeHtml);
|
|
17412
|
-
}
|
|
17413
|
-
function one(value, includeImageAlt, includeHtml) {
|
|
17414
|
-
if (node(value)) {
|
|
17415
|
-
if ("value" in value) {
|
|
17416
|
-
return value.type === "html" && !includeHtml ? "" : value.value;
|
|
17417
|
-
}
|
|
17418
|
-
if (includeImageAlt && "alt" in value && value.alt) {
|
|
17419
|
-
return value.alt;
|
|
17420
|
-
}
|
|
17421
|
-
if ("children" in value) {
|
|
17422
|
-
return all(value.children, includeImageAlt, includeHtml);
|
|
17423
|
-
}
|
|
17424
|
-
}
|
|
17425
|
-
if (Array.isArray(value)) {
|
|
17426
|
-
return all(value, includeImageAlt, includeHtml);
|
|
17427
|
-
}
|
|
17428
|
-
return "";
|
|
17429
|
-
}
|
|
17430
|
-
function all(values, includeImageAlt, includeHtml) {
|
|
17431
|
-
const result = [];
|
|
17432
|
-
let index = -1;
|
|
17433
|
-
while (++index < values.length) {
|
|
17434
|
-
result[index] = one(values[index], includeImageAlt, includeHtml);
|
|
17435
|
-
}
|
|
17436
|
-
return result.join("");
|
|
17461
|
+
return result.join("");
|
|
17437
17462
|
}
|
|
17438
17463
|
function node(value) {
|
|
17439
17464
|
return Boolean(value && typeof value === "object");
|
|
@@ -20343,10 +20368,10 @@ function resolveAll(constructs2, events, context) {
|
|
|
20343
20368
|
const called = [];
|
|
20344
20369
|
let index = -1;
|
|
20345
20370
|
while (++index < constructs2.length) {
|
|
20346
|
-
const
|
|
20347
|
-
if (
|
|
20348
|
-
events =
|
|
20349
|
-
called.push(
|
|
20371
|
+
const resolve5 = constructs2[index].resolveAll;
|
|
20372
|
+
if (resolve5 && !called.includes(resolve5)) {
|
|
20373
|
+
events = resolve5(events, context);
|
|
20374
|
+
called.push(resolve5);
|
|
20350
20375
|
}
|
|
20351
20376
|
}
|
|
20352
20377
|
return events;
|
|
@@ -25520,7 +25545,7 @@ var handle = {
|
|
|
25520
25545
|
};
|
|
25521
25546
|
|
|
25522
25547
|
// node_modules/mdast-util-to-markdown/lib/join.js
|
|
25523
|
-
var
|
|
25548
|
+
var join7 = [joinDefaults];
|
|
25524
25549
|
function joinDefaults(left, right, parent, state) {
|
|
25525
25550
|
if (right.type === "code" && formatCodeAsIndented(right, state) && (left.type === "list" || left.type === right.type && formatCodeAsIndented(left, state))) {
|
|
25526
25551
|
return false;
|
|
@@ -25903,7 +25928,7 @@ function toMarkdown(tree, options) {
|
|
|
25903
25928
|
handle: undefined,
|
|
25904
25929
|
indentLines,
|
|
25905
25930
|
indexStack: [],
|
|
25906
|
-
join: [...
|
|
25931
|
+
join: [...join7],
|
|
25907
25932
|
options: {},
|
|
25908
25933
|
safe: safeBound,
|
|
25909
25934
|
stack: [],
|
|
@@ -26156,7 +26181,7 @@ import { default as default2 } from "node:path";
|
|
|
26156
26181
|
import { default as default3 } from "node:process";
|
|
26157
26182
|
|
|
26158
26183
|
// node_modules/vfile/lib/minurl.js
|
|
26159
|
-
import { fileURLToPath as
|
|
26184
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
26160
26185
|
|
|
26161
26186
|
// node_modules/vfile/lib/minurl.shared.js
|
|
26162
26187
|
function isUrl(fileUrlOrPath) {
|
|
@@ -26209,40 +26234,40 @@ class VFile {
|
|
|
26209
26234
|
get basename() {
|
|
26210
26235
|
return typeof this.path === "string" ? default2.basename(this.path) : undefined;
|
|
26211
26236
|
}
|
|
26212
|
-
set basename(
|
|
26213
|
-
assertNonEmpty(
|
|
26214
|
-
assertPart(
|
|
26215
|
-
this.path = default2.join(this.dirname || "",
|
|
26237
|
+
set basename(basename3) {
|
|
26238
|
+
assertNonEmpty(basename3, "basename");
|
|
26239
|
+
assertPart(basename3, "basename");
|
|
26240
|
+
this.path = default2.join(this.dirname || "", basename3);
|
|
26216
26241
|
}
|
|
26217
26242
|
get dirname() {
|
|
26218
26243
|
return typeof this.path === "string" ? default2.dirname(this.path) : undefined;
|
|
26219
26244
|
}
|
|
26220
|
-
set dirname(
|
|
26245
|
+
set dirname(dirname5) {
|
|
26221
26246
|
assertPath(this.basename, "dirname");
|
|
26222
|
-
this.path = default2.join(
|
|
26247
|
+
this.path = default2.join(dirname5 || "", this.basename);
|
|
26223
26248
|
}
|
|
26224
26249
|
get extname() {
|
|
26225
26250
|
return typeof this.path === "string" ? default2.extname(this.path) : undefined;
|
|
26226
26251
|
}
|
|
26227
|
-
set extname(
|
|
26228
|
-
assertPart(
|
|
26252
|
+
set extname(extname2) {
|
|
26253
|
+
assertPart(extname2, "extname");
|
|
26229
26254
|
assertPath(this.dirname, "extname");
|
|
26230
|
-
if (
|
|
26231
|
-
if (
|
|
26255
|
+
if (extname2) {
|
|
26256
|
+
if (extname2.codePointAt(0) !== 46) {
|
|
26232
26257
|
throw new Error("`extname` must start with `.`");
|
|
26233
26258
|
}
|
|
26234
|
-
if (
|
|
26259
|
+
if (extname2.includes(".", 1)) {
|
|
26235
26260
|
throw new Error("`extname` cannot contain multiple dots");
|
|
26236
26261
|
}
|
|
26237
26262
|
}
|
|
26238
|
-
this.path = default2.join(this.dirname, this.stem + (
|
|
26263
|
+
this.path = default2.join(this.dirname, this.stem + (extname2 || ""));
|
|
26239
26264
|
}
|
|
26240
26265
|
get path() {
|
|
26241
26266
|
return this.history[this.history.length - 1];
|
|
26242
26267
|
}
|
|
26243
26268
|
set path(path) {
|
|
26244
26269
|
if (isUrl(path)) {
|
|
26245
|
-
path =
|
|
26270
|
+
path = fileURLToPath4(path);
|
|
26246
26271
|
}
|
|
26247
26272
|
assertNonEmpty(path, "path");
|
|
26248
26273
|
if (this.path !== path) {
|
|
@@ -26396,7 +26421,7 @@ class Processor extends CallableInstance {
|
|
|
26396
26421
|
assertParser("process", this.parser || this.Parser);
|
|
26397
26422
|
assertCompiler("process", this.compiler || this.Compiler);
|
|
26398
26423
|
return done ? executor(undefined, done) : new Promise(executor);
|
|
26399
|
-
function executor(
|
|
26424
|
+
function executor(resolve5, reject) {
|
|
26400
26425
|
const realFile = vfile(file);
|
|
26401
26426
|
const parseTree = self.parse(realFile);
|
|
26402
26427
|
self.run(parseTree, realFile, function(error, tree, file2) {
|
|
@@ -26415,8 +26440,8 @@ class Processor extends CallableInstance {
|
|
|
26415
26440
|
function realDone(error, file2) {
|
|
26416
26441
|
if (error || !file2) {
|
|
26417
26442
|
reject(error);
|
|
26418
|
-
} else if (
|
|
26419
|
-
|
|
26443
|
+
} else if (resolve5) {
|
|
26444
|
+
resolve5(file2);
|
|
26420
26445
|
} else {
|
|
26421
26446
|
ok(done, "`done` is defined if `resolve` is not");
|
|
26422
26447
|
done(undefined, file2);
|
|
@@ -26449,7 +26474,7 @@ class Processor extends CallableInstance {
|
|
|
26449
26474
|
file = undefined;
|
|
26450
26475
|
}
|
|
26451
26476
|
return done ? executor(undefined, done) : new Promise(executor);
|
|
26452
|
-
function executor(
|
|
26477
|
+
function executor(resolve5, reject) {
|
|
26453
26478
|
ok(typeof file !== "function", "`file` can’t be a `done` anymore, we checked");
|
|
26454
26479
|
const realFile = vfile(file);
|
|
26455
26480
|
transformers.run(tree, realFile, realDone);
|
|
@@ -26457,8 +26482,8 @@ class Processor extends CallableInstance {
|
|
|
26457
26482
|
const resultingTree = outputTree || tree;
|
|
26458
26483
|
if (error) {
|
|
26459
26484
|
reject(error);
|
|
26460
|
-
} else if (
|
|
26461
|
-
|
|
26485
|
+
} else if (resolve5) {
|
|
26486
|
+
resolve5(resultingTree);
|
|
26462
26487
|
} else {
|
|
26463
26488
|
ok(done, "`done` is defined if `resolve` is not");
|
|
26464
26489
|
done(undefined, resultingTree, file2);
|
|
@@ -28492,214 +28517,984 @@ function flushCell(map4, context, range, rowKind, rowEnd, previousCell) {
|
|
|
28492
28517
|
map4.add(a, b, []);
|
|
28493
28518
|
}
|
|
28494
28519
|
}
|
|
28495
|
-
map4.add(range[3] + 1, 0, [["exit", valueToken, context]]);
|
|
28520
|
+
map4.add(range[3] + 1, 0, [["exit", valueToken, context]]);
|
|
28521
|
+
}
|
|
28522
|
+
if (rowEnd !== undefined) {
|
|
28523
|
+
previousCell.end = Object.assign({}, getPoint(context.events, rowEnd));
|
|
28524
|
+
map4.add(rowEnd, 0, [["exit", previousCell, context]]);
|
|
28525
|
+
previousCell = undefined;
|
|
28526
|
+
}
|
|
28527
|
+
return previousCell;
|
|
28528
|
+
}
|
|
28529
|
+
function flushTableEnd(map4, context, index2, table, tableBody) {
|
|
28530
|
+
const exits = [];
|
|
28531
|
+
const related = getPoint(context.events, index2);
|
|
28532
|
+
if (tableBody) {
|
|
28533
|
+
tableBody.end = Object.assign({}, related);
|
|
28534
|
+
exits.push(["exit", tableBody, context]);
|
|
28535
|
+
}
|
|
28536
|
+
table.end = Object.assign({}, related);
|
|
28537
|
+
exits.push(["exit", table, context]);
|
|
28538
|
+
map4.add(index2 + 1, 0, exits);
|
|
28539
|
+
}
|
|
28540
|
+
function getPoint(events, index2) {
|
|
28541
|
+
const event = events[index2];
|
|
28542
|
+
const side = event[0] === "enter" ? "start" : "end";
|
|
28543
|
+
return event[1][side];
|
|
28544
|
+
}
|
|
28545
|
+
// node_modules/micromark-extension-gfm-task-list-item/dev/lib/syntax.js
|
|
28546
|
+
var tasklistCheck = { name: "tasklistCheck", tokenize: tokenizeTasklistCheck };
|
|
28547
|
+
function gfmTaskListItem() {
|
|
28548
|
+
return {
|
|
28549
|
+
text: { [codes.leftSquareBracket]: tasklistCheck }
|
|
28550
|
+
};
|
|
28551
|
+
}
|
|
28552
|
+
function tokenizeTasklistCheck(effects, ok3, nok) {
|
|
28553
|
+
const self = this;
|
|
28554
|
+
return open;
|
|
28555
|
+
function open(code3) {
|
|
28556
|
+
ok(code3 === codes.leftSquareBracket, "expected `[`");
|
|
28557
|
+
if (self.previous !== codes.eof || !self._gfmTasklistFirstContentOfListItem) {
|
|
28558
|
+
return nok(code3);
|
|
28559
|
+
}
|
|
28560
|
+
effects.enter("taskListCheck");
|
|
28561
|
+
effects.enter("taskListCheckMarker");
|
|
28562
|
+
effects.consume(code3);
|
|
28563
|
+
effects.exit("taskListCheckMarker");
|
|
28564
|
+
return inside;
|
|
28565
|
+
}
|
|
28566
|
+
function inside(code3) {
|
|
28567
|
+
if (markdownLineEndingOrSpace(code3)) {
|
|
28568
|
+
effects.enter("taskListCheckValueUnchecked");
|
|
28569
|
+
effects.consume(code3);
|
|
28570
|
+
effects.exit("taskListCheckValueUnchecked");
|
|
28571
|
+
return close;
|
|
28572
|
+
}
|
|
28573
|
+
if (code3 === codes.uppercaseX || code3 === codes.lowercaseX) {
|
|
28574
|
+
effects.enter("taskListCheckValueChecked");
|
|
28575
|
+
effects.consume(code3);
|
|
28576
|
+
effects.exit("taskListCheckValueChecked");
|
|
28577
|
+
return close;
|
|
28578
|
+
}
|
|
28579
|
+
return nok(code3);
|
|
28580
|
+
}
|
|
28581
|
+
function close(code3) {
|
|
28582
|
+
if (code3 === codes.rightSquareBracket) {
|
|
28583
|
+
effects.enter("taskListCheckMarker");
|
|
28584
|
+
effects.consume(code3);
|
|
28585
|
+
effects.exit("taskListCheckMarker");
|
|
28586
|
+
effects.exit("taskListCheck");
|
|
28587
|
+
return after;
|
|
28588
|
+
}
|
|
28589
|
+
return nok(code3);
|
|
28590
|
+
}
|
|
28591
|
+
function after(code3) {
|
|
28592
|
+
if (markdownLineEnding(code3)) {
|
|
28593
|
+
return ok3(code3);
|
|
28594
|
+
}
|
|
28595
|
+
if (markdownSpace(code3)) {
|
|
28596
|
+
return effects.check({ tokenize: spaceThenNonSpace }, ok3, nok)(code3);
|
|
28597
|
+
}
|
|
28598
|
+
return nok(code3);
|
|
28599
|
+
}
|
|
28600
|
+
}
|
|
28601
|
+
function spaceThenNonSpace(effects, ok3, nok) {
|
|
28602
|
+
return factorySpace(effects, after, types.whitespace);
|
|
28603
|
+
function after(code3) {
|
|
28604
|
+
return code3 === codes.eof ? nok(code3) : ok3(code3);
|
|
28605
|
+
}
|
|
28606
|
+
}
|
|
28607
|
+
// node_modules/micromark-extension-gfm/index.js
|
|
28608
|
+
function gfm(options) {
|
|
28609
|
+
return combineExtensions([
|
|
28610
|
+
gfmAutolinkLiteral(),
|
|
28611
|
+
gfmFootnote(),
|
|
28612
|
+
gfmStrikethrough(options),
|
|
28613
|
+
gfmTable(),
|
|
28614
|
+
gfmTaskListItem()
|
|
28615
|
+
]);
|
|
28616
|
+
}
|
|
28617
|
+
|
|
28618
|
+
// node_modules/remark-gfm/lib/index.js
|
|
28619
|
+
var emptyOptions2 = {};
|
|
28620
|
+
function remarkGfm(options) {
|
|
28621
|
+
const self = this;
|
|
28622
|
+
const settings = options || emptyOptions2;
|
|
28623
|
+
const data = self.data();
|
|
28624
|
+
const micromarkExtensions = data.micromarkExtensions || (data.micromarkExtensions = []);
|
|
28625
|
+
const fromMarkdownExtensions = data.fromMarkdownExtensions || (data.fromMarkdownExtensions = []);
|
|
28626
|
+
const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
|
|
28627
|
+
micromarkExtensions.push(gfm(settings));
|
|
28628
|
+
fromMarkdownExtensions.push(gfmFromMarkdown());
|
|
28629
|
+
toMarkdownExtensions.push(gfmToMarkdown(settings));
|
|
28630
|
+
}
|
|
28631
|
+
// src/audit/core/markdown.ts
|
|
28632
|
+
var processor = remark().use(remarkGfm);
|
|
28633
|
+
function lineFromOffset(content3, offset) {
|
|
28634
|
+
if (offset === undefined)
|
|
28635
|
+
return;
|
|
28636
|
+
return content3.slice(0, offset).split(`
|
|
28637
|
+
`).length;
|
|
28638
|
+
}
|
|
28639
|
+
function stripYamlFrontmatter(content3) {
|
|
28640
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/.exec(content3);
|
|
28641
|
+
if (match)
|
|
28642
|
+
return content3.slice(match[0].length);
|
|
28643
|
+
const eof = /^---\r?\n([\s\S]*?)\r?\n---\s*$/.exec(content3);
|
|
28644
|
+
if (eof)
|
|
28645
|
+
return "";
|
|
28646
|
+
return content3;
|
|
28647
|
+
}
|
|
28648
|
+
function destinationConsumesToSliceEnd(slice, afterDest) {
|
|
28649
|
+
let i = afterDest;
|
|
28650
|
+
if (i >= slice.length)
|
|
28651
|
+
return false;
|
|
28652
|
+
if (slice[i] === ")") {
|
|
28653
|
+
return i === slice.length - 1;
|
|
28654
|
+
}
|
|
28655
|
+
if (!/\s/.test(slice[i]))
|
|
28656
|
+
return false;
|
|
28657
|
+
while (i < slice.length && /\s/.test(slice[i]))
|
|
28658
|
+
i++;
|
|
28659
|
+
if (i >= slice.length)
|
|
28660
|
+
return false;
|
|
28661
|
+
if (slice[i] === ")") {
|
|
28662
|
+
return i === slice.length - 1;
|
|
28663
|
+
}
|
|
28664
|
+
const open = slice[i];
|
|
28665
|
+
if (open !== '"' && open !== "'" && open !== "(")
|
|
28666
|
+
return false;
|
|
28667
|
+
const close = open === "(" ? ")" : open;
|
|
28668
|
+
i++;
|
|
28669
|
+
while (i < slice.length && slice[i] !== close)
|
|
28670
|
+
i++;
|
|
28671
|
+
if (i >= slice.length)
|
|
28672
|
+
return false;
|
|
28673
|
+
i++;
|
|
28674
|
+
while (i < slice.length && /\s/.test(slice[i]))
|
|
28675
|
+
i++;
|
|
28676
|
+
return i === slice.length - 1 && slice[i] === ")";
|
|
28677
|
+
}
|
|
28678
|
+
function findUrlSpanInSlice(content3, nodeStart, nodeEnd, url) {
|
|
28679
|
+
const slice = content3.slice(nodeStart, nodeEnd);
|
|
28680
|
+
let searchFrom = 0;
|
|
28681
|
+
while (searchFrom < slice.length) {
|
|
28682
|
+
const openParen = slice.indexOf("](", searchFrom);
|
|
28683
|
+
if (openParen === -1)
|
|
28684
|
+
break;
|
|
28685
|
+
const after = openParen + 2;
|
|
28686
|
+
if (slice.startsWith(`<${url}>`, after)) {
|
|
28687
|
+
const afterDest = after + 2 + url.length;
|
|
28688
|
+
if (destinationConsumesToSliceEnd(slice, afterDest)) {
|
|
28689
|
+
const urlStart = nodeStart + after + 1;
|
|
28690
|
+
return { urlStart, urlEnd: urlStart + url.length };
|
|
28691
|
+
}
|
|
28692
|
+
} else if (slice.startsWith(url, after)) {
|
|
28693
|
+
const next = slice[after + url.length];
|
|
28694
|
+
if (next === ")" || next !== undefined && /\s/.test(next)) {
|
|
28695
|
+
if (destinationConsumesToSliceEnd(slice, after + url.length)) {
|
|
28696
|
+
const urlStart = nodeStart + after;
|
|
28697
|
+
return { urlStart, urlEnd: urlStart + url.length };
|
|
28698
|
+
}
|
|
28699
|
+
}
|
|
28700
|
+
}
|
|
28701
|
+
searchFrom = openParen + 1;
|
|
28702
|
+
}
|
|
28703
|
+
const auto = `<${url}>`;
|
|
28704
|
+
if (slice === auto) {
|
|
28705
|
+
return { urlStart: nodeStart + 1, urlEnd: nodeStart + 1 + url.length };
|
|
28706
|
+
}
|
|
28707
|
+
if (slice === url) {
|
|
28708
|
+
return { urlStart: nodeStart, urlEnd: nodeEnd };
|
|
28709
|
+
}
|
|
28710
|
+
const trimmed = slice.trim();
|
|
28711
|
+
if (trimmed === auto) {
|
|
28712
|
+
const lead = slice.indexOf(auto);
|
|
28713
|
+
if (lead !== -1) {
|
|
28714
|
+
return { urlStart: nodeStart + lead + 1, urlEnd: nodeStart + lead + 1 + url.length };
|
|
28715
|
+
}
|
|
28716
|
+
}
|
|
28717
|
+
if (trimmed === url) {
|
|
28718
|
+
const lead = slice.indexOf(url);
|
|
28719
|
+
if (lead !== -1) {
|
|
28720
|
+
return { urlStart: nodeStart + lead, urlEnd: nodeStart + lead + url.length };
|
|
28721
|
+
}
|
|
28722
|
+
}
|
|
28723
|
+
return;
|
|
28724
|
+
}
|
|
28725
|
+
function findUrlInDefinitionSlice(content3, nodeStart, nodeEnd, url) {
|
|
28726
|
+
const slice = content3.slice(nodeStart, nodeEnd);
|
|
28727
|
+
const labelEnd2 = slice.indexOf("]:");
|
|
28728
|
+
if (labelEnd2 === -1)
|
|
28729
|
+
return;
|
|
28730
|
+
let i = labelEnd2 + 2;
|
|
28731
|
+
while (i < slice.length && /\s/.test(slice[i] ?? ""))
|
|
28732
|
+
i++;
|
|
28733
|
+
if (slice.startsWith(`<${url}>`, i)) {
|
|
28734
|
+
const urlStart = nodeStart + i + 1;
|
|
28735
|
+
return { urlStart, urlEnd: urlStart + url.length };
|
|
28736
|
+
}
|
|
28737
|
+
if (slice.startsWith(url, i)) {
|
|
28738
|
+
const urlStart = nodeStart + i;
|
|
28739
|
+
return { urlStart, urlEnd: urlStart + url.length };
|
|
28740
|
+
}
|
|
28741
|
+
return;
|
|
28742
|
+
}
|
|
28743
|
+
function collectReferenceDefinitions(content3, tree) {
|
|
28744
|
+
const defs = new Map;
|
|
28745
|
+
visit2(tree, (node2) => {
|
|
28746
|
+
if (node2.type !== "definition")
|
|
28747
|
+
return;
|
|
28748
|
+
if (!("identifier" in node2) || !("url" in node2))
|
|
28749
|
+
return;
|
|
28750
|
+
const id = String(node2.identifier).toLowerCase();
|
|
28751
|
+
if (defs.has(id))
|
|
28752
|
+
return;
|
|
28753
|
+
const url = typeof node2.url === "string" ? node2.url : "";
|
|
28754
|
+
if (!url)
|
|
28755
|
+
return;
|
|
28756
|
+
const start = node2.position?.start.offset;
|
|
28757
|
+
const end = node2.position?.end.offset;
|
|
28758
|
+
if (start === undefined || end === undefined)
|
|
28759
|
+
return;
|
|
28760
|
+
const span = findUrlInDefinitionSlice(content3, start, end, url);
|
|
28761
|
+
if (!span)
|
|
28762
|
+
return;
|
|
28763
|
+
defs.set(id, {
|
|
28764
|
+
url,
|
|
28765
|
+
urlStart: span.urlStart,
|
|
28766
|
+
urlEnd: span.urlEnd,
|
|
28767
|
+
line: lineFromOffset(content3, start) ?? 1
|
|
28768
|
+
});
|
|
28769
|
+
});
|
|
28770
|
+
return defs;
|
|
28771
|
+
}
|
|
28772
|
+
function extractLinksFromMarkdown(content3, _filePath) {
|
|
28773
|
+
const tree = processor.parse(content3);
|
|
28774
|
+
const refDefs = collectReferenceDefinitions(content3, tree);
|
|
28775
|
+
const links = [];
|
|
28776
|
+
visit2(tree, (node2) => {
|
|
28777
|
+
if (node2.type === "link" && "url" in node2 && typeof node2.url === "string") {
|
|
28778
|
+
const target = node2.url.trim();
|
|
28779
|
+
const start = node2.position?.start.offset;
|
|
28780
|
+
const end = node2.position?.end.offset;
|
|
28781
|
+
const span = start !== undefined && end !== undefined ? findUrlSpanInSlice(content3, start, end, target) : undefined;
|
|
28782
|
+
links.push({
|
|
28783
|
+
target,
|
|
28784
|
+
line: lineFromOffset(content3, node2.position?.start.offset),
|
|
28785
|
+
urlStart: span?.urlStart,
|
|
28786
|
+
urlEnd: span?.urlEnd
|
|
28787
|
+
});
|
|
28788
|
+
}
|
|
28789
|
+
if (node2.type === "linkReference" && "identifier" in node2) {
|
|
28790
|
+
const id = String(node2.identifier).toLowerCase();
|
|
28791
|
+
const def = refDefs.get(id);
|
|
28792
|
+
if (def) {
|
|
28793
|
+
links.push({
|
|
28794
|
+
target: def.url.trim(),
|
|
28795
|
+
line: def.line,
|
|
28796
|
+
urlStart: def.urlStart,
|
|
28797
|
+
urlEnd: def.urlEnd
|
|
28798
|
+
});
|
|
28799
|
+
}
|
|
28800
|
+
}
|
|
28801
|
+
});
|
|
28802
|
+
return links;
|
|
28803
|
+
}
|
|
28804
|
+
function phrasingText(nodes) {
|
|
28805
|
+
if (!nodes?.length)
|
|
28806
|
+
return "";
|
|
28807
|
+
let out = "";
|
|
28808
|
+
for (const node2 of nodes) {
|
|
28809
|
+
if (node2.type === "text" || node2.type === "inlineCode") {
|
|
28810
|
+
out += "value" in node2 && node2.value !== undefined ? String(node2.value) : "";
|
|
28811
|
+
continue;
|
|
28812
|
+
}
|
|
28813
|
+
if (node2.children?.length) {
|
|
28814
|
+
out += phrasingText(node2.children);
|
|
28815
|
+
}
|
|
28816
|
+
}
|
|
28817
|
+
return out;
|
|
28818
|
+
}
|
|
28819
|
+
function extractHeadingSlugs(content3, _filePath) {
|
|
28820
|
+
const body = stripYamlFrontmatter(content3);
|
|
28821
|
+
const slugger = new BananaSlug;
|
|
28822
|
+
const slugs = new Set;
|
|
28823
|
+
const tree = processor.parse(body);
|
|
28824
|
+
visit2(tree, (node2) => {
|
|
28825
|
+
if (node2.type === "heading" && "children" in node2) {
|
|
28826
|
+
const text5 = phrasingText(node2.children);
|
|
28827
|
+
if (text5)
|
|
28828
|
+
slugs.add(slugger.slug(text5));
|
|
28829
|
+
}
|
|
28830
|
+
});
|
|
28831
|
+
return slugs;
|
|
28832
|
+
}
|
|
28833
|
+
function slugifyAnchor(anchor) {
|
|
28834
|
+
const slugger = new BananaSlug;
|
|
28835
|
+
return slugger.slug(decodeURIComponent(anchor));
|
|
28836
|
+
}
|
|
28837
|
+
|
|
28838
|
+
// src/audit/fix/match-anchor.ts
|
|
28839
|
+
var ANCHOR_MATCH_MIN_SCORE = 2 / 3;
|
|
28840
|
+
var ANCHOR_MATCH_MIN_MARGIN = 0.15;
|
|
28841
|
+
function tokenize(slug2) {
|
|
28842
|
+
return new Set(slug2.split("-").filter(Boolean));
|
|
28843
|
+
}
|
|
28844
|
+
function jaccard(a, b) {
|
|
28845
|
+
if (a.size === 0 && b.size === 0)
|
|
28846
|
+
return 0;
|
|
28847
|
+
let intersection = 0;
|
|
28848
|
+
for (const token of a) {
|
|
28849
|
+
if (b.has(token))
|
|
28850
|
+
intersection++;
|
|
28851
|
+
}
|
|
28852
|
+
const union = a.size + b.size - intersection;
|
|
28853
|
+
return union === 0 ? 0 : intersection / union;
|
|
28854
|
+
}
|
|
28855
|
+
function scoreAnchorMatch(brokenSlug, candidateSlug) {
|
|
28856
|
+
if (brokenSlug === candidateSlug)
|
|
28857
|
+
return 1;
|
|
28858
|
+
if (candidateSlug.startsWith(`${brokenSlug}-`)) {
|
|
28859
|
+
return 1;
|
|
28860
|
+
}
|
|
28861
|
+
if (brokenSlug.startsWith(candidateSlug) && brokenSlug.length > candidateSlug.length) {
|
|
28862
|
+
return 0;
|
|
28863
|
+
}
|
|
28864
|
+
return jaccard(tokenize(brokenSlug), tokenize(candidateSlug));
|
|
28865
|
+
}
|
|
28866
|
+
function findBestAnchorMatch(brokenSlug, candidates) {
|
|
28867
|
+
const scored = [];
|
|
28868
|
+
for (const slug2 of candidates) {
|
|
28869
|
+
if (slug2 === brokenSlug)
|
|
28870
|
+
continue;
|
|
28871
|
+
scored.push({ slug: slug2, score: scoreAnchorMatch(brokenSlug, slug2) });
|
|
28872
|
+
}
|
|
28873
|
+
if (scored.length === 0)
|
|
28874
|
+
return null;
|
|
28875
|
+
scored.sort((a, b) => b.score - a.score || a.slug.localeCompare(b.slug));
|
|
28876
|
+
const best = scored[0];
|
|
28877
|
+
const second = scored[1];
|
|
28878
|
+
if (!best || best.score < ANCHOR_MATCH_MIN_SCORE)
|
|
28879
|
+
return null;
|
|
28880
|
+
if (second && best.score - second.score < ANCHOR_MATCH_MIN_MARGIN)
|
|
28881
|
+
return null;
|
|
28882
|
+
return best;
|
|
28883
|
+
}
|
|
28884
|
+
|
|
28885
|
+
// src/audit/fix/anchors.ts
|
|
28886
|
+
function resolveLink(sourceFile, target) {
|
|
28887
|
+
const withoutAnchor = target.split("#")[0]?.split("?")[0] ?? "";
|
|
28888
|
+
if (!withoutAnchor)
|
|
28889
|
+
return sourceFile;
|
|
28890
|
+
return resolve5(dirname5(sourceFile), withoutAnchor);
|
|
28891
|
+
}
|
|
28892
|
+
function replaceAnchorInTarget(target, oldAnchor, newAnchor) {
|
|
28893
|
+
const hashIndex = target.indexOf("#");
|
|
28894
|
+
if (hashIndex === -1)
|
|
28895
|
+
return target;
|
|
28896
|
+
const pathPart = target.slice(0, hashIndex);
|
|
28897
|
+
const fragment = target.slice(hashIndex + 1);
|
|
28898
|
+
const queryIndex = fragment.indexOf("?");
|
|
28899
|
+
const anchorPart = queryIndex === -1 ? fragment : fragment.slice(0, queryIndex);
|
|
28900
|
+
const queryPart = queryIndex === -1 ? "" : fragment.slice(queryIndex);
|
|
28901
|
+
if (anchorPart !== oldAnchor)
|
|
28902
|
+
return target;
|
|
28903
|
+
return `${pathPart}#${newAnchor}${queryPart}`;
|
|
28904
|
+
}
|
|
28905
|
+
function collectAnchorFixes(ctx) {
|
|
28906
|
+
const editsByFile = new Map;
|
|
28907
|
+
for (const filePath of ctx.files) {
|
|
28908
|
+
const content3 = readFileContent(filePath);
|
|
28909
|
+
const links = extractLinksFromMarkdown(content3, filePath);
|
|
28910
|
+
const pending = [];
|
|
28911
|
+
const relFile = relPath(filePath, ctx.root);
|
|
28912
|
+
for (const { target, line, urlStart, urlEnd } of links) {
|
|
28913
|
+
if (isExternalLink(target) && !target.startsWith("#"))
|
|
28914
|
+
continue;
|
|
28915
|
+
if (isPlaceholderLink(target))
|
|
28916
|
+
continue;
|
|
28917
|
+
const anchor = target.includes("#") ? target.split("#")[1]?.split("?")[0] ?? "" : "";
|
|
28918
|
+
if (!anchor)
|
|
28919
|
+
continue;
|
|
28920
|
+
const resolved = resolveLink(filePath, target);
|
|
28921
|
+
if (!existsSync8(resolved))
|
|
28922
|
+
continue;
|
|
28923
|
+
const targetContent = readFileSync6(resolved, "utf8");
|
|
28924
|
+
const slugs = extractHeadingSlugs(targetContent, resolved);
|
|
28925
|
+
const anchorSlug = slugifyAnchor(anchor);
|
|
28926
|
+
if (slugs.has(anchorSlug))
|
|
28927
|
+
continue;
|
|
28928
|
+
const match = findBestAnchorMatch(anchorSlug, slugs);
|
|
28929
|
+
if (!match)
|
|
28930
|
+
continue;
|
|
28931
|
+
const nextTarget = replaceAnchorInTarget(target, anchor, match.slug);
|
|
28932
|
+
if (nextTarget === target)
|
|
28933
|
+
continue;
|
|
28934
|
+
const lineLabel = line ? `${relFile}:${line}` : relFile;
|
|
28935
|
+
const description = `${lineLabel} #${anchor} → #${match.slug} (score ${match.score.toFixed(2)})`;
|
|
28936
|
+
if (urlStart !== undefined && urlEnd !== undefined && content3.slice(urlStart, urlEnd) === target) {
|
|
28937
|
+
pending.push({ urlStart, urlEnd, from: target, to: nextTarget, description });
|
|
28938
|
+
}
|
|
28939
|
+
}
|
|
28940
|
+
if (pending.length === 0)
|
|
28941
|
+
continue;
|
|
28942
|
+
const uniqueBySpan = new Map;
|
|
28943
|
+
for (const edit of pending) {
|
|
28944
|
+
uniqueBySpan.set(`${edit.urlStart}:${edit.urlEnd}:${edit.from}`, edit);
|
|
28945
|
+
}
|
|
28946
|
+
const uniquePending = [...uniqueBySpan.values()];
|
|
28947
|
+
uniquePending.sort((a, b) => b.urlStart - a.urlStart);
|
|
28948
|
+
let updated = content3;
|
|
28949
|
+
const descriptions = [];
|
|
28950
|
+
for (const edit of uniquePending) {
|
|
28951
|
+
if (updated.slice(edit.urlStart, edit.urlEnd) !== edit.from)
|
|
28952
|
+
continue;
|
|
28953
|
+
updated = updated.slice(0, edit.urlStart) + edit.to + updated.slice(edit.urlEnd);
|
|
28954
|
+
descriptions.push(edit.description);
|
|
28955
|
+
}
|
|
28956
|
+
if (updated === content3 || descriptions.length === 0)
|
|
28957
|
+
continue;
|
|
28958
|
+
editsByFile.set(filePath, { content: updated, descriptions });
|
|
28959
|
+
}
|
|
28960
|
+
const edits = [];
|
|
28961
|
+
for (const [absPath, { content: content3, descriptions }] of editsByFile) {
|
|
28962
|
+
edits.push({
|
|
28963
|
+
file: relPath(absPath, ctx.root),
|
|
28964
|
+
description: descriptions.join("; "),
|
|
28965
|
+
content: content3
|
|
28966
|
+
});
|
|
28967
|
+
}
|
|
28968
|
+
return edits;
|
|
28969
|
+
}
|
|
28970
|
+
|
|
28971
|
+
// src/audit/fix/doc-meta.ts
|
|
28972
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
28973
|
+
import { join as join8 } from "node:path";
|
|
28974
|
+
|
|
28975
|
+
// src/audit/core/git-meta.ts
|
|
28976
|
+
import { spawnSync } from "node:child_process";
|
|
28977
|
+
function lastGitCommitDate(relPath2, root2) {
|
|
28978
|
+
const proc = spawnSync("git", ["log", "-1", "--format=%cs", "--", relPath2], {
|
|
28979
|
+
cwd: root2,
|
|
28980
|
+
encoding: "utf8"
|
|
28981
|
+
});
|
|
28982
|
+
if (proc.status !== 0)
|
|
28983
|
+
return null;
|
|
28984
|
+
const date = proc.stdout.trim();
|
|
28985
|
+
return date || null;
|
|
28986
|
+
}
|
|
28987
|
+
|
|
28988
|
+
// src/audit/fix/doc-meta.ts
|
|
28989
|
+
function bumpDocMetaLastReviewed(content3, gitDate) {
|
|
28990
|
+
const reviewedStr = docMetaLastReviewed(content3);
|
|
28991
|
+
if (!reviewedStr)
|
|
28992
|
+
return null;
|
|
28993
|
+
const reviewed = new Date(`${reviewedStr}T00:00:00Z`);
|
|
28994
|
+
const committed = new Date(`${gitDate}T00:00:00Z`);
|
|
28995
|
+
if (Number.isNaN(reviewed.getTime()) || Number.isNaN(committed.getTime()))
|
|
28996
|
+
return null;
|
|
28997
|
+
if (committed.getTime() <= reviewed.getTime())
|
|
28998
|
+
return null;
|
|
28999
|
+
return replaceDocMetaLastReviewed(content3, gitDate);
|
|
29000
|
+
}
|
|
29001
|
+
function collectDocMetaFixes(ctx) {
|
|
29002
|
+
const edits = [];
|
|
29003
|
+
for (const relPath2 of ctx.docMetaPaths) {
|
|
29004
|
+
const abs = join8(ctx.root, relPath2);
|
|
29005
|
+
if (!existsSync9(abs))
|
|
29006
|
+
continue;
|
|
29007
|
+
const content3 = readFileSync7(abs, "utf8");
|
|
29008
|
+
if (!DOC_META_RE.test(content3))
|
|
29009
|
+
continue;
|
|
29010
|
+
const reviewedStr = docMetaLastReviewed(content3);
|
|
29011
|
+
if (!reviewedStr)
|
|
29012
|
+
continue;
|
|
29013
|
+
const reviewed = new Date(`${reviewedStr}T00:00:00Z`);
|
|
29014
|
+
if (Number.isNaN(reviewed.getTime()))
|
|
29015
|
+
continue;
|
|
29016
|
+
const gitDate = lastGitCommitDate(relPath2, ctx.root);
|
|
29017
|
+
if (!gitDate)
|
|
29018
|
+
continue;
|
|
29019
|
+
const updated = bumpDocMetaLastReviewed(content3, gitDate);
|
|
29020
|
+
if (!updated)
|
|
29021
|
+
continue;
|
|
29022
|
+
edits.push({
|
|
29023
|
+
file: relPath2,
|
|
29024
|
+
description: `last-reviewed ${reviewedStr} → ${gitDate}`,
|
|
29025
|
+
content: updated
|
|
29026
|
+
});
|
|
29027
|
+
}
|
|
29028
|
+
return edits;
|
|
29029
|
+
}
|
|
29030
|
+
|
|
29031
|
+
// src/audit/core/fix.ts
|
|
29032
|
+
function collectFixes(ctx, kinds) {
|
|
29033
|
+
const meta = kinds.has("doc-meta") ? collectDocMetaFixes(ctx) : [];
|
|
29034
|
+
const anchors = kinds.has("anchors") ? collectAnchorFixes(ctx) : [];
|
|
29035
|
+
return coalesceFixEdits(meta, anchors);
|
|
29036
|
+
}
|
|
29037
|
+
function coalesceFixEdits(metaEdits, anchorEdits) {
|
|
29038
|
+
const metaByFile = new Map(metaEdits.map((e) => [e.file, e]));
|
|
29039
|
+
const anchorByFile = new Map(anchorEdits.map((e) => [e.file, e]));
|
|
29040
|
+
const files = new Set([...metaByFile.keys(), ...anchorByFile.keys()]);
|
|
29041
|
+
const out = [];
|
|
29042
|
+
for (const file of [...files].sort()) {
|
|
29043
|
+
const meta = metaByFile.get(file);
|
|
29044
|
+
const anchors = anchorByFile.get(file);
|
|
29045
|
+
if (meta && anchors) {
|
|
29046
|
+
out.push({
|
|
29047
|
+
file,
|
|
29048
|
+
description: `${meta.description}; ${anchors.description}`,
|
|
29049
|
+
content: overlayLastReviewed(anchors.content, meta.content)
|
|
29050
|
+
});
|
|
29051
|
+
} else if (meta) {
|
|
29052
|
+
out.push(meta);
|
|
29053
|
+
} else if (anchors) {
|
|
29054
|
+
out.push(anchors);
|
|
29055
|
+
}
|
|
29056
|
+
}
|
|
29057
|
+
return out;
|
|
29058
|
+
}
|
|
29059
|
+
function overlayLastReviewed(targetContent, metaContent) {
|
|
29060
|
+
const date = docMetaLastReviewed(metaContent);
|
|
29061
|
+
if (!date)
|
|
29062
|
+
return targetContent;
|
|
29063
|
+
return replaceDocMetaLastReviewed(targetContent, date) ?? targetContent;
|
|
29064
|
+
}
|
|
29065
|
+
function underRoot(rootAbs, candidateAbs) {
|
|
29066
|
+
return candidateAbs === rootAbs || candidateAbs.startsWith(rootAbs + sep3);
|
|
29067
|
+
}
|
|
29068
|
+
function resolveWritePath(root2, relFile) {
|
|
29069
|
+
const rootResolved = resolve6(root2);
|
|
29070
|
+
const abs = resolve6(rootResolved, relFile);
|
|
29071
|
+
if (!underRoot(rootResolved, abs)) {
|
|
29072
|
+
throw new Error(`Refusing autofix outside repo root: ${relFile}`);
|
|
29073
|
+
}
|
|
29074
|
+
const rootReal = existsSync10(rootResolved) ? realpathSync4(rootResolved) : rootResolved;
|
|
29075
|
+
let cursor = abs;
|
|
29076
|
+
while (true) {
|
|
29077
|
+
if (existsSync10(cursor)) {
|
|
29078
|
+
const real = realpathSync4(cursor);
|
|
29079
|
+
if (!underRoot(rootReal, real)) {
|
|
29080
|
+
throw new Error(`Refusing autofix outside repo root: ${relFile}`);
|
|
29081
|
+
}
|
|
29082
|
+
return abs;
|
|
29083
|
+
}
|
|
29084
|
+
const parent = dirname6(cursor);
|
|
29085
|
+
if (parent === cursor || !underRoot(rootResolved, parent) && parent !== rootResolved) {
|
|
29086
|
+
return abs;
|
|
29087
|
+
}
|
|
29088
|
+
cursor = parent;
|
|
28496
29089
|
}
|
|
28497
|
-
|
|
28498
|
-
|
|
28499
|
-
|
|
28500
|
-
|
|
29090
|
+
}
|
|
29091
|
+
function applyFixes(ctx, options) {
|
|
29092
|
+
const kinds = new Set(options.kinds);
|
|
29093
|
+
const edits = collectFixes(ctx, kinds);
|
|
29094
|
+
const modifiedFiles = [];
|
|
29095
|
+
if (edits.length > 0) {
|
|
29096
|
+
console.error(`Doc audit autofix:
|
|
29097
|
+
`);
|
|
29098
|
+
for (const edit of edits) {
|
|
29099
|
+
console.error(`- ${edit.file}: ${edit.description}`);
|
|
29100
|
+
if (!options.dryRun) {
|
|
29101
|
+
const abs = resolveWritePath(ctx.root, edit.file);
|
|
29102
|
+
writeFileSync(abs, edit.content, "utf8");
|
|
29103
|
+
modifiedFiles.push(edit.file);
|
|
29104
|
+
}
|
|
29105
|
+
}
|
|
29106
|
+
console.error("");
|
|
28501
29107
|
}
|
|
28502
|
-
return
|
|
29108
|
+
return { edits, modifiedFiles };
|
|
28503
29109
|
}
|
|
28504
|
-
function
|
|
28505
|
-
|
|
28506
|
-
|
|
28507
|
-
|
|
28508
|
-
|
|
28509
|
-
|
|
29110
|
+
function parseFixKinds(raw) {
|
|
29111
|
+
if (raw === true)
|
|
29112
|
+
return ["doc-meta", "anchors"];
|
|
29113
|
+
switch (raw) {
|
|
29114
|
+
case "doc-meta":
|
|
29115
|
+
return ["doc-meta"];
|
|
29116
|
+
case "anchors":
|
|
29117
|
+
return ["anchors"];
|
|
29118
|
+
default:
|
|
29119
|
+
throw new Error(`Unknown --fix kind: ${raw}. Use doc-meta or anchors.`);
|
|
28510
29120
|
}
|
|
28511
|
-
table.end = Object.assign({}, related);
|
|
28512
|
-
exits.push(["exit", table, context]);
|
|
28513
|
-
map4.add(index2 + 1, 0, exits);
|
|
28514
29121
|
}
|
|
28515
|
-
|
|
28516
|
-
|
|
28517
|
-
|
|
28518
|
-
|
|
29122
|
+
var FIX_KIND_RULE = {
|
|
29123
|
+
"doc-meta": "doc-meta",
|
|
29124
|
+
anchors: "links"
|
|
29125
|
+
};
|
|
29126
|
+
function fixKindsForOnly(kinds, only) {
|
|
29127
|
+
if (!only)
|
|
29128
|
+
return kinds;
|
|
29129
|
+
return kinds.filter((kind) => only.has(FIX_KIND_RULE[kind]));
|
|
28519
29130
|
}
|
|
28520
|
-
|
|
28521
|
-
|
|
28522
|
-
function
|
|
29131
|
+
|
|
29132
|
+
// src/audit/core/report.ts
|
|
29133
|
+
function issue(rule, file, message, opts) {
|
|
28523
29134
|
return {
|
|
28524
|
-
|
|
29135
|
+
rule,
|
|
29136
|
+
file,
|
|
29137
|
+
link: opts?.link,
|
|
29138
|
+
message,
|
|
29139
|
+
severity: opts?.severity ?? "error"
|
|
28525
29140
|
};
|
|
28526
29141
|
}
|
|
28527
|
-
function
|
|
28528
|
-
|
|
28529
|
-
|
|
28530
|
-
|
|
28531
|
-
|
|
28532
|
-
|
|
28533
|
-
|
|
29142
|
+
function finalizeIssues(issues, strict) {
|
|
29143
|
+
if (!strict)
|
|
29144
|
+
return issues;
|
|
29145
|
+
return issues.map((i) => i.severity === "warning" ? { ...i, severity: "error" } : i);
|
|
29146
|
+
}
|
|
29147
|
+
function printReport(issues, options) {
|
|
29148
|
+
const finalized = finalizeIssues(issues, options.strict ?? false);
|
|
29149
|
+
const errors2 = finalized.filter((i) => i.severity === "error");
|
|
29150
|
+
const warnings = finalized.filter((i) => i.severity === "warning");
|
|
29151
|
+
const label = options.label ?? "Audit";
|
|
29152
|
+
if (options.json) {
|
|
29153
|
+
console.log(JSON.stringify({
|
|
29154
|
+
label,
|
|
29155
|
+
fileCount: options.fileCount,
|
|
29156
|
+
errors: errors2.length,
|
|
29157
|
+
warnings: warnings.length,
|
|
29158
|
+
issues: finalized
|
|
29159
|
+
}, null, 2));
|
|
29160
|
+
return errors2.length > 0 ? 1 : 0;
|
|
29161
|
+
}
|
|
29162
|
+
if (warnings.length > 0) {
|
|
29163
|
+
console.log(`${label} warnings:
|
|
29164
|
+
`);
|
|
29165
|
+
for (const i of warnings) {
|
|
29166
|
+
const linkPart = i.link ? ` (${i.link})` : "";
|
|
29167
|
+
console.log(`- ${i.file}${linkPart}: ${i.message}`);
|
|
28534
29168
|
}
|
|
28535
|
-
|
|
28536
|
-
effects.enter("taskListCheckMarker");
|
|
28537
|
-
effects.consume(code3);
|
|
28538
|
-
effects.exit("taskListCheckMarker");
|
|
28539
|
-
return inside;
|
|
29169
|
+
console.log("");
|
|
28540
29170
|
}
|
|
28541
|
-
|
|
28542
|
-
|
|
28543
|
-
|
|
28544
|
-
|
|
28545
|
-
|
|
28546
|
-
|
|
29171
|
+
if (errors2.length === 0) {
|
|
29172
|
+
const warnNote = warnings.length > 0 ? `, ${warnings.length} warning(s)` : "";
|
|
29173
|
+
const countNote = options.successSuffix ?? (options.fileCount !== undefined ? ` (${options.fileCount} files scanned${warnNote})` : "");
|
|
29174
|
+
console.log(`${label} passed${countNote}.`);
|
|
29175
|
+
return 0;
|
|
29176
|
+
}
|
|
29177
|
+
console.log(`${label} failed:
|
|
29178
|
+
`);
|
|
29179
|
+
for (const i of errors2) {
|
|
29180
|
+
const linkPart = i.link ? ` (${i.link})` : "";
|
|
29181
|
+
console.log(`- ${i.file}${linkPart}: ${i.message}`);
|
|
29182
|
+
}
|
|
29183
|
+
return 1;
|
|
29184
|
+
}
|
|
29185
|
+
|
|
29186
|
+
// src/references/check.ts
|
|
29187
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
29188
|
+
import { join as join10, relative as relative7 } from "node:path";
|
|
29189
|
+
|
|
29190
|
+
// src/references/constants.ts
|
|
29191
|
+
var CANONICAL_REFS_DIR = ".skeleton/references";
|
|
29192
|
+
var GENERATED_MARKER_START = "<!-- skeleton: generated-reference";
|
|
29193
|
+
var GENERATED_MARKER_RE = /<!-- skeleton: generated-reference\s*\nsource: ([^\n]+)\s*\nredundancy: intentional\s*\n-->\s*\n?/;
|
|
29194
|
+
var SHARED_REF_LINK_RE = /\((?:\.\.\/)+references\/([^)]+)\)/g;
|
|
29195
|
+
function formatGeneratedHeader(sourceRelPath) {
|
|
29196
|
+
return `${GENERATED_MARKER_START}
|
|
29197
|
+
source: ${sourceRelPath}
|
|
29198
|
+
redundancy: intentional
|
|
29199
|
+
-->
|
|
29200
|
+
|
|
29201
|
+
`;
|
|
29202
|
+
}
|
|
29203
|
+
function stripGeneratedHeader(content3) {
|
|
29204
|
+
return content3.replace(GENERATED_MARKER_RE, "");
|
|
29205
|
+
}
|
|
29206
|
+
function isGeneratedReference(content3) {
|
|
29207
|
+
return content3.startsWith(GENERATED_MARKER_START);
|
|
29208
|
+
}
|
|
29209
|
+
|
|
29210
|
+
// src/references/discover.ts
|
|
29211
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
|
|
29212
|
+
import { join as join9, relative as relative6 } from "node:path";
|
|
29213
|
+
function walkMarkdownFiles(dir, root2) {
|
|
29214
|
+
const files = [];
|
|
29215
|
+
if (!existsSync11(dir))
|
|
29216
|
+
return files;
|
|
29217
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
29218
|
+
if (entry.name.startsWith("."))
|
|
29219
|
+
continue;
|
|
29220
|
+
const fullPath = join9(dir, entry.name);
|
|
29221
|
+
if (entry.isDirectory()) {
|
|
29222
|
+
files.push(...walkMarkdownFiles(fullPath, root2));
|
|
29223
|
+
continue;
|
|
28547
29224
|
}
|
|
28548
|
-
if (
|
|
28549
|
-
|
|
28550
|
-
effects.consume(code3);
|
|
28551
|
-
effects.exit("taskListCheckValueChecked");
|
|
28552
|
-
return close;
|
|
29225
|
+
if (entry.name.endsWith(".md")) {
|
|
29226
|
+
files.push(normalizeRelPath(relative6(root2, fullPath)));
|
|
28553
29227
|
}
|
|
28554
|
-
return nok(code3);
|
|
28555
29228
|
}
|
|
28556
|
-
|
|
28557
|
-
|
|
28558
|
-
|
|
28559
|
-
|
|
28560
|
-
|
|
28561
|
-
|
|
28562
|
-
|
|
29229
|
+
return files;
|
|
29230
|
+
}
|
|
29231
|
+
function canonicalExists(root2, refPath) {
|
|
29232
|
+
return existsSync11(join9(root2, CANONICAL_REFS_DIR, refPath));
|
|
29233
|
+
}
|
|
29234
|
+
function findSharedRefLinks(content3, sourceFile) {
|
|
29235
|
+
const links = [];
|
|
29236
|
+
for (const match of content3.matchAll(SHARED_REF_LINK_RE)) {
|
|
29237
|
+
const refPath = match[1];
|
|
29238
|
+
if (!refPath)
|
|
29239
|
+
continue;
|
|
29240
|
+
links.push({ refPath: normalizeRelPath(refPath), sourceFile });
|
|
29241
|
+
}
|
|
29242
|
+
return links;
|
|
29243
|
+
}
|
|
29244
|
+
function findLocalCanonicalLinks(root2, content3, sourceFile) {
|
|
29245
|
+
const links = [];
|
|
29246
|
+
const localRefRe = /\((?:\.\/)?references\/([^)]+)\)/g;
|
|
29247
|
+
for (const match of content3.matchAll(localRefRe)) {
|
|
29248
|
+
const refPath = normalizeRelPath(match[1] ?? "");
|
|
29249
|
+
if (!refPath || !canonicalExists(root2, refPath))
|
|
29250
|
+
continue;
|
|
29251
|
+
links.push({ refPath, sourceFile });
|
|
29252
|
+
}
|
|
29253
|
+
const inReferencesDir = /\/references\//.test(sourceFile);
|
|
29254
|
+
if (inReferencesDir) {
|
|
29255
|
+
const refsIdx = sourceFile.lastIndexOf("/references/");
|
|
29256
|
+
const withinRefs = sourceFile.slice(refsIdx + "/references/".length);
|
|
29257
|
+
const withinDir = withinRefs.includes("/") ? withinRefs.slice(0, withinRefs.lastIndexOf("/")) : "";
|
|
29258
|
+
const siblingRe = /\((?!https?:|#|\.\.\/)([a-z0-9./_-]+\.md)\)/gi;
|
|
29259
|
+
for (const match of content3.matchAll(siblingRe)) {
|
|
29260
|
+
const raw = normalizeRelPath(match[1] ?? "");
|
|
29261
|
+
if (!raw)
|
|
29262
|
+
continue;
|
|
29263
|
+
const refPath = withinDir ? normalizeRelPath(join9(withinDir, raw)) : raw;
|
|
29264
|
+
if (!canonicalExists(root2, refPath))
|
|
29265
|
+
continue;
|
|
29266
|
+
links.push({ refPath, sourceFile });
|
|
28563
29267
|
}
|
|
28564
|
-
return nok(code3);
|
|
28565
29268
|
}
|
|
28566
|
-
|
|
28567
|
-
|
|
28568
|
-
|
|
29269
|
+
return links;
|
|
29270
|
+
}
|
|
29271
|
+
function discoverSkillReferencePlans(root2) {
|
|
29272
|
+
const index2 = buildSkillIndex(root2);
|
|
29273
|
+
const plans = [];
|
|
29274
|
+
for (const slug2 of index2.slugs) {
|
|
29275
|
+
const skillDir = join9(root2, slug2);
|
|
29276
|
+
if (!existsSync11(join9(skillDir, "SKILL.md")))
|
|
29277
|
+
continue;
|
|
29278
|
+
const refPaths = new Set;
|
|
29279
|
+
const links = [];
|
|
29280
|
+
for (const relFile of walkMarkdownFiles(skillDir, root2)) {
|
|
29281
|
+
const content3 = readFileSync8(join9(root2, relFile), "utf8");
|
|
29282
|
+
if (isGeneratedReference(content3))
|
|
29283
|
+
continue;
|
|
29284
|
+
for (const link2 of findSharedRefLinks(content3, relFile)) {
|
|
29285
|
+
refPaths.add(link2.refPath);
|
|
29286
|
+
links.push(link2);
|
|
29287
|
+
}
|
|
29288
|
+
for (const link2 of findLocalCanonicalLinks(root2, content3, relFile)) {
|
|
29289
|
+
refPaths.add(link2.refPath);
|
|
29290
|
+
links.push(link2);
|
|
29291
|
+
}
|
|
28569
29292
|
}
|
|
28570
|
-
|
|
28571
|
-
|
|
29293
|
+
const queue = [...refPaths];
|
|
29294
|
+
while (queue.length > 0) {
|
|
29295
|
+
const refPath = queue.pop();
|
|
29296
|
+
if (!refPath || !canonicalExists(root2, refPath))
|
|
29297
|
+
continue;
|
|
29298
|
+
const canonicalContent = readFileSync8(join9(root2, CANONICAL_REFS_DIR, refPath), "utf8");
|
|
29299
|
+
const syntheticSource = generatedRefPath(slug2, refPath);
|
|
29300
|
+
for (const link2 of findLocalCanonicalLinks(root2, canonicalContent, syntheticSource)) {
|
|
29301
|
+
if (refPaths.has(link2.refPath))
|
|
29302
|
+
continue;
|
|
29303
|
+
refPaths.add(link2.refPath);
|
|
29304
|
+
links.push(link2);
|
|
29305
|
+
queue.push(link2.refPath);
|
|
29306
|
+
}
|
|
29307
|
+
}
|
|
29308
|
+
if (refPaths.size > 0) {
|
|
29309
|
+
plans.push({ skill: slug2, refPaths, links });
|
|
28572
29310
|
}
|
|
28573
|
-
return nok(code3);
|
|
28574
29311
|
}
|
|
29312
|
+
return plans.sort((a, b) => a.skill.localeCompare(b.skill));
|
|
28575
29313
|
}
|
|
28576
|
-
function
|
|
28577
|
-
return
|
|
28578
|
-
|
|
28579
|
-
|
|
29314
|
+
function generatedRefPath(skill, refPath) {
|
|
29315
|
+
return normalizeRelPath(join9(skill, "references", refPath));
|
|
29316
|
+
}
|
|
29317
|
+
function rewriteSharedRefTarget(sourceFile, skill, refPath) {
|
|
29318
|
+
const sourceDir = sourceFile.slice(0, sourceFile.lastIndexOf("/"));
|
|
29319
|
+
const target = generatedRefPath(skill, refPath);
|
|
29320
|
+
if (!sourceDir)
|
|
29321
|
+
return target;
|
|
29322
|
+
const fromParts = sourceDir.split("/");
|
|
29323
|
+
const toParts = target.split("/");
|
|
29324
|
+
let i = 0;
|
|
29325
|
+
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
|
|
29326
|
+
i++;
|
|
28580
29327
|
}
|
|
29328
|
+
const ups = fromParts.length - i;
|
|
29329
|
+
const down = toParts.slice(i);
|
|
29330
|
+
const rel = [...Array(ups).fill(".."), ...down].join("/");
|
|
29331
|
+
return rel || (toParts.at(-1) ?? refPath);
|
|
28581
29332
|
}
|
|
28582
|
-
|
|
28583
|
-
|
|
28584
|
-
|
|
28585
|
-
|
|
28586
|
-
|
|
28587
|
-
gfmStrikethrough(options),
|
|
28588
|
-
gfmTable(),
|
|
28589
|
-
gfmTaskListItem()
|
|
28590
|
-
]);
|
|
29333
|
+
function rewriteSharedRefLinks(content3, sourceFile, skill) {
|
|
29334
|
+
return content3.replace(SHARED_REF_LINK_RE, (_match, refPath) => {
|
|
29335
|
+
const rewritten = rewriteSharedRefTarget(sourceFile, skill, normalizeRelPath(refPath));
|
|
29336
|
+
return `(${rewritten})`;
|
|
29337
|
+
});
|
|
28591
29338
|
}
|
|
28592
29339
|
|
|
28593
|
-
//
|
|
28594
|
-
|
|
28595
|
-
|
|
28596
|
-
const
|
|
28597
|
-
|
|
28598
|
-
|
|
28599
|
-
|
|
28600
|
-
|
|
28601
|
-
|
|
28602
|
-
|
|
28603
|
-
|
|
28604
|
-
|
|
28605
|
-
|
|
28606
|
-
|
|
28607
|
-
|
|
28608
|
-
|
|
28609
|
-
|
|
28610
|
-
|
|
28611
|
-
|
|
28612
|
-
|
|
28613
|
-
|
|
28614
|
-
|
|
28615
|
-
|
|
29340
|
+
// src/references/check.ts
|
|
29341
|
+
function listAllGeneratedFiles(root2) {
|
|
29342
|
+
const files = [];
|
|
29343
|
+
const walk = (dir) => {
|
|
29344
|
+
if (!existsSync12(dir))
|
|
29345
|
+
return;
|
|
29346
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
29347
|
+
if (entry.name.startsWith("."))
|
|
29348
|
+
continue;
|
|
29349
|
+
const fullPath = join10(dir, entry.name);
|
|
29350
|
+
if (entry.isDirectory()) {
|
|
29351
|
+
walk(fullPath);
|
|
29352
|
+
continue;
|
|
29353
|
+
}
|
|
29354
|
+
if (!entry.name.endsWith(".md"))
|
|
29355
|
+
continue;
|
|
29356
|
+
const content3 = readFileSync9(fullPath, "utf8");
|
|
29357
|
+
if (isGeneratedReference(content3)) {
|
|
29358
|
+
files.push(normalizeRelPath(relative7(root2, fullPath)));
|
|
29359
|
+
}
|
|
29360
|
+
}
|
|
29361
|
+
};
|
|
29362
|
+
walk(root2);
|
|
29363
|
+
return files;
|
|
28616
29364
|
}
|
|
28617
|
-
function
|
|
28618
|
-
|
|
28619
|
-
|
|
28620
|
-
|
|
28621
|
-
|
|
28622
|
-
const
|
|
28623
|
-
const
|
|
28624
|
-
for (const
|
|
28625
|
-
|
|
28626
|
-
|
|
28627
|
-
if (match?.[1] && match[2]) {
|
|
28628
|
-
refDefs.set(match[1].toLowerCase(), match[2]);
|
|
29365
|
+
function runGeneratedReferencesCheck(root2) {
|
|
29366
|
+
const issues = [];
|
|
29367
|
+
const canonicalDir = join10(root2, CANONICAL_REFS_DIR);
|
|
29368
|
+
if (!existsSync12(canonicalDir))
|
|
29369
|
+
return issues;
|
|
29370
|
+
const plans = discoverSkillReferencePlans(root2);
|
|
29371
|
+
const needed = new Set;
|
|
29372
|
+
for (const plan of plans) {
|
|
29373
|
+
for (const refPath of plan.refPaths) {
|
|
29374
|
+
needed.add(generatedRefPath(plan.skill, refPath));
|
|
28629
29375
|
}
|
|
28630
29376
|
}
|
|
28631
|
-
|
|
28632
|
-
|
|
28633
|
-
|
|
28634
|
-
|
|
28635
|
-
|
|
28636
|
-
});
|
|
29377
|
+
for (const targetRel of needed) {
|
|
29378
|
+
const targetPath = join10(root2, targetRel);
|
|
29379
|
+
if (!existsSync12(targetPath)) {
|
|
29380
|
+
issues.push(issue("generated-references", targetRel, "missing generated copy — run skeleton references sync"));
|
|
29381
|
+
continue;
|
|
28637
29382
|
}
|
|
28638
|
-
|
|
28639
|
-
|
|
28640
|
-
|
|
28641
|
-
|
|
28642
|
-
links.push({
|
|
28643
|
-
target: url.trim(),
|
|
28644
|
-
line: lineFromOffset(content3, node2.position?.start.offset)
|
|
28645
|
-
});
|
|
28646
|
-
}
|
|
29383
|
+
const generated = readFileSync9(targetPath, "utf8");
|
|
29384
|
+
if (!isGeneratedReference(generated)) {
|
|
29385
|
+
issues.push(issue("generated-references", targetRel, "expected generated-reference provenance header"));
|
|
29386
|
+
continue;
|
|
28647
29387
|
}
|
|
28648
|
-
|
|
28649
|
-
|
|
28650
|
-
|
|
28651
|
-
|
|
28652
|
-
|
|
28653
|
-
|
|
28654
|
-
|
|
28655
|
-
|
|
28656
|
-
|
|
28657
|
-
|
|
28658
|
-
const target = match[2]?.trim();
|
|
28659
|
-
if (target)
|
|
28660
|
-
links.push({ target, line: i + 1 });
|
|
29388
|
+
const body = stripGeneratedHeader(generated);
|
|
29389
|
+
const sourceRel = normalizeRelPath(generated.match(/source: ([^\n]+)/)?.[1] ?? join10(CANONICAL_REFS_DIR, targetRel.split("/references/")[1] ?? ""));
|
|
29390
|
+
const canonicalPath = join10(root2, sourceRel);
|
|
29391
|
+
if (!existsSync12(canonicalPath)) {
|
|
29392
|
+
issues.push(issue("generated-references", targetRel, `canonical source missing: ${sourceRel}`));
|
|
29393
|
+
continue;
|
|
29394
|
+
}
|
|
29395
|
+
const canonical = readFileSync9(canonicalPath, "utf8");
|
|
29396
|
+
if (body !== canonical) {
|
|
29397
|
+
issues.push(issue("generated-references", targetRel, "stale generated copy — run skeleton references sync"));
|
|
28661
29398
|
}
|
|
28662
29399
|
}
|
|
28663
|
-
|
|
28664
|
-
|
|
28665
|
-
|
|
28666
|
-
if (filePath.endsWith(".mdc")) {
|
|
28667
|
-
return extractHeadingSlugsLineBased(content3);
|
|
28668
|
-
}
|
|
28669
|
-
const slugger = new BananaSlug;
|
|
28670
|
-
const slugs = new Set;
|
|
28671
|
-
const tree = processor.parse(content3);
|
|
28672
|
-
visit2(tree, (node2) => {
|
|
28673
|
-
if (node2.type === "heading" && "children" in node2) {
|
|
28674
|
-
const text5 = node2.children.filter((c) => c.type === "text" || c.type === "inlineCode").map((c) => ("value" in c) ? String(c.value) : "").join("");
|
|
28675
|
-
if (text5)
|
|
28676
|
-
slugs.add(slugger.slug(text5));
|
|
29400
|
+
for (const generatedRel of listAllGeneratedFiles(root2)) {
|
|
29401
|
+
if (!needed.has(generatedRel)) {
|
|
29402
|
+
issues.push(issue("generated-references", generatedRel, "orphaned generated copy — run skeleton references sync"));
|
|
28677
29403
|
}
|
|
28678
|
-
}
|
|
28679
|
-
|
|
29404
|
+
}
|
|
29405
|
+
for (const plan of plans) {
|
|
29406
|
+
const skillDir = join10(root2, plan.skill);
|
|
29407
|
+
if (!existsSync12(skillDir))
|
|
29408
|
+
continue;
|
|
29409
|
+
const walk = (dir) => {
|
|
29410
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
29411
|
+
if (entry.name.startsWith("."))
|
|
29412
|
+
continue;
|
|
29413
|
+
const fullPath = join10(dir, entry.name);
|
|
29414
|
+
if (entry.isDirectory()) {
|
|
29415
|
+
walk(fullPath);
|
|
29416
|
+
continue;
|
|
29417
|
+
}
|
|
29418
|
+
if (!entry.name.endsWith(".md"))
|
|
29419
|
+
continue;
|
|
29420
|
+
const relFile = normalizeRelPath(relative7(root2, fullPath));
|
|
29421
|
+
const content3 = readFileSync9(fullPath, "utf8");
|
|
29422
|
+
if (content3.match(SHARED_REF_LINK_RE)) {
|
|
29423
|
+
issues.push(issue("generated-references", relFile, "still links to shared root references/ — run skeleton references sync"));
|
|
29424
|
+
}
|
|
29425
|
+
}
|
|
29426
|
+
};
|
|
29427
|
+
walk(skillDir);
|
|
29428
|
+
}
|
|
29429
|
+
return issues;
|
|
28680
29430
|
}
|
|
28681
|
-
function
|
|
28682
|
-
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
|
|
29431
|
+
function runGeneratedReferencesRule(ctx) {
|
|
29432
|
+
return runGeneratedReferencesCheck(ctx.root);
|
|
29433
|
+
}
|
|
29434
|
+
var generatedReferencesRule = {
|
|
29435
|
+
id: "generated-references",
|
|
29436
|
+
global: true,
|
|
29437
|
+
run: runGeneratedReferencesRule
|
|
29438
|
+
};
|
|
29439
|
+
|
|
29440
|
+
// src/audit/rules/banned.ts
|
|
29441
|
+
function runBannedRule(ctx) {
|
|
29442
|
+
const issues = [];
|
|
29443
|
+
for (const abs of collectBannedFiles(ctx.config, ctx.root)) {
|
|
29444
|
+
const rel = relPath(abs, ctx.root);
|
|
29445
|
+
issues.push(issue("banned", rel, "file matches scan.banned — must not exist in repo"));
|
|
28689
29446
|
}
|
|
28690
|
-
return
|
|
29447
|
+
return issues;
|
|
28691
29448
|
}
|
|
28692
|
-
|
|
28693
|
-
|
|
28694
|
-
|
|
29449
|
+
var bannedRule = { id: "banned", run: runBannedRule };
|
|
29450
|
+
|
|
29451
|
+
// src/audit/rules/doc-meta.ts
|
|
29452
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
|
|
29453
|
+
import { join as join11 } from "node:path";
|
|
29454
|
+
function runDocMetaRule(ctx) {
|
|
29455
|
+
const issues = [];
|
|
29456
|
+
const today = new Date;
|
|
29457
|
+
for (const relPath2 of ctx.docMetaPaths) {
|
|
29458
|
+
const abs = join11(ctx.root, relPath2);
|
|
29459
|
+
if (!existsSync13(abs))
|
|
29460
|
+
continue;
|
|
29461
|
+
const content3 = readFileSync10(abs, "utf8");
|
|
29462
|
+
if (!DOC_META_RE.test(content3)) {
|
|
29463
|
+
issues.push(issue("doc-meta", relPath2, "missing doc-meta comment (owner + last-reviewed)"));
|
|
29464
|
+
continue;
|
|
29465
|
+
}
|
|
29466
|
+
const reviewedStr = docMetaLastReviewed(content3);
|
|
29467
|
+
if (!reviewedStr)
|
|
29468
|
+
continue;
|
|
29469
|
+
const reviewed = new Date(`${reviewedStr}T00:00:00Z`);
|
|
29470
|
+
if (Number.isNaN(reviewed.getTime()))
|
|
29471
|
+
continue;
|
|
29472
|
+
const ageDays = (today.getTime() - reviewed.getTime()) / 86400000;
|
|
29473
|
+
if (ageDays > ctx.config.daysUntilStale) {
|
|
29474
|
+
issues.push(issue("doc-meta", relPath2, `doc-meta last-reviewed ${reviewedStr} is stale (>${ctx.config.daysUntilStale} days)`, { severity: "warning" }));
|
|
29475
|
+
}
|
|
29476
|
+
const gitDate = lastGitCommitDate(relPath2, ctx.root);
|
|
29477
|
+
if (!gitDate)
|
|
29478
|
+
continue;
|
|
29479
|
+
const committed = new Date(`${gitDate}T00:00:00Z`);
|
|
29480
|
+
if (Number.isNaN(committed.getTime()))
|
|
29481
|
+
continue;
|
|
29482
|
+
if (committed.getTime() > reviewed.getTime()) {
|
|
29483
|
+
issues.push(issue("doc-meta", relPath2, `content changed after last-reviewed ${reviewedStr} (git: ${gitDate}) — bump last-reviewed or confirm review`, { severity: "warning" }));
|
|
29484
|
+
}
|
|
29485
|
+
}
|
|
29486
|
+
return issues;
|
|
28695
29487
|
}
|
|
29488
|
+
var docMetaRule = { id: "doc-meta", run: runDocMetaRule };
|
|
28696
29489
|
|
|
28697
29490
|
// src/audit/rules/links.ts
|
|
28698
|
-
|
|
29491
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
|
|
29492
|
+
import { dirname as dirname7, resolve as resolve7 } from "node:path";
|
|
29493
|
+
function resolveLink2(sourceFile, target) {
|
|
28699
29494
|
const withoutAnchor = target.split("#")[0]?.split("?")[0] ?? "";
|
|
28700
29495
|
if (!withoutAnchor)
|
|
28701
29496
|
return sourceFile;
|
|
28702
|
-
return
|
|
29497
|
+
return resolve7(dirname7(sourceFile), withoutAnchor);
|
|
28703
29498
|
}
|
|
28704
29499
|
function validateTarget(ctx, sourceFile, target, linkLabel) {
|
|
28705
29500
|
const issues = [];
|
|
@@ -28710,7 +29505,7 @@ function validateTarget(ctx, sourceFile, target, linkLabel) {
|
|
|
28710
29505
|
const relSource = relPath(sourceFile, ctx.root);
|
|
28711
29506
|
const anchor = target.includes("#") ? target.split("#")[1]?.split("?")[0] ?? "" : "";
|
|
28712
29507
|
const pathPart = target.split("#")[0]?.split("?")[0] ?? "";
|
|
28713
|
-
const resolved =
|
|
29508
|
+
const resolved = resolveLink2(sourceFile, target);
|
|
28714
29509
|
const relTarget = relPath(resolved, ctx.root);
|
|
28715
29510
|
const skillMatch = SKILL_LINK_IN_TARGET_RE.exec(target);
|
|
28716
29511
|
if (skillMatch?.[1] && ctx.retiredSkills.has(skillMatch[1])) {
|
|
@@ -28730,19 +29525,19 @@ function validateTarget(ctx, sourceFile, target, linkLabel) {
|
|
|
28730
29525
|
}
|
|
28731
29526
|
if ((target.includes(".claude/agents/") || target.includes(".cursor/agents/")) && target.endsWith(".md")) {
|
|
28732
29527
|
const agentPath = resolved.endsWith(".md") ? resolved : `${resolved}.md`;
|
|
28733
|
-
if (!
|
|
29528
|
+
if (!existsSync14(agentPath)) {
|
|
28734
29529
|
issues.push(issue("links", relSource, "missing agent file", { link: linkLabel }));
|
|
28735
29530
|
}
|
|
28736
29531
|
return issues;
|
|
28737
29532
|
}
|
|
28738
|
-
if (pathPart && !
|
|
29533
|
+
if (pathPart && !existsSync14(resolved)) {
|
|
28739
29534
|
issues.push(issue("links", relSource, `broken link → ${relTarget}`, {
|
|
28740
29535
|
link: linkLabel
|
|
28741
29536
|
}));
|
|
28742
29537
|
return issues;
|
|
28743
29538
|
}
|
|
28744
|
-
if (anchor &&
|
|
28745
|
-
const targetContent =
|
|
29539
|
+
if (anchor && existsSync14(resolved)) {
|
|
29540
|
+
const targetContent = readFileSync11(resolved, "utf8");
|
|
28746
29541
|
const slugs = extractHeadingSlugs(targetContent, resolved);
|
|
28747
29542
|
const anchorSlug = slugifyAnchor(anchor);
|
|
28748
29543
|
if (!slugs.has(anchorSlug)) {
|
|
@@ -28767,9 +29562,58 @@ function runLinksRule(ctx) {
|
|
|
28767
29562
|
}
|
|
28768
29563
|
var linksRule = { id: "links", run: runLinksRule };
|
|
28769
29564
|
|
|
29565
|
+
// src/audit/rules/prose-policy.ts
|
|
29566
|
+
function runProsePolicyRule(ctx) {
|
|
29567
|
+
if (ctx.policies.length === 0)
|
|
29568
|
+
return [];
|
|
29569
|
+
const issues = [];
|
|
29570
|
+
const draftPrefixes = ctx.config.draftPathPrefixes ?? [];
|
|
29571
|
+
for (const filePath of ctx.files) {
|
|
29572
|
+
const rel = relPath(filePath, ctx.root);
|
|
29573
|
+
const content3 = readFileContent(filePath);
|
|
29574
|
+
const lines = content3.split(`
|
|
29575
|
+
`);
|
|
29576
|
+
const policies = policiesForFile(ctx.policies, rel);
|
|
29577
|
+
for (const entry of policies) {
|
|
29578
|
+
if (entry.mode === "fingerprint")
|
|
29579
|
+
continue;
|
|
29580
|
+
if (!entry.regex)
|
|
29581
|
+
continue;
|
|
29582
|
+
if (entry.id === "draft-marker") {
|
|
29583
|
+
for (let i = 0;i < lines.length; i++) {
|
|
29584
|
+
if (entry.regex.test(lines[i] ?? "") && !isDraftPlacementAllowed(rel, draftPrefixes)) {
|
|
29585
|
+
issues.push(issue("prose-policy", rel, entry.message, {
|
|
29586
|
+
link: `line ${i + 1}`,
|
|
29587
|
+
severity: entry.severity
|
|
29588
|
+
}));
|
|
29589
|
+
}
|
|
29590
|
+
}
|
|
29591
|
+
continue;
|
|
29592
|
+
}
|
|
29593
|
+
const isMultiline = entry.pattern?.includes("[\\s\\S]");
|
|
29594
|
+
if (isMultiline) {
|
|
29595
|
+
if (entry.regex.test(content3)) {
|
|
29596
|
+
issues.push(issue("prose-policy", rel, entry.message, { severity: entry.severity }));
|
|
29597
|
+
}
|
|
29598
|
+
continue;
|
|
29599
|
+
}
|
|
29600
|
+
for (let i = 0;i < lines.length; i++) {
|
|
29601
|
+
if (entry.regex.test(lines[i] ?? "")) {
|
|
29602
|
+
issues.push(issue("prose-policy", rel, entry.message, {
|
|
29603
|
+
link: `line ${i + 1}`,
|
|
29604
|
+
severity: entry.severity
|
|
29605
|
+
}));
|
|
29606
|
+
}
|
|
29607
|
+
}
|
|
29608
|
+
}
|
|
29609
|
+
}
|
|
29610
|
+
return issues;
|
|
29611
|
+
}
|
|
29612
|
+
var prosePolicyRule = { id: "prose-policy", run: runProsePolicyRule };
|
|
29613
|
+
|
|
28770
29614
|
// src/audit/rules/registry.ts
|
|
28771
|
-
import { existsSync as
|
|
28772
|
-
import { join as
|
|
29615
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
|
|
29616
|
+
import { join as join12 } from "node:path";
|
|
28773
29617
|
function runRegistryRule(ctx) {
|
|
28774
29618
|
const issues = [];
|
|
28775
29619
|
const registry = new Set(ctx.registryPaths);
|
|
@@ -28777,12 +29621,12 @@ function runRegistryRule(ctx) {
|
|
|
28777
29621
|
issues.push(issue("registry", REGISTRY_REL_PATH, "registry table header found but 0 rows parsed — check | Topic | Canonical file | format and link syntax"));
|
|
28778
29622
|
}
|
|
28779
29623
|
for (const rel of ctx.registryPaths) {
|
|
28780
|
-
const abs =
|
|
28781
|
-
if (!
|
|
29624
|
+
const abs = join12(ctx.root, rel);
|
|
29625
|
+
if (!existsSync15(abs)) {
|
|
28782
29626
|
issues.push(issue("registry", rel, "registry entry file missing"));
|
|
28783
29627
|
continue;
|
|
28784
29628
|
}
|
|
28785
|
-
const content3 =
|
|
29629
|
+
const content3 = readFileSync12(abs, "utf8");
|
|
28786
29630
|
if (!SOURCE_OF_TRUTH_BANNER_RE.test(content3)) {
|
|
28787
29631
|
issues.push(issue("registry", rel, "missing **Source of truth for** banner (required for registry entry)"));
|
|
28788
29632
|
}
|
|
@@ -28793,7 +29637,7 @@ function runRegistryRule(ctx) {
|
|
|
28793
29637
|
continue;
|
|
28794
29638
|
if (!rel.endsWith(".md") && !rel.endsWith(".mdc"))
|
|
28795
29639
|
continue;
|
|
28796
|
-
const content3 =
|
|
29640
|
+
const content3 = readFileSync12(filePath, "utf8");
|
|
28797
29641
|
if (!SOURCE_OF_TRUTH_BANNER_LINE_RE.test(content3))
|
|
28798
29642
|
continue;
|
|
28799
29643
|
if (!registry.has(rel)) {
|
|
@@ -28830,16 +29674,16 @@ function runScanRootsRule(ctx) {
|
|
|
28830
29674
|
var scanRootsRule = { id: "scan-roots", run: runScanRootsRule };
|
|
28831
29675
|
|
|
28832
29676
|
// src/audit/rules/skill-index.ts
|
|
28833
|
-
import { existsSync as
|
|
28834
|
-
import { join as
|
|
29677
|
+
import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
|
|
29678
|
+
import { join as join13, relative as relative8 } from "node:path";
|
|
28835
29679
|
function walkSkillMarkdown(dir) {
|
|
28836
29680
|
const files = [];
|
|
28837
|
-
if (!
|
|
29681
|
+
if (!existsSync16(dir))
|
|
28838
29682
|
return files;
|
|
28839
29683
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
28840
29684
|
if (entry.name.startsWith("."))
|
|
28841
29685
|
continue;
|
|
28842
|
-
const fullPath =
|
|
29686
|
+
const fullPath = join13(dir, entry.name);
|
|
28843
29687
|
if (entry.isDirectory()) {
|
|
28844
29688
|
files.push(...walkSkillMarkdown(fullPath));
|
|
28845
29689
|
continue;
|
|
@@ -28865,8 +29709,8 @@ function parseReadmeTaxonomySlugs(content3) {
|
|
|
28865
29709
|
}
|
|
28866
29710
|
function scanFileForSkillLinks(ctx, filePath, index2) {
|
|
28867
29711
|
const issues = [];
|
|
28868
|
-
const rel =
|
|
28869
|
-
const content3 =
|
|
29712
|
+
const rel = relative8(ctx.root, filePath).replace(/\\/g, "/");
|
|
29713
|
+
const content3 = readFileSync13(filePath, "utf8");
|
|
28870
29714
|
if (isGeneratedReference(content3))
|
|
28871
29715
|
return issues;
|
|
28872
29716
|
for (const match of content3.matchAll(SKILL_LINK_RE)) {
|
|
@@ -28889,14 +29733,14 @@ function validateReadmeTaxonomy(ctx, index2, diskSlugs) {
|
|
|
28889
29733
|
for (const skillRoot of index2.roots) {
|
|
28890
29734
|
if (skillRoot.kind !== "nested")
|
|
28891
29735
|
continue;
|
|
28892
|
-
const readmePath =
|
|
28893
|
-
if (!
|
|
29736
|
+
const readmePath = join13(ctx.root, skillRoot.relPath, "README.md");
|
|
29737
|
+
if (!existsSync16(readmePath))
|
|
28894
29738
|
continue;
|
|
28895
|
-
const readme =
|
|
29739
|
+
const readme = readFileSync13(readmePath, "utf8");
|
|
28896
29740
|
if (!readme.includes("## Taxonomy"))
|
|
28897
29741
|
continue;
|
|
28898
29742
|
const taxonomySlugs = parseReadmeTaxonomySlugs(readme);
|
|
28899
|
-
const nestedSlugs = diskSlugs.filter((slug2) =>
|
|
29743
|
+
const nestedSlugs = diskSlugs.filter((slug2) => existsSync16(join13(ctx.root, skillRoot.relPath, slug2, "SKILL.md")));
|
|
28900
29744
|
const publicSlugs = nestedSlugs.filter((slug2) => !nonPublic.has(slug2));
|
|
28901
29745
|
const relReadme = `${skillRoot.relPath}/README.md`;
|
|
28902
29746
|
for (const slug2 of publicSlugs) {
|
|
@@ -28918,16 +29762,16 @@ function runSkillIndexRule(ctx) {
|
|
|
28918
29762
|
const diskSlugs = listSkillSlugs(index2);
|
|
28919
29763
|
issues.push(...validateReadmeTaxonomy(ctx, index2, diskSlugs));
|
|
28920
29764
|
for (const skillRoot of index2.roots) {
|
|
28921
|
-
const scanRoot = skillRoot.kind === "nested" ?
|
|
28922
|
-
if (skillRoot.kind === "nested" &&
|
|
29765
|
+
const scanRoot = skillRoot.kind === "nested" ? join13(ctx.root, skillRoot.relPath) : join13(ctx.root);
|
|
29766
|
+
if (skillRoot.kind === "nested" && existsSync16(scanRoot)) {
|
|
28923
29767
|
for (const skillMd of walkSkillMarkdown(scanRoot)) {
|
|
28924
29768
|
issues.push(...scanFileForSkillLinks(ctx, skillMd, index2));
|
|
28925
29769
|
}
|
|
28926
29770
|
}
|
|
28927
29771
|
if (skillRoot.kind === "flat") {
|
|
28928
29772
|
for (const slug2 of index2.slugs) {
|
|
28929
|
-
const skillDir =
|
|
28930
|
-
if (
|
|
29773
|
+
const skillDir = join13(ctx.root, slug2);
|
|
29774
|
+
if (existsSync16(skillDir)) {
|
|
28931
29775
|
for (const skillMd of walkSkillMarkdown(skillDir)) {
|
|
28932
29776
|
issues.push(...scanFileForSkillLinks(ctx, skillMd, index2));
|
|
28933
29777
|
}
|
|
@@ -28949,21 +29793,54 @@ var docsRules = [
|
|
|
28949
29793
|
{ ...coverageGapsRule, global: true },
|
|
28950
29794
|
linksRule,
|
|
28951
29795
|
docMetaRule,
|
|
28952
|
-
{ ...bannedRule, global: true }
|
|
29796
|
+
{ ...bannedRule, global: true },
|
|
29797
|
+
prosePolicyRule
|
|
28953
29798
|
];
|
|
28954
29799
|
var skillsRules = [
|
|
28955
29800
|
{ ...skillIndexRule, global: true },
|
|
28956
|
-
{ ...generatedReferencesRule, global: true }
|
|
29801
|
+
{ ...generatedReferencesRule, global: true },
|
|
29802
|
+
prosePolicyRule
|
|
28957
29803
|
];
|
|
28958
29804
|
var allRules = [...docsRules, ...skillsRules];
|
|
28959
|
-
function
|
|
29805
|
+
function assembleRules(pluginRules = []) {
|
|
29806
|
+
const coreIds = new Set(allRules.map((rule) => rule.id));
|
|
29807
|
+
const seenPlugin = new Set;
|
|
29808
|
+
for (const rule of pluginRules) {
|
|
29809
|
+
if (seenPlugin.has(rule.id) || coreIds.has(rule.id)) {
|
|
29810
|
+
throw new Error(`Duplicate audit rule id: ${rule.id}`);
|
|
29811
|
+
}
|
|
29812
|
+
seenPlugin.add(rule.id);
|
|
29813
|
+
}
|
|
29814
|
+
const docs = [...docsRules];
|
|
29815
|
+
const skills = [...skillsRules];
|
|
29816
|
+
for (const rule of pluginRules) {
|
|
29817
|
+
const suites = rule.suites ?? ["docs"];
|
|
29818
|
+
const inDocs = suites.includes("docs");
|
|
29819
|
+
const inSkills = suites.includes("skills");
|
|
29820
|
+
if (!inDocs && !inSkills) {
|
|
29821
|
+
const listed = suites.length === 0 ? "(empty)" : suites.join(", ");
|
|
29822
|
+
throw new Error(`Plugin rule "${rule.id}" suites attach to no known suite (got ${listed}; allowed: docs, skills). ` + `"self" is the union of docs+skills — put the rule in docs and/or skills.`);
|
|
29823
|
+
}
|
|
29824
|
+
if (inDocs)
|
|
29825
|
+
docs.push(rule);
|
|
29826
|
+
if (inSkills)
|
|
29827
|
+
skills.push(rule);
|
|
29828
|
+
}
|
|
29829
|
+
const selfById = new Map;
|
|
29830
|
+
for (const rule of [...docs, ...skills]) {
|
|
29831
|
+
selfById.set(rule.id, rule);
|
|
29832
|
+
}
|
|
29833
|
+
return { docs, skills, self: [...selfById.values()] };
|
|
29834
|
+
}
|
|
29835
|
+
function rulesForSuite(suite, pluginRules = []) {
|
|
29836
|
+
const assembled = assembleRules(pluginRules);
|
|
28960
29837
|
switch (suite) {
|
|
28961
29838
|
case "docs":
|
|
28962
|
-
return
|
|
29839
|
+
return assembled.docs;
|
|
28963
29840
|
case "skills":
|
|
28964
|
-
return
|
|
29841
|
+
return assembled.skills;
|
|
28965
29842
|
case "self":
|
|
28966
|
-
return
|
|
29843
|
+
return assembled.self;
|
|
28967
29844
|
default:
|
|
28968
29845
|
throw new Error(`Unknown suite: ${suite}`);
|
|
28969
29846
|
}
|
|
@@ -28974,22 +29851,42 @@ function parseAuditArgs(argv) {
|
|
|
28974
29851
|
let suite = "docs";
|
|
28975
29852
|
let strict = false;
|
|
28976
29853
|
let json = false;
|
|
29854
|
+
let dryRun = false;
|
|
28977
29855
|
let paths = [];
|
|
28978
29856
|
let only = null;
|
|
28979
|
-
|
|
29857
|
+
let fix = null;
|
|
29858
|
+
for (let i = 0;i < argv.length; i++) {
|
|
29859
|
+
const arg = argv[i] ?? "";
|
|
28980
29860
|
if (arg.startsWith("--suite=")) {
|
|
28981
29861
|
suite = arg.slice("--suite=".length);
|
|
28982
29862
|
} else if (arg === "--strict") {
|
|
28983
29863
|
strict = true;
|
|
28984
29864
|
} else if (arg === "--json") {
|
|
28985
29865
|
json = true;
|
|
29866
|
+
} else if (arg === "--dry-run") {
|
|
29867
|
+
dryRun = true;
|
|
29868
|
+
} else if (arg.startsWith("--dry-run=")) {
|
|
29869
|
+
throw new Error("audit: use --dry-run (boolean flag), not --dry-run=<value>");
|
|
29870
|
+
} else if (arg === "--fix") {
|
|
29871
|
+
const next = argv[i + 1];
|
|
29872
|
+
if (next && !next.startsWith("-")) {
|
|
29873
|
+
if (next !== "doc-meta" && next !== "anchors") {
|
|
29874
|
+
throw new Error(`Unknown --fix kind: ${next}. Use --fix, --fix=doc-meta, or --fix=anchors.`);
|
|
29875
|
+
}
|
|
29876
|
+
fix = next;
|
|
29877
|
+
i++;
|
|
29878
|
+
} else {
|
|
29879
|
+
fix = true;
|
|
29880
|
+
}
|
|
29881
|
+
} else if (arg.startsWith("--fix=")) {
|
|
29882
|
+
fix = arg.slice("--fix=".length);
|
|
28986
29883
|
} else if (arg.startsWith("--paths=")) {
|
|
28987
29884
|
paths = arg.slice("--paths=".length).split(",").map((path2) => path2.trim()).filter(Boolean);
|
|
28988
29885
|
} else if (arg.startsWith("--only=")) {
|
|
28989
29886
|
only = new Set(arg.slice("--only=".length).split(",").filter(Boolean));
|
|
28990
29887
|
}
|
|
28991
29888
|
}
|
|
28992
|
-
return { suite, strict, json, paths, only };
|
|
29889
|
+
return { suite, strict, json, paths, only, fix, dryRun };
|
|
28993
29890
|
}
|
|
28994
29891
|
function labelForSuite(suite) {
|
|
28995
29892
|
switch (suite) {
|
|
@@ -29012,16 +29909,40 @@ function shouldRunRule(rule, options, pathScoped) {
|
|
|
29012
29909
|
return false;
|
|
29013
29910
|
return true;
|
|
29014
29911
|
}
|
|
29015
|
-
function runAudit(options) {
|
|
29016
|
-
const
|
|
29912
|
+
async function runAudit(options) {
|
|
29913
|
+
const pathScoped = options.paths.length > 0;
|
|
29914
|
+
const base = createContext({
|
|
29017
29915
|
root: options.root,
|
|
29018
|
-
paths:
|
|
29916
|
+
paths: pathScoped ? options.paths : undefined,
|
|
29917
|
+
includeExcludedSkillTrees: options.suite === "skills" && !pathScoped
|
|
29019
29918
|
});
|
|
29020
|
-
const
|
|
29021
|
-
const
|
|
29919
|
+
const loaded = await loadPlugins(base.root, base.config);
|
|
29920
|
+
const ctx = { ...base, policies: loaded.policies };
|
|
29921
|
+
if (options.fix !== null && options.fix !== undefined) {
|
|
29922
|
+
if (options.suite !== "docs") {
|
|
29923
|
+
console.error("--fix is supported only for audit docs");
|
|
29924
|
+
return 1;
|
|
29925
|
+
}
|
|
29926
|
+
const kinds = fixKindsForOnly(parseFixKinds(options.fix), options.only);
|
|
29927
|
+
if (kinds.length === 0) {
|
|
29928
|
+
console.error("--fix has no overlapping rules with --only (doc-meta → doc-meta, anchors → links).");
|
|
29929
|
+
return 1;
|
|
29930
|
+
}
|
|
29931
|
+
applyFixes(ctx, { kinds, dryRun: options.dryRun });
|
|
29932
|
+
if (!options.dryRun) {
|
|
29933
|
+
const refreshed = createContext({
|
|
29934
|
+
root: options.root,
|
|
29935
|
+
paths: options.paths.length > 0 ? options.paths : undefined,
|
|
29936
|
+
policies: loaded.policies
|
|
29937
|
+
});
|
|
29938
|
+
Object.assign(ctx, refreshed);
|
|
29939
|
+
}
|
|
29940
|
+
}
|
|
29941
|
+
const rules = rulesForSuite(options.suite, loaded.rules).filter((r) => !options.only || options.only.has(r.id));
|
|
29942
|
+
const skipGlobalsForPaths = pathScoped && !options.globalOnly;
|
|
29022
29943
|
const issues = [];
|
|
29023
29944
|
for (const rule of rules) {
|
|
29024
|
-
if (!shouldRunRule(rule, options,
|
|
29945
|
+
if (!shouldRunRule(rule, options, skipGlobalsForPaths))
|
|
29025
29946
|
continue;
|
|
29026
29947
|
issues.push(...rule.run(ctx));
|
|
29027
29948
|
}
|
|
@@ -29036,19 +29957,19 @@ function runAudit(options) {
|
|
|
29036
29957
|
}
|
|
29037
29958
|
|
|
29038
29959
|
// src/customize/resolve.ts
|
|
29039
|
-
import { existsSync as
|
|
29040
|
-
import { basename as
|
|
29960
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
|
|
29961
|
+
import { basename as basename3, join as join14, relative as relative9 } from "node:path";
|
|
29041
29962
|
var CUSTOMIZE_PREFIX = "Customize: ";
|
|
29042
29963
|
function customizeDir(root2) {
|
|
29043
|
-
return
|
|
29964
|
+
return join14(root2, REGISTRY_DIR_REL, "customize");
|
|
29044
29965
|
}
|
|
29045
29966
|
function customizePathForSlug(root2, slug2) {
|
|
29046
|
-
return
|
|
29967
|
+
return join14(customizeDir(root2), `${slug2}.md`);
|
|
29047
29968
|
}
|
|
29048
29969
|
function findCustomizeViaRegistry(root2, slug2) {
|
|
29049
29970
|
for (const rel of parseRegistryPaths(root2)) {
|
|
29050
29971
|
const expected = `${REGISTRY_DIR_REL}/customize/${slug2}.md`;
|
|
29051
|
-
if (normalizeRelPath(rel) === expected &&
|
|
29972
|
+
if (normalizeRelPath(rel) === expected && existsSync17(join14(root2, rel))) {
|
|
29052
29973
|
return rel;
|
|
29053
29974
|
}
|
|
29054
29975
|
}
|
|
@@ -29056,17 +29977,17 @@ function findCustomizeViaRegistry(root2, slug2) {
|
|
|
29056
29977
|
}
|
|
29057
29978
|
function resolveSlugFile(root2, slug2) {
|
|
29058
29979
|
const direct = customizePathForSlug(root2, slug2);
|
|
29059
|
-
if (
|
|
29980
|
+
if (existsSync17(direct)) {
|
|
29060
29981
|
return {
|
|
29061
|
-
content:
|
|
29062
|
-
path: normalizeRelPath(
|
|
29982
|
+
content: readFileSync14(direct, "utf8"),
|
|
29983
|
+
path: normalizeRelPath(relative9(root2, direct))
|
|
29063
29984
|
};
|
|
29064
29985
|
}
|
|
29065
29986
|
const registryPath = findCustomizeViaRegistry(root2, slug2);
|
|
29066
29987
|
if (registryPath) {
|
|
29067
|
-
const abs =
|
|
29988
|
+
const abs = join14(root2, registryPath);
|
|
29068
29989
|
return {
|
|
29069
|
-
content:
|
|
29990
|
+
content: readFileSync14(abs, "utf8"),
|
|
29070
29991
|
path: registryPath
|
|
29071
29992
|
};
|
|
29072
29993
|
}
|
|
@@ -29085,21 +30006,21 @@ function readAlwaysInclude(root2, basenames, skipBasename) {
|
|
|
29085
30006
|
const paths = [];
|
|
29086
30007
|
const dir = customizeDir(root2);
|
|
29087
30008
|
for (const name of basenames) {
|
|
29088
|
-
const file =
|
|
30009
|
+
const file = basename3(name);
|
|
29089
30010
|
if (skipBasename && file === skipBasename)
|
|
29090
30011
|
continue;
|
|
29091
|
-
const abs =
|
|
29092
|
-
if (!
|
|
30012
|
+
const abs = join14(dir, file);
|
|
30013
|
+
if (!existsSync17(abs))
|
|
29093
30014
|
continue;
|
|
29094
|
-
parts.push(
|
|
29095
|
-
paths.push(normalizeRelPath(
|
|
30015
|
+
parts.push(readFileSync14(abs, "utf8").trimEnd());
|
|
30016
|
+
paths.push(normalizeRelPath(relative9(root2, abs)));
|
|
29096
30017
|
}
|
|
29097
30018
|
return { parts, paths };
|
|
29098
30019
|
}
|
|
29099
30020
|
function resolveCustomize(root2, slug2) {
|
|
29100
30021
|
const slugFile = resolveSlugFile(root2, slug2);
|
|
29101
30022
|
const alwaysNames = alwaysIncludeBasenames(root2);
|
|
29102
|
-
const skip = slugFile.path != null ?
|
|
30023
|
+
const skip = slugFile.path != null ? basename3(slugFile.path) : null;
|
|
29103
30024
|
const always = readAlwaysInclude(root2, alwaysNames, skip);
|
|
29104
30025
|
const parts = [];
|
|
29105
30026
|
const included = [];
|
|
@@ -29132,49 +30053,49 @@ function resolveCustomizeFromRoot(slug2, startDir) {
|
|
|
29132
30053
|
|
|
29133
30054
|
// src/init/init.ts
|
|
29134
30055
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
29135
|
-
import { copyFileSync, existsSync as
|
|
29136
|
-
import { join as
|
|
30056
|
+
import { copyFileSync, existsSync as existsSync21, mkdirSync as mkdirSync2, readFileSync as readFileSync16 } from "node:fs";
|
|
30057
|
+
import { join as join18 } from "node:path";
|
|
29137
30058
|
|
|
29138
30059
|
// src/init/merge-hooks.ts
|
|
29139
|
-
import { existsSync as
|
|
29140
|
-
import { dirname as
|
|
30060
|
+
import { existsSync as existsSync20, mkdirSync, readFileSync as readFileSync15, writeFileSync as writeFileSync2 } from "node:fs";
|
|
30061
|
+
import { dirname as dirname10, join as join17 } from "node:path";
|
|
29141
30062
|
|
|
29142
30063
|
// src/init/package-paths.ts
|
|
29143
|
-
import { existsSync as
|
|
29144
|
-
import { dirname as
|
|
29145
|
-
import { fileURLToPath as
|
|
29146
|
-
var MODULE_DIR =
|
|
29147
|
-
var PACKAGE_ROOT_CANDIDATES = [
|
|
30064
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
30065
|
+
import { dirname as dirname8, join as join15 } from "node:path";
|
|
30066
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
30067
|
+
var MODULE_DIR = dirname8(fileURLToPath5(import.meta.url));
|
|
30068
|
+
var PACKAGE_ROOT_CANDIDATES = [join15(MODULE_DIR, "../.."), join15(MODULE_DIR, "..")];
|
|
29148
30069
|
function resolvePackageRoot() {
|
|
29149
30070
|
for (const candidate of PACKAGE_ROOT_CANDIDATES) {
|
|
29150
|
-
if (
|
|
30071
|
+
if (existsSync18(join15(candidate, "package.json")))
|
|
29151
30072
|
return candidate;
|
|
29152
30073
|
}
|
|
29153
30074
|
throw new Error("Could not resolve @csark0812/skeleton package root");
|
|
29154
30075
|
}
|
|
29155
30076
|
function resolveTemplatesDir() {
|
|
29156
|
-
const dir =
|
|
29157
|
-
if (!
|
|
30077
|
+
const dir = join15(resolvePackageRoot(), "templates/skeleton-init");
|
|
30078
|
+
if (!existsSync18(dir)) {
|
|
29158
30079
|
throw new Error("Missing templates/skeleton-init in package");
|
|
29159
30080
|
}
|
|
29160
30081
|
return dir;
|
|
29161
30082
|
}
|
|
29162
30083
|
|
|
29163
30084
|
// src/init/resolve-hook-command.ts
|
|
29164
|
-
import { existsSync as
|
|
30085
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
29165
30086
|
import { createRequire as createRequire3 } from "node:module";
|
|
29166
|
-
import { dirname as
|
|
30087
|
+
import { dirname as dirname9, join as join16, relative as relative10, resolve as resolve8 } from "node:path";
|
|
29167
30088
|
var PACKAGE_NAME = "@csark0812/skeleton";
|
|
29168
30089
|
var HOOK_DIST = "dist/hooks/customize-on-skill-read.js";
|
|
29169
30090
|
var HOOK_SRC = "src/hooks/customize-on-skill-read.ts";
|
|
29170
30091
|
var PACKAGE_ROOT = resolvePackageRoot();
|
|
29171
30092
|
function toRepoRelative(cwd, absPath) {
|
|
29172
|
-
const rel =
|
|
30093
|
+
const rel = relative10(cwd, absPath).replace(/\\/g, "/");
|
|
29173
30094
|
return rel.startsWith("..") ? absPath.replace(/\\/g, "/") : rel;
|
|
29174
30095
|
}
|
|
29175
30096
|
function tryResolvePublished(cwd) {
|
|
29176
30097
|
try {
|
|
29177
|
-
const req = createRequire3(
|
|
30098
|
+
const req = createRequire3(join16(cwd, "package.json"));
|
|
29178
30099
|
return req.resolve(`${PACKAGE_NAME}/${HOOK_DIST}`);
|
|
29179
30100
|
} catch {
|
|
29180
30101
|
return null;
|
|
@@ -29183,10 +30104,10 @@ function tryResolvePublished(cwd) {
|
|
|
29183
30104
|
function walkNodeModules(cwd) {
|
|
29184
30105
|
let dir = cwd;
|
|
29185
30106
|
while (true) {
|
|
29186
|
-
const candidate =
|
|
29187
|
-
if (
|
|
30107
|
+
const candidate = join16(dir, "node_modules", PACKAGE_NAME, HOOK_DIST);
|
|
30108
|
+
if (existsSync19(candidate))
|
|
29188
30109
|
return candidate;
|
|
29189
|
-
const parent =
|
|
30110
|
+
const parent = dirname9(dir);
|
|
29190
30111
|
if (parent === dir)
|
|
29191
30112
|
break;
|
|
29192
30113
|
dir = parent;
|
|
@@ -29194,7 +30115,7 @@ function walkNodeModules(cwd) {
|
|
|
29194
30115
|
return null;
|
|
29195
30116
|
}
|
|
29196
30117
|
function isInsidePackageRoot(cwd) {
|
|
29197
|
-
const rel =
|
|
30118
|
+
const rel = relative10(PACKAGE_ROOT, resolve8(cwd)).replace(/\\/g, "/");
|
|
29198
30119
|
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
29199
30120
|
}
|
|
29200
30121
|
function resolveHookCommand(cwd) {
|
|
@@ -29205,11 +30126,11 @@ function resolveHookCommand(cwd) {
|
|
|
29205
30126
|
if (hoisted)
|
|
29206
30127
|
return toRepoRelative(cwd, hoisted);
|
|
29207
30128
|
if (isInsidePackageRoot(cwd)) {
|
|
29208
|
-
const distHook =
|
|
29209
|
-
if (
|
|
30129
|
+
const distHook = join16(PACKAGE_ROOT, HOOK_DIST);
|
|
30130
|
+
if (existsSync19(distHook))
|
|
29210
30131
|
return toRepoRelative(cwd, distHook);
|
|
29211
|
-
const srcHook =
|
|
29212
|
-
if (
|
|
30132
|
+
const srcHook = join16(PACKAGE_ROOT, HOOK_SRC);
|
|
30133
|
+
if (existsSync19(srcHook)) {
|
|
29213
30134
|
const rel = toRepoRelative(cwd, srcHook);
|
|
29214
30135
|
return rel.includes("/") ? `bun ${rel}` : `bun ./${rel}`;
|
|
29215
30136
|
}
|
|
@@ -29228,21 +30149,21 @@ function identityKey(platform, event, matcher) {
|
|
|
29228
30149
|
return `skeleton:customize:${platform}:${event}:${matcher}`;
|
|
29229
30150
|
}
|
|
29230
30151
|
function loadFragment(name, hookCommand) {
|
|
29231
|
-
const raw =
|
|
30152
|
+
const raw = readFileSync15(join17(TEMPLATES_DIR, name), "utf8");
|
|
29232
30153
|
return JSON.parse(raw.replaceAll("{{HOOK_COMMAND}}", hookCommand));
|
|
29233
30154
|
}
|
|
29234
30155
|
function readJson(path2) {
|
|
29235
|
-
if (!
|
|
30156
|
+
if (!existsSync20(path2))
|
|
29236
30157
|
return null;
|
|
29237
30158
|
try {
|
|
29238
|
-
return JSON.parse(
|
|
30159
|
+
return JSON.parse(readFileSync15(path2, "utf8"));
|
|
29239
30160
|
} catch (error) {
|
|
29240
30161
|
throw new Error(`Invalid JSON in ${path2}: ${error}`);
|
|
29241
30162
|
}
|
|
29242
30163
|
}
|
|
29243
30164
|
function writeJson(path2, value) {
|
|
29244
|
-
mkdirSync(
|
|
29245
|
-
|
|
30165
|
+
mkdirSync(dirname10(path2), { recursive: true });
|
|
30166
|
+
writeFileSync2(path2, `${JSON.stringify(value, null, 2)}
|
|
29246
30167
|
`, "utf8");
|
|
29247
30168
|
}
|
|
29248
30169
|
function deepEqual(a, b) {
|
|
@@ -29341,14 +30262,14 @@ function mergeNestedHooks(platform, targetPath, fragment, eventName, opts) {
|
|
|
29341
30262
|
}
|
|
29342
30263
|
function mergeHookConfigs(opts) {
|
|
29343
30264
|
const results = [];
|
|
29344
|
-
const cursorPath =
|
|
30265
|
+
const cursorPath = join17(opts.cwd, ".cursor/hooks.json");
|
|
29345
30266
|
const cursorFragment = loadFragment("cursor-hooks.fragment.json", opts.hookCommand);
|
|
29346
30267
|
results.push(mergeCursorHooks(cursorPath, cursorFragment, opts));
|
|
29347
|
-
const claudePath =
|
|
30268
|
+
const claudePath = join17(opts.cwd, ".claude/settings.json");
|
|
29348
30269
|
const claudeFragment = loadFragment("claude-settings.fragment.json", opts.hookCommand);
|
|
29349
30270
|
results.push(mergeNestedHooks("claude", claudePath, claudeFragment, "PostToolUse", opts));
|
|
29350
|
-
const codexPath =
|
|
29351
|
-
if (
|
|
30271
|
+
const codexPath = join17(opts.cwd, ".codex/hooks.json");
|
|
30272
|
+
if (existsSync20(join17(opts.cwd, ".codex"))) {
|
|
29352
30273
|
const codexFragment = loadFragment("codex-hooks.fragment.json", opts.hookCommand);
|
|
29353
30274
|
results.push(mergeNestedHooks("codex", codexPath, codexFragment, "PostToolUse", opts));
|
|
29354
30275
|
} else {
|
|
@@ -29357,11 +30278,11 @@ function mergeHookConfigs(opts) {
|
|
|
29357
30278
|
return results;
|
|
29358
30279
|
}
|
|
29359
30280
|
function mergePackageJsonScripts(cwd) {
|
|
29360
|
-
const pkgPath =
|
|
29361
|
-
if (!
|
|
30281
|
+
const pkgPath = join17(cwd, "package.json");
|
|
30282
|
+
if (!existsSync20(pkgPath))
|
|
29362
30283
|
return "skipped";
|
|
29363
|
-
const fragment = JSON.parse(
|
|
29364
|
-
const pkg = JSON.parse(
|
|
30284
|
+
const fragment = JSON.parse(readFileSync15(join17(TEMPLATES_DIR, "package.json.scripts.fragment.json"), "utf8"));
|
|
30285
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf8"));
|
|
29365
30286
|
pkg.scripts ??= {};
|
|
29366
30287
|
let changed = false;
|
|
29367
30288
|
for (const [key, value] of Object.entries(fragment)) {
|
|
@@ -29372,7 +30293,7 @@ function mergePackageJsonScripts(cwd) {
|
|
|
29372
30293
|
}
|
|
29373
30294
|
if (!changed)
|
|
29374
30295
|
return "skipped";
|
|
29375
|
-
|
|
30296
|
+
writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
29376
30297
|
`, "utf8");
|
|
29377
30298
|
return "updated";
|
|
29378
30299
|
}
|
|
@@ -29417,27 +30338,27 @@ function skillsAddArgs(options = {}) {
|
|
|
29417
30338
|
// src/init/init.ts
|
|
29418
30339
|
var TEMPLATES_DIR2 = resolveTemplatesDir();
|
|
29419
30340
|
function writeScaffold(cwd) {
|
|
29420
|
-
const
|
|
29421
|
-
mkdirSync2(
|
|
30341
|
+
const skeletonDir2 = join18(cwd, ".skeleton");
|
|
30342
|
+
mkdirSync2(skeletonDir2, { recursive: true });
|
|
29422
30343
|
let created = false;
|
|
29423
|
-
const configPath =
|
|
29424
|
-
if (!
|
|
29425
|
-
copyFileSync(
|
|
30344
|
+
const configPath = join18(skeletonDir2, "config.yaml");
|
|
30345
|
+
if (!existsSync21(configPath)) {
|
|
30346
|
+
copyFileSync(join18(TEMPLATES_DIR2, "config.yaml"), configPath);
|
|
29426
30347
|
created = true;
|
|
29427
30348
|
}
|
|
29428
|
-
const registryPath =
|
|
29429
|
-
if (!
|
|
29430
|
-
copyFileSync(
|
|
30349
|
+
const registryPath = join18(skeletonDir2, "registry.md");
|
|
30350
|
+
if (!existsSync21(registryPath)) {
|
|
30351
|
+
copyFileSync(join18(TEMPLATES_DIR2, "registry.md"), registryPath);
|
|
29431
30352
|
created = true;
|
|
29432
30353
|
}
|
|
29433
|
-
mkdirSync2(
|
|
30354
|
+
mkdirSync2(join18(skeletonDir2, "customize"), { recursive: true });
|
|
29434
30355
|
return created ? "created" : "skipped";
|
|
29435
30356
|
}
|
|
29436
30357
|
function assertPackageResolvable(cwd) {
|
|
29437
|
-
const pkgPath =
|
|
29438
|
-
if (!
|
|
30358
|
+
const pkgPath = join18(cwd, "package.json");
|
|
30359
|
+
if (!existsSync21(pkgPath))
|
|
29439
30360
|
return;
|
|
29440
|
-
const pkg = JSON.parse(
|
|
30361
|
+
const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
|
|
29441
30362
|
const hasDep = pkg.devDependencies?.["@csark0812/skeleton"] || pkg.dependencies?.["@csark0812/skeleton"];
|
|
29442
30363
|
if (!hasDep) {
|
|
29443
30364
|
try {
|
|
@@ -29518,51 +30439,188 @@ function parseInitArgs(argv) {
|
|
|
29518
30439
|
return { forceHooks, skills, noSkills, skillsFlags };
|
|
29519
30440
|
}
|
|
29520
30441
|
|
|
30442
|
+
// src/plugins/build.ts
|
|
30443
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
30444
|
+
import { createHash } from "node:crypto";
|
|
30445
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17, writeFileSync as writeFileSync3 } from "node:fs";
|
|
30446
|
+
import { basename as basename4, dirname as dirname11, resolve as resolve9 } from "node:path";
|
|
30447
|
+
function parseBuildPluginArgs(argv) {
|
|
30448
|
+
let check = false;
|
|
30449
|
+
let entry;
|
|
30450
|
+
for (const arg of argv) {
|
|
30451
|
+
if (arg === "--check") {
|
|
30452
|
+
check = true;
|
|
30453
|
+
} else if (arg.startsWith("--check=")) {
|
|
30454
|
+
throw new Error("build-plugin: use --check (boolean flag), not --check=<value>");
|
|
30455
|
+
} else if (arg.startsWith("-")) {
|
|
30456
|
+
throw new Error(`build-plugin: unknown flag ${arg}`);
|
|
30457
|
+
} else if (!entry) {
|
|
30458
|
+
entry = arg;
|
|
30459
|
+
}
|
|
30460
|
+
}
|
|
30461
|
+
return { entry, check };
|
|
30462
|
+
}
|
|
30463
|
+
function collectPluginEntries(root2, config, entry) {
|
|
30464
|
+
if (entry) {
|
|
30465
|
+
const abs = entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry) ? resolveAbsolutePluginTsPath(root2, entry) : resolvePluginTsPath(root2, entry);
|
|
30466
|
+
return [abs];
|
|
30467
|
+
}
|
|
30468
|
+
return (config.plugins ?? []).map((e) => resolvePluginTsPath(root2, e));
|
|
30469
|
+
}
|
|
30470
|
+
function localImportPaths(tsAbs, content3) {
|
|
30471
|
+
const dir = dirname11(tsAbs);
|
|
30472
|
+
const deps = [];
|
|
30473
|
+
const re = /(?:from\s+|import\s*\(\s*|import\s+)["'](\.[^"']+)["']/g;
|
|
30474
|
+
for (const match of content3.matchAll(re)) {
|
|
30475
|
+
const spec = match[1];
|
|
30476
|
+
if (!spec)
|
|
30477
|
+
continue;
|
|
30478
|
+
const candidates = [];
|
|
30479
|
+
if (spec.endsWith(".js")) {
|
|
30480
|
+
const withoutJs = spec.slice(0, -".js".length);
|
|
30481
|
+
candidates.push(resolve9(dir, `${withoutJs}.ts`), resolve9(dir, spec), resolve9(dir, withoutJs, "index.ts"));
|
|
30482
|
+
} else {
|
|
30483
|
+
candidates.push(resolve9(dir, spec), resolve9(dir, `${spec}.ts`), resolve9(dir, `${spec}.js`), resolve9(dir, spec, "index.ts"));
|
|
30484
|
+
}
|
|
30485
|
+
for (const candidate of candidates) {
|
|
30486
|
+
if (existsSync22(candidate) && candidate.endsWith(".ts")) {
|
|
30487
|
+
deps.push(candidate);
|
|
30488
|
+
break;
|
|
30489
|
+
}
|
|
30490
|
+
}
|
|
30491
|
+
}
|
|
30492
|
+
return deps;
|
|
30493
|
+
}
|
|
30494
|
+
function stampPathForMjs(mjsAbs) {
|
|
30495
|
+
return `${mjsAbs}.stamp`;
|
|
30496
|
+
}
|
|
30497
|
+
function sourceFingerprint(tsAbs, seen = new Set) {
|
|
30498
|
+
const hash = createHash("sha256");
|
|
30499
|
+
function walk(abs) {
|
|
30500
|
+
if (seen.has(abs))
|
|
30501
|
+
return;
|
|
30502
|
+
seen.add(abs);
|
|
30503
|
+
const content3 = readFileSync17(abs, "utf8");
|
|
30504
|
+
hash.update(basename4(abs));
|
|
30505
|
+
hash.update("\x00");
|
|
30506
|
+
hash.update(content3);
|
|
30507
|
+
hash.update("\x00");
|
|
30508
|
+
for (const dep of localImportPaths(abs, content3).sort()) {
|
|
30509
|
+
walk(dep);
|
|
30510
|
+
}
|
|
30511
|
+
}
|
|
30512
|
+
walk(tsAbs);
|
|
30513
|
+
return hash.digest("hex");
|
|
30514
|
+
}
|
|
30515
|
+
function writeStamp(tsAbs, mjsAbs) {
|
|
30516
|
+
writeFileSync3(stampPathForMjs(mjsAbs), `${sourceFingerprint(tsAbs)}
|
|
30517
|
+
`, "utf8");
|
|
30518
|
+
}
|
|
30519
|
+
async function buildOne(tsAbs) {
|
|
30520
|
+
const mjsAbs = mjsPathForTs(tsAbs);
|
|
30521
|
+
if (!existsSync22(tsAbs)) {
|
|
30522
|
+
throw new Error(`Plugin source not found: ${tsAbs}`);
|
|
30523
|
+
}
|
|
30524
|
+
const proc = spawnSync3("bun", ["build", tsAbs, "--target=node", "--format=esm", `--outfile=${mjsAbs}`, "--packages=external"], { encoding: "utf8" });
|
|
30525
|
+
if (proc.error) {
|
|
30526
|
+
const code3 = proc.error.code;
|
|
30527
|
+
if (code3 === "ENOENT") {
|
|
30528
|
+
throw new Error("skeleton build-plugin requires the bun binary on PATH (bun build). Install Bun 1.2.x.");
|
|
30529
|
+
}
|
|
30530
|
+
throw new Error(`skeleton build-plugin failed to spawn bun: ${proc.error.message}`);
|
|
30531
|
+
}
|
|
30532
|
+
if (proc.status !== 0) {
|
|
30533
|
+
throw new Error(`skeleton build-plugin failed for ${tsAbs}:
|
|
30534
|
+
${proc.stderr || proc.stdout || `exit ${proc.status}`}`);
|
|
30535
|
+
}
|
|
30536
|
+
writeStamp(tsAbs, mjsAbs);
|
|
30537
|
+
return mjsAbs;
|
|
30538
|
+
}
|
|
30539
|
+
function checkOne(tsAbs) {
|
|
30540
|
+
const mjsAbs = mjsPathForTs(tsAbs);
|
|
30541
|
+
if (!existsSync22(mjsAbs)) {
|
|
30542
|
+
throw new Error(`Plugin not built: ${tsAbs} (missing ${mjsAbs}). Run: skeleton build-plugin`);
|
|
30543
|
+
}
|
|
30544
|
+
if (!existsSync22(tsAbs)) {
|
|
30545
|
+
throw new Error(`Plugin source not found: ${tsAbs}`);
|
|
30546
|
+
}
|
|
30547
|
+
const stampAbs = stampPathForMjs(mjsAbs);
|
|
30548
|
+
if (!existsSync22(stampAbs)) {
|
|
30549
|
+
throw new Error(`Plugin stale: ${mjsAbs} has no fingerprint stamp. Run: skeleton build-plugin`);
|
|
30550
|
+
}
|
|
30551
|
+
const expected = readFileSync17(stampAbs, "utf8").trim();
|
|
30552
|
+
const actual = sourceFingerprint(tsAbs);
|
|
30553
|
+
if (expected !== actual) {
|
|
30554
|
+
throw new Error(`Plugin stale: ${mjsAbs} does not match ${tsAbs} (or local imports). Run: skeleton build-plugin`);
|
|
30555
|
+
}
|
|
30556
|
+
}
|
|
30557
|
+
async function runBuildPlugin(options = {}) {
|
|
30558
|
+
const root2 = options.root ?? findRepoRoot();
|
|
30559
|
+
const config = loadConfig(root2);
|
|
30560
|
+
const entries = collectPluginEntries(root2, config, options.entry);
|
|
30561
|
+
if (entries.length === 0) {
|
|
30562
|
+
return { built: [], checked: [] };
|
|
30563
|
+
}
|
|
30564
|
+
const built = [];
|
|
30565
|
+
const checked = [];
|
|
30566
|
+
if (options.check) {
|
|
30567
|
+
for (const entry of entries) {
|
|
30568
|
+
checkOne(entry);
|
|
30569
|
+
checked.push(mjsPathForTs(entry));
|
|
30570
|
+
}
|
|
30571
|
+
return { built, checked };
|
|
30572
|
+
}
|
|
30573
|
+
for (const entry of entries) {
|
|
30574
|
+
built.push(await buildOne(entry));
|
|
30575
|
+
}
|
|
30576
|
+
return { built, checked };
|
|
30577
|
+
}
|
|
30578
|
+
|
|
29521
30579
|
// src/references/sync.ts
|
|
29522
30580
|
import {
|
|
29523
|
-
existsSync as
|
|
30581
|
+
existsSync as existsSync23,
|
|
29524
30582
|
mkdirSync as mkdirSync3,
|
|
29525
30583
|
readdirSync as readdirSync6,
|
|
29526
|
-
readFileSync as
|
|
30584
|
+
readFileSync as readFileSync18,
|
|
29527
30585
|
unlinkSync,
|
|
29528
|
-
writeFileSync as
|
|
30586
|
+
writeFileSync as writeFileSync4
|
|
29529
30587
|
} from "node:fs";
|
|
29530
|
-
import { dirname as
|
|
30588
|
+
import { dirname as dirname12, join as join19, relative as relative11 } from "node:path";
|
|
29531
30589
|
function walkMarkdownFiles2(dir, root2) {
|
|
29532
30590
|
const files = [];
|
|
29533
|
-
if (!
|
|
30591
|
+
if (!existsSync23(dir))
|
|
29534
30592
|
return files;
|
|
29535
30593
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
29536
30594
|
if (entry.name.startsWith("."))
|
|
29537
30595
|
continue;
|
|
29538
|
-
const fullPath =
|
|
30596
|
+
const fullPath = join19(dir, entry.name);
|
|
29539
30597
|
if (entry.isDirectory()) {
|
|
29540
30598
|
files.push(...walkMarkdownFiles2(fullPath, root2));
|
|
29541
30599
|
continue;
|
|
29542
30600
|
}
|
|
29543
30601
|
if (entry.name.endsWith(".md")) {
|
|
29544
|
-
files.push(normalizeRelPath(
|
|
30602
|
+
files.push(normalizeRelPath(relative11(root2, fullPath)));
|
|
29545
30603
|
}
|
|
29546
30604
|
}
|
|
29547
30605
|
return files;
|
|
29548
30606
|
}
|
|
29549
30607
|
function listGeneratedReferenceFiles(skillDir, skill) {
|
|
29550
|
-
const refsDir =
|
|
29551
|
-
if (!
|
|
30608
|
+
const refsDir = join19(skillDir, "references");
|
|
30609
|
+
if (!existsSync23(refsDir))
|
|
29552
30610
|
return [];
|
|
29553
30611
|
const files = [];
|
|
29554
30612
|
const walk = (dir) => {
|
|
29555
30613
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
29556
|
-
const fullPath =
|
|
30614
|
+
const fullPath = join19(dir, entry.name);
|
|
29557
30615
|
if (entry.isDirectory()) {
|
|
29558
30616
|
walk(fullPath);
|
|
29559
30617
|
continue;
|
|
29560
30618
|
}
|
|
29561
30619
|
if (!entry.name.endsWith(".md"))
|
|
29562
30620
|
continue;
|
|
29563
|
-
const content3 =
|
|
30621
|
+
const content3 = readFileSync18(fullPath, "utf8");
|
|
29564
30622
|
if (isGeneratedReference(content3)) {
|
|
29565
|
-
const refPath = normalizeRelPath(
|
|
30623
|
+
const refPath = normalizeRelPath(relative11(refsDir, fullPath));
|
|
29566
30624
|
files.push(generatedRefPath(skill, refPath));
|
|
29567
30625
|
}
|
|
29568
30626
|
}
|
|
@@ -29572,8 +30630,8 @@ function listGeneratedReferenceFiles(skillDir, skill) {
|
|
|
29572
30630
|
}
|
|
29573
30631
|
function syncReferences(options = {}) {
|
|
29574
30632
|
const root2 = options.root ?? process.cwd();
|
|
29575
|
-
const canonicalDir =
|
|
29576
|
-
if (!
|
|
30633
|
+
const canonicalDir = join19(root2, CANONICAL_REFS_DIR);
|
|
30634
|
+
if (!existsSync23(canonicalDir)) {
|
|
29577
30635
|
throw new Error(`canonical references dir not found: ${CANONICAL_REFS_DIR}`);
|
|
29578
30636
|
}
|
|
29579
30637
|
const result = {
|
|
@@ -29584,24 +30642,24 @@ function syncReferences(options = {}) {
|
|
|
29584
30642
|
};
|
|
29585
30643
|
const plans = discoverSkillReferencePlans(root2);
|
|
29586
30644
|
for (const plan of plans) {
|
|
29587
|
-
const skillDir =
|
|
30645
|
+
const skillDir = join19(root2, plan.skill);
|
|
29588
30646
|
for (const refPath of plan.refPaths) {
|
|
29589
|
-
const sourceRel = normalizeRelPath(
|
|
29590
|
-
const canonicalPath =
|
|
29591
|
-
if (!
|
|
30647
|
+
const sourceRel = normalizeRelPath(join19(CANONICAL_REFS_DIR, refPath));
|
|
30648
|
+
const canonicalPath = join19(root2, sourceRel);
|
|
30649
|
+
if (!existsSync23(canonicalPath)) {
|
|
29592
30650
|
throw new Error(`canonical reference missing: ${sourceRel}`);
|
|
29593
30651
|
}
|
|
29594
30652
|
const targetRel = generatedRefPath(plan.skill, refPath);
|
|
29595
|
-
const targetPath =
|
|
29596
|
-
const canonicalContent =
|
|
30653
|
+
const targetPath = join19(root2, targetRel);
|
|
30654
|
+
const canonicalContent = readFileSync18(canonicalPath, "utf8");
|
|
29597
30655
|
const nextContent = formatGeneratedHeader(sourceRel) + canonicalContent;
|
|
29598
30656
|
if (!options.dryRun) {
|
|
29599
|
-
mkdirSync3(
|
|
30657
|
+
mkdirSync3(dirname12(targetPath), { recursive: true });
|
|
29600
30658
|
}
|
|
29601
|
-
const existing =
|
|
30659
|
+
const existing = existsSync23(targetPath) ? readFileSync18(targetPath, "utf8") : null;
|
|
29602
30660
|
if (existing !== nextContent) {
|
|
29603
30661
|
if (!options.dryRun)
|
|
29604
|
-
|
|
30662
|
+
writeFileSync4(targetPath, nextContent, "utf8");
|
|
29605
30663
|
result.written.push(targetRel);
|
|
29606
30664
|
} else {
|
|
29607
30665
|
result.skipped.push(targetRel);
|
|
@@ -29609,12 +30667,12 @@ function syncReferences(options = {}) {
|
|
|
29609
30667
|
}
|
|
29610
30668
|
if (options.rewriteLinks !== false) {
|
|
29611
30669
|
for (const relFile of walkMarkdownFiles2(skillDir, root2)) {
|
|
29612
|
-
const filePath =
|
|
29613
|
-
const content3 =
|
|
30670
|
+
const filePath = join19(root2, relFile);
|
|
30671
|
+
const content3 = readFileSync18(filePath, "utf8");
|
|
29614
30672
|
const next = rewriteSharedRefLinks(content3, relFile, plan.skill);
|
|
29615
30673
|
if (next !== content3) {
|
|
29616
30674
|
if (!options.dryRun)
|
|
29617
|
-
|
|
30675
|
+
writeFileSync4(filePath, next, "utf8");
|
|
29618
30676
|
result.rewritten.push(relFile);
|
|
29619
30677
|
}
|
|
29620
30678
|
}
|
|
@@ -29622,7 +30680,7 @@ function syncReferences(options = {}) {
|
|
|
29622
30680
|
for (const generatedRel of listGeneratedReferenceFiles(skillDir, plan.skill)) {
|
|
29623
30681
|
const refPath = generatedRel.slice(`${plan.skill}/references/`.length);
|
|
29624
30682
|
if (!plan.refPaths.has(refPath)) {
|
|
29625
|
-
const fullPath =
|
|
30683
|
+
const fullPath = join19(root2, generatedRel);
|
|
29626
30684
|
if (!options.dryRun)
|
|
29627
30685
|
unlinkSync(fullPath);
|
|
29628
30686
|
result.removed.push(generatedRel);
|
|
@@ -29667,8 +30725,8 @@ function printSyncResult(result) {
|
|
|
29667
30725
|
}
|
|
29668
30726
|
|
|
29669
30727
|
// src/register.ts
|
|
29670
|
-
import { existsSync as
|
|
29671
|
-
import { dirname as
|
|
30728
|
+
import { existsSync as existsSync24, readFileSync as readFileSync19, writeFileSync as writeFileSync5 } from "node:fs";
|
|
30729
|
+
import { dirname as dirname13, join as join20, relative as relative12 } from "node:path";
|
|
29672
30730
|
var REGISTRY_TABLE_ROW_RE2 = /^\|\s*([^|]+)\|\s*\[[^\]]*\]\(([^)]+)\)\s*\|/;
|
|
29673
30731
|
var REGISTRY_TABLE_HEADER = "| Topic | Canonical file |";
|
|
29674
30732
|
function extractTopic(content3) {
|
|
@@ -29676,8 +30734,8 @@ function extractTopic(content3) {
|
|
|
29676
30734
|
return match?.[1]?.trim().replace(/\s+$/, "") ?? null;
|
|
29677
30735
|
}
|
|
29678
30736
|
function toRegistryLink(root2, absPath) {
|
|
29679
|
-
const fromRegistry =
|
|
29680
|
-
return normalizeRelPath(
|
|
30737
|
+
const fromRegistry = join20(root2, REGISTRY_DIR_REL);
|
|
30738
|
+
return normalizeRelPath(relative12(fromRegistry, absPath));
|
|
29681
30739
|
}
|
|
29682
30740
|
function inferSection(registryLink) {
|
|
29683
30741
|
return registryLink.startsWith("customize/") ? "Customizations" : "Documentation";
|
|
@@ -29701,12 +30759,12 @@ function parseRegistryRows(content3) {
|
|
|
29701
30759
|
return rows;
|
|
29702
30760
|
}
|
|
29703
30761
|
function pathFromRegistryLink(root2, link2) {
|
|
29704
|
-
return normalizeRelPath(
|
|
30762
|
+
return normalizeRelPath(relative12(root2, join20(root2, REGISTRY_DIR_REL, link2)));
|
|
29705
30763
|
}
|
|
29706
30764
|
function isOutsideScan(root2, relPath2) {
|
|
29707
30765
|
const config = loadConfig(root2);
|
|
29708
30766
|
const skillIndex = buildSkillIndex(root2);
|
|
29709
|
-
const scanned = collectScanFiles(config, root2, skillIndex).map((abs) => normalizeRelPath(
|
|
30767
|
+
const scanned = collectScanFiles(config, root2, skillIndex).map((abs) => normalizeRelPath(relative12(root2, abs)));
|
|
29710
30768
|
if (scanned.includes(relPath2))
|
|
29711
30769
|
return false;
|
|
29712
30770
|
return !config.scan.include.some((pattern) => matchesGlobScope(relPath2, pattern));
|
|
@@ -29768,11 +30826,11 @@ ${newLine}
|
|
|
29768
30826
|
function registerPath(options) {
|
|
29769
30827
|
const root2 = options.root ?? findRepoRoot();
|
|
29770
30828
|
const relPath2 = normalizeRelPath(options.path);
|
|
29771
|
-
const absPath =
|
|
29772
|
-
if (!
|
|
30829
|
+
const absPath = join20(root2, relPath2);
|
|
30830
|
+
if (!existsSync24(absPath)) {
|
|
29773
30831
|
throw new Error(`File not found: ${relPath2}`);
|
|
29774
30832
|
}
|
|
29775
|
-
const content3 =
|
|
30833
|
+
const content3 = readFileSync19(absPath, "utf8");
|
|
29776
30834
|
let topic = options.topic ?? extractTopic(content3);
|
|
29777
30835
|
if (!topic) {
|
|
29778
30836
|
throw new Error(`No **Source of truth for** banner in ${relPath2} — add banner or pass --topic`);
|
|
@@ -29780,9 +30838,9 @@ function registerPath(options) {
|
|
|
29780
30838
|
const registryLink = toRegistryLink(root2, absPath);
|
|
29781
30839
|
topic = ensureCustomizeTopic(topic, registryLink);
|
|
29782
30840
|
const section = inferSection(registryLink);
|
|
29783
|
-
const registryAbs =
|
|
29784
|
-
let registryContent =
|
|
29785
|
-
if (!
|
|
30841
|
+
const registryAbs = join20(root2, REGISTRY_REL_PATH);
|
|
30842
|
+
let registryContent = existsSync24(registryAbs) ? readFileSync19(registryAbs, "utf8") : defaultRegistryContent();
|
|
30843
|
+
if (!existsSync24(registryAbs) && !existsSync24(join20(root2, ".skeleton/config.yaml"))) {
|
|
29786
30844
|
throw new Error("Missing .skeleton/config.yaml — run skeleton init first");
|
|
29787
30845
|
}
|
|
29788
30846
|
const { content: updated, action } = upsertRow(registryContent, topic, registryLink, section, root2);
|
|
@@ -29795,11 +30853,11 @@ function registerPath(options) {
|
|
|
29795
30853
|
warnOutsideScan: isOutsideScan(root2, relPath2)
|
|
29796
30854
|
};
|
|
29797
30855
|
if (!options.dryRun && action !== "noop") {
|
|
29798
|
-
const dir =
|
|
29799
|
-
if (!
|
|
30856
|
+
const dir = dirname13(registryAbs);
|
|
30857
|
+
if (!existsSync24(dir)) {
|
|
29800
30858
|
throw new Error(`Missing ${REGISTRY_DIR_REL}/ directory`);
|
|
29801
30859
|
}
|
|
29802
|
-
|
|
30860
|
+
writeFileSync5(registryAbs, registryContent, "utf8");
|
|
29803
30861
|
}
|
|
29804
30862
|
if (result.warnOutsideScan) {
|
|
29805
30863
|
console.error(`warning: ${relPath2} is outside scan.include — register succeeded but audit will not scan it`);
|
|
@@ -29817,12 +30875,12 @@ function registerPath(options) {
|
|
|
29817
30875
|
}
|
|
29818
30876
|
|
|
29819
30877
|
// src/validate/changed.ts
|
|
29820
|
-
import { spawnSync as
|
|
29821
|
-
import { existsSync as
|
|
29822
|
-
import { basename as
|
|
30878
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
30879
|
+
import { existsSync as existsSync25, readFileSync as readFileSync20 } from "node:fs";
|
|
30880
|
+
import { basename as basename5, extname as extname2, join as join21 } from "node:path";
|
|
29823
30881
|
|
|
29824
30882
|
// src/validate/git-diff.ts
|
|
29825
|
-
import { spawnSync as
|
|
30883
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
29826
30884
|
function gitDiffChangedFiles(options = {}) {
|
|
29827
30885
|
const root2 = options.root ?? findRepoRoot();
|
|
29828
30886
|
let args;
|
|
@@ -29833,7 +30891,7 @@ function gitDiffChangedFiles(options = {}) {
|
|
|
29833
30891
|
} else {
|
|
29834
30892
|
args = ["diff", "HEAD", "--name-only", "--diff-filter=ACMR"];
|
|
29835
30893
|
}
|
|
29836
|
-
const proc =
|
|
30894
|
+
const proc = spawnSync4("git", args, { cwd: root2, encoding: "utf8" });
|
|
29837
30895
|
if (proc.status !== 0) {
|
|
29838
30896
|
throw new Error(proc.stderr?.trim() || "git diff failed");
|
|
29839
30897
|
}
|
|
@@ -29843,17 +30901,34 @@ function gitDiffChangedFiles(options = {}) {
|
|
|
29843
30901
|
|
|
29844
30902
|
// src/validate/changed.ts
|
|
29845
30903
|
var DOC_EXTENSIONS = new Set([".md", ".mdc", ".yaml", ".yml"]);
|
|
30904
|
+
var POLICY_EXTENSIONS = new Set([".yaml", ".yml"]);
|
|
29846
30905
|
var SHELL_EXTENSIONS = new Set([".sh", ".bash", ".zsh"]);
|
|
29847
30906
|
var SKIP_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
|
|
29848
30907
|
var COMMAND_CONFIG_NAMES = new Set(["package.json", "project.json"]);
|
|
29849
|
-
function
|
|
30908
|
+
function isSkeletonYamlCandidate(normalized, ext) {
|
|
30909
|
+
if (!POLICY_EXTENSIONS.has(ext))
|
|
30910
|
+
return false;
|
|
30911
|
+
if (!(normalized.startsWith(".skeleton/") || normalized.startsWith(".skeleton\\"))) {
|
|
30912
|
+
return false;
|
|
30913
|
+
}
|
|
30914
|
+
const name = basename5(normalized).toLowerCase();
|
|
30915
|
+
if (name === "config.yaml" || name === "config.yml")
|
|
30916
|
+
return false;
|
|
30917
|
+
return true;
|
|
30918
|
+
}
|
|
30919
|
+
function bucketFor(relPath2, root2, wiredPolicies) {
|
|
29850
30920
|
const normalized = normalizeRelPath(relPath2);
|
|
29851
|
-
const ext =
|
|
29852
|
-
const name =
|
|
30921
|
+
const ext = extname2(normalized).toLowerCase();
|
|
30922
|
+
const name = basename5(normalized);
|
|
29853
30923
|
if (SKIP_EXTENSIONS.has(ext))
|
|
29854
30924
|
return "skip";
|
|
29855
30925
|
if (COMMAND_CONFIG_NAMES.has(name))
|
|
29856
30926
|
return "skip";
|
|
30927
|
+
if (isSkeletonYamlCandidate(normalized, ext)) {
|
|
30928
|
+
if (wiredPolicies.has(normalized))
|
|
30929
|
+
return "policy";
|
|
30930
|
+
return "skip";
|
|
30931
|
+
}
|
|
29857
30932
|
const skillIndex = buildSkillIndex(root2);
|
|
29858
30933
|
if (isSkillPath(normalized, skillIndex))
|
|
29859
30934
|
return "skills";
|
|
@@ -29885,21 +30960,31 @@ function parseJsonContent(content3) {
|
|
|
29885
30960
|
}
|
|
29886
30961
|
}
|
|
29887
30962
|
function validateJson(relPath2, root2) {
|
|
29888
|
-
const abs =
|
|
30963
|
+
const abs = join21(root2, relPath2);
|
|
29889
30964
|
try {
|
|
29890
|
-
parseJsonContent(
|
|
30965
|
+
parseJsonContent(readFileSync20(abs, "utf8"));
|
|
29891
30966
|
return 0;
|
|
29892
30967
|
} catch (error) {
|
|
29893
30968
|
console.error(`validate changed: invalid JSON in ${relPath2}: ${error}`);
|
|
29894
30969
|
return 1;
|
|
29895
30970
|
}
|
|
29896
30971
|
}
|
|
30972
|
+
function validatePolicy(relPath2, root2) {
|
|
30973
|
+
const abs = join21(root2, relPath2);
|
|
30974
|
+
try {
|
|
30975
|
+
loadPolicyFile(abs, readFileSync20(abs, "utf8"));
|
|
30976
|
+
return 0;
|
|
30977
|
+
} catch (error) {
|
|
30978
|
+
console.error(`validate changed: invalid policy ${relPath2}: ${error}`);
|
|
30979
|
+
return 1;
|
|
30980
|
+
}
|
|
30981
|
+
}
|
|
29897
30982
|
function validateShell(relPath2, root2) {
|
|
29898
|
-
const abs =
|
|
29899
|
-
const shellcheck =
|
|
30983
|
+
const abs = join21(root2, relPath2);
|
|
30984
|
+
const shellcheck = spawnSync5("shellcheck", [abs], { encoding: "utf8" });
|
|
29900
30985
|
if (shellcheck.status === 0)
|
|
29901
30986
|
return 0;
|
|
29902
|
-
const bash =
|
|
30987
|
+
const bash = spawnSync5("bash", ["-n", abs], { encoding: "utf8" });
|
|
29903
30988
|
if (bash.status === 0)
|
|
29904
30989
|
return 0;
|
|
29905
30990
|
console.error(`validate changed: shell syntax check failed for ${relPath2}: ${bash.stderr || shellcheck.stderr}`);
|
|
@@ -29917,23 +31002,23 @@ function resolvePaths(options) {
|
|
|
29917
31002
|
}
|
|
29918
31003
|
function codeValidationHint(root2) {
|
|
29919
31004
|
let pm2 = null;
|
|
29920
|
-
const pkgPath =
|
|
29921
|
-
if (
|
|
31005
|
+
const pkgPath = join21(root2, "package.json");
|
|
31006
|
+
if (existsSync25(pkgPath)) {
|
|
29922
31007
|
try {
|
|
29923
|
-
const pkg = JSON.parse(
|
|
31008
|
+
const pkg = JSON.parse(readFileSync20(pkgPath, "utf8"));
|
|
29924
31009
|
const raw = pkg.packageManager?.split("@")[0];
|
|
29925
31010
|
if (raw === "bun" || raw === "npm" || raw === "pnpm" || raw === "yarn")
|
|
29926
31011
|
pm2 = raw;
|
|
29927
31012
|
} catch {}
|
|
29928
31013
|
}
|
|
29929
31014
|
if (!pm2) {
|
|
29930
|
-
if (
|
|
31015
|
+
if (existsSync25(join21(root2, "bun.lock")) || existsSync25(join21(root2, "bun.lockb")))
|
|
29931
31016
|
pm2 = "bun";
|
|
29932
|
-
else if (
|
|
31017
|
+
else if (existsSync25(join21(root2, "pnpm-lock.yaml")))
|
|
29933
31018
|
pm2 = "pnpm";
|
|
29934
|
-
else if (
|
|
31019
|
+
else if (existsSync25(join21(root2, "yarn.lock")))
|
|
29935
31020
|
pm2 = "yarn";
|
|
29936
|
-
else if (
|
|
31021
|
+
else if (existsSync25(join21(root2, "package-lock.json")))
|
|
29937
31022
|
pm2 = "npm";
|
|
29938
31023
|
}
|
|
29939
31024
|
switch (pm2) {
|
|
@@ -29949,43 +31034,66 @@ function codeValidationHint(root2) {
|
|
|
29949
31034
|
return " Run your local code validation gates (test + typecheck + build).";
|
|
29950
31035
|
}
|
|
29951
31036
|
}
|
|
29952
|
-
function runValidateChanged(options = {}) {
|
|
31037
|
+
async function runValidateChanged(options = {}) {
|
|
29953
31038
|
const root2 = options.root ?? findRepoRoot();
|
|
29954
31039
|
const relPaths = resolvePaths(options);
|
|
29955
31040
|
if (relPaths.length === 0) {
|
|
29956
31041
|
console.log("validate changed: no changed files.");
|
|
29957
31042
|
return 0;
|
|
29958
31043
|
}
|
|
31044
|
+
const config = loadConfig(root2);
|
|
31045
|
+
let wiredPolicies;
|
|
31046
|
+
try {
|
|
31047
|
+
wiredPolicies = await collectWiredPolicyRelPaths(root2, config);
|
|
31048
|
+
} catch (error) {
|
|
31049
|
+
console.error(`validate changed: ${error instanceof Error ? error.message : error}`);
|
|
31050
|
+
return 1;
|
|
31051
|
+
}
|
|
29959
31052
|
const buckets = {
|
|
29960
31053
|
docs: [],
|
|
29961
31054
|
skills: [],
|
|
29962
31055
|
shell: [],
|
|
29963
|
-
json: []
|
|
31056
|
+
json: [],
|
|
31057
|
+
policy: []
|
|
29964
31058
|
};
|
|
29965
31059
|
let missing = 0;
|
|
29966
31060
|
let skipped = 0;
|
|
31061
|
+
const orphans = [];
|
|
29967
31062
|
for (const relPath2 of relPaths) {
|
|
29968
|
-
const
|
|
29969
|
-
|
|
31063
|
+
const normalized = normalizeRelPath(relPath2);
|
|
31064
|
+
const abs = join21(root2, normalized);
|
|
31065
|
+
if (!existsSync25(abs)) {
|
|
29970
31066
|
missing++;
|
|
29971
31067
|
console.error(`validate changed: path not found: ${relPath2}`);
|
|
29972
31068
|
continue;
|
|
29973
31069
|
}
|
|
29974
|
-
const
|
|
31070
|
+
const ext = extname2(normalized).toLowerCase();
|
|
31071
|
+
if (isSkeletonYamlCandidate(normalized, ext) && !wiredPolicies.has(normalized)) {
|
|
31072
|
+
orphans.push(normalized);
|
|
31073
|
+
continue;
|
|
31074
|
+
}
|
|
31075
|
+
const bucket = bucketFor(normalized, root2, wiredPolicies);
|
|
29975
31076
|
if (bucket === "skip") {
|
|
29976
31077
|
skipped++;
|
|
29977
31078
|
continue;
|
|
29978
31079
|
}
|
|
29979
|
-
buckets[bucket].push(
|
|
31080
|
+
buckets[bucket].push(normalized);
|
|
29980
31081
|
}
|
|
29981
|
-
|
|
31082
|
+
if (orphans.length > 0) {
|
|
31083
|
+
for (const orphan of orphans) {
|
|
31084
|
+
console.error(`validate changed: ${orphan} is under .skeleton/ but not referenced by any plugin policies glob.
|
|
31085
|
+
` + " Export it from a plugin `policies` array (see docs/developer/plugins.md), or remove the file.");
|
|
31086
|
+
}
|
|
31087
|
+
return 1;
|
|
31088
|
+
}
|
|
31089
|
+
const audited = buckets.docs.length + buckets.skills.length + buckets.shell.length + buckets.json.length + buckets.policy.length;
|
|
29982
31090
|
let exitCode = 0;
|
|
29983
31091
|
if (missing > 0 && audited === 0 && skipped === 0) {
|
|
29984
31092
|
console.error("validate changed: no paths existed on disk. Pass real paths or use --staged / --base.");
|
|
29985
31093
|
return 1;
|
|
29986
31094
|
}
|
|
29987
31095
|
if (options.base) {
|
|
29988
|
-
const globalExit = runAudit({
|
|
31096
|
+
const globalExit = await runAudit({
|
|
29989
31097
|
suite: "self",
|
|
29990
31098
|
strict: false,
|
|
29991
31099
|
json: false,
|
|
@@ -30003,7 +31111,7 @@ function runValidateChanged(options = {}) {
|
|
|
30003
31111
|
return 1;
|
|
30004
31112
|
}
|
|
30005
31113
|
if (buckets.docs.length > 0) {
|
|
30006
|
-
const docExit = runAudit({
|
|
31114
|
+
const docExit = await runAudit({
|
|
30007
31115
|
suite: "docs",
|
|
30008
31116
|
strict: false,
|
|
30009
31117
|
json: false,
|
|
@@ -30016,14 +31124,14 @@ function runValidateChanged(options = {}) {
|
|
|
30016
31124
|
exitCode = 1;
|
|
30017
31125
|
}
|
|
30018
31126
|
if (buckets.skills.length > 0) {
|
|
30019
|
-
const skillsOnly = buckets.docs.length === 0 && buckets.shell.length === 0 && buckets.json.length === 0;
|
|
31127
|
+
const skillsOnly = buckets.docs.length === 0 && buckets.shell.length === 0 && buckets.json.length === 0 && buckets.policy.length === 0;
|
|
30020
31128
|
if (skillsOnly && !options.base) {
|
|
30021
31129
|
console.error(`validate changed: skill paths need the full skills suite (path-scoped skill rules are empty).
|
|
30022
31130
|
` + ` Run: skeleton audit skills
|
|
30023
|
-
` + "
|
|
31131
|
+
` + " (audit self covers docs + .skeleton; excluded skill trees still need audit skills)");
|
|
30024
31132
|
return 1;
|
|
30025
31133
|
}
|
|
30026
|
-
const skillExit = runAudit({
|
|
31134
|
+
const skillExit = await runAudit({
|
|
30027
31135
|
suite: "skills",
|
|
30028
31136
|
strict: false,
|
|
30029
31137
|
json: false,
|
|
@@ -30043,6 +31151,46 @@ function runValidateChanged(options = {}) {
|
|
|
30043
31151
|
if (validateJson(relPath2, root2) !== 0)
|
|
30044
31152
|
exitCode = 1;
|
|
30045
31153
|
}
|
|
31154
|
+
for (const relPath2 of buckets.policy) {
|
|
31155
|
+
if (validatePolicy(relPath2, root2) !== 0)
|
|
31156
|
+
exitCode = 1;
|
|
31157
|
+
}
|
|
31158
|
+
if (buckets.policy.length > 0) {
|
|
31159
|
+
if (options.base) {
|
|
31160
|
+
const proseExit = await runAudit({
|
|
31161
|
+
suite: "docs",
|
|
31162
|
+
strict: false,
|
|
31163
|
+
json: false,
|
|
31164
|
+
paths: [],
|
|
31165
|
+
only: null,
|
|
31166
|
+
root: root2
|
|
31167
|
+
});
|
|
31168
|
+
if (proseExit !== 0)
|
|
31169
|
+
exitCode = 1;
|
|
31170
|
+
const skillPaths = listSkillMarkdownPaths(root2, buildSkillIndex(root2));
|
|
31171
|
+
if (skillPaths.length > 0) {
|
|
31172
|
+
const skillProseExit = await runAudit({
|
|
31173
|
+
suite: "skills",
|
|
31174
|
+
strict: false,
|
|
31175
|
+
json: false,
|
|
31176
|
+
paths: skillPaths,
|
|
31177
|
+
only: null,
|
|
31178
|
+
root: root2,
|
|
31179
|
+
pathScopedOnly: true
|
|
31180
|
+
});
|
|
31181
|
+
if (skillProseExit !== 0)
|
|
31182
|
+
exitCode = 1;
|
|
31183
|
+
}
|
|
31184
|
+
} else {
|
|
31185
|
+
if (exitCode === 0) {
|
|
31186
|
+
console.error(`validate changed: policy YAML changes need a full prose-policy pass (path-scoped docs are not enough).
|
|
31187
|
+
` + ` Run: skeleton audit docs
|
|
31188
|
+
` + ` And: skeleton audit skills
|
|
31189
|
+
` + " (audit self covers docs + .skeleton; excluded skill trees still need audit skills)");
|
|
31190
|
+
}
|
|
31191
|
+
return 1;
|
|
31192
|
+
}
|
|
31193
|
+
}
|
|
30046
31194
|
if (exitCode === 0) {
|
|
30047
31195
|
const note = skipped > 0 ? ` (${skipped} path(s) skipped)` : "";
|
|
30048
31196
|
console.log(`validate changed passed${note}.`);
|
|
@@ -30057,6 +31205,8 @@ function usage() {
|
|
|
30057
31205
|
Commands:
|
|
30058
31206
|
init [--force-hooks] [--skills] [--no-skills] [skills add flags…]
|
|
30059
31207
|
audit docs|self|skills [--strict] [--json] [--paths=a,b] [--only=rule]
|
|
31208
|
+
[--fix[=doc-meta|anchors]] [--dry-run]
|
|
31209
|
+
build-plugin [path] [--check]
|
|
30060
31210
|
validate changed [paths…] [--staged] [--base <ref>]
|
|
30061
31211
|
register <path> [--topic=…] [--dry-run] [--json]
|
|
30062
31212
|
customize resolve <slug> [--json]
|
|
@@ -30080,7 +31230,7 @@ function parseRegisterArgs(argv) {
|
|
|
30080
31230
|
}
|
|
30081
31231
|
return { path: path2, topic, dryRun, json };
|
|
30082
31232
|
}
|
|
30083
|
-
function main() {
|
|
31233
|
+
async function main() {
|
|
30084
31234
|
const argv = process.argv.slice(2);
|
|
30085
31235
|
const command = argv[0];
|
|
30086
31236
|
if (!command || command === "--help" || command === "-h") {
|
|
@@ -30096,7 +31246,18 @@ function main() {
|
|
|
30096
31246
|
}
|
|
30097
31247
|
const options = parseAuditArgs(argv.slice(2));
|
|
30098
31248
|
options.suite = sub;
|
|
30099
|
-
process.exit(runAudit(options));
|
|
31249
|
+
process.exit(await runAudit(options));
|
|
31250
|
+
}
|
|
31251
|
+
if (command === "build-plugin") {
|
|
31252
|
+
const { entry, check } = parseBuildPluginArgs(argv.slice(1));
|
|
31253
|
+
const root2 = findRepoRoot();
|
|
31254
|
+
const result = await runBuildPlugin({ root: root2, entry, check });
|
|
31255
|
+
if (check) {
|
|
31256
|
+
console.log(result.checked.length === 0 ? "build-plugin --check: no plugins configured." : `build-plugin --check: ${result.checked.length} plugin(s) up to date.`);
|
|
31257
|
+
} else {
|
|
31258
|
+
console.log(result.built.length === 0 ? "build-plugin: no plugins configured." : `build-plugin: built ${result.built.length} plugin(s).`);
|
|
31259
|
+
}
|
|
31260
|
+
process.exit(0);
|
|
30100
31261
|
}
|
|
30101
31262
|
if (command === "validate" && argv[1] === "changed") {
|
|
30102
31263
|
const rest = argv.slice(2);
|
|
@@ -30114,7 +31275,7 @@ function main() {
|
|
|
30114
31275
|
else if (arg && !arg.startsWith("-"))
|
|
30115
31276
|
paths.push(arg);
|
|
30116
31277
|
}
|
|
30117
|
-
process.exit(runValidateChanged({ paths, staged, base }));
|
|
31278
|
+
process.exit(await runValidateChanged({ paths, staged, base }));
|
|
30118
31279
|
}
|
|
30119
31280
|
if (command === "register") {
|
|
30120
31281
|
const opts = parseRegisterArgs(argv.slice(1));
|