@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,58 @@
1
+ import fs from 'node:fs';
2
+ import { buildMarkdownFromDraft, resolvePublishTargetPath } from './publish.js';
3
+ export function computeDraftDiff(index, draft) {
4
+ const knowledgeRoot = index.getKnowledgeRoot();
5
+ if (!knowledgeRoot) {
6
+ return { targetPath: null, isNew: true, diff: buildMarkdownFromDraft(draft) };
7
+ }
8
+ const targetPath = resolvePublishTargetPath(index, draft);
9
+ const proposed = buildMarkdownFromDraft(draft);
10
+ if (!targetPath || !fs.existsSync(targetPath)) {
11
+ return {
12
+ targetPath,
13
+ isNew: true,
14
+ diff: `--- /dev/null\n+++ ${targetPath ?? draft.slug + '.md'}\n${prefixLines(proposed, '+')}`,
15
+ };
16
+ }
17
+ const current = fs.readFileSync(targetPath, 'utf8');
18
+ if (current === proposed) {
19
+ return { targetPath, isNew: false, diff: '(no changes)' };
20
+ }
21
+ return {
22
+ targetPath,
23
+ isNew: false,
24
+ diff: unifiedDiff(targetPath, current, proposed),
25
+ };
26
+ }
27
+ function prefixLines(text, prefix) {
28
+ return text
29
+ .split('\n')
30
+ .map((line) => `${prefix}${line}`)
31
+ .join('\n');
32
+ }
33
+ function unifiedDiff(pathLabel, before, after) {
34
+ const oldLines = before.split('\n');
35
+ const newLines = after.split('\n');
36
+ const header = [`--- a/${pathLabel}`, `+++ b/${pathLabel}`];
37
+ if (oldLines.join('\n') === newLines.join('\n')) {
38
+ return '(no changes)';
39
+ }
40
+ const max = Math.max(oldLines.length, newLines.length);
41
+ const body = [];
42
+ for (let i = 0; i < max; i += 1) {
43
+ const oldLine = oldLines[i];
44
+ const newLine = newLines[i];
45
+ if (oldLine === newLine) {
46
+ body.push(` ${oldLine ?? ''}`);
47
+ }
48
+ else {
49
+ if (oldLine !== undefined) {
50
+ body.push(`-${oldLine}`);
51
+ }
52
+ if (newLine !== undefined) {
53
+ body.push(`+${newLine}`);
54
+ }
55
+ }
56
+ }
57
+ return [...header, `@@ -1,${oldLines.length} +1,${newLines.length} @@`, ...body].join('\n');
58
+ }
@@ -0,0 +1,13 @@
1
+ import type { DocIndex } from '../index/doc-index.js';
2
+ export declare class DocsWatcher {
3
+ private readonly index;
4
+ private watcher;
5
+ private revision;
6
+ private debounceTimer;
7
+ private readonly subscribers;
8
+ constructor(index: DocIndex);
9
+ getRevision(): number;
10
+ subscribe(listener: (revision: number) => void): () => void;
11
+ start(knowledgeRoot: string): void;
12
+ stop(): Promise<void>;
13
+ }
@@ -0,0 +1,59 @@
1
+ import chokidar from 'chokidar';
2
+ const DEBOUNCE_MS = 350;
3
+ export class DocsWatcher {
4
+ index;
5
+ watcher = null;
6
+ revision = 0;
7
+ debounceTimer = null;
8
+ subscribers = new Set();
9
+ constructor(index) {
10
+ this.index = index;
11
+ }
12
+ getRevision() {
13
+ return this.revision;
14
+ }
15
+ subscribe(listener) {
16
+ this.subscribers.add(listener);
17
+ return () => {
18
+ this.subscribers.delete(listener);
19
+ };
20
+ }
21
+ start(knowledgeRoot) {
22
+ if (this.watcher) {
23
+ return;
24
+ }
25
+ this.watcher = chokidar.watch(knowledgeRoot, {
26
+ ignoreInitial: true,
27
+ ignored: [
28
+ /(^|[/\\])\../,
29
+ '**/.repo-mind/**',
30
+ '**/.worktrees/**',
31
+ '**/node_modules/**',
32
+ ],
33
+ });
34
+ const scheduleRefresh = () => {
35
+ if (this.debounceTimer) {
36
+ clearTimeout(this.debounceTimer);
37
+ }
38
+ this.debounceTimer = setTimeout(() => {
39
+ this.index.refresh();
40
+ this.revision += 1;
41
+ for (const listener of this.subscribers) {
42
+ listener(this.revision);
43
+ }
44
+ }, DEBOUNCE_MS);
45
+ };
46
+ this.watcher.on('all', scheduleRefresh);
47
+ }
48
+ async stop() {
49
+ if (this.debounceTimer) {
50
+ clearTimeout(this.debounceTimer);
51
+ this.debounceTimer = null;
52
+ }
53
+ if (this.watcher) {
54
+ await this.watcher.close();
55
+ this.watcher = null;
56
+ }
57
+ this.subscribers.clear();
58
+ }
59
+ }
@@ -0,0 +1,7 @@
1
+ import type { DocIndex } from '../index/doc-index.js';
2
+ import type { DraftsDb } from './db/drafts-db.js';
3
+ export interface DraftApiResponse {
4
+ status: number;
5
+ body: unknown;
6
+ }
7
+ export declare function handleDraftApi(index: DocIndex, db: DraftsDb | undefined, method: string, pathname: string, bodyRaw: string): DraftApiResponse | null;
@@ -0,0 +1,413 @@
1
+ import { runExport } from '../commands/export.js';
2
+ import { isValidSlug } from '../index/slug.js';
3
+ import { DOC_TYPES, isDocStatus, isDocType } from '../index/types.js';
4
+ import { writeCatalogEmoji } from './catalog-meta.js';
5
+ import { createFolder, createPageFile, deleteFolder, deletePageFile, movePageFile, renamePageFile } from './fs-operations.js';
6
+ import { prepareDocFile, prepareAllDocs } from '../prepare/prepare-docs.js';
7
+ import { syncAllDocLinks } from '../prepare/auto-links.js';
8
+ import { getDoc } from '../tools/get-doc.js';
9
+ import { computeDraftDiff } from './diff.js';
10
+ import { publishDraft } from './publish.js';
11
+ function jsonError(status, message) {
12
+ return { status, body: { error: message } };
13
+ }
14
+ function parseJsonBody(raw) {
15
+ if (!raw.trim()) {
16
+ return {};
17
+ }
18
+ try {
19
+ const parsed = JSON.parse(raw);
20
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
21
+ return null;
22
+ }
23
+ return parsed;
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function stringArray(value) {
30
+ if (!Array.isArray(value)) {
31
+ return undefined;
32
+ }
33
+ return value.filter((item) => typeof item === 'string');
34
+ }
35
+ export function handleDraftApi(index, db, method, pathname, bodyRaw) {
36
+ if (pathname === '/api/export' && method === 'POST') {
37
+ const code = runExport({ force: true });
38
+ if (code !== 0) {
39
+ return jsonError(500, 'export failed');
40
+ }
41
+ return { status: 200, body: { ok: true, path: 'agents.md' } };
42
+ }
43
+ if (pathname === '/api/catalog-meta' && method === 'PUT') {
44
+ const body = parseJsonBody(bodyRaw);
45
+ if (body === null) {
46
+ return jsonError(400, 'invalid JSON body');
47
+ }
48
+ const folderPath = typeof body.path === 'string' ? body.path : '';
49
+ const emoji = typeof body.emoji === 'string' ? body.emoji : '';
50
+ const knowledgeRoot = index.getKnowledgeRoot();
51
+ if (!knowledgeRoot) {
52
+ return jsonError(404, 'no docs/ directory found');
53
+ }
54
+ const meta = writeCatalogEmoji(knowledgeRoot, folderPath, emoji);
55
+ return { status: 200, body: { meta } };
56
+ }
57
+ if (pathname === '/api/fs/folder' && method === 'POST') {
58
+ const body = parseJsonBody(bodyRaw);
59
+ if (body === null) {
60
+ return jsonError(400, 'invalid JSON body');
61
+ }
62
+ const parentPath = typeof body.parentPath === 'string' ? body.parentPath : '';
63
+ const name = typeof body.name === 'string' ? body.name : '';
64
+ try {
65
+ const result = createFolder(index, parentPath, name);
66
+ return { status: 201, body: { result } };
67
+ }
68
+ catch (error) {
69
+ const message = error instanceof Error ? error.message : String(error);
70
+ return jsonError(400, message);
71
+ }
72
+ }
73
+ if (pathname === '/api/fs/page' && method === 'POST') {
74
+ const body = parseJsonBody(bodyRaw);
75
+ if (body === null) {
76
+ return jsonError(400, 'invalid JSON body');
77
+ }
78
+ const parentPath = typeof body.parentPath === 'string' ? body.parentPath : '';
79
+ const name = typeof body.name === 'string' ? body.name : '';
80
+ const title = typeof body.title === 'string' ? body.title : undefined;
81
+ const templateId = typeof body.templateId === 'string' ? body.templateId : undefined;
82
+ if (!db) {
83
+ return jsonError(503, 'drafts database unavailable');
84
+ }
85
+ try {
86
+ const page = createPageFile(index, parentPath, name, { title, templateId });
87
+ const existing = db.getActiveBySlug(page.slug);
88
+ if (existing) {
89
+ return { status: 200, body: { page, draft: existing } };
90
+ }
91
+ const doc = getDoc(index, page.slug);
92
+ const tags = Array.isArray(doc.frontmatter?.tags)
93
+ ? doc.frontmatter.tags.filter((tag) => typeof tag === 'string')
94
+ : [];
95
+ const related = Array.isArray(doc.frontmatter?.related)
96
+ ? doc.frontmatter.related.filter((item) => typeof item === 'string')
97
+ : [];
98
+ const draft = db.create({
99
+ slug: page.slug,
100
+ type: page.type,
101
+ title: typeof doc.frontmatter?.title === 'string' ? doc.frontmatter.title : page.slug,
102
+ body: doc.body ?? '',
103
+ tags,
104
+ related,
105
+ target_path: page.relativePath,
106
+ });
107
+ return { status: 201, body: { page, draft } };
108
+ }
109
+ catch (error) {
110
+ const message = error instanceof Error ? error.message : String(error);
111
+ return jsonError(400, message);
112
+ }
113
+ }
114
+ if (pathname === '/api/fs/move' && method === 'POST') {
115
+ const body = parseJsonBody(bodyRaw);
116
+ if (body === null) {
117
+ return jsonError(400, 'invalid JSON body');
118
+ }
119
+ const fromPath = typeof body.fromPath === 'string' ? body.fromPath : '';
120
+ const toDir = typeof body.toDir === 'string' ? body.toDir : '';
121
+ try {
122
+ const result = movePageFile(index, fromPath, toDir);
123
+ return { status: 200, body: { result } };
124
+ }
125
+ catch (error) {
126
+ const message = error instanceof Error ? error.message : String(error);
127
+ return jsonError(400, message);
128
+ }
129
+ }
130
+ if (pathname === '/api/fs/rename' && method === 'POST') {
131
+ const body = parseJsonBody(bodyRaw);
132
+ if (body === null) {
133
+ return jsonError(400, 'invalid JSON body');
134
+ }
135
+ const pagePath = typeof body.path === 'string' ? body.path : '';
136
+ const newName = typeof body.newName === 'string' ? body.newName : '';
137
+ try {
138
+ const result = renamePageFile(index, pagePath, newName);
139
+ return { status: 200, body: { result } };
140
+ }
141
+ catch (error) {
142
+ const message = error instanceof Error ? error.message : String(error);
143
+ return jsonError(400, message);
144
+ }
145
+ }
146
+ if (pathname === '/api/fs/delete' && method === 'POST') {
147
+ const body = parseJsonBody(bodyRaw);
148
+ if (body === null) {
149
+ return jsonError(400, 'invalid JSON body');
150
+ }
151
+ const targetPath = typeof body.path === 'string' ? body.path : '';
152
+ const kind = body.kind === 'folder' ? 'folder' : body.kind === 'page' ? 'page' : null;
153
+ if (!kind) {
154
+ return jsonError(400, 'kind must be "page" or "folder"');
155
+ }
156
+ try {
157
+ const result = kind === 'page' ? deletePageFile(index, targetPath) : deleteFolder(index, targetPath);
158
+ return { status: 200, body: { result } };
159
+ }
160
+ catch (error) {
161
+ const message = error instanceof Error ? error.message : String(error);
162
+ return jsonError(400, message);
163
+ }
164
+ }
165
+ if (pathname === '/api/drafts/open' && method === 'POST') {
166
+ if (!db) {
167
+ return jsonError(503, 'drafts database unavailable');
168
+ }
169
+ const body = parseJsonBody(bodyRaw);
170
+ if (body === null) {
171
+ return jsonError(400, 'invalid JSON body');
172
+ }
173
+ const slug = typeof body.slug === 'string' ? body.slug : '';
174
+ if (!isValidSlug(slug)) {
175
+ return jsonError(400, `invalid slug: ${slug}`);
176
+ }
177
+ const existing = db.getActiveBySlug(slug);
178
+ if (existing) {
179
+ return { status: 200, body: { draft: existing } };
180
+ }
181
+ const doc = getDoc(index, slug);
182
+ if (!doc.found || !doc.slug || !doc.frontmatter) {
183
+ return jsonError(404, `document not found: ${slug}`);
184
+ }
185
+ const docRecord = index.getDocBySlug(slug);
186
+ try {
187
+ const draft = db.create({
188
+ slug: doc.slug,
189
+ type: doc.frontmatter.type,
190
+ title: doc.frontmatter.title ?? doc.slug,
191
+ body: doc.body ?? '',
192
+ tags: doc.frontmatter.tags ?? [],
193
+ related: doc.frontmatter.related ?? [],
194
+ forked_from: doc.slug,
195
+ target_path: docRecord?.relativePath ?? null,
196
+ });
197
+ return { status: 201, body: { draft } };
198
+ }
199
+ catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ return jsonError(409, message);
202
+ }
203
+ }
204
+ if (pathname === '/api/prepare' && method === 'POST') {
205
+ const body = parseJsonBody(bodyRaw);
206
+ if (body === null) {
207
+ return jsonError(400, 'invalid JSON body');
208
+ }
209
+ const relativePath = typeof body.path === 'string' ? body.path : '';
210
+ if (!relativePath.trim()) {
211
+ return jsonError(400, 'path is required');
212
+ }
213
+ const typeParam = typeof body.type === 'string' ? body.type : undefined;
214
+ if (typeParam && !isDocType(typeParam)) {
215
+ return jsonError(400, `invalid type — expected one of: ${DOC_TYPES.join(', ')}`);
216
+ }
217
+ const slugParam = typeof body.slug === 'string' ? body.slug : undefined;
218
+ if (slugParam && !isValidSlug(slugParam)) {
219
+ return jsonError(400, `invalid slug: ${slugParam}`);
220
+ }
221
+ const statusParam = typeof body.status === 'string' ? body.status : undefined;
222
+ if (statusParam && !isDocStatus(statusParam)) {
223
+ return jsonError(400, `invalid status: ${statusParam}`);
224
+ }
225
+ const titleParam = typeof body.title === 'string' ? body.title : undefined;
226
+ try {
227
+ const result = prepareDocFile(index, relativePath, {
228
+ type: typeParam && isDocType(typeParam) ? typeParam : undefined,
229
+ slug: slugParam,
230
+ title: titleParam,
231
+ status: statusParam && isDocStatus(statusParam) ? statusParam : undefined,
232
+ });
233
+ return { status: 200, body: { result } };
234
+ }
235
+ catch (error) {
236
+ const message = error instanceof Error ? error.message : String(error);
237
+ return jsonError(400, message);
238
+ }
239
+ }
240
+ if (pathname === '/api/prepare-all' && method === 'POST') {
241
+ const body = parseJsonBody(bodyRaw);
242
+ const dryRun = body !== null && body.dryRun === true;
243
+ const result = prepareAllDocs(index, { dryRun });
244
+ return { status: 200, body: { result, dryRun } };
245
+ }
246
+ if (pathname === '/api/sync-links' && method === 'POST') {
247
+ const body = parseJsonBody(bodyRaw);
248
+ const dryRun = body !== null && body.dryRun === true;
249
+ const convertMarkdownLinks = body === null || body.convertBody === undefined ? true : body.convertBody === true;
250
+ const syncRelated = body === null || body.syncRelated === undefined ? true : body.syncRelated === true;
251
+ const result = syncAllDocLinks(index, {
252
+ dryRun,
253
+ convertMarkdownLinks,
254
+ syncRelated,
255
+ });
256
+ return { status: 200, body: { result, dryRun } };
257
+ }
258
+ if (!db) {
259
+ if (pathname.startsWith('/api/drafts')) {
260
+ return jsonError(503, 'drafts database unavailable');
261
+ }
262
+ return null;
263
+ }
264
+ if (!pathname.startsWith('/api/drafts')) {
265
+ return null;
266
+ }
267
+ if (pathname === '/api/drafts' && method === 'GET') {
268
+ return { status: 200, body: { drafts: db.listActive() } };
269
+ }
270
+ const diffMatch = pathname.match(/^\/api\/drafts\/([^/]+)\/diff$/);
271
+ if (diffMatch && method === 'GET') {
272
+ const id = decodeURIComponent(diffMatch[1] ?? '');
273
+ const draft = db.getById(id);
274
+ if (!draft) {
275
+ return jsonError(404, 'draft not found');
276
+ }
277
+ return { status: 200, body: computeDraftDiff(index, draft) };
278
+ }
279
+ if (pathname === '/api/drafts' && method === 'POST') {
280
+ const body = parseJsonBody(bodyRaw);
281
+ if (body === null) {
282
+ return jsonError(400, 'invalid JSON body');
283
+ }
284
+ const forkFrom = typeof body.forkFrom === 'string' ? body.forkFrom : undefined;
285
+ if (forkFrom) {
286
+ if (!isValidSlug(forkFrom)) {
287
+ return jsonError(400, `invalid forkFrom slug: ${forkFrom}`);
288
+ }
289
+ const existing = db.getActiveBySlug(forkFrom);
290
+ if (existing) {
291
+ return jsonError(409, `active draft already exists for slug: ${forkFrom}`);
292
+ }
293
+ const doc = getDoc(index, forkFrom);
294
+ if (!doc.found || !doc.slug || !doc.frontmatter) {
295
+ return jsonError(404, `document not found: ${forkFrom}`);
296
+ }
297
+ const docRecord = index.getDocBySlug(forkFrom);
298
+ try {
299
+ const draft = db.create({
300
+ slug: doc.slug,
301
+ type: doc.frontmatter.type,
302
+ title: doc.frontmatter.title ?? doc.slug,
303
+ body: doc.body ?? '',
304
+ tags: doc.frontmatter.tags ?? [],
305
+ related: doc.frontmatter.related ?? [],
306
+ forked_from: doc.slug,
307
+ target_path: docRecord?.relativePath ?? null,
308
+ });
309
+ return { status: 201, body: { draft } };
310
+ }
311
+ catch (error) {
312
+ const message = error instanceof Error ? error.message : String(error);
313
+ return jsonError(409, message);
314
+ }
315
+ }
316
+ const slug = typeof body.slug === 'string' ? body.slug : '';
317
+ const type = typeof body.type === 'string' ? body.type : '';
318
+ if (!isValidSlug(slug)) {
319
+ return jsonError(400, `invalid slug: ${slug}`);
320
+ }
321
+ if (!isDocType(type)) {
322
+ return jsonError(400, `invalid type — expected one of: ${DOC_TYPES.join(', ')}`);
323
+ }
324
+ try {
325
+ const draft = db.create({
326
+ slug,
327
+ type,
328
+ title: typeof body.title === 'string' ? body.title : slug,
329
+ body: typeof body.body === 'string' ? body.body : '',
330
+ tags: stringArray(body.tags),
331
+ related: stringArray(body.related),
332
+ });
333
+ return { status: 201, body: { draft } };
334
+ }
335
+ catch (error) {
336
+ const message = error instanceof Error ? error.message : String(error);
337
+ return jsonError(409, message);
338
+ }
339
+ }
340
+ const publishMatch = pathname.match(/^\/api\/drafts\/([^/]+)\/publish$/);
341
+ if (publishMatch && method === 'POST') {
342
+ const id = decodeURIComponent(publishMatch[1] ?? '');
343
+ const draft = db.getById(id);
344
+ if (!draft) {
345
+ return jsonError(404, 'draft not found');
346
+ }
347
+ try {
348
+ const result = publishDraft(index, draft);
349
+ const published = db.markPublished(id, result.path);
350
+ return { status: 200, body: { published, result } };
351
+ }
352
+ catch (error) {
353
+ const message = error instanceof Error ? error.message : String(error);
354
+ if (message.includes('already exists') || message.includes('broken related')) {
355
+ return jsonError(409, message);
356
+ }
357
+ return jsonError(400, message);
358
+ }
359
+ }
360
+ const idMatch = pathname.match(/^\/api\/drafts\/([^/]+)$/);
361
+ if (idMatch) {
362
+ const id = decodeURIComponent(idMatch[1] ?? '');
363
+ if (method === 'PUT') {
364
+ const body = parseJsonBody(bodyRaw);
365
+ if (body === null) {
366
+ return jsonError(400, 'invalid JSON body');
367
+ }
368
+ const type = typeof body.type === 'string' ? body.type : undefined;
369
+ if (type && !isDocType(type)) {
370
+ return jsonError(400, `invalid type: ${type}`);
371
+ }
372
+ const slug = typeof body.slug === 'string' ? body.slug : undefined;
373
+ if (slug && !isValidSlug(slug)) {
374
+ return jsonError(400, `invalid slug: ${slug}`);
375
+ }
376
+ const status = typeof body.status === 'string' ? body.status : undefined;
377
+ if (status && !isDocStatus(status)) {
378
+ return jsonError(400, `invalid status: ${status}`);
379
+ }
380
+ try {
381
+ const draft = db.update(id, {
382
+ slug,
383
+ type: type && isDocType(type) ? type : undefined,
384
+ status: status && isDocStatus(status) ? status : undefined,
385
+ title: typeof body.title === 'string' ? body.title : undefined,
386
+ body: typeof body.body === 'string' ? body.body : undefined,
387
+ tags: stringArray(body.tags),
388
+ related: stringArray(body.related),
389
+ });
390
+ return { status: 200, body: { draft } };
391
+ }
392
+ catch (error) {
393
+ const message = error instanceof Error ? error.message : String(error);
394
+ const status = message.includes('not found') ? 404 : 409;
395
+ return jsonError(status, message);
396
+ }
397
+ }
398
+ if (method === 'DELETE') {
399
+ try {
400
+ const deleted = db.delete(id);
401
+ if (!deleted) {
402
+ return jsonError(404, 'draft not found');
403
+ }
404
+ return { status: 200, body: { deleted: true } };
405
+ }
406
+ catch (error) {
407
+ const message = error instanceof Error ? error.message : String(error);
408
+ return jsonError(400, message);
409
+ }
410
+ }
411
+ }
412
+ return jsonError(405, 'method not allowed');
413
+ }
@@ -0,0 +1,52 @@
1
+ import type { DocIndex } from '../index/doc-index.js';
2
+ import type { DocType } from '../index/types.js';
3
+ export interface CreateFolderResult {
4
+ relativePath: string;
5
+ absolutePath: string;
6
+ }
7
+ export interface CreatePageResult {
8
+ relativePath: string;
9
+ absolutePath: string;
10
+ slug: string;
11
+ type: DocType;
12
+ }
13
+ export interface FsPageMutationResult {
14
+ relativePath: string;
15
+ absolutePath: string;
16
+ slug: string;
17
+ previousSlug: string;
18
+ slugChanged: boolean;
19
+ inboundWarnings: Array<{
20
+ slug: string;
21
+ title: string;
22
+ }>;
23
+ cascadeUpdated: string[];
24
+ }
25
+ export interface FsDeletePageResult {
26
+ relativePath: string;
27
+ slug: string;
28
+ inboundWarnings: Array<{
29
+ slug: string;
30
+ title: string;
31
+ }>;
32
+ cascadeUpdated: string[];
33
+ }
34
+ export interface FsDeleteFolderResult {
35
+ relativePath: string;
36
+ deletedSlugs: string[];
37
+ inboundWarnings: Array<{
38
+ slug: string;
39
+ title: string;
40
+ }>;
41
+ cascadeUpdated: string[];
42
+ }
43
+ export interface CreatePageOptions {
44
+ title?: string;
45
+ templateId?: string;
46
+ }
47
+ export declare function createFolder(index: DocIndex, parentPath: string, name: string): CreateFolderResult;
48
+ export declare function createPageFile(index: DocIndex, parentPath: string, name: string, options?: CreatePageOptions): CreatePageResult;
49
+ export declare function movePageFile(index: DocIndex, fromPath: string, toDir: string): FsPageMutationResult;
50
+ export declare function renamePageFile(index: DocIndex, pagePath: string, newName: string): FsPageMutationResult;
51
+ export declare function deletePageFile(index: DocIndex, pagePath: string): FsDeletePageResult;
52
+ export declare function deleteFolder(index: DocIndex, folderPath: string): FsDeleteFolderResult;