drafted 1.16.0 → 1.17.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.
@@ -5,7 +5,7 @@ import assert from 'node:assert/strict';
5
5
  import { mkdtempSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { tmpdir } from 'node:os';
8
- import { projectlessMutationNeedsOrg, boundOrgRejected, receiptOrg } from './server.mjs';
8
+ import { projectlessMutationNeedsOrg, boundOrgRejected, receiptOrg, splitOrgScope, stripUrlOrigin } from './server.mjs';
9
9
  import { loadPersistedProject, savePersistedProject } from './active-project-store.mjs';
10
10
 
11
11
  // One rule governs create AND fork (a fork is a create). A write proceeds when its
@@ -145,6 +145,27 @@ assert.equal(boundOrgRejected({ message: `not a member of org "${stale}"` }), fa
145
145
  );
146
146
  }
147
147
 
148
+ // Shape A grammar: org is the top folder of the filesystem (/o/<org>/<root>/...).
149
+ // The org segment is stripped for the root handlers and carried as the per-request
150
+ // scope; bare roots stay accepted (backward compat, session working org).
151
+ assert.deepEqual(splitOrgScope('/o/acme/wiki/engineering/authz.md'), { path: '/wiki/engineering/authz.md', org: 'acme' }, 'org-scoped wiki path strips to the bare root + org');
152
+ assert.deepEqual(splitOrgScope('/o/acme/projects/design/beoflow/wireframes/x.html'), { path: '/projects/design/beoflow/wireframes/x.html', org: 'acme' }, 'org-scoped project path strips to the bare root + org');
153
+ assert.deepEqual(splitOrgScope('/o/acme'), { path: '/', org: 'acme' }, 'ls /o/<org> lists that org\'s roots');
154
+ assert.deepEqual(splitOrgScope('/o/acme/'), { path: '/', org: 'acme' }, 'trailing slash on the org root is harmless');
155
+ assert.deepEqual(splitOrgScope('/o/acme%20brand/skills'), { path: '/skills', org: 'acme brand' }, 'org segment is URL-decoded');
156
+ assert.deepEqual(splitOrgScope('/wiki/engineering'), { path: '/wiki/engineering', org: null }, 'bare root paths pass through untouched');
157
+ assert.deepEqual(splitOrgScope('/projects/x/y/z.html'), { path: '/projects/x/y/z.html', org: null }, 'bare project paths pass through untouched');
158
+ assert.ok(splitOrgScope('/o/').error, 'a bare /o/ is an invalid org-scoped path');
159
+ assert.ok(splitOrgScope('/o/').error?.includes('expected /o/<org>/<root>'), 'error names the expected grammar');
160
+
161
+ // Full share URLs (Q2): the URL's pathname IS the fs path — stripping the origin must
162
+ // leave an addressable path, and non-URL inputs pass through untouched.
163
+ assert.equal(stripUrlOrigin('https://drafted.live/o/acme/wiki/engineering/authz.md'), '/o/acme/wiki/engineering/authz.md', 'full URL strips to its pathname');
164
+ assert.equal(stripUrlOrigin('https://drafted.live/o/acme/projects/beoflow/designs/pricing/hero.html?x=1'), '/o/acme/projects/beoflow/designs/pricing/hero.html', 'URL query params are dropped with the origin');
165
+ assert.equal(stripUrlOrigin('/o/acme/wiki/x'), '/o/acme/wiki/x', 'plain paths pass through untouched');
166
+ assert.equal(stripUrlOrigin('not a url'), 'not a url', 'non-URL input passes through');
167
+ assert.equal(stripUrlOrigin('/f/00000000-0000-0000-0000-000000000000'), '/f/00000000-0000-0000-0000-000000000000', '/f/ frame links pass through');
168
+
148
169
  console.log('org-guard policy OK');
149
170
  // Importing server.mjs builds the stdio MCP singleton, which opens a WS reconnect
150
171
  // loop that keeps the event loop alive. Assertions are done — exit deterministically.
