mrplex 0.0.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/LICENSE +21 -0
- package/README.md +284 -0
- package/dist/cli/config.d.ts +28 -0
- package/dist/cli/config.js +48 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +13 -0
- package/dist/cli/exit-codes.js +39 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/format.d.ts +22 -0
- package/dist/cli/format.js +148 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/main.d.ts +16 -0
- package/dist/cli/main.js +1518 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/client/kernel-client.d.ts +97 -0
- package/dist/client/kernel-client.js +14 -0
- package/dist/client/kernel-client.js.map +1 -0
- package/dist/client/local.d.ts +26 -0
- package/dist/client/local.js +103 -0
- package/dist/client/local.js.map +1 -0
- package/dist/client/remote-mcp.d.ts +35 -0
- package/dist/client/remote-mcp.js +174 -0
- package/dist/client/remote-mcp.js.map +1 -0
- package/dist/embed/backfill.d.ts +24 -0
- package/dist/embed/backfill.js +33 -0
- package/dist/embed/backfill.js.map +1 -0
- package/dist/embed/chunker.d.ts +31 -0
- package/dist/embed/chunker.js +121 -0
- package/dist/embed/chunker.js.map +1 -0
- package/dist/embed/cmd-hook.d.ts +21 -0
- package/dist/embed/cmd-hook.js +100 -0
- package/dist/embed/cmd-hook.js.map +1 -0
- package/dist/embed/config.d.ts +61 -0
- package/dist/embed/config.js +109 -0
- package/dist/embed/config.js.map +1 -0
- package/dist/embed/hook.d.ts +38 -0
- package/dist/embed/hook.js +56 -0
- package/dist/embed/hook.js.map +1 -0
- package/dist/embed/http-hook.d.ts +13 -0
- package/dist/embed/http-hook.js +39 -0
- package/dist/embed/http-hook.js.map +1 -0
- package/dist/embed/worker.d.ts +62 -0
- package/dist/embed/worker.js +228 -0
- package/dist/embed/worker.js.map +1 -0
- package/dist/fixtures/hwe/characters/albion-ever.md +12 -0
- package/dist/fixtures/hwe/characters/cent.md +11 -0
- package/dist/fixtures/hwe/concepts/cent-demiurge-resonance.md +10 -0
- package/dist/fixtures/hwe/concepts/phase-looper.md +10 -0
- package/dist/fixtures/hwe/entities/demiurge.md +11 -0
- package/dist/fixtures/hwe/entities/monad.md +11 -0
- package/dist/fixtures/hwe/moc/globocorp-network.md +19 -0
- package/dist/fixtures/hwe/organizations/globocorp.md +11 -0
- package/dist/fixtures/hwe/readme.md +24 -0
- package/dist/fixtures/starship/crew/aria-okonkwo.md +14 -0
- package/dist/fixtures/starship/crew/bexley-orr.md +14 -0
- package/dist/fixtures/starship/crew/dax-thorne.md +14 -0
- package/dist/fixtures/starship/crew/isolde-marsh.md +16 -0
- package/dist/fixtures/starship/crew/kestrel-vance.md +15 -0
- package/dist/fixtures/starship/crew/quill-vasquez.md +14 -0
- package/dist/fixtures/starship/crew/soren-halloway.md +16 -0
- package/dist/fixtures/starship/encounters/the-drift-choir.md +14 -0
- package/dist/fixtures/starship/encounters/the-hollow-signal.md +16 -0
- package/dist/fixtures/starship/encounters/the-wandering-buoy.md +14 -0
- package/dist/fixtures/starship/equipment/coolant-loop-b.md +13 -0
- package/dist/fixtures/starship/equipment/emergency-ration-bars.md +12 -0
- package/dist/fixtures/starship/equipment/long-range-sensors.md +13 -0
- package/dist/fixtures/starship/equipment/plasma-manifold-3.md +15 -0
- package/dist/fixtures/starship/equipment/shuttle-corvid.md +13 -0
- package/dist/fixtures/starship/logs/marsh-4419-1.md +13 -0
- package/dist/fixtures/starship/logs/okonkwo-4415-1.md +13 -0
- package/dist/fixtures/starship/logs/orr-4413-1.md +13 -0
- package/dist/fixtures/starship/logs/orr-4420-1.md +12 -0
- package/dist/fixtures/starship/logs/thorne-4413-2.md +13 -0
- package/dist/fixtures/starship/logs/vance-4419-2.md +13 -0
- package/dist/fixtures/starship/misc/the-galley-ficus.md +11 -0
- package/dist/fixtures/starship/missions/the-cinder-run.md +17 -0
- package/dist/fixtures/starship/missions/the-drift-choir.md +17 -0
- package/dist/fixtures/starship/missions/the-hollow-signal.md +17 -0
- package/dist/fixtures/starship/missions/the-silent-beacon.md +17 -0
- package/dist/fixtures/starship/moc/crew.md +19 -0
- package/dist/fixtures/starship/moc/encounters.md +14 -0
- package/dist/fixtures/starship/moc/missions.md +15 -0
- package/dist/fixtures/starship/readme.md +60 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/auth/glob.d.ts +46 -0
- package/dist/kernel/auth/glob.js +112 -0
- package/dist/kernel/auth/glob.js.map +1 -0
- package/dist/kernel/auth/scope.d.ts +53 -0
- package/dist/kernel/auth/scope.js +83 -0
- package/dist/kernel/auth/scope.js.map +1 -0
- package/dist/kernel/casefold.d.ts +37 -0
- package/dist/kernel/casefold.js +44 -0
- package/dist/kernel/casefold.js.map +1 -0
- package/dist/kernel/constants.d.ts +21 -0
- package/dist/kernel/constants.js +22 -0
- package/dist/kernel/constants.js.map +1 -0
- package/dist/kernel/context.d.ts +70 -0
- package/dist/kernel/context.js +114 -0
- package/dist/kernel/context.js.map +1 -0
- package/dist/kernel/deletion.d.ts +50 -0
- package/dist/kernel/deletion.js +83 -0
- package/dist/kernel/deletion.js.map +1 -0
- package/dist/kernel/diff.d.ts +36 -0
- package/dist/kernel/diff.js +61 -0
- package/dist/kernel/diff.js.map +1 -0
- package/dist/kernel/errors.d.ts +37 -0
- package/dist/kernel/errors.js +55 -0
- package/dist/kernel/errors.js.map +1 -0
- package/dist/kernel/frontmatter-input.d.ts +32 -0
- package/dist/kernel/frontmatter-input.js +85 -0
- package/dist/kernel/frontmatter-input.js.map +1 -0
- package/dist/kernel/graph.d.ts +34 -0
- package/dist/kernel/graph.js +559 -0
- package/dist/kernel/graph.js.map +1 -0
- package/dist/kernel/history.d.ts +74 -0
- package/dist/kernel/history.js +143 -0
- package/dist/kernel/history.js.map +1 -0
- package/dist/kernel/kernel.d.ts +162 -0
- package/dist/kernel/kernel.js +675 -0
- package/dist/kernel/kernel.js.map +1 -0
- package/dist/kernel/path-config.d.ts +101 -0
- package/dist/kernel/path-config.js +171 -0
- package/dist/kernel/path-config.js.map +1 -0
- package/dist/kernel/query/ast.d.ts +24 -0
- package/dist/kernel/query/ast.js +52 -0
- package/dist/kernel/query/ast.js.map +1 -0
- package/dist/kernel/query/cel-parse.d.ts +37 -0
- package/dist/kernel/query/cel-parse.js +88 -0
- package/dist/kernel/query/cel-parse.js.map +1 -0
- package/dist/kernel/query/degrees.d.ts +31 -0
- package/dist/kernel/query/degrees.js +135 -0
- package/dist/kernel/query/degrees.js.map +1 -0
- package/dist/kernel/query/graph-ast.d.ts +51 -0
- package/dist/kernel/query/graph-ast.js +139 -0
- package/dist/kernel/query/graph-ast.js.map +1 -0
- package/dist/kernel/query/query.d.ts +56 -0
- package/dist/kernel/query/query.js +276 -0
- package/dist/kernel/query/query.js.map +1 -0
- package/dist/kernel/validation.d.ts +64 -0
- package/dist/kernel/validation.js +183 -0
- package/dist/kernel/validation.js.map +1 -0
- package/dist/kernel/version-id.d.ts +10 -0
- package/dist/kernel/version-id.js +21 -0
- package/dist/kernel/version-id.js.map +1 -0
- package/dist/kernel/wire.d.ts +148 -0
- package/dist/kernel/wire.js +6 -0
- package/dist/kernel/wire.js.map +1 -0
- package/dist/links/backfill.d.ts +28 -0
- package/dist/links/backfill.js +33 -0
- package/dist/links/backfill.js.map +1 -0
- package/dist/links/extract.d.ts +29 -0
- package/dist/links/extract.js +233 -0
- package/dist/links/extract.js.map +1 -0
- package/dist/links/link-config.d.ts +70 -0
- package/dist/links/link-config.js +107 -0
- package/dist/links/link-config.js.map +1 -0
- package/dist/links/maintain.d.ts +37 -0
- package/dist/links/maintain.js +91 -0
- package/dist/links/maintain.js.map +1 -0
- package/dist/links/repair.d.ts +46 -0
- package/dist/links/repair.js +96 -0
- package/dist/links/repair.js.map +1 -0
- package/dist/links/resolve.d.ts +41 -0
- package/dist/links/resolve.js +121 -0
- package/dist/links/resolve.js.map +1 -0
- package/dist/links/stale.d.ts +32 -0
- package/dist/links/stale.js +73 -0
- package/dist/links/stale.js.map +1 -0
- package/dist/markdown/content-hash.d.ts +34 -0
- package/dist/markdown/content-hash.js +46 -0
- package/dist/markdown/content-hash.js.map +1 -0
- package/dist/markdown/frontmatter.d.ts +69 -0
- package/dist/markdown/frontmatter.js +147 -0
- package/dist/markdown/frontmatter.js.map +1 -0
- package/dist/markdown/hash-backfill.d.ts +22 -0
- package/dist/markdown/hash-backfill.js +43 -0
- package/dist/markdown/hash-backfill.js.map +1 -0
- package/dist/mcp/query-syntax.d.ts +20 -0
- package/dist/mcp/query-syntax.js +194 -0
- package/dist/mcp/query-syntax.js.map +1 -0
- package/dist/mcp/render.d.ts +31 -0
- package/dist/mcp/render.js +110 -0
- package/dist/mcp/render.js.map +1 -0
- package/dist/mcp/server.d.ts +60 -0
- package/dist/mcp/server.js +170 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +93 -0
- package/dist/mcp/tools.js +1218 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/rest/conditional.d.ts +37 -0
- package/dist/rest/conditional.js +58 -0
- package/dist/rest/conditional.js.map +1 -0
- package/dist/rest/negotiate.d.ts +33 -0
- package/dist/rest/negotiate.js +54 -0
- package/dist/rest/negotiate.js.map +1 -0
- package/dist/rest/routes.d.ts +37 -0
- package/dist/rest/routes.js +744 -0
- package/dist/rest/routes.js.map +1 -0
- package/dist/seed/fixture-seed.d.ts +35 -0
- package/dist/seed/fixture-seed.js +75 -0
- package/dist/seed/fixture-seed.js.map +1 -0
- package/dist/seed/starship-config.d.ts +3 -0
- package/dist/seed/starship-config.js +5 -0
- package/dist/seed/starship-config.js.map +1 -0
- package/dist/server/headers.d.ts +36 -0
- package/dist/server/headers.js +30 -0
- package/dist/server/headers.js.map +1 -0
- package/dist/server/http-error.d.ts +53 -0
- package/dist/server/http-error.js +104 -0
- package/dist/server/http-error.js.map +1 -0
- package/dist/server/serve.d.ts +56 -0
- package/dist/server/serve.js +135 -0
- package/dist/server/serve.js.map +1 -0
- package/dist/shell/audit.d.ts +32 -0
- package/dist/shell/audit.js +28 -0
- package/dist/shell/audit.js.map +1 -0
- package/dist/shell/guard.d.ts +50 -0
- package/dist/shell/guard.js +176 -0
- package/dist/shell/guard.js.map +1 -0
- package/dist/shell/keys.d.ts +36 -0
- package/dist/shell/keys.js +64 -0
- package/dist/shell/keys.js.map +1 -0
- package/dist/shell/login.d.ts +56 -0
- package/dist/shell/login.js +130 -0
- package/dist/shell/login.js.map +1 -0
- package/dist/shell/oidc.d.ts +62 -0
- package/dist/shell/oidc.js +102 -0
- package/dist/shell/oidc.js.map +1 -0
- package/dist/shell/policy.d.ts +100 -0
- package/dist/shell/policy.js +232 -0
- package/dist/shell/policy.js.map +1 -0
- package/dist/shell/proxy-policy.d.ts +34 -0
- package/dist/shell/proxy-policy.js +120 -0
- package/dist/shell/proxy-policy.js.map +1 -0
- package/dist/shell/proxy.d.ts +47 -0
- package/dist/shell/proxy.js +253 -0
- package/dist/shell/proxy.js.map +1 -0
- package/dist/shell/serve.d.ts +86 -0
- package/dist/shell/serve.js +0 -0
- package/dist/shell/serve.js.map +1 -0
- package/dist/shell/stdio.d.ts +55 -0
- package/dist/shell/stdio.js +63 -0
- package/dist/shell/stdio.js.map +1 -0
- package/dist/storage/registry.d.ts +20 -0
- package/dist/storage/registry.js +38 -0
- package/dist/storage/registry.js.map +1 -0
- package/dist/storage/search-plan.d.ts +93 -0
- package/dist/storage/search-plan.js +15 -0
- package/dist/storage/search-plan.js.map +1 -0
- package/dist/storage/types.d.ts +448 -0
- package/dist/storage/types.js +14 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/storage/versions-since.d.ts +72 -0
- package/dist/storage/versions-since.js +92 -0
- package/dist/storage/versions-since.js.map +1 -0
- package/dist/storage-postgres/adapter.d.ts +17 -0
- package/dist/storage-postgres/adapter.js +782 -0
- package/dist/storage-postgres/adapter.js.map +1 -0
- package/dist/storage-postgres/compile-postgres.d.ts +22 -0
- package/dist/storage-postgres/compile-postgres.js +830 -0
- package/dist/storage-postgres/compile-postgres.js.map +1 -0
- package/dist/storage-postgres/errors.d.ts +34 -0
- package/dist/storage-postgres/errors.js +50 -0
- package/dist/storage-postgres/errors.js.map +1 -0
- package/dist/storage-postgres/migrations/0001_init.sql +113 -0
- package/dist/storage-postgres/migrations/0002_content_hash.sql +7 -0
- package/dist/storage-postgres/migrations/index.d.ts +22 -0
- package/dist/storage-postgres/migrations/index.js +70 -0
- package/dist/storage-postgres/migrations/index.js.map +1 -0
- package/dist/storage-sqlite/adapter.d.ts +2 -0
- package/dist/storage-sqlite/adapter.js +688 -0
- package/dist/storage-sqlite/adapter.js.map +1 -0
- package/dist/storage-sqlite/compile-filter.d.ts +41 -0
- package/dist/storage-sqlite/compile-filter.js +881 -0
- package/dist/storage-sqlite/compile-filter.js.map +1 -0
- package/dist/storage-sqlite/compile-sqlite.d.ts +19 -0
- package/dist/storage-sqlite/compile-sqlite.js +176 -0
- package/dist/storage-sqlite/compile-sqlite.js.map +1 -0
- package/dist/storage-sqlite/migrations/0001_init.sql +123 -0
- package/dist/storage-sqlite/migrations/0002_content_hash.sql +7 -0
- package/dist/storage-sqlite/migrations/index.d.ts +6 -0
- package/dist/storage-sqlite/migrations/index.js +41 -0
- package/dist/storage-sqlite/migrations/index.js.map +1 -0
- package/dist/storage-sqlite/vec.d.ts +40 -0
- package/dist/storage-sqlite/vec.js +62 -0
- package/dist/storage-sqlite/vec.js.map +1 -0
- package/dist/sync/cursor.d.ts +36 -0
- package/dist/sync/cursor.js +66 -0
- package/dist/sync/cursor.js.map +1 -0
- package/dist/sync/daemon.d.ts +40 -0
- package/dist/sync/daemon.js +202 -0
- package/dist/sync/daemon.js.map +1 -0
- package/dist/sync/feed.d.ts +38 -0
- package/dist/sync/feed.js +125 -0
- package/dist/sync/feed.js.map +1 -0
- package/dist/sync/fs-store.d.ts +8 -0
- package/dist/sync/fs-store.js +76 -0
- package/dist/sync/fs-store.js.map +1 -0
- package/dist/sync/intrinsics.d.ts +52 -0
- package/dist/sync/intrinsics.js +76 -0
- package/dist/sync/intrinsics.js.map +1 -0
- package/dist/sync/paths.d.ts +38 -0
- package/dist/sync/paths.js +56 -0
- package/dist/sync/paths.js.map +1 -0
- package/dist/sync/push.d.ts +48 -0
- package/dist/sync/push.js +328 -0
- package/dist/sync/push.js.map +1 -0
- package/dist/sync/reconcile.d.ts +54 -0
- package/dist/sync/reconcile.js +252 -0
- package/dist/sync/reconcile.js.map +1 -0
- package/dist/sync/run.d.ts +27 -0
- package/dist/sync/run.js +46 -0
- package/dist/sync/run.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mrplex links repair — rewrite stale link text so the written destination
|
|
3
|
+
* matches the target's current path (design §11.2 "Link rewriting is
|
|
4
|
+
* cosmetic, not structural — and opt-in").
|
|
5
|
+
*
|
|
6
|
+
* The graph is identity-bound, so a move breaks nothing structurally; this
|
|
7
|
+
* only repairs the visible text. Each source document is rewritten as an
|
|
8
|
+
* ordinary optimistic `docs.put` under the caller's token — `prev` checks
|
|
9
|
+
* apply, conflicts are reported and skipped, and every repair is a normal
|
|
10
|
+
* authored version in the chain (NOT a silent side effect of the move).
|
|
11
|
+
*
|
|
12
|
+
* Rewriting is surgical: only the destination spans of stale edges are
|
|
13
|
+
* spliced (right-to-left so earlier offsets stay valid); the rest of the
|
|
14
|
+
* body is byte-identical. Only inline/wikilink edges with a captured
|
|
15
|
+
* destination span are rewritten; reference-definition links are left for a
|
|
16
|
+
* later phase.
|
|
17
|
+
*/
|
|
18
|
+
import { normalizeKey } from "../kernel/casefold.js";
|
|
19
|
+
import { extractEdges } from "./extract.js";
|
|
20
|
+
import { normalizeEdges } from "./resolve.js";
|
|
21
|
+
/**
|
|
22
|
+
* Compute the body rewrites needed to repair stale links across a repo's
|
|
23
|
+
* live documents. Pure w.r.t. storage reads — no writes. The caller issues
|
|
24
|
+
* the puts (so auth, conflict handling, and dry-run all live at the op
|
|
25
|
+
* layer, where the actor and kernel are in scope).
|
|
26
|
+
*/
|
|
27
|
+
export async function planRepairs(storage, repoId, config) {
|
|
28
|
+
const live = await storage.versions_live_by_repo(repoId);
|
|
29
|
+
const currentPathByDoc = new Map();
|
|
30
|
+
for (const v of live)
|
|
31
|
+
currentPathByDoc.set(v.document_id, v.path);
|
|
32
|
+
const plans = [];
|
|
33
|
+
for (const version of live) {
|
|
34
|
+
const stored = await storage.links_by_source(version.document_id);
|
|
35
|
+
if (stored.length === 0)
|
|
36
|
+
continue;
|
|
37
|
+
const raw = extractEdges({ body: version.body, frontmatter: version.frontmatter, config });
|
|
38
|
+
const normalized = normalizeEdges(raw, version.path, config);
|
|
39
|
+
// The stored rows correspond to the storable (non-external) edges in
|
|
40
|
+
// order; re-derive that alignment, keeping the RawEdge (for dest_span).
|
|
41
|
+
const storableRaw = [];
|
|
42
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
43
|
+
if (normalized[i].candidates.length > 0) {
|
|
44
|
+
storableRaw.push(raw[i]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Collect (span, replacement) rewrites for stale, span-bearing edges.
|
|
48
|
+
// Keyed by span start so multiple reference links sharing one `[id]: dest`
|
|
49
|
+
// definition produce a SINGLE rewrite (they resolve to the same target,
|
|
50
|
+
// so the replacement is identical) — splicing the shared span twice would
|
|
51
|
+
// corrupt the body.
|
|
52
|
+
const rewriteBySpan = new Map();
|
|
53
|
+
for (let i = 0; i < stored.length && i < storableRaw.length; i++) {
|
|
54
|
+
const row = stored[i];
|
|
55
|
+
const edge = storableRaw[i];
|
|
56
|
+
if (!row || !edge)
|
|
57
|
+
continue;
|
|
58
|
+
if (row.target_id === null)
|
|
59
|
+
continue; // dangling — nothing to repair
|
|
60
|
+
const currentPath = currentPathByDoc.get(row.target_id);
|
|
61
|
+
if (currentPath === undefined)
|
|
62
|
+
continue; // target not live
|
|
63
|
+
// row.target_norm is already folded (maintain.ts), so compare directly.
|
|
64
|
+
if (row.target_norm === normalizeKey(currentPath))
|
|
65
|
+
continue; // fresh
|
|
66
|
+
if (!edge.dest_span)
|
|
67
|
+
continue; // no rewritable span (shouldn't happen post-R4)
|
|
68
|
+
// Preserve any anchor the writer had on the destination.
|
|
69
|
+
const anchorIx = edge.target.indexOf("#");
|
|
70
|
+
const anchor = anchorIx >= 0 ? edge.target.slice(anchorIx) : "";
|
|
71
|
+
// Wikilinks are written without the .md extension by convention; keep
|
|
72
|
+
// that shape. Inline / reference links get the full repo-relative path.
|
|
73
|
+
const replacement = edge.wikilink ? stripMd(currentPath) + anchor : currentPath + anchor;
|
|
74
|
+
rewriteBySpan.set(edge.dest_span.start, {
|
|
75
|
+
start: edge.dest_span.start,
|
|
76
|
+
end: edge.dest_span.end,
|
|
77
|
+
text: replacement,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const rewrites = [...rewriteBySpan.values()];
|
|
81
|
+
if (rewrites.length === 0)
|
|
82
|
+
continue;
|
|
83
|
+
// Splice right-to-left so earlier offsets remain valid.
|
|
84
|
+
rewrites.sort((a, b) => b.start - a.start);
|
|
85
|
+
let body = version.body;
|
|
86
|
+
for (const r of rewrites) {
|
|
87
|
+
body = body.slice(0, r.start) + r.text + body.slice(r.end);
|
|
88
|
+
}
|
|
89
|
+
plans.push({ version, newBody: body, edges: rewrites.length });
|
|
90
|
+
}
|
|
91
|
+
return plans;
|
|
92
|
+
}
|
|
93
|
+
function stripMd(path) {
|
|
94
|
+
return path.endsWith(".md") ? path.slice(0, -3) : path;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=repair.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/links/repair.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target normalization — design §11.2 "Path resolution rules" (WS2 half).
|
|
3
|
+
*
|
|
4
|
+
* Turns a RawEdge's as-written target into:
|
|
5
|
+
* • target_raw — the repo-root-relative path stored in the links table,
|
|
6
|
+
* anchor preserved (this is what `links repair` compares
|
|
7
|
+
* against and what dangling re-resolution keys on)
|
|
8
|
+
* • candidates — ordered repo-root-relative *paths* (anchor stripped) to
|
|
9
|
+
* try against the live path set; the first that resolves
|
|
10
|
+
* to a document binds the edge (WS3)
|
|
11
|
+
*
|
|
12
|
+
* Paths are stored without a leading slash (e.g. "people/alice.md"), so
|
|
13
|
+
* "repo-absolute" here means repo-root-relative in that same form. Binding
|
|
14
|
+
* a candidate to a document id is WS3's job (resolve-against-live-paths);
|
|
15
|
+
* this module is pure and has no storage dependency.
|
|
16
|
+
*
|
|
17
|
+
* External targets (a URI scheme like https:, or a bare fragment "#x")
|
|
18
|
+
* produce no candidates — they never enter the graph (links are repo-local,
|
|
19
|
+
* §11.2).
|
|
20
|
+
*/
|
|
21
|
+
import type { RawEdge } from "./extract.js";
|
|
22
|
+
import type { LinkConfig } from "./link-config.js";
|
|
23
|
+
export type NormalizedEdge = {
|
|
24
|
+
ord: number;
|
|
25
|
+
field: string;
|
|
26
|
+
/** Repo-root-relative, anchor preserved. Stored as links.target_raw. */
|
|
27
|
+
target_raw: string;
|
|
28
|
+
/**
|
|
29
|
+
* Ordered candidate paths (no anchor) to resolve against live paths.
|
|
30
|
+
* Empty = external / unresolvable target (never binds, never dangles).
|
|
31
|
+
*/
|
|
32
|
+
candidates: string[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Normalize one raw edge against its source document's path and the repo's
|
|
36
|
+
* link config. `srcPath` is the repo-root-relative path of the document the
|
|
37
|
+
* edge originates from (e.g. "moc/employees.md").
|
|
38
|
+
*/
|
|
39
|
+
export declare function normalizeTarget(edge: RawEdge, srcPath: string, config: LinkConfig): NormalizedEdge;
|
|
40
|
+
/** Convenience: normalize a whole edge list for one source document. */
|
|
41
|
+
export declare function normalizeEdges(edges: readonly RawEdge[], srcPath: string, config: LinkConfig): NormalizedEdge[];
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target normalization — design §11.2 "Path resolution rules" (WS2 half).
|
|
3
|
+
*
|
|
4
|
+
* Turns a RawEdge's as-written target into:
|
|
5
|
+
* • target_raw — the repo-root-relative path stored in the links table,
|
|
6
|
+
* anchor preserved (this is what `links repair` compares
|
|
7
|
+
* against and what dangling re-resolution keys on)
|
|
8
|
+
* • candidates — ordered repo-root-relative *paths* (anchor stripped) to
|
|
9
|
+
* try against the live path set; the first that resolves
|
|
10
|
+
* to a document binds the edge (WS3)
|
|
11
|
+
*
|
|
12
|
+
* Paths are stored without a leading slash (e.g. "people/alice.md"), so
|
|
13
|
+
* "repo-absolute" here means repo-root-relative in that same form. Binding
|
|
14
|
+
* a candidate to a document id is WS3's job (resolve-against-live-paths);
|
|
15
|
+
* this module is pure and has no storage dependency.
|
|
16
|
+
*
|
|
17
|
+
* External targets (a URI scheme like https:, or a bare fragment "#x")
|
|
18
|
+
* produce no candidates — they never enter the graph (links are repo-local,
|
|
19
|
+
* §11.2).
|
|
20
|
+
*/
|
|
21
|
+
const SCHEME = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
22
|
+
/**
|
|
23
|
+
* Normalize one raw edge against its source document's path and the repo's
|
|
24
|
+
* link config. `srcPath` is the repo-root-relative path of the document the
|
|
25
|
+
* edge originates from (e.g. "moc/employees.md").
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeTarget(edge, srcPath, config) {
|
|
28
|
+
const base = { ord: edge.ord, field: edge.field };
|
|
29
|
+
// Split off the anchor. `preserve_anchors` keeps it on target_raw; the
|
|
30
|
+
// path used for resolution never includes it.
|
|
31
|
+
const hashIx = edge.target.indexOf("#");
|
|
32
|
+
const hasAnchor = hashIx >= 0;
|
|
33
|
+
const rawPath = hasAnchor ? edge.target.slice(0, hashIx) : edge.target;
|
|
34
|
+
const anchor = hasAnchor ? edge.target.slice(hashIx) : "";
|
|
35
|
+
const keepAnchor = config.resolution.preserve_anchors ? anchor : "";
|
|
36
|
+
// A bare fragment ("#section") is a same-document link — no edge target.
|
|
37
|
+
if (rawPath.length === 0) {
|
|
38
|
+
return { ...base, target_raw: edge.target, candidates: [] };
|
|
39
|
+
}
|
|
40
|
+
// External URIs (https:, mailto:, etc.) are repo-external — dropped.
|
|
41
|
+
if (SCHEME.test(rawPath)) {
|
|
42
|
+
return { ...base, target_raw: edge.target, candidates: [] };
|
|
43
|
+
}
|
|
44
|
+
if (edge.wikilink) {
|
|
45
|
+
return normalizeWikilink(base, rawPath, keepAnchor, config);
|
|
46
|
+
}
|
|
47
|
+
return normalizeCommonMark(base, rawPath, keepAnchor, srcPath);
|
|
48
|
+
}
|
|
49
|
+
/** Convenience: normalize a whole edge list for one source document. */
|
|
50
|
+
export function normalizeEdges(edges, srcPath, config) {
|
|
51
|
+
return edges.map((e) => normalizeTarget(e, srcPath, config));
|
|
52
|
+
}
|
|
53
|
+
// -----------------------------------------------------------------------------
|
|
54
|
+
// CommonMark: relative-to-source unless repo-absolute (leading '/').
|
|
55
|
+
// -----------------------------------------------------------------------------
|
|
56
|
+
function normalizeCommonMark(base, rawPath, anchor, srcPath) {
|
|
57
|
+
const resolved = rawPath.startsWith("/")
|
|
58
|
+
? normalizeSegments(rawPath.slice(1).split("/"))
|
|
59
|
+
: normalizeSegments([...dirSegments(srcPath), ...rawPath.split("/")]);
|
|
60
|
+
// Unresolvable (escaped above repo root via too many "..") → no candidate.
|
|
61
|
+
const candidates = resolved === null ? [] : [resolved];
|
|
62
|
+
const target_raw = resolved === null ? rawPath + anchor : resolved + anchor;
|
|
63
|
+
return { ...base, target_raw, candidates };
|
|
64
|
+
}
|
|
65
|
+
// -----------------------------------------------------------------------------
|
|
66
|
+
// Wikilinks: root-relative, with extension elision candidates.
|
|
67
|
+
// -----------------------------------------------------------------------------
|
|
68
|
+
function normalizeWikilink(base, rawPath, anchor, config) {
|
|
69
|
+
// Wikilinks are root-relative (Obsidian-style short links resolve from the
|
|
70
|
+
// vault root), leading slash tolerated.
|
|
71
|
+
const stripped = rawPath.startsWith("/") ? rawPath.slice(1) : rawPath;
|
|
72
|
+
const resolved = normalizeSegments(stripped.split("/"));
|
|
73
|
+
if (resolved === null) {
|
|
74
|
+
return { ...base, target_raw: rawPath + anchor, candidates: [] };
|
|
75
|
+
}
|
|
76
|
+
if (!config.resolution.wikilink_elision) {
|
|
77
|
+
return { ...base, target_raw: resolved + anchor, candidates: [resolved] };
|
|
78
|
+
}
|
|
79
|
+
// Elision: [[foo]] → foo.md → foo/<index>.md. Only an explicit `.md`
|
|
80
|
+
// suffix skips elision — every other bare name gets `.md` appended,
|
|
81
|
+
// including dotted daily-note names like `2024.01.01` (→ 2024.01.01.md).
|
|
82
|
+
// Deliberately NOT keyed on "contains a dot": that would strand daily
|
|
83
|
+
// notes. A non-`.md` extension (e.g. `[[diagram.png]]`) therefore also
|
|
84
|
+
// gets `.md` appended and simply dangles — mrplex documents are markdown
|
|
85
|
+
// (§2), and non-document attachments are inert to the graph regardless.
|
|
86
|
+
// target_raw records the primary (.md) candidate so `links repair` has a
|
|
87
|
+
// canonical written form (§11.2 table).
|
|
88
|
+
if (resolved.endsWith(".md")) {
|
|
89
|
+
return { ...base, target_raw: resolved + anchor, candidates: [resolved] };
|
|
90
|
+
}
|
|
91
|
+
const dotMd = `${resolved}.md`;
|
|
92
|
+
const indexMd = `${resolved}/${config.resolution.index_basename}.md`;
|
|
93
|
+
return { ...base, target_raw: dotMd + anchor, candidates: [dotMd, indexMd] };
|
|
94
|
+
}
|
|
95
|
+
// -----------------------------------------------------------------------------
|
|
96
|
+
// Segment normalization — collapse '.', apply '..', reject repo-root escape.
|
|
97
|
+
// -----------------------------------------------------------------------------
|
|
98
|
+
function normalizeSegments(segments) {
|
|
99
|
+
const out = [];
|
|
100
|
+
for (const seg of segments) {
|
|
101
|
+
if (seg === "" || seg === ".")
|
|
102
|
+
continue;
|
|
103
|
+
if (seg === "..") {
|
|
104
|
+
if (out.length === 0)
|
|
105
|
+
return null; // escaped above repo root
|
|
106
|
+
out.pop();
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
out.push(seg);
|
|
110
|
+
}
|
|
111
|
+
if (out.length === 0)
|
|
112
|
+
return null;
|
|
113
|
+
return out.join("/");
|
|
114
|
+
}
|
|
115
|
+
/** Directory segments of a repo-root-relative file path. */
|
|
116
|
+
function dirSegments(path) {
|
|
117
|
+
const segs = path.split("/");
|
|
118
|
+
segs.pop(); // drop the filename
|
|
119
|
+
return segs;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/links/resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* links.stale — find live documents whose written link text no longer
|
|
3
|
+
* matches the resolved target's current path (design §11.2 "Link rewriting
|
|
4
|
+
* is cosmetic"). A move is identity-bound, so the edge stays resolved but
|
|
5
|
+
* the *text* goes stale; this surfaces that gap so `mrplex links repair`
|
|
6
|
+
* can rewrite it.
|
|
7
|
+
*
|
|
8
|
+
* Staleness is defined only for RESOLVED edges (target_id not null) — a
|
|
9
|
+
* dangling edge has no current target path to disagree with. And only for
|
|
10
|
+
* body edges with a rewritable destination span; a reference-style link's
|
|
11
|
+
* destination lives in a separate definition (out of scope for Phase 1
|
|
12
|
+
* repair), and frontmatter edges aren't a "link text" in the body.
|
|
13
|
+
*/
|
|
14
|
+
import type { Storage } from "../storage/types.js";
|
|
15
|
+
import type { LinkConfig } from "./link-config.js";
|
|
16
|
+
export type StaleLink = {
|
|
17
|
+
source_id: number;
|
|
18
|
+
source_path: string;
|
|
19
|
+
ord: number;
|
|
20
|
+
written: string;
|
|
21
|
+
current: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Scan a repo's live documents for stale link text. Scope filtering is the
|
|
25
|
+
* caller's concern (the kernel op applies it); this walks the raw index.
|
|
26
|
+
*
|
|
27
|
+
* Algorithm: for each live source, re-extract its edges (to recover the
|
|
28
|
+
* per-edge destination span + written form), resolve them, and for each
|
|
29
|
+
* resolved edge compare the target's current path against what was written.
|
|
30
|
+
* Re-extraction keeps this consistent with exactly what repair will rewrite.
|
|
31
|
+
*/
|
|
32
|
+
export declare function findStaleLinks(storage: Storage, repoId: number, config: LinkConfig): Promise<StaleLink[]>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* links.stale — find live documents whose written link text no longer
|
|
3
|
+
* matches the resolved target's current path (design §11.2 "Link rewriting
|
|
4
|
+
* is cosmetic"). A move is identity-bound, so the edge stays resolved but
|
|
5
|
+
* the *text* goes stale; this surfaces that gap so `mrplex links repair`
|
|
6
|
+
* can rewrite it.
|
|
7
|
+
*
|
|
8
|
+
* Staleness is defined only for RESOLVED edges (target_id not null) — a
|
|
9
|
+
* dangling edge has no current target path to disagree with. And only for
|
|
10
|
+
* body edges with a rewritable destination span; a reference-style link's
|
|
11
|
+
* destination lives in a separate definition (out of scope for Phase 1
|
|
12
|
+
* repair), and frontmatter edges aren't a "link text" in the body.
|
|
13
|
+
*/
|
|
14
|
+
import { normalizeKey } from "../kernel/casefold.js";
|
|
15
|
+
import { extractEdges } from "./extract.js";
|
|
16
|
+
import { normalizeEdges } from "./resolve.js";
|
|
17
|
+
/**
|
|
18
|
+
* Scan a repo's live documents for stale link text. Scope filtering is the
|
|
19
|
+
* caller's concern (the kernel op applies it); this walks the raw index.
|
|
20
|
+
*
|
|
21
|
+
* Algorithm: for each live source, re-extract its edges (to recover the
|
|
22
|
+
* per-edge destination span + written form), resolve them, and for each
|
|
23
|
+
* resolved edge compare the target's current path against what was written.
|
|
24
|
+
* Re-extraction keeps this consistent with exactly what repair will rewrite.
|
|
25
|
+
*/
|
|
26
|
+
export async function findStaleLinks(storage, repoId, config) {
|
|
27
|
+
const out = [];
|
|
28
|
+
const live = await storage.versions_live_by_repo(repoId);
|
|
29
|
+
// Map document_id → current path for O(1) target lookups.
|
|
30
|
+
const currentPathByDoc = new Map();
|
|
31
|
+
for (const v of live)
|
|
32
|
+
currentPathByDoc.set(v.document_id, v.path);
|
|
33
|
+
for (const version of live) {
|
|
34
|
+
const stored = await storage.links_by_source(version.document_id);
|
|
35
|
+
if (stored.length === 0)
|
|
36
|
+
continue;
|
|
37
|
+
// Re-extract to recover destination spans + the written target string.
|
|
38
|
+
const raw = extractEdges({ body: version.body, frontmatter: version.frontmatter, config });
|
|
39
|
+
const normalized = normalizeEdges(raw, version.path, config);
|
|
40
|
+
// Align stored rows (dense ord) with the re-extracted edges that
|
|
41
|
+
// produced them (external edges were dropped during storage, so filter
|
|
42
|
+
// the normalized list the same way maintain.ts does).
|
|
43
|
+
const storable = normalized.filter((e) => e.candidates.length > 0);
|
|
44
|
+
for (let i = 0; i < stored.length && i < storable.length; i++) {
|
|
45
|
+
const row = stored[i];
|
|
46
|
+
const edge = storable[i];
|
|
47
|
+
if (!row || !edge)
|
|
48
|
+
continue;
|
|
49
|
+
if (row.target_id === null)
|
|
50
|
+
continue; // dangling: no current path to compare
|
|
51
|
+
const currentTargetPath = currentPathByDoc.get(row.target_id);
|
|
52
|
+
if (currentTargetPath === undefined)
|
|
53
|
+
continue; // target not live (deleted)
|
|
54
|
+
// The written target resolves to the target's OLD path; compare its
|
|
55
|
+
// folded form against the target's CURRENT folded path. A mismatch is
|
|
56
|
+
// stale text. Comparison is case-insensitive (identity is folded), so
|
|
57
|
+
// a pure recasing of the target isn't flagged unless the path changed.
|
|
58
|
+
// row.target_norm is already folded (maintain.ts stores it), so
|
|
59
|
+
// compare it directly against the folded current path.
|
|
60
|
+
if (row.target_norm !== normalizeKey(currentTargetPath)) {
|
|
61
|
+
out.push({
|
|
62
|
+
source_id: version.document_id,
|
|
63
|
+
source_path: version.path,
|
|
64
|
+
ord: row.ord,
|
|
65
|
+
written: row.target_raw,
|
|
66
|
+
current: currentTargetPath,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=stale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale.js","sourceRoot":"","sources":["../../src/links/stale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAU9C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAgB,EAChB,MAAc,EACd,MAAkB;IAElB,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACzD,0DAA0D;IAC1D,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACnD,KAAK,MAAM,CAAC,IAAI,IAAI;QAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAElE,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAClE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAElC,uEAAuE;QACvE,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3F,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAE7D,iEAAiE;QACjE,uEAAuE;QACvE,sDAAsD;QACtD,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAEnE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACtB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI;gBAAE,SAAS;YAC5B,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI;gBAAE,SAAS,CAAC,uCAAuC;YAE7E,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC9D,IAAI,iBAAiB,KAAK,SAAS;gBAAE,SAAS,CAAC,4BAA4B;YAE3E,oEAAoE;YACpE,sEAAsE;YACtE,sEAAsE;YACtE,uEAAuE;YACvE,gEAAgE;YAChE,uDAAuD;YACvD,IAAI,GAAG,CAAC,WAAW,KAAK,YAAY,CAAC,iBAAiB,CAAC,EAAE,CAAC;gBACxD,GAAG,CAAC,IAAI,CAAC;oBACP,SAAS,EAAE,OAAO,CAAC,WAAW;oBAC9B,WAAW,EAAE,OAAO,CAAC,IAAI;oBACzB,GAAG,EAAE,GAAG,CAAC,GAAG;oBACZ,OAAO,EAAE,GAAG,CAAC,UAAU;oBACvB,OAAO,EAAE,iBAAiB;iBAC3B,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `$content_hash` — the canonical content fingerprint (sync/history plan §2).
|
|
3
|
+
*
|
|
4
|
+
* A derived, server-owned property: the SHA-256 (bare lowercase hex, no
|
|
5
|
+
* algorithm prefix) of a version's canonical content —
|
|
6
|
+
* `join({ frontmatter_raw, body })` where `frontmatter_raw` has already been
|
|
7
|
+
* stripped of `$*` system lines. The hash excludes all intrinsics and the
|
|
8
|
+
* document path (a pure move does not change it).
|
|
9
|
+
*
|
|
10
|
+
* The server and every client must produce byte-identical input, which is why
|
|
11
|
+
* the hash lives in this one shared module rather than parallel
|
|
12
|
+
* implementations. Three byte-exactness traps it honors (§2.1):
|
|
13
|
+
*
|
|
14
|
+
* 1. Empty-frontmatter collapse — route through `join`, which collapses the
|
|
15
|
+
* empty block, so `frontmatter_raw === ""` hashes bare `<body>`.
|
|
16
|
+
* 2. Trailing-newline normalization — `join` forces the frontmatter block to
|
|
17
|
+
* end in `\n`; a local file lacking it normalizes identically.
|
|
18
|
+
* 3. Line endings — the delimiter grammar is LF-based; CRLF is normalized to
|
|
19
|
+
* LF before hashing.
|
|
20
|
+
*/
|
|
21
|
+
/** Canonical bytes hashed for `$content_hash`. */
|
|
22
|
+
export declare function canonicalContent(frontmatterRaw: string, body: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Hash a version's canonical content. `frontmatterRaw` must already be the
|
|
25
|
+
* stored raw (stripped of `$*` system lines by `canonicalizeFrontmatter`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function contentHash(frontmatterRaw: string, body: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Hash a whole file as the server would after storing it. Normalizes CRLF→LF,
|
|
30
|
+
* strips embedded `$*` lines via the same split/strip/join path so a file
|
|
31
|
+
* carrying intrinsics from a prior materialization hashes to the same value as
|
|
32
|
+
* its stored version.
|
|
33
|
+
*/
|
|
34
|
+
export declare function contentHashOfFile(text: string): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `$content_hash` — the canonical content fingerprint (sync/history plan §2).
|
|
3
|
+
*
|
|
4
|
+
* A derived, server-owned property: the SHA-256 (bare lowercase hex, no
|
|
5
|
+
* algorithm prefix) of a version's canonical content —
|
|
6
|
+
* `join({ frontmatter_raw, body })` where `frontmatter_raw` has already been
|
|
7
|
+
* stripped of `$*` system lines. The hash excludes all intrinsics and the
|
|
8
|
+
* document path (a pure move does not change it).
|
|
9
|
+
*
|
|
10
|
+
* The server and every client must produce byte-identical input, which is why
|
|
11
|
+
* the hash lives in this one shared module rather than parallel
|
|
12
|
+
* implementations. Three byte-exactness traps it honors (§2.1):
|
|
13
|
+
*
|
|
14
|
+
* 1. Empty-frontmatter collapse — route through `join`, which collapses the
|
|
15
|
+
* empty block, so `frontmatter_raw === ""` hashes bare `<body>`.
|
|
16
|
+
* 2. Trailing-newline normalization — `join` forces the frontmatter block to
|
|
17
|
+
* end in `\n`; a local file lacking it normalizes identically.
|
|
18
|
+
* 3. Line endings — the delimiter grammar is LF-based; CRLF is normalized to
|
|
19
|
+
* LF before hashing.
|
|
20
|
+
*/
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import { extractSystemProperties, join, split } from "./frontmatter.js";
|
|
23
|
+
/** Canonical bytes hashed for `$content_hash`. */
|
|
24
|
+
export function canonicalContent(frontmatterRaw, body) {
|
|
25
|
+
return join({ frontmatter_raw: frontmatterRaw, body });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Hash a version's canonical content. `frontmatterRaw` must already be the
|
|
29
|
+
* stored raw (stripped of `$*` system lines by `canonicalizeFrontmatter`).
|
|
30
|
+
*/
|
|
31
|
+
export function contentHash(frontmatterRaw, body) {
|
|
32
|
+
return createHash("sha256").update(canonicalContent(frontmatterRaw, body), "utf8").digest("hex"); // bare hex
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Hash a whole file as the server would after storing it. Normalizes CRLF→LF,
|
|
36
|
+
* strips embedded `$*` lines via the same split/strip/join path so a file
|
|
37
|
+
* carrying intrinsics from a prior materialization hashes to the same value as
|
|
38
|
+
* its stored version.
|
|
39
|
+
*/
|
|
40
|
+
export function contentHashOfFile(text) {
|
|
41
|
+
const lf = text.replace(/\r\n/g, "\n");
|
|
42
|
+
const { frontmatter_raw, body } = split(lf);
|
|
43
|
+
const stripped = extractSystemProperties(frontmatter_raw).raw;
|
|
44
|
+
return contentHash(stripped, body);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=content-hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-hash.js","sourceRoot":"","sources":["../../src/markdown/content-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAExE,kDAAkD;AAClD,MAAM,UAAU,gBAAgB,CAAC,cAAsB,EAAE,IAAY;IACnE,OAAO,IAAI,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,cAAsB,EAAE,IAAY;IAC9D,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW;AAC/G,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,uBAAuB,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC;IAC9D,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split, parse, and join Markdown files with YAML frontmatter.
|
|
3
|
+
*
|
|
4
|
+
* The design (§3.2) stores frontmatter twice: `frontmatter_raw` (byte-verbatim
|
|
5
|
+
* YAML source) and `frontmatter` (parsed JSON, a derived query index). The
|
|
6
|
+
* utilities here are the source of truth for that split/join contract.
|
|
7
|
+
*
|
|
8
|
+
* The delimiter grammar we recognize is deliberately narrow and LF-based:
|
|
9
|
+
*
|
|
10
|
+
* ---LF
|
|
11
|
+
* <raw YAML>
|
|
12
|
+
* ---LF
|
|
13
|
+
* <body>
|
|
14
|
+
*
|
|
15
|
+
* That is, a file has frontmatter iff it starts with the literal `---\n` AND a
|
|
16
|
+
* subsequent line consisting of exactly `---\n` (or `---` at EOF). Files that
|
|
17
|
+
* don't match are treated as pure body, with `frontmatter_raw = ""`. Round-trip
|
|
18
|
+
* (`join(split(x)) === x`) holds exactly for the canonical form; a
|
|
19
|
+
* present-but-empty frontmatter (`---\n---\n<body>`) collapses to no-frontmatter
|
|
20
|
+
* (semantically equivalent — `parse("") === {}`).
|
|
21
|
+
*/
|
|
22
|
+
export type Split = {
|
|
23
|
+
frontmatter_raw: string;
|
|
24
|
+
body: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Split a raw Markdown file into `{ frontmatter_raw, body }`. No parsing yet —
|
|
28
|
+
* `frontmatter_raw` is the exact YAML source between the delimiters.
|
|
29
|
+
*/
|
|
30
|
+
export declare function split(text: string): Split;
|
|
31
|
+
/**
|
|
32
|
+
* Join back to the canonical file form. Inverse of `split` for canonical inputs.
|
|
33
|
+
*/
|
|
34
|
+
export declare function join(parts: Split): string;
|
|
35
|
+
/** Value stored in `versions.frontmatter` — parsed frontmatter as JSON. */
|
|
36
|
+
export type FrontmatterJson = Record<string, unknown>;
|
|
37
|
+
export declare class FrontmatterInvalidError extends Error {
|
|
38
|
+
constructor(message: string);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse the raw YAML source into a JSON object. Empty raw → `{}`. Anything that
|
|
42
|
+
* doesn't parse to a plain map throws `FrontmatterInvalidError` (surfaced as
|
|
43
|
+
* kernel error `frontmatter_invalid` in M1 writes, per design §4.3).
|
|
44
|
+
*/
|
|
45
|
+
export declare function parse(frontmatterRaw: string): FrontmatterJson;
|
|
46
|
+
/**
|
|
47
|
+
* Append a system property (`$key: value`) as a plain text line to raw YAML
|
|
48
|
+
* source. Non-destructive — never round-trips through the YAML parser, so
|
|
49
|
+
* existing comments, key order, and whitespace are preserved byte-exact.
|
|
50
|
+
*
|
|
51
|
+
* `value` must be a simple scalar (string / number / boolean). Strings are
|
|
52
|
+
* emitted unquoted; callers are responsible for supplying values that are
|
|
53
|
+
* safe as bare YAML scalars (all current system props — version ids, ISO
|
|
54
|
+
* timestamps, user slugs — satisfy this).
|
|
55
|
+
*/
|
|
56
|
+
export declare function appendSystemProperty(frontmatterRaw: string, key: string, value: string | number | boolean): string;
|
|
57
|
+
/**
|
|
58
|
+
* Strip every `$key: value` line from raw YAML source and return the parsed
|
|
59
|
+
* key→value map alongside the cleaned source. Non-destructive on the
|
|
60
|
+
* remainder — only the matched lines are removed, everything else stays
|
|
61
|
+
* byte-exact.
|
|
62
|
+
*
|
|
63
|
+
* Values are returned as trimmed strings (leading/trailing whitespace after
|
|
64
|
+
* the colon removed). Callers coerce as needed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function extractSystemProperties(frontmatterRaw: string): {
|
|
67
|
+
raw: string;
|
|
68
|
+
props: Record<string, string>;
|
|
69
|
+
};
|