drafted 1.19.32 → 1.19.34

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 CHANGED
@@ -3339,16 +3339,38 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
3339
3339
  out.push(body ? `${title}:\n${body}` : `${title}: no matches`);
3340
3340
  };
3341
3341
 
3342
+ // PER-HIT, not a footer. Whether a wiki page or skill is git-owned depends
3343
+ // on the FOLDER it sits in, and two hits with the same-shaped path can
3344
+ // differ: /o/<org>/wiki/x is read-only in an org whose root is linked and
3345
+ // writable in one whose root is not. A summary line at the bottom makes
3346
+ // the reader cross-reference; the mark belongs on the line it describes.
3347
+ const repoByFolder = new Map(
3348
+ (Array.isArray(repos.v?.repos) ? repos.v.repos : []).map(r => [r.folderName || '', `${r.slug}@${r.branch}`])
3349
+ );
3350
+ const gitMark = (folder) => {
3351
+ const r = repoByFolder.get(folder ?? '');
3352
+ return r ? ` [git: ${r} — edit via commit]` : '';
3353
+ };
3354
+
3342
3355
  section('Wiki', wiki, (v) => {
3343
3356
  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') : '';
3357
+ 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
3358
  });
3346
3359
  section('Skills', skills, (v) => {
3347
3360
  // Fewer than the other legs on purpose: skills search is fuzzy and its tail
3348
3361
  // is weak matches, which is noise in a fan-out whose job is "where does this
3349
3362
  // live?". fs(search, path="/skills") is the place to go deeper.
3350
3363
  const list = (Array.isArray(v) ? v : (v?.skills || [])).slice(0, 5);
3351
- return list.length ? list.map(s => ` ${scope}/skills/${s.slug} — ${clip(s.description || s.name, 120)}`.trimEnd()).join('\n') : '';
3364
+ return list.length ? list.map(s => {
3365
+ // A repo-indexed hit IS git-owned by construction — it was read out of
3366
+ // a repo index, so it needs no folder lookup. Its branch comes from the
3367
+ // repos leg; falling back to the bare slug keeps the mark honest if
3368
+ // that leg failed.
3369
+ const mark = s.repoEntry
3370
+ ? ` [git: ${[...repoByFolder.values()].find(v2 => v2.startsWith(`${s.repoSlug}@`)) || s.repoSlug} — edit via commit]`
3371
+ : gitMark(s.folder);
3372
+ return ` ${scope}/skills/${s.slug} — ${clip(s.description || s.name, 120)}`.trimEnd() + mark;
3373
+ }).join('\n') : '';
3352
3374
  });
3353
3375
 
3354
3376
  // NO client-side org filter on either leg. Both endpoints scope server-side
@@ -3384,14 +3406,12 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
3384
3406
  : (note ? note.trimStart() : '');
3385
3407
  });
3386
3408
 
3387
- // Which of these hits are read-only in Drafted. A git-owned folder's wiki
3388
- // and skills answer 409 repo_owned on write, and nothing else in the
3389
- // output distinguishes them from an ordinary path — the failure lands at
3390
- // write time, after the edit is composed.
3391
- const linked = (Array.isArray(repos.v?.repos) ? repos.v.repos : [])
3392
- .map(r => `${r.folderName ? r.folderName : '(org root)'} ${r.slug}@${r.branch}`);
3393
- if (linked.length) {
3394
- out.push(`Git-owned (edits are commits, not fs writes): ${linked.join('; ')}`);
3409
+ // No summary footer: every affected line now carries its own mark, and two
3410
+ // mechanisms saying the same thing is one that can disagree. The note below
3411
+ // is the part a per-hit mark cannot express that the marks are about
3412
+ // wiki/skills only, so a project hit under a linked folder is still writable.
3413
+ if (repoByFolder.size) {
3414
+ out.push('Marked [git: …] = wiki/skill owned by that repo; edit via commit. Projects and tasks in the same folder write normally.');
3395
3415
  }
3396
3416
 
3397
3417
  const searched = orgFromPath || org || null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.19.32",
3
+ "version": "1.19.34",
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": [
@@ -120,10 +120,16 @@ export function liftFolderScope(path, to) {
120
120
  * org's folder names (each of which is its full path). One segment deeper only —
121
121
  * `ls` shows a level, not a tree.
122
122
  */
123
- /** `repoOf(fullFolderName)` -> "slug@branch" when git owns that folder, else
124
- * null. A git-owned folder's wiki and skills answer 409 repo_owned on write,
125
- * and without this the listing renders it identically to a writable one — the
126
- * agent finds out at write time, after composing the edit. */
123
+ /** `repoOf(fullFolderName)` -> "slug@branch" when a repo is linked to that
124
+ * folder, else null.
125
+ *
126
+ * SAY WHAT IS OWNED, NOT "THE FOLDER". A link makes the folder's WIKI and
127
+ * SKILLS git-owned — those answer 409 repo_owned on write. Its TASKS and
128
+ * PROJECTS are untouched and write normally (verified: a frame write into a
129
+ * project inside a linked folder succeeds). A bare "GIT-OWNED" on the folder
130
+ * overclaims, and an agent that believes it will not attempt a frame write it
131
+ * is perfectly entitled to make — a false negative costs more here than the
132
+ * 409 it was meant to prevent. */
127
133
  export function subfolderEntries(base, prefix, names, repoOf = null) {
128
134
  const depth = prefix ? normalizeFolder(prefix).split('/').length : 0;
129
135
  const kids = new Set();
@@ -142,7 +148,7 @@ export function subfolderEntries(base, prefix, names, repoOf = null) {
142
148
  type: 'directory',
143
149
  path: `${String(base || '').replace(/\/+$/, '')}/${name}`,
144
150
  hint: repo
145
- ? `folder — carries its own wiki, skills, tasks and projects · GIT-OWNED by ${repo} (edits are commits)`
151
+ ? `folder — carries its own wiki, skills, tasks and projects · wiki + skills GIT-OWNED by ${repo} (edit those via commit); tasks + projects write normally`
146
152
  : 'folder — carries its own wiki, skills, tasks and projects',
147
153
  };
148
154
  });