@@ -128,11 +128,27 @@
128
128
  return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
129
129
  }
130
130
 
131
- function renderProjects(projects, activeId) {
131
+ // Derive the Drafted server origin from payload URLs (frameUrl, canvasUrl) so the
132
+ // widget deep-links correctly on local installs and hosted servers alike — never
133
+ // hardcode https://drafted.live (breaks localhost/self-hosted deployments).
134
+ function serverOrigin(sc) {
135
+ const candidates = [
136
+ sc.canvasUrl, sc.serverUrl,
137
+ ...(Array.isArray(sc.projects) ? sc.projects.map(p => p.canvasUrl).filter(Boolean) : []),
138
+ ...(Array.isArray(sc.projects) ? sc.projects.map(p => p.frameUrl).filter(Boolean) : []),
139
+ ...(Array.isArray(sc.entries) ? sc.entries.map(e => e.frameUrl).filter(Boolean) : []),
140
+ ];
141
+ for (const u of candidates) {
142
+ try { return new URL(u).origin; } catch { /* keep looking */ }
143
+ }
144
+ return 'https://drafted.live';
145
+ }
146
+
147
+ function renderProjects(projects, activeId, origin) {
132
148
  if (!projects?.length) return '<div class="empty">No projects yet. Use project(action="create") to start one.</div>';
133
149
  return projects.slice(0, 30).map(p => {
134
150
  const isActive = p.id === activeId;
135
- const url = p.slug ? `https://drafted.live/project/${escapeHtml(p.slug)}` : '#';
151
+ const url = p.slug ? `${origin}/project/${escapeHtml(p.slug)}` : '#';
136
152
  return `
137
153
  <a class="project" href="${url}" target="_blank" rel="noopener">
138
154
  <div>
@@ -170,7 +186,7 @@
170
186
  if (Array.isArray(sc.projects)) {
171
187
  document.getElementById('title').textContent = `${sc.projects.length} project${sc.projects.length === 1 ? '' : 's'}`;
172
188
  document.getElementById('subtitle').textContent = sc.activeProject ? 'One active' : 'None active — use project(action="open") to switch';
173
- root.innerHTML = renderProjects(sc.projects, sc.activeProject);
189
+ root.innerHTML = renderProjects(sc.projects, sc.activeProject, serverOrigin(sc));
174
190
  return;
175
191
  }
176
192
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
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": [
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Start a new project — or a reusable template — with proper search of knowledge, skills, and templates first
2
+ description: Start a new project — or a reusable template — with proper search of knowledge and skills first
3
3
  argument-hint: <project name and what you want to do>
4
4
  ---
5
5
 
@@ -8,16 +8,13 @@ Spin up a project on the surface, or generalize one into a reusable template. Go
8
8
  First ask: **a new project, or a reusable template?** (This command does both.)
9
9
 
10
10
  Search before creating (also satisfies the gates):
11
- 1. `wiki(action="search")` for relevant knowledge. [G1]
12
- 2. `skill(action="search")` for procedures that should be attached.
13
- 3. `template(action="list")` for an existing template to fork. [G3 — also enforced on `project(action="create")`.]
11
+ 1. `fs(search, path/o/<org>/wiki", query="<terms>")` for relevant knowledge. [G1]
12
+ 2. `fs(search, path/o/<org>/skills", query="<topic>")` for procedures that should be attached. [G2/G3]
14
13
 
15
14
  Then:
16
- - If a fitting template exists `project(action="create", templateSlug=...)` to fork it; otherwise create fresh and define the layers.
17
- - `project(action="open")` the new project.
18
- - `frame(action="write")` a real brief at the earliest layer (goal, audience, constraints 6-12 lines, not a placeholder).
19
- - `frame(action="anchor")` the brief so downstream work surfaces it.
20
- - Attach the relevant skills you found with `skill(action="attach")`.
21
- - `focus` the brief so the user watches it land.
15
+ - Create the project — a project is a folder: `fs(mkdir, path/o/<org>/projects/<name>")`. It starts with **no layers**; you define them by writing into them.
16
+ - `fs(write, path/o/<org>/projects/<name>/<layer>/<lane>/brief.md", content=...)` a real brief at the earliest layer (goal, audience, constraints — 6-12 lines, not a placeholder). The layer + lane auto-create.
17
+ - Attach the relevant skills you found via the skill gates.
18
+ - `fs(ls, path/o/<org>/projects/<name>")` to confirm the structure, and hand the user the `projectUrl` from the response.
22
19
 
23
20
  For a **template**: build the layer structure + anchored guidance, then note how to fork it next time. Don't speculatively fill downstream frames — wait for direction.
@@ -6,8 +6,8 @@ argument-hint: <what the procedure is for>
6
6
  Turn a repeatable way of working into a Drafted skill so every future agent in the org follows it. Procedure: $ARGUMENTS
7
7
 
8
8
  Do the searches first (they also satisfy the gates):
9
- 1. `wiki(action="search")` for relevant org knowledge the procedure should reference. [G1]
10
- 2. `skill(action="search")` for prior art — an existing skill to improve instead of duplicating. [G2 — also enforced on `skill(action="add")`.] If a close match exists, prefer `/drafted:improve-skill`.
9
+ 1. `fs(search, path/o/<org>/wiki", query="<terms>")` for relevant org knowledge the procedure should reference. [G1]
10
+ 2. `fs(search, path/o/<org>/skills", query="<topic>")` for prior art — an existing skill to improve instead of duplicating. [G2 — also enforced on writing a new skill.] If a close match exists, prefer `/drafted:improve-skill`.
11
11
 
12
12
  Then define a PROPER procedure, not a vague note:
13
13
  - a clear trigger ("when to use this"),
@@ -15,4 +15,4 @@ Then define a PROPER procedure, not a vague note:
15
15
  - success criteria / what "done right" looks like,
16
16
  - written in the second person so any agent can follow it directly. Keep it sharp (~40 lines).
17
17
 
18
- Show the draft to the user with a proposed `name` (Title Case), one-line `description`, `tags` (3-5), and `triggerPatterns`. After approval, `skill(action="add")`. Confirm with the slug and note it will auto-surface on matching tasks.
18
+ Show the draft to the user with a proposed `name` (Title Case), one-line `description`, `tags` (3-5), and `triggerPatterns`. After approval, write it with `fs(write, path/o/<org>/skills/<slug>", content=...)` — the slug is derived from the name. Confirm with the slug and note it will auto-surface on matching tasks.
@@ -7,9 +7,9 @@ Session-end deposit. Harvest what's durable from this conversation back into the
7
7
 
8
8
  Review the session, then **present the user options for which store(s) to deposit into** — don't auto-decide. Offer any that apply:
9
9
 
10
- - **Knowledge → wiki** — durable facts, decisions, or findings worth keeping. Search first (`wiki(action="search")`) to avoid fragmenting, then `wiki(action="write")`.
10
+ - **Knowledge → wiki** — durable facts, decisions, or findings worth keeping. Search first (`fs(search, path/o/<org>/wiki", ...)`) to avoid fragmenting, then `fs(write, path/o/<org>/wiki/<path>", ...)`.
11
11
  - **Procedure → skill** — a repeatable way of working that emerged. Follow `/drafted:create-skill`.
12
- - **Template → surface** — a reusable project structure that emerged. `template(action="create")` from the current project.
12
+ - **Template → surface** — a reusable project structure that emerged. Build it as a project via `fs(mkdir, ...)` + frames.
13
13
 
14
14
  Show the user the candidate deposits per store, let them pick which to keep, then write the chosen ones with their approval. Confirm what landed where.
15
15
 
@@ -7,9 +7,9 @@ When the user had to correct the work in this project, codify the correction so
7
7
 
8
8
  1. Identify the recurring correction(s) or standing rule from this session.
9
9
  2. Choose the right Drafted-side gate per correction (each counts toward the project's context budget):
10
- - **Project anchor** — a brief, constraint, or style guide that must be in context project-wide: `frame(action="write")` it, then `frame(action="anchor")`. [G5]
11
- - **Attached skill** — a procedure that must be loaded before work: `/drafted:create-skill` (or `skill(action="search")` for an existing one), then `skill(action="attach")`. [G4]
12
- - **Layer rule** — a standing instruction for one stage: set that layer's rules via `project(action="update", layers=...)`. [G6]
10
+ - **Project anchor** — a brief, constraint, or style guide that must be in context project-wide: `fs(write, path/o/<org>/projects/<project>/<layer>/<lane>/<file>", content=...)` it, then anchor it so it surfaces on open. [G5]
11
+ - **Attached skill** — a procedure that must be loaded before work: `/drafted:create-skill` (or `fs(search, path/o/<org>/skills", ...)` for an existing one), then attach it to the project. [G4]
12
+ - **Layer rule** — a standing instruction for one stage: set that layer's rules so work in it is gated. [G6]
13
13
  3. Propose which mechanism for each correction, confirm with the user, then apply.
14
14
  4. Confirm what is now enforced — the next session in this project will be gated on it automatically.
15
15
 
@@ -5,10 +5,10 @@ argument-hint: <which skill, and what's off>
5
5
 
6
6
  Improve an existing org skill when it underperformed in practice. Skill / issue: $ARGUMENTS
7
7
 
8
- 1. `skill(action="search")` then `skill(action="load")` the skill in question — read it fully.
8
+ 1. `fs(search, path/o/<org>/skills", query="<slug>")` then `fs(read, path/o/<org>/skills/<slug>")` the skill in question — read it fully.
9
9
  2. Pinpoint the inefficiency: a missing step, a wrong instruction, an ambiguous trigger, or a step that wastes effort.
10
10
  3. Propose the specific edit to the user — show before/after of the changed steps.
11
- 4. After approval, `skill(action="update")` (the version bumps automatically).
11
+ 4. After approval, `fs(write, path/o/<org>/skills/<slug>", content=<updated>)` — writing an existing skill updates it (the version bumps automatically). If it was archived, writing restores it.
12
12
  5. Confirm what changed so the next agent benefits.
13
13
 
14
14
  Skills are the org's stable processes — every fix compounds across everyone who uses them.
@@ -5,10 +5,10 @@ argument-hint: <what's wrong, or the topic to clean up>
5
5
 
6
6
  Improve the org wiki when knowledge has drifted. Issue/topic: $ARGUMENTS
7
7
 
8
- 1. `wiki(action="search")` (3-5 paraphrased queries) and `wiki(action="read")` the affected pages — don't trust titles, open them.
8
+ 1. `fs(search, path/o/<org>/wiki", query="<terms>")` (3-5 paraphrased queries) and `fs(read, path/o/<org>/wiki/<path>")` the affected pages — don't trust titles, open them.
9
9
  2. Diagnose: duplicate pages, a stale fact, a contradiction, or knowledge fragmented across pages.
10
10
  3. Propose the fix to the user: consolidate duplicates, correct the fact, reconcile the contradiction, or re-link fragments.
11
- 4. Apply with `wiki(action="edit")` (hashline) or `wiki(action="mv")` (which rewrites inbound links). Check `wiki(action="links")` before moving or deleting.
12
- 5. `wiki(action="log")` what changed.
11
+ 4. Apply with `fs(write, path/o/<org>/wiki/<path>", content=<updated>)` (hashline `edit` for surgical changes) or `fs(mv, path/o/<org>/wiki/<from>", to/o/<org>/wiki/<to>")` (which rewrites inbound links). Check what links to a page before moving or archiving it.
12
+ 5. Never hard-delete — `fs(rm, path/o/<org>/wiki/<path>")` moves a page to the archive folder.
13
13
 
14
14
  Leave the wiki more coherent than you found it — fewer, sharper, better-linked pages.
@@ -12,9 +12,9 @@ First decide WHAT to ingest. If it's obvious from the conversation (a research r
12
12
  3. **Interrogate the user** (grill-me) — when the knowledge is in their head. Interview relentlessly, walking ONE branch of the decision tree at a time and proposing your recommended answer at each step, until you reach shared understanding. Then structure what you captured.
13
13
 
14
14
  Then deposit:
15
- - `wiki(action="search")` first (3-5 paraphrased queries) so you don't fragment existing pages.
15
+ - `fs(search, path/o/<org>/wiki", query="<terms>")` first (3-5 paraphrased queries) so you don't fragment existing pages.
16
16
  - Propose the page set to the user before writing.
17
- - Write with `wiki(action="write")`, or `wiki(action="source-register")` + `wiki(action="bulk-write")` for a batch. Cross-link related pages.
18
- - `wiki(action="log")` a one-line entry for the ingest.
17
+ - Write with `fs(write, path/o/<org>/wiki/<path>", content=...)` per page. Cross-link related pages.
18
+ - Confirm what landed where, with the page links.
19
19
 
20
20
  The wiki is the org's durable knowledge — write for the next agent and teammate, not just this session.
@@ -7,8 +7,8 @@ Onboard the user to Drafted — a producibles harness that compounds across thre
7
7
 
8
8
  This is the over-arching prime command: orient, then seed all three stores so every later session starts smart.
9
9
 
10
- 1. **Orient** — in 3-4 lines explain the loop: you *prime* from the harness (the system makes you search the wiki, and auto-loads the project's attached skills, anchors, and layer rules before you work), you *build*, then you *compound* (deposit what you learned). The more it's used, the less searching and the more stable the work.
11
- 2. **Name the org** — call `get_org` to list the user's orgs. Confirm which one the harness should be built in — if unsure, ask then name it explicitly (`org=...`) on every create in the steps below, or bind it once for this session with `get_org(action="use", org="<name>")` and omit it thereafter. Never rely on an "active" org: a project-less create by a multi-org user is refused rather than guessed.
10
+ 1. **Orient** — in 3-4 lines explain the loop: you *prime* from the harness (the system makes you search the wiki, and auto-loads the project's attached skills, anchors, and layer rules before you work), you *build*, then you *compound* (deposit what you learned). The more it's used, the less searching and the more stable the work. Drafted is navigated like a filesystem — one `fs` tool, three roots: `/wiki`, `/skills`, `/projects`.
11
+ 2. **Name the org** — the org is an address, not a cursor: address wiki/skills with `org=...` on the call, and projects self-derive their org from the path. Never rely on an "active" org: a project-less create by a multi-org user is refused rather than guessed.
12
12
  3. **Seed knowledge** — run the `/drafted:ingest` flow: help the user point at existing business materials (folders, docs, past research) or interrogate them for tacit knowledge, and land durable pages in the wiki.
13
13
  4. **Seed procedures** — from those materials and the conversation, surface 1-3 candidate SOPs. For the most valuable, run the `/drafted:create-skill` flow.
14
14
  5. **Seed the surface** — run the `/drafted:create-project` flow for the user's immediate piece of work (or a reusable template).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: drafted
3
- description: Use the Drafted producibles harness — a compounding workspace that uplifts any AI across three primitives: knowledge (the org wiki), procedures (skills), and the project surface (frames on a shared real-time canvas). Prime from the harness before working, build durable artifacts as frames instead of burying output in chat, and deposit what you learned back so the next session starts smarter. When Google Drive is connected, prefer Google Workspace frames for docs, sheets, and slides.
3
+ description: Use the Drafted producibles harness — a compounding workspace that uplifts any AI across three primitives: knowledge (the org wiki), procedures (skills), and the project surface (frames on a shared real-time canvas). Drafted is navigated like a filesystem — one fs tool, one path grammar, org first: `fs(ls, path="/")` lists your orgs, then `/o/<org>/<root>/...`. Prime from the harness before working, build durable artifacts as frames instead of burying output in chat, and deposit what you learned back so the next session starts smarter. When Google Drive is connected, prefer Google Workspace frames for docs, sheets, and slides.
4
4
  ---
5
5
 
6
6
  # Drafted — a producibles harness that compounds
@@ -13,33 +13,37 @@ Drafted makes any AI more effective by giving it a memory and a workspace that g
13
13
 
14
14
  The point is the **compounding loop**: you don't produce in a vacuum, you draw on what the org already knows and leave it richer each pass.
15
15
 
16
- ## The loopprime build compound
17
-
18
- - **Prime (session start).** Pull accumulated value in. The system *enforces* this: you must search the wiki before working, and a project's attached skills, anchored frames, and layer rules are auto-loaded when you open it. Don't fight the gates — they make you start smart.
19
- - **Build (the work).** Produce artifacts as frames on the surface.
20
- - **Compound (session end / when you notice something).** Deposit learning back: capture knowledge, distill or fix a skill, harden the project. This is *your* responsibility — the system can't force it, so do it.
16
+ ## The surface is a filesystem one tool, one path grammar
21
17
 
22
- ## Mental model
18
+ There is exactly **one** tool to know: `fs`. It looks, feels, and behaves like a local filesystem, and an **org is the top folder** — you address an org, you never switch to it:
23
19
 
24
20
  ```
25
- Organization
26
- └── Project (a bounded piece of work, e.g. "Q4 strategy memo")
27
- └── Layer (a stage of thinking, e.g. research → drafts → final)
28
- └── Lane (a group of related frames, e.g. one competitor per lane)
29
- └── Frame (an HTML / markdown / image file)
21
+ fs(ls, path="/") → the orgs you belong to
22
+ fs(ls, path="/o/<org>") → that org's wiki · skills · projects
23
+ /o/<org>/wiki/<path> org knowledge pages (markdown, free nesting)
24
+ /o/<org>/skills/<slug> reusable procedures (flat: one dir per skill)
25
+ /o/<org>/projects/<project>/<layer>/<lane>/<file> frames on the surface (project layer lane file)
30
26
  ```
31
27
 
32
- **Layers are stages** they depend on the project's template. `project(action="create")` returns the active layers; always check rather than assume. **Frames have addresses** like `/layer/lane/filename`; the canvas auto-arranges them by layer (vertical) and lane (horizontal).
28
+ Verbs: `ls` (list) · `read` (cat, hashline-annotated) · `write` (create/overwrite) · `edit` (hashline ops) · `mv` (rename/move) · `rm` (move to archive) · `mkdir` (create a project) · `search` (grep).
29
+
30
+ **A project is a folder.** It starts with **no layers** — writing to `/o/<org>/projects/<name>/<layer>/...` auto-creates the layer (mkdir -p). The agent defines the filesystem structure by writing into it. URLs are the same paths: `https://drafted.live/o/<org>/projects/<project>/<layer>/<lane>/<file>` — the URL's pathname IS the fs path (one canonical link for both humans and agents). Bare `/wiki`, `/skills`, `/projects` roots still work via the session's working org.
31
+
32
+ ## The loop — prime → build → compound
33
+
34
+ - **Prime (session start).** Pull accumulated value in. The system *enforces* this: you must search the wiki before working (G1), and a project's attached skills and anchored frames are required reading when you open it. Don't fight the gates — they make you start smart.
35
+ - **Build (the work).** Produce artifacts as frames on the surface.
36
+ - **Compound (session end / when you notice something).** Deposit learning back: capture knowledge, distill or fix a skill, harden the project. This is *your* responsibility — the system can't force it, so do it.
33
37
 
34
38
  ## The gates you'll encounter (and how to satisfy them)
35
39
 
36
40
  These reset every session. A gate that blocks you tells you exactly what to call next — do it, don't work around it.
37
41
 
38
- - **G1 — wiki search before work.** Before you read or edit anything, `wiki(action="search")` for relevant org knowledge.
39
- - **G2 — prior-art before a new skill.** `skill(action="add")` requires a `skill(action="search")` first.
40
- - **G3 — prior-art before a new project.** `project(action="create")` requires wiki + skill + `template(action="list")` searches first.
42
+ - **G1 — wiki search before work.** Before reading or editing anything, `fs(search, path="/o/<org>/wiki", query="<terms>")` for relevant org knowledge.
43
+ - **G2 — prior-art before a new skill.** Writing a new `/o/<org>/skills/<slug>` requires `fs(search, path="/o/<org>/skills", query="<topic>")` first.
44
+ - **G3 — prior-art before a new project.** Creating a project requires wiki + skill searches first.
41
45
  - **G4 — attached skills** are auto-injected when you open a project. Follow them — they're how the org does this work.
42
- - **G5 — the project's anchored frames** are auto-injected on open. They are required reading (briefs, constraints, style guides).
46
+ - **G5 — the project's anchored frames** are required reading (briefs, constraints, style guides).
43
47
  - **G6 — a layer's rules** are surfaced when you work in that layer. Honor them.
44
48
 
45
49
  ## The commands (when to reach for each)
@@ -55,30 +59,30 @@ These bookend the loop. Prime/feed at the start, deposit at the end.
55
59
  - `/drafted:improve-project-harness` — turn corrections into enforced gates (anchors / attached skills / layer rules).
56
60
  - `/drafted:extract` — session-end: deposit knowledge, a skill, and/or a template (the user picks which).
57
61
 
58
- ## Tool surface (action-based)
59
-
60
- `project(list|open|create|update|move)` · `frame(read|write|edit|anchor|mv|search|…)` · `ls` · `skill(search|load|list|add|update|attach|…)` · `wiki(search|read|write|edit|mv|links|log|…)` · `template(list|create|fork|…)` · `focus` · `get_org` · `asset` · `layer`.
61
-
62
- ## Sign in
63
-
64
- If a tool returns an auth error: on a desktop/CLI agent run the `auth(action="login")` tool (it prints a one-time URL; the user opens it). On the web/Cowork connector, authorization happens via the connector's OAuth — ask the user to re-authorize the Drafted connector in their settings, then retry.
65
-
66
62
  ## Working on the surface
67
63
 
68
- - **Always `project(action="open")` first.** Every read/write operates on the active project. Use `project(action="list")` to find one. Every response includes a `project` field verify it matches your intent before writing.
64
+ - **Navigate by path, exactly like a filesystem.** `fs(ls, path="/")` to see your orgs; `fs(ls, path="/o/<org>/projects")` to see a project list; `fs(ls, path="/o/<org>/projects/<project>")` for layers/lanes; `fs(ls, path="/o/<org>/projects/<project>/<layer>")` for frames. The project is resolved from the path itself no separate "open" step.
65
+ - **Create a project with `fs(mkdir, path="/o/<org>/projects/<name>")`** — or just `fs(write, path="/o/<org>/projects/<name>/<layer>/<lane>/<file>", content=...)` and the project + layer auto-create in the addressed org.
69
66
  - **Default to the surface for substantive artifacts.** When asked to draft, write, plan, analyze, compare, design, document, summarize, report, spec, model, or make a deck/table, create or update frames instead of leaving the durable result only in chat. One visible frame per artifact or section.
70
- - **Prefer Google Workspace when Drive is connected.** Call `get_org`; if it reports `googleDrive.connected: true`, use `frame(action="write", googleType="google-doc"|"google-sheet"|"google-slide", …)` for docs, sheets, and decks; populate immediately with the native write actions.
71
- - **Read before editing.** `frame(action="edit")` uses hashline addressing every line in a `frame(action="read")` response gets a 4-char hash; pass it to edit for surgical changes.
72
- - **`focus` after writing** so the user watches your work land on their surface.
67
+ - **Read before editing.** `fs(read)` returns every line hashline-annotated (`1abc|<content>`); `fs(edit, ops=[{type:"replace", lineHash:"1abc", newContent:"..."}])` targets exactly that line. For partial reads, pass `lines: "2-50"` you get back just that range, still hash-annotated, and can edit within it.
68
+ - **Prefer Google Workspace when Drive is connected.** Use `fs(write, path=".../<name>.google-doc"|".google-sheet"|".google-slide")` for docs, sheets, and decks; populate immediately with the matching native write action.
69
+ - **`fs(mv, from="/o/<org>/projects/<p>/<layer>/<lane>/<file>", to="...")`** renames or moves (cross-project too). **`fs(rm, path="/o/<org>/projects/<project>")` archives** agents never hard-delete; the archive is in the web UI.
70
+ - **Return a clickable link** for what you touched — the `frameUrl`/`projectUrl` in the fs response is the URL the user opens.
73
71
 
74
72
  ## Quality conventions
75
73
 
76
74
  - **Match format to layer intent.** Research/strategy/copy are usually markdown; visual work (wireframes, designs, dashboards) is HTML.
77
- - **Respect the template's conventions.** Read a `design-system` layer before `/designs/`; read an `audience` layer before `/copy/`.
78
75
  - **Wireframes are low-fidelity** (grayscale, placeholders); reserve color and real content for the designs/final layer.
79
76
  - **Choose dimensions to fit content** — `autoSize: true` for HTML, or explicit `width`/`height`.
80
77
  - **Don't re-read unchanged frames** you already have this conversation.
78
+ - **Diagrams: prefer native Excalidraw.** For flowcharts, process maps, architecture/system/data-flow diagrams, or visual maps, write a `.excalidraw` file (scene JSON) and load the `excalidraw-drafted` skill for the authoring guidance. Use HTML/markdown frames for web/UI mockups, rich layouts, or non-editable artifacts.
79
+
80
+ ## Skill authoring
81
+
82
+ - **Author reusable skills in Drafted, not just the local repo.** The portable part — the method, SKILL.md, and script source — belongs in the Drafted skill library: `fs(write, path="/o/<org>/skills/<slug>", content=...)` for a new skill (G2 gate first), or `fs(mv, path="/o/<org>/skills/<old>", to="/o/<org>/skills/<new>")` to rename. Declare how to rebuild in the skill's `setup:` frontmatter so any machine or agent can regenerate it.
83
+ - **Machine-specific build output is never portable.** Build `node_modules`, downloaded browsers, compiled binaries into a `.skillinstall/` directory inside the skill — Drafted always strips it on push and skill push auto-gitignores it, so the rebuildable bundle stays local while the method and recipe live in Drafted.
84
+ - **Improve skills when you find a better way.** Fix or distill a skill that underperformed rather than leaving it stale.
81
85
 
82
86
  ## Surface URL recognition
83
87
 
84
- Any URL containing `/f/{uuid}` is a Drafted frame link. Use `frame(action="read", path=URL)` to get its content and `focus(target=URL)` to pan the canvas to it. Never `WebFetch` Drafted URLs — the MCP tools authenticate properly.
88
+ Any URL containing `/f/{uuid}` is a Drafted frame link `fs(read, path=URL)` gets its content. Canonical links are the fs paths themselves: `/o/<org>/wiki/...`, `/o/<org>/skills/<slug>`, `/o/<org>/projects/<project>/<layer>/<lane>/<file>`. Never `WebFetch` Drafted URLs — the MCP tools authenticate properly.
@@ -22,7 +22,7 @@ export const LAYERS = {
22
22
 
23
23
  // File extensions recognized per layer
24
24
  export const DESIGN_EXTENSIONS = new Set(['.html', '.htm']);
25
- export const DOC_EXTENSIONS = new Set(['.md', '.txt']);
25
+ export const DOC_EXTENSIONS = new Set(['.md', '.txt', '.json', '.csv', '.yaml', '.yml', '.jsonl']);
26
26
  export const EXCALIDRAW_EXTENSIONS = new Set(['.excalidraw']);
27
27
  export const ASSET_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.pdf', '.mp4', '.webm', '.mov', '.m4v']);
28
28