daftari 1.12.6 → 1.13.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/CHANGELOG.md +11 -0
- package/README.md +55 -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
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.13.0] - 2026-05-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `daftari audit` CLI subcommand. Scans N markdown repos and reports broken
|
|
15
|
+
cross-repo references and link-graph transitive staleness. Outputs markdown
|
|
16
|
+
(default: stdout) and optional JSON. Exit code 1 if `fail_on.broken_refs` or
|
|
17
|
+
`fail_on.transitive_staleness` thresholds are exceeded. Anonymous repos passed
|
|
18
|
+
via `--repo` get no URL patterns — URL-based cross-refs into them aren't
|
|
19
|
+
detected; use `--config` with an `urls:` block to enable them. See issue #85.
|
|
20
|
+
|
|
10
21
|
## [1.12.6] - 2026-05-27
|
|
11
22
|
|
|
12
23
|
### Changed
|
package/README.md
CHANGED
|
@@ -164,6 +164,54 @@ Deliberately deferred to keep the surface tight:
|
|
|
164
164
|
|
|
165
165
|
Each is a clean increment on a surface that already works.
|
|
166
166
|
|
|
167
|
+
## Coherence audit
|
|
168
|
+
|
|
169
|
+
`daftari audit` runs a read-only, deterministic check across one or more
|
|
170
|
+
markdown repos for broken cross-repo references and link-graph transitive
|
|
171
|
+
staleness. It does **not** create a `.daftari/` vault on disk and does not
|
|
172
|
+
write to the audited repos.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Anonymous repos (no URL patterns):
|
|
176
|
+
daftari audit --repo ~/repos/service-a --repo ~/repos/service-b
|
|
177
|
+
|
|
178
|
+
# Or with a config file (recommended; see daftari audit --help for the schema):
|
|
179
|
+
daftari audit --config audit.yaml
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Anonymous repos passed via `--repo` do not get URL patterns, so cross-repo
|
|
183
|
+
references that take the form of GitHub URLs (e.g.
|
|
184
|
+
`https://github.com/org/service-a/blob/main/docs/api.md`) into them will be
|
|
185
|
+
silently treated as external. Declare repos in `audit.yaml` with their `urls`
|
|
186
|
+
field to detect these.
|
|
187
|
+
|
|
188
|
+
`audit.yaml` schema:
|
|
189
|
+
|
|
190
|
+
```yaml
|
|
191
|
+
repos:
|
|
192
|
+
- name: service-a
|
|
193
|
+
path: ~/repos/service-a
|
|
194
|
+
docs_glob: "docs/**/*.md" # default: "**/*.md"
|
|
195
|
+
urls: # optional; enables URL-pattern matching
|
|
196
|
+
- "github.com/org/service-a"
|
|
197
|
+
|
|
198
|
+
- name: service-b
|
|
199
|
+
path: ~/repos/service-b
|
|
200
|
+
urls:
|
|
201
|
+
- "github.com/org/service-b"
|
|
202
|
+
|
|
203
|
+
output:
|
|
204
|
+
markdown: coherence-report.md # default: stdout
|
|
205
|
+
json: coherence-report.json # default: not emitted
|
|
206
|
+
|
|
207
|
+
staleness:
|
|
208
|
+
threshold_days: 540 # default: 540 (18 months)
|
|
209
|
+
|
|
210
|
+
fail_on:
|
|
211
|
+
broken_refs: 1 # default: fail on any broken ref
|
|
212
|
+
transitive_staleness: 100 # default: generous; teams tune
|
|
213
|
+
```
|
|
214
|
+
|
|
167
215
|
## Development
|
|
168
216
|
|
|
169
217
|
```
|
|
@@ -181,6 +229,13 @@ Design tenets: functions and types, no classes; tool handlers return
|
|
|
181
229
|
- <docs/architecture.md> — layered design, request path, accumulation vs. generative domains
|
|
182
230
|
- <docs/file-format.md> — complete frontmatter reference
|
|
183
231
|
|
|
232
|
+
## Integrations
|
|
233
|
+
|
|
234
|
+
- [`integrations/langchain/`](integrations/langchain/) — `langchain-daftari`, a
|
|
235
|
+
Python package that exposes the 14 daftari tools as LangChain `BaseTool`s
|
|
236
|
+
for use with LangGraph / `create_react_agent`. Sync + async, schemas pulled
|
|
237
|
+
live from `tools/list`.
|
|
238
|
+
|
|
184
239
|
## Privacy
|
|
185
240
|
|
|
186
241
|
Daftari is a local MCP server. It runs on your machine, against vault files on
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broken_refs.d.ts","sourceRoot":"","sources":["../../../src/audit/checks/broken_refs.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5E,wBAAgB,eAAe,CAAC,SAAS,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,gBAAgB,EAAE,CA0DhG"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/audit/checks/broken_refs.ts
|
|
2
|
+
// Pure function: given snapshots and classified edges, return every broken
|
|
3
|
+
// reference as a BrokenRefFinding. No throws. No classes.
|
|
4
|
+
//
|
|
5
|
+
// Resolution logic:
|
|
6
|
+
// 1. Look up target repo by name; if not found → missing_file.
|
|
7
|
+
// 2. Look up targetPath in that repo's docs Map.
|
|
8
|
+
// 3. If not found and targetPath lacks a ".md" extension, try targetPath +
|
|
9
|
+
// ".md" (spec §7 bare-path fallback).
|
|
10
|
+
// 4. If still not found → missing_file.
|
|
11
|
+
// 5. If found and targetAnchor is set: check DocSnapshot.headings. If absent
|
|
12
|
+
// → missing_anchor.
|
|
13
|
+
export function checkBrokenRefs(snapshots, edges) {
|
|
14
|
+
// Index snapshots by repo name for O(1) lookup.
|
|
15
|
+
const byRepo = new Map();
|
|
16
|
+
for (const snap of snapshots) {
|
|
17
|
+
byRepo.set(snap.config.name, snap);
|
|
18
|
+
}
|
|
19
|
+
const findings = [];
|
|
20
|
+
for (const edge of edges) {
|
|
21
|
+
const targetSnap = byRepo.get(edge.targetRepo);
|
|
22
|
+
if (!targetSnap) {
|
|
23
|
+
// Target repo itself is unknown — treat as missing file.
|
|
24
|
+
findings.push({
|
|
25
|
+
kind: "missing_file",
|
|
26
|
+
source: { repo: edge.sourceRepo, path: edge.sourcePath },
|
|
27
|
+
target: { repo: edge.targetRepo, path: edge.targetPath, anchor: edge.targetAnchor },
|
|
28
|
+
rawHref: edge.rawHref,
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Resolve the target doc: exact match, then .md fallback.
|
|
33
|
+
let resolvedPath = null;
|
|
34
|
+
if (targetSnap.docs.has(edge.targetPath)) {
|
|
35
|
+
resolvedPath = edge.targetPath;
|
|
36
|
+
}
|
|
37
|
+
else if (!edge.targetPath.endsWith(".md")) {
|
|
38
|
+
const withMd = `${edge.targetPath}.md`;
|
|
39
|
+
if (targetSnap.docs.has(withMd)) {
|
|
40
|
+
resolvedPath = withMd;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (resolvedPath === null) {
|
|
44
|
+
findings.push({
|
|
45
|
+
kind: "missing_file",
|
|
46
|
+
source: { repo: edge.sourceRepo, path: edge.sourcePath },
|
|
47
|
+
target: { repo: edge.targetRepo, path: edge.targetPath, anchor: edge.targetAnchor },
|
|
48
|
+
rawHref: edge.rawHref,
|
|
49
|
+
});
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// File found. Check anchor if present.
|
|
53
|
+
if (edge.targetAnchor !== null) {
|
|
54
|
+
const doc = targetSnap.docs.get(resolvedPath);
|
|
55
|
+
if (doc && !doc.headings.has(edge.targetAnchor)) {
|
|
56
|
+
findings.push({
|
|
57
|
+
kind: "missing_anchor",
|
|
58
|
+
source: { repo: edge.sourceRepo, path: edge.sourcePath },
|
|
59
|
+
target: { repo: edge.targetRepo, path: resolvedPath, anchor: edge.targetAnchor },
|
|
60
|
+
rawHref: edge.rawHref,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return findings;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=broken_refs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broken_refs.js","sourceRoot":"","sources":["../../../src/audit/checks/broken_refs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.d.ts","sourceRoot":"","sources":["../../../src/audit/checks/staleness.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAY5E,wBAAgB,cAAc,CAC5B,SAAS,EAAE,YAAY,EAAE,EACzB,KAAK,EAAE,QAAQ,EAAE,EACjB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,IAAI,GACR,gBAAgB,EAAE,CAsGpB"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// src/audit/checks/staleness.ts
|
|
2
|
+
// Memoized BFS from each fresh doc to the nearest directly-stale leaf. BFS
|
|
3
|
+
// (not DFS) so the recorded chain is the shortest path. Cycles handled by
|
|
4
|
+
// the BFS visited set; verdicts memoized across roots.
|
|
5
|
+
const key = (repo, path) => `${repo} ${path}`;
|
|
6
|
+
function isDirectlyStale(mtimeIso, now, thresholdDays) {
|
|
7
|
+
const ms = now.getTime() - new Date(mtimeIso).getTime();
|
|
8
|
+
return ms > thresholdDays * 86_400_000;
|
|
9
|
+
}
|
|
10
|
+
export function checkStaleness(snapshots, edges, thresholdDays, now) {
|
|
11
|
+
const nodes = new Map();
|
|
12
|
+
for (const snap of snapshots) {
|
|
13
|
+
for (const d of snap.docs.values()) {
|
|
14
|
+
nodes.set(key(snap.config.name, d.relPath), {
|
|
15
|
+
repo: snap.config.name,
|
|
16
|
+
path: d.relPath,
|
|
17
|
+
mtime: d.mtime,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const adj = new Map();
|
|
22
|
+
for (const edge of edges) {
|
|
23
|
+
const src = key(edge.sourceRepo, edge.sourcePath);
|
|
24
|
+
const dst = key(edge.targetRepo, edge.targetPath);
|
|
25
|
+
// Resolve the dst against the .md-extension fallback so transitive edges
|
|
26
|
+
// see the same node identity broken-refs check does.
|
|
27
|
+
let dstKey = dst;
|
|
28
|
+
if (!nodes.has(dstKey)) {
|
|
29
|
+
const alt = key(edge.targetRepo, `${edge.targetPath}.md`);
|
|
30
|
+
if (nodes.has(alt))
|
|
31
|
+
dstKey = alt;
|
|
32
|
+
}
|
|
33
|
+
if (!nodes.has(dstKey))
|
|
34
|
+
continue; // dangling edge — broken_refs handles it
|
|
35
|
+
if (!nodes.has(src))
|
|
36
|
+
continue;
|
|
37
|
+
const list = adj.get(src) ?? [];
|
|
38
|
+
list.push(dstKey);
|
|
39
|
+
adj.set(src, list);
|
|
40
|
+
}
|
|
41
|
+
// Classify directly stale leaves.
|
|
42
|
+
const direct = new Set();
|
|
43
|
+
for (const [k, info] of nodes) {
|
|
44
|
+
if (isDirectlyStale(info.mtime, now, thresholdDays))
|
|
45
|
+
direct.add(k);
|
|
46
|
+
}
|
|
47
|
+
// BFS from each fresh root to find shortest path to a stale node.
|
|
48
|
+
// Memoize the shortest chain per node so a second root reaching it
|
|
49
|
+
// doesn't recompute.
|
|
50
|
+
const shortestChain = new Map(); // null = no stale path
|
|
51
|
+
function chainFor(root) {
|
|
52
|
+
if (direct.has(root))
|
|
53
|
+
return null; // direct, not transitive
|
|
54
|
+
if (shortestChain.has(root))
|
|
55
|
+
return shortestChain.get(root) ?? null;
|
|
56
|
+
const prev = new Map();
|
|
57
|
+
prev.set(root, null);
|
|
58
|
+
const queue = [root];
|
|
59
|
+
let target = null;
|
|
60
|
+
while (queue.length > 0) {
|
|
61
|
+
const cur = queue.shift();
|
|
62
|
+
if (cur !== root && direct.has(cur)) {
|
|
63
|
+
target = cur;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
for (const next of adj.get(cur) ?? []) {
|
|
67
|
+
if (prev.has(next))
|
|
68
|
+
continue;
|
|
69
|
+
prev.set(next, cur);
|
|
70
|
+
queue.push(next);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!target) {
|
|
74
|
+
shortestChain.set(root, null);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const path = [];
|
|
78
|
+
let n = target;
|
|
79
|
+
while (n) {
|
|
80
|
+
path.unshift(n);
|
|
81
|
+
n = prev.get(n) ?? null;
|
|
82
|
+
}
|
|
83
|
+
const chain = path.map((k) => {
|
|
84
|
+
const info = nodes.get(k);
|
|
85
|
+
return { repo: info.repo, path: info.path, mtime: info.mtime };
|
|
86
|
+
});
|
|
87
|
+
shortestChain.set(root, chain);
|
|
88
|
+
return chain;
|
|
89
|
+
}
|
|
90
|
+
const findings = [];
|
|
91
|
+
for (const [k, info] of nodes) {
|
|
92
|
+
if (direct.has(k)) {
|
|
93
|
+
findings.push({
|
|
94
|
+
kind: "direct",
|
|
95
|
+
repo: info.repo,
|
|
96
|
+
path: info.path,
|
|
97
|
+
mtime: info.mtime,
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const chain = chainFor(k);
|
|
102
|
+
if (chain) {
|
|
103
|
+
findings.push({
|
|
104
|
+
kind: "transitive",
|
|
105
|
+
repo: info.repo,
|
|
106
|
+
path: info.path,
|
|
107
|
+
mtime: info.mtime,
|
|
108
|
+
staleChain: chain,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return findings;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=staleness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.js","sourceRoot":"","sources":["../../../src/audit/checks/staleness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Result } from "../frontmatter/types.js";
|
|
2
|
+
import type { AuditConfig, AuditError, RepoSnapshot } from "./types.js";
|
|
3
|
+
export declare function collectRepos(config: AuditConfig): Promise<Result<RepoSnapshot[], AuditError>>;
|
|
4
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/audit/collect.ts"],"names":[],"mappings":"AAWA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAA2B,YAAY,EAAE,MAAM,YAAY,CAAC;AAoHjG,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,UAAU,CAAC,CAAC,CAa7C"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// src/audit/collect.ts
|
|
2
|
+
// The only IO stage of the audit pipeline. Per repo: glob docs, strip
|
|
3
|
+
// frontmatter, extract headings (GitHub-slugged) and links, then batch
|
|
4
|
+
// git log to populate mtimes; on any git failure, fall back to fs mtime.
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { statSync } from "node:fs";
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import { resolve as nodeResolve } from "node:path";
|
|
9
|
+
import { glob } from "glob";
|
|
10
|
+
import matter from "gray-matter";
|
|
11
|
+
import { err, ok } from "../frontmatter/types.js";
|
|
12
|
+
import { extractLinksFromBody } from "./links.js";
|
|
13
|
+
import { runtimeError } from "./types.js";
|
|
14
|
+
function slugify(heading) {
|
|
15
|
+
// GitHub slug: lowercase, strip non-alphanumeric (keep `-_`), whitespace -> `-`.
|
|
16
|
+
return heading
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(/\s+/g, "-")
|
|
19
|
+
.replace(/[^a-z0-9\-_]/g, "")
|
|
20
|
+
.replace(/-+/g, "-")
|
|
21
|
+
.replace(/^-+|-+$/g, "");
|
|
22
|
+
}
|
|
23
|
+
function extractHeadings(body) {
|
|
24
|
+
const out = new Set();
|
|
25
|
+
for (const line of body.split(/\r?\n/)) {
|
|
26
|
+
const m = line.match(/^#{1,6}\s+(.+?)\s*#*\s*$/);
|
|
27
|
+
if (m)
|
|
28
|
+
out.add(slugify(m[1]));
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
function gitMtimes(repoPath, docsGlob) {
|
|
33
|
+
const opts = {
|
|
34
|
+
cwd: repoPath,
|
|
35
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
36
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], opts);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
let out;
|
|
45
|
+
try {
|
|
46
|
+
// Use the :(glob) pathspec magic so git's internal glob matcher handles
|
|
47
|
+
// "**/*.md" correctly on all platforms and git configurations, including
|
|
48
|
+
// matching top-level files (bare "**/*.md" without the magic prefix skips
|
|
49
|
+
// files at depth 0 on some git versions).
|
|
50
|
+
const pathspec = docsGlob.startsWith(":(") ? docsGlob : `:(glob)${docsGlob}`;
|
|
51
|
+
out = execFileSync("git", ["log", "--all", "--name-only", `--pretty=format:COMMIT %aI`, "--", pathspec], opts).toString();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const mtimes = new Map();
|
|
57
|
+
let currentIso = null;
|
|
58
|
+
for (const line of out.split("\n")) {
|
|
59
|
+
if (line.startsWith("COMMIT ")) {
|
|
60
|
+
currentIso = line.slice("COMMIT ".length).trim() || null;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const path = line.trim();
|
|
64
|
+
if (!path || !currentIso)
|
|
65
|
+
continue;
|
|
66
|
+
// First time we see a path is its newest commit (git log is newest-first).
|
|
67
|
+
if (!mtimes.has(path))
|
|
68
|
+
mtimes.set(path, currentIso);
|
|
69
|
+
}
|
|
70
|
+
return mtimes;
|
|
71
|
+
}
|
|
72
|
+
async function loadDoc(repoPath, relPath, mtimeFromGit) {
|
|
73
|
+
const absPath = nodeResolve(repoPath, relPath);
|
|
74
|
+
const text = await readFile(absPath, "utf-8");
|
|
75
|
+
const parsed = matter(text);
|
|
76
|
+
const body = parsed.content;
|
|
77
|
+
let mtime;
|
|
78
|
+
let mtimeSource;
|
|
79
|
+
if (mtimeFromGit) {
|
|
80
|
+
mtime = mtimeFromGit;
|
|
81
|
+
mtimeSource = "git";
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
mtime = statSync(absPath).mtime.toISOString();
|
|
85
|
+
mtimeSource = "fs";
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
relPath: relPath.split(/[\\/]/).join("/"),
|
|
89
|
+
absPath,
|
|
90
|
+
mtime,
|
|
91
|
+
mtimeSource,
|
|
92
|
+
headings: extractHeadings(body),
|
|
93
|
+
links: extractLinksFromBody(body),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function collectOne(config) {
|
|
97
|
+
const files = await glob(config.docsGlob, {
|
|
98
|
+
cwd: config.path,
|
|
99
|
+
nodir: true,
|
|
100
|
+
posix: true,
|
|
101
|
+
dot: false,
|
|
102
|
+
});
|
|
103
|
+
const onlyMd = files.filter((f) => /\.(md|markdown)$/i.test(f));
|
|
104
|
+
const mtimes = gitMtimes(config.path, config.docsGlob);
|
|
105
|
+
const docs = new Map();
|
|
106
|
+
for (const rel of onlyMd) {
|
|
107
|
+
const posixRel = rel.split(/[\\/]/).join("/");
|
|
108
|
+
try {
|
|
109
|
+
const snap = await loadDoc(config.path, posixRel, mtimes?.get(posixRel));
|
|
110
|
+
docs.set(posixRel, snap);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
114
|
+
process.stderr.write(`daftari audit: warning: unreadable doc ${posixRel}: ${msg}\n`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { config, docs };
|
|
118
|
+
}
|
|
119
|
+
export async function collectRepos(config) {
|
|
120
|
+
// Sequential per-repo; concurrency would help but is out of scope until the
|
|
121
|
+
// 30s budget gets squeezed (see plan §perf).
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const r of config.repos) {
|
|
124
|
+
try {
|
|
125
|
+
out.push(await collectOne(r));
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
129
|
+
return err(runtimeError(`collect failed for repo ${r.name}: ${reason}`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return ok(out);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.js","sourceRoot":"","sources":["../../src/audit/collect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Result } from "../frontmatter/types.js";
|
|
2
|
+
import { type AuditConfig, type AuditError } from "./types.js";
|
|
3
|
+
export declare function parseAuditConfig(argv: string[], yamlLoader?: (path: string) => string): Result<AuditConfig, AuditError>;
|
|
4
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/audit/config.ts"],"names":[],"mappings":"AAQA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAgC,MAAM,YAAY,CAAC;AAsG7F,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EAAE,EACd,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACpC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAwFjC"}
|
|
@@ -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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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type AuditConfig = {
|
|
2
|
+
repos: RepoConfig[];
|
|
3
|
+
output: {
|
|
4
|
+
markdown?: string;
|
|
5
|
+
json?: string;
|
|
6
|
+
};
|
|
7
|
+
staleness: {
|
|
8
|
+
thresholdDays: number;
|
|
9
|
+
};
|
|
10
|
+
failOn: {
|
|
11
|
+
brokenRefs: number;
|
|
12
|
+
transitiveStaleness: number;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type RepoConfig = {
|
|
16
|
+
name: string;
|
|
17
|
+
path: string;
|
|
18
|
+
docsGlob: string;
|
|
19
|
+
urls: string[];
|
|
20
|
+
};
|
|
21
|
+
export type DocSnapshot = {
|
|
22
|
+
relPath: string;
|
|
23
|
+
absPath: string;
|
|
24
|
+
mtime: string;
|
|
25
|
+
mtimeSource: "git" | "fs";
|
|
26
|
+
headings: Set<string>;
|
|
27
|
+
links: LinkRef[];
|
|
28
|
+
};
|
|
29
|
+
export type LinkRef = {
|
|
30
|
+
rawHref: string;
|
|
31
|
+
href: string;
|
|
32
|
+
anchor: string | null;
|
|
33
|
+
isUrl: boolean;
|
|
34
|
+
isRelative: boolean;
|
|
35
|
+
};
|
|
36
|
+
export type RepoSnapshot = {
|
|
37
|
+
config: RepoConfig;
|
|
38
|
+
docs: Map<string, DocSnapshot>;
|
|
39
|
+
};
|
|
40
|
+
export type LinkEdge = {
|
|
41
|
+
sourceRepo: string;
|
|
42
|
+
sourcePath: string;
|
|
43
|
+
targetRepo: string;
|
|
44
|
+
targetPath: string;
|
|
45
|
+
targetAnchor: string | null;
|
|
46
|
+
rawHref: string;
|
|
47
|
+
};
|
|
48
|
+
export type BrokenRefFinding = {
|
|
49
|
+
kind: "missing_file" | "missing_anchor";
|
|
50
|
+
source: {
|
|
51
|
+
repo: string;
|
|
52
|
+
path: string;
|
|
53
|
+
};
|
|
54
|
+
target: {
|
|
55
|
+
repo: string;
|
|
56
|
+
path: string;
|
|
57
|
+
anchor: string | null;
|
|
58
|
+
};
|
|
59
|
+
rawHref: string;
|
|
60
|
+
};
|
|
61
|
+
export type StalenessFinding = {
|
|
62
|
+
kind: "direct" | "transitive";
|
|
63
|
+
repo: string;
|
|
64
|
+
path: string;
|
|
65
|
+
mtime: string;
|
|
66
|
+
staleChain?: Array<{
|
|
67
|
+
repo: string;
|
|
68
|
+
path: string;
|
|
69
|
+
mtime: string;
|
|
70
|
+
}>;
|
|
71
|
+
};
|
|
72
|
+
export type AuditReport = {
|
|
73
|
+
generatedAt: string;
|
|
74
|
+
config: AuditConfig;
|
|
75
|
+
totals: {
|
|
76
|
+
reposScanned: number;
|
|
77
|
+
docsScanned: number;
|
|
78
|
+
brokenRefs: number;
|
|
79
|
+
directlyStale: number;
|
|
80
|
+
transitivelyStale: number;
|
|
81
|
+
};
|
|
82
|
+
brokenRefs: BrokenRefFinding[];
|
|
83
|
+
staleness: StalenessFinding[];
|
|
84
|
+
};
|
|
85
|
+
export type AuditError = {
|
|
86
|
+
kind: "config";
|
|
87
|
+
message: string;
|
|
88
|
+
} | {
|
|
89
|
+
kind: "runtime";
|
|
90
|
+
message: string;
|
|
91
|
+
};
|
|
92
|
+
export declare const configError: (message: string) => AuditError;
|
|
93
|
+
export declare const runtimeError: (message: string) => AuditError;
|
|
94
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/audit/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/audit/types.ts
|
|
2
|
+
// Shared types for the coherence audit. Pure data shapes; no logic.
|
|
3
|
+
export const configError = (message) => ({
|
|
4
|
+
kind: "config",
|
|
5
|
+
message,
|
|
6
|
+
});
|
|
7
|
+
export const runtimeError = (message) => ({
|
|
8
|
+
kind: "runtime",
|
|
9
|
+
message,
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/audit/types.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,oEAAoE;AAkFpE,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAAc,EAAE,CAAC,CAAC;IAC3D,IAAI,EAAE,QAAQ;IACd,OAAO;CACR,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAe,EAAc,EAAE,CAAC,CAAC;IAC5D,IAAI,EAAE,SAAS;IACf,OAAO;CACR,CAAC,CAAC"}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA6KA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8DnE;AAED,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BvD"}
|
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ const USAGE = `daftari — an MCP server that exposes a curated markdown vault t
|
|
|
20
20
|
Usage:
|
|
21
21
|
daftari --init [path] Scaffold a new vault (default: ./daftari-vault)
|
|
22
22
|
daftari --vault <path> [options] Start the MCP server (stdio) against a vault
|
|
23
|
+
daftari audit [options] Run a cross-repo coherence audit (see: daftari audit --help)
|
|
23
24
|
|
|
24
25
|
Server options:
|
|
25
26
|
--user <username> Identity the server runs as (default: guest)
|
|
@@ -200,6 +201,11 @@ export async function initVault(targetPath) {
|
|
|
200
201
|
return 0;
|
|
201
202
|
}
|
|
202
203
|
export async function run(argv) {
|
|
204
|
+
if (argv[0] === "audit") {
|
|
205
|
+
const { runAudit } = await import("./audit/index.js");
|
|
206
|
+
process.exitCode = await runAudit(argv.slice(1));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
203
209
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
204
210
|
process.stdout.write(USAGE);
|
|
205
211
|
return;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,eAAe;AACf,EAAE;AACF,0DAA0D;AAC1D,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,+BAA+B;AAE/B,OAAO,EACL,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAExC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,MAAM,KAAK,GAAG
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|