okf-kit 0.3.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 +124 -0
- package/dist/bundle.d.ts +2 -0
- package/dist/bundle.js +69 -0
- package/dist/bundle.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +142 -0
- package/dist/cli.js.map +1 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +4 -0
- package/dist/errors.js.map +1 -0
- package/dist/git.d.ts +15 -0
- package/dist/git.js +30 -0
- package/dist/git.js.map +1 -0
- package/dist/init.d.ts +11 -0
- package/dist/init.js +63 -0
- package/dist/init.js.map +1 -0
- package/dist/links.d.ts +15 -0
- package/dist/links.js +82 -0
- package/dist/links.js.map +1 -0
- package/dist/report.d.ts +9 -0
- package/dist/report.js +51 -0
- package/dist/report.js.map +1 -0
- package/dist/rules/frontmatter-required.d.ts +2 -0
- package/dist/rules/frontmatter-required.js +46 -0
- package/dist/rules/frontmatter-required.js.map +1 -0
- package/dist/rules/index.d.ts +9 -0
- package/dist/rules/index.js +16 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/links-resolve.d.ts +2 -0
- package/dist/rules/links-resolve.js +39 -0
- package/dist/rules/links-resolve.js.map +1 -0
- package/dist/rules/no-absolute-links.d.ts +2 -0
- package/dist/rules/no-absolute-links.js +25 -0
- package/dist/rules/no-absolute-links.js.map +1 -0
- package/dist/rules/reserved-files-bare.d.ts +2 -0
- package/dist/rules/reserved-files-bare.js +22 -0
- package/dist/rules/reserved-files-bare.js.map +1 -0
- package/dist/rules/sources-fresh.d.ts +2 -0
- package/dist/rules/sources-fresh.js +99 -0
- package/dist/rules/sources-fresh.js.map +1 -0
- package/dist/rules/sources-shape.d.ts +2 -0
- package/dist/rules/sources-shape.js +41 -0
- package/dist/rules/sources-shape.js.map +1 -0
- package/dist/templates.d.ts +14 -0
- package/dist/templates.js +293 -0
- package/dist/templates.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +23 -0
- package/dist/util.js +50 -0
- package/dist/util.js.map +1 -0
- package/package.json +51 -0
package/dist/report.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function summarize(findings) {
|
|
2
|
+
const summary = { errors: 0, warnings: 0, notices: 0 };
|
|
3
|
+
for (const finding of findings) {
|
|
4
|
+
if (finding.severity === "error")
|
|
5
|
+
summary.errors++;
|
|
6
|
+
else if (finding.severity === "warning")
|
|
7
|
+
summary.warnings++;
|
|
8
|
+
else
|
|
9
|
+
summary.notices++;
|
|
10
|
+
}
|
|
11
|
+
return summary;
|
|
12
|
+
}
|
|
13
|
+
export function renderText(bundleDir, findings) {
|
|
14
|
+
if (findings.length === 0) {
|
|
15
|
+
return `okf-kit: clean, no findings in ${bundleDir}\n`;
|
|
16
|
+
}
|
|
17
|
+
const byFile = new Map();
|
|
18
|
+
for (const finding of findings) {
|
|
19
|
+
const group = byFile.get(finding.file);
|
|
20
|
+
if (group)
|
|
21
|
+
group.push(finding);
|
|
22
|
+
else
|
|
23
|
+
byFile.set(finding.file, [finding]);
|
|
24
|
+
}
|
|
25
|
+
const lines = [];
|
|
26
|
+
for (const [file, group] of byFile) {
|
|
27
|
+
for (const finding of group) {
|
|
28
|
+
lines.push(`${severityLabel(finding.severity)} ${finding.ruleId} ${file}: ${finding.message}`);
|
|
29
|
+
if (finding.detail)
|
|
30
|
+
lines.push(` ${finding.detail}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const summary = summarize(findings);
|
|
34
|
+
lines.push(`${findings.length} findings (errors ${summary.errors}, warnings ${summary.warnings}, notices ${summary.notices})`);
|
|
35
|
+
return lines.join("\n") + "\n";
|
|
36
|
+
}
|
|
37
|
+
export function renderJson(bundleDir, findings) {
|
|
38
|
+
const summary = summarize(findings);
|
|
39
|
+
return JSON.stringify({ bundleDir, findings, summary }, null, 2) + "\n";
|
|
40
|
+
}
|
|
41
|
+
function severityLabel(s) {
|
|
42
|
+
switch (s) {
|
|
43
|
+
case "error":
|
|
44
|
+
return "ERROR ";
|
|
45
|
+
case "warning":
|
|
46
|
+
return "WARN ";
|
|
47
|
+
case "notice":
|
|
48
|
+
return "NOTICE";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,SAAS,CAAC,QAAmB;IAC3C,MAAM,OAAO,GAAY,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAChE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;YAAE,OAAO,CAAC,MAAM,EAAE,CAAC;aAC9C,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;;YACvD,OAAO,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB,EAAE,QAAmB;IAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,kCAAkC,SAAS,IAAI,CAAC;IACzD,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC5C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;YAC1B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACnC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CACR,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CACnF,CAAC;YACF,IAAI,OAAO,CAAC,MAAM;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpC,KAAK,CAAC,IAAI,CACR,GAAG,QAAQ,CAAC,MAAM,qBAAqB,OAAO,CAAC,MAAM,cAAc,OAAO,CAAC,QAAQ,aAAa,OAAO,CAAC,OAAO,GAAG,CACnH,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB,EAAE,QAAmB;IAC/D,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAC1E,CAAC;AAED,SAAS,aAAa,CAAC,CAAW;IAChC,QAAQ,CAAC,EAAE,CAAC;QACV,KAAK,OAAO;YACV,OAAO,QAAQ,CAAC;QAClB,KAAK,SAAS;YACZ,OAAO,QAAQ,CAAC;QAClB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC;IACpB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isRecord } from "../util.js";
|
|
2
|
+
const RULE_ID = "frontmatter-required";
|
|
3
|
+
export const frontmatterRequiredRule = {
|
|
4
|
+
id: RULE_ID,
|
|
5
|
+
description: "Every non-reserved markdown file must have a frontmatter block that parses and carries a non-empty string `type`.",
|
|
6
|
+
run(ctx) {
|
|
7
|
+
const findings = [];
|
|
8
|
+
for (const doc of ctx.docs) {
|
|
9
|
+
if (doc.isReserved)
|
|
10
|
+
continue;
|
|
11
|
+
const { frontmatter } = doc;
|
|
12
|
+
if (!frontmatter.present) {
|
|
13
|
+
findings.push({
|
|
14
|
+
ruleId: RULE_ID,
|
|
15
|
+
severity: "error",
|
|
16
|
+
file: doc.relPath,
|
|
17
|
+
message: "Missing frontmatter block: the file must open with a `---` line and close with a matching `---` line.",
|
|
18
|
+
});
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (frontmatter.parseError) {
|
|
22
|
+
findings.push({
|
|
23
|
+
ruleId: RULE_ID,
|
|
24
|
+
severity: "error",
|
|
25
|
+
file: doc.relPath,
|
|
26
|
+
message: "Frontmatter block is not parseable YAML.",
|
|
27
|
+
detail: frontmatter.parseError,
|
|
28
|
+
});
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const type = isRecord(frontmatter.parsed)
|
|
32
|
+
? frontmatter.parsed.type
|
|
33
|
+
: undefined;
|
|
34
|
+
if (typeof type !== "string" || type.trim() === "") {
|
|
35
|
+
findings.push({
|
|
36
|
+
ruleId: RULE_ID,
|
|
37
|
+
severity: "error",
|
|
38
|
+
file: doc.relPath,
|
|
39
|
+
message: "Frontmatter `type` must be present and a non-empty string.",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return findings;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=frontmatter-required.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-required.js","sourceRoot":"","sources":["../../src/rules/frontmatter-required.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,OAAO,GAAG,sBAAsB,CAAC;AAEvC,MAAM,CAAC,MAAM,uBAAuB,GAAS;IAC3C,EAAE,EAAE,OAAO;IACX,WAAW,EACT,mHAAmH;IACrH,GAAG,CAAC,GAAG;QACL,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC3B,IAAI,GAAG,CAAC,UAAU;gBAAE,SAAS;YAC7B,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC;YAE5B,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EACL,uGAAuG;iBAC1G,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,IAAI,WAAW,CAAC,UAAU,EAAE,CAAC;gBAC3B,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EAAE,0CAA0C;oBACnD,MAAM,EAAE,WAAW,CAAC,UAAU;iBAC/B,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC;gBACvC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI;gBACzB,CAAC,CAAC,SAAS,CAAC;YACd,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnD,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EAAE,4DAA4D;iBACtE,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Rule } from "../types.js";
|
|
2
|
+
import { frontmatterRequiredRule } from "./frontmatter-required.js";
|
|
3
|
+
import { reservedFilesBareRule } from "./reserved-files-bare.js";
|
|
4
|
+
import { linksResolveRule } from "./links-resolve.js";
|
|
5
|
+
import { noAbsoluteLinksRule } from "./no-absolute-links.js";
|
|
6
|
+
import { sourcesShapeRule } from "./sources-shape.js";
|
|
7
|
+
import { sourcesFreshRule } from "./sources-fresh.js";
|
|
8
|
+
export declare const allRules: Rule[];
|
|
9
|
+
export { frontmatterRequiredRule, reservedFilesBareRule, linksResolveRule, noAbsoluteLinksRule, sourcesShapeRule, sourcesFreshRule, };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { frontmatterRequiredRule } from "./frontmatter-required.js";
|
|
2
|
+
import { reservedFilesBareRule } from "./reserved-files-bare.js";
|
|
3
|
+
import { linksResolveRule } from "./links-resolve.js";
|
|
4
|
+
import { noAbsoluteLinksRule } from "./no-absolute-links.js";
|
|
5
|
+
import { sourcesShapeRule } from "./sources-shape.js";
|
|
6
|
+
import { sourcesFreshRule } from "./sources-fresh.js";
|
|
7
|
+
export const allRules = [
|
|
8
|
+
frontmatterRequiredRule,
|
|
9
|
+
reservedFilesBareRule,
|
|
10
|
+
linksResolveRule,
|
|
11
|
+
noAbsoluteLinksRule,
|
|
12
|
+
sourcesShapeRule,
|
|
13
|
+
sourcesFreshRule,
|
|
14
|
+
];
|
|
15
|
+
export { frontmatterRequiredRule, reservedFilesBareRule, linksResolveRule, noAbsoluteLinksRule, sourcesShapeRule, sourcesFreshRule, };
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,CAAC,MAAM,QAAQ,GAAW;IAC9B,uBAAuB;IACvB,qBAAqB;IACrB,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB;CACjB,CAAC;AAEF,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,GACjB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { extractMarkdownLinks } from "../links.js";
|
|
4
|
+
const RULE_ID = "links-resolve";
|
|
5
|
+
export const linksResolveRule = {
|
|
6
|
+
id: RULE_ID,
|
|
7
|
+
description: "Markdown links to `.md` files must resolve to a real file in the bundle.",
|
|
8
|
+
run(ctx) {
|
|
9
|
+
const findings = [];
|
|
10
|
+
for (const doc of ctx.docs) {
|
|
11
|
+
for (const link of extractMarkdownLinks(doc.body)) {
|
|
12
|
+
const resolved = resolveLinkPath(ctx.bundleDir, doc.relPath, link.pathPart);
|
|
13
|
+
if (!fs.existsSync(resolved)) {
|
|
14
|
+
findings.push({
|
|
15
|
+
ruleId: RULE_ID,
|
|
16
|
+
severity: "error",
|
|
17
|
+
file: doc.relPath,
|
|
18
|
+
message: `Link target does not resolve: \`${link.target}\``,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return findings;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Relative targets resolve against the containing file's directory. Targets
|
|
28
|
+
* starting with `/` resolve against the bundle root, not the repo root or
|
|
29
|
+
* filesystem root (see the `no-absolute-links` rule for why that leading
|
|
30
|
+
* slash is still worth flagging).
|
|
31
|
+
*/
|
|
32
|
+
function resolveLinkPath(bundleDir, docRelPath, pathPart) {
|
|
33
|
+
if (pathPart.startsWith("/")) {
|
|
34
|
+
return path.join(bundleDir, pathPart.slice(1));
|
|
35
|
+
}
|
|
36
|
+
const docDir = path.dirname(path.join(bundleDir, docRelPath));
|
|
37
|
+
return path.resolve(docDir, pathPart);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=links-resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links-resolve.js","sourceRoot":"","sources":["../../src/rules/links-resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD,MAAM,OAAO,GAAG,eAAe,CAAC;AAEhC,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,OAAO;IACX,WAAW,EACT,0EAA0E;IAC5E,GAAG,CAAC,GAAG;QACL,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC3B,KAAK,MAAM,IAAI,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClD,MAAM,QAAQ,GAAG,eAAe,CAC9B,GAAG,CAAC,SAAS,EACb,GAAG,CAAC,OAAO,EACX,IAAI,CAAC,QAAQ,CACd,CAAC;gBACF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC7B,QAAQ,CAAC,IAAI,CAAC;wBACZ,MAAM,EAAE,OAAO;wBACf,QAAQ,EAAE,OAAO;wBACjB,IAAI,EAAE,GAAG,CAAC,OAAO;wBACjB,OAAO,EAAE,mCAAmC,IAAI,CAAC,MAAM,IAAI;qBAC5D,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF;;;;;GAKG;AACH,SAAS,eAAe,CACtB,SAAiB,EACjB,UAAkB,EAClB,QAAgB;IAEhB,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { extractMarkdownLinks } from "../links.js";
|
|
2
|
+
const RULE_ID = "no-absolute-links";
|
|
3
|
+
export const noAbsoluteLinksRule = {
|
|
4
|
+
id: RULE_ID,
|
|
5
|
+
description: "Link targets should not start with `/`.",
|
|
6
|
+
run(ctx) {
|
|
7
|
+
const findings = [];
|
|
8
|
+
for (const doc of ctx.docs) {
|
|
9
|
+
for (const link of extractMarkdownLinks(doc.body)) {
|
|
10
|
+
if (!link.pathPart.startsWith("/"))
|
|
11
|
+
continue;
|
|
12
|
+
findings.push({
|
|
13
|
+
ruleId: RULE_ID,
|
|
14
|
+
severity: "warning",
|
|
15
|
+
file: doc.relPath,
|
|
16
|
+
message: `Link target \`${link.target}\` starts with \`/\`: GitHub resolves a leading slash against ` +
|
|
17
|
+
"the repository root, not the bundle root, so this link 404s once the bundle is viewed outside " +
|
|
18
|
+
"its own repository (agent-tasks#387). Use a same-directory relative link instead.",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return findings;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=no-absolute-links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-absolute-links.js","sourceRoot":"","sources":["../../src/rules/no-absolute-links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD,MAAM,OAAO,GAAG,mBAAmB,CAAC;AAEpC,MAAM,CAAC,MAAM,mBAAmB,GAAS;IACvC,EAAE,EAAE,OAAO;IACX,WAAW,EAAE,yCAAyC;IACtD,GAAG,CAAC,GAAG;QACL,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC3B,KAAK,MAAM,IAAI,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC7C,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,SAAS;oBACnB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EACL,iBAAiB,IAAI,CAAC,MAAM,gEAAgE;wBAC5F,gGAAgG;wBAChG,mFAAmF;iBACtF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const RULE_ID = "reserved-files-bare";
|
|
2
|
+
export const reservedFilesBareRule = {
|
|
3
|
+
id: RULE_ID,
|
|
4
|
+
description: "Reserved files (index.md, log.md, at any depth) must not carry a frontmatter block.",
|
|
5
|
+
run(ctx) {
|
|
6
|
+
const findings = [];
|
|
7
|
+
for (const doc of ctx.docs) {
|
|
8
|
+
if (!doc.isReserved)
|
|
9
|
+
continue;
|
|
10
|
+
if (doc.frontmatter.present) {
|
|
11
|
+
findings.push({
|
|
12
|
+
ruleId: RULE_ID,
|
|
13
|
+
severity: "error",
|
|
14
|
+
file: doc.relPath,
|
|
15
|
+
message: `Reserved file \`${doc.basename}\` must not carry a frontmatter block.`,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return findings;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=reserved-files-bare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reserved-files-bare.js","sourceRoot":"","sources":["../../src/rules/reserved-files-bare.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,GAAG,qBAAqB,CAAC;AAEtC,MAAM,CAAC,MAAM,qBAAqB,GAAS;IACzC,EAAE,EAAE,OAAO;IACX,WAAW,EACT,qFAAqF;IACvF,GAAG,CAAC,GAAG;QACL,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC3B,IAAI,CAAC,GAAG,CAAC,UAAU;gBAAE,SAAS;YAC9B,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;gBAC5B,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EAAE,mBAAmB,GAAG,CAAC,QAAQ,wCAAwC;iBACjF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { runGit as defaultRunGit } from "../git.js";
|
|
4
|
+
import { getTimestampEpoch, getValidSources } from "../util.js";
|
|
5
|
+
const RULE_ID = "sources-fresh";
|
|
6
|
+
export const sourcesFreshRule = {
|
|
7
|
+
id: RULE_ID,
|
|
8
|
+
description: "Frontmatter `sources` paths must not have a last-commit time newer than the doc's `timestamp`.",
|
|
9
|
+
run(ctx) {
|
|
10
|
+
const findings = [];
|
|
11
|
+
const docsWithSources = ctx.docs
|
|
12
|
+
.map((doc) => ({ doc, sources: getValidSources(doc.frontmatter.parsed) }))
|
|
13
|
+
.filter((entry) => entry.sources !== undefined);
|
|
14
|
+
if (docsWithSources.length === 0)
|
|
15
|
+
return findings;
|
|
16
|
+
if (!ctx.repoRoot) {
|
|
17
|
+
// Never silently skip: an unset repoRoot means no existence check ran
|
|
18
|
+
// either (sources-shape), so staleness truly was not assessed, not
|
|
19
|
+
// "everything looked fine". One notice for the whole bundle, not one
|
|
20
|
+
// per doc: this is a bundle-level condition, not a per-doc finding.
|
|
21
|
+
findings.push({
|
|
22
|
+
ruleId: RULE_ID,
|
|
23
|
+
severity: "notice",
|
|
24
|
+
file: "",
|
|
25
|
+
message: "staleness skipped: not inside a git work tree",
|
|
26
|
+
});
|
|
27
|
+
return findings;
|
|
28
|
+
}
|
|
29
|
+
const repoRoot = ctx.repoRoot;
|
|
30
|
+
const git = ctx.runGit ?? defaultRunGit;
|
|
31
|
+
// One git call per unique source path across all docs, even though a
|
|
32
|
+
// STALE/untracked finding is reported per (doc, path) below.
|
|
33
|
+
const commitEpochCache = new Map();
|
|
34
|
+
const commitEpochFor = (source) => {
|
|
35
|
+
const cached = commitEpochCache.get(source);
|
|
36
|
+
if (cached !== undefined)
|
|
37
|
+
return cached;
|
|
38
|
+
const epoch = getLastCommitEpoch(git, repoRoot, source);
|
|
39
|
+
commitEpochCache.set(source, epoch);
|
|
40
|
+
return epoch;
|
|
41
|
+
};
|
|
42
|
+
for (const { doc, sources } of docsWithSources) {
|
|
43
|
+
const timestampEpoch = getTimestampEpoch(doc.frontmatter.parsed);
|
|
44
|
+
if (timestampEpoch === undefined) {
|
|
45
|
+
findings.push({
|
|
46
|
+
ruleId: RULE_ID,
|
|
47
|
+
severity: "notice",
|
|
48
|
+
file: doc.relPath,
|
|
49
|
+
message: "staleness not assessable: no valid timestamp",
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
for (const source of sources) {
|
|
54
|
+
// A missing path on disk is sources-shape's job to report; avoid a
|
|
55
|
+
// duplicate/confusing finding here.
|
|
56
|
+
if (!fs.existsSync(path.join(repoRoot, source)))
|
|
57
|
+
continue;
|
|
58
|
+
const commitEpoch = commitEpochFor(source);
|
|
59
|
+
if (commitEpoch === null) {
|
|
60
|
+
findings.push({
|
|
61
|
+
ruleId: RULE_ID,
|
|
62
|
+
severity: "notice",
|
|
63
|
+
file: doc.relPath,
|
|
64
|
+
message: `untracked by git, staleness unknown: \`${source}\``,
|
|
65
|
+
});
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (commitEpoch > timestampEpoch) {
|
|
69
|
+
findings.push({
|
|
70
|
+
ruleId: RULE_ID,
|
|
71
|
+
severity: "warning",
|
|
72
|
+
file: doc.relPath,
|
|
73
|
+
message: `STALE: \`${source}\` changed ${epochToIso(commitEpoch)} after doc timestamp ${epochToIso(timestampEpoch)}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return findings;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Last-commit epoch (seconds) for `source` relative to `repoRoot`, or null
|
|
83
|
+
* when the path has no git history (untracked) or the git call itself
|
|
84
|
+
* failed. `git log` with a pathspec that matches no commits exits 0 with
|
|
85
|
+
* empty stdout, which is exactly the "untracked" case, distinct from a real
|
|
86
|
+
* git failure (which RunGit also reports as null): both collapse to null
|
|
87
|
+
* here because sources-fresh treats them the same way, "staleness unknown".
|
|
88
|
+
*/
|
|
89
|
+
function getLastCommitEpoch(git, repoRoot, source) {
|
|
90
|
+
const out = git(["log", "-1", "--format=%ct", "--", source], repoRoot);
|
|
91
|
+
if (!out)
|
|
92
|
+
return null;
|
|
93
|
+
const epoch = Number.parseInt(out, 10);
|
|
94
|
+
return Number.isNaN(epoch) ? null : epoch;
|
|
95
|
+
}
|
|
96
|
+
function epochToIso(epochSeconds) {
|
|
97
|
+
return new Date(epochSeconds * 1000).toISOString();
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=sources-fresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources-fresh.js","sourceRoot":"","sources":["../../src/rules/sources-fresh.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getValidSources, hasSourcesKey } from "../util.js";
|
|
4
|
+
const RULE_ID = "sources-shape";
|
|
5
|
+
export const sourcesShapeRule = {
|
|
6
|
+
id: RULE_ID,
|
|
7
|
+
description: "Frontmatter `sources`, when present, must be a non-empty array of non-empty strings; with a repo root (explicit --repo-root or auto-detected), each path must exist.",
|
|
8
|
+
run(ctx) {
|
|
9
|
+
const findings = [];
|
|
10
|
+
for (const doc of ctx.docs) {
|
|
11
|
+
const parsed = doc.frontmatter.parsed;
|
|
12
|
+
if (!hasSourcesKey(parsed))
|
|
13
|
+
continue;
|
|
14
|
+
const sources = getValidSources(parsed);
|
|
15
|
+
if (!sources) {
|
|
16
|
+
findings.push({
|
|
17
|
+
ruleId: RULE_ID,
|
|
18
|
+
severity: "error",
|
|
19
|
+
file: doc.relPath,
|
|
20
|
+
message: "Frontmatter `sources` must be a non-empty array of non-empty strings.",
|
|
21
|
+
});
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (!ctx.repoRoot)
|
|
25
|
+
continue;
|
|
26
|
+
for (const source of sources) {
|
|
27
|
+
const target = path.join(ctx.repoRoot, source);
|
|
28
|
+
if (!fs.existsSync(target)) {
|
|
29
|
+
findings.push({
|
|
30
|
+
ruleId: RULE_ID,
|
|
31
|
+
severity: "error",
|
|
32
|
+
file: doc.relPath,
|
|
33
|
+
message: `Source path does not exist under repo root: \`${source}\``,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return findings;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=sources-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources-shape.js","sourceRoot":"","sources":["../../src/rules/sources-shape.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG5D,MAAM,OAAO,GAAG,eAAe,CAAC;AAEhC,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,OAAO;IACX,WAAW,EACT,sKAAsK;IACxK,GAAG,CAAC,GAAG;QACL,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC;YACtC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;gBAAE,SAAS;YAErC,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EACL,uEAAuE;iBAC1E,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,QAAQ;gBAAE,SAAS;YAC5B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAC/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC3B,QAAQ,CAAC,IAAI,CAAC;wBACZ,MAAM,EAAE,OAAO;wBACf,QAAQ,EAAE,OAAO;wBACjB,IAAI,EAAE,GAAG,CAAC,OAAO;wBACjB,OAAO,EAAE,iDAAiD,MAAM,IAAI;qBACrE,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold content for `okf-kit init`. Everything here is static except the
|
|
3
|
+
* `timestamp` value, which the caller (src/init.ts) fills in with the real
|
|
4
|
+
* current instant at generation time: never hardcode an artificial
|
|
5
|
+
* midnight datetime here, that is exactly the lesson this kit is built to
|
|
6
|
+
* enforce (see FRONTMATTER_GUIDANCE below).
|
|
7
|
+
*/
|
|
8
|
+
export declare function indexTemplate(): string;
|
|
9
|
+
export declare function logTemplate(timestamp: string): string;
|
|
10
|
+
export declare function overviewTemplate(timestamp: string): string;
|
|
11
|
+
export declare function moduleTemplate(timestamp: string): string;
|
|
12
|
+
export declare function invariantTemplate(timestamp: string): string;
|
|
13
|
+
export declare function runbookTemplate(timestamp: string): string;
|
|
14
|
+
export declare function benchmarkTemplate(timestamp: string): string;
|