drafted 1.11.12 → 1.11.13

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.
@@ -29,6 +29,8 @@ Skill rules:
29
29
  - Search skills before starting repeatable work: skill(action="search") or skill(action="list").
30
30
  - Load and follow relevant skills with skill(action="load"). Read supporting skill files when needed.
31
31
  - When the user asks to record, distill, create, save, install, or update a skill/procedure/SOP/checklist/method/protocol/playbook, create or update a Drafted skill for the org with skill tools.
32
+ - Every skill has a portable part — its method, SKILL.md, and any script source — and that part always belongs in Drafted, not only in a local repo. When you build a runnable skill, push the source to Drafted (skill action="push", or skill action="add" for prose) and declare how to rebuild it in the skill's `setup:` frontmatter (e.g. ["npm ci"]) so any machine or agent can regenerate it. Do not leave a reusable skill authored only as local files when Drafted is connected.
33
+ - Machine-specific build output (node_modules, downloaded browsers, compiled binaries) is never portable: build it into a `.skillinstall/` directory inside the skill. Drafted always strips `.skillinstall/` from a pushed bundle and skill push auto-gitignores it, so the rebuildable bundle stays local while the method and recipe stay in Drafted.
32
34
  - Improve skills when you find a better checklist, standard, or operating method.
33
35
 
34
36
  Project/producible rules:
package/cli/drafted.mjs CHANGED
@@ -1828,7 +1828,7 @@ skillCmd
1828
1828
  function collectSkillTree(dir) {
1829
1829
  const root = resolve(dir);
1830
1830
  if (!existsSync(root) || !statSync(root).isDirectory()) throw new Error(`not a directory: ${dir}`);
1831
- const SKIP_DIRS = new Set(['node_modules', '.git', '.venv', 'venv', '__pycache__', 'dist', 'build', '.next', 'target', 'coverage', '.cache', '.turbo', '.gradle', 'Pods', '.terraform']);
1831
+ const SKIP_DIRS = new Set(['node_modules', '.git', '.venv', 'venv', '__pycache__', 'dist', 'build', '.next', 'target', 'coverage', '.cache', '.turbo', '.gradle', 'Pods', '.terraform', '.skillinstall']);
1832
1832
  let ignore = () => false;
1833
1833
  const ignPath = join(root, '.skillignore');
1834
1834
  if (existsSync(ignPath)) {
@@ -1854,9 +1854,25 @@ function collectSkillTree(dir) {
1854
1854
  return out;
1855
1855
  }
1856
1856
 
1857
+ // Ensure the pushed source tree's .gitignore excludes the rebuildable bundle dir,
1858
+ // so a skill's machine-specific build output (built into .skillinstall/ by its
1859
+ // `setup:` recipe) can never be committed. The server already strips .skillinstall/
1860
+ // from the bundle (skill-ingest DENY_DIRS) — this keeps the author's git clean too.
1861
+ // Idempotent; returns true only when it adds the entry.
1862
+ function ensureSkillInstallIgnored(dir) {
1863
+ try {
1864
+ const gi = join(dir, '.gitignore');
1865
+ const existing = existsSync(gi) ? readFileSync(gi, 'utf8') : '';
1866
+ if (existing.split(/\r?\n/).some((l) => l.trim().replace(/\/$/, '') === '.skillinstall')) return false;
1867
+ const body = existing && !existing.endsWith('\n') ? existing + '\n' : existing;
1868
+ writeFileSync(gi, body + '.skillinstall/\n');
1869
+ return true;
1870
+ } catch { return false; }
1871
+ }
1872
+
1857
1873
  skillCmd
1858
1874
  .command('push')
1859
- .description('Push a local source tree into a skill (bulk ingest; server strips artifacts + enforces caps)')
1875
+ .description('Push a local source tree into a Drafted skill (bulk ingest). Stores the portable bits — SKILL.md, scripts, and the setup: recipe; build output is stripped server-side. Build machine-specific output into .skillinstall/ (auto-gitignored, always stripped) and declare its rebuild in setup:.')
1860
1876
  .option('--id <id>', 'skill id')
1861
1877
  .option('--slug <slug>', 'skill slug (resolved to id)')
1862
1878
  .requiredOption('--dir <dir>', 'local source tree to push')
@@ -1878,8 +1894,9 @@ skillCmd
1878
1894
  });
