Skip to content

Commit ba09a2d

Browse files
authored
Enrich quality reports with baseline signal metrics (#40)
Capture stdout/stderr line counts in baseline artifacts and compare them in QualityReport highlights so QA output is more informative than exit codes alone; add a baseline-mode intake example for onboarding workflows.
1 parent 1290900 commit ba09a2d

10 files changed

Lines changed: 152 additions & 11 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ kubectl apply -k examples/deploy/kubernetes/base
4141
kubectl apply -k examples/deploy/kubernetes/overlays/redis
4242
```
4343

44-
Submit quality runs through intake API `POST /qa/runs` using payloads in `examples/runs/`.
44+
Submit quality runs through intake API `POST /qa/runs` using payloads in `examples/runs/` (comparison and baseline examples are included).
4545

4646
## Core Artifacts Per Run
4747

docs/server.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,14 @@ curl -sS -X POST http://127.0.0.1:8080/qa/runs \
230230
--data-binary @examples/runs/demo-node-pr-quality-intake.json
231231
```
232232

233+
Onboarding baseline capture example:
234+
235+
```bash
236+
curl -sS -X POST http://127.0.0.1:8080/qa/runs \
237+
-H "content-type: application/json" \
238+
--data-binary @examples/runs/demo-node-baseline-intake.json
239+
```
240+
233241
Stop services:
234242

235243
```bash
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"source": "developer",
3+
"repository": {
4+
"provider": "github",
5+
"owner": "speedscale",
6+
"name": "demo"
7+
},
8+
"appRef": {
9+
"name": "demo-node",
10+
"qualityTarget": "demo-node"
11+
},
12+
"request": {
13+
"mode": "baseline",
14+
"branch": "main"
15+
},
16+
"requestedBy": {
17+
"type": "user",
18+
"login": "developer"
19+
},
20+
"metadata": {
21+
"reason": "initial-onboarding-baseline"
22+
}
23+
}

schemas/quality-baseline.schema.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,34 @@ properties:
6363
required:
6464
- command
6565
- exitCode
66+
- stdoutLines
67+
- stderrLines
6668
properties:
6769
command:
6870
type: string
6971
exitCode:
7072
type: number
73+
stdoutLines:
74+
type: number
75+
stderrLines:
76+
type: number
7177
additionalProperties: false
7278
validation:
7379
type: object
7480
required:
7581
- command
7682
- exitCode
83+
- stdoutLines
84+
- stderrLines
7785
properties:
7886
command:
7987
type: string
8088
exitCode:
8189
type: number
90+
stdoutLines:
91+
type: number
92+
stderrLines:
93+
type: number
8294
additionalProperties: false
8395
additionalProperties: false
8496
additionalProperties: false

schemas/quality-report.schema.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ properties:
3535
- outcome
3636
- summary
3737
- comparedCommands
38+
- highlights
3839
properties:
3940
runRef:
4041
type: object
@@ -88,22 +89,46 @@ properties:
8889
type: object
8990
required:
9091
- currentExitCode
92+
- currentStdoutLines
93+
- currentStderrLines
9194
properties:
9295
baselineExitCode:
9396
type: number
9497
currentExitCode:
9598
type: number
99+
baselineStdoutLines:
100+
type: number
101+
currentStdoutLines:
102+
type: number
103+
baselineStderrLines:
104+
type: number
105+
currentStderrLines:
106+
type: number
96107
additionalProperties: false
97108
validation:
98109
type: object
99110
required:
100111
- currentExitCode
112+
- currentStdoutLines
113+
- currentStderrLines
101114
properties:
102115
baselineExitCode:
103116
type: number
104117
currentExitCode:
105118
type: number
119+
baselineStdoutLines:
120+
type: number
121+
currentStdoutLines:
122+
type: number
123+
baselineStderrLines:
124+
type: number
125+
currentStderrLines:
126+
type: number
106127
additionalProperties: false
107128
additionalProperties: false
129+
highlights:
130+
type: array
131+
items:
132+
type: string
108133
additionalProperties: false
109134
additionalProperties: false

src/bin/demo.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,15 @@ async function main(): Promise<void> {
9999
app: sampleApp,
100100
build: {
101101
command: buildResult.result.command,
102-
exitCode: buildResult.result.exitCode
102+
exitCode: buildResult.result.exitCode,
103+
stdout: buildResult.result.stdout,
104+
stderr: buildResult.result.stderr
103105
},
104106
validation: {
105107
command: validationResult.result.command,
106-
exitCode: validationResult.result.exitCode
108+
exitCode: validationResult.result.exitCode,
109+
stdout: validationResult.result.stdout,
110+
stderr: validationResult.result.stderr
107111
}
108112
});
109113

src/bin/worker.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ async function processRun(runName: string, sourceDir?: string): Promise<void> {
9393
app: runnerContext.app,
9494
build: {
9595
command: buildResult.result.command,
96-
exitCode: buildResult.result.exitCode
96+
exitCode: buildResult.result.exitCode,
97+
stdout: buildResult.result.stdout,
98+
stderr: buildResult.result.stderr
9799
}
98100
});
99101

@@ -126,11 +128,15 @@ async function processRun(runName: string, sourceDir?: string): Promise<void> {
126128
app: runnerContext.app,
127129
build: {
128130
command: buildResult.result.command,
129-
exitCode: buildResult.result.exitCode
131+
exitCode: buildResult.result.exitCode,
132+
stdout: buildResult.result.stdout,
133+
stderr: buildResult.result.stderr
130134
},
131135
validation: {
132136
command: validationResult.result.command,
133-
exitCode: validationResult.result.exitCode
137+
exitCode: validationResult.result.exitCode,
138+
stdout: validationResult.result.stdout,
139+
stderr: validationResult.result.stderr
134140
}
135141
});
136142

src/contracts/quality-baseline.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ export interface QualityBaseline {
1818
build: {
1919
command: string;
2020
exitCode: number;
21+
stdoutLines: number;
22+
stderrLines: number;
2123
};
2224
validation?: {
2325
command: string;
2426
exitCode: number;
27+
stdoutLines: number;
28+
stderrLines: number;
2529
};
2630
};
2731
};

src/contracts/quality-report.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,20 @@ export interface QualityReport {
2424
build: {
2525
baselineExitCode?: number;
2626
currentExitCode: number;
27+
baselineStdoutLines?: number;
28+
currentStdoutLines: number;
29+
baselineStderrLines?: number;
30+
currentStderrLines: number;
2731
};
2832
validation?: {
2933
baselineExitCode?: number;
3034
currentExitCode: number;
35+
baselineStdoutLines?: number;
36+
currentStdoutLines: number;
37+
baselineStderrLines?: number;
38+
currentStderrLines: number;
3139
};
3240
};
41+
highlights: string[];
3342
};
3443
}

src/lib/quality-report.ts

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { readJsonFile, resolveFromRepo, writeJsonFile } from "./io.js";
77
interface CommandSnapshot {
88
command: string;
99
exitCode: number;
10+
stdout: string;
11+
stderr: string;
1012
}
1113

1214
interface WriteQualityArtifactsInput {
@@ -84,12 +86,33 @@ function toMarkdown(report: QualityReport): string {
8486
`- outcome: ${report.spec.outcome}`,
8587
`- summary: ${report.spec.summary}`,
8688
`- build baseline/current: ${String(report.spec.comparedCommands.build.baselineExitCode ?? "n/a")} -> ${report.spec.comparedCommands.build.currentExitCode}`,
89+
`- build stderr lines baseline/current: ${String(report.spec.comparedCommands.build.baselineStderrLines ?? "n/a")} -> ${report.spec.comparedCommands.build.currentStderrLines}`,
8790
validationSection,
8891
`- baseline store: ${report.spec.target.baselineStorePath}`,
92+
report.spec.highlights.length > 0 ? "- highlights:" : "- highlights: none",
93+
...report.spec.highlights.map((entry) => ` - ${entry}`),
8994
""
9095
].join("\n");
9196
}
9297

98+
function countLines(text: string): number {
99+
const normalized = text.trim();
100+
if (normalized.length === 0) {
101+
return 0;
102+
}
103+
104+
return normalized.split(/\r?\n/).length;
105+
}
106+
107+
function firstUsefulLine(text: string): string | undefined {
108+
const line = text
109+
.split(/\r?\n/)
110+
.map((entry) => entry.trim())
111+
.find((entry) => entry.length > 0);
112+
113+
return line;
114+
}
115+
93116
export async function writeQualityArtifacts(input: WriteQualityArtifactsInput): Promise<QualityReportOutcome> {
94117
const { run, app, build, validation } = input;
95118
const target = resolveQualityTarget(run, app);
@@ -111,8 +134,21 @@ export async function writeQualityArtifacts(input: WriteQualityArtifactsInput):
111134
},
112135
target,
113136
commands: {
114-
build,
115-
validation
137+
build: {
138+
command: build.command,
139+
exitCode: build.exitCode,
140+
stdoutLines: countLines(build.stdout),
141+
stderrLines: countLines(build.stderr)
142+
},
143+
validation:
144+
typeof validation !== "undefined"
145+
? {
146+
command: validation.command,
147+
exitCode: validation.exitCode,
148+
stdoutLines: countLines(validation.stdout),
149+
stderrLines: countLines(validation.stderr)
150+
}
151+
: undefined
116152
}
117153
}
118154
};
@@ -173,16 +209,30 @@ export async function writeQualityArtifacts(input: WriteQualityArtifactsInput):
173209
comparedCommands: {
174210
build: {
175211
baselineExitCode: baselineForCompare?.spec.commands.build.exitCode,
176-
currentExitCode: build.exitCode
212+
currentExitCode: build.exitCode,
213+
baselineStdoutLines: baselineForCompare?.spec.commands.build.stdoutLines,
214+
currentStdoutLines: countLines(build.stdout),
215+
baselineStderrLines: baselineForCompare?.spec.commands.build.stderrLines,
216+
currentStderrLines: countLines(build.stderr)
177217
},
178218
validation:
179219
typeof validation !== "undefined"
180220
? {
181221
baselineExitCode: baselineForCompare?.spec.commands.validation?.exitCode,
182-
currentExitCode: validation.exitCode
222+
currentExitCode: validation.exitCode,
223+
baselineStdoutLines: baselineForCompare?.spec.commands.validation?.stdoutLines,
224+
currentStdoutLines: countLines(validation.stdout),
225+
baselineStderrLines: baselineForCompare?.spec.commands.validation?.stderrLines,
226+
currentStderrLines: countLines(validation.stderr)
183227
}
184228
: undefined
185-
}
229+
},
230+
highlights: [
231+
firstUsefulLine(build.stderr) ? `build stderr: ${firstUsefulLine(build.stderr)}` : undefined,
232+
typeof validation !== "undefined" && firstUsefulLine(validation.stderr)
233+
? `validation stderr: ${firstUsefulLine(validation.stderr)}`
234+
: undefined
235+
].filter((entry): entry is string => typeof entry === "string")
186236
}
187237
};
188238

0 commit comments

Comments
 (0)