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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +143 -199
- package/bin/superwiki.mjs +46 -4
- package/commands/do.md +5 -0
- package/commands/summarize.md +5 -0
- package/package.json +1 -1
- package/skills/sw-config/assets/implementer.md +29 -17
- package/skills/sw-config/assets/planner.md +5 -5
- package/skills/sw-config/assets/reviewer.md +14 -7
- package/skills/sw-do/SKILL.md +54 -0
- package/skills/sw-implement/SKILL.md +48 -30
- package/skills/sw-init/SKILL.md +8 -2
- package/skills/sw-init/assets/agents-block.md +3 -1
- package/skills/sw-init/assets/sw.mjs +58 -2
- package/skills/sw-init/assets/templates/task.md +8 -1
- package/skills/sw-init/assets/viewer.html +74 -2
- package/skills/sw-init/scripts/init.mjs +96 -12
- package/skills/sw-lint/SKILL.md +1 -0
- package/skills/sw-plan/SKILL.md +29 -9
- package/skills/sw-run/SKILL.md +67 -52
- package/skills/sw-summarize/SKILL.md +103 -0
|
@@ -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
|
-
|
|
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
|
|
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
|
|
20
|
-
--tasks
|
|
21
|
-
--no-tasks
|
|
22
|
-
--areas
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/skills/sw-lint/SKILL.md
CHANGED
|
@@ -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 |
|
package/skills/sw-plan/SKILL.md
CHANGED
|
@@ -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
|
|
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. **
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
package/skills/sw-run/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
17
|
-
2. **Standing answers
|
|
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
|
|
28
|
+
| Question | If the message does not answer it |
|
|
20
29
|
| --- | --- |
|
|
21
|
-
| May plans be approved without them? | yes
|
|
22
|
-
| Which
|
|
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.
|
|
29
|
-
-
|
|
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
|
|
35
|
-
2. **
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
|
53
|
-
- The review still says `changes needed
|
|
54
|
-
- The
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
- Your `peak` is above 200k
|
|
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
|
|
62
|
-
|
|
63
|
-
- **
|
|
64
|
-
- **
|
|
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
|
|
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
|
|
75
|
-
3. What stopped the run,
|
|
76
|
-
4. Offers and
|
|
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.
|