@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.
Files changed (177) hide show
  1. package/README.md +110 -0
  2. package/dist/ab-demo/arm-baseline.d.ts +8 -0
  3. package/dist/ab-demo/arm-baseline.js +40 -0
  4. package/dist/ab-demo/arm-repomind.d.ts +7 -0
  5. package/dist/ab-demo/arm-repomind.js +35 -0
  6. package/dist/ab-demo/estimate-tokens.d.ts +3 -0
  7. package/dist/ab-demo/estimate-tokens.js +10 -0
  8. package/dist/ab-demo/load-questions.d.ts +2 -0
  9. package/dist/ab-demo/load-questions.js +65 -0
  10. package/dist/ab-demo/paths.d.ts +5 -0
  11. package/dist/ab-demo/paths.js +31 -0
  12. package/dist/ab-demo/run-ab.d.ts +7 -0
  13. package/dist/ab-demo/run-ab.js +128 -0
  14. package/dist/ab-demo/run-arms.d.ts +3 -0
  15. package/dist/ab-demo/run-arms.js +67 -0
  16. package/dist/ab-demo/session-overhead.d.ts +3 -0
  17. package/dist/ab-demo/session-overhead.js +68 -0
  18. package/dist/ab-demo/types.d.ts +65 -0
  19. package/dist/ab-demo/types.js +1 -0
  20. package/dist/ab-demo/validate-corpus.d.ts +3 -0
  21. package/dist/ab-demo/validate-corpus.js +38 -0
  22. package/dist/check/collect-violations.d.ts +11 -0
  23. package/dist/check/collect-violations.js +127 -0
  24. package/dist/cli.d.ts +2 -0
  25. package/dist/cli.js +147 -0
  26. package/dist/commands/check.d.ts +6 -0
  27. package/dist/commands/check.js +19 -0
  28. package/dist/commands/export.d.ts +8 -0
  29. package/dist/commands/export.js +80 -0
  30. package/dist/commands/init.d.ts +4 -0
  31. package/dist/commands/init.js +86 -0
  32. package/dist/commands/prepare.d.ts +7 -0
  33. package/dist/commands/prepare.js +61 -0
  34. package/dist/commands/setup.d.ts +11 -0
  35. package/dist/commands/setup.js +84 -0
  36. package/dist/commands/sync-links.d.ts +7 -0
  37. package/dist/commands/sync-links.js +41 -0
  38. package/dist/commands/ui.d.ts +5 -0
  39. package/dist/commands/ui.js +83 -0
  40. package/dist/index/asset-file.d.ts +4 -0
  41. package/dist/index/asset-file.js +26 -0
  42. package/dist/index/doc-index.d.ts +21 -0
  43. package/dist/index/doc-index.js +231 -0
  44. package/dist/index/knowledge-file.d.ts +4 -0
  45. package/dist/index/knowledge-file.js +17 -0
  46. package/dist/index/link-index.d.ts +41 -0
  47. package/dist/index/link-index.js +150 -0
  48. package/dist/index/path-inference.d.ts +9 -0
  49. package/dist/index/path-inference.js +33 -0
  50. package/dist/index/resolve-asset-href.d.ts +2 -0
  51. package/dist/index/resolve-asset-href.js +65 -0
  52. package/dist/index/resolve-md-href.d.ts +7 -0
  53. package/dist/index/resolve-md-href.js +116 -0
  54. package/dist/index/slug.d.ts +5 -0
  55. package/dist/index/slug.js +43 -0
  56. package/dist/index/types.d.ts +44 -0
  57. package/dist/index/types.js +71 -0
  58. package/dist/mcp/server.d.ts +1 -0
  59. package/dist/mcp/server.js +155 -0
  60. package/dist/package-version.d.ts +2 -0
  61. package/dist/package-version.js +15 -0
  62. package/dist/prepare/auto-links.d.ts +22 -0
  63. package/dist/prepare/auto-links.js +124 -0
  64. package/dist/prepare/prepare-docs.d.ts +36 -0
  65. package/dist/prepare/prepare-docs.js +106 -0
  66. package/dist/tools/explore-graph.d.ts +25 -0
  67. package/dist/tools/explore-graph.js +84 -0
  68. package/dist/tools/get-doc.d.ts +11 -0
  69. package/dist/tools/get-doc.js +21 -0
  70. package/dist/tools/get-glossary-term.d.ts +9 -0
  71. package/dist/tools/get-glossary-term.js +41 -0
  72. package/dist/tools/list-docs.d.ts +19 -0
  73. package/dist/tools/list-docs.js +35 -0
  74. package/dist/tools/search-docs.d.ts +14 -0
  75. package/dist/tools/search-docs.js +80 -0
  76. package/dist/ui/api-handlers.d.ts +12 -0
  77. package/dist/ui/api-handlers.js +223 -0
  78. package/dist/ui/catalog-meta.d.ts +4 -0
  79. package/dist/ui/catalog-meta.js +47 -0
  80. package/dist/ui/db/drafts-db.d.ts +49 -0
  81. package/dist/ui/db/drafts-db.js +179 -0
  82. package/dist/ui/diff.d.ts +8 -0
  83. package/dist/ui/diff.js +58 -0
  84. package/dist/ui/docs-watcher.d.ts +13 -0
  85. package/dist/ui/docs-watcher.js +59 -0
  86. package/dist/ui/draft-api.d.ts +7 -0
  87. package/dist/ui/draft-api.js +413 -0
  88. package/dist/ui/fs-operations.d.ts +52 -0
  89. package/dist/ui/fs-operations.js +304 -0
  90. package/dist/ui/fs-tree.d.ts +28 -0
  91. package/dist/ui/fs-tree.js +148 -0
  92. package/dist/ui/graph-all.d.ts +5 -0
  93. package/dist/ui/graph-all.js +50 -0
  94. package/dist/ui/link-cascade.d.ts +9 -0
  95. package/dist/ui/link-cascade.js +113 -0
  96. package/dist/ui/parse-multipart.d.ts +12 -0
  97. package/dist/ui/parse-multipart.js +64 -0
  98. package/dist/ui/publish.d.ts +14 -0
  99. package/dist/ui/publish.js +83 -0
  100. package/dist/ui/safe-path.d.ts +6 -0
  101. package/dist/ui/safe-path.js +42 -0
  102. package/dist/ui/serve-asset.d.ts +4 -0
  103. package/dist/ui/serve-asset.js +49 -0
  104. package/dist/ui/server.d.ts +17 -0
  105. package/dist/ui/server.js +237 -0
  106. package/dist/ui/stats.d.ts +9 -0
  107. package/dist/ui/stats.js +23 -0
  108. package/dist/ui/templates.d.ts +12 -0
  109. package/dist/ui/templates.js +39 -0
  110. package/dist/ui/upload-asset.d.ts +11 -0
  111. package/dist/ui/upload-asset.js +61 -0
  112. package/package.json +55 -0
  113. package/templates/adr-example.md +27 -0
  114. package/templates/agent-instruction-example.md +20 -0
  115. package/templates/combat-system-example.md +27 -0
  116. package/templates/feature-spec-example.md +27 -0
  117. package/templates/glossary-term-example.md +15 -0
  118. package/templates/open-question-example.md +26 -0
  119. package/ui/dist/assets/arc-DhC0JPue.js +1 -0
  120. package/ui/dist/assets/architectureDiagram-3BPJPVTR-Cun_Ijrv.js +36 -0
  121. package/ui/dist/assets/blockDiagram-GPEHLZMM-CgiNAArN.js +132 -0
  122. package/ui/dist/assets/c4Diagram-AAUBKEIU-BIwHcwcH.js +10 -0
  123. package/ui/dist/assets/channel-CNwAp9ic.js +1 -0
  124. package/ui/dist/assets/chunk-2J33WTMH-DXRgHPpp.js +1 -0
  125. package/ui/dist/assets/chunk-4BX2VUAB-BTb70kIb.js +1 -0
  126. package/ui/dist/assets/chunk-55IACEB6-BrAelyhX.js +1 -0
  127. package/ui/dist/assets/chunk-727SXJPM-BlYnlPdj.js +206 -0
  128. package/ui/dist/assets/chunk-AQP2D5EJ-DSPgdKZ8.js +231 -0
  129. package/ui/dist/assets/chunk-FMBD7UC4-BhH8ir2K.js +15 -0
  130. package/ui/dist/assets/chunk-ND2GUHAM-DCAuTSxB.js +1 -0
  131. package/ui/dist/assets/chunk-QZHKN3VN-DtYEkbYr.js +1 -0
  132. package/ui/dist/assets/classDiagram-4FO5ZUOK-DnHeGLmR.js +1 -0
  133. package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-DnHeGLmR.js +1 -0
  134. package/ui/dist/assets/cose-bilkent-S5V4N54A-CAM4jLYo.js +1 -0
  135. package/ui/dist/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
  136. package/ui/dist/assets/dagre-BM42HDAG-CISbgani.js +4 -0
  137. package/ui/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  138. package/ui/dist/assets/diagram-2AECGRRQ-BmXargwF.js +43 -0
  139. package/ui/dist/assets/diagram-5GNKFQAL-COlrLu0O.js +10 -0
  140. package/ui/dist/assets/diagram-KO2AKTUF-B-kUxuHX.js +3 -0
  141. package/ui/dist/assets/diagram-LMA3HP47-C3AVVxcm.js +24 -0
  142. package/ui/dist/assets/diagram-OG6HWLK6-JHeftSsO.js +24 -0
  143. package/ui/dist/assets/erDiagram-TEJ5UH35-BSWwMysi.js +85 -0
  144. package/ui/dist/assets/flowDiagram-I6XJVG4X-D-q1cK69.js +162 -0
  145. package/ui/dist/assets/ganttDiagram-6RSMTGT7-DrYn1H_t.js +292 -0
  146. package/ui/dist/assets/gitGraphDiagram-PVQCEYII-vJByl99X.js +106 -0
  147. package/ui/dist/assets/graph-CAnANduQ.js +1 -0
  148. package/ui/dist/assets/graph-DwoitsWW.js +2 -0
  149. package/ui/dist/assets/infoDiagram-5YYISTIA-D6zhGTMj.js +2 -0
  150. package/ui/dist/assets/init-Gi6I4Gst.js +1 -0
  151. package/ui/dist/assets/ishikawaDiagram-YF4QCWOH-CY-U_l7l.js +70 -0
  152. package/ui/dist/assets/journeyDiagram-JHISSGLW-jKj4lBEJ.js +139 -0
  153. package/ui/dist/assets/kanban-definition-UN3LZRKU-PZ-5AYw2.js +89 -0
  154. package/ui/dist/assets/katex-C5jXJg4s.js +257 -0
  155. package/ui/dist/assets/layout-DGIYPm2g.js +1 -0
  156. package/ui/dist/assets/linear-COY9pyF4.js +1 -0
  157. package/ui/dist/assets/main-BBzCq-49.js +308 -0
  158. package/ui/dist/assets/mermaid.core-Bddhr0ku.js +309 -0
  159. package/ui/dist/assets/mindmap-definition-RKZ34NQL-CPY2Fdu_.js +96 -0
  160. package/ui/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  161. package/ui/dist/assets/pieDiagram-4H26LBE5-C7GJ49et.js +30 -0
  162. package/ui/dist/assets/quadrantDiagram-W4KKPZXB-DQyQN5K7.js +7 -0
  163. package/ui/dist/assets/requirementDiagram-4Y6WPE33-CDrkwz1t.js +84 -0
  164. package/ui/dist/assets/sankeyDiagram-5OEKKPKP-BrYb9Eql.js +40 -0
  165. package/ui/dist/assets/sequenceDiagram-3UESZ5HK-B8If_JZp.js +162 -0
  166. package/ui/dist/assets/stateDiagram-AJRCARHV-BbpTp9VX.js +1 -0
  167. package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-BT4PvMFS.js +1 -0
  168. package/ui/dist/assets/theme-DV7vqTnV.js +1 -0
  169. package/ui/dist/assets/theme-SpsWsRN5.css +1 -0
  170. package/ui/dist/assets/timeline-definition-PNZ67QCA-DhUg6aIV.js +120 -0
  171. package/ui/dist/assets/transform-BwXaE9hv.js +1 -0
  172. package/ui/dist/assets/vennDiagram-CIIHVFJN-DpQVNNzF.js +34 -0
  173. package/ui/dist/assets/wardley-L42UT6IY-CyaxzHGP.js +173 -0
  174. package/ui/dist/assets/wardleyDiagram-YWT4CUSO-Bm0mA7wm.js +78 -0
  175. package/ui/dist/assets/xychartDiagram-2RQKCTM6-OJbmgDx6.js +7 -0
  176. package/ui/dist/graph.html +27 -0
  177. package/ui/dist/index.html +37 -0
@@ -0,0 +1,3 @@
1
+ import type { AbDryRunReport, AbQuestion } from './types.js';
2
+ export declare function materializeCorpusRepo(corpusPath: string): string;
3
+ export declare function validateCorpusAgainstQuestions(corpusPath: string, questions: AbQuestion[]): AbDryRunReport;
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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,6 @@
1
+ import { type CheckViolation } from '../check/collect-violations.js';
2
+ export type { CheckViolation };
3
+ export interface CheckOptions {
4
+ cwd?: string;
5
+ }
6
+ export declare function runCheck(options?: CheckOptions): number;
@@ -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,4 @@
1
+ export interface InitOptions {
2
+ cwd?: string;
3
+ }
4
+ export declare function runInit(options?: InitOptions): number;
@@ -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,7 @@
1
+ export interface PrepareCommandOptions {
2
+ cwd?: string;
3
+ dryRun?: boolean;
4
+ all?: boolean;
5
+ path?: string;
6
+ }
7
+ export declare function runPrepare(options?: PrepareCommandOptions): number;
@@ -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 };