superwiki 0.1.2 → 0.1.4

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.
@@ -1,118 +1,197 @@
1
1
  #!/usr/bin/env node
2
- // Scaffolds docs/ as a Superwiki vault. Safe to re-run: user content is kept, tool files are refreshed.
3
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
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.
4
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
4
5
  import { basename, dirname, join, resolve } from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
 
7
- const assets = join(dirname(fileURLToPath(import.meta.url)), '..', 'assets');
8
- const argv = process.argv.slice(2);
9
- const opt = name => { const i = argv.indexOf(name); return i >= 0 ? argv[i + 1] : undefined; };
8
+ const ASSETS = join(dirname(fileURLToPath(import.meta.url)), '..', 'assets');
9
+ const AREA_ID = /^[A-Za-z][A-Za-z0-9]*$/;
10
+ const MANAGED_BLOCK = /<!-- sw:start[\s\S]*?<!-- sw:end -->/;
11
+ // What a vault consists of at the top of docs/. Anything else there belongs to someone else.
12
+ const VAULT_ENTRIES = ['index.md', 'log.md', 'raw', 'wiki', 'tasks', 'plans', 'viewer.html'];
13
+ const ROLES = ['plan', 'implement', 'review'];
10
14
 
11
- if (argv.includes('--help')) {
12
- console.log(`init.mjs [--root <dir>] [--tasks | --no-tasks] [--areas "M=Mobile,B=Backend"]
15
+ const HELP = `init.mjs [--root <dir>] [--tasks | --no-tasks] [--areas "M=Mobile,B=Backend"]
13
16
 
14
17
  --root project folder (default: current directory)
15
18
  --tasks add the task module (docs/tasks, docs/plans)
16
19
  --no-tasks wiki only
17
- --areas task id prefixes and their names (default: T=Tasks)`);
18
- process.exit(0);
20
+ --areas task id prefixes and their names (default: T=Tasks)
21
+
22
+ Run without --tasks or --no-tasks on an existing vault to upgrade it with its saved choices.`;
23
+
24
+ class UsageError extends Error {}
25
+
26
+ function parseArgs(argv) {
27
+ const options = { root: '.', tasks: null, areas: {}, help: false };
28
+ for (let i = 0; i < argv.length; i++) {
29
+ const arg = argv[i];
30
+ if (arg === '--help') options.help = true;
31
+ else if (arg === '--root') options.root = argv[++i] ?? '.';
32
+ else if (arg === '--tasks') options.tasks = true;
33
+ else if (arg === '--no-tasks') options.tasks = false;
34
+ else if (arg === '--areas') options.areas = parseAreas(argv[++i] ?? '');
35
+ else throw new UsageError(`unknown argument: ${arg}\n\n${HELP}`);
36
+ }
37
+ return options;
38
+ }
39
+
40
+ function parseAreas(text) {
41
+ const areas = {};
42
+ for (const pair of text.split(',').map(part => part.trim()).filter(Boolean)) {
43
+ const [id, ...name] = pair.split('=');
44
+ if (!AREA_ID.test(id)) throw new UsageError(`bad area id "${id}": letters and digits, starting with a letter`);
45
+ areas[id] = name.join('=').trim() || id;
46
+ }
47
+ return areas;
19
48
  }
20
49
 
21
- const root = resolve(opt('--root') || '.');
22
- const docs = join(root, 'docs');
23
- const dotdir = join(docs, '.sw');
24
- const configPath = join(dotdir, 'config.json');
25
- const previous = existsSync(configPath) ? JSON.parse(readFileSync(configPath, 'utf8')) : null;
26
- const tasks = argv.includes('--no-tasks') ? false : argv.includes('--tasks') ? true : previous?.tasks ?? null;
27
- if (tasks === null) {
28
- console.error('say --tasks or --no-tasks');
29
- process.exit(2);
50
+ // Collects one report line per file or folder touched. The state of a tool-owned file is
51
+ // judged by its content, so an upgrade that changes nothing says "unchanged".
52
+ function createReport(root) {
53
+ const lines = [];
54
+ const rel = path => path.slice(root.length + 1);
55
+ const add = (state, text) => lines.push(`${state.padEnd(9)} ${text}`);
56
+ return {
57
+ lines,
58
+ folder(path) {
59
+ if (existsSync(path)) return;
60
+ mkdirSync(path, { recursive: true });
61
+ add('created', `${rel(path)}/`);
62
+ },
63
+ // User content: written once, never replaced.
64
+ keep(path, content) {
65
+ if (existsSync(path)) return add('kept', rel(path));
66
+ writeFileSync(path, content);
67
+ add('created', rel(path));
68
+ },
69
+ // Tool-owned content: always brought up to date.
70
+ write(path, content, label = rel(path)) {
71
+ const before = existsSync(path) ? readFileSync(path, 'utf8') : null;
72
+ if (before !== content) writeFileSync(path, content);
73
+ add(before === null ? 'created' : before === content ? 'unchanged' : 'updated', label);
74
+ },
75
+ copy(from, to) {
76
+ if (existsSync(from)) this.write(to, readFileSync(from, 'utf8'));
77
+ else add('missing', `${rel(to)} (not in this Superwiki build; report this to the user)`);
78
+ },
79
+ line: add,
80
+ };
30
81
  }
31
82
 
32
- const areas = {};
33
- for (const pair of (opt('--areas') || '').split(',').map(s => s.trim()).filter(Boolean)) {
34
- const [id, ...name] = pair.split('=');
35
- if (!/^[A-Za-z][A-Za-z0-9]*$/.test(id)) { console.error(`bad area id "${id}": letters and digits, starting with a letter`); process.exit(2); }
36
- areas[id] = name.join('=').trim() || id;
83
+ function readConfig(path) {
84
+ return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : null;
37
85
  }
38
86
 
39
- const today = new Date().toISOString().slice(0, 10);
40
- const report = [];
41
- const rel = p => p.slice(root.length + 1);
42
- const dir = p => { if (!existsSync(p)) { mkdirSync(p, { recursive: true }); report.push(`created ${rel(p)}/`); } };
43
- const keep = (p, content) => {
44
- if (existsSync(p)) { report.push(`kept ${rel(p)}`); return false; }
45
- writeFileSync(p, content);
46
- report.push(`created ${rel(p)}`);
47
- return true;
48
- };
49
- // Writes a tool-owned file and reports created / updated / unchanged from the actual content.
50
- const write = (p, content, label = rel(p)) => {
51
- const before = existsSync(p) ? readFileSync(p, 'utf8') : null;
52
- if (before !== content) writeFileSync(p, content);
53
- report.push(`${before === null ? 'created ' : before === content ? 'unchanged' : 'updated '} ${label}`);
54
- };
55
- const refresh = (from, to) => {
56
- if (!existsSync(from)) { report.push(`missing ${rel(to)} (not in this Superwiki build; report this to the user)`); return; }
57
- write(to, readFileSync(from, 'utf8'));
58
- };
59
-
60
- const foreign = existsSync(docs) && !previous ? readdirSync(docs).filter(n => !n.startsWith('.')) : [];
61
-
62
- dir(docs);
63
- dir(join(docs, 'raw'));
64
- dir(join(docs, 'raw', 'assets'));
65
- dir(join(docs, 'wiki'));
66
- if (tasks) { dir(join(docs, 'tasks')); dir(join(docs, 'plans')); }
67
- dir(dotdir);
68
- dir(join(dotdir, 'templates'));
69
- // Git drops empty folders; the vault needs them to exist.
70
- for (const d of ['raw/assets', 'wiki', ...(tasks ? ['tasks', 'plans'] : [])]) {
71
- const p = join(docs, d);
72
- if (!readdirSync(p).length) writeFileSync(join(p, '.gitkeep'), '');
87
+ function buildConfig(previous, { root, tasks, areas }) {
88
+ const models = Object.fromEntries(ROLES.map(role => [role, previous?.models?.[role] ?? {}]));
89
+ const chosenAreas = Object.keys(areas).length ? areas : previous?.areas ?? { T: 'Tasks' };
90
+ return {
91
+ version: 1,
92
+ name: previous?.name ?? basename(root),
93
+ tasks,
94
+ areas: tasks ? chosenAreas : {},
95
+ models,
96
+ ...(previous?.tools ? { tools: previous.tools } : {}),
97
+ };
73
98
  }
74
99
 
75
- keep(join(docs, 'index.md'), '# Index\n\nCatalog of the wiki: one line per page, `- [[file-name]]: summary`, grouped by type.\n');
76
- keep(join(docs, 'log.md'), `# Log\n\nAppend-only. Entry format: \`## [YYYY-MM-DD] kind | title\`.\n\n## [${today}] init | Superwiki vault created\n`);
77
-
78
- // The viewer snapshot and the local server's address are per-machine and regenerated on demand.
79
- write(join(dotdir, '.gitignore'), 'data.js\nserver.json\n');
80
- refresh(join(assets, 'sw.mjs'), join(dotdir, 'sw.mjs'));
81
- refresh(join(assets, 'viewer.html'), join(docs, 'viewer.html'));
82
- for (const t of ['page.md', ...(tasks ? ['task.md', 'plan.md', 'guide.md'] : [])]) refresh(join(assets, 'templates', t), join(dotdir, 'templates', t));
83
-
84
- const config = {
85
- version: 1,
86
- name: previous?.name ?? basename(root),
87
- tasks,
88
- areas: tasks ? (Object.keys(areas).length ? areas : previous?.areas ?? { T: 'Tasks' }) : {},
89
- models: previous?.models ?? { plan: {}, implement: {} },
90
- ...(previous?.tools ? { tools: previous.tools } : {}),
91
- };
92
- write(configPath, JSON.stringify(config, null, 2) + '\n');
93
-
94
- // Schema block: {{#tasks}}..{{/tasks}} kept with the task module, {{^tasks}}..{{/tasks}} without it.
95
- const block = readFileSync(join(assets, 'agents-block.md'), 'utf8')
96
- .replace(/\{\{#tasks\}\}\n([\s\S]*?)\{\{\/tasks\}\}\n/g, (_, body) => (tasks ? body : ''))
97
- .replace(/\{\{\^tasks\}\}\n([\s\S]*?)\{\{\/tasks\}\}\n/g, (_, body) => (tasks ? '' : body))
98
- .trimEnd();
99
- const agentsPath = join(root, 'AGENTS.md');
100
- const managed = /<!-- sw:start[\s\S]*?<!-- sw:end -->/;
101
- if (!existsSync(agentsPath)) write(agentsPath, `# Agent instructions\n\n${block}\n`);
102
- else {
103
- const text = readFileSync(agentsPath, 'utf8');
104
- write(agentsPath, managed.test(text) ? text.replace(managed, () => block) : `${text.trimEnd()}\n\n${block}\n`, 'AGENTS.md (Superwiki block)');
100
+ // The schema block keeps {{#tasks}}..{{/tasks}} parts with the task module and
101
+ // {{^tasks}}..{{/tasks}} parts without it.
102
+ function schemaBlock(tasks) {
103
+ return readFileSync(join(ASSETS, 'agents-block.md'), 'utf8')
104
+ .replace(/\{\{#tasks\}\}\n([\s\S]*?)\{\{\/tasks\}\}\n/g, (_, body) => (tasks ? body : ''))
105
+ .replace(/\{\{\^tasks\}\}\n([\s\S]*?)\{\{\/tasks\}\}\n/g, (_, body) => (tasks ? '' : body))
106
+ .trimEnd();
105
107
  }
106
108
 
107
- // Claude Code reads CLAUDE.md, not AGENTS.md; an import keeps one source of truth.
108
- const claudePath = join(root, 'CLAUDE.md');
109
- if (!existsSync(claudePath)) {
110
- writeFileSync(claudePath, '@AGENTS.md\n');
111
- report.push('created CLAUDE.md (imports AGENTS.md)');
112
- } else if (!/AGENTS\.md/.test(readFileSync(claudePath, 'utf8'))) {
113
- report.push('note CLAUDE.md does not mention AGENTS.md; add a line "@AGENTS.md" so Claude Code reads the Superwiki rules');
114
- } else report.push('kept CLAUDE.md');
115
-
116
- console.log(report.join('\n'));
117
- console.log(`\nvault: docs/ tasks: ${tasks ? `on (areas: ${Object.keys(config.areas).join(', ')})` : 'off'}`);
118
- if (foreign.length) console.log(`\ndocs/ already had content (${foreign.slice(0, 8).join(', ')}${foreign.length > 8 ? ', ...' : ''}). It was left untouched and is outside the vault; sw-migrate converts it.`);
109
+ function writeVault(docs, tasks, report) {
110
+ const folders = ['raw/assets', 'wiki', ...(tasks ? ['tasks', 'plans'] : [])];
111
+ report.folder(docs);
112
+ for (const folder of ['raw', ...folders, '.sw', '.sw/templates']) report.folder(join(docs, folder));
113
+ // Git drops empty folders; the vault needs them to exist.
114
+ for (const folder of folders) {
115
+ if (!readdirSync(join(docs, folder)).length) writeFileSync(join(docs, folder, '.gitkeep'), '');
116
+ }
117
+
118
+ const today = new Date().toISOString().slice(0, 10);
119
+ report.keep(join(docs, 'index.md'), '# Index\n\nCatalog of the wiki: one line per page, `- [[file-name]]: summary`, grouped by type.\n');
120
+ report.keep(join(docs, 'log.md'), `# Log\n\nAppend-only. Entry format: \`## [YYYY-MM-DD] kind | title\`.\n\n## [${today}] init | Superwiki vault created\n`);
121
+
122
+ // The viewer snapshot and the local server's address are per-machine and regenerated on demand.
123
+ report.write(join(docs, '.sw', '.gitignore'), 'data.js\nserver.json\n');
124
+ report.copy(join(ASSETS, 'sw.mjs'), join(docs, '.sw', 'sw.mjs'));
125
+ report.copy(join(ASSETS, 'viewer.html'), join(docs, 'viewer.html'));
126
+ for (const template of ['page.md', ...(tasks ? ['task.md', 'plan.md', 'guide.md'] : [])]) {
127
+ report.copy(join(ASSETS, 'templates', template), join(docs, '.sw', 'templates', template));
128
+ }
129
+ }
130
+
131
+ function writeAgentRules(root, tasks, report) {
132
+ const block = schemaBlock(tasks);
133
+ const agentsPath = join(root, 'AGENTS.md');
134
+ if (existsSync(agentsPath)) {
135
+ const text = readFileSync(agentsPath, 'utf8');
136
+ const updated = MANAGED_BLOCK.test(text) ? text.replace(MANAGED_BLOCK, () => block) : `${text.trimEnd()}\n\n${block}\n`;
137
+ report.write(agentsPath, updated, 'AGENTS.md (Superwiki block)');
138
+ } else {
139
+ report.write(agentsPath, `# Agent instructions\n\n${block}\n`);
140
+ }
141
+
142
+ // Claude Code reads CLAUDE.md, not AGENTS.md; an import keeps one source of truth.
143
+ const claudePath = join(root, 'CLAUDE.md');
144
+ if (!existsSync(claudePath)) {
145
+ writeFileSync(claudePath, '@AGENTS.md\n');
146
+ report.line('created', 'CLAUDE.md (imports AGENTS.md)');
147
+ } else if (/AGENTS\.md/.test(readFileSync(claudePath, 'utf8'))) {
148
+ report.line('kept', 'CLAUDE.md');
149
+ } else {
150
+ report.line('note', 'CLAUDE.md does not mention AGENTS.md; add a line "@AGENTS.md" so Claude Code reads the Superwiki rules');
151
+ }
152
+ }
153
+
154
+ // What was in docs/ before the first init and is not part of a vault.
155
+ function foreignEntries(docs) {
156
+ if (!existsSync(docs)) return [];
157
+ return readdirSync(docs).filter(name => !name.startsWith('.') && !VAULT_ENTRIES.includes(name));
158
+ }
159
+
160
+ function main(argv) {
161
+ const options = parseArgs(argv);
162
+ if (options.help) {
163
+ console.log(HELP);
164
+ return;
165
+ }
166
+ const root = resolve(options.root);
167
+ const docs = join(root, 'docs');
168
+ const configPath = join(docs, '.sw', 'config.json');
169
+ const previous = readConfig(configPath);
170
+ const tasks = options.tasks ?? previous?.tasks ?? null;
171
+ if (tasks === null) throw new UsageError('say --tasks or --no-tasks');
172
+
173
+ const foreign = previous ? [] : foreignEntries(docs);
174
+ const hadTaskFiles = !previous && existsSync(join(docs, 'tasks'));
175
+ const report = createReport(root);
176
+ writeVault(docs, tasks, report);
177
+ const config = buildConfig(previous, { root, tasks, areas: options.areas });
178
+ report.write(configPath, JSON.stringify(config, null, 2) + '\n');
179
+ writeAgentRules(root, tasks, report);
180
+
181
+ console.log(report.lines.join('\n'));
182
+ console.log(`\nvault: docs/ tasks: ${tasks ? `on (areas: ${Object.keys(config.areas).join(', ')})` : 'off'}`);
183
+ if (foreign.length) {
184
+ const shown = `${foreign.slice(0, 8).join(', ')}${foreign.length > 8 ? ', ...' : ''}`;
185
+ // After sw-migrate the task files are already there; only a first init on old docs needs the hint.
186
+ const hint = hadTaskFiles ? '' : ' If it holds a task index, sw-migrate converts it.';
187
+ console.log(`\ndocs/ already had content (${shown}). It was left untouched and is outside the vault.${hint}`);
188
+ }
189
+ }
190
+
191
+ try {
192
+ main(process.argv.slice(2));
193
+ } catch (error) {
194
+ if (!(error instanceof UsageError)) throw error;
195
+ console.error(error.message);
196
+ process.exitCode = 2;
197
+ }
@@ -5,7 +5,7 @@ description: Use when the user wants to convert an existing docs folder, task in
5
5
 
6
6
  # sw-migrate
7
7
 
8
- Converts a project that tracks work in markdown tables into a Superwiki vault: one file per task, wikilinks, an append-only log. A script does the bulk conversion from a mapping you write; you only read samples, never the whole index.
8
+ Converts a project that tracks work in markdown tables into a Superwiki vault: one file per task, wikilinks, an append-only log. A script inspects the existing files and does the conversion from a mapping you write. You never read the index yourself; it can be hundreds of kilobytes.
9
9
 
10
10
  Run everything from the project root. `<skill-dir>` is this skill's directory; `<init-dir>` is the sw-init skill's directory (a sibling folder).
11
11
 
@@ -19,43 +19,54 @@ Stop and tell the user if any of these fails; do not work around them.
19
19
 
20
20
  ## Steps
21
21
 
22
- 1. **Branch**: `git switch -c sw-migrate`. All changes stay on this branch. Never merge, push or delete the branch yourself.
23
- 2. **Sample, do not read.** The index can be hundreds of kilobytes.
24
- - `ls docs docs/*/ | head -60` for the layout.
25
- - `grep -n '^|' docs/<index> | awk 'length > 0' | cut -c1-200 | head -40` for table headers and a few rows.
26
- - `grep -n '^## ' docs/<detail file> | head` and about 30 lines of one section, if tasks have detail files.
27
- - The status values in use: `grep -o '| *[A-Za-z ✅]* *|' ... | sort | uniq -c` on the status column, or read ten rows.
28
- 3. **Write the mapping** to a temporary file outside the repo. `node <skill-dir>/scripts/migrate.mjs --help` prints the format. Decide:
29
- - which columns are id, title, status, dependencies, milestone, order, dates;
30
- - every status value → `todo`, `in-progress`, `done` or `cancelled`;
22
+ 1. **Branch.** The conversion belongs on its own branch. If the project lets you run git, `git switch -c sw-migrate`; if it does not, or you are already on a branch the user made for this, ask the user to confirm the branch and stay on it. Never merge, push or delete a branch yourself.
23
+ 2. **Inspect**: `node <skill-dir>/scripts/migrate.mjs --inspect`. It prints, for every file that lists tasks: the tables with their line, row count and columns, the values of status-like columns with their counts, and how many per-task headings a file has. It also warns when `docs/wiki/`, `docs/tasks/` or `docs/plans/` already exist. This output is all you need for the mapping; do not open the files.
24
+ 3. **Write the mapping** to a temporary file outside the repo. `node <skill-dir>/scripts/migrate.mjs --help` prints the format. Decide, from the inspect output:
25
+ - the index file, and which columns are id, title, status, dependencies, milestone, order and dates;
26
+ - every status value → `todo`, `in-progress`, `done` or `cancelled`. Write the value as inspect shows it; decoration such as a check mark is already stripped;
31
27
  - the marker for soft dependencies, if the project has them;
32
- - which other columns to keep as body sections (sources, notes);
28
+ - other columns worth keeping: as frontmatter (`fields`, for short values such as a review class) or as a body section (`sections`, for prose such as sources and notes);
33
29
  - where per-task detail sections live and their heading prefix;
34
- - the changelog file and its columns, if any.
35
- 4. **Dry run**: `node <skill-dir>/scripts/migrate.mjs --mapping <file> --dry-run`.
36
- 5. **Show the user the mapping and the dry-run report, and wait for approval.** State the task counts per status next to the project's own numbers (its summary table, or a `grep -c`). If they differ, find out why before going on. Fix every line under `problems`.
37
- 6. **Convert**: the same command without `--dry-run`, then the `init.mjs` command the report prints (replace the area names with real ones), then `node docs/.sw/sw.mjs status` and `node docs/.sw/sw.mjs lint`.
38
- 7. **Verify**: `status` totals equal the dry-run counts and the project's own numbers; `lint` has 0 errors. Lint errors here are real inconsistencies in the source (a task started before its dependency finished, a dependency cycle). Report them; do not edit task files to make them pass.
30
+ - the table that is the changelog, if any. It may be in the index file itself;
31
+ - `archiveAlso`: anything in `docs/wiki/`, `docs/tasks/` or `docs/plans/` that the mapping does not consume. Superwiki owns those folders.
32
+ 4. **Dry run**: `node <skill-dir>/scripts/migrate.mjs --mapping <file> --dry-run`. The report has two parts:
33
+ - `PROBLEMS`: each one must be fixed, in the mapping or in the files, and the dry run repeated until it says `no problems`. An unknown status is a question for the user, not a guess.
34
+ - "For information": files whose links were rewritten, detail sections without a row, links that now point into the archive. Nothing to fix; pass the counts on.
35
+ 5. **Show the user the mapping and the dry-run report, and wait for approval.** Put the task counts per status next to the project's own numbers (inspect's status counts, or the project's summary table). If they differ, find out why before going on.
36
+ 6. **Convert**: the same command without `--dry-run`, then the `init.mjs` command the report prints, with real area names in place of the repeated ids, then `node docs/.sw/sw.mjs status` and `node docs/.sw/sw.mjs lint`.
37
+ 7. **Verify.** `status` shows `ready` and `blocked` where the report said `todo`: their sum must equal the todo count, and the other counts must match as they are. `lint` must have 0 errors. An error here is a real inconsistency in the source (a task started before its dependency finished, a dependency cycle): report it; do not edit task files to make it pass.
39
38
  8. **Report** what moved where, the counts, the lint result, and what is left for a human decision (next section). Commit only if the user asks.
40
39
 
41
- ## What the script does not do
40
+ ## What the conversion does
41
+
42
+ So that you can tell the user without looking:
43
+
44
+ - every row of the task tables becomes `docs/tasks/<ID>.md`, with its detail section and the kept columns as the body;
45
+ - the changelog becomes `docs/log.md`, oldest entry first;
46
+ - the index, the detail files, the changelog and everything in `archiveAlso` move to `docs/legacy/`, keeping their layout;
47
+ - links to tasks become wikilinks everywhere under `docs/`, so other documents are edited too; the report lists them;
48
+ - `init.mjs` then adds `docs/index.md`, the viewer, the CLI and the Superwiki block in `AGENTS.md`.
49
+
50
+ ## What it leaves for the user
42
51
 
43
52
  Tell the user about each of these; act only on what they choose.
44
53
 
45
54
  | Left over | Where it is | Options |
46
- |---|---|---|
47
- | Rules and conventions written in the old index | `docs/legacy/` | most are replaced by the Superwiki block in `AGENTS.md`; project-specific ones go to `AGENTS.md` outside the Superwiki block |
55
+ | --- | --- | --- |
56
+ | Rules and conventions written in the old index | `docs/legacy/` | most are replaced by the Superwiki block in `AGENTS.md`; project-specific ones go to `AGENTS.md` outside that block |
48
57
  | Milestones, glossaries, decision records in the old index | `docs/legacy/` | turn into `docs/wiki/` pages with `type:` and `summary:`, and list them in `index.md` |
49
- | Research, specs, decisions in other folders | unchanged, outside the vault | leave, or move into `raw/` (sources) or `wiki/` (maintained pages) |
50
- | Plans written by other tools | unchanged | leave, or move to `docs/plans/<ID>-plan.md` when a plan belongs to exactly one task |
51
- | The old viewer or scripts that parse the old index | unchanged | delete once `docs/viewer.html` shows the same numbers |
52
- | Instructions in `AGENTS.md` / `CLAUDE.md` that describe the old index | unchanged | rewrite to point at the Superwiki rules; show the diff first |
58
+ | Research, specs, decisions in other folders | in place, outside the vault | leave, or move into `raw/` (sources) or `wiki/` (maintained pages) |
59
+ | Plans written by other tools | in place, or in the archive if they were under `docs/plans/` | leave, or turn one into `docs/plans/<ID>-plan.md` when it belongs to exactly one task |
60
+ | The old viewer or scripts that parse the old index | in place | delete once `docs/viewer.html` shows the same numbers |
61
+ | Instructions in `AGENTS.md` / `CLAUDE.md`, and links in files outside `docs/`, that point at the old index | in place | rewrite to point at the Superwiki rules and the new task files; show the diff first |
62
+ | Task files that carry a long history (plan, review record, daily notes in one section) | `docs/tasks/` | leave; new tasks keep the plan in `docs/plans/` and history in the log |
53
63
 
54
64
  `docs/legacy/` is an archive, not part of the vault. Delete it only when the user says so.
55
65
 
56
66
  ## Common mistakes
57
67
 
58
- - Reading the whole index to "understand it first". Samples are enough; the dry run tells you what did not parse.
68
+ - Opening the index or the detail files to "understand them first". Inspect and the dry run tell you what is there and what did not parse.
59
69
  - Mapping a status the script reported as unknown to `todo` without asking. Ask what it means.
70
+ - Leaving a line under `PROBLEMS` and converting anyway.
60
71
  - Fixing lint errors by changing statuses. The old data was inconsistent; the user decides which side is right.
61
- - Hand-editing hundreds of links. If the script missed a link shape, fix the mapping or report it.
72
+ - Hand-editing links. If the script missed a link shape, report it.