superwiki 0.1.0 → 0.1.2
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 +63 -23
- package/package.json +1 -1
- package/skills/sw-config/SKILL.md +5 -5
- package/skills/sw-config/assets/implementer.md +39 -10
- package/skills/sw-config/assets/planner.md +52 -17
- package/skills/sw-config/scripts/config.mjs +161 -86
- package/skills/sw-implement/SKILL.md +24 -14
- package/skills/sw-init/assets/agents-block.md +22 -5
- package/skills/sw-init/assets/sw.mjs +360 -165
- package/skills/sw-init/assets/templates/guide.md +32 -0
- package/skills/sw-init/assets/templates/page.md +1 -0
- package/skills/sw-init/assets/templates/plan.md +11 -4
- package/skills/sw-init/assets/viewer.html +7 -2
- package/skills/sw-init/scripts/init.mjs +1 -1
- package/skills/sw-migrate/SKILL.md +2 -2
- package/skills/sw-plan/SKILL.md +19 -17
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ It is built to be cheap for the agent. One small index to read, one file per tas
|
|
|
24
24
|
Measured on a real project with 165 tasks, converted from a single markdown index:
|
|
25
25
|
|
|
26
26
|
| | Before | After |
|
|
27
|
-
|
|
27
|
+
| --- | --- | --- |
|
|
28
28
|
| Read at the start of every session | 197 KB index | 94-byte catalog + 1.7 KB of rules |
|
|
29
29
|
| Read to start one task | the index, then the task's section | one file, 2 KB at the median |
|
|
30
30
|
| Marking a task done | a status cell, plus a ✅ at every reference to it (median 12 places) | one frontmatter line |
|
|
@@ -33,7 +33,7 @@ Measured on a real project with 165 tasks, converted from a single markdown inde
|
|
|
33
33
|
|
|
34
34
|
## What you get
|
|
35
35
|
|
|
36
|
-
```
|
|
36
|
+
```text
|
|
37
37
|
docs/
|
|
38
38
|
index.md catalog of the wiki, one line per page
|
|
39
39
|
log.md append-only history
|
|
@@ -57,7 +57,7 @@ npx superwiki install claude
|
|
|
57
57
|
This copies the skills into the folder your agent reads. Name one or more targets:
|
|
58
58
|
|
|
59
59
|
| Target | Installs into | For |
|
|
60
|
-
|
|
60
|
+
| --- | --- | --- |
|
|
61
61
|
| `claude` | `~/.claude/skills` | [Claude Code](#claude-code) |
|
|
62
62
|
| `codex` | `~/.agents/skills` | [Codex CLI](#codex-cli) |
|
|
63
63
|
| `copilot` | `~/.copilot/skills` | [GitHub Copilot CLI](#github-copilot-cli) |
|
|
@@ -153,33 +153,71 @@ A project's `docs/` folder is plain markdown and keeps working as an Obsidian va
|
|
|
153
153
|
|
|
154
154
|
## Use
|
|
155
155
|
|
|
156
|
+
| Skill | What it does |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `sw-init` | set up `docs/` in the current project, or upgrade it |
|
|
159
|
+
| `sw-migrate` | convert an existing table-based task index, on a new git branch |
|
|
160
|
+
| `sw-ingest` | file a source into the wiki |
|
|
161
|
+
| `sw-plan` | plan a task with the planner subagent and get your approval |
|
|
162
|
+
| `sw-implement` | run a task with the implementer subagent and record the result |
|
|
163
|
+
| `sw-explain` | explain a task: what, why, dependencies, what it unblocks |
|
|
164
|
+
| `sw-triage` | for a problem: seen before? lessons, likely causes |
|
|
165
|
+
| `sw-lint` | structural checks by script, semantic review on request |
|
|
166
|
+
| `sw-visualize` | open the viewer |
|
|
167
|
+
| `sw-config` | the model each tool uses for planning and implementing; task areas |
|
|
168
|
+
|
|
169
|
+
### Examples
|
|
170
|
+
|
|
171
|
+
Shown as typed in Claude Code. In Codex, write `$sw-plan` instead of `/sw-plan`. Task ids are an area prefix and a number, such as `P-15`.
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
/sw-init set up the vault; asks whether you want tasks
|
|
175
|
+
/sw-migrate convert the task tables this project already has
|
|
176
|
+
|
|
177
|
+
/sw-plan what can start now? pick one and plan it
|
|
178
|
+
/sw-plan P-15 plan task P-15 (a small task is sent straight to sw-implement)
|
|
179
|
+
/sw-plan add CSV export to the sources page
|
|
180
|
+
new work: creates the task, then plans it
|
|
181
|
+
|
|
182
|
+
/sw-implement P-15 run P-15; refuses if a dependency is not done
|
|
183
|
+
/sw-implement continue what is in progress, or pick a ready task
|
|
184
|
+
|
|
185
|
+
/sw-explain M-06 what M-06 is, why it exists, what it waits on and unblocks
|
|
186
|
+
/sw-triage photo uploads hang at 100% on mobile since yesterday
|
|
187
|
+
has this happened before? lessons and likely causes
|
|
188
|
+
/sw-ingest ~/Downloads/interview-notes.md
|
|
189
|
+
file a source and summarise it into the wiki
|
|
190
|
+
|
|
191
|
+
/sw-config plan with opus, implement with sonnet
|
|
192
|
+
/sw-lint check links, frontmatter and task dependencies
|
|
193
|
+
/sw-visualize open the task board and the wiki in the browser
|
|
156
194
|
```
|
|
157
|
-
|
|
158
|
-
sw-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
sw-visualize open the viewer
|
|
166
|
-
sw-config models per role and tool, task areas
|
|
195
|
+
|
|
196
|
+
You do not have to type a command. The rules `sw-init` adds to `AGENTS.md` tell the agent which skill fits, so a plain request should reach the same skill:
|
|
197
|
+
|
|
198
|
+
```text
|
|
199
|
+
What should I work on next?
|
|
200
|
+
Implement P-15.
|
|
201
|
+
Why does M-06 exist, and what is it blocked by?
|
|
202
|
+
Users get the magic-link email twice. Have we seen this before?
|
|
167
203
|
```
|
|
168
204
|
|
|
169
205
|
A filled-in example vault is in [examples/demo/docs](examples/demo/docs).
|
|
170
206
|
|
|
171
|
-
|
|
207
|
+
### The CLI
|
|
208
|
+
|
|
209
|
+
The skills call a small script that answers questions without the agent reading the vault. You can run it yourself, from the project root:
|
|
172
210
|
|
|
173
211
|
```bash
|
|
174
|
-
node docs/.sw/sw.mjs status
|
|
175
|
-
node docs/.sw/sw.mjs ready
|
|
176
|
-
node docs/.sw/sw.mjs check
|
|
177
|
-
node docs/.sw/sw.mjs explain
|
|
178
|
-
node docs/.sw/sw.mjs search sync timeout
|
|
179
|
-
node docs/.sw/sw.mjs next-id
|
|
180
|
-
node docs/.sw/sw.mjs lint
|
|
181
|
-
node docs/.sw/sw.mjs serve --open
|
|
182
|
-
node docs/.sw/sw.mjs snapshot
|
|
212
|
+
node docs/.sw/sw.mjs status # counts per area
|
|
213
|
+
node docs/.sw/sw.mjs ready # tasks that can start now
|
|
214
|
+
node docs/.sw/sw.mjs check P-15 # can it start or finish, and what is open
|
|
215
|
+
node docs/.sw/sw.mjs explain P-15 # dependencies, what it unblocks, plan, area guide
|
|
216
|
+
node docs/.sw/sw.mjs search sync timeout # where something is mentioned
|
|
217
|
+
node docs/.sw/sw.mjs next-id P # next free id in an area
|
|
218
|
+
node docs/.sw/sw.mjs lint # broken links, bad frontmatter, dependency errors
|
|
219
|
+
node docs/.sw/sw.mjs serve --open # the viewer, reading files live
|
|
220
|
+
node docs/.sw/sw.mjs snapshot # or: freeze the vault into docs/viewer.html, no server
|
|
183
221
|
```
|
|
184
222
|
|
|
185
223
|
## Develop
|
|
@@ -188,6 +226,8 @@ node docs/.sw/sw.mjs snapshot # or: freeze the vault into docs/viewer.html,
|
|
|
188
226
|
npm test # builds skills/sw-init/assets/sw.mjs, then runs the tests
|
|
189
227
|
```
|
|
190
228
|
|
|
229
|
+
Releases are cut by the `Release` workflow (Actions → Release → Run workflow): it tests, bumps the version in `package.json` and the plugin manifests, publishes to npm, tags, and creates a GitHub release. It publishes through npm trusted publishing, so no token is stored: the package's settings on npmjs.com name this repository and `release.yml` as its trusted publisher.
|
|
230
|
+
|
|
191
231
|
`node scripts/build-demo.mjs` builds the public demo into `site/` (the viewer with the example vault baked in); the Pages workflow deploys it on every push to `main`.
|
|
192
232
|
|
|
193
233
|
`src/core.js` is shared by the CLI and the viewer. Edit sources in `src/`; the files in `skills/sw-init/assets/` named `sw.mjs` and `viewer.html` are generated.
|
package/package.json
CHANGED
|
@@ -20,11 +20,11 @@ node <skill-dir>/scripts/config.mjs sync --tools claude,codex,copilot
|
|
|
20
20
|
|
|
21
21
|
A model is chosen per role (`plan`, `implement`) and per tool (`claude`, `codex`, `copilot`), because each tool can only run its own models. No tool lets a skill change the model of the running session, so sw-plan and sw-implement hand the work to a subagent, and the subagent's file carries the model.
|
|
22
22
|
|
|
23
|
-
| Tool |
|
|
24
|
-
|
|
25
|
-
| Claude Code | `.claude/agents/sw-planner.md`, `sw-implementer.md` |
|
|
26
|
-
| Codex | `.codex/agents/sw-planner.toml`, `sw-implementer.toml` |
|
|
27
|
-
| Copilot CLI | `.github/agents/sw-planner.agent.md`, `sw-implementer.agent.md` |
|
|
23
|
+
| Tool | Files the script writes |
|
|
24
|
+
|---|---|
|
|
25
|
+
| Claude Code | `.claude/agents/sw-planner.md`, `sw-implementer.md` |
|
|
26
|
+
| Codex | `.codex/agents/sw-planner.toml`, `sw-implementer.toml` |
|
|
27
|
+
| Copilot CLI | `.github/agents/sw-planner.agent.md`, `sw-implementer.agent.md` |
|
|
28
28
|
|
|
29
29
|
When the user asks to set a model:
|
|
30
30
|
|
|
@@ -1,15 +1,44 @@
|
|
|
1
|
+
# Implementer
|
|
2
|
+
|
|
1
3
|
You implement one task in a Superwiki vault.
|
|
2
4
|
|
|
3
|
-
Input: a task id.
|
|
5
|
+
Input: a task id; possibly which checks you may run that need services or data.
|
|
6
|
+
|
|
7
|
+
What you read is what this task costs, and every extra step re-sends everything you have read so far. Read little, in few steps. Reading less must not shrink the work: the task text decides what gets built.
|
|
8
|
+
|
|
9
|
+
## Start
|
|
10
|
+
|
|
11
|
+
1. Read `docs/tasks/<ID>.md` and, if it exists, `docs/plans/<ID>-plan.md`. List for yourself every requirement the task states: each "Done when" item, and each item under scope, states or constraints.
|
|
12
|
+
- With a plan: open what it lists under "Read first", then work.
|
|
13
|
+
- Without a plan: run `node docs/.sw/sw.mjs explain <ID>`. If it names an area guide, read it.
|
|
14
|
+
2. Project rules (`AGENTS.md` and the like): if they are not already in your context, list their headings and read only the sections that govern the files you will change.
|
|
15
|
+
|
|
16
|
+
## How to read
|
|
17
|
+
|
|
18
|
+
- **Locate, then open.** Search for the symbol, string or file name first. Open the range the search points at, not the file.
|
|
19
|
+
- **Whole files only when you edit across them.** For a file you change in one place, read that place and what it needs around it.
|
|
20
|
+
- **One example per pattern.** To see how this project does something, find the closest existing case and read that part of it. Do not compare several.
|
|
21
|
+
- **Trust the contract.** Generated types, schemas and the task text say what an API returns. Do not read the other side's code to confirm it.
|
|
22
|
+
- **Batch lookups.** One command that searches for three things costs a third of three commands.
|
|
23
|
+
- **Never read twice.** If you need a file again, use what you already have.
|
|
24
|
+
|
|
25
|
+
A task rarely needs more than a dozen files opened besides the ones it changes. Past that you are surveying, not implementing: stop looking and work with what you have, or report what you could not find.
|
|
26
|
+
|
|
27
|
+
## Work
|
|
28
|
+
|
|
29
|
+
1. Do the work. Follow the plan's steps in order; where there is no plan, work from the task's "Goal" and "Done when". Follow the repository's own rules.
|
|
30
|
+
2. Build every requirement on your list. If you think one should be done differently or left out, do not decide silently: build what the task says where you can, and report the alternative.
|
|
31
|
+
3. Verify. After a step, run the narrowest check that covers it. Run the full verification list once, at the end, after the last edit.
|
|
32
|
+
- A check marked `needs: ...` in the plan runs only if your input says it may. Otherwise report it as not verified, with what it needs.
|
|
33
|
+
4. Do not edit `docs/tasks/<ID>.md`, `docs/log.md`, `docs/index.md` or the plan: the session that dispatched you records status.
|
|
34
|
+
5. Stop and report, without guessing, if the plan cannot be followed as written, a dependency is missing, or a requirement cannot be met.
|
|
35
|
+
|
|
36
|
+
## Report
|
|
4
37
|
|
|
5
|
-
|
|
6
|
-
2. Do the work. Follow the plan's steps in order; where there is no plan, work from the task's "Goal" and "Done when". Follow the repository's own rules (`AGENTS.md`).
|
|
7
|
-
3. Verify each "Done when" item with the plan's verification commands. Run them; do not assume.
|
|
8
|
-
4. Do not edit `docs/tasks/<ID>.md`, `docs/log.md` or `docs/index.md`: the session that dispatched you records status. If you learned something the wiki should hold (a decision made, a constraint found), say so in your report instead of writing it.
|
|
9
|
-
5. Stop and report, without guessing, if the plan cannot be followed as written, a dependency is missing, or a "Done when" item cannot be met.
|
|
38
|
+
About 30 lines:
|
|
10
39
|
|
|
11
|
-
|
|
12
|
-
- each "Done when" item: met or not, with the command you ran and its result;
|
|
40
|
+
- `Requirements:` every item from your list, one line each, marked `met` (with the command or test that shows it), `not met` (with what it needs) or `differs` (what you built instead, and why). No item may be missing from this list;
|
|
13
41
|
- files changed;
|
|
14
|
-
-
|
|
15
|
-
-
|
|
42
|
+
- other decisions the task or plan left open;
|
|
43
|
+
- if the area has a guide, `Guide:` facts you had to find in the code that it did not state and the next task in this area would need. One line each, at most eight;
|
|
44
|
+
- anything else the wiki or a follow-up task should record.
|
|
@@ -1,17 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
# Planner
|
|
2
|
+
|
|
3
|
+
You write the plan for one task in a Superwiki vault. The only file you create or change is `docs/plans/<ID>-plan.md`.
|
|
4
|
+
|
|
5
|
+
Input: a task id and today's date; possibly notes, or feedback on an earlier draft.
|
|
6
|
+
|
|
7
|
+
What you read is what planning costs, and every extra step re-sends everything you have read so far. Read to decide, not to be thorough.
|
|
8
|
+
|
|
9
|
+
## Start
|
|
10
|
+
|
|
11
|
+
1. Run `node docs/.sw/sw.mjs explain <ID>` and read `docs/tasks/<ID>.md`. List for yourself every requirement the task states: each "Done when" item, and each item under scope, states or constraints.
|
|
12
|
+
2. If `explain` names an area guide, read it. Read other linked pages only if the plan depends on what they say. If the plan file already exists, you are revising it.
|
|
13
|
+
3. Project rules (`AGENTS.md` and the like): if they are not already in your context, list their headings and read only the sections that govern the files the task will change.
|
|
14
|
+
|
|
15
|
+
## How to read
|
|
16
|
+
|
|
17
|
+
Read code to answer three questions: which files change, which existing pattern to copy, how the result is verified. Stop when you can answer them.
|
|
18
|
+
|
|
19
|
+
- **Locate, then open.** Search for the symbol, string or file name first. Open the range the search points at, not the file.
|
|
20
|
+
- **One example per pattern.** Find the closest existing case and read that part of it. Do not compare several.
|
|
21
|
+
- **Trust what is stated.** The task, the guide, generated types and schemas say what exists. Check a stated fact in code only where the plan would be wrong if the fact were false.
|
|
22
|
+
- **Batch lookups.** One command that searches for three things costs a third of three commands.
|
|
23
|
+
- **Never read twice.**
|
|
24
|
+
|
|
25
|
+
## Write the plan
|
|
26
|
+
|
|
27
|
+
Write `docs/plans/<ID>-plan.md`. Keep it as short as the work allows; most plans fit in 40 to 60 lines.
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
---
|
|
31
|
+
type: plan
|
|
32
|
+
task: <ID>
|
|
33
|
+
status: draft
|
|
34
|
+
updated: <today>
|
|
35
|
+
---
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `## Approach`: the chosen approach and every assumption you made, in a few lines. Mention a rejected option only if someone would otherwise try it.
|
|
39
|
+
- `## Read first`: the files the implementer must open, each with the part that matters (function, section or line range). After these, nothing should need exploring.
|
|
40
|
+
- `## Steps`: in order. Each step names its files, says the change in one or two sentences, and gives its check. Every requirement on your list is covered by a step. Write exact text only where exactness matters: keys, user-facing strings, signatures, test cases (as a table). Leave out code the implementer can write from the description, and leave out status bookkeeping (task file, log): the dispatching session does that.
|
|
41
|
+
- `## Verification`: each "Done when" item with the command or check that proves it. Put `needs: running stack` or `needs: data change` on a check that cannot run from a clean checkout without starting services or altering data.
|
|
42
|
+
|
|
43
|
+
Link vault pages as `[[file-name]]`; refer to code by plain path. Verify any current output you quote by running or tracing the code.
|
|
44
|
+
|
|
45
|
+
## Return
|
|
46
|
+
|
|
47
|
+
A short message, not the plan:
|
|
48
|
+
|
|
49
|
+
- `Approach:` three lines at most.
|
|
50
|
+
- `Questions:` what only the user can answer, each with the answer the plan assumes. Leave out if none.
|
|
51
|
+
- `Split:` if the work does not fit one session, the tasks to split it into (title, dependencies). Leave out if not needed.
|
|
52
|
+
- If the area has a guide, `Guide:` facts you had to find in the code that it did not state and the next task in this area would need. One line each, at most eight.
|
|
@@ -1,104 +1,179 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Reads and changes docs/.sw/config.json, and writes the planner and implementer agent
|
|
3
|
-
//
|
|
4
|
-
|
|
2
|
+
// Reads and changes docs/.sw/config.json, and writes the planner and implementer agent files.
|
|
3
|
+
// A coding tool binds a model to an agent definition, not to a running session, so choosing a
|
|
4
|
+
// model for a role means writing that role's agent file for that tool.
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
6
|
import { dirname, join, resolve } from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
9
|
+
const ASSETS = join(dirname(fileURLToPath(import.meta.url)), '..', 'assets');
|
|
10
|
+
const GENERATED = 'Generated by sw-config from docs/.sw/config.json. Change models with sw-config, not here.';
|
|
11
|
+
const AREA_ID = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
12
|
+
|
|
10
13
|
const ROLES = {
|
|
11
|
-
plan: {
|
|
12
|
-
|
|
14
|
+
plan: {
|
|
15
|
+
name: 'sw-planner',
|
|
16
|
+
instructions: 'planner.md',
|
|
17
|
+
description: 'Writes the plan file for one Superwiki task. Use from sw-plan.',
|
|
18
|
+
},
|
|
19
|
+
implement: {
|
|
20
|
+
name: 'sw-implementer',
|
|
21
|
+
instructions: 'implementer.md',
|
|
22
|
+
description: 'Implements one Superwiki task from its task and plan files. Use from sw-implement.',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// How each tool wants an agent defined: where the file goes and what it looks like.
|
|
27
|
+
const TOOLS = {
|
|
28
|
+
claude: {
|
|
29
|
+
path: role => `.claude/agents/${role.name}.md`,
|
|
30
|
+
render: (role, body, model) => markdownAgent(role, body, model),
|
|
31
|
+
},
|
|
32
|
+
codex: {
|
|
33
|
+
path: role => `.codex/agents/${role.name}.toml`,
|
|
34
|
+
render: (role, body, model) => [
|
|
35
|
+
`# ${GENERATED}`,
|
|
36
|
+
`name = "${role.name.replace(/-/g, '_')}"`,
|
|
37
|
+
`description = ${JSON.stringify(role.description)}`,
|
|
38
|
+
...(model ? [`model = ${JSON.stringify(model)}`] : []),
|
|
39
|
+
// TOML literal strings cannot contain three single quotes in a row.
|
|
40
|
+
`developer_instructions = '''\n${body.replace(/'''/g, "' ' '")}\n'''`,
|
|
41
|
+
'',
|
|
42
|
+
].join('\n'),
|
|
43
|
+
},
|
|
44
|
+
copilot: {
|
|
45
|
+
path: role => `.github/agents/${role.name}.agent.md`,
|
|
46
|
+
render: (role, body, model) => markdownAgent(role, body, model),
|
|
47
|
+
},
|
|
13
48
|
};
|
|
49
|
+
|
|
50
|
+
function markdownAgent(role, body, model) {
|
|
51
|
+
const frontmatter = [`name: ${role.name}`, `description: ${role.description}`, ...(model ? [`model: ${model}`] : [])];
|
|
52
|
+
return `---\n${frontmatter.join('\n')}\n---\n\n<!-- ${GENERATED} -->\n\n${body}\n`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const TOOL_NAMES = Object.keys(TOOLS);
|
|
56
|
+
const ROLE_NAMES = Object.keys(ROLES);
|
|
57
|
+
|
|
14
58
|
const HELP = `config.mjs <command> [--root <dir>]
|
|
15
59
|
|
|
16
60
|
show print the configuration
|
|
17
|
-
model
|
|
18
|
-
model
|
|
61
|
+
model <${ROLE_NAMES.join('|')}> <tool> <model> set the model a tool uses for a role (tool: ${TOOL_NAMES.join(', ')})
|
|
62
|
+
model <${ROLE_NAMES.join('|')}> <tool> --unset go back to the tool's default model
|
|
19
63
|
areas "M=Mobile,B=Backend" add or rename task areas (existing areas are never removed)
|
|
20
64
|
sync [--tools claude,codex] (re)write agent files for the given tools, or for every configured tool`;
|
|
21
65
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
put(`.github/agents/${r.name}.agent.md`, `${fm.join('\n')}\n---\n\n<!-- ${header} -->\n\n${body}\n`);
|
|
66
|
+
class UsageError extends Error {}
|
|
67
|
+
|
|
68
|
+
function parseArgs(argv) {
|
|
69
|
+
const args = [];
|
|
70
|
+
let root = '.';
|
|
71
|
+
let tools = null;
|
|
72
|
+
for (let i = 0; i < argv.length; i++) {
|
|
73
|
+
if (argv[i] === '--root') root = argv[++i] ?? '.';
|
|
74
|
+
else if (argv[i] === '--tools') tools = (argv[++i] ?? '').split(',').map(s => s.trim()).filter(Boolean);
|
|
75
|
+
else args.push(argv[i]);
|
|
76
|
+
}
|
|
77
|
+
return { command: args[0], args: args.slice(1), root: resolve(root), tools };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function loadConfig(root) {
|
|
81
|
+
const path = join(root, 'docs/.sw/config.json');
|
|
82
|
+
if (!existsSync(path)) throw new UsageError('no docs/.sw/config.json here; run sw-init first');
|
|
83
|
+
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
84
|
+
config.models = { plan: {}, implement: {}, ...config.models };
|
|
85
|
+
config.tools ??= [];
|
|
86
|
+
return { config, save: () => writeFileSync(path, JSON.stringify(config, null, 2) + '\n') };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const formatAreas = areas => Object.entries(areas).map(([id, name]) => `${id}=${name}`).join(', ');
|
|
90
|
+
|
|
91
|
+
// Writes a generated file and says what happened to it, judged by content.
|
|
92
|
+
function writeGenerated(root, relPath, content) {
|
|
93
|
+
const path = join(root, relPath);
|
|
94
|
+
const before = existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
95
|
+
if (before !== content) {
|
|
96
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
97
|
+
writeFileSync(path, content);
|
|
98
|
+
}
|
|
99
|
+
const state = before === null ? 'created ' : before === content ? 'unchanged' : 'updated ';
|
|
100
|
+
console.log(`${state} ${relPath}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function writeAgentFiles(root, config, toolNames) {
|
|
104
|
+
for (const [roleName, role] of Object.entries(ROLES)) {
|
|
105
|
+
const body = readFileSync(join(ASSETS, role.instructions), 'utf8').trim();
|
|
106
|
+
for (const toolName of toolNames) {
|
|
107
|
+
const tool = TOOLS[toolName];
|
|
108
|
+
writeGenerated(root, tool.path(role), tool.render(role, body, config.models[roleName][toolName]));
|
|
66
109
|
}
|
|
67
110
|
}
|
|
68
111
|
}
|
|
69
|
-
const save = () => writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
70
112
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
113
|
+
function rememberTools(config, toolNames) {
|
|
114
|
+
for (const name of toolNames) if (!config.tools.includes(name)) config.tools.push(name);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const COMMANDS = {
|
|
118
|
+
show({ config }) {
|
|
119
|
+
console.log(`tasks: ${config.tasks ? 'on' : 'off'}`);
|
|
120
|
+
if (config.tasks) console.log(`areas: ${formatAreas(config.areas)}`);
|
|
121
|
+
for (const role of ROLE_NAMES) {
|
|
122
|
+
console.log(`${role}: ${TOOL_NAMES.map(tool => `${tool}=${config.models[role][tool] || 'default'}`).join(' ')}`);
|
|
123
|
+
}
|
|
124
|
+
console.log(`agent files for: ${config.tools.join(', ') || 'none'}`);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
model({ root, config, save, args }) {
|
|
128
|
+
const [role, tool, model] = args;
|
|
129
|
+
if (!ROLES[role] || !TOOLS[tool] || !model) {
|
|
130
|
+
throw new UsageError(`usage: config.mjs model <${ROLE_NAMES.join('|')}> <${TOOL_NAMES.join('|')}> <model|--unset>`);
|
|
131
|
+
}
|
|
132
|
+
if (model === '--unset') delete config.models[role][tool];
|
|
133
|
+
else config.models[role][tool] = model;
|
|
134
|
+
rememberTools(config, [tool]);
|
|
135
|
+
save();
|
|
136
|
+
writeAgentFiles(root, config, [tool]);
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
areas({ config, save, args }) {
|
|
140
|
+
if (!config.tasks) throw new UsageError('the task module is off; run sw-init with --tasks first');
|
|
141
|
+
for (const pair of (args[0] || '').split(',').map(s => s.trim()).filter(Boolean)) {
|
|
142
|
+
const [id, ...nameParts] = pair.split('=');
|
|
143
|
+
if (!AREA_ID.test(id)) throw new UsageError(`bad area id "${id}"`);
|
|
144
|
+
config.areas[id] = nameParts.join('=').trim() || config.areas[id] || id;
|
|
145
|
+
}
|
|
146
|
+
save();
|
|
147
|
+
console.log(`areas: ${formatAreas(config.areas)}`);
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
sync({ root, config, save, tools }) {
|
|
151
|
+
const toolNames = tools ?? config.tools;
|
|
152
|
+
const unknown = toolNames.filter(name => !TOOLS[name]);
|
|
153
|
+
if (unknown.length) throw new UsageError(`unknown tool: ${unknown.join(', ')}`);
|
|
154
|
+
if (!toolNames.length) throw new UsageError(`no tools configured; pass --tools ${TOOL_NAMES.join(',')}`);
|
|
155
|
+
rememberTools(config, toolNames);
|
|
156
|
+
save();
|
|
157
|
+
writeAgentFiles(root, config, toolNames);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
function main(argv) {
|
|
162
|
+
const { command, args, root, tools } = parseArgs(argv);
|
|
163
|
+
if (!command || command === 'help' || command === '--help') {
|
|
164
|
+
console.log(HELP);
|
|
165
|
+
return 0;
|
|
76
166
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
} else if (cmd === 'areas') {
|
|
87
|
-
if (!config.tasks) fail('the task module is off; run sw-init with --tasks first');
|
|
88
|
-
for (const pair of (rest[0] || '').split(',').map(s => s.trim()).filter(Boolean)) {
|
|
89
|
-
const [id, ...name] = pair.split('=');
|
|
90
|
-
if (!/^[A-Za-z][A-Za-z0-9]*$/.test(id)) fail(`bad area id "${id}"`);
|
|
91
|
-
config.areas[id] = name.join('=').trim() || config.areas[id] || id;
|
|
167
|
+
try {
|
|
168
|
+
const run = COMMANDS[command];
|
|
169
|
+
if (!run) throw new UsageError(`unknown command ${command}\n\n${HELP}`);
|
|
170
|
+
run({ root, args, tools, ...loadConfig(root) });
|
|
171
|
+
return 0;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (!(error instanceof UsageError)) throw error;
|
|
174
|
+
console.error(error.message);
|
|
175
|
+
return 2;
|
|
92
176
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const ti = rest.indexOf('--tools');
|
|
97
|
-
const tools = ti >= 0 ? rest[ti + 1].split(',').map(s => s.trim()) : config.tools;
|
|
98
|
-
const bad = tools.filter(t => !TOOLS.includes(t));
|
|
99
|
-
if (bad.length) fail(`unknown tool: ${bad.join(', ')}`);
|
|
100
|
-
if (!tools.length) fail('no tools configured; pass --tools claude,codex,copilot');
|
|
101
|
-
for (const t of tools) if (!config.tools.includes(t)) config.tools.push(t);
|
|
102
|
-
save();
|
|
103
|
-
sync(tools);
|
|
104
|
-
} else fail(`unknown command ${cmd}\n\n${HELP}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
process.exitCode = main(process.argv.slice(2));
|