drafted 1.17.8 → 1.17.10
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/mcp/server.mjs +53 -8
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -2678,7 +2678,11 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2678
2678
|
}
|
|
2679
2679
|
case 'read': {
|
|
2680
2680
|
const canonPath = wikiPath.replace(/\.md$/, '');
|
|
2681
|
-
|
|
2681
|
+
// hashline-annotated (read → edit stays surgical), like frames; lines
|
|
2682
|
+
// honors partial reads. raw=true keeps it body-only (no synthesized
|
|
2683
|
+
// frontmatter) so ops anchor to the same bytes /edit applies against.
|
|
2684
|
+
const q = `&raw=true&format=hashline${lines ? `&lines=${encodeURIComponent(lines)}` : ''}`;
|
|
2685
|
+
const page = await api('GET', `/api/wiki/page?path=${encodeURIComponent(canonPath)}${q}`, undefined, orgHeader);
|
|
2682
2686
|
if (!page?.content) return err(new Error(`wiki page not found: ${canonPath}`));
|
|
2683
2687
|
return ok(page.content);
|
|
2684
2688
|
}
|
|
@@ -2704,20 +2708,37 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2704
2708
|
return ok(result);
|
|
2705
2709
|
}
|
|
2706
2710
|
case 'rm': {
|
|
2707
|
-
//
|
|
2708
|
-
//
|
|
2709
|
-
//
|
|
2711
|
+
// Live page → soft delete: move it under archive/ (mv cascades inbound
|
|
2712
|
+
// refs, so links keep working into the archive). Already-archived page
|
|
2713
|
+
// → hard purge: it IS the trash (the web UI offers permanent delete
|
|
2714
|
+
// there), and without it an occupied archive slot had no escape hatch
|
|
2715
|
+
// via MCP — the old behavior self-moved (from == to) and reported "as
|
|
2716
|
+
// if nothing happened" while the server renamed the row to its own
|
|
2717
|
+
// collision suffix.
|
|
2710
2718
|
const canonPath = wikiPath.replace(/\.md$/, '');
|
|
2711
|
-
const archivePath = canonPath.startsWith('archive/') ? canonPath : `archive/${canonPath}`;
|
|
2712
2719
|
const page = await api('GET', `/api/wiki/page?path=${encodeURIComponent(canonPath)}`, undefined, orgHeader).catch(() => null);
|
|
2713
2720
|
if (!page?.id) return err(new Error(`wiki page not found: ${canonPath}`));
|
|
2721
|
+
if (canonPath.startsWith('archive/')) {
|
|
2722
|
+
const del = await api('DELETE', `/api/wiki/pages/${page.id}`, undefined, orgHeader);
|
|
2723
|
+
return ok({ deleted: true, path: canonPath, ...(del && del.deleted ? { id: del.deleted } : {}) });
|
|
2724
|
+
}
|
|
2725
|
+
const archivePath = `archive/${canonPath}`;
|
|
2714
2726
|
const result = await api('PATCH', `/api/wiki/pages/${page.id}/move`, { path: archivePath }, orgHeader);
|
|
2715
|
-
|
|
2727
|
+
// Report the server-authoritative final path — an occupied archive slot
|
|
2728
|
+
// auto-suffixes (-old), and the response must match the DB, not guess.
|
|
2729
|
+
return ok({ archived: true, from: canonPath, to: result?.path || archivePath, ...(result?.referrersUpdated ? { referrersUpdated: result.referrersUpdated } : {}) });
|
|
2716
2730
|
}
|
|
2717
2731
|
case 'search': {
|
|
2732
|
+
// The gate means "consult the wiki before project writes". Only an
|
|
2733
|
+
// actual search with a real query counts — not the call itself.
|
|
2734
|
+
const q = String(query || '').trim();
|
|
2735
|
+
if (!q) return ok('(provide a query — e.g. fs(search, path="/wiki", query="<terms>"))');
|
|
2736
|
+
const pages = await api('GET', `/api/wiki/search?q=${encodeURIComponent(q)}`, undefined, orgHeader);
|
|
2737
|
+
// Server responds { hits: [...] } — earlier code read .pages/.results,
|
|
2738
|
+
// which are never present, so every MCP wiki search reported empty.
|
|
2739
|
+
const hits = pages?.hits || pages?.pages || pages?.results || [];
|
|
2718
2740
|
markSearched(gs, 'wiki');
|
|
2719
|
-
|
|
2720
|
-
return ok(formatWikiIndex(pages?.pages || pages?.results || []));
|
|
2741
|
+
return ok(formatWikiIndex(hits));
|
|
2721
2742
|
}
|
|
2722
2743
|
default:
|
|
2723
2744
|
return err(new Error(`fs ${action} not supported for /wiki`));
|
|
@@ -2732,6 +2753,11 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2732
2753
|
}
|
|
2733
2754
|
switch (action) {
|
|
2734
2755
|
case 'ls': {
|
|
2756
|
+
// A specific slug is a targeted ls: just that skill, not the whole root.
|
|
2757
|
+
if (slug) {
|
|
2758
|
+
const s = await api('GET', `/api/skills/slug/${slug}`, undefined, orgHeader).catch(() => null);
|
|
2759
|
+
return ok(s ? [{ slug: s.slug, name: s.name, description: s.description }] : []);
|
|
2760
|
+
}
|
|
2735
2761
|
const list = await api('GET', '/api/skills', undefined, orgHeader);
|
|
2736
2762
|
const skills = Array.isArray(list) ? list : (list?.skills || []);
|
|
2737
2763
|
return ok(skills.map(s => ({ slug: s.slug, name: s.name, description: s.description })));
|
|
@@ -2828,6 +2854,25 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2828
2854
|
projectRef = parts[0]; // /projects/<project>
|
|
2829
2855
|
}
|
|
2830
2856
|
|
|
2857
|
+
// fs grammar: the project comes from the PATH, not the shared session
|
|
2858
|
+
// binding — scope the whole call to the path's project (request-local, per
|
|
2859
|
+
// the DRAFT-36 addressing invariant). Without this, /api/fs falls back to
|
|
2860
|
+
// the session's active project (the user's shared row any browser tab or
|
|
2861
|
+
// parallel agent can rewrite), so ls/read/write silently touched the wrong
|
|
2862
|
+
// project and "fixed itself" when something re-bound — the under-reporting
|
|
2863
|
+
// churn reported from the MJ Directive org.
|
|
2864
|
+
if (projectRef) {
|
|
2865
|
+
const pathProject = await resolveProjectRef(projectRef).catch(() => null);
|
|
2866
|
+
if (!pathProject?.id) return err(new Error(`project not found: ${projectRef}`));
|
|
2867
|
+
// An org-scoped path must name a project of that org — never a same-named
|
|
2868
|
+
// project from another org (the path is both address and guardrail).
|
|
2869
|
+
if (orgFromPath && !(await pathOrgMatches(orgFromPath, pathProject))) {
|
|
2870
|
+
return err(new Error(`project ${projectRef} is not in org ${orgFromPath}`));
|
|
2871
|
+
}
|
|
2872
|
+
getState().projectId = pathProject.id;
|
|
2873
|
+
getState().projectMeta = pathProject;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2831
2876
|
const run = async () => {
|
|
2832
2877
|
const hasFile = layer && filename;
|
|
2833
2878
|
// Layer-root file (lane null) hits the server's 2-segment route.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.10",
|
|
4
4
|
"description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|