canary-test-cli 7.0.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/analysis/cli.js +116 -54
- package/dist/engine/analysis/engine.js +34 -16
- package/dist/engine/analysis/reports.js +5 -4
- package/dist/engine/cli-commands.js +252 -106
- package/dist/engine/cli-common.js +15 -24
- package/dist/engine/cli.core.js +37 -11
- package/dist/engine/cli.js +2 -2
- package/dist/engine/company-knowledge-cli.js +2 -2
- package/dist/engine/core/adoption.js +408 -0
- package/dist/engine/core/framework-probes.js +7 -7
- package/dist/engine/core/fs-glob.js +2 -2
- package/dist/engine/core/gate-result.js +17 -0
- package/dist/engine/core/migrator.js +9 -17
- package/dist/engine/core/pattern-matcher.js +23 -5
- package/dist/engine/core/persona.js +421 -0
- package/dist/engine/core/promotion-verdict.js +261 -0
- package/dist/engine/core/quality-scorer.js +15 -2
- package/dist/engine/core/reporter.js +1 -9
- package/dist/engine/core/skill-dispatch.js +115 -0
- package/dist/engine/core/skill-examples.js +392 -0
- package/dist/engine/core/skill-registry.js +59 -4
- package/dist/engine/core/skill-surfaces.js +307 -0
- package/dist/engine/core/static-linter.js +310 -38
- package/dist/engine/core/test-files.js +77 -0
- package/dist/engine/core/ticket-updater.js +1 -7
- package/dist/engine/core/vacuity-scanner.js +726 -0
- package/dist/engine/core/workflow-discovery.js +2 -8
- package/dist/engine/core/workspace-detect.js +7 -6
- package/dist/engine/data/personas/registry.json +36 -0
- package/dist/engine/guardian/adjudication.js +5 -5
- package/dist/engine/guardian/analysis-emit.js +19 -28
- package/dist/engine/guardian/cli.js +150 -51
- package/dist/engine/guardian/coverage.js +1 -1
- package/dist/engine/guardian/diff-coverage/heuristic-tier.js +1 -1
- package/dist/engine/guardian/diff-coverage/orchestrator.js +2 -2
- package/dist/engine/guardian/pr-check.js +322 -33
- package/dist/engine/guardian/pr-comment.js +4 -3
- package/dist/engine/history/cli.js +210 -6
- package/dist/engine/history/ndjson-store.js +9 -5
- package/dist/engine/history/record.js +34 -5
- package/dist/engine/history/run-recorder.js +165 -0
- package/dist/engine/history/schema.js +25 -7
- package/dist/engine/history/store.js +9 -0
- package/dist/engine/mcp-server.js +35 -13
- package/dist/engine/skills-cli.js +249 -23
- package/dist/engine/util/ensure-ascii.js +37 -0
- package/dist/engine/workflow-cli.js +6 -6
- package/dist/gate-result.d.ts +11 -0
- package/dist/gate-result.js +18 -0
- package/dist/reporters/testtracker.d.ts +1 -1
- package/dist/reporters/testtracker.js +1 -1
- package/dist/uninstall.js +12 -5
- package/package.json +3 -2
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
import { spawnSync } from 'node:child_process';
|
|
37
37
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
38
38
|
import { join } from 'node:path';
|
|
39
|
+
import { ensureAscii } from '../util/ensure-ascii.js';
|
|
39
40
|
// ---------------------------------------------------------------------------
|
|
40
41
|
// Python-compatibility helpers (copied locally per-module, matching reporter.ts)
|
|
41
42
|
// ---------------------------------------------------------------------------
|
|
@@ -58,13 +59,6 @@ function pyTruthy(value) {
|
|
|
58
59
|
function pyGet(obj, key, fallback) {
|
|
59
60
|
return Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : fallback;
|
|
60
61
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Reproduce Python's `json.dumps(..., ensure_ascii=True)` (the library default)
|
|
63
|
-
* on `JSON.stringify` output: escape every code point >= 0x80 as `\uXXXX`.
|
|
64
|
-
*/
|
|
65
|
-
function ensureAscii(json) {
|
|
66
|
-
return json.replace(/[\u0080-\uffff]/g, (ch) => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0'));
|
|
67
|
-
}
|
|
68
62
|
/** Python `str[:n]` by CODE POINT (never splits a surrogate pair). */
|
|
69
63
|
function codePointSlice(s, n) {
|
|
70
64
|
return [...s].slice(0, n).join('');
|
|
@@ -120,7 +114,7 @@ export const defaultSubprocess = (cmd, opts = {}) => {
|
|
|
120
114
|
// ---------------------------------------------------------------------------
|
|
121
115
|
// Schema
|
|
122
116
|
// ---------------------------------------------------------------------------
|
|
123
|
-
|
|
117
|
+
const SCHEMA_VERSION = 'https://github.com/bop-clocktower/canary/schemas/workflow-mapping/v1';
|
|
124
118
|
// Word-list used for automatic semantic-role heuristics.
|
|
125
119
|
const ROLE_TRIGGERS = {
|
|
126
120
|
qa_passed: ['qa pass', 'qa passed', 'qa done', 'tested', 'verified'],
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, readdirSync } from 'node:fs';
|
|
10
10
|
import { join, relative, sep } from 'node:path';
|
|
11
|
-
import {
|
|
11
|
+
import { CONFIG_PROBES, inferPlaywrightTestType, probeFramework, } from './framework-probes.js';
|
|
12
12
|
import { comparePathParts, globDirs, isFile, parseJsonOrNull, readTextOrNull, } from './fs-glob.js';
|
|
13
13
|
/**
|
|
14
14
|
* Workspace package globs declared at *root*, from pnpm-workspace.yaml or
|
|
@@ -146,13 +146,13 @@ function toPosixRel(root, dir) {
|
|
|
146
146
|
function configFindings(dir, rel) {
|
|
147
147
|
const out = [];
|
|
148
148
|
const seen = new Set();
|
|
149
|
-
for (const [filename, framework, shape, confidence] of
|
|
149
|
+
for (const [filename, framework, shape, confidence] of CONFIG_PROBES) {
|
|
150
150
|
if (!isFile(join(dir, filename)))
|
|
151
151
|
continue;
|
|
152
152
|
// Mirrors the config tier's own refinement: a playwright config with no
|
|
153
153
|
// UI-fixture spec is an API suite, and the shape decides which skills ship.
|
|
154
154
|
const refined = framework === 'playwright' && shape === 'e2e_ui'
|
|
155
|
-
?
|
|
155
|
+
? inferPlaywrightTestType(dir)
|
|
156
156
|
: shape;
|
|
157
157
|
// NUL separates the parts because it cannot occur in a path, a framework
|
|
158
158
|
// name, or a shape -- so `a\0b` can never collide with `a` + `\0b`. Written
|
|
@@ -177,14 +177,15 @@ function configFindings(dir, rel) {
|
|
|
177
177
|
* Findings for one package: every config-tier match, or -- only when the config
|
|
178
178
|
* tier found nothing at all -- a single content-tier answer.
|
|
179
179
|
*
|
|
180
|
-
* The language tier is deliberately withheld; see `
|
|
181
|
-
* root `language:` per package would invent findings (#504 part 1,
|
|
180
|
+
* The language tier is deliberately withheld; see `probeFramework` for why
|
|
181
|
+
* inheriting a root `language:` per package would invent findings (#504 part 1,
|
|
182
|
+
* spec test 8).
|
|
182
183
|
*/
|
|
183
184
|
function probePackage(dir, config, rel) {
|
|
184
185
|
const fromConfig = configFindings(dir, rel);
|
|
185
186
|
if (fromConfig.length > 0)
|
|
186
187
|
return fromConfig;
|
|
187
|
-
const [framework, shape, source, confidence] =
|
|
188
|
+
const [framework, shape, source, confidence] = probeFramework(dir, config, ['content']);
|
|
188
189
|
return framework === null
|
|
189
190
|
? []
|
|
190
191
|
: [{ dir: rel, framework, shape, source, confidence }];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"fallback": "junior",
|
|
4
|
+
"minDetectionConfidence": 0.5,
|
|
5
|
+
"minDetectionSignals": 2,
|
|
6
|
+
"detectionMap": {
|
|
7
|
+
"sdet": "sdet",
|
|
8
|
+
"manual": "manual"
|
|
9
|
+
},
|
|
10
|
+
"personas": [
|
|
11
|
+
{
|
|
12
|
+
"id": "sdet",
|
|
13
|
+
"label": "Senior SDET",
|
|
14
|
+
"audience": "Writes and owns automated tests daily; fluent in the framework and the codebase.",
|
|
15
|
+
"depth": "terse",
|
|
16
|
+
"formats": ["bullets", "code"],
|
|
17
|
+
"reasoning": false
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "junior",
|
|
21
|
+
"label": "Junior SDET",
|
|
22
|
+
"audience": "Writes automated tests but is still building judgement about which ones matter.",
|
|
23
|
+
"depth": "brief",
|
|
24
|
+
"formats": ["bullets", "code", "rationale"],
|
|
25
|
+
"reasoning": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "manual",
|
|
29
|
+
"label": "Manual tester",
|
|
30
|
+
"audience": "Tests by hand and reads test output; may not read or write the framework's code.",
|
|
31
|
+
"depth": "guided",
|
|
32
|
+
"formats": ["numbered-steps", "rationale"],
|
|
33
|
+
"reasoning": true
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -162,9 +162,9 @@ export function tallyAdjudications(reactions) {
|
|
|
162
162
|
const FINDING_ROW_RE = /^\|[^|]*\|\s*\[?`([^`]+)`/;
|
|
163
163
|
/**
|
|
164
164
|
* Extract the file paths of the ACTIVE findings shown in a sticky-comment body
|
|
165
|
-
* (PURE). Reads the rendered table `
|
|
166
|
-
* deliberately parsing the exact body reviewers reacted to, not the
|
|
167
|
-
* finding set, so a reaction is attributed to what the reviewer
|
|
165
|
+
* (PURE). Reads the rendered table `renderFindings(fmt='comment')` emitted —
|
|
166
|
+
* this is deliberately parsing the exact body reviewers reacted to, not the
|
|
167
|
+
* current finding set, so a reaction is attributed to what the reviewer saw.
|
|
168
168
|
* Returns `[]` for a no-gaps body (no table).
|
|
169
169
|
*/
|
|
170
170
|
export function activeFindingPaths(commentBody) {
|
|
@@ -204,7 +204,7 @@ export function adjudicationFilename(prNumber) {
|
|
|
204
204
|
return `${ADJUDICATION_SOURCE}-pr-${prNumber}.json`;
|
|
205
205
|
}
|
|
206
206
|
/** True iff the harness home (`dirname(analysesDir)`) exists. */
|
|
207
|
-
function
|
|
207
|
+
function isChannelAvailable(analysesDir) {
|
|
208
208
|
try {
|
|
209
209
|
return statSync(dirname(analysesDir)).isDirectory();
|
|
210
210
|
}
|
|
@@ -241,7 +241,7 @@ export async function collectAdjudications(client, args) {
|
|
|
241
241
|
tally,
|
|
242
242
|
collectedAt: args.collectedAt,
|
|
243
243
|
});
|
|
244
|
-
if (!
|
|
244
|
+
if (!isChannelAvailable(args.analysesDir)) {
|
|
245
245
|
return {
|
|
246
246
|
action: 'unavailable',
|
|
247
247
|
path: null,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* canary's records -- they never clobber a harness record and always pass
|
|
15
15
|
* `AnalysisArchive.safePath` (no traversal).
|
|
16
16
|
* - {@link buildAnalysisRecord} -- the v1.0 envelope wrapping the verbatim
|
|
17
|
-
* `
|
|
17
|
+
* `renderFindings(fmt="json")` findings array.
|
|
18
18
|
*
|
|
19
19
|
* SC-11 boundary: this module is deterministic filesystem/JSON only. It imports
|
|
20
20
|
* no `AgentTier`/LLM-SDK module (only intra-guardian, agent-free helpers).
|
|
@@ -34,36 +34,25 @@
|
|
|
34
34
|
import { createHash, randomBytes } from 'node:crypto';
|
|
35
35
|
import { mkdirSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
36
36
|
import { dirname, join } from 'node:path';
|
|
37
|
+
import { ensureAscii } from '../util/ensure-ascii.js';
|
|
37
38
|
import { coverageDegradedNotice, coverageStatus, } from './coverage.js';
|
|
38
|
-
import { combineNotices,
|
|
39
|
+
import { combineNotices, renderFindings, } from './pr-check.js';
|
|
39
40
|
// 1.1 adds the additive `coverage` block (#554); readers of 1.0 are unaffected.
|
|
40
41
|
// 1.2 adds the additive `skipped` list (#582). Additive again, and bumped again
|
|
41
42
|
// for the reason recorded in #572: a reader that pins a version must be able to
|
|
42
43
|
// tell which fields it can rely on being present, and silence about a new field
|
|
43
44
|
// is indistinguishable from the field being absent for a real reason.
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
// 1.3 adds the additive `provenance` block (#761). Same additive rule. This is
|
|
46
|
+
// the field an archived record needs most: when a run is questioned WEEKS later
|
|
47
|
+
// from its uploaded artifact, `checked: 43` is only interpretable next to the
|
|
48
|
+
// diff endpoints that produced it.
|
|
49
|
+
export const SCHEMA_VERSION = '1.3';
|
|
50
|
+
const ANALYSIS_SOURCE = 'canary-pr-guardian';
|
|
46
51
|
const REF_SAFE = /[^A-Za-z0-9._-]/g;
|
|
47
52
|
const REF_MAX = 100; // cap the sanitized ref so a long branch never hits ENAMETOOLONG
|
|
48
53
|
// The loud-fallback notices carry an em-dash (U+2014) as output data; written as
|
|
49
54
|
// an escape to honor the ASCII-source rule.
|
|
50
55
|
const EM_DASH = '\u{2014}';
|
|
51
|
-
/**
|
|
52
|
-
* Escape every non-ASCII (>= U+0080) code unit to a `\uXXXX` sequence, matching
|
|
53
|
-
* Python's `json.dumps(..., ensure_ascii=True)` (the library default).
|
|
54
|
-
*/
|
|
55
|
-
function ensureAscii(json) {
|
|
56
|
-
// Escape every UTF-16 code UNIT >= 0x80 to \uXXXX, matching Python
|
|
57
|
-
// json.dumps(ensure_ascii=True). Iterating by unit (not code point) means an
|
|
58
|
-
// astral char's surrogate pair emits \udXXX\udXXX, like Python; a code-point
|
|
59
|
-
// regex would stop at U+FFFF and leave astral chars raw.
|
|
60
|
-
let out = '';
|
|
61
|
-
for (let i = 0; i < json.length; i++) {
|
|
62
|
-
const c = json.charCodeAt(i);
|
|
63
|
-
out += c >= 0x80 ? '\\u' + c.toString(16).padStart(4, '0') : json[i];
|
|
64
|
-
}
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
56
|
/** Trim leading/trailing `-` characters (Python `str.strip("-")`). */
|
|
68
57
|
function stripDashes(value) {
|
|
69
58
|
return value.replace(/^-+/, '').replace(/-+$/, '');
|
|
@@ -76,7 +65,7 @@ function stripDashes(value) {
|
|
|
76
65
|
* `REF_MAX` chars with a short hash suffix appended to preserve uniqueness --
|
|
77
66
|
* only when truncation actually happens. Short refs are unchanged.
|
|
78
67
|
*/
|
|
79
|
-
export function analysisFilename(ref, source =
|
|
68
|
+
export function analysisFilename(ref, source = ANALYSIS_SOURCE) {
|
|
80
69
|
let safe = stripDashes(ref.replace(REF_SAFE, '-')) || 'local';
|
|
81
70
|
if (safe.length > REF_MAX) {
|
|
82
71
|
// sha1 is a filename disambiguator (not a security digest); it matches the
|
|
@@ -96,7 +85,8 @@ function isoUtcNow() {
|
|
|
96
85
|
return new Date().toISOString().replace('Z', '+00:00');
|
|
97
86
|
}
|
|
98
87
|
/**
|
|
99
|
-
* Build the v1.0 envelope. `findings` is exactly
|
|
88
|
+
* Build the v1.0 envelope. `findings` is exactly the array from
|
|
89
|
+
* `renderFindings(fmt='json')`.
|
|
100
90
|
*/
|
|
101
91
|
export function buildAnalysisRecord(findings, args) {
|
|
102
92
|
const { ref, gate, effective_tier, degraded_notice, exit_code } = args;
|
|
@@ -104,7 +94,7 @@ export function buildAnalysisRecord(findings, args) {
|
|
|
104
94
|
// input's — so "no findings" can never be read as "coverage said so".
|
|
105
95
|
const coverage = args.coverage ?? null;
|
|
106
96
|
const notice = combineNotices(degraded_notice, coverage ? coverageDegradedNotice(coverage) : null);
|
|
107
|
-
const inner = JSON.parse(
|
|
97
|
+
const inner = JSON.parse(renderFindings(findings, 'json', effective_tier, notice));
|
|
108
98
|
const active = findings.filter((f) => !f.suppressed);
|
|
109
99
|
const suppressed = findings.filter((f) => f.suppressed);
|
|
110
100
|
const byFidelity = {};
|
|
@@ -113,7 +103,7 @@ export function buildAnalysisRecord(findings, args) {
|
|
|
113
103
|
}
|
|
114
104
|
return {
|
|
115
105
|
schemaVersion: SCHEMA_VERSION,
|
|
116
|
-
source:
|
|
106
|
+
source: ANALYSIS_SOURCE,
|
|
117
107
|
ref,
|
|
118
108
|
gate,
|
|
119
109
|
exitCode: exit_code,
|
|
@@ -125,6 +115,7 @@ export function buildAnalysisRecord(findings, args) {
|
|
|
125
115
|
? null
|
|
126
116
|
: { status: coverageStatus(coverage), ...coverage },
|
|
127
117
|
skipped: args.skipped ?? [],
|
|
118
|
+
provenance: args.provenance ?? null,
|
|
128
119
|
summary: {
|
|
129
120
|
total: findings.length,
|
|
130
121
|
unaddressed: active.length,
|
|
@@ -151,7 +142,7 @@ export const emitSeams = {
|
|
|
151
142
|
* The analyses dir itself is created on demand by {@link emitAnalysis} (mirroring
|
|
152
143
|
* harness `AnalysisArchive.save`'s recursive `mkdir`).
|
|
153
144
|
*/
|
|
154
|
-
export function
|
|
145
|
+
export function isChannelAvailable(analysesDir) {
|
|
155
146
|
try {
|
|
156
147
|
return statSync(dirname(analysesDir)).isDirectory();
|
|
157
148
|
}
|
|
@@ -168,7 +159,7 @@ export function channelAvailable(analysesDir) {
|
|
|
168
159
|
*/
|
|
169
160
|
export function emitAnalysis(findings, args) {
|
|
170
161
|
const { analysesDir } = args;
|
|
171
|
-
if (!
|
|
162
|
+
if (!isChannelAvailable(analysesDir)) {
|
|
172
163
|
return {
|
|
173
164
|
action: 'unavailable',
|
|
174
165
|
path: null,
|
|
@@ -178,8 +169,8 @@ export function emitAnalysis(findings, args) {
|
|
|
178
169
|
}
|
|
179
170
|
const target = join(analysesDir, analysisFilename(args.ref));
|
|
180
171
|
try {
|
|
181
|
-
// Build INSIDE the try: a non-I/O error (from JSON/
|
|
182
|
-
// crash pr-check instead of degrading.
|
|
172
|
+
// Build INSIDE the try: a non-I/O error (from JSON/renderFindings) would
|
|
173
|
+
// otherwise crash pr-check instead of degrading.
|
|
183
174
|
const record = emitSeams.buildAnalysisRecord(findings, args);
|
|
184
175
|
mkdirSync(analysesDir, { recursive: true });
|
|
185
176
|
// Atomic write: stage into a same-dir temp then rename (atomic on one
|