drafted 1.19.33 → 1.19.35
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 +48 -14
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -3275,8 +3275,8 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
|
|
|
3275
3275
|
// Name the org, or don't search. See searchNeedsOrg: an unaddressed search
|
|
3276
3276
|
// returns "no matches" from ONE org and reads as "nowhere", which is the
|
|
3277
3277
|
// one wrong answer a search can give that nothing announces.
|
|
3278
|
+
const orgs = await getOrgList();
|
|
3278
3279
|
{
|
|
3279
|
-
const orgs = await getOrgList();
|
|
3280
3280
|
if (searchNeedsOrg({ explicitOrg: org, orgFromPath, orgCount: orgs.length })) {
|
|
3281
3281
|
// Work is almost always org-specific, and this session usually already
|
|
3282
3282
|
// knows WHICH — a binding, an open project, or the linked repo of the
|
|
@@ -3312,7 +3312,21 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
|
|
|
3312
3312
|
));
|
|
3313
3313
|
}
|
|
3314
3314
|
}
|
|
3315
|
-
|
|
3315
|
+
// THE CANONICAL SLUG, not the spelling the caller used. /o/beoflow resolves
|
|
3316
|
+
// (the org NAME is a resolution rung alongside id and slug) but its slug is
|
|
3317
|
+
// beoflow-mnczl0zt — and fs(ls) already prints that. Echoing the input
|
|
3318
|
+
// instead made ls and search disagree about the address of one thing, which
|
|
3319
|
+
// is the same defect as dropping the folder segment, one axis over.
|
|
3320
|
+
// Computed HERE, before any section renders: section() runs its renderer
|
|
3321
|
+
// immediately, so a later assignment would leave wiki + skills on the old
|
|
3322
|
+
// spelling while projects + frames used the new one.
|
|
3323
|
+
const addressed = orgFromPath || org || null;
|
|
3324
|
+
const canonicalOrg = addressed
|
|
3325
|
+
? (orgs.find(o => o.id === addressed
|
|
3326
|
+
|| (o.slug || '').toLowerCase() === String(addressed).toLowerCase()
|
|
3327
|
+
|| (o.name || '').toLowerCase() === String(addressed).toLowerCase())?.slug || addressed)
|
|
3328
|
+
: null;
|
|
3329
|
+
const scope = canonicalOrg ? `/o/${canonicalOrg}` : '';
|
|
3316
3330
|
const leg = async (fn) => { try { return { v: await fn() }; } catch (e) { return { e: e?.message || String(e) }; } };
|
|
3317
3331
|
const [wiki, skills, projects, frames, repos] = await Promise.all([
|
|
3318
3332
|
leg(() => api('GET', `/api/wiki/search?q=${encodeURIComponent(q)}&limit=10`, undefined, orgHeader)),
|
|
@@ -3339,16 +3353,38 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
|
|
|
3339
3353
|
out.push(body ? `${title}:\n${body}` : `${title}: no matches`);
|
|
3340
3354
|
};
|
|
3341
3355
|
|
|
3356
|
+
// PER-HIT, not a footer. Whether a wiki page or skill is git-owned depends
|
|
3357
|
+
// on the FOLDER it sits in, and two hits with the same-shaped path can
|
|
3358
|
+
// differ: /o/<org>/wiki/x is read-only in an org whose root is linked and
|
|
3359
|
+
// writable in one whose root is not. A summary line at the bottom makes
|
|
3360
|
+
// the reader cross-reference; the mark belongs on the line it describes.
|
|
3361
|
+
const repoByFolder = new Map(
|
|
3362
|
+
(Array.isArray(repos.v?.repos) ? repos.v.repos : []).map(r => [r.folderName || '', `${r.slug}@${r.branch}`])
|
|
3363
|
+
);
|
|
3364
|
+
const gitMark = (folder) => {
|
|
3365
|
+
const r = repoByFolder.get(folder ?? '');
|
|
3366
|
+
return r ? ` [git: ${r} — edit via commit]` : '';
|
|
3367
|
+
};
|
|
3368
|
+
|
|
3342
3369
|
section('Wiki', wiki, (v) => {
|
|
3343
3370
|
const hits = (v?.hits || v?.pages || v?.results || []).slice(0, 10);
|
|
3344
|
-
return hits.length ? hits.map(h => ` ${scope}${h.folder ? `/${h.folder}` : ''}/wiki/${h.path}${h.title ? ` — ${clip(h.title, 80)}` : ''}`).join('\n') : '';
|
|
3371
|
+
return hits.length ? hits.map(h => ` ${scope}${h.folder ? `/${h.folder}` : ''}/wiki/${h.path}${h.title ? ` — ${clip(h.title, 80)}` : ''}${gitMark(h.folder)}`).join('\n') : '';
|
|
3345
3372
|
});
|
|
3346
3373
|
section('Skills', skills, (v) => {
|
|
3347
3374
|
// Fewer than the other legs on purpose: skills search is fuzzy and its tail
|
|
3348
3375
|
// is weak matches, which is noise in a fan-out whose job is "where does this
|
|
3349
3376
|
// live?". fs(search, path="/skills") is the place to go deeper.
|
|
3350
3377
|
const list = (Array.isArray(v) ? v : (v?.skills || [])).slice(0, 5);
|
|
3351
|
-
return list.length ? list.map(s =>
|
|
3378
|
+
return list.length ? list.map(s => {
|
|
3379
|
+
// A repo-indexed hit IS git-owned by construction — it was read out of
|
|
3380
|
+
// a repo index, so it needs no folder lookup. Its branch comes from the
|
|
3381
|
+
// repos leg; falling back to the bare slug keeps the mark honest if
|
|
3382
|
+
// that leg failed.
|
|
3383
|
+
const mark = s.repoEntry
|
|
3384
|
+
? ` [git: ${[...repoByFolder.values()].find(v2 => v2.startsWith(`${s.repoSlug}@`)) || s.repoSlug} — edit via commit]`
|
|
3385
|
+
: gitMark(s.folder);
|
|
3386
|
+
return ` ${scope}/skills/${s.slug} — ${clip(s.description || s.name, 120)}`.trimEnd() + mark;
|
|
3387
|
+
}).join('\n') : '';
|
|
3352
3388
|
});
|
|
3353
3389
|
|
|
3354
3390
|
// NO client-side org filter on either leg. Both endpoints scope server-side
|
|
@@ -3369,7 +3405,7 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
|
|
|
3369
3405
|
const projectRows = (Array.isArray(projects.v?.projects) ? projects.v.projects : [])
|
|
3370
3406
|
.filter(x => x.folder !== '__archived');
|
|
3371
3407
|
const folderById = new Map(projectRows.map(x => [x.id, x.folder || '']));
|
|
3372
|
-
const orgAs =
|
|
3408
|
+
const orgAs = canonicalOrg;
|
|
3373
3409
|
section('Projects', projects, () => {
|
|
3374
3410
|
const hits = projectRows.filter(x => matchesAllTerms([x.name, x.slug, x.description], q)).slice(0, 10);
|
|
3375
3411
|
return hits.length
|
|
@@ -3384,17 +3420,15 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
|
|
|
3384
3420
|
: (note ? note.trimStart() : '');
|
|
3385
3421
|
});
|
|
3386
3422
|
|
|
3387
|
-
//
|
|
3388
|
-
//
|
|
3389
|
-
//
|
|
3390
|
-
//
|
|
3391
|
-
|
|
3392
|
-
.
|
|
3393
|
-
if (linked.length) {
|
|
3394
|
-
out.push(`Git-owned WIKI + SKILLS (edit those via commit; tasks + projects write normally): ${linked.join('; ')}`);
|
|
3423
|
+
// No summary footer: every affected line now carries its own mark, and two
|
|
3424
|
+
// mechanisms saying the same thing is one that can disagree. The note below
|
|
3425
|
+
// is the part a per-hit mark cannot express — that the marks are about
|
|
3426
|
+
// wiki/skills only, so a project hit under a linked folder is still writable.
|
|
3427
|
+
if (repoByFolder.size) {
|
|
3428
|
+
out.push('Marked [git: …] = wiki/skill owned by that repo; edit via commit. Projects and tasks in the same folder write normally.');
|
|
3395
3429
|
}
|
|
3396
3430
|
|
|
3397
|
-
const searched =
|
|
3431
|
+
const searched = canonicalOrg;
|
|
3398
3432
|
// ALWAYS say where this looked. "No matches" is only safe to act on when
|
|
3399
3433
|
// the reader can see the scope it was computed over.
|
|
3400
3434
|
return ok(`Search "${q}" in ${searched ? `/o/${searched}` : 'your only org'}\n\n${out.join('\n\n')}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.35",
|
|
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": [
|