@jabworks/condux 0.6.0 → 0.7.0

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/README.md CHANGED
@@ -3,10 +3,11 @@
3
3
  OpenCode plugin for the [condux](https://github.com/jabworks/agentic-toolkit)
4
4
  workflow toolkit. One plugin line installs everything condux needs:
5
5
 
6
- - **Skills** — bundles the 12 condux workflow skills (`workflow`, `discovery`,
6
+ - **Skills** — bundles the 14 condux workflow skills (`workflow`, `discovery`,
7
7
  `draft-plan`, `test-first-development`, `subagent-execution`,
8
- `subagent-deployment`, `finalize`, `code-review`, `preflight`,
9
- `root-cause-analysis`, `plan-review`, `technical-spec`) and registers them on
8
+ `subagent-deployment`, `finalize`, `live-verification`, `code-review`,
9
+ `preflight`, `root-cause-analysis`, `plan-review`, `technical-spec`,
10
+ `condux-doctor`) and registers them on
10
11
  `config.skills.paths` via the `config` hook — no separate `npx skills add`
11
12
  step. A skill you install yourself under the same name still wins; OpenCode
12
13
  dedupes by name.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jabworks/condux",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "OpenCode plugin for the condux workflow toolkit — bundles the condux skills and injects the specialist agents (coder, explorer, planner, researcher) plus an opt-in plan-review listener.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: condux-doctor
3
+ description: "Health check for the condux plugin on the host it is installed on. Probes the SessionStart routing hook on Claude Code and Codex by running it and checking the wire format each host expects, confirms plan-review's Codex Stop hook resolves, checks the OpenCode npm registration and its bundled agents and skills, verifies the four specialist agent definitions shipped, and compares the installed version against the local marketplace clone. Offline and read-only. Checking whether condux itself works on this machine: is condux working, the routing rule stopped appearing, /workflow is not being reached, did the SessionStart hook fire, check my condux install, the specialist agents are missing. Run it after installing or updating the plugin. Not for routing a dev task (that is workflow); not for diagnosing this repo's own build or dist drift (that is toolkit-debugging-playbook)."
4
+ argument-hint: "[--host claude|codex|opencode]"
5
+ ---
6
+
7
+ # /condux-doctor
8
+
9
+ Answers one question: **is condux actually working on this host?**
10
+
11
+ ```bash
12
+ node <skill-base>/doctor.mjs # every host, every probe
13
+ node <skill-base>/doctor.mjs --host codex # one host
14
+ node <skill-base>/doctor.mjs --quiet # only what is not fine
15
+ ```
16
+
17
+ Exit 0 when nothing is broken, 1 when something is, 2 when the doctor cannot
18
+ find condux's own hooks.
19
+
20
+ ## Reading the report
21
+
22
+ One row per probe: `host status detail`, fix on an indented continuation
23
+ line. `done` works · `broken` needs the fix · `absent` is not there and not a
24
+ failure · `skipped` deliberately needs nothing. Only `broken` affects the
25
+ exit code.
26
+
27
+ ## What it probes
28
+
29
+ - **The SessionStart routing hook, by running it.** Claude Code must get a
30
+ `hookSpecificOutput` envelope carrying non-empty context; Codex must get
31
+ the raw routing payload. Each host's manifest must use its own root
32
+ variable — `${CLAUDE_PLUGIN_ROOT}` or `${PLUGIN_ROOT}`, never the other.
33
+ - **plan-review's Codex Stop hook** — resolved, never executed. It blocks a
34
+ turn for up to four days waiting on a human by design.
35
+ - **OpenCode** — `@jabworks/condux` in the plugin array, and when a local
36
+ copy exists, that it ships its bundled `agents/` and `skills/`.
37
+ - **The four specialist agents** — `coder`, `explorer`, `planner`,
38
+ `researcher`. They live in a plugin-level directory reached by its own sync
39
+ step, which is exactly the mirror that drifted once before.
40
+ - **Version** — installed against the local marketplace clone, with that
41
+ clone's own last-fetch date printed beside it. Never fetches.
42
+
43
+ ## Why running the hook matters
44
+
45
+ `session-start.mjs` fails open on purpose: if its payload is missing it exits
46
+ 0 and prints nothing, because a hook that breaks the session is worse than a
47
+ hook that does nothing. Every static check passes in that state. Only
48
+ executing it reveals that condux's routing rule is silently absent — which is
49
+ the difference between `/workflow` being the entry point and being a lucky
50
+ guess from the catalog.
51
+
52
+ ## What it cannot prove
53
+
54
+ That the host *invoked* the hook. Nothing readable from a child process says
55
+ whether Claude Code or Codex loaded a registration this session. If every
56
+ probe is green and the routing rule still never appears, the fault is
57
+ host-side and this report is the evidence.
58
+
59
+ ## Boundaries
60
+
61
+ Routing an actual dev task is `workflow`. Problems with this *repo's* build —
62
+ dist drift, a manifest that will not parse, a skill that will not trigger —
63
+ belong to `toolkit-debugging-playbook`: that one needs the source tree, this
64
+ one needs only the install.
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env node
2
+ // condux's health check — "is condux actually working on this host?"
3
+ //
4
+ // Follows the toolkit health-check convention (toolkit-skill-standards):
5
+ // detect → probe → report → fix. Probes are static parse PLUS execution, and
6
+ // condux is the case that needs it — session-start.mjs fails open by design,
7
+ // so a hook whose payload has gone missing exits 0 and says nothing. Static
8
+ // checks call that healthy; running it does not.
9
+
10
+ import fs from 'node:fs';
11
+ import os from 'node:os';
12
+ import path from 'node:path';
13
+ import process from 'node:process';
14
+ import { spawnSync } from 'node:child_process';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ const SKILL_BASE = path.dirname(fileURLToPath(import.meta.url));
18
+ const PLUGIN_ROOT = path.resolve(SKILL_BASE, '..', '..', '..');
19
+ const HOME = os.homedir();
20
+ const EXEC_TIMEOUT = 5000;
21
+ const PACKAGE = '@jabworks/condux';
22
+
23
+ const USAGE = `usage: doctor [options]
24
+
25
+ --host <claude|codex|opencode> probe one host only
26
+ --quiet print only broken and absent rows
27
+ `;
28
+
29
+ function parseFlags(argv) {
30
+ const flags = {};
31
+
32
+ for (let i = 0; i < argv.length; i++) {
33
+ if (!argv[i].startsWith('--')) continue;
34
+ const key = argv[i].slice(2);
35
+ const next = argv[i + 1];
36
+
37
+ if (next !== undefined && !next.startsWith('--')) {
38
+ flags[key] = next;
39
+ i++;
40
+ } else {
41
+ flags[key] = true;
42
+ }
43
+ }
44
+
45
+ return flags;
46
+ }
47
+
48
+ function firstExisting(candidates) {
49
+ return candidates.find((candidate) => candidate && fs.existsSync(candidate)) ?? null;
50
+ }
51
+
52
+ // The hooks directory is plugin-level in an install and skill-level in the
53
+ // source tree (workflow owns the canonical copy), so search both.
54
+ function findHook(file) {
55
+ return firstExisting([
56
+ path.join(PLUGIN_ROOT, 'hooks', file),
57
+ path.join(SKILL_BASE, '..', 'workflow', 'hooks', file),
58
+ ]);
59
+ }
60
+
61
+ function detectHosts() {
62
+ const configHome = process.env.XDG_CONFIG_HOME || path.join(HOME, '.config');
63
+ const dirs = {
64
+ claude: path.join(HOME, '.claude'),
65
+ codex: path.join(HOME, '.codex'),
66
+ opencode: path.join(configHome, 'opencode'),
67
+ };
68
+
69
+ return Object.fromEntries(
70
+ Object.entries(dirs).map(([host, dir]) => [host, fs.existsSync(dir) ? dir : null]),
71
+ );
72
+ }
73
+
74
+ function readJson(file) {
75
+ try {
76
+ return { value: JSON.parse(fs.readFileSync(file, 'utf8')) };
77
+ } catch (err) {
78
+ return { error: String(err?.message ?? err) };
79
+ }
80
+ }
81
+
82
+ // Pulls the first hook command for an event out of either host's manifest —
83
+ // the two dialects differ only in the root variable they interpolate.
84
+ function hookCommand(manifest, event) {
85
+ const entries = manifest?.hooks?.[event] ?? [];
86
+
87
+ for (const entry of entries) {
88
+ for (const hook of entry.hooks ?? []) {
89
+ if (typeof hook.command === 'string') return hook.command;
90
+ }
91
+ }
92
+
93
+ return null;
94
+ }
95
+
96
+ function resolveCommandPath(command, rootVariable) {
97
+ const match = command.match(/"([^"]+)"/);
98
+ const raw = match ? match[1] : command.split(/\s+/).slice(1)[0] ?? '';
99
+
100
+ return raw.replace(rootVariable, PLUGIN_ROOT);
101
+ }
102
+
103
+ function runSessionStart(script, hostFlag) {
104
+ const res = spawnSync(process.execPath, [script, hostFlag], {
105
+ timeout: EXEC_TIMEOUT,
106
+ encoding: 'utf8',
107
+ });
108
+
109
+ if (res.error) {
110
+ const why = res.error.code === 'ETIMEDOUT' ? 'no answer within 5s' : String(res.error.message);
111
+
112
+ return { ok: false, why };
113
+ }
114
+
115
+ const stdout = (res.stdout ?? '').trim();
116
+ if (stdout === '') {
117
+ // The documented fail-open path: routing.md missing or empty. Harmless to
118
+ // the session and invisible without this probe.
119
+ return { ok: false, why: 'hook produced no output — it is failing open, so routing.md is missing or empty' };
120
+ }
121
+
122
+ if (hostFlag === '--codex') {
123
+ return stdout.includes('/condux:workflow')
124
+ ? { ok: true, why: 'SessionStart emits the routing payload' }
125
+ : { ok: false, why: 'hook output does not carry the routing rule' };
126
+ }
127
+
128
+ try {
129
+ const payload = JSON.parse(stdout);
130
+ const context = payload?.hookSpecificOutput?.additionalContext ?? '';
131
+
132
+ if (payload?.hookSpecificOutput?.hookEventName !== 'SessionStart') {
133
+ return { ok: false, why: 'hook output is JSON but not a SessionStart envelope' };
134
+ }
135
+
136
+ return context.trim() === ''
137
+ ? { ok: false, why: 'SessionStart envelope carries an empty context' }
138
+ : { ok: true, why: 'SessionStart emits a valid envelope with the routing payload' };
139
+ } catch {
140
+ return { ok: false, why: 'hook output is not the JSON envelope Claude Code expects' };
141
+ }
142
+ }
143
+
144
+ // Hooks are registered by the plugin manifest, so outside a plugin install
145
+ // there is nothing registered to probe — and the root variables the manifest
146
+ // interpolates have no meaning there either.
147
+ const IS_PLUGIN = fs.existsSync(path.join(PLUGIN_ROOT, '.claude-plugin', 'plugin.json'));
148
+
149
+ function probeHookManifest(host, { file, event, rootVariable, foreignVariable, hostFlag }) {
150
+ if (!IS_PLUGIN) {
151
+ return { host, status: 'absent', detail: 'not a plugin install — this tree registers no hooks' };
152
+ }
153
+
154
+ const manifest = findHook(file);
155
+ if (!manifest) {
156
+ return { host, status: 'absent', detail: `not a plugin install — no hooks/${file}` };
157
+ }
158
+
159
+ const { value, error } = readJson(manifest);
160
+ if (error) {
161
+ return { host, status: 'broken', detail: `${file} is unparseable: ${error}`, fix: 'reinstall the plugin' };
162
+ }
163
+
164
+ const command = hookCommand(value, event);
165
+ if (!command) return { host, status: 'broken', detail: `${file} declares no ${event} hook`, fix: 'reinstall the plugin' };
166
+
167
+ if (command.includes(foreignVariable)) {
168
+ return {
169
+ host,
170
+ status: 'broken',
171
+ detail: `${event} uses ${foreignVariable}, which this host never expands`,
172
+ fix: `the manifest must use ${rootVariable} — reinstall the plugin`,
173
+ };
174
+ }
175
+
176
+ const script = resolveCommandPath(command, rootVariable);
177
+ if (!fs.existsSync(script)) {
178
+ return { host, status: 'broken', detail: `${event} points at a missing script: ${script}`, fix: 'reinstall the plugin' };
179
+ }
180
+
181
+ const verdict = runSessionStart(script, hostFlag);
182
+
183
+ return verdict.ok
184
+ ? { host, status: 'done', detail: verdict.why }
185
+ : { host, status: 'broken', detail: verdict.why, fix: 'reinstall the plugin — its routing payload did not ship' };
186
+ }
187
+
188
+ function probeClaude(hosts) {
189
+ if (!hosts.claude) return { host: 'claude', status: 'absent', detail: 'no ~/.claude on this machine' };
190
+
191
+ return probeHookManifest('claude', {
192
+ file: 'hooks.json',
193
+ event: 'SessionStart',
194
+ rootVariable: '${CLAUDE_PLUGIN_ROOT}',
195
+ foreignVariable: '${PLUGIN_ROOT}',
196
+ hostFlag: '--claude',
197
+ });
198
+ }
199
+
200
+ function probeCodex(hosts) {
201
+ if (!hosts.codex) return { host: 'codex', status: 'absent', detail: 'no ~/.codex on this machine' };
202
+
203
+ const row = probeHookManifest('codex', {
204
+ file: 'codex-hooks.json',
205
+ event: 'SessionStart',
206
+ rootVariable: '${PLUGIN_ROOT}',
207
+ foreignVariable: '${CLAUDE_PLUGIN_ROOT}',
208
+ hostFlag: '--codex',
209
+ });
210
+
211
+ if (row.status !== 'done') return row;
212
+
213
+ // plan-review's Stop hook blocks a turn for up to four days waiting on a
214
+ // human, so it is resolved and never executed.
215
+ const manifest = findHook('codex-hooks.json');
216
+ const stop = hookCommand(readJson(manifest).value, 'Stop');
217
+ if (!stop) return { ...row, detail: `${row.detail}; no Stop hook for plan-review` };
218
+
219
+ const server = resolveCommandPath(stop, '${PLUGIN_ROOT}');
220
+
221
+ return fs.existsSync(server)
222
+ ? { ...row, detail: `${row.detail}; plan-review Stop hook resolves` }
223
+ : {
224
+ host: 'codex',
225
+ status: 'broken',
226
+ detail: `SessionStart is fine but the Stop hook points at a missing annotate-server.js: ${server}`,
227
+ fix: 'reinstall the plugin',
228
+ };
229
+ }
230
+
231
+ function findPackage(configDir) {
232
+ return firstExisting([
233
+ path.join(configDir, 'node_modules', ...PACKAGE.split('/')),
234
+ path.join(HOME, '.local', 'share', 'opencode', 'node_modules', ...PACKAGE.split('/')),
235
+ path.join(HOME, '.cache', 'opencode', 'node_modules', ...PACKAGE.split('/')),
236
+ ]);
237
+ }
238
+
239
+ function probeOpencode(hosts) {
240
+ if (!hosts.opencode) return { host: 'opencode', status: 'absent', detail: 'no opencode config dir on this machine' };
241
+
242
+ const config = path.join(hosts.opencode, 'opencode.json');
243
+ const fix = `add ${PACKAGE} to the plugin array in ${config}`;
244
+ if (!fs.existsSync(config)) return { host: 'opencode', status: 'absent', detail: 'no opencode.json — condux is not registered', fix };
245
+
246
+ const { value, error } = readJson(config);
247
+ if (error) return { host: 'opencode', status: 'broken', detail: `opencode.json is unparseable: ${error}`, fix };
248
+
249
+ const registered = (value?.plugin ?? []).some((entry) => String(entry).startsWith(PACKAGE));
250
+ if (!registered) return { host: 'opencode', status: 'absent', detail: `${PACKAGE} is not in the plugin array`, fix };
251
+
252
+ const pkg = findPackage(hosts.opencode);
253
+ if (!pkg) {
254
+ return {
255
+ host: 'opencode',
256
+ status: 'absent',
257
+ detail: 'registered, but no local copy to verify — OpenCode fetches plugins at startup',
258
+ };
259
+ }
260
+
261
+ for (const dir of ['agents', 'skills']) {
262
+ const bundled = path.join(pkg, dir);
263
+ if (!fs.existsSync(bundled) || fs.readdirSync(bundled).length === 0) {
264
+ return { host: 'opencode', status: 'broken', detail: `the installed package ships no ${dir}/`, fix: `reinstall ${PACKAGE}` };
265
+ }
266
+ }
267
+
268
+ const check = spawnSync(process.execPath, ['--check', path.join(pkg, 'index.js')], { timeout: EXEC_TIMEOUT, encoding: 'utf8' });
269
+ if (check.status !== 0) {
270
+ return { host: 'opencode', status: 'broken', detail: 'the installed package entry point does not parse', fix: `reinstall ${PACKAGE}` };
271
+ }
272
+
273
+ return { host: 'opencode', status: 'done', detail: 'registered, and the installed package ships its agents and skills' };
274
+ }
275
+
276
+ // The four specialist agents are plugin-level and reached by their own sync
277
+ // step, not by the skill copy — the mirror that drifted in 6ba6572.
278
+ function probeAgents() {
279
+ const dir = firstExisting([
280
+ path.join(PLUGIN_ROOT, 'agents'),
281
+ path.join(SKILL_BASE, '..', 'subagent-execution', 'agents'),
282
+ ]);
283
+
284
+ if (!dir) return { host: 'all', status: 'absent', detail: 'no agents/ directory — not a plugin install' };
285
+
286
+ const agents = fs.readdirSync(dir).filter((entry) => entry.endsWith('.md'));
287
+ const expected = ['coder', 'explorer', 'planner', 'researcher'];
288
+ const missing = expected.filter((name) => !agents.includes(`${name}.md`));
289
+
290
+ return missing.length === 0
291
+ ? { host: 'all', status: 'done', detail: `all four specialist agents present (${dir === path.join(PLUGIN_ROOT, 'agents') ? 'plugin-level' : 'source tree'})` }
292
+ : { host: 'all', status: 'broken', detail: `missing agent definitions: ${missing.join(', ')}`, fix: 'reinstall the plugin' };
293
+ }
294
+
295
+ function compareVersions(a, b) {
296
+ const pa = String(a).split('.').map(Number);
297
+ const pb = String(b).split('.').map(Number);
298
+
299
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
300
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
301
+ if (diff !== 0) return diff < 0 ? -1 : 1;
302
+ }
303
+
304
+ return 0;
305
+ }
306
+
307
+ // Offline by design: the clone the host already fetched is the only reference,
308
+ // and its age is printed so a stale answer reads as stale.
309
+ function marketplaceVersion(name) {
310
+ const root = path.join(HOME, '.claude', 'plugins', 'marketplaces');
311
+ if (!fs.existsSync(root)) return null;
312
+
313
+ for (const entry of fs.readdirSync(root)) {
314
+ const manifest = path.join(root, entry, '.claude-plugin', 'marketplace.json');
315
+ if (!fs.existsSync(manifest)) continue;
316
+
317
+ const { value } = readJson(manifest);
318
+ const plugin = (value?.plugins ?? []).find((candidate) => candidate.name === name);
319
+ if (!plugin) continue;
320
+
321
+ const clone = path.join(root, entry);
322
+ const log = spawnSync('git', ['-C', clone, 'log', '-1', '--format=%cs'], { timeout: EXEC_TIMEOUT, encoding: 'utf8' });
323
+ const fetched = log.status === 0 && log.stdout.trim()
324
+ ? log.stdout.trim()
325
+ : fs.statSync(manifest).mtime.toISOString().slice(0, 10);
326
+
327
+ const source = String(plugin.source ?? '');
328
+ const offered = source.startsWith('.')
329
+ ? readJson(path.join(clone, source, '.claude-plugin', 'plugin.json')).value?.version ?? null
330
+ : null;
331
+
332
+ return { version: offered, clone: entry, fetched, remote: !source.startsWith('.') };
333
+ }
334
+
335
+ return null;
336
+ }
337
+
338
+ function probeVersion() {
339
+ const manifest = path.join(PLUGIN_ROOT, '.claude-plugin', 'plugin.json');
340
+ if (!fs.existsSync(manifest)) {
341
+ return { host: 'all', status: 'absent', detail: 'not a plugin install — nothing to compare a version against' };
342
+ }
343
+
344
+ const installed = readJson(manifest).value?.version ?? null;
345
+ const market = marketplaceVersion('condux');
346
+
347
+ if (!installed) return { host: 'all', status: 'broken', detail: 'plugin.json carries no version', fix: 'reinstall the plugin' };
348
+ if (!market) return { host: 'all', status: 'absent', detail: `installed ${installed}; no local marketplace clone to compare against` };
349
+ if (!market.version) {
350
+ const why = market.remote ? 'its source is remote and this check never fetches' : 'its manifest carries no version';
351
+
352
+ return { host: 'all', status: 'absent', detail: `installed ${installed}; marketplace clone ${market.clone} offers no comparable version — ${why}` };
353
+ }
354
+
355
+ const order = compareVersions(installed, market.version);
356
+ const stamp = `marketplace clone ${market.clone} as of ${market.fetched}`;
357
+
358
+ if (order < 0) {
359
+ return {
360
+ host: 'all',
361
+ status: 'broken',
362
+ detail: `installed ${installed}, marketplace offers ${market.version} (${stamp})`,
363
+ fix: '/plugin update condux@jabworks-agentic-toolkit',
364
+ };
365
+ }
366
+
367
+ const lead = order > 0 ? ` (ahead of marketplace ${market.version} — local build)` : '';
368
+
369
+ return { host: 'all', status: 'done', detail: `installed ${installed}${lead}; ${stamp}` };
370
+ }
371
+
372
+ const WIDTH_HOST = 10;
373
+ const WIDTH_STATUS = 8;
374
+
375
+ function report(rows, quiet) {
376
+ for (const row of rows) {
377
+ if (quiet && row.status === 'done') continue;
378
+ process.stdout.write(
379
+ row.host.padEnd(WIDTH_HOST) + ' ' + row.status.padEnd(WIDTH_STATUS) + ' ' + row.detail + '\n',
380
+ );
381
+ if (row.fix) {
382
+ process.stdout.write(' '.repeat(WIDTH_HOST + WIDTH_STATUS + 2) + '↳ ' + row.fix + '\n');
383
+ }
384
+ }
385
+ }
386
+
387
+ function summarize(rows) {
388
+ const broken = rows.filter((row) => row.status === 'broken').length;
389
+ const verdict = broken === 0 ? 'condux is healthy on every host present' : `${broken} probe(s) broken`;
390
+
391
+ return `\n${verdict} — probes are static-parse plus execution; they cannot prove the host invoked anything.\n`;
392
+ }
393
+
394
+ function main(argv) {
395
+ const flags = parseFlags(argv);
396
+ if (flags.help) {
397
+ process.stdout.write(USAGE);
398
+
399
+ return 0;
400
+ }
401
+
402
+ if (!findHook('session-start.mjs')) {
403
+ report([{ host: 'all', status: 'broken', detail: `condux's hooks are not reachable from ${SKILL_BASE}` }], false);
404
+
405
+ return 2;
406
+ }
407
+
408
+ const hosts = detectHosts();
409
+ const only = typeof flags.host === 'string' ? flags.host : null;
410
+ const perHost = [
411
+ ['claude', () => probeClaude(hosts)],
412
+ ['codex', () => probeCodex(hosts)],
413
+ ['opencode', () => probeOpencode(hosts)],
414
+ ];
415
+
416
+ const rows = perHost.filter(([host]) => !only || host === only).map(([, probe]) => probe());
417
+ if (!only) rows.push(probeAgents(), probeVersion());
418
+
419
+ report(rows, flags.quiet === true);
420
+ process.stdout.write(summarize(rows));
421
+
422
+ return rows.some((row) => row.status === 'broken') ? 1 : 0;
423
+ }
424
+
425
+ process.exit(main(process.argv.slice(2)));
@@ -0,0 +1,17 @@
1
+ [
2
+ { "query": "is condux actually working on this machine?", "should_trigger": true, "expected_skill": "condux-doctor" },
3
+ { "query": "check my condux install", "should_trigger": true, "expected_skill": "condux-doctor" },
4
+ { "query": "the routing rule stopped showing up at session start", "should_trigger": true, "expected_skill": "condux-doctor" },
5
+ { "query": "did the condux SessionStart hook actually fire?", "should_trigger": true, "expected_skill": "condux-doctor" },
6
+ { "query": "the specialist agents are missing after I updated the plugin", "should_trigger": true, "expected_skill": "condux-doctor" },
7
+ { "query": "run a health check on condux", "should_trigger": true, "expected_skill": "condux-doctor" },
8
+ { "query": "verify condux registered properly on codex", "should_trigger": true, "expected_skill": "condux-doctor" },
9
+ { "query": "add a search filter to the invoice table", "should_trigger": true, "expected_skill": "workflow" },
10
+ { "query": "how do condux tiers work?", "should_trigger": true, "expected_skill": "workflow" },
11
+ { "query": "why isn't my skill triggering", "should_trigger": true, "expected_skill": "toolkit-debugging-playbook" },
12
+ { "query": "dist drifted from skills after my edit", "should_trigger": true, "expected_skill": "toolkit-debugging-playbook" },
13
+ { "query": "what should I work on next?", "should_trigger": true, "expected_skill": "groom" },
14
+ { "query": "what is a git hook?", "should_trigger": false, "expected_skill": null },
15
+ { "query": "my laptop fan is loud when node runs", "should_trigger": false, "expected_skill": null },
16
+ { "query": "check the health of the kubernetes cluster", "should_trigger": false, "expected_skill": null }
17
+ ]
@@ -16,7 +16,7 @@
16
16
  { "query": "review the plan in the browser before implementing", "should_trigger": true, "expected_skill": "plan-review" },
17
17
  { "query": "execute the plan with agents", "should_trigger": true, "expected_skill": "subagent-execution" },
18
18
  { "query": "design isnt agreed yet, help me align first", "should_trigger": true, "expected_skill": "discovery" },
19
- { "query": "add a task to my todo list", "should_trigger": false, "expected_skill": null },
19
+ { "query": "add a task to my todo list", "should_trigger": false, "expected_skill": null, "accept": ["record"] },
20
20
  { "query": "plan my sprint for next week", "should_trigger": false, "expected_skill": null },
21
21
  { "query": "make a project timeline gantt chart", "should_trigger": false, "expected_skill": null }
22
22
  ]