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.
- package/agents/skills/README.md +308 -0
- package/agents/skills/canary:generate.md +49 -0
- package/agents/skills/canary:init.md +37 -0
- package/agents/skills/canary:migrate.md +66 -0
- package/agents/skills/claude-code/canary-add-framework/SKILL.md +248 -0
- package/agents/skills/claude-code/canary-blackhawk/SKILL.md +170 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/cli.mjs +188 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/rules.mjs +120 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/scanner.mjs +244 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/string-literals.mjs +116 -0
- package/agents/skills/claude-code/canary-cassandra/SKILL.md +180 -0
- package/agents/skills/claude-code/canary-cassandra/scripts/cli.mjs +268 -0
- package/agents/skills/claude-code/canary-cassandra/scripts/engine.mjs +95 -0
- package/agents/skills/claude-code/canary-ci-ready/SKILL.md +161 -0
- package/agents/skills/claude-code/canary-ci-ready/skill.yaml +14 -0
- package/agents/skills/claude-code/canary-company-knowledge/SKILL.md +196 -0
- package/agents/skills/claude-code/canary-critical-areas/SKILL.md +142 -0
- package/agents/skills/claude-code/canary-critical-areas/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-edge-case-discovery/SKILL.md +160 -0
- package/agents/skills/claude-code/canary-edge-case-discovery/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-fail-fast/SKILL.md +75 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/cli.mjs +118 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/digest.mjs +69 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/failures.mjs +60 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/fastfail_check.mjs +43 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/parse.mjs +149 -0
- package/agents/skills/claude-code/canary-failure-impact/SKILL.md +153 -0
- package/agents/skills/claude-code/canary-failure-impact/skill.yaml +15 -0
- package/agents/skills/claude-code/canary-fleet-health/SKILL.md +196 -0
- package/agents/skills/claude-code/canary-generate-test/SKILL.md +185 -0
- package/agents/skills/claude-code/canary-instrument/SKILL.md +157 -0
- package/agents/skills/claude-code/canary-instrument/scripts/cli.mjs +178 -0
- package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/instrument.mjs +96 -0
- package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/playwright-fixture.ts +44 -0
- package/agents/skills/claude-code/canary-instrument/scripts/run_types.mjs +81 -0
- package/agents/skills/claude-code/canary-instrument/scripts/span_reader.mjs +187 -0
- package/agents/skills/claude-code/canary-katana/SKILL.md +243 -0
- package/agents/skills/claude-code/canary-katana/scripts/alarm.mjs +296 -0
- package/agents/skills/claude-code/canary-katana/scripts/cli.mjs +247 -0
- package/agents/skills/claude-code/canary-katana/scripts/diffscan.mjs +0 -0
- package/agents/skills/claude-code/canary-katana/scripts/ledger.mjs +183 -0
- package/agents/skills/claude-code/canary-pr-guardian/SKILL.md +129 -0
- package/agents/skills/claude-code/canary-pr-guardian/skill.yaml +17 -0
- package/agents/skills/claude-code/canary-promote-test/SKILL.md +228 -0
- package/agents/skills/claude-code/canary-savant/SKILL.md +233 -0
- package/agents/skills/claude-code/canary-savant/scripts/cli.mjs +274 -0
- package/agents/skills/claude-code/canary-savant/scripts/restoration.mjs +274 -0
- package/agents/skills/claude-code/canary-savant/scripts/rules.mjs +168 -0
- package/agents/skills/claude-code/canary-savant/scripts/runner.mjs +572 -0
- package/agents/skills/claude-code/canary-savant/scripts/scanner.mjs +374 -0
- package/agents/skills/claude-code/canary-savant/scripts/string-literals.mjs +116 -0
- package/agents/skills/claude-code/canary-setup-harness/SKILL.md +263 -0
- package/agents/skills/claude-code/canary-shadow/SKILL.md +131 -0
- package/agents/skills/claude-code/canary-shadow/scripts/cases.example.json +32 -0
- package/agents/skills/claude-code/canary-shadow/scripts/cli.mjs +195 -0
- package/agents/skills/claude-code/canary-ship/SKILL.md +177 -0
- package/agents/skills/claude-code/canary-ship/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-strix/SKILL.md +130 -0
- package/agents/skills/claude-code/canary-strix/scripts/cli.mjs +255 -0
- package/agents/skills/claude-code/canary-strix/scripts/scanner.mjs +252 -0
- package/agents/skills/claude-code/canary-strix/scripts/terms.mjs +132 -0
- package/agents/skills/claude-code/canary-test-pipeline/SKILL.md +159 -0
- package/agents/skills/claude-code/canary-test-pipeline/skill.yaml +19 -0
- package/agents/skills/claude-code/canary-test-reporter/SKILL.md +138 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/cli.mjs +98 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/json_report.mjs +58 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/parse.mjs +216 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/render.mjs +114 -0
- package/agents/skills/lib/parse-args.mjs +214 -0
- package/dist/engine/cli-commands.js +3 -65
- package/dist/engine/core/quality-scorer.js +15 -2
- package/dist/engine/core/skill-dispatch.js +115 -0
- package/dist/engine/core/skill-examples.js +103 -3
- package/dist/engine/core/skill-registry.js +59 -4
- package/dist/engine/core/test-files.js +77 -0
- package/dist/engine/core/vacuity-scanner.js +184 -14
- package/dist/engine/guardian/analysis-emit.js +7 -2
- package/dist/engine/guardian/cli.js +121 -9
- package/dist/engine/guardian/pr-check.js +317 -18
- package/dist/engine/skills-cli.js +123 -19
- package/dist/reporters/testtracker.d.ts +1 -1
- package/dist/reporters/testtracker.js +1 -1
- 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
|
|
76
|
+
const registry = deps.makeSkillRegistry();
|
|
77
|
+
const skills = registry.discover();
|
|
48
78
|
if (skills.length === 0) {
|
|
49
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
/**
|
|
267
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|