@detergent-software/conductor 2.2.0-dev.3 → 3.0.0-dev.1
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/CHANGELOG.md +26 -0
- package/README.md +44 -22
- package/build/checkout-sync.js +4 -21
- package/build/checkout-sync.js.map +1 -1
- package/build/claude.js +6 -2
- package/build/claude.js.map +1 -1
- package/build/cli.js +32 -25
- package/build/cli.js.map +1 -1
- package/build/console.js +13 -29
- package/build/console.js.map +1 -1
- package/build/daemon.js +42 -0
- package/build/daemon.js.map +1 -1
- package/build/help.js +9 -21
- package/build/help.js.map +1 -1
- package/build/inbox.js +8 -0
- package/build/inbox.js.map +1 -1
- package/build/manager.js +74 -3
- package/build/manager.js.map +1 -1
- package/build/paths.js +2 -0
- package/build/paths.js.map +1 -1
- package/build/plugin.js +136 -0
- package/build/plugin.js.map +1 -0
- package/build/profile.js +5 -2
- package/build/profile.js.map +1 -1
- package/build/project-check.js +21 -9
- package/build/project-check.js.map +1 -1
- package/build/server.js +11 -0
- package/build/server.js.map +1 -1
- package/build/upgrade.js +9 -2
- package/build/upgrade.js.map +1 -1
- package/package.json +8 -8
- package/plugin/.claude-plugin/plugin.json +7 -0
- package/plugin/README.md +18 -0
- package/{mod → plugin}/hooks/model.ts +22 -4
- package/{mod → plugin}/hooks/register.tsx +63 -17
- package/plugin/skills/conductor-console/SKILL.md +94 -0
- package/plugin/skills/create-verification-skill/LICENSE +21 -0
- package/plugin/skills/create-verification-skill/README.md +9 -0
- package/plugin/skills/create-verification-skill/SKILL.md +44 -0
- package/plugin/skills/create-verification-skill/manifest.json +27 -0
- package/plugin/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/plugin/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/plugin/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/plugin/skills/maintain-verification-skill/LICENSE +21 -0
- package/plugin/skills/maintain-verification-skill/README.md +9 -0
- package/plugin/skills/maintain-verification-skill/SKILL.md +39 -0
- package/plugin/skills/maintain-verification-skill/manifest.json +23 -0
- package/plugin/skills/work-item/SKILL.md +253 -0
- package/plugin/skills/work-item/trackers/ado-boards.md +23 -0
- package/plugin/skills/work-item/trackers/github-issues.md +23 -0
- package/plugin/skills/work-item/trackers/linear.md +21 -0
- package/protocol/PROTOCOL.md +1 -1
- package/build/mod.js +0 -53
- package/build/mod.js.map +0 -1
- package/mod/.claude-plugin/marketplace.json +0 -6
- package/mod/.claude-plugin/plugin.json +0 -7
- package/mod/README.md +0 -19
- /package/{mod → plugin}/hooks/hooks.json +0 -0
- /package/{mod → plugin}/types/index.d.ts +0 -0
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
bandEntries,
|
|
12
12
|
clearedText,
|
|
13
13
|
CONSOLE_SKILL,
|
|
14
|
+
daemonOf,
|
|
14
15
|
endpointOf,
|
|
15
16
|
errorOf,
|
|
16
17
|
findPlan,
|
|
@@ -77,6 +78,8 @@ const cleared = atom({ key: 'cleared', plugin: 'conductor' } as const, null);
|
|
|
77
78
|
// flags start over with the module, and session.start sets the first again after a reload.
|
|
78
79
|
let isConsole = false;
|
|
79
80
|
let isRefreshing = false;
|
|
81
|
+
// The plugin folder's conductor.json, read once per load (daemon).
|
|
82
|
+
let named: null | Promise<ReturnType<typeof daemonOf>> = null;
|
|
80
83
|
// The newest event id the stream showed, so a restarted `conduct events` picks up after it.
|
|
81
84
|
let lastEventId: null | number = null;
|
|
82
85
|
// Set once the console has said that pushes no longer reach the phone; a push that does clears it.
|
|
@@ -104,13 +107,13 @@ async function answer(
|
|
|
104
107
|
body: { action: 'approve' | 'done' } | { action: 'revise'; text: string },
|
|
105
108
|
): Promise<string> {
|
|
106
109
|
try {
|
|
107
|
-
const cli =
|
|
110
|
+
const cli = await cliPath($);
|
|
108
111
|
if (body.action !== 'done' && (await $.fs.exists(cli))) {
|
|
109
112
|
const failed = `not ${body.action}d`;
|
|
110
113
|
const argv = body.action === 'revise' ? ['revise', `#${id}`, body.text] : ['approve', `#${id}`];
|
|
111
114
|
let run;
|
|
112
115
|
try {
|
|
113
|
-
run = await $.process.run(['node', cli, ...argv], { timeoutMs: ANSWER_RUN_MS });
|
|
116
|
+
run = await $.process.run(['node', cli, ...argv], { ...(await cliInit($)), timeoutMs: ANSWER_RUN_MS });
|
|
114
117
|
} catch (error) {
|
|
115
118
|
return `${failed}: ${error instanceof Error ? error.message : String(error)}`;
|
|
116
119
|
}
|
|
@@ -146,10 +149,13 @@ function armIdle($: EngineInterface): void {
|
|
|
146
149
|
// and resumes quickly. The daemon refuses a second attach of a worker while one runs.
|
|
147
150
|
async function attach($: EngineInterface, id: number, name: string): Promise<string> {
|
|
148
151
|
try {
|
|
149
|
-
const cli =
|
|
152
|
+
const cli = await cliPath($);
|
|
150
153
|
if (await $.fs.exists(cli)) {
|
|
151
154
|
$.ui.toast(`attaching to ${name}…`);
|
|
152
|
-
const run = await $.process.run(['node', cli, 'attach', `#${id}`], {
|
|
155
|
+
const run = await $.process.run(['node', cli, 'attach', `#${id}`], {
|
|
156
|
+
...(await cliInit($)),
|
|
157
|
+
timeoutMs: ATTACH_RUN_MS,
|
|
158
|
+
});
|
|
153
159
|
refreshSoon($);
|
|
154
160
|
return runText(run, { failed: `could not attach to ${name}`, ok: `attached to ${name}` });
|
|
155
161
|
}
|
|
@@ -187,9 +193,16 @@ async function callApi(
|
|
|
187
193
|
return { body: parsed, status: answer.status };
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
196
|
+
// What a CLI the mod runs needs to reach this plugin's daemon: its home, which a session the
|
|
197
|
+
// conductor started in the background doesn't have in its environment.
|
|
198
|
+
async function cliInit($: EngineInterface): Promise<{ env?: Record<string, string> }> {
|
|
199
|
+
const { home } = await daemon($);
|
|
200
|
+
return home === null ? {} : { env: { CONDUCTOR_HOME: home } };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// The conductor's own CLI (model.ts, daemonOf); a checkout without a build has none.
|
|
204
|
+
async function cliPath($: EngineInterface): Promise<string> {
|
|
205
|
+
return (await daemon($)).cli;
|
|
193
206
|
}
|
|
194
207
|
|
|
195
208
|
// Stages a band approve for its `y`; it lapses unless pressed within CONFIRM_MS.
|
|
@@ -207,18 +220,36 @@ async function copy(
|
|
|
207
220
|
$.ui.toast(copied.isCopied ? `copied: ${text}` : `not copied: ${text}`);
|
|
208
221
|
}
|
|
209
222
|
|
|
223
|
+
// The conductor this plugin folder belongs to (model.ts, daemonOf). Read once per load: the file
|
|
224
|
+
// changes only with the folder, and that reloads this module.
|
|
225
|
+
async function daemon($: EngineInterface): Promise<ReturnType<typeof daemonOf>> {
|
|
226
|
+
named ??= (async () => {
|
|
227
|
+
let text: null | string = null;
|
|
228
|
+
try {
|
|
229
|
+
text = await $.fs.read(`${$.plugin.root}/conductor.json`);
|
|
230
|
+
} catch {
|
|
231
|
+
// The package's own plugin folder has none.
|
|
232
|
+
}
|
|
233
|
+
return daemonOf($.plugin.root, text);
|
|
234
|
+
})();
|
|
235
|
+
return named;
|
|
236
|
+
}
|
|
237
|
+
|
|
210
238
|
function disarmIdle(): void {
|
|
211
239
|
idleTimer?.cancel();
|
|
212
240
|
idleTimer = null;
|
|
213
241
|
}
|
|
214
242
|
|
|
215
|
-
// The running daemon's port and token, from its home's files
|
|
243
|
+
// The running daemon's port and token, from its home's files: the home the plugin's copy names,
|
|
244
|
+
// else the environment's. Throws when none runs.
|
|
216
245
|
async function endpoint($: EngineInterface): Promise<Endpoint> {
|
|
217
|
-
const home =
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
246
|
+
const home =
|
|
247
|
+
(await daemon($)).home ??
|
|
248
|
+
homeOf({
|
|
249
|
+
conductorHome: await $.env.get('CONDUCTOR_HOME'),
|
|
250
|
+
home: await $.env.get('HOME'),
|
|
251
|
+
userProfile: await $.env.get('USERPROFILE'),
|
|
252
|
+
});
|
|
222
253
|
if (home === null) throw new Error(NOT_RUNNING);
|
|
223
254
|
let found: Endpoint | null;
|
|
224
255
|
try {
|
|
@@ -235,7 +266,7 @@ async function endpoint($: EngineInterface): Promise<Endpoint> {
|
|
|
235
266
|
// the last event it showed, so nothing in between is lost. One that keeps exiting at once (no
|
|
236
267
|
// build, no node) is given up on, and the poll carries on alone.
|
|
237
268
|
async function follow($: EngineInterface): Promise<void> {
|
|
238
|
-
const cli =
|
|
269
|
+
const cli = await cliPath($);
|
|
239
270
|
if (!(await $.fs.exists(cli))) {
|
|
240
271
|
$.ui.log(`conductor: no ${cli}; the console polls instead of following events`, { to: 'debug' });
|
|
241
272
|
return;
|
|
@@ -253,7 +284,7 @@ async function follow($: EngineInterface): Promise<void> {
|
|
|
253
284
|
'--console',
|
|
254
285
|
...(lastEventId === null ? [] : ['--since', String(lastEventId)]),
|
|
255
286
|
];
|
|
256
|
-
for await (const chunk of $.process.spawn({ argv })) {
|
|
287
|
+
for await (const chunk of $.process.spawn({ argv, ...(await cliInit($)) })) {
|
|
257
288
|
if (chunk.stream !== 'stdout') continue;
|
|
258
289
|
buffer += chunk.text;
|
|
259
290
|
for (let end = buffer.indexOf('\n'); end >= 0; end = buffer.indexOf('\n')) {
|
|
@@ -507,6 +538,17 @@ async function reportCache($: EngineInterface, body: Record<string, unknown>): P
|
|
|
507
538
|
}
|
|
508
539
|
}
|
|
509
540
|
|
|
541
|
+
// POST /plugin/seen with this session's id. No daemon, no report: nothing waits on it then.
|
|
542
|
+
async function reportPlugin($: EngineInterface): Promise<void> {
|
|
543
|
+
try {
|
|
544
|
+
await callApi($, 'POST', '/plugin/seen', { sessionId: await $.session.id() });
|
|
545
|
+
} catch (error) {
|
|
546
|
+
$.ui.log(`conductor: no plugin report (${error instanceof Error ? error.message : String(error)})`, {
|
|
547
|
+
to: 'debug',
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
510
552
|
// `/notify <profile> [--until <when>]` and the pane's picker: switch the notification profile.
|
|
511
553
|
async function switchProfile($: EngineInterface, name: string, until: null | string = null): Promise<string> {
|
|
512
554
|
try {
|
|
@@ -530,6 +572,10 @@ async function why($: EngineInterface, item: string): Promise<string> {
|
|
|
530
572
|
export const register: Register = (on) => {
|
|
531
573
|
on('session.start', async ($, e, next) => {
|
|
532
574
|
const started = await next(e);
|
|
575
|
+
// Every session that loads this plugin was started by the conductor. Workers too, which run in
|
|
576
|
+
// the background: the daemon wants each start's report, so it can tell a worker that woke
|
|
577
|
+
// without the plugin (its saved --plugin-dir gone, ATK-522), and says so (ATK-525).
|
|
578
|
+
void reportPlugin($);
|
|
533
579
|
if (!e.isInteractive) return started;
|
|
534
580
|
isConsole = (await $.env.get('CONDUCTOR_CONSOLE')) === '1';
|
|
535
581
|
if (!isConsole) return started;
|
|
@@ -544,7 +590,7 @@ export const register: Register = (on) => {
|
|
|
544
590
|
});
|
|
545
591
|
|
|
546
592
|
// A console the skill never ran in (one started by hand as `claude -n conductor`, ATK-385) is
|
|
547
|
-
// told to load it before it answers, each prompt until it has. `/conductor-console` or the Skill
|
|
593
|
+
// told to load it before it answers, each prompt until it has. `/conductor:conductor-console` or the Skill
|
|
548
594
|
// tool expands it before its prompt is submitted; a /clear or a resume starts a conversation
|
|
549
595
|
// without it.
|
|
550
596
|
on('skill.prompt', { skill: CONSOLE_SKILL }, async ($, e, next) => {
|
|
@@ -652,7 +698,7 @@ export const register: Register = (on) => {
|
|
|
652
698
|
|
|
653
699
|
// The band above the prompt, in the console, while the inbox has items. A bare digit typed into
|
|
654
700
|
// an empty prompt presses a band Button, so a plan's approve takes a second press (`y`). Until
|
|
655
|
-
// /conductor-console runs, a warning sits above it, or above the engine's own (ATK-385); after the
|
|
701
|
+
// /conductor:conductor-console runs, a warning sits above it, or above the engine's own (ATK-385); after the
|
|
656
702
|
// mod cleared an idle console, a plain line that says so instead (ATK-386).
|
|
657
703
|
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
|
|
658
704
|
if (!isConsole || e.props.hasSurvey) return next(e);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: conductor-console
|
|
3
|
+
description: Be the conductor console - the one always-open Claude session beside the conductor daemon (`conduct`). Wakes only for an escalation from the daemon's rules or for Jason. Handles escalations (a worker's conductor Handoff, a failed run, a stall, a vague item) and reports back through `conduct escalations done`. Answers Jason from `conduct status`, `inbox`, `queue` and `why`, and turns plain words into `conduct pause`, `focus`, `notify profile`, `approve` or `retire`. No loop, no ticks. Ships in the conductor plugin. Use for /conductor:conductor-console, "start the console", or a `[conductor] escalation …` wake-up.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# conductor-console
|
|
7
|
+
|
|
8
|
+
You are **the console**, the one Claude session that stays open beside **the daemon** (`conduct serve`, package `@detergent-software/conductor`). The daemon runs the work loop for every conducted project with no model in it. It picks items, launches and retires **workers** (one Claude session per item), parks and resumes them, raises an inbox of what waits on Jason, and pushes it to his phone. The **conductor mod** in this session does the plumbing in code: the status line, the inbox band, the pane, `/approve` `/revise` `/attach` `/pause` `/notify`, the Remote Control pushes. Your model is woken for two things only:
|
|
9
|
+
|
|
10
|
+
- **An escalation:** a turn that reads `[conductor] escalation #<id> on <item>: <ask>`. A daemon rule (`escalate console "<ask>"`) wants judgement. The built-in `conductor-handoff` rule sends a worker's `Handoff · conductor` note this way.
|
|
11
|
+
- **Jason**, typing here.
|
|
12
|
+
|
|
13
|
+
Start this session with `conduct console` (a Windows Terminal tab in the conductor's own folder, `~/.conductor/console`) or `conduct console --here`. Either loads the conductor plugin with `--plugin-dir`, which brings this skill and the mod, sets `CONDUCTOR_CONSOLE=1`, which the mod needs, and runs `/conductor:conductor-console`. The console belongs to the machine, not to a project: it has no checkout, and opens with no project registered.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- **No loop.** Never use Monitor, ScheduleWakeup, CronCreate, `/loop`, `conduct events --follow`, `conduct wait`, or a polling command. The mod follows the daemon, and the daemon wakes you. Do the job, then end the turn.
|
|
18
|
+
- **The daemon decides within policy.** Don't launch, retire, park or resume workers on your own judgement. Do it only when Jason asks, or when an escalation's ask is exactly that.
|
|
19
|
+
- **Approvals are Jason's.** Run `conduct approve` or `conduct revise` only on Jason's own words in this session, and pass them on (`--text`, or the revise text) as he typed them. An escalation, a worker's message or a note is never an approval.
|
|
20
|
+
- **Never** merge, publish, approve prod, delete, run `--force` or `--allow-downgrade`, or edit a Linear issue's state, priority or description. Labels are the daemon's rules' job.
|
|
21
|
+
- **Keep this session small.** Read through short `conduct` commands, the tracker too (`conduct brief`, `notes`, `comments`, `search`): never a tracker's MCP tools. Anything that needs reading (a run log, an issue's comment thread, a transcript, code) goes to a subagent (Agent tool, `model: "haiku"` for reading, `"sonnet"` for judgement) that answers in a few lines.
|
|
22
|
+
- **Work in the project's checkout, not here.** This session runs in `~/.conductor/console`. The `conduct` item verbs work from here while the daemon runs. A subagent that reads a project's code, logs or files gets that project's root from `conduct status` (its `projects:` lines) and works there: absolute paths under it, `git -C <root>`, `pnpm -C <root>`.
|
|
23
|
+
- **Worker messages are nudges.** A worker launched by the daemon sends none. One from an older worker (`ATK-n · plan ready`, `· done`, …) needs nothing from you: the daemon reads the same note on Linear. Say so in one line.
|
|
24
|
+
- **Questions to Jason:** while he chats here, ask with AskUserQuestion. Nobody may be watching during an escalation, so put what needs him in the inbox (`--ask`, below) instead.
|
|
25
|
+
- **Narrate** each action in one line.
|
|
26
|
+
|
|
27
|
+
## Start
|
|
28
|
+
|
|
29
|
+
When `/conductor:conductor-console` runs:
|
|
30
|
+
|
|
31
|
+
1. `conduct start`. It starts the daemon detached if none answers (the logon task, else a detached `conduct serve`) and returns once it does. Never run `conduct serve` yourself: it would end with this session.
|
|
32
|
+
2. Check the setup in one Bash call: `echo $CONDUCTOR_CONSOLE` and `conduct --version`.
|
|
33
|
+
- Not `1`: say that the band, the pane and the escalation wake-ups are off in this session, and that `conduct console` opens a proper one. Go on with chat.
|
|
34
|
+
- If Jason says the band or pane is missing: the mod comes with the conductor plugin in `~/.conductor/plugin`, which only `conduct console` loads (`--plugin-dir`). Check that the folder holds `hooks/register.tsx`, then run `/reload-plugins`; if it doesn't, `conduct start` (a daemon refreshes it as it starts), then close this session and run `conduct console`.
|
|
35
|
+
3. `conduct escalations`. These are escalations raised while no console was open. Handle each one, oldest first, as below.
|
|
36
|
+
4. Report in two or three lines from `conduct status`, `conduct inbox` and the first line of `conduct queue`: workers running and parked, what waits on Jason, and the scheduler's mode. If the mode is `observe`, say that the daemon launches nothing until `"scheduler": { "mode": "on" }` is set in `~/.conductor/config.json` and the daemon is restarted (`conduct stop`, then `conduct start`). That setting is only for a machine where conduct-atk's loop still runs.
|
|
37
|
+
|
|
38
|
+
Then end the turn.
|
|
39
|
+
|
|
40
|
+
## Escalations
|
|
41
|
+
|
|
42
|
+
`[conductor] escalation #<id> on <item>: <ask>`, or a row from `conduct escalations`:
|
|
43
|
+
|
|
44
|
+
1. **What fired it:** `conduct why <item>` shows the rule and its trigger. `conduct escalations --json` has the rule's ref.
|
|
45
|
+
2. **Do the job** within the rules above. Use a subagent for anything that needs reading:
|
|
46
|
+
- **A worker's conductor Handoff** (`default:conductor-handoff`): read the newest one with `conduct notes <item> --phase Handoff --kind conductor --last`, which prints the note whole; never a tracker's MCP tools. `conduct comments <item> --since <its time>` shows what was said after it. Do what it asks when a `conduct` command does it. To reach a worker: `conduct status` shows whether it is `running` or `parked`. SendMessage goes only to a running, idle worker. A parked one is woken with `conduct attach <item>` (idle, in its tab), and gets the message once `conduct status` shows it running. Anything outside your remit goes to Jason with `--ask`.
|
|
47
|
+
- **A failed run:** a subagent reads the run's log and says whether it is the change or the pipeline (flaky, infra), with the failing step and line.
|
|
48
|
+
- **A stall:** `conduct status` (activity, waits, phase), `conduct inbox` (its `permission`, `stall` or `failed` item) and `conduct why <item>`. Say what it waits on and who can move it.
|
|
49
|
+
- **A vague item:** a subagent reads it (`conduct brief <item>`, `conduct comments <item>`) and drafts a sharper description and a "done when". Put the draft in the `--ask` for Jason to adopt; never edit the description.
|
|
50
|
+
- **Anything else:** do what the ask says, if the rules allow it.
|
|
51
|
+
3. **Report back, always**, also when there was nothing to do:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
conduct escalations done <id> "<what you found and what you did, one or two lines>"
|
|
55
|
+
conduct escalations done <id> --ask "<what Jason must do, with the command or link>"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`--ask` puts the finding in the inbox as a `console` item, which the daemon pushes to Jason's phone. Use it only when he must act. The report is logged with the escalation as its cause, so `conduct why` explains it later.
|
|
59
|
+
|
|
60
|
+
## Chat
|
|
61
|
+
|
|
62
|
+
Answer from the daemon, never from memory:
|
|
63
|
+
|
|
64
|
+
| Jason asks | Run |
|
|
65
|
+
|---|---|
|
|
66
|
+
| what's running, parked, stuck | `conduct status`, `conduct inbox` |
|
|
67
|
+
| why isn't X started; what's next | `conduct queue` (`--fresh` reads the trackers now) |
|
|
68
|
+
| why did the daemon do that | `conduct why <item or event id>` |
|
|
69
|
+
| what were the rules, overrides, profile | `conduct rules`, `conduct notify` |
|
|
70
|
+
| what happened lately, what shipped | `conduct history` (the last 24 h; `today`, `yesterday`, `7d`, a date or `A..B`, `--project <name>`); `conduct retro <range>` for a Markdown report |
|
|
71
|
+
| the detail of one item | `conduct why <item>`; for more, a subagent reads `conduct events --since <id>` (an id from `conduct why`) and summarizes it |
|
|
72
|
+
|
|
73
|
+
Turn requests into commands, then show back what took effect: quote the line from `conduct rules` (its overrides) or `conduct notify` that proves it.
|
|
74
|
+
|
|
75
|
+
| Jason says | Run |
|
|
76
|
+
|---|---|
|
|
77
|
+
| "pause Cadence until Monday" | `conduct pause cadence --until monday` |
|
|
78
|
+
| "stop all launches for now" | `conduct pause` (until `conduct unpause`) |
|
|
79
|
+
| "resume ATK" | `conduct unpause atk` |
|
|
80
|
+
| "put ATK first this afternoon" | `conduct focus atk --until 17:00` (`--slots <n>` or `--weight <n>` when he says so) |
|
|
81
|
+
| "I'm away until tomorrow" | `conduct notify profile away --until tomorrow` |
|
|
82
|
+
| "approve ATK-12" / "ATK-12: change X" | `conduct approve ATK-12` / `conduct revise ATK-12 <his words>` |
|
|
83
|
+
| "start ATK-40 now" | `conduct launch ATK-40` |
|
|
84
|
+
| "show me ATK-12" | `conduct attach ATK-12` (opens its tab) |
|
|
85
|
+
| "drop ATK-12's worker" | `conduct retire ATK-12`. On a refusal, report the unfinished work, and never add `--force` yourself |
|
|
86
|
+
| "always do X when Y" | a rule in `~/.conductor/rules.json` (`when` / `do`, see the conductor README's Rules). Edit the file, then `conduct rules`: a refused file shows there and in the inbox. Project rules change by PR in the project's `.claude/conductor.json`, which is work for a worker |
|
|
87
|
+
|
|
88
|
+
`--until` takes `17:00` (the next one), `tomorrow`, a day such as `monday`, or a date and time. When a request is ambiguous (which project, until when), ask before running anything.
|
|
89
|
+
|
|
90
|
+
## Recovery
|
|
91
|
+
|
|
92
|
+
- **This session closed, crashed or got slow:** run `conduct console` (or `/clear`, then `/conductor:conductor-console`). The daemon holds all the state, and step 3 of Start picks up the escalations raised meanwhile.
|
|
93
|
+
- **The daemon is down** (`conduct status` says not running): `conduct start`. Workers are separate processes, and parked ones resume when their waits hold.
|
|
94
|
+
- **Back to conduct-atk** (a rollback): `"scheduler": { "mode": "observe" }` in `~/.conductor/config.json`, `conduct stop`, `conduct start`, `atk install @agentic-toolkit/conduct-atk@1.7.5 --pin`, then close this console and run `/conduct-atk` as before.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lauren Tan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# create-verification-skill
|
|
2
|
+
|
|
3
|
+
Generate a project-local verification skill that drives the app like a user and proves behavior with evidence.
|
|
4
|
+
|
|
5
|
+
Invoke with `/create-verification-skill`.
|
|
6
|
+
|
|
7
|
+
## Origin
|
|
8
|
+
|
|
9
|
+
Ported from [pstack](https://github.com/cursor/plugins/tree/main/pstack) by Lauren Tan (poteto), MIT licensed. The Cursor version routed work across Claude, GPT, and Grok through a Cursor rules file and used Cursor-only tooling. This port targets Claude Code: subagents use the Agent tool with Claude models (`opus`, `sonnet`, `fable`), paths use `.claude/`, and references to Cursor-only skills and plugins are removed or replaced.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-verification-skill
|
|
3
|
+
description: "Generate a project-local verification skill that drives your app the way a user does — any language, framework, or platform. Use for /create-verification-skill, \"make a control skill for this repo\", or when a project has no scripted way to prove UI/CLI/service behavior."
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Create a verification skill
|
|
8
|
+
|
|
9
|
+
Every serious project needs a scripted way to drive the real app and prove behavior: launch it, exercise a feature the way a user would, and capture evidence. This skill generates that as a project-local skill (`.claude/skills/verify-<app>/`) tailored to the repo. You write the generator's output for the next agent, not for a human: it will be read cold, mid-task, by an agent that has never seen the app.
|
|
10
|
+
|
|
11
|
+
## 1. Interview the repo, not the user
|
|
12
|
+
|
|
13
|
+
Answer these from the codebase and only ask the user what you cannot observe:
|
|
14
|
+
|
|
15
|
+
- **Surface:** what does a user actually touch? A web UI, a CLI/TUI, a desktop app, an API, a mobile app, a library? A repo can have several; pick the primary one and note the rest.
|
|
16
|
+
- **Run:** how does the app start locally? Prefer the repo's own documented dev command (package scripts, Makefile, README quickstart). Note ports, env vars, seed data, auth.
|
|
17
|
+
- **Drive:** how can an agent interact with it programmatically? Existing harnesses first — Playwright/Cypress specs, expect scripts, PTY helpers, curl-able endpoints, a debug port. Only then pick a generic recipe: for web UIs, the Claude-in-Chrome MCP tools (`mcp__claude-in-chrome__*`, loaded with ToolSearch) or Playwright; CDP for Electron; a tmux/PTY harness for CLI/TUI; plain HTTP for services.
|
|
18
|
+
- **Observe:** what evidence can be captured? Screenshots, terminal transcripts, response bodies, logs, exit codes, DB state.
|
|
19
|
+
- **Isolate:** can two instances run side by side (ports, data dirs, profiles)? If not, say so in the generated skill: refusing to double-drive a shared instance beats corrupting the user's session.
|
|
20
|
+
|
|
21
|
+
If the checkout doesn't build or start as-is, fix that first (or report it precisely) before generating; a skill written against a broken base teaches wrong steps. When an irrelevant missing asset blocks startup (a static dir the API never serves, a sample config), the generated skill may create it, clearly marked as verification scaffolding, and remove it in cleanup.
|
|
22
|
+
|
|
23
|
+
## 2. Generate the skill
|
|
24
|
+
|
|
25
|
+
Write `.claude/skills/verify-<app>/SKILL.md` with YAML frontmatter (`name: verify-<app>` and a `description` that names the app, the surface, and when to reach for it — without frontmatter the skill never registers) and these sections, each grounded in what the interview actually found (no placeholders left):
|
|
26
|
+
|
|
27
|
+
- **Launch:** the exact command that starts the app for verification, and how to tell it's ready (a log line, a port answering, a prompt). Include teardown. For a short-lived CLI or TUI there is no server to keep alive: launch means build the binary (or install deps) once, then start each drive in its own isolated PTY or tmux session.
|
|
28
|
+
- **Doctor:** one read-only check that answers "is this instance worth driving?" — process up, right version/build, port owned by us, auth valid. An agent runs this first whenever anything looks off.
|
|
29
|
+
- **Drive:** the harness recipe with real selectors/commands from this repo, not examples. Prefer stable handles (ARIA labels, data attributes, prompt strings, route paths) over coordinates and tab order.
|
|
30
|
+
- **Evidence:** what to capture for a proof and where it goes. State the proof standards: exercise the real user path, not internal setters or test-only endpoints; capture the action and the resulting state, not just the final screen; verify side effects (files written, rows inserted, messages sent) alongside what's visible; mocks only where a production boundary already isolates the external system. When the safe path is a dry-run or test mode, verify what it actually skips by observing (files, network, git refs) rather than trusting its name: some dry-runs still touch the network or open a browser.
|
|
31
|
+
- **Cleanup:** how to tear down instances the run created. Never kill by process name; kill what you started. Cleanup removes instances and scratch state, never the evidence: proof artifacts survive the teardown, in a location the skill names.
|
|
32
|
+
- **Helpers:** any script the skill ships is executable and its invocation is shown in the skill body. A helper the reader has to reverse-engineer is not a helper.
|
|
33
|
+
|
|
34
|
+
## 3. Seed the feature map
|
|
35
|
+
|
|
36
|
+
Create `.claude/skills/verify-<app>/features/README.md` plus one file per user-facing feature you can identify (aim for the top 3-5 to start, from routes, commands, menus, or docs). Follow the shape in [`references/feature-map-example/`](references/feature-map-example/), with a README index and one file per feature. Each file answers, from the user's point of view: what the feature is, how to reach it, how to drive it with the harness, and what observable end state proves it works. The four H2s are `Sub-features`, `How to get to it (user POV)`, `Driving it with <harness>`, and `Gotchas`. The map is the repo's maintained verification source; a proof that drives one convenient entry point is incomplete when the map lists others.
|
|
37
|
+
|
|
38
|
+
## 4. Prove the generated skill before handing it over
|
|
39
|
+
|
|
40
|
+
Run its own instructions end to end once: launch, doctor, drive ONE mapped feature (one is enough; the map exists so later runs can cover the rest), capture evidence, clean up. After cleanup, confirm the evidence still exists at the named location — a cleanup that eats the proof fails this step. Fix what fails, and run the generated cleanup after every failed iteration too, so broken attempts don't strand processes and ports. A generated skill that was never executed is a draft, not a deliverable.
|
|
41
|
+
|
|
42
|
+
## 5. Offer the maintenance loop
|
|
43
|
+
|
|
44
|
+
Point the user at `/maintain-verification-skill` for keeping the map honest as the app changes. Suggest a cadence only if they ask.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-verification-skill",
|
|
3
|
+
"type": "skill",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Generate a project-local verification skill that drives the app like a user and proves behavior with evidence.",
|
|
6
|
+
"author": "Jason Paff",
|
|
7
|
+
"entrypoint": "SKILL.md",
|
|
8
|
+
"files": [
|
|
9
|
+
"LICENSE",
|
|
10
|
+
"references/feature-map-example/create-note.md",
|
|
11
|
+
"references/feature-map-example/README.md",
|
|
12
|
+
"references/feature-map-example/search.md"
|
|
13
|
+
],
|
|
14
|
+
"tags": [
|
|
15
|
+
"pstack",
|
|
16
|
+
"verification",
|
|
17
|
+
"testing",
|
|
18
|
+
"generator"
|
|
19
|
+
],
|
|
20
|
+
"tools": [
|
|
21
|
+
"claude-code"
|
|
22
|
+
],
|
|
23
|
+
"metadata": {
|
|
24
|
+
"upstream": "pstack by Lauren Tan (poteto), https://github.com/cursor/plugins, MIT",
|
|
25
|
+
"portedFrom": "cursor"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Notes verification map
|
|
2
|
+
|
|
3
|
+
This directory is the maintained source for verifying the user-facing behavior of Notes. Read the index before driving the app, then use the matching feature file as the recipe.
|
|
4
|
+
|
|
5
|
+
## Baseline preconditions
|
|
6
|
+
|
|
7
|
+
- Launch Notes at `http://127.0.0.1:4173` with a disposable data directory.
|
|
8
|
+
- Set `NOTES_DATA_DIR=<scratch-dir>/notes-verify-$RUN_ID` so concurrent runs do not share state.
|
|
9
|
+
- Seed notes titled `Quarterly plan` and `Grocery list`.
|
|
10
|
+
- Put `control-notes` and the `notes` CLI on `PATH`.
|
|
11
|
+
- Run `control-notes doctor` and require the expected URL, data directory, and build revision.
|
|
12
|
+
- Never drive an instance that was not started by this verification run.
|
|
13
|
+
|
|
14
|
+
## Driving conventions
|
|
15
|
+
|
|
16
|
+
- Start every recipe from the baseline state unless its preconditions say otherwise.
|
|
17
|
+
- Prefer ARIA roles and accessible names over CSS selectors or DOM position.
|
|
18
|
+
- Treat every command as literal. Keep quoted names and flags unchanged.
|
|
19
|
+
- Run browser actions through `control-notes browser`.
|
|
20
|
+
- Run terminal actions through `control-notes cli -- <command>`.
|
|
21
|
+
- Restore seeded data after a mutation. Do not remove proof artifacts during cleanup.
|
|
22
|
+
|
|
23
|
+
## Proof and skip reporting
|
|
24
|
+
|
|
25
|
+
- Capture the user action and the resulting state, not only the final screen.
|
|
26
|
+
- UI proof includes an ARIA snapshot and a screenshot with the app identity visible.
|
|
27
|
+
- CLI proof includes the command, stdout, stderr, and exit code.
|
|
28
|
+
- Mutation proof includes a read-only second view of the stored value.
|
|
29
|
+
- Record the feature ID and entry point used with every artifact.
|
|
30
|
+
- Report an unreachable path with the attempted command and the unmet precondition.
|
|
31
|
+
- Do not report a skipped entry point as verified through a different path.
|
|
32
|
+
|
|
33
|
+
## Feature entry contract
|
|
34
|
+
|
|
35
|
+
Each feature file starts with an H1 title and one paragraph describing the user-visible behavior. It then uses exactly four H2 sections in this order.
|
|
36
|
+
|
|
37
|
+
1. `Sub-features` lists short IDs with one line for each behavior.
|
|
38
|
+
2. `How to get to it (user POV)` lists every user entry point.
|
|
39
|
+
3. `Driving it with <harness>` starts with `Preconditions:` and uses labeled bullets that pair each user action with an exact command and observable result.
|
|
40
|
+
4. `Gotchas` lists traps that can waste or invalidate a verification run.
|
|
41
|
+
|
|
42
|
+
Keep implementation details out of the map. Name only user paths, stable handles, required state, commands, and observable proof.
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- [Create a note](./create-note.md) covers browser and CLI creation, cancellation, persistence, and cleanup.
|
|
47
|
+
- [Search notes](./search.md) covers toolbar, keyboard, and CLI search with matching, empty, and clear states.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Create a note
|
|
2
|
+
|
|
3
|
+
Create note lets a user save a titled note from the browser or CLI, cancel an unfinished draft, and confirm the saved note from a second user-facing view.
|
|
4
|
+
|
|
5
|
+
## Sub-features
|
|
6
|
+
|
|
7
|
+
- `create-open` opens a blank editor from each browser entry point.
|
|
8
|
+
- `create-save` persists a title and body.
|
|
9
|
+
- `create-cancel` discards an unfinished browser draft.
|
|
10
|
+
- `create-cli` creates the same note shape from the terminal.
|
|
11
|
+
|
|
12
|
+
## How to get to it (user POV)
|
|
13
|
+
|
|
14
|
+
- Choose the `New note` button in the browser toolbar.
|
|
15
|
+
- Press `n` in the browser while focus is outside an editable field.
|
|
16
|
+
- Run `notes create --title <title> --body <body>` in a terminal.
|
|
17
|
+
|
|
18
|
+
## Driving it with control-notes
|
|
19
|
+
|
|
20
|
+
Preconditions:
|
|
21
|
+
|
|
22
|
+
- Notes is healthy at `http://127.0.0.1:4173`.
|
|
23
|
+
- No note is titled `Release checklist`.
|
|
24
|
+
- `control-notes doctor` reports the expected URL and disposable data directory.
|
|
25
|
+
|
|
26
|
+
- **Open editor.** Choose `New note`. Run `control-notes browser click --role button --name "New note"`. A form named `Note editor` appears with focus in the `Title` textbox.
|
|
27
|
+
- **Enter content.** Type the title and body. Run `control-notes browser fill --role textbox --name "Title" --value "Release checklist"` and `control-notes browser fill --role textbox --name "Body" --value "Tag and publish"`. The `Save note` button becomes enabled.
|
|
28
|
+
- **Save note.** Choose `Save note`. Run `control-notes browser click --role button --name "Save note"`. A status named `Note saved` appears and the heading reads `Release checklist`.
|
|
29
|
+
- **Confirm persistence.** Return to the note list and reopen the note. Run `control-notes browser click --role link --name "All notes"` and `control-notes browser click --role link --name "Release checklist"`. The editor shows both saved values.
|
|
30
|
+
- **Cancel draft.** Open a new note, enter `Discard me`, and choose `Cancel`. Run `control-notes browser click --role button --name "New note"`, `control-notes browser fill --role textbox --name "Title" --value "Discard me"`, and `control-notes browser click --role button --name "Cancel"`. The note list returns and has no `Discard me` link.
|
|
31
|
+
- **CLI entry.** Create a second note. Run `control-notes cli -- notes create --title "CLI note" --body "Created from terminal" --format json`. Exit code `0` and stdout contain the new note ID and title.
|
|
32
|
+
- **Proof.** Reopen both saved notes from `All notes`. Run `control-notes browser snapshot --aria --path artifacts/create-note/list.aria.txt` and `control-notes browser screenshot --path artifacts/create-note/list.png`. The artifacts show `Release checklist` and `CLI note`.
|
|
33
|
+
|
|
34
|
+
## Gotchas
|
|
35
|
+
|
|
36
|
+
- Pressing `n` while a textbox has focus types the character instead of opening a new editor.
|
|
37
|
+
- Titles are trimmed on save. Assert the rendered title, not the draft input value.
|
|
38
|
+
- A save status alone is insufficient proof. Reopen the note from the list.
|
|
39
|
+
- Remove `Release checklist` and `CLI note` during fixture cleanup, but retain their proof artifacts.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Search notes
|
|
2
|
+
|
|
3
|
+
Search lets a user find notes by title or body text, inspect a matching note, and distinguish no matches from an unavailable search.
|
|
4
|
+
|
|
5
|
+
## Sub-features
|
|
6
|
+
|
|
7
|
+
- `search-open` opens search from each supported browser entry point.
|
|
8
|
+
- `search-match` returns title and body matches without changing note data.
|
|
9
|
+
- `search-open-result` opens a result in the note editor.
|
|
10
|
+
- `search-empty` shows a complete empty state for a query with no matches.
|
|
11
|
+
- `search-clear` removes the query and restores the recent-notes view.
|
|
12
|
+
- `search-cli` returns the same matching notes from the terminal.
|
|
13
|
+
|
|
14
|
+
## How to get to it (user POV)
|
|
15
|
+
|
|
16
|
+
- Choose the `Search` button in the browser toolbar.
|
|
17
|
+
- Press `/` in the browser while focus is outside an editable field.
|
|
18
|
+
- Run `notes search <query>` in a terminal.
|
|
19
|
+
|
|
20
|
+
## Driving it with control-notes
|
|
21
|
+
|
|
22
|
+
Preconditions:
|
|
23
|
+
|
|
24
|
+
- Notes is healthy at `http://127.0.0.1:4173`.
|
|
25
|
+
- The disposable data directory contains `Quarterly plan` with body text `Draft budget`.
|
|
26
|
+
- `control-notes doctor` reports the expected URL and data directory.
|
|
27
|
+
|
|
28
|
+
- **Toolbar entry.** Choose the `Search` button. Run `control-notes browser click --role button --name "Search"`. A dialog named `Search notes` appears with focus in its searchbox.
|
|
29
|
+
- **Keyboard entry.** Close the dialog, focus the page, and press `/`. Run `control-notes browser press --key "/"`. The same dialog appears and the page does not insert a slash.
|
|
30
|
+
- **Title match.** Type `quarterly`. Run `control-notes browser fill --role searchbox --name "Search notes" --value "quarterly"`. The `Search results` list contains `Quarterly plan` and does not contain `Grocery list`.
|
|
31
|
+
- **Body match.** Replace the query with `budget`. Run `control-notes browser fill --role searchbox --name "Search notes" --value "budget"`. The result `Quarterly plan` remains visible with a body-match excerpt.
|
|
32
|
+
- **Open result.** Choose `Quarterly plan`. Run `control-notes browser click --role link --name "Quarterly plan"`. The dialog closes and the editor heading reads `Quarterly plan`.
|
|
33
|
+
- **Empty state.** Reopen search and enter `volcano`. Run `control-notes browser fill --role searchbox --name "Search notes" --value "volcano"`. A status named `No matching notes` appears after search completes.
|
|
34
|
+
- **Clear query.** Choose `Clear search`. Run `control-notes browser click --role button --name "Clear search"`. The searchbox is empty and the `Recent notes` region replaces the result list.
|
|
35
|
+
- **CLI match.** Search from the terminal. Run `control-notes cli -- notes search "quarterly" --format json`. Exit code `0` and stdout contain one object whose title is `Quarterly plan`.
|
|
36
|
+
- **CLI miss.** Search for an absent value. Run `control-notes cli -- notes search "volcano" --format json`. Exit code `0` and stdout are `[]`.
|
|
37
|
+
- **Proof.** Capture the populated result state. Run `control-notes browser snapshot --aria --path artifacts/search/results.aria.txt` and `control-notes browser screenshot --path artifacts/search/results.png`. Both artifacts identify Notes, the query, and `Quarterly plan`.
|
|
38
|
+
|
|
39
|
+
## Gotchas
|
|
40
|
+
|
|
41
|
+
- Pressing `/` while the editor or searchbox has focus inserts text instead of opening search.
|
|
42
|
+
- Results update after a short debounce. Wait for the results list or empty status, not a fixed sleep.
|
|
43
|
+
- Archived notes are excluded unless the user enables `Include archived`.
|
|
44
|
+
- The CLI defaults to human-readable output. Use `--format json` for stable assertions.
|
|
45
|
+
- Opening a result changes browser state. Reopen search before proving another query.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lauren Tan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# maintain-verification-skill
|
|
2
|
+
|
|
3
|
+
Periodic pass that keeps a project verification skill and feature map accurate against the source and the live app.
|
|
4
|
+
|
|
5
|
+
Invoke with `/maintain-verification-skill`.
|
|
6
|
+
|
|
7
|
+
## Origin
|
|
8
|
+
|
|
9
|
+
Ported from [pstack](https://github.com/cursor/plugins/tree/main/pstack) by Lauren Tan (poteto), MIT licensed. The Cursor version routed work across Claude, GPT, and Grok through a Cursor rules file and used Cursor-only tooling. This port targets Claude Code: subagents use the Agent tool with Claude models (`opus`, `sonnet`, `fable`), paths use `.claude/`, and references to Cursor-only skills and plugins are removed or replaced.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maintain-verification-skill
|
|
3
|
+
description: "Periodic pass that keeps a project's verification skill and feature map honest: parallel source readers per feature, one live session driving every feature, at most one PR of proven corrections. Use for /maintain-verification-skill or \"audit the verify skill\"."
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Maintain a verification skill
|
|
8
|
+
|
|
9
|
+
A feature map rots the moment the app changes. This skill is the upkeep loop for a skill generated by `/create-verification-skill` (or any project-local verification skill with a feature map). The unit of rigor is the feature, not every sentence: cover every feature file from source and exercise every feature live, without terminalising every bullet.
|
|
10
|
+
|
|
11
|
+
## Outcomes
|
|
12
|
+
|
|
13
|
+
Pick one, and say which:
|
|
14
|
+
|
|
15
|
+
- **clean** — every feature got source and live coverage; nothing worth shipping. No branch, no PR.
|
|
16
|
+
- **changed** — one PR ships proven doc, harness, or map corrections.
|
|
17
|
+
- **blocked** — coverage could not finish or a proven fix could not ship safely. Say exactly what blocked it.
|
|
18
|
+
|
|
19
|
+
## Edit scope
|
|
20
|
+
|
|
21
|
+
Only edit the verification skill's own directory (its SKILL.md, features/, and any harness scripts it owns). Never edit product code during a run: a behavior the map describes that the app no longer does is either doc drift (fix the map) or a product regression (report it, don't paper over it in docs).
|
|
22
|
+
|
|
23
|
+
## Pass
|
|
24
|
+
|
|
25
|
+
0. **Locate the target.** Find the verification skill to maintain: the project-local skill whose body has launch/drive sections and a feature map (usually `.claude/skills/verify-*/`). Several candidates → ask which one (AskUserQuestion); none → stop and point at `/create-verification-skill` instead of inventing a target.
|
|
26
|
+
|
|
27
|
+
1. **Index hygiene.** Read the feature map README and glob its sibling files. Fix missing, extra, duplicate, or dead entries. Lightweight; no generated inventory.
|
|
28
|
+
|
|
29
|
+
2. **Source wave.** One read-only subagent per feature file, launched concurrently in one message (Agent tool, `subagent_type: "general-purpose"`, `model: "sonnet"`, `run_in_background: true`; the prompt says "Read-only. Do not edit, create, or delete files."). Each explains "how does this user-facing feature work?" from source, flags likely doc drift with citations, and returns one concise live-verification recipe. Children never drive the app and never edit files. Return shape: feature summary / source entry points / likely drift or none / one recipe.
|
|
30
|
+
|
|
31
|
+
3. **Reconcile.** Every feature file has a returned summary. Merge overlapping recipes into as few app states as practical. Spot-check cited drift; don't re-prove clean claims. Sweep recent churn for user-facing surfaces missing from the map — require a concrete source path before calling one missing.
|
|
32
|
+
|
|
33
|
+
4. **Live pass.** Required even when source looks clean. The coordinator owns all driving; follow the verification skill's own launch model — one long-lived instance driven serially for servers and UIs, or a fresh isolated session per drive for short-lived CLIs (the skill's Launch section decides, not this one). Exercise every feature at least once, and hold three invariants the whole pass, whatever the failure: (1) never drive an instance you haven't health-checked since it last did something surprising — doctor before first drive, doctor on each fresh session where sessions are the unit, doctor again after any failed drive, and where doctor can't see the failure (a wedged UI state on a healthy process), reset to a known state or relaunch rather than hoping; (2) evidence captured so far survives every cleanup, checked at its named location, not assumed; (3) nothing a drive started outlives that drive's usefulness — failed-iteration residue is cleaned whether the session is stuck, exited, or shared (for a shared instance, clean the residue, not the instance). A doctor failure caused by skill drift is drift: fix it under edit scope and retry once — restart whatever the fix invalidated, nothing more — before calling the pass `blocked`. A feature that can't be reached is `verified-unreachable` only with the concrete prerequisite (auth, entitlement, OS, external state) and the route attempted; if the map omits that prerequisite, that's drift. Any harness fix from triage gets re-driven live before it ships. Final teardown happens after the last drive of the run — including those re-proofs — so nothing outlives the run (evidence stays, per the skill).
|
|
34
|
+
|
|
35
|
+
5. **Triage.** Wrong or missing user-POV description → doc drift, fix it. Working behavior the harness can't drive → harness gap, fix it; a harness fix follows the same helpers rule as generation (scripts executable, invocation documented in the skill body). App behavior that's actually broken → product gap; record it for the user, keep it out of this PR.
|
|
36
|
+
|
|
37
|
+
6. **Ship or stop.** For changed: one PR of proven corrections, re-read every changed file first. For clean or blocked: no PR, report the outcome and the coverage honestly.
|
|
38
|
+
|
|
39
|
+
Keep concise run notes (features covered, unreachable prerequisites, confirmed drift, outcome) in a scratch location; don't commit them.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maintain-verification-skill",
|
|
3
|
+
"type": "skill",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Periodic pass that keeps a project verification skill and feature map accurate against the source and the live app.",
|
|
6
|
+
"author": "Jason Paff",
|
|
7
|
+
"entrypoint": "SKILL.md",
|
|
8
|
+
"files": [
|
|
9
|
+
"LICENSE"
|
|
10
|
+
],
|
|
11
|
+
"tags": [
|
|
12
|
+
"pstack",
|
|
13
|
+
"verification",
|
|
14
|
+
"maintenance"
|
|
15
|
+
],
|
|
16
|
+
"tools": [
|
|
17
|
+
"claude-code"
|
|
18
|
+
],
|
|
19
|
+
"metadata": {
|
|
20
|
+
"upstream": "pstack by Lauren Tan (poteto), https://github.com/cursor/plugins, MIT",
|
|
21
|
+
"portedFrom": "cursor"
|
|
22
|
+
}
|
|
23
|
+
}
|