@justyork/repo-mind 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/dist/ab-demo/arm-baseline.d.ts +8 -0
- package/dist/ab-demo/arm-baseline.js +40 -0
- package/dist/ab-demo/arm-repomind.d.ts +7 -0
- package/dist/ab-demo/arm-repomind.js +35 -0
- package/dist/ab-demo/estimate-tokens.d.ts +3 -0
- package/dist/ab-demo/estimate-tokens.js +10 -0
- package/dist/ab-demo/load-questions.d.ts +2 -0
- package/dist/ab-demo/load-questions.js +65 -0
- package/dist/ab-demo/paths.d.ts +5 -0
- package/dist/ab-demo/paths.js +31 -0
- package/dist/ab-demo/run-ab.d.ts +7 -0
- package/dist/ab-demo/run-ab.js +128 -0
- package/dist/ab-demo/run-arms.d.ts +3 -0
- package/dist/ab-demo/run-arms.js +67 -0
- package/dist/ab-demo/session-overhead.d.ts +3 -0
- package/dist/ab-demo/session-overhead.js +68 -0
- package/dist/ab-demo/types.d.ts +65 -0
- package/dist/ab-demo/types.js +1 -0
- package/dist/ab-demo/validate-corpus.d.ts +3 -0
- package/dist/ab-demo/validate-corpus.js +38 -0
- package/dist/check/collect-violations.d.ts +11 -0
- package/dist/check/collect-violations.js +127 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +147 -0
- package/dist/commands/check.d.ts +6 -0
- package/dist/commands/check.js +19 -0
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.js +80 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +86 -0
- package/dist/commands/prepare.d.ts +7 -0
- package/dist/commands/prepare.js +61 -0
- package/dist/commands/setup.d.ts +11 -0
- package/dist/commands/setup.js +84 -0
- package/dist/commands/sync-links.d.ts +7 -0
- package/dist/commands/sync-links.js +41 -0
- package/dist/commands/ui.d.ts +5 -0
- package/dist/commands/ui.js +83 -0
- package/dist/index/asset-file.d.ts +4 -0
- package/dist/index/asset-file.js +26 -0
- package/dist/index/doc-index.d.ts +21 -0
- package/dist/index/doc-index.js +231 -0
- package/dist/index/knowledge-file.d.ts +4 -0
- package/dist/index/knowledge-file.js +17 -0
- package/dist/index/link-index.d.ts +41 -0
- package/dist/index/link-index.js +150 -0
- package/dist/index/path-inference.d.ts +9 -0
- package/dist/index/path-inference.js +33 -0
- package/dist/index/resolve-asset-href.d.ts +2 -0
- package/dist/index/resolve-asset-href.js +65 -0
- package/dist/index/resolve-md-href.d.ts +7 -0
- package/dist/index/resolve-md-href.js +116 -0
- package/dist/index/slug.d.ts +5 -0
- package/dist/index/slug.js +43 -0
- package/dist/index/types.d.ts +44 -0
- package/dist/index/types.js +71 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +155 -0
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.js +15 -0
- package/dist/prepare/auto-links.d.ts +22 -0
- package/dist/prepare/auto-links.js +124 -0
- package/dist/prepare/prepare-docs.d.ts +36 -0
- package/dist/prepare/prepare-docs.js +106 -0
- package/dist/tools/explore-graph.d.ts +25 -0
- package/dist/tools/explore-graph.js +84 -0
- package/dist/tools/get-doc.d.ts +11 -0
- package/dist/tools/get-doc.js +21 -0
- package/dist/tools/get-glossary-term.d.ts +9 -0
- package/dist/tools/get-glossary-term.js +41 -0
- package/dist/tools/list-docs.d.ts +19 -0
- package/dist/tools/list-docs.js +35 -0
- package/dist/tools/search-docs.d.ts +14 -0
- package/dist/tools/search-docs.js +80 -0
- package/dist/ui/api-handlers.d.ts +12 -0
- package/dist/ui/api-handlers.js +223 -0
- package/dist/ui/catalog-meta.d.ts +4 -0
- package/dist/ui/catalog-meta.js +47 -0
- package/dist/ui/db/drafts-db.d.ts +49 -0
- package/dist/ui/db/drafts-db.js +179 -0
- package/dist/ui/diff.d.ts +8 -0
- package/dist/ui/diff.js +58 -0
- package/dist/ui/docs-watcher.d.ts +13 -0
- package/dist/ui/docs-watcher.js +59 -0
- package/dist/ui/draft-api.d.ts +7 -0
- package/dist/ui/draft-api.js +413 -0
- package/dist/ui/fs-operations.d.ts +52 -0
- package/dist/ui/fs-operations.js +304 -0
- package/dist/ui/fs-tree.d.ts +28 -0
- package/dist/ui/fs-tree.js +148 -0
- package/dist/ui/graph-all.d.ts +5 -0
- package/dist/ui/graph-all.js +50 -0
- package/dist/ui/link-cascade.d.ts +9 -0
- package/dist/ui/link-cascade.js +113 -0
- package/dist/ui/parse-multipart.d.ts +12 -0
- package/dist/ui/parse-multipart.js +64 -0
- package/dist/ui/publish.d.ts +14 -0
- package/dist/ui/publish.js +83 -0
- package/dist/ui/safe-path.d.ts +6 -0
- package/dist/ui/safe-path.js +42 -0
- package/dist/ui/serve-asset.d.ts +4 -0
- package/dist/ui/serve-asset.js +49 -0
- package/dist/ui/server.d.ts +17 -0
- package/dist/ui/server.js +237 -0
- package/dist/ui/stats.d.ts +9 -0
- package/dist/ui/stats.js +23 -0
- package/dist/ui/templates.d.ts +12 -0
- package/dist/ui/templates.js +39 -0
- package/dist/ui/upload-asset.d.ts +11 -0
- package/dist/ui/upload-asset.js +61 -0
- package/package.json +55 -0
- package/templates/adr-example.md +27 -0
- package/templates/agent-instruction-example.md +20 -0
- package/templates/combat-system-example.md +27 -0
- package/templates/feature-spec-example.md +27 -0
- package/templates/glossary-term-example.md +15 -0
- package/templates/open-question-example.md +26 -0
- package/ui/dist/assets/arc-DhC0JPue.js +1 -0
- package/ui/dist/assets/architectureDiagram-3BPJPVTR-Cun_Ijrv.js +36 -0
- package/ui/dist/assets/blockDiagram-GPEHLZMM-CgiNAArN.js +132 -0
- package/ui/dist/assets/c4Diagram-AAUBKEIU-BIwHcwcH.js +10 -0
- package/ui/dist/assets/channel-CNwAp9ic.js +1 -0
- package/ui/dist/assets/chunk-2J33WTMH-DXRgHPpp.js +1 -0
- package/ui/dist/assets/chunk-4BX2VUAB-BTb70kIb.js +1 -0
- package/ui/dist/assets/chunk-55IACEB6-BrAelyhX.js +1 -0
- package/ui/dist/assets/chunk-727SXJPM-BlYnlPdj.js +206 -0
- package/ui/dist/assets/chunk-AQP2D5EJ-DSPgdKZ8.js +231 -0
- package/ui/dist/assets/chunk-FMBD7UC4-BhH8ir2K.js +15 -0
- package/ui/dist/assets/chunk-ND2GUHAM-DCAuTSxB.js +1 -0
- package/ui/dist/assets/chunk-QZHKN3VN-DtYEkbYr.js +1 -0
- package/ui/dist/assets/classDiagram-4FO5ZUOK-DnHeGLmR.js +1 -0
- package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-DnHeGLmR.js +1 -0
- package/ui/dist/assets/cose-bilkent-S5V4N54A-CAM4jLYo.js +1 -0
- package/ui/dist/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/ui/dist/assets/dagre-BM42HDAG-CISbgani.js +4 -0
- package/ui/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/ui/dist/assets/diagram-2AECGRRQ-BmXargwF.js +43 -0
- package/ui/dist/assets/diagram-5GNKFQAL-COlrLu0O.js +10 -0
- package/ui/dist/assets/diagram-KO2AKTUF-B-kUxuHX.js +3 -0
- package/ui/dist/assets/diagram-LMA3HP47-C3AVVxcm.js +24 -0
- package/ui/dist/assets/diagram-OG6HWLK6-JHeftSsO.js +24 -0
- package/ui/dist/assets/erDiagram-TEJ5UH35-BSWwMysi.js +85 -0
- package/ui/dist/assets/flowDiagram-I6XJVG4X-D-q1cK69.js +162 -0
- package/ui/dist/assets/ganttDiagram-6RSMTGT7-DrYn1H_t.js +292 -0
- package/ui/dist/assets/gitGraphDiagram-PVQCEYII-vJByl99X.js +106 -0
- package/ui/dist/assets/graph-CAnANduQ.js +1 -0
- package/ui/dist/assets/graph-DwoitsWW.js +2 -0
- package/ui/dist/assets/infoDiagram-5YYISTIA-D6zhGTMj.js +2 -0
- package/ui/dist/assets/init-Gi6I4Gst.js +1 -0
- package/ui/dist/assets/ishikawaDiagram-YF4QCWOH-CY-U_l7l.js +70 -0
- package/ui/dist/assets/journeyDiagram-JHISSGLW-jKj4lBEJ.js +139 -0
- package/ui/dist/assets/kanban-definition-UN3LZRKU-PZ-5AYw2.js +89 -0
- package/ui/dist/assets/katex-C5jXJg4s.js +257 -0
- package/ui/dist/assets/layout-DGIYPm2g.js +1 -0
- package/ui/dist/assets/linear-COY9pyF4.js +1 -0
- package/ui/dist/assets/main-BBzCq-49.js +308 -0
- package/ui/dist/assets/mermaid.core-Bddhr0ku.js +309 -0
- package/ui/dist/assets/mindmap-definition-RKZ34NQL-CPY2Fdu_.js +96 -0
- package/ui/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/ui/dist/assets/pieDiagram-4H26LBE5-C7GJ49et.js +30 -0
- package/ui/dist/assets/quadrantDiagram-W4KKPZXB-DQyQN5K7.js +7 -0
- package/ui/dist/assets/requirementDiagram-4Y6WPE33-CDrkwz1t.js +84 -0
- package/ui/dist/assets/sankeyDiagram-5OEKKPKP-BrYb9Eql.js +40 -0
- package/ui/dist/assets/sequenceDiagram-3UESZ5HK-B8If_JZp.js +162 -0
- package/ui/dist/assets/stateDiagram-AJRCARHV-BbpTp9VX.js +1 -0
- package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-BT4PvMFS.js +1 -0
- package/ui/dist/assets/theme-DV7vqTnV.js +1 -0
- package/ui/dist/assets/theme-SpsWsRN5.css +1 -0
- package/ui/dist/assets/timeline-definition-PNZ67QCA-DhUg6aIV.js +120 -0
- package/ui/dist/assets/transform-BwXaE9hv.js +1 -0
- package/ui/dist/assets/vennDiagram-CIIHVFJN-DpQVNNzF.js +34 -0
- package/ui/dist/assets/wardley-L42UT6IY-CyaxzHGP.js +173 -0
- package/ui/dist/assets/wardleyDiagram-YWT4CUSO-Bm0mA7wm.js +78 -0
- package/ui/dist/assets/xychartDiagram-2RQKCTM6-OJbmgDx6.js +7 -0
- package/ui/dist/graph.html +27 -0
- package/ui/dist/index.html +37 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const WIKILINK_PATTERN = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g;
|
|
2
|
+
export function parseWikilinkTargets(body) {
|
|
3
|
+
const targets = [];
|
|
4
|
+
for (const match of body.matchAll(WIKILINK_PATTERN)) {
|
|
5
|
+
const display = match[1]?.trim() ?? '';
|
|
6
|
+
const slugPart = match[2]?.trim() ?? display;
|
|
7
|
+
if (slugPart) {
|
|
8
|
+
targets.push(slugPart);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return targets;
|
|
12
|
+
}
|
|
13
|
+
function buildSlugLookups(docs) {
|
|
14
|
+
const slugSet = new Set(docs.map((doc) => doc.slug));
|
|
15
|
+
const titleToSlug = new Map();
|
|
16
|
+
for (const doc of docs) {
|
|
17
|
+
titleToSlug.set(doc.slug.toLowerCase(), doc.slug);
|
|
18
|
+
titleToSlug.set(doc.title.toLowerCase(), doc.slug);
|
|
19
|
+
}
|
|
20
|
+
return { slugSet, titleToSlug };
|
|
21
|
+
}
|
|
22
|
+
export function resolveWikilinkTarget(raw, lookups) {
|
|
23
|
+
if (lookups.slugSet.has(raw)) {
|
|
24
|
+
return { slug: raw, broken: false };
|
|
25
|
+
}
|
|
26
|
+
const byKey = lookups.titleToSlug.get(raw.toLowerCase());
|
|
27
|
+
if (byKey) {
|
|
28
|
+
return { slug: byKey, broken: false };
|
|
29
|
+
}
|
|
30
|
+
return { slug: null, broken: true };
|
|
31
|
+
}
|
|
32
|
+
function addBacklink(backlinks, to, entry) {
|
|
33
|
+
const list = backlinks.get(to) ?? [];
|
|
34
|
+
const duplicate = list.some((item) => item.from === entry.from && item.kind === entry.kind);
|
|
35
|
+
if (!duplicate) {
|
|
36
|
+
list.push(entry);
|
|
37
|
+
backlinks.set(to, list);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function buildLinkIndex(docs, extraEdges = []) {
|
|
41
|
+
const lookups = buildSlugLookups(docs);
|
|
42
|
+
const edges = [...extraEdges];
|
|
43
|
+
const backlinks = new Map();
|
|
44
|
+
const brokenTargets = new Set();
|
|
45
|
+
function registerEdge(edge) {
|
|
46
|
+
edges.push(edge);
|
|
47
|
+
if (lookups.slugSet.has(edge.to)) {
|
|
48
|
+
addBacklink(backlinks, edge.to, { from: edge.from, kind: edge.kind });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const doc of docs) {
|
|
52
|
+
if (doc.contentKind !== 'markdown') {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
for (const related of doc.related) {
|
|
56
|
+
if (related === doc.slug) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const broken = !lookups.slugSet.has(related);
|
|
60
|
+
if (broken) {
|
|
61
|
+
brokenTargets.add(related);
|
|
62
|
+
}
|
|
63
|
+
registerEdge({
|
|
64
|
+
from: doc.slug,
|
|
65
|
+
to: related,
|
|
66
|
+
kind: 'related',
|
|
67
|
+
...(broken ? { rawTarget: related } : {}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
for (const raw of parseWikilinkTargets(doc.body)) {
|
|
71
|
+
const resolved = resolveWikilinkTarget(raw, lookups);
|
|
72
|
+
if (resolved.broken || !resolved.slug) {
|
|
73
|
+
brokenTargets.add(raw);
|
|
74
|
+
registerEdge({
|
|
75
|
+
from: doc.slug,
|
|
76
|
+
to: raw,
|
|
77
|
+
kind: 'wikilink',
|
|
78
|
+
rawTarget: raw,
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (resolved.slug === doc.slug) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
registerEdge({
|
|
86
|
+
from: doc.slug,
|
|
87
|
+
to: resolved.slug,
|
|
88
|
+
kind: 'wikilink',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const edge of extraEdges) {
|
|
93
|
+
if (lookups.slugSet.has(edge.to)) {
|
|
94
|
+
addBacklink(backlinks, edge.to, { from: edge.from, kind: edge.kind });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { edges, backlinks, brokenTargets };
|
|
98
|
+
}
|
|
99
|
+
export function getOutboundSlugs(snapshot, fromSlug) {
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
const result = [];
|
|
102
|
+
for (const edge of snapshot.edges) {
|
|
103
|
+
if (edge.from !== fromSlug || seen.has(edge.to)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
seen.add(edge.to);
|
|
107
|
+
result.push(edge.to);
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
export function computeLinkHealth(snapshot, docs) {
|
|
112
|
+
const inbound = new Set();
|
|
113
|
+
const outboundPairs = new Set();
|
|
114
|
+
const reversePairs = new Set();
|
|
115
|
+
for (const edge of snapshot.edges) {
|
|
116
|
+
if (edge.kind === 'parent_of') {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
inbound.add(edge.to);
|
|
120
|
+
outboundPairs.add(`${edge.from}\0${edge.to}`);
|
|
121
|
+
reversePairs.add(`${edge.to}\0${edge.from}`);
|
|
122
|
+
}
|
|
123
|
+
let oneWayCount = 0;
|
|
124
|
+
for (const pair of outboundPairs) {
|
|
125
|
+
if (!reversePairs.has(pair)) {
|
|
126
|
+
oneWayCount += 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const orphanSlugs = docs.length <= 1
|
|
130
|
+
? []
|
|
131
|
+
: docs
|
|
132
|
+
.filter((doc) => !inbound.has(doc.slug))
|
|
133
|
+
.map((doc) => doc.slug);
|
|
134
|
+
return {
|
|
135
|
+
orphanSlugs,
|
|
136
|
+
brokenTargets: [...snapshot.brokenTargets].sort(),
|
|
137
|
+
oneWayCount,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function getBacklinksForSlug(snapshot, slug, docsBySlug) {
|
|
141
|
+
const entries = snapshot.backlinks.get(slug) ?? [];
|
|
142
|
+
return entries.map((entry) => {
|
|
143
|
+
const doc = docsBySlug.get(entry.from);
|
|
144
|
+
return {
|
|
145
|
+
slug: entry.from,
|
|
146
|
+
title: doc?.title ?? entry.from,
|
|
147
|
+
kind: entry.kind,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DocDomain, type DocType } from './types.js';
|
|
2
|
+
/** Infers doc type from any path segment matching a known type folder. */
|
|
3
|
+
export declare function inferTypeFromRelative(relative: string): DocType;
|
|
4
|
+
/** Infers domain from the first path segment when it is a known domain id. */
|
|
5
|
+
export declare function inferDomainFromRelative(relative: string): DocDomain;
|
|
6
|
+
/** Resolves domain from frontmatter override or path inference. */
|
|
7
|
+
export declare function resolveDomain(relative: string, explicit: unknown): DocDomain;
|
|
8
|
+
/** Domain encoded in path (ignores frontmatter). */
|
|
9
|
+
export declare function domainFromPath(relative: string): DocDomain;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DIR_TO_TYPE, isDocDomain } from './types.js';
|
|
2
|
+
function normalizedSegments(relative) {
|
|
3
|
+
return relative.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
4
|
+
}
|
|
5
|
+
/** Infers doc type from any path segment matching a known type folder. */
|
|
6
|
+
export function inferTypeFromRelative(relative) {
|
|
7
|
+
for (const segment of normalizedSegments(relative)) {
|
|
8
|
+
const mapped = DIR_TO_TYPE[segment];
|
|
9
|
+
if (mapped) {
|
|
10
|
+
return mapped;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return 'wiki-page';
|
|
14
|
+
}
|
|
15
|
+
/** Infers domain from the first path segment when it is a known domain id. */
|
|
16
|
+
export function inferDomainFromRelative(relative) {
|
|
17
|
+
const [first] = normalizedSegments(relative);
|
|
18
|
+
if (first && isDocDomain(first)) {
|
|
19
|
+
return first;
|
|
20
|
+
}
|
|
21
|
+
return 'shared';
|
|
22
|
+
}
|
|
23
|
+
/** Resolves domain from frontmatter override or path inference. */
|
|
24
|
+
export function resolveDomain(relative, explicit) {
|
|
25
|
+
if (typeof explicit === 'string' && isDocDomain(explicit)) {
|
|
26
|
+
return explicit;
|
|
27
|
+
}
|
|
28
|
+
return inferDomainFromRelative(relative);
|
|
29
|
+
}
|
|
30
|
+
/** Domain encoded in path (ignores frontmatter). */
|
|
31
|
+
export function domainFromPath(relative) {
|
|
32
|
+
return inferDomainFromRelative(relative);
|
|
33
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isImageFileName } from './asset-file.js';
|
|
2
|
+
function stripLinkSuffix(href) {
|
|
3
|
+
return href.split('#')[0]?.split('?')[0]?.trim() ?? '';
|
|
4
|
+
}
|
|
5
|
+
function isExternalHref(href) {
|
|
6
|
+
return (href.startsWith('http://') ||
|
|
7
|
+
href.startsWith('https://') ||
|
|
8
|
+
href.startsWith('mailto:') ||
|
|
9
|
+
href.startsWith('#') ||
|
|
10
|
+
href.startsWith('data:'));
|
|
11
|
+
}
|
|
12
|
+
function resolveRelativePath(fromDocRelative, href) {
|
|
13
|
+
const pathPart = stripLinkSuffix(href);
|
|
14
|
+
if (!pathPart || isExternalHref(pathPart)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const fromDir = fromDocRelative.includes('/')
|
|
18
|
+
? fromDocRelative.slice(0, fromDocRelative.lastIndexOf('/'))
|
|
19
|
+
: '';
|
|
20
|
+
const segments = pathPart.replace(/\\/g, '/').split('/');
|
|
21
|
+
const stack = fromDir ? fromDir.split('/') : [];
|
|
22
|
+
for (const segment of segments) {
|
|
23
|
+
if (!segment || segment === '.') {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (segment === '..') {
|
|
27
|
+
if (stack.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
stack.pop();
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
stack.push(segment);
|
|
34
|
+
}
|
|
35
|
+
const resolved = stack.join('/');
|
|
36
|
+
return resolved || null;
|
|
37
|
+
}
|
|
38
|
+
function resolveDocsRootPath(href) {
|
|
39
|
+
let pathPart = stripLinkSuffix(href).replace(/\\/g, '/');
|
|
40
|
+
if (!pathPart || isExternalHref(pathPart) || pathPart.split('/').includes('..')) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (!pathPart.startsWith('/') && !pathPart.startsWith('docs/')) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (pathPart.startsWith('/')) {
|
|
47
|
+
pathPart = pathPart.slice(1);
|
|
48
|
+
}
|
|
49
|
+
if (pathPart.startsWith('docs/')) {
|
|
50
|
+
pathPart = pathPart.slice(5);
|
|
51
|
+
}
|
|
52
|
+
return pathPart || null;
|
|
53
|
+
}
|
|
54
|
+
/** Resolves an image/asset href to a path relative to the docs root. */
|
|
55
|
+
export function resolveAssetRelativePath(fromDocRelative, href) {
|
|
56
|
+
const docsRoot = resolveDocsRootPath(href);
|
|
57
|
+
if (docsRoot && isImageFileName(docsRoot)) {
|
|
58
|
+
return docsRoot;
|
|
59
|
+
}
|
|
60
|
+
const relative = resolveRelativePath(fromDocRelative, href);
|
|
61
|
+
if (relative && isImageFileName(relative)) {
|
|
62
|
+
return relative;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function readmeRelativePath(folderRelativePath: string): string;
|
|
2
|
+
export declare function parseSlugFromHref(href: string): string | null;
|
|
3
|
+
/** Resolves a markdown href relative to the current doc path under docs/. */
|
|
4
|
+
export declare function resolveMarkdownHref(fromDocRelative: string, href: string): string | null;
|
|
5
|
+
/** Resolves href as path under docs/ root (e.g. `docs/README.md`, `/adr/foo.md`). */
|
|
6
|
+
export declare function resolveDocsKnowledgeHref(href: string): string | null;
|
|
7
|
+
export declare function slugForMarkdownHref(fromDocRelative: string, href: string, slugByRelative: Map<string, string>): string | null;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export function readmeRelativePath(folderRelativePath) {
|
|
2
|
+
const base = folderRelativePath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
3
|
+
return base ? `${base}/README.md` : 'README.md';
|
|
4
|
+
}
|
|
5
|
+
export function parseSlugFromHref(href) {
|
|
6
|
+
if (!href) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
if (href.startsWith('?slug=') || href.startsWith('/?slug=')) {
|
|
10
|
+
const query = href.includes('?') ? href.slice(href.indexOf('?')) : href;
|
|
11
|
+
const slug = new URLSearchParams(query).get('slug');
|
|
12
|
+
return slug?.trim() || null;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(href, 'http://local');
|
|
16
|
+
const slug = url.searchParams.get('slug');
|
|
17
|
+
return slug?.trim() || null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function stripLinkSuffix(href) {
|
|
24
|
+
return href.split('#')[0]?.split('?')[0]?.trim() ?? '';
|
|
25
|
+
}
|
|
26
|
+
function normalizeMdPath(relative) {
|
|
27
|
+
const normalized = relative.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
28
|
+
if (!normalized) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
return normalized.toLowerCase().endsWith('.md') ? normalized : `${normalized}.md`;
|
|
32
|
+
}
|
|
33
|
+
/** Resolves a markdown href relative to the current doc path under docs/. */
|
|
34
|
+
export function resolveMarkdownHref(fromDocRelative, href) {
|
|
35
|
+
const pathPart = stripLinkSuffix(href);
|
|
36
|
+
if (!pathPart || pathPart.startsWith('http://') || pathPart.startsWith('https://')) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (pathPart.startsWith('mailto:') || pathPart.startsWith('#')) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const fromDir = fromDocRelative.includes('/')
|
|
43
|
+
? fromDocRelative.slice(0, fromDocRelative.lastIndexOf('/'))
|
|
44
|
+
: '';
|
|
45
|
+
const segments = pathPart.replace(/\\/g, '/').split('/');
|
|
46
|
+
const stack = fromDir ? fromDir.split('/') : [];
|
|
47
|
+
for (const segment of segments) {
|
|
48
|
+
if (!segment || segment === '.') {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (segment === '..') {
|
|
52
|
+
if (stack.length === 0) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
stack.pop();
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
stack.push(segment);
|
|
59
|
+
}
|
|
60
|
+
return normalizeMdPath(stack.join('/'));
|
|
61
|
+
}
|
|
62
|
+
/** Resolves href as path under docs/ root (e.g. `docs/README.md`, `/adr/foo.md`). */
|
|
63
|
+
export function resolveDocsKnowledgeHref(href) {
|
|
64
|
+
let pathPart = stripLinkSuffix(href).replace(/\\/g, '/');
|
|
65
|
+
if (!pathPart || pathPart.startsWith('http://') || pathPart.startsWith('https://')) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (pathPart.startsWith('mailto:') || pathPart.startsWith('#')) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (pathPart.startsWith('/')) {
|
|
72
|
+
pathPart = pathPart.slice(1);
|
|
73
|
+
}
|
|
74
|
+
if (pathPart.startsWith('docs/')) {
|
|
75
|
+
pathPart = pathPart.slice(5);
|
|
76
|
+
}
|
|
77
|
+
if (!pathPart) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return normalizeMdPath(pathPart);
|
|
81
|
+
}
|
|
82
|
+
function lookupSlugByRelative(relativePath, slugByRelative) {
|
|
83
|
+
const direct = slugByRelative.get(relativePath);
|
|
84
|
+
if (direct) {
|
|
85
|
+
return direct;
|
|
86
|
+
}
|
|
87
|
+
const lower = relativePath.toLowerCase();
|
|
88
|
+
for (const [key, slug] of slugByRelative.entries()) {
|
|
89
|
+
if (key.toLowerCase() === lower) {
|
|
90
|
+
return slug;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
export function slugForMarkdownHref(fromDocRelative, href, slugByRelative) {
|
|
96
|
+
const fromQuery = parseSlugFromHref(href);
|
|
97
|
+
if (fromQuery) {
|
|
98
|
+
for (const slug of slugByRelative.values()) {
|
|
99
|
+
if (slug === fromQuery) {
|
|
100
|
+
return fromQuery;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const candidates = [
|
|
106
|
+
resolveMarkdownHref(fromDocRelative, href),
|
|
107
|
+
resolveDocsKnowledgeHref(href),
|
|
108
|
+
].filter((value) => Boolean(value));
|
|
109
|
+
for (const resolved of candidates) {
|
|
110
|
+
const slug = lookupSlugByRelative(resolved, slugByRelative);
|
|
111
|
+
if (slug) {
|
|
112
|
+
return slug;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const SLUG_PATTERN: RegExp;
|
|
2
|
+
export declare function isValidSlug(slug: string): boolean;
|
|
3
|
+
/** Builds a stable slug from a path relative to the docs root. */
|
|
4
|
+
export declare function slugFromRelativePath(relativePath: string): string;
|
|
5
|
+
export declare function resolveDocPath(knowledgeRoot: string, typeDir: string, slug: string): string | null;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
3
|
+
export function isValidSlug(slug) {
|
|
4
|
+
return SLUG_PATTERN.test(slug);
|
|
5
|
+
}
|
|
6
|
+
/** Builds a stable slug from a path relative to the docs root. */
|
|
7
|
+
export function slugFromRelativePath(relativePath) {
|
|
8
|
+
const normalized = relativePath
|
|
9
|
+
.replace(/\\/g, '/')
|
|
10
|
+
.replace(/\.(md|ya?ml|json)$/i, '');
|
|
11
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
12
|
+
if (segments.length === 0) {
|
|
13
|
+
return 'untitled';
|
|
14
|
+
}
|
|
15
|
+
const candidates = [
|
|
16
|
+
segments.length === 1 ? segments[0] : segments.join('-'),
|
|
17
|
+
segments[segments.length - 1],
|
|
18
|
+
];
|
|
19
|
+
for (const raw of candidates) {
|
|
20
|
+
const slug = raw
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
23
|
+
.replace(/-+/g, '-')
|
|
24
|
+
.replace(/^-|-$/g, '');
|
|
25
|
+
if (isValidSlug(slug)) {
|
|
26
|
+
return slug;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return 'untitled';
|
|
30
|
+
}
|
|
31
|
+
export function resolveDocPath(knowledgeRoot, typeDir, slug) {
|
|
32
|
+
if (!isValidSlug(slug)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const candidate = path.resolve(knowledgeRoot, typeDir, `${slug}.md`);
|
|
36
|
+
const rootWithSep = knowledgeRoot.endsWith(path.sep)
|
|
37
|
+
? knowledgeRoot
|
|
38
|
+
: `${knowledgeRoot}${path.sep}`;
|
|
39
|
+
if (!candidate.startsWith(rootWithSep)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const DOC_TYPES: readonly ["adr", "feature-spec", "glossary-term", "open-question", "agent-instruction", "wiki-page"];
|
|
2
|
+
export type DocType = (typeof DOC_TYPES)[number];
|
|
3
|
+
export declare const DOC_STATUSES: readonly ["draft", "proposed", "accepted", "superseded"];
|
|
4
|
+
export type DocStatus = (typeof DOC_STATUSES)[number];
|
|
5
|
+
export type ContentKind = 'markdown' | 'yaml' | 'json';
|
|
6
|
+
/** Business area / documentation domain (orthogonal to doc `type`). */
|
|
7
|
+
export declare const DOC_DOMAINS: readonly ["product", "technical", "game-design", "analytics", "art", "narrative", "ops", "shared"];
|
|
8
|
+
export type DocDomain = (typeof DOC_DOMAINS)[number];
|
|
9
|
+
export declare const DOMAIN_LABELS: Record<DocDomain, string>;
|
|
10
|
+
/** Default type subfolders created under each domain by `repo-mind init`. */
|
|
11
|
+
export declare const DOMAIN_TYPE_DIRS: Record<DocDomain, readonly string[]>;
|
|
12
|
+
export declare const TYPE_TO_DIR: Record<DocType, string>;
|
|
13
|
+
export declare const DIR_TO_TYPE: Record<string, DocType>;
|
|
14
|
+
export interface DocFrontmatter {
|
|
15
|
+
type: DocType;
|
|
16
|
+
slug: string;
|
|
17
|
+
status: DocStatus;
|
|
18
|
+
/** Documentation domain — product, technical, game-design, etc. */
|
|
19
|
+
domain?: DocDomain;
|
|
20
|
+
title?: string;
|
|
21
|
+
tags?: string[];
|
|
22
|
+
related?: string[];
|
|
23
|
+
owner?: string;
|
|
24
|
+
updated?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface DocRecord {
|
|
27
|
+
path: string;
|
|
28
|
+
relativePath: string;
|
|
29
|
+
slug: string;
|
|
30
|
+
type: DocType;
|
|
31
|
+
domain: DocDomain;
|
|
32
|
+
status: DocStatus;
|
|
33
|
+
title: string;
|
|
34
|
+
tags: string[];
|
|
35
|
+
related: string[];
|
|
36
|
+
body: string;
|
|
37
|
+
frontmatter: DocFrontmatter;
|
|
38
|
+
/** True when the file has explicit RepoMind frontmatter (type field). */
|
|
39
|
+
prepared: boolean;
|
|
40
|
+
contentKind: ContentKind;
|
|
41
|
+
}
|
|
42
|
+
export declare function isDocType(value: unknown): value is DocType;
|
|
43
|
+
export declare function isDocStatus(value: unknown): value is DocStatus;
|
|
44
|
+
export declare function isDocDomain(value: unknown): value is DocDomain;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const DOC_TYPES = [
|
|
2
|
+
'adr',
|
|
3
|
+
'feature-spec',
|
|
4
|
+
'glossary-term',
|
|
5
|
+
'open-question',
|
|
6
|
+
'agent-instruction',
|
|
7
|
+
'wiki-page',
|
|
8
|
+
];
|
|
9
|
+
export const DOC_STATUSES = [
|
|
10
|
+
'draft',
|
|
11
|
+
'proposed',
|
|
12
|
+
'accepted',
|
|
13
|
+
'superseded',
|
|
14
|
+
];
|
|
15
|
+
/** Business area / documentation domain (orthogonal to doc `type`). */
|
|
16
|
+
export const DOC_DOMAINS = [
|
|
17
|
+
'product',
|
|
18
|
+
'technical',
|
|
19
|
+
'game-design',
|
|
20
|
+
'analytics',
|
|
21
|
+
'art',
|
|
22
|
+
'narrative',
|
|
23
|
+
'ops',
|
|
24
|
+
'shared',
|
|
25
|
+
];
|
|
26
|
+
export const DOMAIN_LABELS = {
|
|
27
|
+
product: 'Product',
|
|
28
|
+
technical: 'Technical',
|
|
29
|
+
'game-design': 'Game design',
|
|
30
|
+
analytics: 'Analytics',
|
|
31
|
+
art: 'Art',
|
|
32
|
+
narrative: 'Narrative',
|
|
33
|
+
ops: 'Operations',
|
|
34
|
+
shared: 'Shared',
|
|
35
|
+
};
|
|
36
|
+
/** Default type subfolders created under each domain by `repo-mind init`. */
|
|
37
|
+
export const DOMAIN_TYPE_DIRS = {
|
|
38
|
+
product: ['specs', 'wiki', 'open-questions'],
|
|
39
|
+
technical: ['adr', 'specs', 'glossary', 'wiki'],
|
|
40
|
+
'game-design': ['specs', 'glossary', 'wiki'],
|
|
41
|
+
analytics: ['specs', 'wiki'],
|
|
42
|
+
art: ['wiki', 'specs'],
|
|
43
|
+
narrative: ['wiki', 'specs'],
|
|
44
|
+
ops: ['specs', 'wiki'],
|
|
45
|
+
shared: ['agents', 'glossary', 'wiki'],
|
|
46
|
+
};
|
|
47
|
+
export const TYPE_TO_DIR = {
|
|
48
|
+
adr: 'adr',
|
|
49
|
+
'feature-spec': 'specs',
|
|
50
|
+
'glossary-term': 'glossary',
|
|
51
|
+
'open-question': 'open-questions',
|
|
52
|
+
'agent-instruction': 'agents',
|
|
53
|
+
'wiki-page': 'wiki',
|
|
54
|
+
};
|
|
55
|
+
export const DIR_TO_TYPE = {
|
|
56
|
+
adr: 'adr',
|
|
57
|
+
specs: 'feature-spec',
|
|
58
|
+
glossary: 'glossary-term',
|
|
59
|
+
'open-questions': 'open-question',
|
|
60
|
+
agents: 'agent-instruction',
|
|
61
|
+
wiki: 'wiki-page',
|
|
62
|
+
};
|
|
63
|
+
export function isDocType(value) {
|
|
64
|
+
return typeof value === 'string' && DOC_TYPES.includes(value);
|
|
65
|
+
}
|
|
66
|
+
export function isDocStatus(value) {
|
|
67
|
+
return typeof value === 'string' && DOC_STATUSES.includes(value);
|
|
68
|
+
}
|
|
69
|
+
export function isDocDomain(value) {
|
|
70
|
+
return typeof value === 'string' && DOC_DOMAINS.includes(value);
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startMcpServer(): Promise<void>;
|