1879
1895
  const data = await res.json().catch(() => ({}));
1880
1896
  if (!res.ok) { emitSkillResult(opts.format, { status: 'error', id, error: data.error || `HTTP ${res.status}` }); process.exit(1); }
1881
- if (opts.format === 'json') console.log(JSON.stringify({ status: 'ok', id, hash: data.hash, count: data.count, stripped: data.stripped, files: data.files }));
1882
- else console.log(`ok\t${id}\t${data.hash}\tpushed ${data.count}${data.stripped ? `, stripped ${data.stripped}` : ''}`);
1897
+ const gitignored = ensureSkillInstallIgnored(opts.dir);
1898
+ if (opts.format === 'json') console.log(JSON.stringify({ status: 'ok', id, hash: data.hash, count: data.count, stripped: data.stripped, gitignored, files: data.files }));
1899
+ else console.log(`ok\t${id}\t${data.hash}\tpushed ${data.count}${data.stripped ? `, stripped ${data.stripped}` : ''}${gitignored ? ', gitignored .skillinstall/' : ''}`);
1883
1900
  });
1884
1901
 
1885
1902
  program.parse();
package/install-mcp.sh CHANGED
@@ -443,6 +443,8 @@ Skill rules:
443
443
  - Search skills before starting repeatable work: skill(action="search") or skill(action="list").
444
444
  - Load and follow relevant skills with skill(action="load"). Read supporting skill files when needed.
445
445
  - When the user asks to record, distill, create, save, install, or update a skill/procedure/SOP/checklist/method/protocol/playbook, create or update a Drafted skill for the org with skill tools.
446
+ - Every skill has a portable part — its method, SKILL.md, and any script source — and that part always belongs in Drafted, not only in a local repo. When you build a runnable skill, push the source to Drafted (skill action="push", or skill action="add" for prose) and declare how to rebuild it in the skill's `setup:` frontmatter (e.g. ["npm ci"]) so any machine or agent can regenerate it. Do not leave a reusable skill authored only as local files when Drafted is connected.
447
+ - Machine-specific build output (node_modules, downloaded browsers, compiled binaries) is never portable: build it into a `.skillinstall/` directory inside the skill. Drafted always strips `.skillinstall/` from a pushed bundle and skill push auto-gitignores it, so the rebuildable bundle stays local while the method and recipe stay in Drafted.
446
448
  - Improve skills when you find a better checklist, standard, or operating method.
447
449
 
448
450
  Project/producible rules:
