@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,38 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { DocIndex } from '../index/doc-index.js';
|
|
5
|
+
export function materializeCorpusRepo(corpusPath) {
|
|
6
|
+
if (!fs.existsSync(corpusPath)) {
|
|
7
|
+
throw new Error(`corpus directory not found: ${corpusPath}`);
|
|
8
|
+
}
|
|
9
|
+
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'repo-mind-ab-demo-'));
|
|
10
|
+
const docsDir = path.join(workDir, 'docs');
|
|
11
|
+
fs.cpSync(corpusPath, docsDir, { recursive: true });
|
|
12
|
+
return workDir;
|
|
13
|
+
}
|
|
14
|
+
export function validateCorpusAgainstQuestions(corpusPath, questions) {
|
|
15
|
+
const workDir = materializeCorpusRepo(corpusPath);
|
|
16
|
+
try {
|
|
17
|
+
const index = new DocIndex(workDir);
|
|
18
|
+
const docs = index.refresh();
|
|
19
|
+
const slugSet = new Set(docs.map((doc) => doc.slug));
|
|
20
|
+
const missingAnchors = [];
|
|
21
|
+
for (const question of questions) {
|
|
22
|
+
for (const slug of question.anchorSlugs) {
|
|
23
|
+
if (!slugSet.has(slug)) {
|
|
24
|
+
missingAnchors.push({ questionId: question.id, slug });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
corpusPath,
|
|
30
|
+
docCount: docs.length,
|
|
31
|
+
questionCount: questions.length,
|
|
32
|
+
missingAnchors,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DocIndex } from '../index/doc-index.js';
|
|
2
|
+
export interface CheckViolation {
|
|
3
|
+
path: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CheckReport {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
violations: CheckViolation[];
|
|
9
|
+
warnings: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function collectCheckReport(index: DocIndex): CheckReport | null;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { resolveAssetRelativePath } from '../index/resolve-asset-href.js';
|
|
4
|
+
import { parseWikilinkTargets, resolveWikilinkTarget } from '../index/link-index.js';
|
|
5
|
+
import { domainFromPath } from '../index/path-inference.js';
|
|
6
|
+
import { assetExists } from '../ui/serve-asset.js';
|
|
7
|
+
import { DOC_DOMAINS, DOC_STATUSES, DOC_TYPES, isDocDomain, isDocStatus, isDocType, } from '../index/types.js';
|
|
8
|
+
const ORPHAN_WORKTREE_DAYS = 7;
|
|
9
|
+
export function collectCheckReport(index) {
|
|
10
|
+
const knowledgeRoot = index.getKnowledgeRoot();
|
|
11
|
+
if (!knowledgeRoot) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const violations = [];
|
|
15
|
+
const warnings = [];
|
|
16
|
+
const docs = index.refresh();
|
|
17
|
+
const slugCounts = new Map();
|
|
18
|
+
const slugSet = new Set(docs.map((doc) => doc.slug));
|
|
19
|
+
for (const doc of docs) {
|
|
20
|
+
slugCounts.set(doc.slug, (slugCounts.get(doc.slug) ?? 0) + 1);
|
|
21
|
+
if (!isDocType(doc.frontmatter.type)) {
|
|
22
|
+
violations.push({
|
|
23
|
+
path: doc.path,
|
|
24
|
+
message: `invalid type "${String(doc.frontmatter.type)}" — expected one of: ${DOC_TYPES.join(', ')}`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!isDocStatus(doc.frontmatter.status)) {
|
|
28
|
+
violations.push({
|
|
29
|
+
path: doc.path,
|
|
30
|
+
message: `invalid status "${String(doc.frontmatter.status)}" — expected one of: ${DOC_STATUSES.join(', ')}`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (!doc.slug) {
|
|
34
|
+
violations.push({ path: doc.path, message: 'missing slug in frontmatter' });
|
|
35
|
+
}
|
|
36
|
+
if (doc.prepared && doc.frontmatter.domain !== undefined && !isDocDomain(doc.frontmatter.domain)) {
|
|
37
|
+
violations.push({
|
|
38
|
+
path: doc.path,
|
|
39
|
+
message: `invalid domain "${String(doc.frontmatter.domain)}" — expected one of: ${DOC_DOMAINS.join(', ')}`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (doc.prepared &&
|
|
43
|
+
doc.frontmatter.domain !== undefined &&
|
|
44
|
+
isDocDomain(doc.frontmatter.domain) &&
|
|
45
|
+
doc.frontmatter.domain !== domainFromPath(doc.relativePath) &&
|
|
46
|
+
domainFromPath(doc.relativePath) !== 'shared') {
|
|
47
|
+
warnings.push(`domain "${doc.frontmatter.domain}" in frontmatter does not match path domain "${domainFromPath(doc.relativePath)}" (${doc.relativePath})`);
|
|
48
|
+
}
|
|
49
|
+
for (const related of doc.related) {
|
|
50
|
+
if (!slugSet.has(related)) {
|
|
51
|
+
violations.push({
|
|
52
|
+
path: doc.path,
|
|
53
|
+
message: `broken related slug "${related}"`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (doc.contentKind !== 'markdown') {
|
|
58
|
+
if (doc.contentKind === 'json') {
|
|
59
|
+
try {
|
|
60
|
+
JSON.parse(doc.body);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
warnings.push(`invalid JSON syntax in ${doc.relativePath}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const lookups = {
|
|
69
|
+
slugSet,
|
|
70
|
+
titleToSlug: new Map(docs.flatMap((item) => [
|
|
71
|
+
[item.slug.toLowerCase(), item.slug],
|
|
72
|
+
[item.title.toLowerCase(), item.slug],
|
|
73
|
+
])),
|
|
74
|
+
};
|
|
75
|
+
for (const raw of parseWikilinkTargets(doc.body)) {
|
|
76
|
+
const resolved = resolveWikilinkTarget(raw, lookups);
|
|
77
|
+
if (resolved.broken) {
|
|
78
|
+
warnings.push(`broken wikilink [[${raw}]] in ${doc.relativePath}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const imagePattern = /!\[[^\]]*\]\(([^)]+)\)/g;
|
|
82
|
+
for (const match of doc.body.matchAll(imagePattern)) {
|
|
83
|
+
const href = match[1]?.trim() ?? '';
|
|
84
|
+
if (!href ||
|
|
85
|
+
href.startsWith('http://') ||
|
|
86
|
+
href.startsWith('https://') ||
|
|
87
|
+
href.startsWith('data:')) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const relative = resolveAssetRelativePath(doc.relativePath, href);
|
|
91
|
+
if (!relative) {
|
|
92
|
+
warnings.push(`unresolved image path "${href}" in ${doc.relativePath}`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!assetExists(knowledgeRoot, relative)) {
|
|
96
|
+
warnings.push(`missing image asset "${relative}" in ${doc.relativePath}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const [slug, count] of slugCounts) {
|
|
101
|
+
if (count > 1) {
|
|
102
|
+
violations.push({
|
|
103
|
+
path: knowledgeRoot,
|
|
104
|
+
message: `duplicate slug "${slug}" (${count} docs)`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const worktreesDir = path.join(knowledgeRoot, '.worktrees');
|
|
109
|
+
if (fs.existsSync(worktreesDir)) {
|
|
110
|
+
const cutoff = Date.now() - ORPHAN_WORKTREE_DAYS * 24 * 60 * 60 * 1000;
|
|
111
|
+
for (const entry of fs.readdirSync(worktreesDir, { withFileTypes: true })) {
|
|
112
|
+
if (!entry.isDirectory()) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const entryPath = path.join(worktreesDir, entry.name);
|
|
116
|
+
const stat = fs.statSync(entryPath);
|
|
117
|
+
if (stat.mtimeMs < cutoff) {
|
|
118
|
+
warnings.push(`orphaned worktree older than ${ORPHAN_WORKTREE_DAYS} days: ${entryPath}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
ok: violations.length === 0,
|
|
124
|
+
violations,
|
|
125
|
+
warnings,
|
|
126
|
+
};
|
|
127
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runCheck } from './commands/check.js';
|
|
3
|
+
import { runExport } from './commands/export.js';
|
|
4
|
+
import { runInit } from './commands/init.js';
|
|
5
|
+
import { runPrepare } from './commands/prepare.js';
|
|
6
|
+
import { runSetup } from './commands/setup.js';
|
|
7
|
+
import { runSyncLinks } from './commands/sync-links.js';
|
|
8
|
+
import { runUi } from './commands/ui.js';
|
|
9
|
+
import { startMcpServer } from './mcp/server.js';
|
|
10
|
+
function printHelp() {
|
|
11
|
+
console.log(`repo-mind — unified docs workspace for humans and AI agents
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
repo-mind init [--cwd <dir>]
|
|
15
|
+
repo-mind setup [--cursor] [--claude] [--force]
|
|
16
|
+
repo-mind check [--cwd <dir>]
|
|
17
|
+
repo-mind export [--force] [--cwd <dir>]
|
|
18
|
+
repo-mind prepare [--all] [--dry-run] [--cwd <dir>] [relative-path]
|
|
19
|
+
repo-mind sync-links [--dry-run] [--no-convert-body] [--no-sync-related] [--cwd <dir>]
|
|
20
|
+
repo-mind mcp
|
|
21
|
+
repo-mind ui [--port <n>] [--cwd <dir>]
|
|
22
|
+
|
|
23
|
+
Commands:
|
|
24
|
+
init Scaffold docs/ with example structured pages
|
|
25
|
+
setup Configure Cursor/Claude MCP and CLAUDE.md snippet
|
|
26
|
+
check Validate frontmatter schema and related links
|
|
27
|
+
export Write agents.md export to repo root
|
|
28
|
+
prepare Add RepoMind frontmatter to markdown files (--all for batch)
|
|
29
|
+
sync-links Convert markdown links to wikilinks and sync related frontmatter
|
|
30
|
+
mcp Start the MCP stdio server
|
|
31
|
+
ui Confluence-style workspace over docs/ (127.0.0.1)
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
function parseArgs(argv) {
|
|
35
|
+
const [command, ...rest] = argv;
|
|
36
|
+
const flags = {};
|
|
37
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
38
|
+
const arg = rest[i];
|
|
39
|
+
if (arg === '--all') {
|
|
40
|
+
flags.all = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === '--dry-run') {
|
|
44
|
+
flags.dryRun = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg === '--no-convert-body') {
|
|
48
|
+
flags.noConvertBody = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg === '--no-sync-related') {
|
|
52
|
+
flags.noSyncRelated = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === '--force') {
|
|
56
|
+
flags.force = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (arg === '--cursor') {
|
|
60
|
+
flags.cursor = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === '--claude') {
|
|
64
|
+
flags.claude = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (arg === '--cwd' && rest[i + 1]) {
|
|
68
|
+
flags.cwd = rest[i + 1];
|
|
69
|
+
i += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (arg === '--port' && rest[i + 1]) {
|
|
73
|
+
flags.port = rest[i + 1];
|
|
74
|
+
i += 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (arg === '--help' || arg === '-h') {
|
|
78
|
+
flags.help = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { command, flags };
|
|
82
|
+
}
|
|
83
|
+
async function main() {
|
|
84
|
+
const { command, flags } = parseArgs(process.argv.slice(2));
|
|
85
|
+
if (!command || flags.help) {
|
|
86
|
+
printHelp();
|
|
87
|
+
process.exit(command ? 0 : 1);
|
|
88
|
+
}
|
|
89
|
+
const cwd = typeof flags.cwd === 'string' ? flags.cwd : undefined;
|
|
90
|
+
const positionalPath = process.argv.slice(2).find((arg, index, argv) => index > 0 &&
|
|
91
|
+
!arg.startsWith('-') &&
|
|
92
|
+
argv[index - 1] !== '--cwd' &&
|
|
93
|
+
argv[index - 1] !== '--port' &&
|
|
94
|
+
arg !== argv[1]);
|
|
95
|
+
switch (command) {
|
|
96
|
+
case 'init':
|
|
97
|
+
process.exit(runInit({ cwd }));
|
|
98
|
+
break;
|
|
99
|
+
case 'setup':
|
|
100
|
+
process.exit(runSetup({
|
|
101
|
+
cwd,
|
|
102
|
+
cursor: flags.cursor === true,
|
|
103
|
+
claude: flags.claude === true,
|
|
104
|
+
force: flags.force === true,
|
|
105
|
+
}));
|
|
106
|
+
break;
|
|
107
|
+
case 'check':
|
|
108
|
+
process.exit(runCheck({ cwd }));
|
|
109
|
+
break;
|
|
110
|
+
case 'export':
|
|
111
|
+
process.exit(runExport({ cwd, force: flags.force === true }));
|
|
112
|
+
break;
|
|
113
|
+
case 'prepare':
|
|
114
|
+
process.exit(runPrepare({
|
|
115
|
+
cwd,
|
|
116
|
+
dryRun: flags.dryRun === true,
|
|
117
|
+
all: flags.all === true,
|
|
118
|
+
path: positionalPath,
|
|
119
|
+
}));
|
|
120
|
+
break;
|
|
121
|
+
case 'sync-links':
|
|
122
|
+
process.exit(runSyncLinks({
|
|
123
|
+
cwd,
|
|
124
|
+
dryRun: flags.dryRun === true,
|
|
125
|
+
convertBody: flags.noConvertBody !== true,
|
|
126
|
+
syncRelated: flags.noSyncRelated !== true,
|
|
127
|
+
}));
|
|
128
|
+
break;
|
|
129
|
+
case 'mcp':
|
|
130
|
+
await startMcpServer();
|
|
131
|
+
break;
|
|
132
|
+
case 'ui': {
|
|
133
|
+
const portRaw = typeof flags.port === 'string' ? Number.parseInt(flags.port, 10) : undefined;
|
|
134
|
+
const port = portRaw && !Number.isNaN(portRaw) ? portRaw : undefined;
|
|
135
|
+
process.exit(await runUi({ cwd, port }));
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
default:
|
|
139
|
+
console.error(`Unknown command: ${command}`);
|
|
140
|
+
printHelp();
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
main().catch((error) => {
|
|
145
|
+
console.error(error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { collectCheckReport } from '../check/collect-violations.js';
|
|
3
|
+
import { DocIndex } from '../index/doc-index.js';
|
|
4
|
+
export function runCheck(options = {}) {
|
|
5
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
6
|
+
const index = new DocIndex(cwd);
|
|
7
|
+
const report = collectCheckReport(index);
|
|
8
|
+
if (!report) {
|
|
9
|
+
console.error('no docs/ found — run `repo-mind init` or create a docs/ directory');
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
for (const violation of report.violations) {
|
|
13
|
+
console.error(`${violation.path}: ${violation.message}`);
|
|
14
|
+
}
|
|
15
|
+
for (const warning of report.warnings) {
|
|
16
|
+
console.warn(`warning: ${warning}`);
|
|
17
|
+
}
|
|
18
|
+
return report.ok ? 0 : 1;
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type DocType } from '../index/types.js';
|
|
2
|
+
export interface ExportOptions {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
force?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function runExport(options?: ExportOptions): number;
|
|
7
|
+
export declare function resolveExportPath(cwd: string): string;
|
|
8
|
+
export declare function typeDirFor(type: DocType): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { DocIndex } from '../index/doc-index.js';
|
|
4
|
+
import { DOC_TYPES, TYPE_TO_DIR } from '../index/types.js';
|
|
5
|
+
const TYPE_SECTIONS = {
|
|
6
|
+
adr: 'ADRs',
|
|
7
|
+
'feature-spec': 'Feature Specs',
|
|
8
|
+
'glossary-term': 'Glossary',
|
|
9
|
+
'open-question': 'Open Questions',
|
|
10
|
+
'agent-instruction': 'Agent Instructions',
|
|
11
|
+
'wiki-page': 'Wiki',
|
|
12
|
+
};
|
|
13
|
+
export function runExport(options = {}) {
|
|
14
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
15
|
+
const index = new DocIndex(cwd);
|
|
16
|
+
const knowledgeRoot = index.getKnowledgeRoot();
|
|
17
|
+
if (!knowledgeRoot) {
|
|
18
|
+
console.error('no docs/ found — run `repo-mind init` or create a docs/ directory');
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
const repoRoot = path.dirname(knowledgeRoot);
|
|
22
|
+
const outputPath = path.join(repoRoot, 'agents.md');
|
|
23
|
+
if (fs.existsSync(outputPath) && !options.force) {
|
|
24
|
+
console.error('agents.md already exists — pass --force to overwrite');
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
const docs = index.refresh();
|
|
28
|
+
const lines = ['# Project Knowledge Export', ''];
|
|
29
|
+
for (const type of DOC_TYPES) {
|
|
30
|
+
lines.push(`## ${TYPE_SECTIONS[type]}`, '');
|
|
31
|
+
const typeDocs = docs
|
|
32
|
+
.filter((doc) => doc.type === type)
|
|
33
|
+
.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
34
|
+
if (typeDocs.length === 0) {
|
|
35
|
+
lines.push('_No documents._', '');
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
for (const doc of typeDocs) {
|
|
39
|
+
lines.push(`### ${doc.title}`, '');
|
|
40
|
+
lines.push(`- slug: ${doc.slug}`);
|
|
41
|
+
lines.push(`- status: ${doc.status}`);
|
|
42
|
+
lines.push('');
|
|
43
|
+
lines.push('```yaml');
|
|
44
|
+
lines.push(renderFrontmatter(doc.frontmatter));
|
|
45
|
+
lines.push('```', '');
|
|
46
|
+
lines.push(doc.body, '');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
fs.writeFileSync(outputPath, `${lines.join('\n').trim()}\n`, 'utf8');
|
|
50
|
+
console.log(`Wrote ${outputPath}`);
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
function renderFrontmatter(frontmatter) {
|
|
54
|
+
const lines = [];
|
|
55
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
lines.push(`${key}:`);
|
|
61
|
+
for (const item of value) {
|
|
62
|
+
lines.push(` - ${item}`);
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
lines.push(`${key}: ${value}`);
|
|
67
|
+
}
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
|
70
|
+
export function resolveExportPath(cwd) {
|
|
71
|
+
const index = new DocIndex(cwd);
|
|
72
|
+
const knowledgeRoot = index.getKnowledgeRoot();
|
|
73
|
+
if (!knowledgeRoot) {
|
|
74
|
+
return path.join(cwd, 'agents.md');
|
|
75
|
+
}
|
|
76
|
+
return path.join(path.dirname(knowledgeRoot), 'agents.md');
|
|
77
|
+
}
|
|
78
|
+
export function typeDirFor(type) {
|
|
79
|
+
return TYPE_TO_DIR[type];
|
|
80
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { KNOWLEDGE_DIR } from '../index/doc-index.js';
|
|
5
|
+
import { DOC_DOMAINS, DOMAIN_LABELS, DOMAIN_TYPE_DIRS } from '../index/types.js';
|
|
6
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
7
|
+
const TEMPLATE_FILES = [
|
|
8
|
+
{ template: 'adr-example.md', target: 'technical/adr/use-plain-markdown.md' },
|
|
9
|
+
{ template: 'feature-spec-example.md', target: 'technical/specs/user-authentication.md' },
|
|
10
|
+
{ template: 'glossary-term-example.md', target: 'shared/glossary/mcp.md' },
|
|
11
|
+
{ template: 'open-question-example.md', target: 'product/open-questions/search-ranking.md' },
|
|
12
|
+
{ template: 'agent-instruction-example.md', target: 'shared/agents/query-first.md' },
|
|
13
|
+
{ template: 'combat-system-example.md', target: 'game-design/specs/combat-system.md' },
|
|
14
|
+
];
|
|
15
|
+
const KNOWLEDGE_README = `# Project documentation
|
|
16
|
+
|
|
17
|
+
This \`docs/\` directory is the single source of truth for project knowledge.
|
|
18
|
+
|
|
19
|
+
## Domains
|
|
20
|
+
|
|
21
|
+
| Domain | Purpose |
|
|
22
|
+
|--------|---------|
|
|
23
|
+
| \`product/\` | PRD, roadmap, user value, open product questions |
|
|
24
|
+
| \`technical/\` | Architecture, ADR, API, infrastructure |
|
|
25
|
+
| \`game-design/\` | Mechanics, balance, systems design |
|
|
26
|
+
| \`analytics/\` | Metrics, events, dashboards, experiments |
|
|
27
|
+
| \`art/\` | Visual style, assets, UI art guidelines |
|
|
28
|
+
| \`narrative/\` | Story, lore, dialogue, quests |
|
|
29
|
+
| \`ops/\` | Liveops, release, support runbooks |
|
|
30
|
+
| \`shared/\` | Cross-domain glossary, agent rules |
|
|
31
|
+
|
|
32
|
+
Each domain contains type subfolders (\`specs/\`, \`adr/\`, \`wiki/\`, …). See \`.cursor/skills/repomind-docs/structure.md\` for the full taxonomy.
|
|
33
|
+
|
|
34
|
+
Humans edit via \`repo-mind ui\`; agents query via MCP (\`search_docs\`, \`get_doc\`).
|
|
35
|
+
|
|
36
|
+
Run \`npx @justyork/repo-mind check\` to validate frontmatter, domains, and links.
|
|
37
|
+
`;
|
|
38
|
+
function domainReadme(domain) {
|
|
39
|
+
const label = DOMAIN_LABELS[domain];
|
|
40
|
+
const subdirs = DOMAIN_TYPE_DIRS[domain].map((d) => `- \`${d}/\``).join('\n');
|
|
41
|
+
return `# ${label}
|
|
42
|
+
|
|
43
|
+
Documentation domain: **${domain}**.
|
|
44
|
+
|
|
45
|
+
## Subfolders
|
|
46
|
+
|
|
47
|
+
${subdirs}
|
|
48
|
+
|
|
49
|
+
Add a \`README.md\` in each subfolder when it grows beyond a few pages.
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
const KNOWLEDGE_GITIGNORE = `.worktrees/
|
|
53
|
+
.repo-mind/
|
|
54
|
+
`;
|
|
55
|
+
export function runInit(options = {}) {
|
|
56
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
57
|
+
const knowledgeRoot = path.join(cwd, KNOWLEDGE_DIR);
|
|
58
|
+
if (!fs.existsSync(path.join(cwd, '.git'))) {
|
|
59
|
+
console.warn('warning: no .git directory found — initialize git for version control');
|
|
60
|
+
}
|
|
61
|
+
fs.mkdirSync(knowledgeRoot, { recursive: true });
|
|
62
|
+
fs.mkdirSync(path.join(knowledgeRoot, 'assets'), { recursive: true });
|
|
63
|
+
for (const domain of DOC_DOMAINS) {
|
|
64
|
+
const domainRoot = path.join(knowledgeRoot, domain);
|
|
65
|
+
fs.mkdirSync(domainRoot, { recursive: true });
|
|
66
|
+
writeIfMissing(path.join(domainRoot, 'README.md'), domainReadme(domain));
|
|
67
|
+
for (const subdir of DOMAIN_TYPE_DIRS[domain]) {
|
|
68
|
+
fs.mkdirSync(path.join(domainRoot, subdir), { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
writeIfMissing(path.join(knowledgeRoot, 'README.md'), KNOWLEDGE_README);
|
|
72
|
+
writeIfMissing(path.join(knowledgeRoot, '.gitignore'), KNOWLEDGE_GITIGNORE);
|
|
73
|
+
for (const { template, target } of TEMPLATE_FILES) {
|
|
74
|
+
const source = path.join(PACKAGE_ROOT, 'templates', template);
|
|
75
|
+
const dest = path.join(knowledgeRoot, target);
|
|
76
|
+
writeIfMissing(dest, fs.readFileSync(source, 'utf8'));
|
|
77
|
+
}
|
|
78
|
+
console.log(`Initialized ${KNOWLEDGE_DIR}/ with domain-based example docs.`);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
function writeIfMissing(filePath, content) {
|
|
82
|
+
if (!fs.existsSync(filePath)) {
|
|
83
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
84
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { DocIndex } from '../index/doc-index.js';
|
|
3
|
+
import { listUnpreparedFiles, prepareAllDocs, prepareDocFile, } from '../prepare/prepare-docs.js';
|
|
4
|
+
function printPrepareAllResult(result, dryRun) {
|
|
5
|
+
const verb = dryRun ? 'Would prepare' : 'Prepared';
|
|
6
|
+
for (const item of result.prepared) {
|
|
7
|
+
console.log(`${verb}: ${item.relativePath} → ${item.slug} (${item.type})`);
|
|
8
|
+
}
|
|
9
|
+
for (const item of result.skipped) {
|
|
10
|
+
console.error(`Skipped ${item.relativePath}: ${item.reason}`);
|
|
11
|
+
}
|
|
12
|
+
console.log(`${verb} ${result.prepared.length} file(s), skipped ${result.skipped.length}`);
|
|
13
|
+
}
|
|
14
|
+
export function runPrepare(options = {}) {
|
|
15
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
16
|
+
const index = new DocIndex(cwd);
|
|
17
|
+
if (!index.getKnowledgeRoot()) {
|
|
18
|
+
console.error('no docs/ found — run `repo-mind init` or create a docs/ directory');
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
if (options.all) {
|
|
22
|
+
const result = prepareAllDocs(index, { dryRun: options.dryRun });
|
|
23
|
+
printPrepareAllResult(result, options.dryRun === true);
|
|
24
|
+
return result.skipped.length > 0 ? 1 : 0;
|
|
25
|
+
}
|
|
26
|
+
if (options.path) {
|
|
27
|
+
const relativePath = options.path.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
28
|
+
if (options.dryRun) {
|
|
29
|
+
const match = listUnpreparedFiles(index).find((file) => file.relativePath === relativePath);
|
|
30
|
+
if (!match) {
|
|
31
|
+
console.error(`not unprepared or not found: ${relativePath}`);
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
console.log(`Would prepare: ${match.relativePath} → ${match.suggestedSlug} (${match.suggestedType})`);
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const result = prepareDocFile(index, relativePath);
|
|
39
|
+
console.log(`Prepared: ${result.relativePath} → ${result.slug} (${result.type})`);
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const unprepared = listUnpreparedFiles(index);
|
|
48
|
+
if (unprepared.length === 0) {
|
|
49
|
+
console.log('All markdown docs already have frontmatter.');
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
console.log(`${unprepared.length} unprepared markdown file(s).`);
|
|
53
|
+
console.log('Run with --all to add frontmatter, or pass a relative path under docs/.');
|
|
54
|
+
for (const file of unprepared.slice(0, 10)) {
|
|
55
|
+
console.log(` ${file.relativePath} → ${file.suggestedSlug} (${file.suggestedType})`);
|
|
56
|
+
}
|
|
57
|
+
if (unprepared.length > 10) {
|
|
58
|
+
console.log(` … and ${unprepared.length - 10} more`);
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const NPM_PACKAGE_NAME = "@justyork/repo-mind";
|
|
2
|
+
declare const MCP_SERVER_NAME = "repo-mind";
|
|
3
|
+
declare const CLAUDE_SNIPPET = "\n<!-- repo-mind -->\nProject knowledge lives in `docs/`. Use repo-mind MCP (`search_docs`, `get_doc`, `get_glossary_term`) \u2014 the same files humans edit in `repo-mind ui`.\n<!-- /repo-mind -->\n";
|
|
4
|
+
export interface SetupOptions {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
cursor?: boolean;
|
|
7
|
+
claude?: boolean;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function runSetup(options?: SetupOptions): number;
|
|
11
|
+
export { CLAUDE_SNIPPET, MCP_SERVER_NAME, NPM_PACKAGE_NAME };
|