canary-test-cli 7.1.0 → 7.2.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.
Files changed (83) hide show
  1. package/agents/skills/README.md +308 -0
  2. package/agents/skills/canary:generate.md +49 -0
  3. package/agents/skills/canary:init.md +37 -0
  4. package/agents/skills/canary:migrate.md +66 -0
  5. package/agents/skills/claude-code/canary-add-framework/SKILL.md +248 -0
  6. package/agents/skills/claude-code/canary-blackhawk/SKILL.md +170 -0
  7. package/agents/skills/claude-code/canary-blackhawk/scripts/cli.mjs +188 -0
  8. package/agents/skills/claude-code/canary-blackhawk/scripts/rules.mjs +120 -0
  9. package/agents/skills/claude-code/canary-blackhawk/scripts/scanner.mjs +244 -0
  10. package/agents/skills/claude-code/canary-blackhawk/scripts/string-literals.mjs +116 -0
  11. package/agents/skills/claude-code/canary-cassandra/SKILL.md +180 -0
  12. package/agents/skills/claude-code/canary-cassandra/scripts/cli.mjs +268 -0
  13. package/agents/skills/claude-code/canary-cassandra/scripts/engine.mjs +95 -0
  14. package/agents/skills/claude-code/canary-ci-ready/SKILL.md +161 -0
  15. package/agents/skills/claude-code/canary-ci-ready/skill.yaml +14 -0
  16. package/agents/skills/claude-code/canary-company-knowledge/SKILL.md +196 -0
  17. package/agents/skills/claude-code/canary-critical-areas/SKILL.md +142 -0
  18. package/agents/skills/claude-code/canary-critical-areas/skill.yaml +16 -0
  19. package/agents/skills/claude-code/canary-edge-case-discovery/SKILL.md +160 -0
  20. package/agents/skills/claude-code/canary-edge-case-discovery/skill.yaml +16 -0
  21. package/agents/skills/claude-code/canary-fail-fast/SKILL.md +75 -0
  22. package/agents/skills/claude-code/canary-fail-fast/scripts/cli.mjs +118 -0
  23. package/agents/skills/claude-code/canary-fail-fast/scripts/digest.mjs +69 -0
  24. package/agents/skills/claude-code/canary-fail-fast/scripts/failures.mjs +60 -0
  25. package/agents/skills/claude-code/canary-fail-fast/scripts/fastfail_check.mjs +43 -0
  26. package/agents/skills/claude-code/canary-fail-fast/scripts/parse.mjs +149 -0
  27. package/agents/skills/claude-code/canary-failure-impact/SKILL.md +153 -0
  28. package/agents/skills/claude-code/canary-failure-impact/skill.yaml +15 -0
  29. package/agents/skills/claude-code/canary-fleet-health/SKILL.md +196 -0
  30. package/agents/skills/claude-code/canary-generate-test/SKILL.md +185 -0
  31. package/agents/skills/claude-code/canary-instrument/SKILL.md +157 -0
  32. package/agents/skills/claude-code/canary-instrument/scripts/cli.mjs +178 -0
  33. package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/instrument.mjs +96 -0
  34. package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/playwright-fixture.ts +44 -0
  35. package/agents/skills/claude-code/canary-instrument/scripts/run_types.mjs +81 -0
  36. package/agents/skills/claude-code/canary-instrument/scripts/span_reader.mjs +187 -0
  37. package/agents/skills/claude-code/canary-katana/SKILL.md +243 -0
  38. package/agents/skills/claude-code/canary-katana/scripts/alarm.mjs +296 -0
  39. package/agents/skills/claude-code/canary-katana/scripts/cli.mjs +247 -0
  40. package/agents/skills/claude-code/canary-katana/scripts/diffscan.mjs +0 -0
  41. package/agents/skills/claude-code/canary-katana/scripts/ledger.mjs +183 -0
  42. package/agents/skills/claude-code/canary-pr-guardian/SKILL.md +129 -0
  43. package/agents/skills/claude-code/canary-pr-guardian/skill.yaml +17 -0
  44. package/agents/skills/claude-code/canary-promote-test/SKILL.md +228 -0
  45. package/agents/skills/claude-code/canary-savant/SKILL.md +233 -0
  46. package/agents/skills/claude-code/canary-savant/scripts/cli.mjs +274 -0
  47. package/agents/skills/claude-code/canary-savant/scripts/restoration.mjs +274 -0
  48. package/agents/skills/claude-code/canary-savant/scripts/rules.mjs +168 -0
  49. package/agents/skills/claude-code/canary-savant/scripts/runner.mjs +572 -0
  50. package/agents/skills/claude-code/canary-savant/scripts/scanner.mjs +374 -0
  51. package/agents/skills/claude-code/canary-savant/scripts/string-literals.mjs +116 -0
  52. package/agents/skills/claude-code/canary-setup-harness/SKILL.md +263 -0
  53. package/agents/skills/claude-code/canary-shadow/SKILL.md +131 -0
  54. package/agents/skills/claude-code/canary-shadow/scripts/cases.example.json +32 -0
  55. package/agents/skills/claude-code/canary-shadow/scripts/cli.mjs +195 -0
  56. package/agents/skills/claude-code/canary-ship/SKILL.md +177 -0
  57. package/agents/skills/claude-code/canary-ship/skill.yaml +16 -0
  58. package/agents/skills/claude-code/canary-strix/SKILL.md +130 -0
  59. package/agents/skills/claude-code/canary-strix/scripts/cli.mjs +255 -0
  60. package/agents/skills/claude-code/canary-strix/scripts/scanner.mjs +252 -0
  61. package/agents/skills/claude-code/canary-strix/scripts/terms.mjs +132 -0
  62. package/agents/skills/claude-code/canary-test-pipeline/SKILL.md +159 -0
  63. package/agents/skills/claude-code/canary-test-pipeline/skill.yaml +19 -0
  64. package/agents/skills/claude-code/canary-test-reporter/SKILL.md +138 -0
  65. package/agents/skills/claude-code/canary-test-reporter/scripts/cli.mjs +98 -0
  66. package/agents/skills/claude-code/canary-test-reporter/scripts/json_report.mjs +58 -0
  67. package/agents/skills/claude-code/canary-test-reporter/scripts/parse.mjs +216 -0
  68. package/agents/skills/claude-code/canary-test-reporter/scripts/render.mjs +114 -0
  69. package/agents/skills/lib/parse-args.mjs +214 -0
  70. package/dist/engine/cli-commands.js +3 -65
  71. package/dist/engine/core/quality-scorer.js +15 -2
  72. package/dist/engine/core/skill-dispatch.js +115 -0
  73. package/dist/engine/core/skill-examples.js +103 -3
  74. package/dist/engine/core/skill-registry.js +59 -4
  75. package/dist/engine/core/test-files.js +77 -0
  76. package/dist/engine/core/vacuity-scanner.js +184 -14
  77. package/dist/engine/guardian/analysis-emit.js +7 -2
  78. package/dist/engine/guardian/cli.js +121 -9
  79. package/dist/engine/guardian/pr-check.js +317 -18
  80. package/dist/engine/skills-cli.js +123 -19
  81. package/dist/reporters/testtracker.d.ts +1 -1
  82. package/dist/reporters/testtracker.js +1 -1
  83. package/package.json +3 -2
