daftari 1.12.6 → 1.13.1
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/CHANGELOG.md +20 -0
- package/README.md +166 -0
- package/dist/audit/checks/broken_refs.d.ts +3 -0
- package/dist/audit/checks/broken_refs.d.ts.map +1 -0
- package/dist/audit/checks/broken_refs.js +67 -0
- package/dist/audit/checks/broken_refs.js.map +1 -0
- package/dist/audit/checks/staleness.d.ts +3 -0
- package/dist/audit/checks/staleness.d.ts.map +1 -0
- package/dist/audit/checks/staleness.js +114 -0
- package/dist/audit/checks/staleness.js.map +1 -0
- package/dist/audit/collect.d.ts +4 -0
- package/dist/audit/collect.d.ts.map +1 -0
- package/dist/audit/collect.js +134 -0
- package/dist/audit/collect.js.map +1 -0
- package/dist/audit/config.d.ts +4 -0
- package/dist/audit/config.d.ts.map +1 -0
- package/dist/audit/config.js +174 -0
- package/dist/audit/config.js.map +1 -0
- package/dist/audit/exit.d.ts +6 -0
- package/dist/audit/exit.d.ts.map +1 -0
- package/dist/audit/exit.js +8 -0
- package/dist/audit/exit.js.map +1 -0
- package/dist/audit/index.d.ts +2 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +93 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/links.d.ts +4 -0
- package/dist/audit/links.d.ts.map +1 -0
- package/dist/audit/links.js +156 -0
- package/dist/audit/links.js.map +1 -0
- package/dist/audit/report.d.ts +4 -0
- package/dist/audit/report.d.ts.map +1 -0
- package/dist/audit/report.js +58 -0
- package/dist/audit/report.js.map +1 -0
- package/dist/audit/types.d.ts +94 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +11 -0
- package/dist/audit/types.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// src/audit/config.ts
|
|
2
|
+
// Parses CLI argv + an optional audit.yaml into an AuditConfig. CLI wins on
|
|
3
|
+
// overlap with YAML; for --output flags only, a stderr warning is emitted so
|
|
4
|
+
// operators see when the file they expected to be written has been displaced.
|
|
5
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import yaml from "js-yaml";
|
|
8
|
+
import { err, ok } from "../frontmatter/types.js";
|
|
9
|
+
import { configError } from "./types.js";
|
|
10
|
+
// Inner helpers throw tagged AuditError objects (not class instances). The
|
|
11
|
+
// top-level parseAuditConfig wraps in try/catch and converts to Result.
|
|
12
|
+
function isAuditError(e) {
|
|
13
|
+
return (typeof e === "object" &&
|
|
14
|
+
e !== null &&
|
|
15
|
+
(e.kind === "config" || e.kind === "runtime"));
|
|
16
|
+
}
|
|
17
|
+
const DEFAULTS = {
|
|
18
|
+
docsGlob: "**/*.md",
|
|
19
|
+
thresholdDays: 540,
|
|
20
|
+
failOn: { brokenRefs: 1, transitiveStaleness: 100 },
|
|
21
|
+
};
|
|
22
|
+
function readArg(argv, flag) {
|
|
23
|
+
for (let i = 0; i < argv.length; i++) {
|
|
24
|
+
if (argv[i] === flag)
|
|
25
|
+
return argv[i + 1];
|
|
26
|
+
const prefix = `${flag}=`;
|
|
27
|
+
if (argv[i]?.startsWith(prefix))
|
|
28
|
+
return argv[i]?.slice(prefix.length);
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
function readMulti(argv, flag) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (let i = 0; i < argv.length; i++) {
|
|
35
|
+
if (argv[i] === flag && argv[i + 1] !== undefined) {
|
|
36
|
+
out.push(argv[i + 1]);
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const prefix = `${flag}=`;
|
|
41
|
+
if (argv[i]?.startsWith(prefix))
|
|
42
|
+
out.push(argv[i].slice(prefix.length));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function validateRepoPath(p, label) {
|
|
48
|
+
if (!existsSync(p)) {
|
|
49
|
+
throw configError(`${label}: path does not exist: ${p}`);
|
|
50
|
+
}
|
|
51
|
+
if (!statSync(p).isDirectory()) {
|
|
52
|
+
throw configError(`${label}: not a directory: ${p}`);
|
|
53
|
+
}
|
|
54
|
+
return realpathSync(p);
|
|
55
|
+
}
|
|
56
|
+
function parseYamlRepos(raw) {
|
|
57
|
+
if (!raw)
|
|
58
|
+
return [];
|
|
59
|
+
return raw.map((r, i) => {
|
|
60
|
+
if (typeof r.name !== "string" || r.name.length === 0) {
|
|
61
|
+
throw configError(`repos[${i}]: missing name`);
|
|
62
|
+
}
|
|
63
|
+
if (typeof r.path !== "string" || r.path.length === 0) {
|
|
64
|
+
throw configError(`repos[${i}] (${r.name}): missing path`);
|
|
65
|
+
}
|
|
66
|
+
const path = validateRepoPath(resolve(r.path), `repos[${i}] (${r.name})`);
|
|
67
|
+
const docsGlob = typeof r.docs_glob === "string" ? r.docs_glob : DEFAULTS.docsGlob;
|
|
68
|
+
const urls = Array.isArray(r.urls)
|
|
69
|
+
? r.urls.filter((u) => typeof u === "string")
|
|
70
|
+
: [];
|
|
71
|
+
return { name: r.name, path, docsGlob, urls };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function ensureUnique(repos) {
|
|
75
|
+
const seenName = new Set();
|
|
76
|
+
const seenPath = new Set();
|
|
77
|
+
for (const r of repos) {
|
|
78
|
+
if (seenName.has(r.name)) {
|
|
79
|
+
throw configError(`duplicate repo name: ${r.name}`);
|
|
80
|
+
}
|
|
81
|
+
if (seenPath.has(r.path)) {
|
|
82
|
+
throw configError(`duplicate repo path: ${r.path}`);
|
|
83
|
+
}
|
|
84
|
+
seenName.add(r.name);
|
|
85
|
+
seenPath.add(r.path);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function warn(msg) {
|
|
89
|
+
process.stderr.write(`daftari audit: warning: ${msg}\n`);
|
|
90
|
+
}
|
|
91
|
+
export function parseAuditConfig(argv, yamlLoader) {
|
|
92
|
+
try {
|
|
93
|
+
const configPath = readArg(argv, "--config");
|
|
94
|
+
let yamlRaw = {};
|
|
95
|
+
if (configPath !== undefined) {
|
|
96
|
+
const load = yamlLoader ?? ((p) => readFileSync(p, "utf-8"));
|
|
97
|
+
let text;
|
|
98
|
+
try {
|
|
99
|
+
text = load(configPath);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
103
|
+
throw configError(`cannot read --config ${configPath}: ${reason}`);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const raw = yaml.load(text);
|
|
107
|
+
if (raw === null || raw === undefined) {
|
|
108
|
+
yamlRaw = {};
|
|
109
|
+
}
|
|
110
|
+
else if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
111
|
+
throw configError(`${configPath}: expected a YAML map at top level, got ${Array.isArray(raw) ? "array" : typeof raw}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
yamlRaw = raw;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
if (isAuditError(e))
|
|
119
|
+
throw e;
|
|
120
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
121
|
+
throw configError(`malformed YAML in ${configPath}: ${reason}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const yamlRepos = parseYamlRepos(yamlRaw.repos);
|
|
125
|
+
const cliRepoPaths = readMulti(argv, "--repo");
|
|
126
|
+
const cliRepos = cliRepoPaths.map((rawPath, i) => {
|
|
127
|
+
const path = validateRepoPath(resolve(rawPath), `--repo ${rawPath}`);
|
|
128
|
+
return {
|
|
129
|
+
name: `repo-${i}`,
|
|
130
|
+
path,
|
|
131
|
+
docsGlob: DEFAULTS.docsGlob,
|
|
132
|
+
urls: [],
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
const repos = [...yamlRepos, ...cliRepos];
|
|
136
|
+
if (repos.length === 0) {
|
|
137
|
+
throw configError("no repos configured: pass --repo or --config");
|
|
138
|
+
}
|
|
139
|
+
ensureUnique(repos);
|
|
140
|
+
// Output handling: CLI wins, warn if it displaces YAML.
|
|
141
|
+
const yamlMd = typeof yamlRaw.output?.markdown === "string" ? yamlRaw.output.markdown : undefined;
|
|
142
|
+
const yamlJson = typeof yamlRaw.output?.json === "string" ? yamlRaw.output.json : undefined;
|
|
143
|
+
const cliMd = readArg(argv, "--output");
|
|
144
|
+
const cliJson = readArg(argv, "--output-json");
|
|
145
|
+
if (cliMd && yamlMd && cliMd !== yamlMd) {
|
|
146
|
+
warn(`--output overrides output.markdown from config (${yamlMd} → ${cliMd})`);
|
|
147
|
+
}
|
|
148
|
+
if (cliJson && yamlJson && cliJson !== yamlJson) {
|
|
149
|
+
warn(`--output-json overrides output.json from config (${yamlJson} → ${cliJson})`);
|
|
150
|
+
}
|
|
151
|
+
const output = {
|
|
152
|
+
markdown: cliMd ?? yamlMd,
|
|
153
|
+
json: cliJson ?? yamlJson,
|
|
154
|
+
};
|
|
155
|
+
const thresholdDays = typeof yamlRaw.staleness?.threshold_days === "number"
|
|
156
|
+
? yamlRaw.staleness.threshold_days
|
|
157
|
+
: DEFAULTS.thresholdDays;
|
|
158
|
+
const failOn = {
|
|
159
|
+
brokenRefs: typeof yamlRaw.fail_on?.broken_refs === "number"
|
|
160
|
+
? yamlRaw.fail_on.broken_refs
|
|
161
|
+
: DEFAULTS.failOn.brokenRefs,
|
|
162
|
+
transitiveStaleness: typeof yamlRaw.fail_on?.transitive_staleness === "number"
|
|
163
|
+
? yamlRaw.fail_on.transitive_staleness
|
|
164
|
+
: DEFAULTS.failOn.transitiveStaleness,
|
|
165
|
+
};
|
|
166
|
+
return ok({ repos, output, staleness: { thresholdDays }, failOn });
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
if (isAuditError(e))
|
|
170
|
+
return err(e);
|
|
171
|
+
throw e; // not ours; let it propagate
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/audit/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit.d.ts","sourceRoot":"","sources":["../../src/audit/exit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,GAC1D,CAAC,GAAG,CAAC,CAIP"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit.js","sourceRoot":"","sources":["../../src/audit/exit.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAC7B,MAAmB,EACnB,MAA2D;IAE3D,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU;QAAE,OAAO,CAAC,CAAC;IAC5D,IAAI,MAAM,CAAC,MAAM,CAAC,iBAAiB,IAAI,MAAM,CAAC,mBAAmB;QAAE,OAAO,CAAC,CAAC;IAC5E,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AA2CA,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyD9D"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/audit/index.ts
|
|
2
|
+
// Top-level entry for `daftari audit`. Loads config, runs the pipeline,
|
|
3
|
+
// emits reports, and translates AuditError.kind to exit codes:
|
|
4
|
+
// 0 — clean run within thresholds
|
|
5
|
+
// 1 — clean run, thresholds exceeded
|
|
6
|
+
// 2 — config error
|
|
7
|
+
// 3 — runtime error
|
|
8
|
+
import { writeFile } from "node:fs/promises";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { checkBrokenRefs } from "./checks/broken_refs.js";
|
|
11
|
+
import { checkStaleness } from "./checks/staleness.js";
|
|
12
|
+
import { collectRepos } from "./collect.js";
|
|
13
|
+
import { parseAuditConfig } from "./config.js";
|
|
14
|
+
import { computeExitCode } from "./exit.js";
|
|
15
|
+
import { classifyEdges } from "./links.js";
|
|
16
|
+
import { renderJson, renderMarkdown } from "./report.js";
|
|
17
|
+
const HELP = `daftari audit — coherence checks across markdown repos.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
daftari audit --repo <path> [--repo <path> ...] [--output <md>]
|
|
21
|
+
daftari audit --config audit.yaml [--repo <path> ...]
|
|
22
|
+
daftari audit --help
|
|
23
|
+
|
|
24
|
+
Flags:
|
|
25
|
+
--repo <path> Add a repo to the audit. May be repeated. Anonymous
|
|
26
|
+
CLI repos get no URL patterns — URL-based cross-repo
|
|
27
|
+
references to them will not be detected. Use --config
|
|
28
|
+
to declare urls explicitly.
|
|
29
|
+
--config <path> Load an audit.yaml. CLI flags override its values for
|
|
30
|
+
output paths (a warning is printed to stderr).
|
|
31
|
+
--output <md> Markdown report destination (default: stdout).
|
|
32
|
+
--output-json <json> JSON report destination (default: not written).
|
|
33
|
+
|
|
34
|
+
Exit codes:
|
|
35
|
+
0 — run succeeded, all findings under configured thresholds
|
|
36
|
+
1 — run succeeded but a fail_on threshold was exceeded
|
|
37
|
+
2 — config error (missing fields, bad paths, malformed YAML)
|
|
38
|
+
3 — runtime error (IO failure during collection)
|
|
39
|
+
`;
|
|
40
|
+
export async function runAudit(argv) {
|
|
41
|
+
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
42
|
+
process.stdout.write(HELP);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
const parsed = parseAuditConfig(argv);
|
|
46
|
+
if (!parsed.ok) {
|
|
47
|
+
process.stderr.write(`daftari audit: ${parsed.error.message}\n`);
|
|
48
|
+
return parsed.error.kind === "config" ? 2 : 3;
|
|
49
|
+
}
|
|
50
|
+
const config = parsed.value;
|
|
51
|
+
const collected = await collectRepos(config);
|
|
52
|
+
if (!collected.ok) {
|
|
53
|
+
process.stderr.write(`daftari audit: ${collected.error.message}\n`);
|
|
54
|
+
return collected.error.kind === "config" ? 2 : 3;
|
|
55
|
+
}
|
|
56
|
+
const snapshots = collected.value;
|
|
57
|
+
const edges = classifyEdges(snapshots);
|
|
58
|
+
const brokenRefs = checkBrokenRefs(snapshots, edges);
|
|
59
|
+
const staleness = checkStaleness(snapshots, edges, config.staleness.thresholdDays, new Date());
|
|
60
|
+
const totals = {
|
|
61
|
+
reposScanned: snapshots.length,
|
|
62
|
+
docsScanned: snapshots.reduce((n, s) => n + s.docs.size, 0),
|
|
63
|
+
brokenRefs: brokenRefs.length,
|
|
64
|
+
directlyStale: staleness.filter((f) => f.kind === "direct").length,
|
|
65
|
+
transitivelyStale: staleness.filter((f) => f.kind === "transitive").length,
|
|
66
|
+
};
|
|
67
|
+
const report = {
|
|
68
|
+
generatedAt: new Date().toISOString(),
|
|
69
|
+
config,
|
|
70
|
+
totals,
|
|
71
|
+
brokenRefs,
|
|
72
|
+
staleness,
|
|
73
|
+
};
|
|
74
|
+
const md = renderMarkdown(report);
|
|
75
|
+
try {
|
|
76
|
+
if (config.output.markdown) {
|
|
77
|
+
await writeFile(resolve(config.output.markdown), md, "utf-8");
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
process.stdout.write(md);
|
|
81
|
+
}
|
|
82
|
+
if (config.output.json) {
|
|
83
|
+
await writeFile(resolve(config.output.json), renderJson(report), "utf-8");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
88
|
+
process.stderr.write(`daftari audit: write failed: ${reason}\n`);
|
|
89
|
+
return 3;
|
|
90
|
+
}
|
|
91
|
+
return computeExitCode(report, config.failOn);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/audit/links.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAalE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAe5D;AAmED,wBAAgB,aAAa,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,QAAQ,EAAE,CA4DnE"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// src/audit/links.ts
|
|
2
|
+
// Link extraction + classification. Regex-only (no markdown parser dep — v1
|
|
3
|
+
// scope per plan resolution #1). Edges are classified by URL pattern match
|
|
4
|
+
// (with prefix-boundary check, resolution #6) or relative-path escape into
|
|
5
|
+
// another configured repo.
|
|
6
|
+
import { relative as nodeRelative, resolve as nodeResolve, posix } from "node:path";
|
|
7
|
+
const MD_LINK_RE = /\[([^\]]*)\]\(([^)\s]+)\)/g;
|
|
8
|
+
function splitAnchor(rawHref) {
|
|
9
|
+
const hashIdx = rawHref.indexOf("#");
|
|
10
|
+
if (hashIdx === -1)
|
|
11
|
+
return { href: rawHref, anchor: null };
|
|
12
|
+
return {
|
|
13
|
+
href: rawHref.slice(0, hashIdx),
|
|
14
|
+
anchor: rawHref.slice(hashIdx + 1) || null,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function extractLinksFromBody(body) {
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const m of body.matchAll(MD_LINK_RE)) {
|
|
20
|
+
const rawHref = m[2].trim();
|
|
21
|
+
if (!rawHref)
|
|
22
|
+
continue;
|
|
23
|
+
const { href, anchor } = splitAnchor(rawHref);
|
|
24
|
+
const isUrl = /^https?:\/\//i.test(rawHref);
|
|
25
|
+
const isRelative = !isUrl &&
|
|
26
|
+
!rawHref.startsWith("#") &&
|
|
27
|
+
!rawHref.startsWith("/") &&
|
|
28
|
+
!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(rawHref); // any scheme like mailto:
|
|
29
|
+
out.push({ rawHref, href, anchor, isUrl, isRelative });
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
// A URL pattern matches a URL iff the URL's host+path starts with the pattern
|
|
34
|
+
// AND the next char in the URL is /, #, ?, or end-of-string. This rejects
|
|
35
|
+
// substring matches like `github.com/org/service-a-tools` against pattern
|
|
36
|
+
// `github.com/org/service-a`.
|
|
37
|
+
function urlMatchesPattern(url, pattern) {
|
|
38
|
+
const stripped = url.replace(/^https?:\/\//i, "");
|
|
39
|
+
if (!stripped.startsWith(pattern))
|
|
40
|
+
return false;
|
|
41
|
+
const next = stripped.charAt(pattern.length);
|
|
42
|
+
return next === "" || next === "/" || next === "#" || next === "?";
|
|
43
|
+
}
|
|
44
|
+
// Pulls the path-after-pattern out of a known-matching URL: strips scheme,
|
|
45
|
+
// pattern, and the GitHub blob/tree/... prefix when present. Returns relPath
|
|
46
|
+
// + optional anchor.
|
|
47
|
+
function extractTargetPathFromUrl(url, pattern) {
|
|
48
|
+
const stripped = url.replace(/^https?:\/\//i, "");
|
|
49
|
+
const rest = stripped.slice(pattern.length); // starts with "/" or ""
|
|
50
|
+
if (rest === "")
|
|
51
|
+
return null; // pattern alone, no file
|
|
52
|
+
// strip leading "/"
|
|
53
|
+
let tail = rest.startsWith("/") ? rest.slice(1) : rest;
|
|
54
|
+
// Split anchor / query
|
|
55
|
+
const queryIdx = tail.indexOf("?");
|
|
56
|
+
if (queryIdx !== -1)
|
|
57
|
+
tail = tail.slice(0, queryIdx);
|
|
58
|
+
const hashIdx = tail.indexOf("#");
|
|
59
|
+
const anchor = hashIdx !== -1 ? tail.slice(hashIdx + 1) || null : null;
|
|
60
|
+
if (hashIdx !== -1)
|
|
61
|
+
tail = tail.slice(0, hashIdx);
|
|
62
|
+
if (!tail)
|
|
63
|
+
return null;
|
|
64
|
+
// GitHub URLs: /blob/<branch>/<path> or /tree/<branch>/<path>.
|
|
65
|
+
// Strip if present; otherwise treat `tail` as the relPath directly.
|
|
66
|
+
const ghMatch = tail.match(/^(blob|tree|raw)\/[^/]+\/(.+)$/);
|
|
67
|
+
const relPath = ghMatch ? ghMatch[2] : tail;
|
|
68
|
+
return { relPath: posix.normalize(relPath), anchor };
|
|
69
|
+
}
|
|
70
|
+
// Note: repo.config.path values are always real (symlink-resolved) paths —
|
|
71
|
+
// config.ts#validateRepoPath calls realpathSync() before storing them. The
|
|
72
|
+
// containment check below (`rel.startsWith("..")`) is therefore symlink-safe
|
|
73
|
+
// for the source side. The resolved href target itself is not realpathSync'd
|
|
74
|
+
// because it may not exist on disk (that's what broken_refs.ts catches).
|
|
75
|
+
// Reference-style links `[text][ref]` are intentionally NOT extracted in v1
|
|
76
|
+
// (audit operates on arbitrary markdown repos without needing full AST parsing;
|
|
77
|
+
// mention here per plan resolution #1).
|
|
78
|
+
function resolveRelative(fromRepoPath, fromRelPath, href, repos) {
|
|
79
|
+
const fromAbs = nodeResolve(fromRepoPath, fromRelPath);
|
|
80
|
+
const fromDir = nodeResolve(fromAbs, "..");
|
|
81
|
+
const resolvedAbs = nodeResolve(fromDir, href);
|
|
82
|
+
for (const repo of repos) {
|
|
83
|
+
const rel = nodeRelative(repo.config.path, resolvedAbs);
|
|
84
|
+
if (!rel.startsWith("..") && !nodeResolve(repo.config.path, rel).startsWith("..")) {
|
|
85
|
+
// posix-normalize for cross-platform sanity
|
|
86
|
+
const posixRel = rel.split(/[\\/]/).join("/");
|
|
87
|
+
return { repo: repo.config.name, relPath: posixRel };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
export function classifyEdges(snapshots) {
|
|
93
|
+
const edges = [];
|
|
94
|
+
for (const snap of snapshots) {
|
|
95
|
+
const sourceRepo = snap.config.name;
|
|
96
|
+
for (const doc of snap.docs.values()) {
|
|
97
|
+
for (const link of doc.links) {
|
|
98
|
+
if (link.isUrl) {
|
|
99
|
+
// Try each repo's url patterns. First match wins.
|
|
100
|
+
let matched = false;
|
|
101
|
+
for (const targetSnap of snapshots) {
|
|
102
|
+
for (const pattern of targetSnap.config.urls) {
|
|
103
|
+
if (!urlMatchesPattern(link.href, pattern))
|
|
104
|
+
continue;
|
|
105
|
+
const target = extractTargetPathFromUrl(link.href, pattern);
|
|
106
|
+
if (!target)
|
|
107
|
+
continue;
|
|
108
|
+
edges.push({
|
|
109
|
+
sourceRepo,
|
|
110
|
+
sourcePath: doc.relPath,
|
|
111
|
+
targetRepo: targetSnap.config.name,
|
|
112
|
+
targetPath: target.relPath,
|
|
113
|
+
targetAnchor: target.anchor ?? link.anchor,
|
|
114
|
+
rawHref: link.rawHref,
|
|
115
|
+
});
|
|
116
|
+
matched = true;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
if (matched)
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
// Unmatched URLs are external (no configured repo owns them); drop.
|
|
123
|
+
// Per plan resolution #3: anonymous repos get no urls[], and URLs
|
|
124
|
+
// targeting unconfigured repos go silently unflagged in v1.
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!link.isRelative)
|
|
128
|
+
continue; // anchors-only, mailto:, etc.
|
|
129
|
+
const resolved = resolveRelative(snap.config.path, doc.relPath, link.href, snapshots);
|
|
130
|
+
if (!resolved) {
|
|
131
|
+
// Escaped to an unconfigured location; record as edge into sourceRepo
|
|
132
|
+
// with an unresolvable path so broken_refs flags it.
|
|
133
|
+
edges.push({
|
|
134
|
+
sourceRepo,
|
|
135
|
+
sourcePath: doc.relPath,
|
|
136
|
+
targetRepo: sourceRepo,
|
|
137
|
+
targetPath: link.href, // unresolved sentinel
|
|
138
|
+
targetAnchor: link.anchor,
|
|
139
|
+
rawHref: link.rawHref,
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
edges.push({
|
|
144
|
+
sourceRepo,
|
|
145
|
+
sourcePath: doc.relPath,
|
|
146
|
+
targetRepo: resolved.repo,
|
|
147
|
+
targetPath: resolved.relPath,
|
|
148
|
+
targetAnchor: link.anchor,
|
|
149
|
+
rawHref: link.rawHref,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return edges;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/audit/links.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/audit/report.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAsC,MAAM,YAAY,CAAC;AAyBlF,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CA8B1D;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAEtD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/audit/report.ts
|
|
2
|
+
// Pure formatters over AuditReport. No IO.
|
|
3
|
+
function renderBrokenRefs(rows) {
|
|
4
|
+
if (rows.length === 0)
|
|
5
|
+
return "_no broken cross-repo references._\n";
|
|
6
|
+
const lines = ["| kind | source | target | href |", "|---|---|---|---|"];
|
|
7
|
+
for (const r of rows) {
|
|
8
|
+
const targetAnchor = r.target.anchor ? `#${r.target.anchor}` : "";
|
|
9
|
+
lines.push(`| ${r.kind} | ${r.source.repo}/${r.source.path} | ` +
|
|
10
|
+
`${r.target.repo}/${r.target.path}${targetAnchor} | \`${r.rawHref}\` |`);
|
|
11
|
+
}
|
|
12
|
+
return `${lines.join("\n")}\n`;
|
|
13
|
+
}
|
|
14
|
+
function renderStaleness(rows) {
|
|
15
|
+
if (rows.length === 0)
|
|
16
|
+
return "_no staleness findings._\n";
|
|
17
|
+
const lines = ["| kind | doc | mtime | chain |", "|---|---|---|---|"];
|
|
18
|
+
for (const r of rows) {
|
|
19
|
+
const chain = r.staleChain ? r.staleChain.map((n) => `${n.repo}/${n.path}`).join(" → ") : "—";
|
|
20
|
+
lines.push(`| ${r.kind} | ${r.repo}/${r.path} | ${r.mtime} | ${chain} |`);
|
|
21
|
+
}
|
|
22
|
+
return `${lines.join("\n")}\n`;
|
|
23
|
+
}
|
|
24
|
+
export function renderMarkdown(report) {
|
|
25
|
+
const t = report.totals;
|
|
26
|
+
const empty = t.brokenRefs === 0 && t.directlyStale === 0 && t.transitivelyStale === 0;
|
|
27
|
+
const head = [
|
|
28
|
+
"# Coherence Audit Report",
|
|
29
|
+
"",
|
|
30
|
+
`_generated: ${report.generatedAt}_`,
|
|
31
|
+
"",
|
|
32
|
+
"## Totals",
|
|
33
|
+
"",
|
|
34
|
+
`- repos scanned: **${t.reposScanned}**`,
|
|
35
|
+
`- docs scanned: **${t.docsScanned}**`,
|
|
36
|
+
`- broken cross-repo refs: **${t.brokenRefs}**`,
|
|
37
|
+
`- directly stale docs: **${t.directlyStale}**`,
|
|
38
|
+
`- transitively stale docs: **${t.transitivelyStale}**`,
|
|
39
|
+
"",
|
|
40
|
+
];
|
|
41
|
+
if (empty) {
|
|
42
|
+
head.push("_no findings — coherence checks passed._\n");
|
|
43
|
+
return head.join("\n");
|
|
44
|
+
}
|
|
45
|
+
return [
|
|
46
|
+
...head,
|
|
47
|
+
"## Broken cross-repo references",
|
|
48
|
+
"",
|
|
49
|
+
renderBrokenRefs(report.brokenRefs),
|
|
50
|
+
"## Staleness",
|
|
51
|
+
"",
|
|
52
|
+
renderStaleness(report.staleness),
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
export function renderJson(report) {
|
|
56
|
+
return JSON.stringify(report, null, 2);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/audit/report.ts"],"names":[],"mappings":"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"}
|