superwiki 0.1.6 → 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.
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: sw-run
3
+ description: Use when the user wants several Superwiki tasks worked through one after another without being asked at each step (run the backlog, do all tasks of an area, keep going until done or blocked, act as orchestrator), or invokes sw-run or sw:run.
4
+ ---
5
+
6
+ # sw-run
7
+
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.
9
+
10
+ Run commands from the project root.
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
+
23
+ ## Before the first task
24
+
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:
27
+
28
+ | Question | If the message does not answer it |
29
+ | --- | --- |
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 |
32
+ | Commit after each task? Push? | no commit, no push |
33
+ | Go on with other tasks when one stops? | no; stop the run |
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.
36
+ 3. **Check that the run can do what was agreed**, before any task is touched:
37
+ - `node docs/.sw/sw.mjs lint`: errors are fixed or reported first.
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.
40
+
41
+ ## Each task
42
+
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.
73
+
74
+ ## When to stop
75
+
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.
77
+
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.
84
+
85
+ ## Keeping the run cheap
86
+
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.
91
+
92
+ ## The report
93
+
94
+ At the end or on a stop:
95
+
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.
100
+ 5. `node docs/.sw/sw.mjs stats`, as printed.
101
+ 6. What is ready next.
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: sw-summarize
3
+ description: Use when a Superwiki task's work is finished and it has to be closed with evidence, when the user asks what a task did, what it changed or how it was verified, or invokes sw-summarize or sw:summarize with a task id. sw-implement runs it as the gate before `done`.
4
+ ---
5
+
6
+ # sw-summarize
7
+
8
+ Closes a task with a `## Summary` section in its task file: what was planned, what was built, what changed, and the evidence for each "Done when" item. `status: done` then means verified, and the task file says how.
9
+
10
+ One rule carries the skill: **no verdict without a command run in this session.** Name the command that proves the item, run it now, read its exit code and its output, then write the verdict. An earlier run, the implementer's report, a passing test you remember, or "should pass" is not evidence.
11
+
12
+ It runs in the session that invokes it: by hand as `sw-summarize <ID>`, or as a step of sw-implement. It is not handed to the implementer; the one who did the work does not also sign it off.
13
+
14
+ Run commands from the project root.
15
+
16
+ ## Steps
17
+
18
+ 1. **Read the task**: `docs/tasks/<ID>.md`. Number its "Done when" items in the order they are written; the verification list uses the same numbers. If the task has a plan, read only its `## Approach` and `## Verification`.
19
+ 2. **Take the changes from git**, never from memory:
20
+ - uncommitted work: `git status --porcelain`;
21
+ - work already committed: `git log --name-status --format='%h %s' --grep='^<ID>:'`, the commits whose message starts with `<ID>:`;
22
+ - the short HEAD for the source line: `git rev-parse --short HEAD`.
23
+
24
+ List the files that belong to this task, without its bookkeeping (the task file, its plan, `docs/log.md`, `docs/index.md`, the area guide). Uncommitted work can sit next to other tasks' changes, so a file belongs to the task when it is both in `git status --porcelain` and in the "files changed" of this task's implementer reports, the first report and each fix round's. A session that ran several tasks holds other tasks' reports too; they do not count. Invoked by hand with no report in this session: the whole list, when the tree holds nothing else; otherwise ask the user which files belong to the task. No git repository, or the vault is ignored by git: say so in the section instead of listing files from recollection.
25
+ 3. **Pick one command per "Done when" item.**
26
+
27
+ | Where the command comes from | When |
28
+ | --- | --- |
29
+ | the plan's `## Verification` | the task has a plan |
30
+ | the commands the implementer reported | no plan, and a report is in this session |
31
+ | you choose it | otherwise, or the listed command does not prove the item |
32
+
33
+ A requirement about text (a document mentions something, a file exists, a format is described) is proven by `grep`, `test -f` or `lint`, not by having written the text. A requirement about behaviour (what a program, an endpoint or an agent following a prompt does) is proven by running the thing and observing it: run the program, call the endpoint, or dispatch a fresh agent on the scenario and read what it does. Finding the code or text that should cause the behaviour proves only that the text is there; without a run the item is `unverified`. A check needs the environment when it starts a service, needs a running stack or changes data. It runs only if the user allowed it: under sw-implement the answer is on that skill's list of such checks; when you summarize outside it, ask the user before you run one.
34
+ 4. **Run every command now and read the result.** Exit code first, then the line that shows the item holds: the test count, the matching line, the `0 errors`.
35
+
36
+ | What you have | Verdict |
37
+ | --- | --- |
38
+ | the command ran in this session, exited as expected, and its output shows the item | `verified` |
39
+ | the command ran and shows the item does not hold | `failed` |
40
+ | no command was run: a check that needs the environment and was not allowed, a difference from the item's wording that the user has not decided (run no command for it), nothing that can prove it, or only someone's word | `unverified`, with the reason |
41
+ | the command proves part of the item | `unverified`, saying which part is open |
42
+
43
+ 5. **Write the section** in the format below. It is the last section of the file. If the file already has a `## Summary`, replace it whole; every verdict in the new one comes from this session. Leave the frontmatter and the other sections as they are.
44
+ 6. **Log it**: append `## [date] summary | <ID> <verified> of <items> verified` to `docs/log.md`, in the layout its last entries use.
45
+ 7. **Ask the gate**: `node docs/.sw/sw.mjs check <ID>`. It prints `summary:` with the counts, and `can finish: yes` only when every item is verified and no dependency is open.
46
+ 8. **Close or hand back.**
47
+
48
+ | Invoked | Do |
49
+ | --- | --- |
50
+ | from sw-implement | return to its next step; it records the outcome, and its report shows the summary, so step 9 is skipped |
51
+ | by hand, and every item is verified, `check` says `can finish: yes`, and the task's `review:` is empty | set `status: done` and `finished:` today, append `## [date] task \| <ID> done` to the log, run `node docs/.sw/sw.mjs board` |
52
+ | by hand, and the task requires a review | write the summary only; the status stays. Say that sw-implement runs the review |
53
+ | by hand, and an item is unverified or failed | the status stays. Say what each open item needs |
54
+
55
+ 9. **Report**, when invoked by hand: show the user the summary itself, not only its counts. In this order: what was planned, what was built and each deviation, the files changed, the verdict for each item with its command, and whether the task can be closed. Say it in the language of the conversation; the section in the file stays as written.
56
+
57
+ ## The section
58
+
59
+ ```text
60
+ ## Summary
61
+
62
+ Summarized <date>: <verified> of <items> verified.
63
+
64
+ ### Plan
65
+ <the approach as planned, two to four lines>
66
+
67
+ ### Implementation
68
+ <what was built; each deviation from the plan, or from the task's wording when there was no plan; or "No deviations.">
69
+
70
+ ### Changes
71
+ - `path` (added | modified | deleted | renamed)
72
+
73
+ Source: `git status --porcelain` at <short HEAD>, matched against the implementer's reports.
74
+
75
+ ### Verification
76
+ 1. **verified**: <"Done when" item 1>
77
+ - command: `<command>`
78
+ - result: <exit code and the line that shows it>
79
+ 2. **unverified**: <item 2>
80
+ - reason: <what it needs>
81
+ 3. **failed**: <item 3>
82
+ - command: `<command>`
83
+ - result: <exit code and the line that shows the failure>
84
+ ```
85
+
86
+ - The four parts are always there, in this order.
87
+ - A task that had no plan: the Plan part reads `No plan: implemented directly from the task.`, followed, when sw-do wrote an Approach note in "Notes", by that note in one or two lines.
88
+ - An item rewritten after an accepted difference is a deviation, with or without a plan: name it under Implementation with its old wording, from the `- Changed <date>` line in the task's "Notes".
89
+ - "Verification" is a numbered list with one entry per "Done when" item, numbered as the items are. Each entry starts with the verdict in bold: `verified`, `unverified` or `failed`. `check`, `lint` and the viewer read exactly that; an item without an entry counts as unverified.
90
+ - The source line says how the files were picked. Changes taken from commits: name the commits instead of `git status --porcelain`. Picked without reports: `the whole tree` or `chosen by the user` in place of `matched against the implementer's reports`.
91
+ - Keep the plan and implementation parts short. The section is a record, not a retelling of the log.
92
+
93
+ ## Common mistakes
94
+
95
+ - Writing `verified` from the implementer's report. The report says where to look; the command you run says whether it is true.
96
+ - One `npm test` as the evidence for every item. Each item gets the command that shows that item; a suite proves only what its tests cover.
97
+ - "The file was written, so the item is met." Run the `grep` or `test -f`.
98
+ - Closing a behavioural item with `grep`. The line exists; whether anything follows it is what the item asks.
99
+ - Listing changed files from what you remember of the session. Git knows; ask it.
100
+ - Softening a `failed` or `unverified` into `verified` with a note. A note does not open the gate; the verdict does.
101
+ - Running a check that needs the environment and was not allowed, because the summary would otherwise stay incomplete. It stays incomplete, and the task stays open.
102
+ - Setting `done` by hand on a task that requires a review.
103
+ - Keeping parts of an old summary. Evidence from another session is not fresh.