@@ -16,9 +16,10 @@ import { Command } from 'commander';
16
16
  import pc from 'picocolors';
17
17
  import { CliExitError, jsonIndent2, normalizeUsageExit } from './cli-common.js';
18
18
  import { gateOutcome } from './core/gate-result.js';
19
- import { checkExamples, spawnRunner, } from './core/skill-examples.js';
19
+ import { checkExamples, countDeclaredIllustrative, spawnRunner, } from './core/skill-examples.js';
20
20
  import { SurfaceFindingKind, checkSurfaces, collectSurfaces, } from './core/skill-surfaces.js';
21
21
  import { isExecutableSkillAllowed, resolveCliPath, } from './core/skill-registry.js';
22
+ import { SkillDispatchError, dispatchProseSkill, } from './core/skill-dispatch.js';
22
23
  import { CROSS, EM_DASH } from './main-deps.js';
23
24
  function overlayName(skill) {
24
25
  // Clone layout: ~/.canary/overlays/<overlay>/.canary/skills/<name>/SKILL.md
@@ -42,11 +43,40 @@ function formatSkill(skill, verbose) {
42
43
  line += `\n ${pc.dim(skill.path)}`;
43
44
  return line;
44
45
  }
46
+ /**
47
+ * An empty discovery, reported as the abstention it is (#757).
48
+ *
49
+ * `No skills found.` was a claim about the world made by a check that had
50
+ * verified nothing. All discovery can attest is that four specific roots held
51
+ * nothing -- so it says which four, and whether each one even existed. The
52
+ * distinction is what separates "this project has no skills" from "the tree I
53
+ * was pointed at is not there", and an installed CLI that shipped no bundled
54
+ * skills at all printed the first while meaning the second.
55
+ */
56
+ function reportEmptyDiscovery(registry, deps) {
57
+ const roots = registry.searchRoots();
58
+ const outcome = gateOutcome({ checked: 0, findings: [] }, 'advisory', {
59
+ noun: 'skill(s)',
60
+ });
61
+ deps.out(pc.bold(pc.yellow(outcome.summaryLine)));
62
+ deps.out('No skill was discoverable. Searched:');
63
+ for (const root of roots) {
64
+ const state = root.exists ? 'present, no skill inside' : 'does not exist';
65
+ deps.out(` ${root.tier.padEnd(8)} ${root.path} ${pc.dim(`(${state})`)}`);
66
+ }
67
+ const bundled = roots.find((r) => r.tier === 'bundled');
68
+ if (bundled && !bundled.exists) {
69
+ deps.out(`${pc.yellow(CROSS)} The bundled-skill root is missing from this ` +
70
+ `install ${EM_DASH} the CLI cannot see its own skills, so no cwd will ` +
71
+ 'make them appear. Reinstall, or run from a Canary checkout.');
72
+ }
73
+ }
45
74
  function listCmd(opts, deps) {
46
75
  const verbose = opts.verbose ?? false;
47
- const skills = deps.makeSkillRegistry().discover();
76
+ const registry = deps.makeSkillRegistry();
77
+ const skills = registry.discover();
48
78
  if (skills.length === 0) {
49
- deps.out(pc.yellow('No skills found.'));
79
+ reportEmptyDiscovery(registry, deps);
50
80
  return;
51
81
  }
52
82
  const bundled = skills.filter((s) => s.source === 'bundled');
@@ -89,6 +119,55 @@ function listCmd(opts, deps) {
89
119
  deps.out(formatSkill(skill, verbose));
90
120
  }
91
121
  }
122
+ /**
123
+ * Tier 2: render a dispatched prose skill (#756).
124
+ *
125
+ * The determinism label leads, in both modes. A consumer merging this with a
126
+ * `cli:` detector's findings has to be able to see that an agent produced it;
127
+ * a payload that only carried the workflow text would read exactly like a
128
+ * deterministic run's output.
129
+ */
130
+ function renderDispatch(dispatch, json, deps) {
131
+ if (json) {
132
+ deps.out(jsonIndent2(dispatch));
133
+ return;
134
+ }
135
+ deps.out(`${pc.bold(dispatch.skill)} ${EM_DASH} dispatched as a prose skill ` +
136
+ `${pc.dim('(no cli:/entry: target)')}`);
137
+ deps.out(`${pc.yellow('determinism:')} ${pc.bold('agent-applied')} ${EM_DASH} canary ` +
138
+ 'applied no judgment here. The workflow below must be executed by an ' +
139
+ 'agent runtime, and its results must not be merged with a ' +
140
+ "deterministic detector's findings without carrying this label.");
141
+ if (dispatch.requires.length) {
142
+ deps.out(`${pc.dim('requires:')} ${dispatch.requires.join(', ')}`);
143
+ }
144
+ if (dispatch.args.length) {
145
+ deps.out(`${pc.dim('args (forwarded, uninterpreted):')} ${dispatch.args.join(' ')}`);
146
+ }
147
+ deps.out(`${pc.dim(`source: ${dispatch.path}`)}\n`);
148
+ deps.out(dispatch.instructions);
149
+ }
150
+ /**
151
+ * Dispatch a skill with no `cli:`/`entry:` target instead of refusing it.
152
+ *
153
+ * Deliberately reached BEFORE the `--allow-executable-skills` gate: that flag
154
+ * guards spawning someone else's code, and dispatch spawns nothing. Gating it
155
+ * would leave the 14 CLI-less skills unreachable in precisely the CI contexts
156
+ * #756 is about. See `core/skill-dispatch.ts` for the full reasoning.
157
+ */
158
+ function runProseSkill(skill, args, opts, deps) {
159
+ try {
160
+ renderDispatch(dispatchProseSkill(skill, args), opts.json ?? false, deps);
161
+ }
162
+ catch (exc) {
163
+ if (!(exc instanceof SkillDispatchError))
164
+ throw exc;
165
+ // A dispatch that could not happen is reported as a failure, never as an
166
+ // empty run: exit 2 keeps the old "this skill is not runnable" contract.
167
+ deps.out(`${pc.red(CROSS)} Skill ${pc.bold(skill.name)}: ${exc.message}`);
168
+ throw new CliExitError(2);
169
+ }
170
+ }
92
171
  async function runCmd(name, args, opts, deps) {
93
172
  const skill = deps.makeSkillRegistry().find(name);
94
173
  if (skill === null) {
@@ -99,9 +178,10 @@ async function runCmd(name, args, opts, deps) {
99
178
  deps.out(`${pc.red(CROSS)} Skill ${pc.bold(name)}: ${skill.error}`);
100
179
  throw new CliExitError(2);
101
180
  }
181
+ // Tier 2 (#756): a skill with no cli:/entry: is dispatched, not refused.
102
182
  if (!skill.isExecutable) {
103
- deps.out(pc.yellow(`Skill ${pc.bold(name)} is markdown-only ${EM_DASH} no cli: or entry: field to run.`));
104
- throw new CliExitError(2);
183
+ runProseSkill(skill, args, opts, deps);
184
+ return;
105
185
  }
106
186
  if (!isExecutableSkillAllowed(opts.allowExecutableSkills ?? false)) {
107
187
  deps.out(`${pc.red(CROSS)} Refusing to invoke executable skill in non-interactive context. Pass ${pc.bold('--allow-executable-skills')} to opt in (e.g. in trusted CI configurations).`);
@@ -215,6 +295,11 @@ function verifyJson(root, surfaceResult, exampleResult) {
215
295
  abstained: !(exampleResult.checked > 0),
216
296
  findings: exampleResult.findings,
217
297
  skipped: exampleResult.skipped ?? [],
298
+ // The skipped bucket, split (#707). Emitted rather than left for the
299
+ // consumer to derive: a CI step counting declared-illustrative examples
300
+ // by matching the reason string would be a second copy of a literal
301
+ // owned here, and it would drift to 0 silently.
302
+ illustrative: countDeclaredIllustrative(exampleResult.skipped ?? []),
218
303
  },
219
304
  });
220
305
  }
@@ -239,6 +324,15 @@ function verifyReport(root, surfaceResult, exampleResult, deps) {
239
324
  noun: 'documented example(s)',
240
325
  });
