superwiki 0.1.7 → 0.1.8

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.
@@ -452,6 +452,54 @@ function extractWikilinks(body) {
452
452
  return out;
453
453
  }
454
454
 
455
+ // ---------- Closing summary ----------
456
+ // The `## Summary` section sw-summarize writes into a task file when the task is closed. Its
457
+ // "Verification" list holds one numbered entry per "Done when" item: `1. **verified**: ...`.
458
+ const VERDICT_ENTRY = /^(\d+)\.\s+\*\*(verified|failed|unverified)\*\*/;
459
+
460
+ // Line ranges [start, end) of a body's `## ` sections, by heading text. Fenced code is skipped.
461
+ function levelTwoSections(lines) {
462
+ const out = [];
463
+ let fenced = false;
464
+ lines.forEach((line, i) => {
465
+ if (/^\s*(```|~~~)/.test(line)) { fenced = !fenced; return; }
466
+ if (fenced) return;
467
+ const m = /^## +(.*?)\s*$/.exec(line);
468
+ if (!m) return;
469
+ if (out.length) out[out.length - 1].end = i;
470
+ out.push({ title: m[1].toLowerCase(), start: i, end: lines.length });
471
+ });
472
+ return out;
473
+ }
474
+
475
+ // null when the body has no `## Summary`. Otherwise the verdict counts over the "Done when" items
476
+ // (an item without an entry is unverified), `complete` when every item is verified, the section's
477
+ // markdown without its heading (`text`) and the body without the section (`rest`).
478
+ function closingSummary(body) {
479
+ const lines = String(body ?? '').split(/\r?\n/);
480
+ const sections = levelTwoSections(lines);
481
+ const section = sections.find(s => s.title === 'summary');
482
+ if (!section) return null;
483
+ const doneWhen = sections.find(s => s.title === 'done when');
484
+ const items = doneWhen ? lines.slice(doneWhen.start + 1, doneWhen.end).filter(l => /^[-*+]\s+\S/.test(l)).length : 0;
485
+ const inside = lines.slice(section.start + 1, section.end);
486
+ const entries = new Map();
487
+ for (const line of inside) {
488
+ const m = VERDICT_ENTRY.exec(line);
489
+ if (m && !entries.has(Number(m[1]))) entries.set(Number(m[1]), m[2]);
490
+ }
491
+ const counts = { verified: 0, unverified: 0, failed: 0 };
492
+ if (items) for (let n = 1; n <= items; n++) counts[entries.get(n) ?? 'unverified']++;
493
+ else for (const verdict of entries.values()) counts[verdict]++;
494
+ return {
495
+ items,
496
+ ...counts,
497
+ complete: entries.size > 0 && counts.unverified === 0 && counts.failed === 0,
498
+ text: inside.join('\n').trim(),
499
+ rest: [...lines.slice(0, section.start), ...lines.slice(section.end)].join('\n').trim(),
500
+ };
501
+ }
502
+
455
503
  // ---------- Vault ----------
456
504
  const key = name => String(name).toLowerCase();
457
505
  const areaOf = id => (String(id).includes('-') ? String(id).slice(0, String(id).lastIndexOf('-')) : '');
@@ -496,6 +544,8 @@ function buildVault(files) {
496
544
  started: d.started || '', finished: d.finished || '',
497
545
  // Any value asks for a separate review before the task may be done; the value names the kind.
498
546
  review: d.review ? String(d.review) : '',
547
+ // The closing summary's verdicts, or null while the task file has no `## Summary`.
548
+ summary: closingSummary(p.body),
499
549
  state: null, wave: 0, dependents: [], plan: null,
500
550
  });
501
551
  }
@@ -610,6 +660,8 @@ function lint(vault) {
610
660
  const softOpen = t.openSoftDeps.filter(id => taskOf(vault, id));
611
661
  if (t.status === 'in-progress' && hardOpen.length) add('error', 'started-before-deps', path, `in-progress but not done: ${hardOpen.join(', ')}`);
612
662
  if (t.status === 'done' && (hardOpen.length || softOpen.length)) add('error', 'done-before-deps', path, `done but not done: ${[...hardOpen, ...softOpen].join(', ')}`);
663
+ // A done task without a summary predates the gate and is fine; a summary that is there must hold.
664
+ if (t.status === 'done' && t.summary && !t.summary.complete) add('error', 'done-unverified', path, `done but summary has ${t.summary.unverified} unverified, ${t.summary.failed} failed`);
613
665
  if ((t.status === 'in-progress' || t.status === 'done') && !t.started) add('warn', 'missing-date', path, '`started` is empty');
614
666
  if (t.status === 'done' && !t.finished) add('warn', 'missing-date', path, '`finished` is empty');
615
667
  }
@@ -1476,14 +1528,34 @@ function taskSections(t) {
1476
1528
  ${cell('Chain', `${up.size} ↑ · ${down.size} ↓`)}
1477
1529
  </div>
1478
1530
  ${callout}
1531
+ ${t.summary ? `<div class="sec"><h4>Summary <span class="muted">${verdictCounts(t.summary)}</span></h4><div class="n-body n-summary"></div></div>` : ''}
1479
1532
  <div class="sec"><h4>Depends on <span class="muted">direct; open ▸ to follow the chain; dashed = soft</span></h4><div class="tree">${treeHtml(deps, 'deps')}</div></div>
1480
1533
  <div class="sec"><h4>Blocks <span class="muted">tasks waiting on this one</span></h4><div class="tree">${treeHtml(dependents, 'dependents')}</div></div>
1481
- <div class="sec"><h4>Details <span class="muted">${esc(t.page.path)}</span></h4><div class="n-body"></div></div>
1534
+ <div class="sec"><h4>Details <span class="muted">${esc(t.page.path)}</span></h4><div class="n-body n-details"></div></div>
1482
1535
  ${linkedFrom(t.page)}`;
1483
- el.querySelector('.n-body').append(renderMd(t.page.body, t.page));
1536
+ // The closing summary has its own section above, so "Details" shows the body without it.
1537
+ if (t.summary) el.querySelector('.n-summary').append(badgeVerdicts(renderMd(t.summary.text, t.page)));
1538
+ el.querySelector('.n-details').append(renderMd(t.summary ? t.summary.rest : t.page.body, t.page));
1484
1539
  return el;
1485
1540
  }
1486
1541
 
1542
+ // The closing summary's verdicts wear the status colours: verified as done, unverified as unknown, failed as blocked.
1543
+ const VERDICT_CLASS = { verified: 'st-done', unverified: 'st-unknown', failed: 'st-blocked' };
1544
+
1545
+ function verdictCounts(s) {
1546
+ return Object.keys(VERDICT_CLASS).filter(v => s[v]).map(v => `<span class="badge ${VERDICT_CLASS[v]}">${s[v]} ${v}</span>`).join(' ');
1547
+ }
1548
+
1549
+ // In a rendered summary, the bold verdict that opens a numbered entry becomes a badge.
1550
+ function badgeVerdicts(root) {
1551
+ for (const li of root.querySelectorAll('ol > li')) {
1552
+ const strong = li.querySelector(':scope > strong:first-child, :scope > p:first-child > strong:first-child');
1553
+ const cls = strong && VERDICT_CLASS[strong.textContent.trim()];
1554
+ if (cls && !strong.previousSibling?.textContent.trim()) strong.classList.add('badge', cls);
1555
+ }
1556
+ return root;
1557
+ }
1558
+
1487
1559
  function renderDrawer() {
1488
1560
  const page = pageByPath.get(ui.selected);
1489
1561
  if (!page) return;
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  // Scaffolds docs/ as a Superwiki vault. Safe to re-run: user content is kept, tool files are
3
- // replaced with this version, and the report says which was which.
3
+ // replaced with this version, and the report says which was which. On request it also keeps a
4
+ // copy of the Superwiki skills in the project, for agents that start from a clone.
4
5
  import { spawnSync } from 'node:child_process';
5
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
6
+ import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
6
7
  import { basename, dirname, join, resolve } from 'node:path';
7
8
  import { fileURLToPath } from 'node:url';
8
9
 
9
- const ASSETS = join(dirname(fileURLToPath(import.meta.url)), '..', 'assets');
10
+ const SKILL_DIR = join(dirname(fileURLToPath(import.meta.url)), '..');
11
+ const ASSETS = join(SKILL_DIR, 'assets');
12
+ // The folder of a project each agent reads skills from. Codex and Copilot share one.
13
+ const SKILL_FOLDERS = { claude: '.claude/skills', codex: '.agents/skills', copilot: '.agents/skills' };
14
+ // An empty file in a skill folder that Superwiki copied; the installer writes the same one.
15
+ const INSTALLED_MARKER = '.sw-installed';
10
16
  const AREA_ID = /^[A-Za-z][A-Za-z0-9]*$/;
11
17
  const MANAGED_BLOCK = /<!-- sw:start[\s\S]*?<!-- sw:end -->/;
12
18
  // What a vault consists of at the top of docs/. Anything else there belongs to someone else.
@@ -15,18 +21,23 @@ const ROLES = ['plan', 'implement', 'review'];
15
21
  const NEW_INDEX = '# Index\n\n## Wiki\n\nCatalog of the wiki: one line per page, `- [[file-name]]: summary`, grouped by type.\n';
16
22
 
17
23
  const HELP = `init.mjs [--root <dir>] [--tasks | --no-tasks] [--areas "M=Mobile,B=Backend"]
24
+ [--skills claude,codex | --no-skills]
18
25
 
19
- --root project folder (default: current directory)
20
- --tasks add the task module (docs/tasks, docs/plans)
21
- --no-tasks wiki only
22
- --areas task id prefixes and their names (default: T=Tasks)
26
+ --root project folder (default: current directory)
27
+ --tasks add the task module (docs/tasks, docs/plans)
28
+ --no-tasks wiki only
29
+ --areas task id prefixes and their names (default: T=Tasks)
30
+ --skills also keep the Superwiki skills in the project, for the agents named:
31
+ claude (.claude/skills), codex and copilot (.agents/skills), or all.
32
+ Commit those folders: agents that start from a clone have no home folder
33
+ --no-skills stop keeping them in the project (the default; existing copies are not deleted)
23
34
 
24
- Run without --tasks or --no-tasks on an existing vault to upgrade it with its saved choices.`;
35
+ Run without flags on an existing vault to upgrade it: the upgrade reuses the saved choices.`;
25
36
 
26
37
  class UsageError extends Error {}
27
38
 
28
39
  function parseArgs(argv) {
29
- const options = { root: '.', tasks: null, areas: {}, help: false };
40
+ const options = { root: '.', tasks: null, areas: {}, skills: null, help: false };
30
41
  for (let i = 0; i < argv.length; i++) {
31
42
  const arg = argv[i];
32
43
  if (arg === '--help') options.help = true;
@@ -34,11 +45,25 @@ function parseArgs(argv) {
34
45
  else if (arg === '--tasks') options.tasks = true;
35
46
  else if (arg === '--no-tasks') options.tasks = false;
36
47
  else if (arg === '--areas') options.areas = parseAreas(argv[++i] ?? '');
48
+ else if (arg === '--skills') options.skills = parseSkillAgents(argv[++i] ?? '');
49
+ else if (arg === '--no-skills') options.skills = [];
37
50
  else throw new UsageError(`unknown argument: ${arg}\n\n${HELP}`);
38
51
  }
39
52
  return options;
40
53
  }
41
54
 
55
+ function parseSkillAgents(text) {
56
+ const known = Object.keys(SKILL_FOLDERS);
57
+ const agents = new Set();
58
+ for (const name of text.split(',').map(part => part.trim()).filter(Boolean)) {
59
+ if (name === 'all') known.forEach(agent => agents.add(agent));
60
+ else if (known.includes(name)) agents.add(name);
61
+ else throw new UsageError(`bad agent "${name}" in --skills: ${known.join(', ')} or all`);
62
+ }
63
+ if (!agents.size) throw new UsageError(`--skills needs at least one agent: ${known.join(', ')} or all`);
64
+ return [...agents];
65
+ }
66
+
42
67
  function parseAreas(text) {
43
68
  const areas = {};
44
69
  for (const pair of text.split(',').map(part => part.trim()).filter(Boolean)) {
@@ -86,7 +111,8 @@ function readConfig(path) {
86
111
  return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : null;
87
112
  }
88
113
 
89
- function buildConfig(previous, { root, tasks, areas }) {
114
+ // `skills` lists the agents whose project folder holds a copy of the skills; [] means none.
115
+ function buildConfig(previous, { root, tasks, areas, skills }) {
90
116
  const models = Object.fromEntries(ROLES.map(role => [role, previous?.models?.[role] ?? {}]));
91
117
  const chosenAreas = Object.keys(areas).length ? areas : previous?.areas ?? { T: 'Tasks' };
92
118
  return {
@@ -95,6 +121,7 @@ function buildConfig(previous, { root, tasks, areas }) {
95
121
  tasks,
96
122
  areas: tasks ? chosenAreas : {},
97
123
  models,
124
+ skills: skills ?? previous?.skills ?? [],
98
125
  ...(previous?.tools ? { tools: previous.tools } : {}),
99
126
  };
100
127
  }
@@ -165,6 +192,61 @@ function writeAgentRules(root, tasks, report) {
165
192
  }
166
193
  }
167
194
 
195
+ // A name the config holds that this version does not know has no folder and is skipped.
196
+ const skillFolders = agents => [...new Set(agents.map(agent => SKILL_FOLDERS[agent]).filter(Boolean))];
197
+ const sameFolder = (a, b) => existsSync(a) && existsSync(b) && realpathSync(a) === realpathSync(b);
198
+
199
+ // Every file under a folder, by its path relative to that folder.
200
+ function filesIn(folder, prefix = '') {
201
+ const files = new Map();
202
+ for (const entry of readdirSync(folder, { withFileTypes: true })) {
203
+ const path = join(folder, entry.name);
204
+ const name = `${prefix}${entry.name}`;
205
+ if (entry.isDirectory()) for (const [inner, content] of filesIn(path, `${name}/`)) files.set(inner, content);
206
+ else files.set(name, readFileSync(path));
207
+ }
208
+ return files;
209
+ }
210
+
211
+ function sameSkill(source, copy) {
212
+ const expected = filesIn(source);
213
+ const found = filesIn(copy);
214
+ expected.delete(INSTALLED_MARKER);
215
+ found.delete(INSTALLED_MARKER);
216
+ return expected.size === found.size && [...expected].every(([name, content]) => found.get(name)?.equals(content));
217
+ }
218
+
219
+ // A copy of every sw-* skill that sits next to this one goes into the project folders the
220
+ // chosen agents read. The source is the folder this script was installed into, so the copy needs
221
+ // neither the installer nor the network and works the same from a plugin.
222
+ function writeSkills(root, agents, report) {
223
+ const source = join(SKILL_DIR, '..');
224
+ if (Object.values(SKILL_FOLDERS).some(folder => sameFolder(source, join(root, folder)))) {
225
+ return report.line('note', 'skills in the repository left alone: init.mjs runs from the project\'s own copy; update them with "npx superwiki install --project . <targets>"');
226
+ }
227
+ const names = readdirSync(source).filter(name => name.startsWith('sw-') && existsSync(join(source, name, 'SKILL.md'))).sort();
228
+ for (const folder of skillFolders(agents)) {
229
+ mkdirSync(join(root, folder), { recursive: true });
230
+ for (const name of names) {
231
+ const from = join(source, name);
232
+ const to = join(root, folder, name);
233
+ const label = `${folder}/${name}/`;
234
+ const present = lstatSync(to, { throwIfNoEntry: false });
235
+ // Superwiki's own: a real folder carrying the marker. A link, or a folder without it, is someone else's.
236
+ if (present && !(present.isDirectory() && existsSync(join(to, INSTALLED_MARKER)))) {
237
+ report.line('kept', `${label} (not installed by Superwiki)`);
238
+ } else if (present && sameSkill(from, to)) {
239
+ report.line('unchanged', label);
240
+ } else {
241
+ if (present) rmSync(to, { recursive: true, force: true });
242
+ cpSync(from, to, { recursive: true });
243
+ writeFileSync(join(to, INSTALLED_MARKER), '');
244
+ report.line(present ? 'updated' : 'created', label);
245
+ }
246
+ }
247
+ }
248
+ }
249
+
168
250
  // What was in docs/ before the first init and is not part of a vault.
169
251
  function foreignEntries(docs) {
170
252
  if (!existsSync(docs)) return [];
@@ -189,12 +271,14 @@ function main(argv) {
189
271
  const report = createReport(root);
190
272
  writeVault(docs, tasks, report);
191
273
  if (tasks) writeTaskBoard(docs, report);
192
- const config = buildConfig(previous, { root, tasks, areas: options.areas });
274
+ const config = buildConfig(previous, { root, tasks, areas: options.areas, skills: options.skills });
193
275
  report.write(configPath, JSON.stringify(config, null, 2) + '\n');
194
276
  writeAgentRules(root, tasks, report);
277
+ if (config.skills.length) writeSkills(root, config.skills, report);
195
278
 
196
279
  console.log(report.lines.join('\n'));
197
- console.log(`\nvault: docs/ tasks: ${tasks ? `on (areas: ${Object.keys(config.areas).join(', ')})` : 'off'}`);
280
+ const taskState = tasks ? `on (areas: ${Object.keys(config.areas).join(', ')})` : 'off';
281
+ console.log(`\nvault: docs/ tasks: ${taskState} skills: ${skillFolders(config.skills).join(', ') || 'not in the repository'}`);
198
282
  if (foreign.length) {
199
283
  const shown = `${foreign.slice(0, 8).join(', ')}${foreign.length > 8 ? ', ...' : ''}`;
200
284
  // After sw-migrate the task files are already there; only a first init on old docs needs the hint.
@@ -28,6 +28,7 @@ Two passes. The first is a script and costs almost nothing. The second reads pag
28
28
  | Finding | Why it needs a human |
29
29
  |---|---|
30
30
  | `started-before-deps`, `done-before-deps`, `dep-cycle`, `cancelled-dep` | either the status or the dependency is wrong; only the user knows which |
31
+ | `done-unverified` | the task is `done` but its `## Summary` holds an unverified or failed item. Run sw-summarize for it; do not edit a verdict by hand |
31
32
  | `missing-date` you could not fill | no history to take it from, or it hangs on a finding above |
32
33
  | `duplicate-name` | one of the pages must be renamed and every link to it re-pointed |
33
34
  | `broken-link` with no clear target, `orphan-plan`, `unknown-dep` | the page may be missing or the reference stale |
@@ -18,9 +18,11 @@ Needs the task module (`docs/tasks/`). If it is missing, say so and offer sw-ini
18
18
  - write `docs/tasks/<ID>.md` from `docs/.sw/templates/task.md` with `status: todo`, a "Goal" and a "Done when" list;
19
19
  - append `## [date] task | <ID> created` to `docs/log.md`;
20
20
  - run `node docs/.sw/sw.mjs board`, so the task list in `index.md` shows it.
21
- 2. **Does it need a plan?** Judge from the task file alone. A task is small when all of these hold: one area, three "Done when" items or fewer, nothing left open in its notes, and the change it describes is confined to a few files. A small task needs no plan: say so and offer `sw-implement <ID>` directly. Go on with planning only if the user wants a plan anyway, or the task is not small.
21
+ 2. **Does it need a plan?** Judge from the task file alone. A task is small when all of these hold: one area, three "Done when" items or fewer, nothing left open in its notes, and the change it describes is confined to a few files. A small task needs no plan: say so and offer `sw-implement <ID>` directly. Go on with planning only if the user wants a plan anyway, or the task is not small. Called from sw-do: the route is already chosen; skip this step.
22
22
  3. **Clarify.** Ask the user only what the task file leaves open about scope or intent, one question at a time, each with your recommendation. Add the answers to the task's "Notes" now. Do not read code to find questions; the planner surfaces the technical ones. Skip this when nothing is open.
23
- 4. **Dispatch the planner.** Its prompt is: the task id, today's date, the project root if it is not your working directory, and any feedback from an earlier round. It writes the plan file as a draft and returns a short message.
23
+ 4. **Dispatch the planner.** Its prompt is: the task id, today's date, the project root if it is not your working directory, and, from the second round on, what the user's reply changed: the answers that differ from the ones the plan assumed, the feedback, the ids of the tasks a split created, or `split declined`. It writes the plan file as a draft and returns a short message.
24
+
25
+ A second round goes to the planner that wrote the draft where the tool can continue it (a further message to that agent), otherwise to a fresh one.
24
26
 
25
27
  | Tool | How |
26
28
  | --- | --- |
@@ -29,24 +31,42 @@ Needs the task module (`docs/tasks/`). If it is missing, say so and offer sw-ini
29
31
  | Copilot CLI | `task` tool with agent `sw-planner` |
30
32
  | No subagents available, or the agent is not defined | follow `planner.md` yourself, in this session, and tell the user the configured model was not used |
31
33
 
32
- 5. **Get approval.** Show the user the planner's `Approach`, its `Questions` (each with the assumed answer as your recommendation) and its `Split`, and name the plan file so they can read it. Do not read the plan file into your own context unless the user asks you about its content. (Claude Code: enter plan mode with `EnterPlanMode` now and present through `ExitPlanMode`, if those tools are available; anywhere else, a normal message.)
33
- - An answer differs from what the plan assumed, or the user wants changes: dispatch the planner again with the answers or feedback; it revises the file.
34
- - The planner proposes a split: create the tasks (step 1, "New work") only when the user agrees.
35
- - The user drops the plan: delete the draft file.
34
+ 5. **Present the plan and get approval.** Show the user, in a normal message, the planner's `Approach`, its `Questions` (each with the assumed answer as your recommendation), its `Split`, and the path of the plan file so they can read it. Plan mode is not used: a reply can need files written before the plan is shown again. Read the plan file into your own context only if the user asks you about its content.
35
+
36
+ Handle the reply in this order:
37
+ 1. The user agrees to a split: carry it out first, as "An agreed split" below says.
38
+ 2. An answer differs from the one the plan assumed, the user wants changes, or the user declines a split: dispatch the planner again (step 4) with all of it in one prompt, and with the ids of any tasks the split created. Then present the revised plan the same way and wait for the reply again.
39
+ 3. The user accepts the plan as presented, with nothing left to change: that is the approval; go to step 6. A split agreed in the same reply needs no second round, since the plan already covers only the items that stay.
40
+ 4. The user drops the plan: delete the draft file and stop. Tasks a split already created stay; say so.
41
+
42
+ Called from sw-do, every presentation up to the approval belongs to its one planned stop.
36
43
  6. **Record**, after approval:
37
- - in the plan file, change `status: draft` to `status: approved`;
44
+ - in the plan file, change `status: draft` to `status: approved`: read only its frontmatter (the first lines, up to the closing `---`) and change that line with your edit tool;
38
45
  - in the task's "Notes", the answers given in step 5;
39
46
  - in the area guide, if `node docs/.sw/sw.mjs explain <ID>` names one, the planner's `Guide:` lines, one line per fact;
40
47
  - in `docs/log.md`, a new entry `## [date] plan | <ID>`, in the layout the log's last entries use.
41
48
 
42
49
  Then run `node docs/.sw/sw.mjs lint`. If it reports `stale-board`, a task's title, milestone or dependencies changed along the way: run `node docs/.sw/sw.mjs board`.
43
- 7. **Stop.** Do not start implementing. Tell the user the plan is approved and that sw-implement `<ID>` runs it.
50
+ 7. **Stop.** Do not start implementing. Tell the user the plan is approved and that sw-implement `<ID>` runs it. Called from sw-do: return to it; it continues with sw-implement.
51
+
52
+ ## An agreed split
53
+
54
+ The planner's `Split:` names, per new task, its title, its dependencies and the "Done when" items of `<ID>` it takes over. Once the user agrees:
55
+
56
+ 1. For each new task:
57
+ - its id from `node docs/.sw/sw.mjs next-id <AREA>`, where `<AREA>` is the id prefix of `<ID>` (`T` for `T-11`);
58
+ - `docs/tasks/<NEW>.md` from `docs/.sw/templates/task.md`, without the template's comment: `status: todo`; the title and `deps` the `Split:` line names; `review:`, `milestone:` and `priority:` copied from `<ID>`; `soft_deps:` empty; a "Goal" of one or two lines drawn from the items it takes over; under "Done when" those items, word for word; under "Sources" every link of `<ID>`'s "Sources"; in "Notes" `- Split from [[<ID>]] on <date>.`, then a copy of every note of `<ID>` and every answer the user gave in this round that bears on the moved items, so that the new task is later planned on them;
59
+ - in `docs/log.md`, `## [date] task | <NEW> created`.
60
+ 2. In `docs/tasks/<ID>.md`: remove the moved items from "Done when"; add to `deps` each new task the `Split:` line says `<ID>` must wait for; add to "Notes" `- Split <date>: "Done when" items <n, ...> moved to [[<NEW>]].`, numbered as the items stood before the split, one line per new task.
61
+ 3. Run `node docs/.sw/sw.mjs board`.
44
62
 
45
63
  ## Common mistakes
46
64
 
47
65
  - Planning a small task. The plan costs more than the work.
48
66
  - Exploring the codebase before dispatching, or reading the plan back. You pay for it twice.
49
67
  - Editing the plan yourself. If something in it is wrong, send it back to the planner.
50
- - Setting the task to `in-progress`. Planning does not change status.
68
+ - Setting the task to `in-progress`. Planning does not change status; only sw-run marks a task started before planning it.
69
+ - Creating the tasks of a proposed split before the user agrees to it.
70
+ - Approving a revised plan the user has not been shown.
51
71
  - Planning several tasks in one plan file. One task, one plan; shared design goes to a `type: decision` wiki page that the plans link.
52
72
  - Adding the new task to the list in `index.md` by hand. `board` writes that list from the task files.
@@ -5,82 +5,97 @@ description: Use when the user wants several Superwiki tasks worked through one
5
5
 
6
6
  # sw-run
7
7
 
8
- Works through tasks in order, unattended: for each one, plan it if it needs a plan, implement it, have it reviewed if it requires that, record it, and go on to the next. You are the orchestrator. Every piece of work goes to a subagent with a clean context; your session holds only the queue, the reports and the decisions.
9
-
10
- Each task is run exactly as sw-implement runs it. This skill adds what a run of many needs: answers agreed once at the start, decisions you make in the user's place and write down, and rules for when to stop.
8
+ Works through tasks in order, unattended, each one as the single-task skills run it. You are the orchestrator: subagents with clean contexts do the work; your session holds the queue, the reports and the decisions.
11
9
 
12
10
  Run commands from the project root.
13
11
 
12
+ ## The skills a run follows
13
+
14
+ Load each once and follow it for every task:
15
+
16
+ - **sw-do**: step 1's `check` table, steps 2 and 3 (the rubric, the `Route:` line) and step 4's medium route (the Approach note);
17
+ - **sw-plan**: steps 4 to 6 and "An agreed split", on the large route;
18
+ - **sw-implement**: steps 2 to 10 and "Dispatching";
19
+ - **sw-summarize**: all of it, as sw-implement step 8 calls it.
20
+
21
+ This skill takes the place of their other steps.
22
+
14
23
  ## Before the first task
15
24
 
16
- 1. **Scope.** Which tasks: an area, a list of ids, or everything that becomes ready. Take it from the user's message. Default order is the order of `node docs/.sw/sw.mjs ready`.
17
- 2. **Standing answers.** The user will not be asked again, so these are settled now. Ask only for the ones their message leaves open, in one question:
25
+ 1. **Scope**, from the user's message: an area, a list of ids, or everything that becomes ready. An area is a task id prefix; `areas` in `docs/.sw/config.json` maps each prefix to its name, so "the backend area" is the prefix named Backend. Its tasks are the ids with that prefix that `node docs/.sw/sw.mjs ready` lists at the start, in its order; tasks that become ready later join only when the message asks for them. No prefix matches: ask, or, when questions are ruled out, stop and say so.
26
+ 2. **Standing answers**, settled now, since the user will not be asked again:
18
27
 
19
- | Question | If the user does not say |
28
+ | Question | If the message does not answer it |
20
29
  | --- | --- |
21
- | May plans be approved without them? | yes; that is what unattended means |
22
- | Which `needs:` checks (starting services, changing data) may run? | none |
30
+ | May plans and proposed splits be approved without them? | yes |
31
+ | Which checks that start a service, need a running stack or change data may run? | none |
23
32
  | Commit after each task? Push? | no commit, no push |
24
33
  | Go on with other tasks when one stops? | no; stop the run |
25
34
 
35
+ A question is answered only where the message speaks to it: "finish them all" sets the scope and "don't ask me" rules out questions, and neither answers one; "don't commit" answers commit, not push. Ask for the open ones in one question. When the message rules out questions, ask nothing: write one line that starts `Standing answers:` and gives all four, the user's where given and the default otherwise, and start.
26
36
  3. **Check that the run can do what was agreed**, before any task is touched:
27
37
  - `node docs/.sw/sw.mjs lint`: errors are fixed or reported first.
28
- - Commits were agreed: run one harmless git command. If the project's settings or the tool refuse git, say so now. Do not start a run whose terms cannot be met, and do not look for another way to run the refused command.
29
- - The working tree holds changes that belong to no task of this run: say what they are and ask once whether to leave them or commit them first.
30
- 4. **A clean session per task.** You cannot open a new session; a fresh subagent per role is the clean context. If the user asked for sessions, say this is how it is done.
38
+ - Commits were agreed: run one harmless git command. Refused by the project's settings or the tool: say so now, and neither start the run nor look for another way to run the command.
39
+ - Changes in the working tree that belong to no task of this run: say what they are and ask once whether to leave them or commit them first. Questions ruled out: leave them.
31
40
 
32
41
  ## Each task
33
42
 
34
- 1. **Next task**: `node docs/.sw/sw.mjs ready`. A task in progress comes first, then the first ready task in scope. None left: go to "The report".
35
- 2. **Run it as sw-implement does**: gate, mark it started, checks that need the environment, implementer, judge the report, review if required, record, task list. Load sw-implement once and follow it for every task. What differs in a run:
36
-
37
- | In sw-implement or sw-plan | In a run |
38
- | --- | --- |
39
- | a task that is not small: "recommend sw-plan and let the user choose" | plan it. Mark the task started first, then dispatch the planner as sw-plan step 4 says |
40
- | the planner's `Questions` go to the user | answer each with the planner's assumed answer, unless the task file, a wiki page or a recorded lesson says otherwise (`node docs/.sw/sw.mjs search <words>`). Write every answer into the task's "Notes" as `decided without the user: ...` |
41
- | the plan waits for approval | approve it, if that was agreed, with a log entry that says it was approved under the run's standing answer |
42
- | a `differs` item is the user's call | send it back to the implementer once with the task's wording. Still differs: stop |
43
- | offers after a task (wiki pages, lessons, follow-up tasks) | do not ask and do not act. List them in the report |
44
-
45
- 3. **Close the task.** Commit, if agreed: only the files this task changed, message `<ID>: <title>`. Push only if agreed. A refused commit or push stops the run.
46
- 4. **Check your own size**: `node docs/.sw/sw.mjs stats`, row `main`. If its `peak` is above 200k tokens, stop here, between tasks: every further step would pay for all of it. Tell the user to start a new session and run sw-run again; the queue is in the files, so nothing is lost.
43
+ 1. **Next task**: `node docs/.sw/sw.mjs ready`. A task in scope that is in progress comes first, then the first ready one in scope. None left: go to "The report".
44
+ 2. **Gate**: sw-implement step 2, with sw-do's `check` table. A task whose plan is approved goes to step 5; one already in progress skips step 3.
45
+ 3. **Mark it started** (sw-implement step 3), before routing.
46
+ 4. **Route it** with sw-do's rubric, from the task file alone, and say its `Route:` line, as sw-do step 3 does. A draft plan means large.
47
+ - Small: step 5.
48
+ - Medium: the `Approach (sw-do, <date>):` note into "Notes", as sw-do writes it, unless one is there; then step 5.
49
+ - Large: sw-plan steps 4 to 6; step 6 runs whole, so the plan is `approved`.
50
+ 5. **Implement**: sw-implement steps 4 to 10, with the differences below. Step 10 keeps the area guide for every task.
51
+ 6. **Close the task**, also one that stops the run:
52
+ - `node docs/.sw/sw.mjs lint`. An error stops the run.
53
+ - Commit, if agreed and the task is `done`: the files under its Summary's `### Changes` plus its bookkeeping (task file, plan, `docs/log.md`, `docs/index.md`, the area guide if changed), message `<ID>: <title>`. Push only if agreed. A refused commit or push stops the run.
54
+ 7. **Check your own size**: `node docs/.sw/sw.mjs stats`, row `main`. A `peak` above 200k tokens stops the run here, between tasks.
55
+
56
+ ### What differs in a run
57
+
58
+ | In the single-task skills | In a run |
59
+ | --- | --- |
60
+ | sw-implement step 2: a draft plan stops; a task that is not small and has no plan goes to the user | step 4's route decides |
61
+ | sw-plan step 5: the user sees the plan, answers its questions, approves it and any split | nothing is presented. Each question gets the assumed answer, unless the task file, a wiki page or a lesson says otherwise (`sw.mjs search`); the plan and any split are approved under the standing answer. A split's new task joins the queue only if it is in scope |
62
+ | sw-plan step 6: the answers go into "Notes"; the `plan` log entry | the answers go into "Notes" once, as decisions made without the user; the log entry gets the body line `Approved under the run's standing answer.` |
63
+ | sw-implement steps 4 and 6: the user allows checks that need the environment | the standing answer, no question: unattended means fewer questions, not more permission |
64
+ | sw-implement step 6: a `differs` item is the user's call | one fix round, its entry the item in the task's wording |
65
+ | sw-implement steps 11 and 12: offers, and the report | offers, open decisions and open findings go into the run's report; the rest is in each task's Summary |
66
+
67
+ ### Decisions made without the user
68
+
69
+ Every answer you give in the user's place, recorded where it was made:
70
+
71
+ - on a task (a planner question answered, a plan or split approved, a `differs` item sent back): in its "Notes" as `- <date>, decided without the user: ...`, and in the report with its id;
72
+ - for the run (each default in the `Standing answers:` line, working-tree changes left alone): in the report under `run` and nowhere else, even when it later decides something on a task, as a check not allowed does.
47
73
 
48
74
  ## When to stop
49
75
 
50
- Stop the run, leave the task `in-progress` with what is open in its "Notes", and report. Do not skip the task and take the next one unless that was agreed and the next task does not depend on it.
76
+ A stop on a task fires once the task is closed (step 6), with its summary and outcome recorded; a summary with an `unverified` or `failed` item takes step 9's `blocked: <n> unverified, <m> failed` row. Then stop and report, also on the last task in scope. Take the next task instead only if agreed and independent of this one.
51
77
 
52
- - A requirement is `not met` or still `differs` after one more round with the implementer.
53
- - The review still says `changes needed` after two rounds.
54
- - The task cannot be verified without a `needs:` check that was not allowed.
55
- - A commit or push that was agreed is refused.
56
- - `lint` reports an error after the task was recorded.
57
- - Your `peak` is above 200k (this one is between tasks, with nothing left open).
78
+ - A requirement is `not met`, or a fix round reports an entry `not fixed`. A `differs` item not fixed is a difference the user has not decided: `unverified` in the summary.
79
+ - The review after the second fix round still says `changes needed`.
80
+ - The summary leaves an item `failed` or `unverified`, also one whose check was not allowed: that stop too fires after the summary, never before the work, with the task built and reviewed where required.
81
+ - `lint` reports an error, or an agreed commit or push is refused.
82
+ - A large task, when plans may not be approved without the user: stop before its planner.
83
+ - Your `peak` is above 200k.
58
84
 
59
85
  ## Keeping the run cheap
60
86
 
61
- A run pays for the orchestrator's context once per step, for every task. What keeps it small:
62
-
63
- - **A fresh subagent for every task and every role.** Never continue the agent that planned or implemented the previous task.
64
- - **Prompts as the role files ask**: task id, date, project root, the checks it may run, the standing answers that concern it. No reading lists and no project summary; each role knows what to read.
65
- - **Reports, not files.** Do not read code, plans or the other tasks' files. `ready`, `check` and `explain` answer what you need about the queue.
66
- - **One load of each skill.** Do not load a skill again to re-read it.
67
- - **Edit frontmatter with your edit tool**, not with `sed` or another text substitution: a pattern that does not match fails silently and the status is then wrong without an error.
87
+ - **A fresh subagent for every task and role**, as the clean session a skill cannot open. Only a fix round continues one: the implementer of the same task.
88
+ - **Prompts exactly as sw-plan step 4 and sw-implement's "Dispatching" give them.** No reading lists, no project summary, no word on commits: the role files say changes stay uncommitted and committing is yours.
89
+ - **Reports, not files.** You read no code and no other task's files. Of a plan you read what sw-summarize reads, `## Approach` and `## Verification`, and its frontmatter when sw-plan step 6 approves it.
90
+ - **Edit frontmatter with your edit tool**: a `sed` pattern that does not match fails silently.
68
91
 
69
92
  ## The report
70
93
 
71
- At the end, or when the run stops:
94
+ At the end or on a stop:
72
95
 
73
- 1. A table: task, outcome, review verdict, commit.
74
- 2. **Decisions made without the user**, one line each with the task id. This is the list the user must read.
75
- 3. What stopped the run, if it stopped, and what would let it continue.
76
- 4. Offers and open findings collected along the way.
96
+ 1. A table, one row per task in scope (`not reached` for the rest): task, route, outcome, the last review verdict with the number of reviews, commit; `-` where none.
97
+ 2. **Decisions made without the user**, one line each with the task id, or `run`.
98
+ 3. What stopped the run, and what would let it continue: for a difference, the user's call on it, applied as sw-implement step 6 says; for a check not allowed, the user's yes; for a failed review, its blocking findings.
99
+ 4. Offers, open decisions, and every review's `important` and `minor` findings.
77
100
  5. `node docs/.sw/sw.mjs stats`, as printed.
78
- 6. What is ready next.
79
-
80
- ## Common mistakes
81
-
82
- - Asking the user mid-run something the standing answers cover, or not asking at the start something they do not.
83
- - Deciding a `needs:` check may run because the run is unattended. Unattended means fewer questions, not more permission.
84
- - Piling several tasks into one uncommitted tree after a commit was refused.
85
- - Running one long subagent that plans, implements and reviews a task. Measured on a real project, such an agent reached a context of almost a million tokens and sent over ten times what the three separate roles sent on another task of the same project.
86
- - Carrying on past 200k because the next task looks small.
101
+ 6. What is ready next.