living-ai-documentation 1.0.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/LICENSE +661 -0
- package/README.fr.md +344 -0
- package/README.md +344 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +262 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/frontend/accuracy-gauge.js +70 -0
- package/dist/src/frontend/admin.html +1532 -0
- package/dist/src/frontend/annotations.js +585 -0
- package/dist/src/frontend/boot.js +101 -0
- package/dist/src/frontend/config.js +29 -0
- package/dist/src/frontend/confirm-modal.js +82 -0
- package/dist/src/frontend/context.html +1252 -0
- package/dist/src/frontend/dark-mode.js +20 -0
- package/dist/src/frontend/diagram/alignment.js +161 -0
- package/dist/src/frontend/diagram/clipboard.js +187 -0
- package/dist/src/frontend/diagram/constants.js +109 -0
- package/dist/src/frontend/diagram/custom-shapes.js +104 -0
- package/dist/src/frontend/diagram/debug.js +43 -0
- package/dist/src/frontend/diagram/drawio-export.js +649 -0
- package/dist/src/frontend/diagram/edge-panel.js +293 -0
- package/dist/src/frontend/diagram/edge-rendering.js +12 -0
- package/dist/src/frontend/diagram/evidence.js +146 -0
- package/dist/src/frontend/diagram/grid.js +78 -0
- package/dist/src/frontend/diagram/groups.js +102 -0
- package/dist/src/frontend/diagram/history.js +157 -0
- package/dist/src/frontend/diagram/image-name-modal.js +48 -0
- package/dist/src/frontend/diagram/image-upload.js +36 -0
- package/dist/src/frontend/diagram/label-editor.js +115 -0
- package/dist/src/frontend/diagram/link-panel.js +144 -0
- package/dist/src/frontend/diagram/main.js +364 -0
- package/dist/src/frontend/diagram/network.js +2214 -0
- package/dist/src/frontend/diagram/node-panel.js +389 -0
- package/dist/src/frontend/diagram/node-rendering.js +964 -0
- package/dist/src/frontend/diagram/persistence.js +168 -0
- package/dist/src/frontend/diagram/ports.js +421 -0
- package/dist/src/frontend/diagram/selection-overlay.js +387 -0
- package/dist/src/frontend/diagram/state.js +43 -0
- package/dist/src/frontend/diagram/t.js +3 -0
- package/dist/src/frontend/diagram/toast.js +21 -0
- package/dist/src/frontend/diagram/unlock-hold.js +206 -0
- package/dist/src/frontend/diagram/zoom.js +20 -0
- package/dist/src/frontend/diagram-link-modal.js +137 -0
- package/dist/src/frontend/diagram.html +1494 -0
- package/dist/src/frontend/documents.js +479 -0
- package/dist/src/frontend/export.js +338 -0
- package/dist/src/frontend/file-attach.js +178 -0
- package/dist/src/frontend/files-modal.js +243 -0
- package/dist/src/frontend/i18n/en.json +624 -0
- package/dist/src/frontend/i18n/fr.json +624 -0
- package/dist/src/frontend/i18n.js +32 -0
- package/dist/src/frontend/image-paste.js +126 -0
- package/dist/src/frontend/index.html +2806 -0
- package/dist/src/frontend/local-search.js +476 -0
- package/dist/src/frontend/metadata.js +318 -0
- package/dist/src/frontend/misc.js +92 -0
- package/dist/src/frontend/new-doc-modal.js +285 -0
- package/dist/src/frontend/new-folder-modal.js +169 -0
- package/dist/src/frontend/search.js +194 -0
- package/dist/src/frontend/shape-editor.html +685 -0
- package/dist/src/frontend/sidebar-helpers.js +96 -0
- package/dist/src/frontend/sidebar-resize.js +98 -0
- package/dist/src/frontend/sidebar.js +351 -0
- package/dist/src/frontend/snippet-detect.js +25 -0
- package/dist/src/frontend/snippet-table.js +85 -0
- package/dist/src/frontend/snippet-tree.js +94 -0
- package/dist/src/frontend/snippets.js +1146 -0
- package/dist/src/frontend/state.js +46 -0
- package/dist/src/frontend/utils.js +21 -0
- package/dist/src/frontend/validate.js +107 -0
- package/dist/src/frontend/vendor/wordcloud2.js +1187 -0
- package/dist/src/frontend/wordcloud.js +693 -0
- package/dist/src/lib/config.d.ts +26 -0
- package/dist/src/lib/config.d.ts.map +1 -0
- package/dist/src/lib/config.js +195 -0
- package/dist/src/lib/config.js.map +1 -0
- package/dist/src/lib/hash.d.ts +2 -0
- package/dist/src/lib/hash.d.ts.map +1 -0
- package/dist/src/lib/hash.js +18 -0
- package/dist/src/lib/hash.js.map +1 -0
- package/dist/src/lib/metadata.d.ts +31 -0
- package/dist/src/lib/metadata.d.ts.map +1 -0
- package/dist/src/lib/metadata.js +128 -0
- package/dist/src/lib/metadata.js.map +1 -0
- package/dist/src/lib/parser.d.ts +11 -0
- package/dist/src/lib/parser.d.ts.map +1 -0
- package/dist/src/lib/parser.js +111 -0
- package/dist/src/lib/parser.js.map +1 -0
- package/dist/src/lib/status.d.ts +9 -0
- package/dist/src/lib/status.d.ts.map +1 -0
- package/dist/src/lib/status.js +72 -0
- package/dist/src/lib/status.js.map +1 -0
- package/dist/src/mcp/server.d.ts +3 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +2046 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/diagrams.d.ts +82 -0
- package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
- package/dist/src/mcp/tools/diagrams.js +594 -0
- package/dist/src/mcp/tools/diagrams.js.map +1 -0
- package/dist/src/mcp/tools/documents.d.ts +44 -0
- package/dist/src/mcp/tools/documents.d.ts.map +1 -0
- package/dist/src/mcp/tools/documents.js +186 -0
- package/dist/src/mcp/tools/documents.js.map +1 -0
- package/dist/src/mcp/tools/git.d.ts +10 -0
- package/dist/src/mcp/tools/git.d.ts.map +1 -0
- package/dist/src/mcp/tools/git.js +217 -0
- package/dist/src/mcp/tools/git.js.map +1 -0
- package/dist/src/mcp/tools/metadata.d.ts +57 -0
- package/dist/src/mcp/tools/metadata.d.ts.map +1 -0
- package/dist/src/mcp/tools/metadata.js +222 -0
- package/dist/src/mcp/tools/metadata.js.map +1 -0
- package/dist/src/mcp/tools/source.d.ts +29 -0
- package/dist/src/mcp/tools/source.d.ts.map +1 -0
- package/dist/src/mcp/tools/source.js +196 -0
- package/dist/src/mcp/tools/source.js.map +1 -0
- package/dist/src/routes/annotations.d.ts +3 -0
- package/dist/src/routes/annotations.d.ts.map +1 -0
- package/dist/src/routes/annotations.js +83 -0
- package/dist/src/routes/annotations.js.map +1 -0
- package/dist/src/routes/browse-source.d.ts +3 -0
- package/dist/src/routes/browse-source.d.ts.map +1 -0
- package/dist/src/routes/browse-source.js +79 -0
- package/dist/src/routes/browse-source.js.map +1 -0
- package/dist/src/routes/browse.d.ts +3 -0
- package/dist/src/routes/browse.d.ts.map +1 -0
- package/dist/src/routes/browse.js +91 -0
- package/dist/src/routes/browse.js.map +1 -0
- package/dist/src/routes/config.d.ts +3 -0
- package/dist/src/routes/config.d.ts.map +1 -0
- package/dist/src/routes/config.js +145 -0
- package/dist/src/routes/config.js.map +1 -0
- package/dist/src/routes/context.d.ts +3 -0
- package/dist/src/routes/context.d.ts.map +1 -0
- package/dist/src/routes/context.js +287 -0
- package/dist/src/routes/context.js.map +1 -0
- package/dist/src/routes/diagrams.d.ts +3 -0
- package/dist/src/routes/diagrams.d.ts.map +1 -0
- package/dist/src/routes/diagrams.js +69 -0
- package/dist/src/routes/diagrams.js.map +1 -0
- package/dist/src/routes/documents.d.ts +11 -0
- package/dist/src/routes/documents.d.ts.map +1 -0
- package/dist/src/routes/documents.js +450 -0
- package/dist/src/routes/documents.js.map +1 -0
- package/dist/src/routes/export.d.ts +3 -0
- package/dist/src/routes/export.d.ts.map +1 -0
- package/dist/src/routes/export.js +280 -0
- package/dist/src/routes/export.js.map +1 -0
- package/dist/src/routes/files.d.ts +3 -0
- package/dist/src/routes/files.d.ts.map +1 -0
- package/dist/src/routes/files.js +180 -0
- package/dist/src/routes/files.js.map +1 -0
- package/dist/src/routes/images.d.ts +3 -0
- package/dist/src/routes/images.d.ts.map +1 -0
- package/dist/src/routes/images.js +49 -0
- package/dist/src/routes/images.js.map +1 -0
- package/dist/src/routes/metadata.d.ts +3 -0
- package/dist/src/routes/metadata.d.ts.map +1 -0
- package/dist/src/routes/metadata.js +131 -0
- package/dist/src/routes/metadata.js.map +1 -0
- package/dist/src/routes/shape-libraries.d.ts +3 -0
- package/dist/src/routes/shape-libraries.d.ts.map +1 -0
- package/dist/src/routes/shape-libraries.js +118 -0
- package/dist/src/routes/shape-libraries.js.map +1 -0
- package/dist/src/routes/wordcloud.d.ts +3 -0
- package/dist/src/routes/wordcloud.d.ts.map +1 -0
- package/dist/src/routes/wordcloud.js +95 -0
- package/dist/src/routes/wordcloud.js.map +1 -0
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +93 -0
- package/dist/src/server.js.map +1 -0
- package/dist/starter-doc/.living-doc.json +52 -0
- package/dist/starter-doc/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
- package/dist/starter-doc/AI/2026_01_01_how_to.md +112 -0
- package/dist/starter-doc/AI/PROJECT-INSTRUCTIONS.md +172 -0
- package/dist/starter-doc/AI/PROJECT-STACK.md +77 -0
- package/dist/starter-doc/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
- package/dist/starter-doc/AI/default/AGENTS.md +31 -0
- package/dist/starter-doc/AI/default/CLAUDE.md +31 -0
- package/dist/starter-doc/AI/default/MEMORY.md +24 -0
- package/dist/starter-doc/AI/rules/no-magic-numbers.md +18 -0
- package/dist/starter-doc/AI/rules/track-current-work.md +23 -0
- package/dist/starter-doc/WORKLOG/current-task.md +57 -0
- package/dist/starter-doc-fr/.living-doc.json +52 -0
- package/dist/starter-doc-fr/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
- package/dist/starter-doc-fr/AI/2026_01_01_how_to.md +100 -0
- package/dist/starter-doc-fr/AI/PROJECT-INSTRUCTIONS.md +172 -0
- package/dist/starter-doc-fr/AI/PROJECT-STACK.md +77 -0
- package/dist/starter-doc-fr/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
- package/dist/starter-doc-fr/AI/default/AGENTS.md +31 -0
- package/dist/starter-doc-fr/AI/default/CLAUDE.md +31 -0
- package/dist/starter-doc-fr/AI/default/MEMORY.md +24 -0
- package/dist/starter-doc-fr/AI/rules/no-magic-numbers.md +18 -0
- package/dist/starter-doc-fr/AI/rules/track-current-work.md +23 -0
- package/dist/starter-doc-fr/WORKLOG/current-task.md +57 -0
- package/images/living_documentation.jpg +0 -0
- package/images/readme-extra-files.png +0 -0
- package/images/readme-filename-pattern.png +0 -0
- package/images/readme-intelligent-search-demo.jpg +0 -0
- package/images/readme-sidebar.png +0 -0
- package/package.json +72 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.browseSourceRouter = browseSourceRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const metadata_1 = require("../lib/metadata");
|
|
11
|
+
// Directories that are never useful to browse for source selection
|
|
12
|
+
const IGNORED_DIRS = new Set([
|
|
13
|
+
"node_modules",
|
|
14
|
+
"dist",
|
|
15
|
+
"build",
|
|
16
|
+
"out",
|
|
17
|
+
"coverage",
|
|
18
|
+
".git",
|
|
19
|
+
".next",
|
|
20
|
+
".nuxt",
|
|
21
|
+
".cache",
|
|
22
|
+
".turbo",
|
|
23
|
+
".svelte-kit",
|
|
24
|
+
"target",
|
|
25
|
+
"bin",
|
|
26
|
+
"obj",
|
|
27
|
+
"__pycache__",
|
|
28
|
+
".venv",
|
|
29
|
+
"venv",
|
|
30
|
+
]);
|
|
31
|
+
function isIgnoredDir(name) {
|
|
32
|
+
if (IGNORED_DIRS.has(name))
|
|
33
|
+
return true;
|
|
34
|
+
if (name.startsWith(".") && name !== ".github")
|
|
35
|
+
return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function browseSourceRouter(docsPath) {
|
|
39
|
+
const router = (0, express_1.Router)();
|
|
40
|
+
// GET /api/browse-source?path=<relative path under sourceRoot>
|
|
41
|
+
router.get("/", (req, res) => {
|
|
42
|
+
try {
|
|
43
|
+
const root = (0, metadata_1.resolveSourceRoot)(docsPath);
|
|
44
|
+
const rel = req.query.path || "";
|
|
45
|
+
const current = path_1.default.resolve(root, rel);
|
|
46
|
+
if (!(current === root || current.startsWith(path_1.default.resolve(root) + path_1.default.sep))) {
|
|
47
|
+
return res.status(400).json({ error: "Path escapes sourceRoot" });
|
|
48
|
+
}
|
|
49
|
+
if (!fs_1.default.existsSync(current) || !fs_1.default.statSync(current).isDirectory()) {
|
|
50
|
+
return res.status(400).json({ error: "Not a directory" });
|
|
51
|
+
}
|
|
52
|
+
const entries = fs_1.default.readdirSync(current, { withFileTypes: true });
|
|
53
|
+
const dirs = entries
|
|
54
|
+
.filter((e) => e.isDirectory() && !isIgnoredDir(e.name))
|
|
55
|
+
.map((e) => ({
|
|
56
|
+
name: e.name,
|
|
57
|
+
path: path_1.default.relative(root, path_1.default.join(current, e.name)),
|
|
58
|
+
}))
|
|
59
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
60
|
+
const files = entries
|
|
61
|
+
.filter((e) => e.isFile() && !e.name.startsWith("."))
|
|
62
|
+
.map((e) => ({
|
|
63
|
+
name: e.name,
|
|
64
|
+
path: path_1.default.relative(root, path_1.default.join(current, e.name)),
|
|
65
|
+
}))
|
|
66
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
67
|
+
const currentRel = path_1.default.relative(root, current);
|
|
68
|
+
const parent = current === root ? null : path_1.default.relative(root, path_1.default.dirname(current));
|
|
69
|
+
res.json({ sourceRoot: root, current: currentRel, parent, dirs, files });
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
res
|
|
73
|
+
.status(400)
|
|
74
|
+
.json({ error: err instanceof Error ? err.message : String(err) });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return router;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=browse-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browse-source.js","sourceRoot":"","sources":["../../../src/routes/browse-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../../src/routes/browse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAiBpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAsFrD"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.browseRouter = browseRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const RESERVED_SUBFOLDERS = new Set(['files', 'images']);
|
|
11
|
+
function isReservedAtDocsRoot(parentDir, name, docsPath) {
|
|
12
|
+
return (path_1.default.resolve(parentDir) === path_1.default.resolve(docsPath) &&
|
|
13
|
+
RESERVED_SUBFOLDERS.has(name));
|
|
14
|
+
}
|
|
15
|
+
function browseRouter(docsPath) {
|
|
16
|
+
const router = (0, express_1.Router)();
|
|
17
|
+
// GET /api/browse?path=... — list dirs and .md files at a given path
|
|
18
|
+
router.get('/', (req, res) => {
|
|
19
|
+
const rawPath = req.query.path || '/';
|
|
20
|
+
const current = path_1.default.resolve(rawPath);
|
|
21
|
+
try {
|
|
22
|
+
const entries = fs_1.default.readdirSync(current, { withFileTypes: true });
|
|
23
|
+
const dirs = entries
|
|
24
|
+
.filter((e) => e.isDirectory() &&
|
|
25
|
+
!e.name.startsWith('.') &&
|
|
26
|
+
!isReservedAtDocsRoot(current, e.name, docsPath))
|
|
27
|
+
.map((e) => ({ name: e.name, path: path_1.default.join(current, e.name) }))
|
|
28
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
29
|
+
const allFiles = req.query.all === '1';
|
|
30
|
+
const files = entries
|
|
31
|
+
.filter((e) => e.isFile() && (allFiles || e.name.toLowerCase().endsWith('.md')))
|
|
32
|
+
.map((e) => ({ name: e.name, path: path_1.default.join(current, e.name) }))
|
|
33
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
34
|
+
const parsed = path_1.default.parse(current);
|
|
35
|
+
const parent = current === parsed.root ? null : path_1.default.dirname(current);
|
|
36
|
+
res.json({ current, parent, dirs, files });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
res.status(400).json({ error: 'Cannot read directory' });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// GET /api/browse/alldirs?path=... — recursively list all subdirectories (relative paths)
|
|
43
|
+
router.get('/alldirs', (req, res) => {
|
|
44
|
+
const rawPath = req.query.path || '';
|
|
45
|
+
if (!rawPath)
|
|
46
|
+
return res.status(400).json({ error: 'path is required' });
|
|
47
|
+
const base = path_1.default.resolve(rawPath);
|
|
48
|
+
const results = [];
|
|
49
|
+
function collect(dir) {
|
|
50
|
+
try {
|
|
51
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
52
|
+
for (const e of entries) {
|
|
53
|
+
if (e.isDirectory() &&
|
|
54
|
+
!e.name.startsWith('.') &&
|
|
55
|
+
!isReservedAtDocsRoot(dir, e.name, docsPath)) {
|
|
56
|
+
const full = path_1.default.join(dir, e.name);
|
|
57
|
+
results.push(path_1.default.relative(base, full));
|
|
58
|
+
collect(full);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch { /* skip unreadable dirs */ }
|
|
63
|
+
}
|
|
64
|
+
collect(base);
|
|
65
|
+
res.json(results);
|
|
66
|
+
});
|
|
67
|
+
// POST /api/browse/mkdir — create a directory
|
|
68
|
+
router.post('/mkdir', (req, res) => {
|
|
69
|
+
const { path: dirPath } = req.body;
|
|
70
|
+
if (!dirPath || typeof dirPath !== 'string') {
|
|
71
|
+
return res.status(400).json({ error: 'path is required' });
|
|
72
|
+
}
|
|
73
|
+
const resolved = path_1.default.resolve(dirPath);
|
|
74
|
+
const parent = path_1.default.dirname(resolved);
|
|
75
|
+
const name = path_1.default.basename(resolved);
|
|
76
|
+
if (isReservedAtDocsRoot(parent, name, docsPath)) {
|
|
77
|
+
return res.status(400).json({
|
|
78
|
+
error: `"${name}" is a reserved folder name at the docs root`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
fs_1.default.mkdirSync(resolved, { recursive: true });
|
|
83
|
+
res.json({ created: resolved });
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
res.status(500).json({ error: 'Failed to create directory' });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return router;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=browse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browse.js","sourceRoot":"","sources":["../../../src/routes/browse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/routes/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAIpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA+HrD"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.configRouter = configRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const config_1 = require("../lib/config");
|
|
10
|
+
function configRouter(docsPath) {
|
|
11
|
+
const router = (0, express_1.Router)();
|
|
12
|
+
// GET /api/config
|
|
13
|
+
router.get('/', (_req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
res.json((0, config_1.readConfig)(docsPath));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
res.status(500).json({ error: 'Failed to read config' });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
// PUT /api/config
|
|
22
|
+
router.put('/', (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const patch = req.body;
|
|
25
|
+
// Only allow safe fields — prevent path traversal via config
|
|
26
|
+
const allowed = [
|
|
27
|
+
'title',
|
|
28
|
+
'filenamePattern',
|
|
29
|
+
'theme',
|
|
30
|
+
'language',
|
|
31
|
+
'showDiagramDebug',
|
|
32
|
+
'diagramNodePalette',
|
|
33
|
+
'diagramEdgePalette',
|
|
34
|
+
'sourceRoot',
|
|
35
|
+
'extraFiles',
|
|
36
|
+
'blockedFileExtensions',
|
|
37
|
+
'exclusiveFolderExpansion',
|
|
38
|
+
'exclusiveCategoryExpansion',
|
|
39
|
+
'codeBlockMaxHeight',
|
|
40
|
+
'markdownSoftBreaks',
|
|
41
|
+
];
|
|
42
|
+
const safe = {};
|
|
43
|
+
for (const key of allowed) {
|
|
44
|
+
if (key in patch) {
|
|
45
|
+
safe[key] = patch[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// filenamePattern: must contain exactly one [Category]
|
|
49
|
+
if ('filenamePattern' in safe && typeof safe.filenamePattern === 'string') {
|
|
50
|
+
const matches = safe.filenamePattern.match(/\[Category\]/gi);
|
|
51
|
+
if (!matches || matches.length === 0) {
|
|
52
|
+
return res.status(400).json({ error: 'filenamePattern must contain [Category]' });
|
|
53
|
+
}
|
|
54
|
+
if (matches.length > 1) {
|
|
55
|
+
return res.status(400).json({ error: 'filenamePattern must contain [Category] exactly once' });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// language: only 'en' or 'fr'
|
|
59
|
+
if ('language' in safe && !['en', 'fr'].includes(safe.language)) {
|
|
60
|
+
delete safe.language;
|
|
61
|
+
}
|
|
62
|
+
// diagramNodePalette / diagramEdgePalette: null or array of strings.
|
|
63
|
+
// Must drop invalid values explicitly — the initial allowed-key loop copied the raw
|
|
64
|
+
// value into `safe`, and silence without a delete would persist garbage (e.g. a string).
|
|
65
|
+
if ('diagramNodePalette' in patch) {
|
|
66
|
+
const v = patch.diagramNodePalette;
|
|
67
|
+
if (v === null || (Array.isArray(v) && v.every((s) => typeof s === 'string'))) {
|
|
68
|
+
safe.diagramNodePalette = v;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
delete safe.diagramNodePalette;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if ('diagramEdgePalette' in patch) {
|
|
75
|
+
const v = patch.diagramEdgePalette;
|
|
76
|
+
if (v === null || (Array.isArray(v) && v.every((s) => typeof s === 'string'))) {
|
|
77
|
+
safe.diagramEdgePalette = v;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
delete safe.diagramEdgePalette;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// sourceRoot: null or a RELATIVE path (relative to docsFolder). Absolute paths are rejected
|
|
84
|
+
// so .living-doc.json stays portable across machines.
|
|
85
|
+
if ('sourceRoot' in patch) {
|
|
86
|
+
const v = patch.sourceRoot;
|
|
87
|
+
if (v === null || v === '') {
|
|
88
|
+
safe.sourceRoot = null;
|
|
89
|
+
}
|
|
90
|
+
else if (typeof v === 'string' && !path_1.default.isAbsolute(v) && !v.startsWith('~')) {
|
|
91
|
+
safe.sourceRoot = v;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
return res.status(400).json({ error: 'sourceRoot must be a relative path or null' });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// blockedFileExtensions: array of extension strings (without leading dot), lowercase
|
|
98
|
+
if ('blockedFileExtensions' in patch) {
|
|
99
|
+
const v = patch.blockedFileExtensions;
|
|
100
|
+
if (Array.isArray(v)) {
|
|
101
|
+
safe.blockedFileExtensions = v
|
|
102
|
+
.filter((e) => typeof e === 'string')
|
|
103
|
+
.map((e) => e.trim().replace(/^\.+/, '').toLowerCase())
|
|
104
|
+
.filter((e) => /^[a-z0-9]+$/.test(e));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
return res.status(400).json({ error: 'blockedFileExtensions must be an array of strings' });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// codeBlockMaxHeight: non-negative integer pixels (0 disables); clamped to [0, 5000]
|
|
111
|
+
if ('codeBlockMaxHeight' in patch) {
|
|
112
|
+
const v = patch.codeBlockMaxHeight;
|
|
113
|
+
if (typeof v === 'number' && Number.isFinite(v) && v >= 0) {
|
|
114
|
+
safe.codeBlockMaxHeight = Math.min(5000, Math.round(v));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
return res.status(400).json({ error: 'codeBlockMaxHeight must be a non-negative number' });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// extraFiles: only RELATIVE .md paths (relative to docsFolder). Absolute entries are rejected.
|
|
121
|
+
if ('extraFiles' in patch && Array.isArray(patch.extraFiles)) {
|
|
122
|
+
const entries = patch.extraFiles;
|
|
123
|
+
const valid = [];
|
|
124
|
+
for (const f of entries) {
|
|
125
|
+
if (typeof f !== 'string')
|
|
126
|
+
continue;
|
|
127
|
+
if (path_1.default.isAbsolute(f) || f.startsWith('~')) {
|
|
128
|
+
return res.status(400).json({ error: 'extraFiles entries must be relative paths' });
|
|
129
|
+
}
|
|
130
|
+
if (!f.toLowerCase().endsWith('.md'))
|
|
131
|
+
continue;
|
|
132
|
+
valid.push(f);
|
|
133
|
+
}
|
|
134
|
+
safe.extraFiles = valid;
|
|
135
|
+
}
|
|
136
|
+
const updated = (0, config_1.writeConfig)(docsPath, safe);
|
|
137
|
+
res.json(updated);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
res.status(500).json({ error: 'Failed to update config' });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return router;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/routes/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/routes/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AA8NjC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoGtD"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.contextRouter = contextRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const config_1 = require("../lib/config");
|
|
11
|
+
const AI_ROOT_DIR = "AI";
|
|
12
|
+
const AI_RULES_DIR = path_1.default.join(AI_ROOT_DIR, "rules");
|
|
13
|
+
const AI_MCP_DIR = path_1.default.join(AI_ROOT_DIR, "MCP");
|
|
14
|
+
const MCP_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
15
|
+
function toPosixPath(value) {
|
|
16
|
+
return value.split(path_1.default.sep).join("/");
|
|
17
|
+
}
|
|
18
|
+
function slugify(value) {
|
|
19
|
+
return value
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
23
|
+
.replace(/^-+|-+$/g, "")
|
|
24
|
+
.slice(0, 80) || "ai-rule";
|
|
25
|
+
}
|
|
26
|
+
function safeString(value, fallback = "") {
|
|
27
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
28
|
+
}
|
|
29
|
+
function safeStringArray(value) {
|
|
30
|
+
if (typeof value === "string") {
|
|
31
|
+
return value
|
|
32
|
+
.split(",")
|
|
33
|
+
.map((item) => item.trim())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
return Array.isArray(value)
|
|
37
|
+
? value
|
|
38
|
+
.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
39
|
+
.map((item) => item.trim())
|
|
40
|
+
: [];
|
|
41
|
+
}
|
|
42
|
+
function frontmatterArray(values) {
|
|
43
|
+
return `[${values.map((item) => JSON.stringify(item)).join(", ")}]`;
|
|
44
|
+
}
|
|
45
|
+
function describePath(entry, root, rootPath) {
|
|
46
|
+
const abs = path_1.default.resolve(rootPath, entry);
|
|
47
|
+
const rel = path_1.default.relative(rootPath, abs);
|
|
48
|
+
if (path_1.default.isAbsolute(rel) || rel.startsWith("..")) {
|
|
49
|
+
return { path: entry, root, exists: false, type: "missing", size: null, modifiedAt: null };
|
|
50
|
+
}
|
|
51
|
+
if (!fs_1.default.existsSync(abs)) {
|
|
52
|
+
return { path: entry, root, exists: false, type: "missing", size: null, modifiedAt: null };
|
|
53
|
+
}
|
|
54
|
+
const stat = fs_1.default.statSync(abs);
|
|
55
|
+
return {
|
|
56
|
+
path: toPosixPath(entry),
|
|
57
|
+
root,
|
|
58
|
+
exists: true,
|
|
59
|
+
type: stat.isDirectory() ? "directory" : "file",
|
|
60
|
+
size: stat.isFile() ? stat.size : null,
|
|
61
|
+
modifiedAt: stat.mtime.toISOString(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function parseFrontmatter(md) {
|
|
65
|
+
const match = md.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
66
|
+
if (!match)
|
|
67
|
+
return { fields: {}, body: md.trim() };
|
|
68
|
+
const fields = {};
|
|
69
|
+
match[1].split(/\r?\n/).forEach((line) => {
|
|
70
|
+
const idx = line.indexOf(":");
|
|
71
|
+
if (idx <= 0 || /^\s+-\s+/.test(line))
|
|
72
|
+
return;
|
|
73
|
+
fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
74
|
+
});
|
|
75
|
+
return { fields, body: md.slice(match[0].length).trim() };
|
|
76
|
+
}
|
|
77
|
+
function parseInlineArray(raw) {
|
|
78
|
+
if (!raw)
|
|
79
|
+
return [];
|
|
80
|
+
return raw
|
|
81
|
+
.replace(/^\[/, "")
|
|
82
|
+
.replace(/\]$/, "")
|
|
83
|
+
.split(",")
|
|
84
|
+
.map((item) => item.trim().replace(/^["']|["']$/g, ""))
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
}
|
|
87
|
+
function firstParagraph(body) {
|
|
88
|
+
return body
|
|
89
|
+
.split(/\n\s*\n/)
|
|
90
|
+
.map((part) => part.trim())
|
|
91
|
+
.find(Boolean) || "";
|
|
92
|
+
}
|
|
93
|
+
function listInstructionFiles(docsPath) {
|
|
94
|
+
const instructionDir = path_1.default.join(docsPath, AI_ROOT_DIR);
|
|
95
|
+
if (!fs_1.default.existsSync(instructionDir))
|
|
96
|
+
return [];
|
|
97
|
+
return fs_1.default.readdirSync(instructionDir, { withFileTypes: true })
|
|
98
|
+
.filter((entry) => {
|
|
99
|
+
if (!entry.name.toLowerCase().endsWith(".md"))
|
|
100
|
+
return false;
|
|
101
|
+
const fullPath = path_1.default.join(instructionDir, entry.name);
|
|
102
|
+
return entry.isFile() || (entry.isSymbolicLink() && fs_1.default.statSync(fullPath).isFile());
|
|
103
|
+
})
|
|
104
|
+
.map((entry) => describePath(path_1.default.join(AI_ROOT_DIR, entry.name), "docsFolder", docsPath))
|
|
105
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
106
|
+
}
|
|
107
|
+
function readAiRules(docsPath) {
|
|
108
|
+
const rulesDir = path_1.default.join(docsPath, AI_RULES_DIR);
|
|
109
|
+
if (!fs_1.default.existsSync(rulesDir))
|
|
110
|
+
return [];
|
|
111
|
+
return fs_1.default.readdirSync(rulesDir, { withFileTypes: true })
|
|
112
|
+
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md"))
|
|
113
|
+
.map((entry) => {
|
|
114
|
+
const abs = path_1.default.join(rulesDir, entry.name);
|
|
115
|
+
const stat = fs_1.default.statSync(abs);
|
|
116
|
+
const raw = fs_1.default.readFileSync(abs, "utf-8");
|
|
117
|
+
const { fields, body } = parseFrontmatter(raw);
|
|
118
|
+
const id = safeString(fields.id, path_1.default.basename(entry.name, ".md"));
|
|
119
|
+
const title = safeString(fields.title, id);
|
|
120
|
+
return {
|
|
121
|
+
id,
|
|
122
|
+
title,
|
|
123
|
+
severity: safeString(fields.severity, "guideline"),
|
|
124
|
+
description: safeString(fields.description, firstParagraph(body)),
|
|
125
|
+
tags: parseInlineArray(fields.tags),
|
|
126
|
+
appliesTo: parseInlineArray(fields.appliesTo),
|
|
127
|
+
path: toPosixPath(path_1.default.join(AI_RULES_DIR, entry.name)),
|
|
128
|
+
body,
|
|
129
|
+
modifiedAt: stat.mtime.toISOString(),
|
|
130
|
+
};
|
|
131
|
+
})
|
|
132
|
+
.sort((a, b) => a.title.localeCompare(b.title));
|
|
133
|
+
}
|
|
134
|
+
function createRuleMarkdown(rule) {
|
|
135
|
+
return [
|
|
136
|
+
"---",
|
|
137
|
+
`id: ${rule.id}`,
|
|
138
|
+
`title: ${rule.title}`,
|
|
139
|
+
`severity: ${rule.severity}`,
|
|
140
|
+
`description: ${rule.description}`,
|
|
141
|
+
`tags: ${frontmatterArray(rule.tags)}`,
|
|
142
|
+
`appliesTo: ${frontmatterArray(rule.appliesTo)}`,
|
|
143
|
+
"---",
|
|
144
|
+
"",
|
|
145
|
+
rule.body,
|
|
146
|
+
"",
|
|
147
|
+
].join("\n");
|
|
148
|
+
}
|
|
149
|
+
function linkInstructionFile(sourcePath, docsPath) {
|
|
150
|
+
if (!sourcePath || typeof sourcePath !== "string") {
|
|
151
|
+
return { error: "path is required", status: 400 };
|
|
152
|
+
}
|
|
153
|
+
const source = path_1.default.resolve(sourcePath);
|
|
154
|
+
if (!fs_1.default.existsSync(source) || !fs_1.default.statSync(source).isFile()) {
|
|
155
|
+
return { error: "Instruction file not found", status: 404 };
|
|
156
|
+
}
|
|
157
|
+
const filename = path_1.default.basename(source);
|
|
158
|
+
if (filename.startsWith(".") || !filename.toLowerCase().endsWith(".md")) {
|
|
159
|
+
return { error: "Instruction file must be a visible Markdown file", status: 400 };
|
|
160
|
+
}
|
|
161
|
+
const instructionDir = path_1.default.join(docsPath, AI_ROOT_DIR);
|
|
162
|
+
const target = path_1.default.join(instructionDir, filename);
|
|
163
|
+
if (!target.startsWith(path_1.default.resolve(docsPath) + path_1.default.sep)) {
|
|
164
|
+
return { error: "Invalid instruction file path", status: 400 };
|
|
165
|
+
}
|
|
166
|
+
if (fs_1.default.existsSync(target)) {
|
|
167
|
+
return { error: "Instruction file already exists", status: 409 };
|
|
168
|
+
}
|
|
169
|
+
fs_1.default.mkdirSync(instructionDir, { recursive: true });
|
|
170
|
+
const symlinkTarget = path_1.default.relative(instructionDir, source);
|
|
171
|
+
fs_1.default.symlinkSync(symlinkTarget, target, "file");
|
|
172
|
+
return describePath(path_1.default.join(AI_ROOT_DIR, filename), "docsFolder", docsPath);
|
|
173
|
+
}
|
|
174
|
+
function deleteInstructionFile(filename, docsPath) {
|
|
175
|
+
if (!filename || typeof filename !== "string") {
|
|
176
|
+
return { error: "filename is required", status: 400 };
|
|
177
|
+
}
|
|
178
|
+
if (filename !== path_1.default.basename(filename) || filename.startsWith(".") || !filename.toLowerCase().endsWith(".md")) {
|
|
179
|
+
return { error: "Invalid instruction file name", status: 400 };
|
|
180
|
+
}
|
|
181
|
+
const target = path_1.default.join(docsPath, AI_ROOT_DIR, filename);
|
|
182
|
+
const aiRoot = path_1.default.resolve(docsPath, AI_ROOT_DIR);
|
|
183
|
+
if (!target.startsWith(aiRoot + path_1.default.sep)) {
|
|
184
|
+
return { error: "Invalid instruction file path", status: 400 };
|
|
185
|
+
}
|
|
186
|
+
if (!fs_1.default.existsSync(target) || !fs_1.default.statSync(target).isFile()) {
|
|
187
|
+
return { error: "Instruction file not found", status: 404 };
|
|
188
|
+
}
|
|
189
|
+
fs_1.default.unlinkSync(target);
|
|
190
|
+
return { deleted: toPosixPath(path_1.default.join(AI_ROOT_DIR, filename)) };
|
|
191
|
+
}
|
|
192
|
+
function contextRouter(docsPath) {
|
|
193
|
+
const router = (0, express_1.Router)();
|
|
194
|
+
router.get("/orientation", (_req, res) => {
|
|
195
|
+
const config = (0, config_1.readConfig)(docsPath);
|
|
196
|
+
res.json({
|
|
197
|
+
instructions: listInstructionFiles(docsPath),
|
|
198
|
+
rules: readAiRules(docsPath),
|
|
199
|
+
sourceRoot: config.sourceRoot,
|
|
200
|
+
rulesFolder: toPosixPath(path_1.default.join(AI_RULES_DIR)),
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
router.post("/instructions", (req, res) => {
|
|
204
|
+
const result = linkInstructionFile(req.body?.path, docsPath);
|
|
205
|
+
if ("error" in result)
|
|
206
|
+
return res.status(result.status).json({ error: result.error });
|
|
207
|
+
res.status(201).json(result);
|
|
208
|
+
});
|
|
209
|
+
router.delete("/instructions/:filename", (req, res) => {
|
|
210
|
+
const result = deleteInstructionFile(req.params.filename, docsPath);
|
|
211
|
+
if ("error" in result)
|
|
212
|
+
return res.status(result.status).json({ error: result.error });
|
|
213
|
+
res.json(result);
|
|
214
|
+
});
|
|
215
|
+
router.post("/rules", (req, res) => {
|
|
216
|
+
const title = safeString(req.body?.title);
|
|
217
|
+
if (!title)
|
|
218
|
+
return res.status(400).json({ error: "Rule title is required" });
|
|
219
|
+
const id = slugify(safeString(req.body?.id, title));
|
|
220
|
+
const rulesDir = path_1.default.join(docsPath, AI_RULES_DIR);
|
|
221
|
+
const target = path_1.default.join(rulesDir, `${id}.md`);
|
|
222
|
+
if (!target.startsWith(path_1.default.resolve(docsPath) + path_1.default.sep)) {
|
|
223
|
+
return res.status(400).json({ error: "Invalid rule path" });
|
|
224
|
+
}
|
|
225
|
+
if (fs_1.default.existsSync(target)) {
|
|
226
|
+
return res.status(409).json({ error: "Rule already exists" });
|
|
227
|
+
}
|
|
228
|
+
const body = safeString(req.body?.body, safeString(req.body?.description));
|
|
229
|
+
const rule = {
|
|
230
|
+
id,
|
|
231
|
+
title,
|
|
232
|
+
severity: safeString(req.body?.severity, "guideline"),
|
|
233
|
+
description: safeString(req.body?.description, firstParagraph(body)),
|
|
234
|
+
tags: safeStringArray(req.body?.tags),
|
|
235
|
+
appliesTo: safeStringArray(req.body?.appliesTo),
|
|
236
|
+
path: toPosixPath(path_1.default.join(AI_RULES_DIR, `${id}.md`)),
|
|
237
|
+
body,
|
|
238
|
+
modifiedAt: null,
|
|
239
|
+
};
|
|
240
|
+
fs_1.default.mkdirSync(rulesDir, { recursive: true });
|
|
241
|
+
fs_1.default.writeFileSync(target, createRuleMarkdown(rule), "utf-8");
|
|
242
|
+
const stat = fs_1.default.statSync(target);
|
|
243
|
+
res.status(201).json({ ...rule, modifiedAt: stat.mtime.toISOString() });
|
|
244
|
+
});
|
|
245
|
+
router.post("/mcp-result", (req, res) => {
|
|
246
|
+
const rawName = safeString(req.body?.name);
|
|
247
|
+
const rawKind = safeString(req.body?.kind);
|
|
248
|
+
const content = typeof req.body?.content === "string" ? req.body.content : "";
|
|
249
|
+
if (!rawName)
|
|
250
|
+
return res.status(400).json({ error: "name is required" });
|
|
251
|
+
if (rawKind !== "tool" && rawKind !== "prompt") {
|
|
252
|
+
return res.status(400).json({ error: "kind must be 'tool' or 'prompt'" });
|
|
253
|
+
}
|
|
254
|
+
if (!content)
|
|
255
|
+
return res.status(400).json({ error: "content is required" });
|
|
256
|
+
const baseName = rawName.endsWith(".md") ? rawName.slice(0, -3) : rawName;
|
|
257
|
+
if (!MCP_NAME_PATTERN.test(baseName)) {
|
|
258
|
+
return res.status(400).json({ error: "Invalid MCP item name" });
|
|
259
|
+
}
|
|
260
|
+
const slugName = baseName.replace(/_/g, "-");
|
|
261
|
+
const mcpDir = path_1.default.join(docsPath, AI_MCP_DIR);
|
|
262
|
+
fs_1.default.mkdirSync(mcpDir, { recursive: true });
|
|
263
|
+
const indexedPattern = /^(\d{3})-(tool|prompt)-(.+)\.md$/;
|
|
264
|
+
let maxIndex = 0;
|
|
265
|
+
let existingFilename = null;
|
|
266
|
+
for (const entry of fs_1.default.readdirSync(mcpDir)) {
|
|
267
|
+
const match = entry.match(indexedPattern);
|
|
268
|
+
if (!match)
|
|
269
|
+
continue;
|
|
270
|
+
const idx = parseInt(match[1], 10);
|
|
271
|
+
if (idx > maxIndex)
|
|
272
|
+
maxIndex = idx;
|
|
273
|
+
if (match[2] === rawKind && match[3] === slugName)
|
|
274
|
+
existingFilename = entry;
|
|
275
|
+
}
|
|
276
|
+
const filename = existingFilename ?? `${String(maxIndex + 1).padStart(3, "0")}-${rawKind}-${slugName}.md`;
|
|
277
|
+
const target = path_1.default.join(mcpDir, filename);
|
|
278
|
+
if (!target.startsWith(path_1.default.resolve(docsPath) + path_1.default.sep)) {
|
|
279
|
+
return res.status(400).json({ error: "Invalid MCP result path" });
|
|
280
|
+
}
|
|
281
|
+
fs_1.default.mkdirSync(mcpDir, { recursive: true });
|
|
282
|
+
fs_1.default.writeFileSync(target, content, "utf-8");
|
|
283
|
+
res.status(201).json({ path: toPosixPath(path_1.default.join(AI_MCP_DIR, filename)) });
|
|
284
|
+
});
|
|
285
|
+
return router;
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=context.js.map
|