241
326
  deps.out(`examples: ${exampleOutcome.summaryLine}`);
327
+ // #707: the denominator, split. "29 unverifiable" reads as one fact and is
328
+ // two — blocks nobody could run, and blocks the author declared as prose.
329
+ // Only the first is a gap, and printing the total alone buries it.
330
+ const skipped = exampleResult.skipped ?? [];
331
+ const declared = countDeclaredIllustrative(skipped);
332
+ deps.out(` denominator: executed=${exampleResult.checked} ` +
333
+ `illustrative=${declared} ` +
334
+ `unverifiable=${skipped.length - declared} ` +
335
+ `total=${exampleResult.checked + skipped.length}`);
242
336
  if (exampleOutcome.abstained) {
243
337
  deps.out(` zero documented commands were executed ${EM_DASH} nothing here is ` +
244
338
  'proven. Add a help-shaped example in a shell fence to a SKILL.md, or ' +
@@ -263,28 +357,22 @@ function partition(s, sep) {
263
357
  return [s, '', ''];
264
358
  return [s.slice(0, i), sep, s.slice(i + sep.length)];
265
359
  }
266
- /** Build the `skills` sub-app wired to `deps`. */
267
- export function buildSkillsCommand(deps) {
268
- const program = new Command('skills');
269
- program
270
- .description('List and invoke discoverable Canary skills.')
271
- .exitOverride(normalizeUsageExit);
272
- program
273
- .command('list')
274
- .description('List every skill discoverable from the current directory.')
275
- .option('-v, --verbose', 'Also print the SKILL.md path for each skill.')
276
- .action((opts) => {
277
- listCmd(opts, deps);
278
- });
360
+ /** Register `skills run` -- the tier-1 target path plus the tier-2 dispatcher. */
361
+ function registerRun(program, deps) {
279
362
  program
280
363
  .command('run')
281
- .description("Invoke a code-bearing skill's declared cli or entry target.")
364
+ .description("Invoke a skill: a code-bearing skill's cli/entry target, or a prose " +
365
+ "skill's workflow via the dispatcher.")
282
366
  .argument('<name>', 'Name of the skill to invoke.')
283
367
  .argument('[args...]', "Arguments forwarded to the skill's cli/entry.")
284
368
  .option('--allow-executable-skills', 'Opt-in to invoking cli:/entry: skills in non-interactive (CI) contexts.')
369
+ .option('--json', 'For a dispatched prose skill, emit the labelled payload as JSON.')
285
370
  .action(async (name, args, opts) => {
286
371
  await runCmd(name, args ?? [], opts, deps);
287
372
  });
373
+ }
374
+ /** Register `skills verify` -- the two-denominator cross-surface check. */
375
+ function registerVerify(program, deps) {
288
376
  program
289
377
  .command('verify')
290
378
  .description('Check that skill surfaces agree and that documented examples still run.')
@@ -295,6 +383,22 @@ export function buildSkillsCommand(deps) {
295
383
  .action((opts) => {
296
384
  verifyCmd(opts, deps);
297
385
  });
386
+ }
387
+ /** Build the `skills` sub-app wired to `deps`. */
388
+ export function buildSkillsCommand(deps) {
389
+ const program = new Command('skills');
390
+ program
391
+ .description('List and invoke discoverable Canary skills.')
392
+ .exitOverride(normalizeUsageExit);
393
+ program
394
+ .command('list')
395
+ .description('List every skill discoverable from the current directory.')
396
+ .option('-v, --verbose', 'Also print the SKILL.md path for each skill.')
397
+ .action((opts) => {
398
+ listCmd(opts, deps);
399
+ });
400
+ registerRun(program, deps);
401
+ registerVerify(program, deps);
298
402
  for (const sub of program.commands) {
299
403
  sub.exitOverride(normalizeUsageExit);
300
404
  }
@@ -82,7 +82,7 @@ export declare function runStatus(totals: {
82
82
  * `test.id`, which is genuinely unique, but a title is not: a Playwright
83
83
  * `dependencies:` setup project runs in full in EVERY shard (dependencies are
84
84
  * not sharded), so a `merge-reports` payload over a sharded matrix legitimately
85
- * carries the same setup title once per shard. Capwell's `capwell-web` suite
85
+ * carries the same setup title once per shard. One consumer’s sharded suite
86
86
  * had every nightly rejected this way and never ingested a single run.
87
87
  *
88
88
  * Collapse rule keeps the merged row honest: worst status wins, the first real
@@ -109,7 +109,7 @@ const STATUS_SEVERITY = {
109
109
  * `test.id`, which is genuinely unique, but a title is not: a Playwright
110
110
  * `dependencies:` setup project runs in full in EVERY shard (dependencies are
111
111
  * not sharded), so a `merge-reports` payload over a sharded matrix legitimately
112
- * carries the same setup title once per shard. Capwell's `capwell-web` suite
112
+ * carries the same setup title once per shard. One consumer’s sharded suite
113
113
  * had every nightly rejected this way and never ingested a single run.
114
114
  *
115
115
  * Collapse rule keeps the merged row honest: worst status wins, the first real
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canary-test-cli",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "description": "Canary — AI-powered test automation agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,8 @@
39
39
  "files": [
40
40
  "bin/canary.js",
41
41
  "bin/canary-mcp.js",
42
- "dist/"
42
+ "dist/",
43
+ "agents/"
43
44
  ],
44
45
  "dependencies": {
45
46
  "@modelcontextprotocol/sdk": "^1.30.0",