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.
Files changed (53) hide show
  1. package/README.md +124 -0
  2. package/dist/bundle.d.ts +2 -0
  3. package/dist/bundle.js +69 -0
  4. package/dist/bundle.js.map +1 -0
  5. package/dist/cli.d.ts +16 -0
  6. package/dist/cli.js +142 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/errors.d.ts +3 -0
  9. package/dist/errors.js +4 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/git.d.ts +15 -0
  12. package/dist/git.js +30 -0
  13. package/dist/git.js.map +1 -0
  14. package/dist/init.d.ts +11 -0
  15. package/dist/init.js +63 -0
  16. package/dist/init.js.map +1 -0
  17. package/dist/links.d.ts +15 -0
  18. package/dist/links.js +82 -0
  19. package/dist/links.js.map +1 -0
  20. package/dist/report.d.ts +9 -0
  21. package/dist/report.js +51 -0
  22. package/dist/report.js.map +1 -0
  23. package/dist/rules/frontmatter-required.d.ts +2 -0
  24. package/dist/rules/frontmatter-required.js +46 -0
  25. package/dist/rules/frontmatter-required.js.map +1 -0
  26. package/dist/rules/index.d.ts +9 -0
  27. package/dist/rules/index.js +16 -0
  28. package/dist/rules/index.js.map +1 -0
  29. package/dist/rules/links-resolve.d.ts +2 -0
  30. package/dist/rules/links-resolve.js +39 -0
  31. package/dist/rules/links-resolve.js.map +1 -0
  32. package/dist/rules/no-absolute-links.d.ts +2 -0
  33. package/dist/rules/no-absolute-links.js +25 -0
  34. package/dist/rules/no-absolute-links.js.map +1 -0
  35. package/dist/rules/reserved-files-bare.d.ts +2 -0
  36. package/dist/rules/reserved-files-bare.js +22 -0
  37. package/dist/rules/reserved-files-bare.js.map +1 -0
  38. package/dist/rules/sources-fresh.d.ts +2 -0
  39. package/dist/rules/sources-fresh.js +99 -0
  40. package/dist/rules/sources-fresh.js.map +1 -0
  41. package/dist/rules/sources-shape.d.ts +2 -0
  42. package/dist/rules/sources-shape.js +41 -0
  43. package/dist/rules/sources-shape.js.map +1 -0
  44. package/dist/templates.d.ts +14 -0
  45. package/dist/templates.js +293 -0
  46. package/dist/templates.js.map +1 -0
  47. package/dist/types.d.ts +43 -0
  48. package/dist/types.js +2 -0
  49. package/dist/types.js.map +1 -0
  50. package/dist/util.d.ts +23 -0
  51. package/dist/util.js +50 -0
  52. package/dist/util.js.map +1 -0
  53. 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,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const frontmatterRequiredRule: Rule;
@@ -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,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const linksResolveRule: Rule;
@@ -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,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const noAbsoluteLinksRule: Rule;
@@ -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,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const reservedFilesBareRule: Rule;
@@ -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,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const sourcesFreshRule: Rule;
@@ -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,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const sourcesShapeRule: Rule;
@@ -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;