canary-test-cli 7.2.0 → 8.0.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 +23 -4
- package/agents/skills/claude-code/canary-batwoman/SKILL.md +119 -0
- package/agents/skills/claude-code/canary-cassandra/SKILL.md +23 -16
- package/agents/skills/claude-code/canary-cassandra/scripts/cli.mjs +3 -1
- package/agents/skills/claude-code/canary-ci-ready/SKILL.md +20 -3
- package/agents/skills/claude-code/canary-fleet-health/SKILL.md +1 -0
- package/agents/skills/claude-code/canary-pr-guardian/SKILL.md +15 -0
- package/agents/skills/claude-code/canary-screech/SKILL.md +109 -0
- package/agents/skills/claude-code/canary-screech/scripts/blast.mjs +125 -0
- package/agents/skills/claude-code/canary-screech/scripts/cli.mjs +128 -0
- package/agents/skills/claude-code/canary-screech/scripts/cluster.mjs +97 -0
- package/agents/skills/claude-code/canary-screech/scripts/history.mjs +73 -0
- package/agents/skills/claude-code/canary-screech/scripts/redness.mjs +94 -0
- package/agents/skills/lib/parse-args.mjs +200 -139
- package/dist/engine/analysis/batwoman/audit.js +39 -0
- package/dist/engine/analysis/batwoman/closure.js +159 -0
- package/dist/engine/analysis/batwoman/gh-history.js +119 -0
- package/dist/engine/analysis/batwoman/probes.js +195 -0
- package/dist/engine/analysis/batwoman/registry.js +142 -0
- package/dist/engine/analysis/batwoman/render.js +194 -0
- package/dist/engine/analysis/batwoman/run-window.js +122 -0
- package/dist/engine/analysis/batwoman/text.js +84 -0
- package/dist/engine/analysis/batwoman/triggers.js +122 -0
- package/dist/engine/analysis/batwoman/verdict.js +64 -0
- package/dist/engine/analysis/cli.js +47 -14
- package/dist/engine/analysis/gh-flaky/gh-run-attempts.js +206 -0
- package/dist/engine/batwoman-cli.js +119 -0
- package/dist/engine/ci-ready-cli.js +71 -0
- package/dist/engine/cli-commands.js +46 -7
- package/dist/engine/cli.core.js +16 -0
- package/dist/engine/company-knowledge-cli.js +10 -2
- package/dist/engine/core/ci-ready.js +112 -0
- package/dist/engine/core/company-knowledge.js +8 -0
- package/dist/engine/core/migrator.js +147 -20
- package/dist/engine/core/permission-matrix.js +219 -0
- package/dist/engine/core/quality-scorer.js +13 -18
- package/dist/engine/core/scaling-curve.js +143 -0
- package/dist/engine/core/string-literals.js +3 -1
- package/dist/engine/core/vacuity-scanner.js +151 -6
- package/dist/engine/core/workflow-discovery.js +41 -23
- package/dist/engine/guardian/adjudication-github.js +136 -0
- package/dist/engine/guardian/adjudication.js +119 -340
- package/dist/engine/guardian/cli.js +180 -264
- package/dist/engine/guardian/coverage.js +2 -1
- package/dist/engine/guardian/diff-coverage/coverage-delta.js +162 -0
- package/dist/engine/guardian/diff-coverage/formats/cobertura.js +45 -1
- package/dist/engine/guardian/diff-coverage/orchestrator.js +25 -21
- package/dist/engine/guardian/diff-coverage/paths.js +5 -9
- package/dist/engine/guardian/diff-coverage/report-tier.js +88 -12
- package/dist/engine/guardian/diff-extractor.js +31 -32
- package/dist/engine/guardian/pr-check.js +262 -430
- package/dist/engine/guardian/pr-comment.js +35 -58
- package/dist/engine/guardian/weak-test.js +236 -0
- package/dist/engine/mcp-server.js +67 -4
- package/dist/engine/permission-matrix-cli.js +51 -0
- package/dist/engine/scaling-curve-cli.js +147 -0
- package/dist/engine/skills-cli.js +48 -32
- package/dist/engine/workflow-cli.js +85 -65
- package/package.json +1 -1
|
@@ -71,52 +71,68 @@ function reportEmptyDiscovery(registry, deps) {
|
|
|
71
71
|
'make them appear. Reinstall, or run from a Canary checkout.');
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Print one source tier: a separating blank line only when an earlier tier
|
|
76
|
+
* printed, then the header, then its skills. Split out of `listCmd` to pay
|
|
77
|
+
* down its complexity; every helper here stays at or under the perf-ratchet
|
|
78
|
+
* warning threshold (10) so the paydown adds no new finding identity.
|
|
79
|
+
*/
|
|
80
|
+
function printTier(deps, header, skills, verbose, printedAbove) {
|
|
81
|
+
if (printedAbove)
|
|
82
|
+
deps.out('');
|
|
83
|
+
deps.out(header);
|
|
84
|
+
for (const skill of skills)
|
|
85
|
+
deps.out(formatSkill(skill, verbose));
|
|
86
|
+
}
|
|
87
|
+
function byOverlayName(a, b) {
|
|
88
|
+
return overlayName(a).localeCompare(overlayName(b));
|
|
89
|
+
}
|
|
90
|
+
/** Overlay skills print one header per overlay, groups sorted by name. */
|
|
91
|
+
function printOverlayGroups(deps, overlay, verbose, printedAbove) {
|
|
92
|
+
let above = printedAbove;
|
|
93
|
+
let cur = null;
|
|
94
|
+
for (const skill of [...overlay].sort(byOverlayName)) {
|
|
95
|
+
const oname = overlayName(skill);
|
|
96
|
+
if (oname !== cur) {
|
|
97
|
+
if (above)
|
|
98
|
+
deps.out('');
|
|
99
|
+
deps.out(`${pc.bold('Overlay skills')} ${pc.dim(`(${oname} ${EM_DASH} override bundled):`)}`);
|
|
100
|
+
cur = oname;
|
|
101
|
+
above = true;
|
|
102
|
+
}
|
|
103
|
+
deps.out(formatSkill(skill, verbose));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
74
106
|
function listCmd(opts, deps) {
|
|
75
|
-
const verbose = opts.verbose
|
|
107
|
+
const verbose = opts.verbose === true;
|
|
76
108
|
const registry = deps.makeSkillRegistry();
|
|
77
109
|
const skills = registry.discover();
|
|
78
110
|
if (skills.length === 0) {
|
|
79
111
|
reportEmptyDiscovery(registry, deps);
|
|
80
112
|
return;
|
|
81
113
|
}
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
114
|
+
const bySource = (source) => skills.filter((s) => s.source === source);
|
|
115
|
+
const bundled = bySource('bundled');
|
|
116
|
+
const overlay = bySource('overlay');
|
|
117
|
+
const globalSkills = bySource('global');
|
|
118
|
+
const local = bySource('local');
|
|
119
|
+
let printed = false;
|
|
86
120
|
if (bundled.length) {
|
|
87
|
-
deps
|
|
88
|
-
|
|
89
|
-
deps.out(formatSkill(skill, verbose));
|
|
121
|
+
printTier(deps, pc.bold('Bundled skills:'), bundled, verbose, printed);
|
|
122
|
+
printed = true;
|
|
90
123
|
}
|
|
91
124
|
if (overlay.length) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let cur = null;
|
|
95
|
-
for (const skill of sorted) {
|
|
96
|
-
const oname = overlayName(skill);
|
|
97
|
-
if (oname !== cur) {
|
|
98
|
-
if (bundled.length || idx > 0)
|
|
99
|
-
deps.out('');
|
|
100
|
-
deps.out(`${pc.bold('Overlay skills')} ${pc.dim(`(${oname} ${EM_DASH} override bundled):`)}`);
|
|
101
|
-
cur = oname;
|
|
102
|
-
idx += 1;
|
|
103
|
-
}
|
|
104
|
-
deps.out(formatSkill(skill, verbose));
|
|
105
|
-
}
|
|
125
|
+
printOverlayGroups(deps, overlay, verbose, printed);
|
|
126
|
+
printed = true;
|
|
106
127
|
}
|
|
107
128
|
if (globalSkills.length) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
for (const skill of globalSkills)
|
|
112
|
-
deps.out(formatSkill(skill, verbose));
|
|
129
|
+
const header = `${pc.bold('Global skills')} ${pc.dim(`(~/.canary/skills/ ${EM_DASH} override overlay):`)}`;
|
|
130
|
+
printTier(deps, header, globalSkills, verbose, printed);
|
|
131
|
+
printed = true;
|
|
113
132
|
}
|
|
114
133
|
if (local.length) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
deps.out(`${pc.bold('Local overlay skills')} ${pc.dim('(override global):')}`);
|
|
118
|
-
for (const skill of local)
|
|
119
|
-
deps.out(formatSkill(skill, verbose));
|
|
134
|
+
const header = `${pc.bold('Local overlay skills')} ${pc.dim('(override global):')}`;
|
|
135
|
+
printTier(deps, header, local, verbose, printed);
|
|
120
136
|
}
|
|
121
137
|
}
|
|
122
138
|
/**
|
|
@@ -79,29 +79,94 @@ async function discoverCmd(opts, deps) {
|
|
|
79
79
|
throw new CliExitError(1);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Which project keys `show` should report on.
|
|
84
|
+
*
|
|
85
|
+
* `--project` names one; otherwise every cached `.canary/workflow-*.json` is
|
|
86
|
+
* listed. An unreadable `.canary` yields no keys rather than throwing: a
|
|
87
|
+
* missing cache is the ordinary first-run state, not an error.
|
|
88
|
+
*/
|
|
89
|
+
function resolveProjectKeys(opts, deps) {
|
|
90
|
+
if (opts.project)
|
|
91
|
+
return [opts.project];
|
|
84
92
|
let keys = [];
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
const canaryDir = join(deps.cwd(), '.canary');
|
|
94
|
+
if (existsSync(canaryDir)) {
|
|
95
|
+
try {
|
|
96
|
+
keys = readdirSync(canaryDir)
|
|
97
|
+
.filter((f) => f.startsWith('workflow-') && f.endsWith('.json'))
|
|
98
|
+
.map((f) => f.slice('workflow-'.length, -'.json'.length));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
keys = [];
|
|
102
|
+
}
|
|
87
103
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
if (keys.length === 0) {
|
|
105
|
+
deps.out(pc.yellow('No cached workflow mappings found.'));
|
|
106
|
+
throw new CliExitError(0);
|
|
107
|
+
}
|
|
108
|
+
return keys;
|
|
109
|
+
}
|
|
110
|
+
/** One mapping as JSON: the whole document, or just its semantic roles. */
|
|
111
|
+
function mappingAsJson(mapping, rolesOnly) {
|
|
112
|
+
if (!rolesOnly)
|
|
113
|
+
return mapping.toJson();
|
|
114
|
+
const rolesDict = {};
|
|
115
|
+
for (const [r, sr] of Object.entries(mapping.semantic_roles)) {
|
|
116
|
+
rolesDict[r] = { status_name: sr.status_name, issue_type: sr.issue_type };
|
|
117
|
+
}
|
|
118
|
+
return jsonIndent2(rolesDict);
|
|
119
|
+
}
|
|
120
|
+
/** `role -> status` lines, indented under a heading. Shared by both modes. */
|
|
121
|
+
function printSemanticRoles(roles, lead, deps) {
|
|
122
|
+
deps.out(`${lead}${pc.bold('Semantic roles:')}`);
|
|
123
|
+
for (const [role, sr] of Object.entries(roles)) {
|
|
124
|
+
deps.out(` ${role.padEnd(20)} ${'\u{2192}'} '${sr.status_name}' ${pc.dim(`(${sr.issue_type})`)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Each issue type, its statuses, and its transitions. */
|
|
128
|
+
function printIssueTypes(issueTypes, deps) {
|
|
129
|
+
for (const it of issueTypes) {
|
|
130
|
+
deps.out(`\n ${pc.bold(it.name)}`);
|
|
131
|
+
for (const s of it.statuses) {
|
|
132
|
+
deps.out(` [${s.category}] ${s.name}`);
|
|
99
133
|
}
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
134
|
+
if (it.transitions.length === 0)
|
|
135
|
+
continue;
|
|
136
|
+
deps.out(' Transitions:');
|
|
137
|
+
for (const t of it.transitions) {
|
|
138
|
+
deps.out(` ${t.from_status} ${'\u{2192}'} ${t.to_status} ${pc.dim(`(${t.name})`)}`);
|
|
103
139
|
}
|
|
104
140
|
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* One cached mapping, rendered for a human.
|
|
144
|
+
*
|
|
145
|
+
* Extracted from `showCmd`, which had grown to hold key resolution, JSON
|
|
146
|
+
* emission and this rendering at once -- and carried the semantic-roles block
|
|
147
|
+
* TWICE, once per mode, which is why the two had already drifted in their
|
|
148
|
+
* indentation.
|
|
149
|
+
*/
|
|
150
|
+
function printMapping(key, mapping, rolesOnly, deps) {
|
|
151
|
+
const confirmedTag = mapping.role_annotations_confirmed
|
|
152
|
+
? pc.green('confirmed')
|
|
153
|
+
: pc.yellow('unconfirmed');
|
|
154
|
+
deps.out(`\n${pc.bold(key)} ${pc.dim(`source=${mapping.source} discovered=${mapping.discovered_at} roles=${confirmedTag}`)}`);
|
|
155
|
+
const hasRoles = Object.keys(mapping.semantic_roles).length > 0;
|
|
156
|
+
if (rolesOnly) {
|
|
157
|
+
if (hasRoles)
|
|
158
|
+
printSemanticRoles(mapping.semantic_roles, ' ', deps);
|
|
159
|
+
else
|
|
160
|
+
deps.out(` ${pc.yellow('No semantic roles resolved yet.')}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
printIssueTypes(mapping.issue_types, deps);
|
|
164
|
+
if (hasRoles)
|
|
165
|
+
printSemanticRoles(mapping.semantic_roles, '\n ', deps);
|
|
166
|
+
}
|
|
167
|
+
function showCmd(opts, deps) {
|
|
168
|
+
const wd = deps.makeWorkflowDiscovery();
|
|
169
|
+
const keys = resolveProjectKeys(opts, deps);
|
|
105
170
|
let anyFound = false;
|
|
106
171
|
for (const key of keys) {
|
|
107
172
|
const mapping = wd.show(key);
|
|
@@ -111,55 +176,10 @@ function showCmd(opts, deps) {
|
|
|
111
176
|
}
|
|
112
177
|
anyFound = true;
|
|
113
178
|
if (opts.json) {
|
|
114
|
-
|
|
115
|
-
const rolesDict = {};
|
|
116
|
-
for (const [r, sr] of Object.entries(mapping.semantic_roles)) {
|
|
117
|
-
rolesDict[r] = {
|
|
118
|
-
status_name: sr.status_name,
|
|
119
|
-
issue_type: sr.issue_type,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
deps.out(jsonIndent2(rolesDict));
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
deps.out(mapping.toJson());
|
|
126
|
-
}
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
const confirmedTag = mapping.role_annotations_confirmed
|
|
130
|
-
? pc.green('confirmed')
|
|
131
|
-
: pc.yellow('unconfirmed');
|
|
132
|
-
deps.out(`\n${pc.bold(key)} ${pc.dim(`source=${mapping.source} discovered=${mapping.discovered_at} roles=${confirmedTag}`)}`);
|
|
133
|
-
if (opts.rolesOnly) {
|
|
134
|
-
if (Object.keys(mapping.semantic_roles).length) {
|
|
135
|
-
deps.out(` ${pc.bold('Semantic roles:')}`);
|
|
136
|
-
for (const [role, sr] of Object.entries(mapping.semantic_roles)) {
|
|
137
|
-
deps.out(` ${role.padEnd(20)} ${'\u{2192}'} '${sr.status_name}' ${pc.dim(`(${sr.issue_type})`)}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
deps.out(` ${pc.yellow('No semantic roles resolved yet.')}`);
|
|
142
|
-
}
|
|
179
|
+
deps.out(mappingAsJson(mapping, opts.rolesOnly === true));
|
|
143
180
|
continue;
|
|
144
181
|
}
|
|
145
|
-
|
|
146
|
-
deps.out(`\n ${pc.bold(it.name)}`);
|
|
147
|
-
for (const s of it.statuses) {
|
|
148
|
-
deps.out(` [${s.category}] ${s.name}`);
|
|
149
|
-
}
|
|
150
|
-
if (it.transitions.length) {
|
|
151
|
-
deps.out(' Transitions:');
|
|
152
|
-
for (const t of it.transitions) {
|
|
153
|
-
deps.out(` ${t.from_status} ${'\u{2192}'} ${t.to_status} ${pc.dim(`(${t.name})`)}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (Object.keys(mapping.semantic_roles).length) {
|
|
158
|
-
deps.out(`\n ${pc.bold('Semantic roles:')}`);
|
|
159
|
-
for (const [role, sr] of Object.entries(mapping.semantic_roles)) {
|
|
160
|
-
deps.out(` ${role.padEnd(20)} ${'\u{2192}'} '${sr.status_name}' ${pc.dim(`(${sr.issue_type})`)}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
182
|
+
printMapping(key, mapping, opts.rolesOnly === true, deps);
|
|
163
183
|
}
|
|
164
184
|
if (!anyFound) {
|
|
165
185
|
throw new CliExitError(1);
|