package/mcp/server.mjs CHANGED
@@ -2751,7 +2751,7 @@ tool('layout', 'Auto-arrange frames using graph layout algorithm. Positions conn
2751
2751
  function collectSkillTreeForPush(dir) {
2752
2752
  const root = resolve(dir);
2753
2753
  if (!existsSync(root) || !statSync(root).isDirectory()) throw new Error(`not a directory: ${dir}`);
2754
- const SKIP = new Set(['node_modules', '.git', '.venv', 'venv', '__pycache__', 'dist', 'build', '.next', 'target', 'coverage', '.cache', '.turbo', '.gradle', 'Pods', '.terraform']);
2754
+ const SKIP = new Set(['node_modules', '.git', '.venv', 'venv', '__pycache__', 'dist', 'build', '.next', 'target', 'coverage', '.cache', '.turbo', '.gradle', 'Pods', '.terraform', '.skillinstall']);
2755
2755
  const NUL = String.fromCharCode(0);
2756
2756
  let ignore = () => false;
2757
2757
  const ign = join(root, '.skillignore');
@@ -2778,7 +2778,22 @@ function collectSkillTreeForPush(dir) {
2778
2778
  return out;
2779
2779
  }
2780
2780
 
2781
- tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/guidelines agents can load and follow. Dispatch by `action`: search/load/list for discovery; history for a skill\'s version git-log; add/update/remove for org skills; fork/push for source-only skills; attach/detach for project binding; favorite/unfavorite for personal pins; read_file/update_file for supporting files inside a skill directory.', {
2781
+ // Ensure a pushed source tree's .gitignore excludes the rebuildable bundle dir, so
2782
+ // a skill's machine-specific build output (built into .skillinstall/ by its `setup:`
2783
+ // recipe) can never be committed. The server also strips .skillinstall/ from the
2784
+ // bundle (skill-ingest DENY_DIRS); this keeps the author's git clean. Idempotent.
2785
+ function ensureSkillInstallIgnored(dir) {
2786
+ try {
2787
+ const gi = join(dir, '.gitignore');
2788
+ const existing = existsSync(gi) ? readFileSync(gi, 'utf8') : '';
2789
+ if (existing.split(/\r?\n/).some((l) => l.trim().replace(/\/$/, '') === '.skillinstall')) return false;
2790
+ const body = existing && !existing.endsWith('\n') ? existing + '\n' : existing;
2791
+ writeFileSync(gi, body + '.skillinstall/\n');
2792
+ return true;
2793
+ } catch { return false; }
2794
+ }
2795
+
2796
+ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/guidelines agents can load and follow. When you build a reusable skill, author it HERE — add for prose, push to ingest a local source tree — so its method and setup: recipe live in Drafted and any machine or agent can reuse it; keep only machine-specific build output local in .skillinstall/ (always stripped on push). Dispatch by `action`: search/load/list for discovery; history for a skill\'s version git-log; add/update/remove for org skills; fork/push for source-only skills; attach/detach for project binding; favorite/unfavorite for personal pins; read_file/update_file for supporting files inside a skill directory.', {
2782
2797
  action: z.enum([
2783
2798
  'search', 'load', 'list', 'history',
2784
2799
  'add', 'update', 'remove',
@@ -2805,7 +2820,7 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
2805
2820
  org: z.string().optional().describe('[fork|push|update] resolve/fork into this Drafted org (id or name); scopes the request without switching the session'),
2806
2821
  setup: z.array(z.string()).optional().describe('[add|update] setup command(s) (in order) run on materialize to build a source-only skill, e.g. ["npm ci","npm run build"]'),
2807
2822
  files: z.array(z.object({ path: z.string(), content: z.string() })).optional().describe('[push] source files to push (path + UTF-8 content); server strips artifacts + enforces caps'),
2808
- dir: z.string().optional().describe('[push] local directory to push instead of files[]; walked locally (heavy dirs + .skillignore pre-filtered), server re-enforces'),
2823
+ dir: z.string().optional().describe('[push] local directory to push instead of files[]; walked locally (heavy dirs, .skillinstall/, and .skillignore pre-filtered), server re-enforces. On push the dir\'s .gitignore is auto-updated to exclude .skillinstall/ (the rebuildable bundle).'),
2809
2824
  deleteMissing: z.boolean().optional().describe('[push] remove stored files not present in the pushed set'),
2810
2825
  }, async (args) => {
2811
2826
  try {
@@ -2952,7 +2967,9 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
2952
2967
  let fileList = files;
2953
2968
  if (!fileList && dir) fileList = collectSkillTreeForPush(dir);
2954
2969
  if (!Array.isArray(fileList) || fileList.length === 0) throw new Error('files[] (non-empty) or dir required for action=push');
2955
- return ok(await api('POST', `/api/skills/${id}/files/bulk`, { files: fileList, deleteMissing: !!deleteMissing }, extra));
2970
+ const pushed = await api('POST', `/api/skills/${id}/files/bulk`, { files: fileList, deleteMissing: !!deleteMissing }, extra);
2971
+ if (dir) { try { if (ensureSkillInstallIgnored(dir)) pushed.gitignored = '.skillinstall/'; } catch { /* best-effort */ } }
2972
+ return ok(pushed);
2956
2973
  }
2957
2974
  case 'attach': {
2958
2975
  const { skillId } = args;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.11.12",
3
+ "version": "1.11.13",
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": [