pm-ops 2026.8.8 → 2026.8.16
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/CHANGELOG.md +12 -0
- package/dist/assurance.d.ts +52 -0
- package/dist/assurance.d.ts.map +1 -0
- package/dist/assurance.js +493 -0
- package/dist/assurance.js.map +1 -0
- package/dist/docstrings.d.ts.map +1 -1
- package/dist/docstrings.js +16 -2
- package/dist/docstrings.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/manifest.json +3 -2
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026.8.16 - 2026-08-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Measure the fleet's coverage and docstring quality as audited assurance bounds via a pm-ops quality measurement provider ([ops-a367](https://github.com/unbraind/pm-ops/blob/main/.agents/pm/features/ops-a367.toon))
|
|
8
|
+
|
|
9
|
+
## 2026.8.10 - 2026-08-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- The docstring gate scanned installed extension artifacts, so it disagreed with itself across machines ([ops-x8fk](https://github.com/unbraind/pm-ops/blob/main/.agents/pm/issues/ops-x8fk.toon))
|
|
14
|
+
|
|
3
15
|
## 2026.8.8 - 2026-08-08
|
|
4
16
|
|
|
5
17
|
### Added
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assurance measurement provider that turns the fleet's pinned code-quality
|
|
3
|
+
* thresholds into audited bounds.
|
|
4
|
+
*
|
|
5
|
+
* Every fleet package historically hand-rolled a `scripts/coverage-gate.ts`
|
|
6
|
+
* with thresholds pinned under `coverageGate` in `package.json`. Lowering one
|
|
7
|
+
* was an ordinary diff: nothing in the workflow recorded *why* a number moved,
|
|
8
|
+
* and nothing proved the bound still meant anything. Under pm-cli's assurance
|
|
9
|
+
* surface, weakening a bound requires an `authorization_decision` naming a
|
|
10
|
+
* terminal Decision item the host verifies, and every non-dry gate verdict is
|
|
11
|
+
* appended to the immutable workspace history stream. That only bites if the
|
|
12
|
+
* quality numbers can be *measured* by an assurance provider, which is what
|
|
13
|
+
* this module exports.
|
|
14
|
+
*
|
|
15
|
+
* The provider reads a local lcov coverage report and the canonical docstring
|
|
16
|
+
* analyzer and reduces them to four measurement keys. It owns no quality
|
|
17
|
+
* thresholds itself — those live in assertions and gates the consuming
|
|
18
|
+
* workspace declares — but it refuses to be satisfied by a *stale* coverage
|
|
19
|
+
* report, because a gate that runs against yesterday's `lcov.info` after today's
|
|
20
|
+
* source edit is a gate that measures nothing.
|
|
21
|
+
*
|
|
22
|
+
* @see {@link https://github.com/unbraind/pm-cli/blob/main/docs/ASSURANCE.md ASSURANCE.md}
|
|
23
|
+
* for the measurement / assertion / gate vocabulary this provider plugs into.
|
|
24
|
+
*/
|
|
25
|
+
import type { ExtensionApi } from "@unbrained/pm-cli/sdk/authoring";
|
|
26
|
+
/**
|
|
27
|
+
* Exact contract the host's `api.registerAssuranceMeasurementProvider`
|
|
28
|
+
* expects for one provider registration.
|
|
29
|
+
*
|
|
30
|
+
* `@unbrained/pm-cli` keeps this type on its internal
|
|
31
|
+
* `core/extensions/extension-types` module and does not yet re-export it
|
|
32
|
+
* through any public `sdk/*` barrel (the SDK only exposes it as the parameter
|
|
33
|
+
* of the registration method). Deriving it from that public method gives the
|
|
34
|
+
* identical, exact shape without reaching into `dist/` internals or
|
|
35
|
+
* hand-copying field names — if the SDK ever widens the method signature, this
|
|
36
|
+
* alias widens with it.
|
|
37
|
+
*/
|
|
38
|
+
export type AssuranceMeasurementProviderDefinition = Parameters<ExtensionApi["registerAssuranceMeasurementProvider"]>[0];
|
|
39
|
+
/** Stable provider id used by measurement declarations and gate allow-lists. */
|
|
40
|
+
export declare const QUALITY_PROVIDER_ID = "pm-ops-quality";
|
|
41
|
+
/**
|
|
42
|
+
* The pm-ops quality assurance measurement provider.
|
|
43
|
+
*
|
|
44
|
+
* Registered through the `services` capability, it exposes four keys over a
|
|
45
|
+
* local lcov report and the canonical docstring analyzer. It declares no
|
|
46
|
+
* network use and a `medium` cost class so a trigger policy that caps work at
|
|
47
|
+
* `low` can decline it before invocation. Key ids use the hyphen form the pm-cli
|
|
48
|
+
* host requires for every stable lowercase id (`^[a-z0-9][a-z0-9-]*$`); the
|
|
49
|
+
* hyphen and underscore spellings name the same measurements.
|
|
50
|
+
*/
|
|
51
|
+
export declare const qualityMeasurementProvider: AssuranceMeasurementProviderDefinition;
|
|
52
|
+
//# sourceMappingURL=assurance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assurance.d.ts","sourceRoot":"","sources":["../assurance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAKpE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,sCAAsC,GAChD,UAAU,CAAC,YAAY,CAAC,sCAAsC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtE,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AA+epD;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,EAAE,sCAgFtC,CAAC"}
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assurance measurement provider that turns the fleet's pinned code-quality
|
|
3
|
+
* thresholds into audited bounds.
|
|
4
|
+
*
|
|
5
|
+
* Every fleet package historically hand-rolled a `scripts/coverage-gate.ts`
|
|
6
|
+
* with thresholds pinned under `coverageGate` in `package.json`. Lowering one
|
|
7
|
+
* was an ordinary diff: nothing in the workflow recorded *why* a number moved,
|
|
8
|
+
* and nothing proved the bound still meant anything. Under pm-cli's assurance
|
|
9
|
+
* surface, weakening a bound requires an `authorization_decision` naming a
|
|
10
|
+
* terminal Decision item the host verifies, and every non-dry gate verdict is
|
|
11
|
+
* appended to the immutable workspace history stream. That only bites if the
|
|
12
|
+
* quality numbers can be *measured* by an assurance provider, which is what
|
|
13
|
+
* this module exports.
|
|
14
|
+
*
|
|
15
|
+
* The provider reads a local lcov coverage report and the canonical docstring
|
|
16
|
+
* analyzer and reduces them to four measurement keys. It owns no quality
|
|
17
|
+
* thresholds itself — those live in assertions and gates the consuming
|
|
18
|
+
* workspace declares — but it refuses to be satisfied by a *stale* coverage
|
|
19
|
+
* report, because a gate that runs against yesterday's `lcov.info` after today's
|
|
20
|
+
* source edit is a gate that measures nothing.
|
|
21
|
+
*
|
|
22
|
+
* @see {@link https://github.com/unbraind/pm-cli/blob/main/docs/ASSURANCE.md ASSURANCE.md}
|
|
23
|
+
* for the measurement / assertion / gate vocabulary this provider plugs into.
|
|
24
|
+
*/
|
|
25
|
+
import { readFileSync, statSync } from "node:fs";
|
|
26
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
27
|
+
import { analyzeDocstringCoverage } from "./docstrings.js";
|
|
28
|
+
/** Stable provider id used by measurement declarations and gate allow-lists. */
|
|
29
|
+
export const QUALITY_PROVIDER_ID = "pm-ops-quality";
|
|
30
|
+
/**
|
|
31
|
+
* The four lcov dimensions this provider can reduce to a single percentage.
|
|
32
|
+
*
|
|
33
|
+
* lcov has native `LF`/`LH`, `BRF`/`BRH`, and `FNF`/`FNH` counters for lines,
|
|
34
|
+
* branches, and functions, but no native statement counter: statements are
|
|
35
|
+
* reconstructed from the per-line `DA:` records the reporter already emits.
|
|
36
|
+
*/
|
|
37
|
+
const COVERAGE_DIMENSIONS = [
|
|
38
|
+
"lines",
|
|
39
|
+
"branches",
|
|
40
|
+
"functions",
|
|
41
|
+
"statements",
|
|
42
|
+
];
|
|
43
|
+
/** Default lcov report path when a measurement omits the `report` parameter. */
|
|
44
|
+
const DEFAULT_REPORT = "coverage/lcov.info";
|
|
45
|
+
/** Default source root when a docstring measurement omits the `root` parameter. */
|
|
46
|
+
const DEFAULT_ROOT = ".";
|
|
47
|
+
/**
|
|
48
|
+
* Parse lcov text into one record per covered file.
|
|
49
|
+
*
|
|
50
|
+
* lcov delimits file sections with `SF:` and `end_of_record`. A well-formed
|
|
51
|
+
* report always pairs them, but real reporters occasionally omit the trailing
|
|
52
|
+
* `end_of_record` on the last section, so the parser flushes a pending record
|
|
53
|
+
* on the next `SF:` and once more at end-of-input rather than dropping it.
|
|
54
|
+
* `SF:` paths may be absolute or repo-relative; both are normalised to a
|
|
55
|
+
* repo-relative POSIX label resolved against `base`, matching how the package's
|
|
56
|
+
* own `coverage-gate.ts` reads them back.
|
|
57
|
+
*/
|
|
58
|
+
function parseLcov(text, base) {
|
|
59
|
+
const records = [];
|
|
60
|
+
let file = "";
|
|
61
|
+
let sourcePath = "";
|
|
62
|
+
let linesFound = 0;
|
|
63
|
+
let linesHit = 0;
|
|
64
|
+
let branchesFound = 0;
|
|
65
|
+
let branchesHit = 0;
|
|
66
|
+
let functionsFound = 0;
|
|
67
|
+
let functionsHit = 0;
|
|
68
|
+
let statementsFound = 0;
|
|
69
|
+
let statementsHit = 0;
|
|
70
|
+
let started = false;
|
|
71
|
+
const flush = () => {
|
|
72
|
+
if (!started) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
records.push({
|
|
76
|
+
file,
|
|
77
|
+
sourcePath,
|
|
78
|
+
linesFound,
|
|
79
|
+
linesHit,
|
|
80
|
+
branchesFound,
|
|
81
|
+
branchesHit,
|
|
82
|
+
functionsFound,
|
|
83
|
+
functionsHit,
|
|
84
|
+
statementsFound,
|
|
85
|
+
statementsHit,
|
|
86
|
+
});
|
|
87
|
+
started = false;
|
|
88
|
+
file = "";
|
|
89
|
+
sourcePath = "";
|
|
90
|
+
linesFound = 0;
|
|
91
|
+
linesHit = 0;
|
|
92
|
+
branchesFound = 0;
|
|
93
|
+
branchesHit = 0;
|
|
94
|
+
functionsFound = 0;
|
|
95
|
+
functionsHit = 0;
|
|
96
|
+
statementsFound = 0;
|
|
97
|
+
statementsHit = 0;
|
|
98
|
+
};
|
|
99
|
+
for (const rawLine of text.split("\n")) {
|
|
100
|
+
const line = rawLine.trimEnd();
|
|
101
|
+
if (line === "end_of_record") {
|
|
102
|
+
flush();
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (line.startsWith("SF:")) {
|
|
106
|
+
flush();
|
|
107
|
+
const raw = line.slice(3).trim();
|
|
108
|
+
const absolute = isAbsolute(raw) ? raw : join(base, raw);
|
|
109
|
+
file = relative(base, absolute).split(sep).join("/");
|
|
110
|
+
sourcePath = absolute;
|
|
111
|
+
started = true;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!started) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (line.startsWith("DA:")) {
|
|
118
|
+
// Each `DA:<line>,<count>[,<checksum>]` record is one instrumented
|
|
119
|
+
// statement: count it as found unconditionally and as hit when its
|
|
120
|
+
// execution count (the second field, never the line number) is > 0.
|
|
121
|
+
statementsFound++;
|
|
122
|
+
const firstComma = line.indexOf(",", 3);
|
|
123
|
+
const secondComma = firstComma === -1 ? -1 : line.indexOf(",", firstComma + 1);
|
|
124
|
+
const countEnd = secondComma === -1 ? line.length : secondComma;
|
|
125
|
+
const count = firstComma === -1
|
|
126
|
+
? Number.NaN
|
|
127
|
+
: Number(line.slice(firstComma + 1, countEnd));
|
|
128
|
+
if (Number.isFinite(count) && count > 0) {
|
|
129
|
+
statementsHit++;
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (line.startsWith("LF:")) {
|
|
134
|
+
linesFound = countAfter(line, 3);
|
|
135
|
+
}
|
|
136
|
+
else if (line.startsWith("LH:")) {
|
|
137
|
+
linesHit = countAfter(line, 3);
|
|
138
|
+
}
|
|
139
|
+
else if (line.startsWith("BRF:")) {
|
|
140
|
+
branchesFound = countAfter(line, 4);
|
|
141
|
+
}
|
|
142
|
+
else if (line.startsWith("BRH:")) {
|
|
143
|
+
branchesHit = countAfter(line, 4);
|
|
144
|
+
}
|
|
145
|
+
else if (line.startsWith("FNF:")) {
|
|
146
|
+
functionsFound = countAfter(line, 4);
|
|
147
|
+
}
|
|
148
|
+
else if (line.startsWith("FNH:")) {
|
|
149
|
+
functionsHit = countAfter(line, 4);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
flush();
|
|
153
|
+
return records;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Read the integer payload following an lcov count tag (`LF:42`).
|
|
157
|
+
*
|
|
158
|
+
* `tagLength` is the length of the tag including its colon. A malformed payload
|
|
159
|
+
* coerces to `0` rather than `NaN` so one corrupt line cannot poison an
|
|
160
|
+
* otherwise valid report into a non-finite percentage.
|
|
161
|
+
*/
|
|
162
|
+
function countAfter(line, tagLength) {
|
|
163
|
+
return Number(line.slice(tagLength)) || 0;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve a path-shaped provider parameter against the workspace base.
|
|
167
|
+
*
|
|
168
|
+
* Absolute paths are honoured as-is; relative paths resolve against the base so
|
|
169
|
+
* a measurement declared with the default `coverage/lcov.info` finds the report
|
|
170
|
+
* regardless of the process working directory the host happened to launch from.
|
|
171
|
+
* A non-string value falls back to the default rather than throwing, because the
|
|
172
|
+
* host has already validated the parameter against its declared `string` type.
|
|
173
|
+
*/
|
|
174
|
+
function resolveParameterPath(value, fallback, base) {
|
|
175
|
+
const raw = typeof value === "string" ? value : fallback;
|
|
176
|
+
return isAbsolute(raw) ? raw : join(base, raw);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Best on-disk root for resolving relative report and source paths.
|
|
180
|
+
*
|
|
181
|
+
* The host populates `repo_root` and `source_workspace_root` from the Git root
|
|
182
|
+
* and the launch directory; `process.cwd()` is the last resort for direct unit
|
|
183
|
+
* tests that construct a context by hand.
|
|
184
|
+
*/
|
|
185
|
+
function workspaceBase(context) {
|
|
186
|
+
return context.repo_root ?? context.source_workspace_root ?? process.cwd();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Reduce one record to the found/hit pair for the requested dimension.
|
|
190
|
+
*
|
|
191
|
+
* Statements are the only dimension reconstructed from `DA:` records rather than
|
|
192
|
+
* read from a native lcov counter, so they branch on the parsed field.
|
|
193
|
+
*/
|
|
194
|
+
function dimensionCount(record, dimension) {
|
|
195
|
+
switch (dimension) {
|
|
196
|
+
case "lines":
|
|
197
|
+
return { found: record.linesFound, hit: record.linesHit };
|
|
198
|
+
case "branches":
|
|
199
|
+
return { found: record.branchesFound, hit: record.branchesHit };
|
|
200
|
+
case "functions":
|
|
201
|
+
return { found: record.functionsFound, hit: record.functionsHit };
|
|
202
|
+
case "statements":
|
|
203
|
+
return { found: record.statementsFound, hit: record.statementsHit };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Round a percentage to two decimals so verdicts carry stable, readable values. */
|
|
207
|
+
function roundPercent(value) {
|
|
208
|
+
return Math.round(value * 100) / 100;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Read and parse the lcov report, then refuse it if it is stale.
|
|
212
|
+
*
|
|
213
|
+
* A missing report fails with an actionable message rather than an opaque
|
|
214
|
+
* filesystem error, because the gate author needs to know the `report`
|
|
215
|
+
* parameter resolved to a path nothing wrote to. After parsing, the staleness
|
|
216
|
+
* refusal compares the report's mtime against every covered source; see
|
|
217
|
+
* {@link assertReportNotStale} for why that comparison is the provider's core
|
|
218
|
+
* security property.
|
|
219
|
+
*/
|
|
220
|
+
function readCoverageReport(reportPath, base) {
|
|
221
|
+
let text;
|
|
222
|
+
try {
|
|
223
|
+
text = readFileSync(reportPath, "utf8");
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
throw new Error(`pm-ops-quality: coverage report not found at ${reportPath}. ` +
|
|
227
|
+
'Provide the "report" parameter or generate the report by running the ' +
|
|
228
|
+
"test suite with an lcov reporter before evaluating the gate.");
|
|
229
|
+
}
|
|
230
|
+
const records = parseLcov(text, base);
|
|
231
|
+
assertReportNotStale(reportPath, records.map((record) => record.sourcePath));
|
|
232
|
+
return records;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Refuse a coverage report older than any source file it covers.
|
|
236
|
+
*
|
|
237
|
+
* This is the security property that makes the provider worth registering: a
|
|
238
|
+
* gate that reads a report file can be satisfied by a *stale* report — run the
|
|
239
|
+
* suite once at full coverage, edit the source, and the gate still passes
|
|
240
|
+
* against yesterday's numbers. Comparing the report's mtime against the mtime of
|
|
241
|
+
* every covered source (and only those, never wall-clock age) makes the verdict
|
|
242
|
+
* depend on *what changed* rather than on *when the gate ran*. If any covered
|
|
243
|
+
* source is newer than the report, resolution throws naming the offending file
|
|
244
|
+
* and both timestamps, so the verdict can never be computed from a stale report
|
|
245
|
+
* and can never silently downgrade to a pass.
|
|
246
|
+
*
|
|
247
|
+
* A source the report names but that no longer exists is skipped here: a missing
|
|
248
|
+
* file cannot be "newer", and the absence is already visible in the verdict's
|
|
249
|
+
* population and contributors. This check answers exactly one question — is the
|
|
250
|
+
* report behind the source tree? — and refuses only when it is.
|
|
251
|
+
*/
|
|
252
|
+
function assertReportNotStale(reportPath, sourcePaths) {
|
|
253
|
+
const reportMtime = statSync(reportPath).mtimeMs;
|
|
254
|
+
const stale = [];
|
|
255
|
+
for (const source of sourcePaths) {
|
|
256
|
+
let info;
|
|
257
|
+
try {
|
|
258
|
+
info = statSync(source);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (info.mtimeMs > reportMtime) {
|
|
264
|
+
stale.push({ source, mtime: info.mtimeMs });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (stale.length === 0) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const reportIso = new Date(reportMtime).toISOString();
|
|
271
|
+
const offenders = stale
|
|
272
|
+
.map((entry) => ` ${entry.source} (modified ${new Date(entry.mtime).toISOString()})`)
|
|
273
|
+
.join("\n");
|
|
274
|
+
throw new Error(`pm-ops-quality: refusing a stale coverage report.\n` +
|
|
275
|
+
`The report ${reportPath} (modified ${reportIso}) is older than source ` +
|
|
276
|
+
"file(s) it covers, so its numbers no longer describe the current tree. " +
|
|
277
|
+
"Re-run the test suite to regenerate the report before evaluating the gate.\n" +
|
|
278
|
+
`Source files newer than the report:\n${offenders}`);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Resolve the `coverage_percent` measurement for one dimension.
|
|
282
|
+
*
|
|
283
|
+
* The percentage is the dimension's total hit over its total found across the
|
|
284
|
+
* whole report; the population denominator is that same total found, so a gate
|
|
285
|
+
* reading the receipt can tell a 100% over two hundred lines from a vacuous
|
|
286
|
+
* 100% over zero. Contributors are the repo-relative files still below full
|
|
287
|
+
* coverage on the requested dimension, sorted for deterministic verdicts.
|
|
288
|
+
*/
|
|
289
|
+
function resolveCoveragePercent(context) {
|
|
290
|
+
const dimension = requireCoverageDimension(context.parameters.dimension);
|
|
291
|
+
const base = workspaceBase(context);
|
|
292
|
+
const reportPath = resolveParameterPath(context.parameters.report, DEFAULT_REPORT, base);
|
|
293
|
+
const records = readCoverageReport(reportPath, base);
|
|
294
|
+
let found = 0;
|
|
295
|
+
let hit = 0;
|
|
296
|
+
const contributors = [];
|
|
297
|
+
for (const record of records) {
|
|
298
|
+
const counts = dimensionCount(record, dimension);
|
|
299
|
+
found += counts.found;
|
|
300
|
+
hit += counts.hit;
|
|
301
|
+
if (counts.hit < counts.found) {
|
|
302
|
+
contributors.push(record.file);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
contributors.sort();
|
|
306
|
+
const value = found === 0 ? 100 : roundPercent((hit / found) * 100);
|
|
307
|
+
return {
|
|
308
|
+
value,
|
|
309
|
+
population_size: found,
|
|
310
|
+
cost: records.length,
|
|
311
|
+
contributors,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Resolve the `uncovered_files` measurement.
|
|
316
|
+
*
|
|
317
|
+
* The value is the repo-relative set of files that are below full coverage on
|
|
318
|
+
* *any* dimension, so a gate can bound how many files still carry coverage debt
|
|
319
|
+
* without caring which dimension slipped. The population is every file in the
|
|
320
|
+
* report, so the receipt reads "3 of 41 files are not fully covered".
|
|
321
|
+
*/
|
|
322
|
+
function resolveUncoveredFiles(context) {
|
|
323
|
+
const base = workspaceBase(context);
|
|
324
|
+
const reportPath = resolveParameterPath(context.parameters.report, DEFAULT_REPORT, base);
|
|
325
|
+
const records = readCoverageReport(reportPath, base);
|
|
326
|
+
// A file is "uncovered" when it falls short of full coverage on any one
|
|
327
|
+
// dimension; a dimension lcov reports as empty (`found` 0) is vacuously
|
|
328
|
+
// satisfied, so a branchless file can still be fully covered.
|
|
329
|
+
const uncovered = records
|
|
330
|
+
.filter((record) => record.linesHit < record.linesFound ||
|
|
331
|
+
record.branchesHit < record.branchesFound ||
|
|
332
|
+
record.functionsHit < record.functionsFound ||
|
|
333
|
+
record.statementsHit < record.statementsFound)
|
|
334
|
+
.map((record) => record.file)
|
|
335
|
+
.sort();
|
|
336
|
+
return {
|
|
337
|
+
value: uncovered,
|
|
338
|
+
population_size: records.length,
|
|
339
|
+
cost: records.length,
|
|
340
|
+
contributors: uncovered,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Resolve the `docstring_percent` measurement.
|
|
345
|
+
*
|
|
346
|
+
* Delegates to the canonical analyzer at {@link analyzeDocstringCoverage} rather
|
|
347
|
+
* than reimplementing it, so the fleet has exactly one docstring definition.
|
|
348
|
+
* The population is the count of documentable symbols and contributors are the
|
|
349
|
+
* `path:symbol` labels still missing a docstring, matching the analyzer's own
|
|
350
|
+
* violation output.
|
|
351
|
+
*/
|
|
352
|
+
function resolveDocstringPercent(context) {
|
|
353
|
+
const base = workspaceBase(context);
|
|
354
|
+
const root = resolveParameterPath(context.parameters.root, DEFAULT_ROOT, base);
|
|
355
|
+
const report = analyzeDocstringCoverage({ root });
|
|
356
|
+
const total = report.declarations_checked;
|
|
357
|
+
const missing = report.violations.length;
|
|
358
|
+
const value = total === 0 ? 100 : roundPercent(((total - missing) / total) * 100);
|
|
359
|
+
const contributors = docstringContributors(report.violations);
|
|
360
|
+
return { value, population_size: total, cost: total, contributors };
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Resolve the `undocumented_symbols` measurement.
|
|
364
|
+
*
|
|
365
|
+
* The value is the stable `path:symbol` label set for every symbol the analyzer
|
|
366
|
+
* flagged, so a gate can name exactly which declarations carry the debt. The
|
|
367
|
+
* population is the full documentable-symbol count, not just the violators, so
|
|
368
|
+
* the receipt distinguishes "5 of 5 undocumented" from "5 of 200 undocumented".
|
|
369
|
+
*/
|
|
370
|
+
function resolveUndocumentedSymbols(context) {
|
|
371
|
+
const base = workspaceBase(context);
|
|
372
|
+
const root = resolveParameterPath(context.parameters.root, DEFAULT_ROOT, base);
|
|
373
|
+
const report = analyzeDocstringCoverage({ root });
|
|
374
|
+
const labels = docstringContributors(report.violations);
|
|
375
|
+
return {
|
|
376
|
+
value: labels,
|
|
377
|
+
population_size: report.declarations_checked,
|
|
378
|
+
cost: report.declarations_checked,
|
|
379
|
+
contributors: labels,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Build sorted `path:symbol` contributor labels from analyzer violations.
|
|
384
|
+
*
|
|
385
|
+
* Centralised so both docstring keys derive identical labels from the same
|
|
386
|
+
* violation stream; a future change to the label format changes both at once.
|
|
387
|
+
*/
|
|
388
|
+
function docstringContributors(violations) {
|
|
389
|
+
return violations
|
|
390
|
+
.map((violation) => `${violation.file}:${violation.symbol}`)
|
|
391
|
+
.sort();
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Validate and accept the required `dimension` parameter for `coverage_percent`.
|
|
395
|
+
*
|
|
396
|
+
* The host validates that a required parameter is *present* and *string-typed*,
|
|
397
|
+
* but it cannot validate the value domain, so an unknown dimension like
|
|
398
|
+
* `"loc"` would otherwise flow through to a percentage of nothing. This guard
|
|
399
|
+
* fails fast with the accepted vocabulary instead of returning a misleading 0%.
|
|
400
|
+
*/
|
|
401
|
+
function requireCoverageDimension(value) {
|
|
402
|
+
if (typeof value !== "string") {
|
|
403
|
+
throw new Error('pm-ops-quality: key "coverage_percent" requires a string "dimension" ' +
|
|
404
|
+
`parameter; received ${value === null ? "null" : typeof value}.`);
|
|
405
|
+
}
|
|
406
|
+
if (!COVERAGE_DIMENSIONS.includes(value)) {
|
|
407
|
+
throw new Error(`pm-ops-quality: unknown coverage dimension "${value}". Expected one of: ` +
|
|
408
|
+
`${COVERAGE_DIMENSIONS.join(", ")}.`);
|
|
409
|
+
}
|
|
410
|
+
return value;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* The pm-ops quality assurance measurement provider.
|
|
414
|
+
*
|
|
415
|
+
* Registered through the `services` capability, it exposes four keys over a
|
|
416
|
+
* local lcov report and the canonical docstring analyzer. It declares no
|
|
417
|
+
* network use and a `medium` cost class so a trigger policy that caps work at
|
|
418
|
+
* `low` can decline it before invocation. Key ids use the hyphen form the pm-cli
|
|
419
|
+
* host requires for every stable lowercase id (`^[a-z0-9][a-z0-9-]*$`); the
|
|
420
|
+
* hyphen and underscore spellings name the same measurements.
|
|
421
|
+
*/
|
|
422
|
+
export const qualityMeasurementProvider = {
|
|
423
|
+
id: QUALITY_PROVIDER_ID,
|
|
424
|
+
cost_class: "medium",
|
|
425
|
+
network: false,
|
|
426
|
+
timeout_ms: 30_000,
|
|
427
|
+
keys: {
|
|
428
|
+
"coverage-percent": {
|
|
429
|
+
value_type: "number",
|
|
430
|
+
description: "Percentage covered for one lcov dimension across the whole report.",
|
|
431
|
+
parameters: {
|
|
432
|
+
dimension: {
|
|
433
|
+
type: "string",
|
|
434
|
+
required: true,
|
|
435
|
+
description: "One of lines, branches, functions, or statements.",
|
|
436
|
+
},
|
|
437
|
+
report: {
|
|
438
|
+
type: "string",
|
|
439
|
+
required: false,
|
|
440
|
+
description: "Path to an lcov report; defaults to coverage/lcov.info.",
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
"uncovered-files": {
|
|
445
|
+
value_type: "string_set",
|
|
446
|
+
description: "Repo-relative files that are not at 100% on every dimension.",
|
|
447
|
+
parameters: {
|
|
448
|
+
report: {
|
|
449
|
+
type: "string",
|
|
450
|
+
required: false,
|
|
451
|
+
description: "Path to an lcov report; defaults to coverage/lcov.info.",
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
"docstring-percent": {
|
|
456
|
+
value_type: "number",
|
|
457
|
+
description: "Percentage of documentable symbols carrying a docstring.",
|
|
458
|
+
parameters: {
|
|
459
|
+
root: {
|
|
460
|
+
type: "string",
|
|
461
|
+
required: false,
|
|
462
|
+
description: "Source root to scan; defaults to the workspace root.",
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
"undocumented-symbols": {
|
|
467
|
+
value_type: "string_set",
|
|
468
|
+
description: "Stable path:symbol labels for symbols missing a docstring.",
|
|
469
|
+
parameters: {
|
|
470
|
+
root: {
|
|
471
|
+
type: "string",
|
|
472
|
+
required: false,
|
|
473
|
+
description: "Source root to scan; defaults to the workspace root.",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
resolve(context) {
|
|
479
|
+
switch (context.key) {
|
|
480
|
+
case "coverage-percent":
|
|
481
|
+
return resolveCoveragePercent(context);
|
|
482
|
+
case "uncovered-files":
|
|
483
|
+
return resolveUncoveredFiles(context);
|
|
484
|
+
case "docstring-percent":
|
|
485
|
+
return resolveDocstringPercent(context);
|
|
486
|
+
case "undocumented-symbols":
|
|
487
|
+
return resolveUndocumentedSymbols(context);
|
|
488
|
+
default:
|
|
489
|
+
throw new Error(`pm-ops-quality: provider does not declare key "${context.key}".`);
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
//# sourceMappingURL=assurance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assurance.js","sourceRoot":"","sources":["../assurance.ts"],"names":[],"mappings":"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"}
|
package/dist/docstrings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docstrings.d.ts","sourceRoot":"","sources":["../docstrings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AAOH;;;GAGG;AACH,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B;;;;GAIG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,0DAA0D;IAC1D,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,wCAAwC;IACxC,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACpD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACnD,gDAAgD;IAChD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;
|
|
1
|
+
{"version":3,"file":"docstrings.d.ts","sourceRoot":"","sources":["../docstrings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AAOH;;;GAGG;AACH,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B;;;;GAIG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,0DAA0D;IAC1D,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,wCAAwC;IACxC,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACpD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACnD,gDAAgD;IAChD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAqkCD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CAKxE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,eAAe,CAoBnH"}
|
package/dist/docstrings.js
CHANGED
|
@@ -129,9 +129,23 @@ const FILLER = new Set([
|
|
|
129
129
|
* - `node_modules` / `dist` / `dist-test` / `coverage` are installed or built
|
|
130
130
|
* output, not authored source;
|
|
131
131
|
* - `test` holds the gate's own fixtures, which intentionally lack docstrings;
|
|
132
|
-
* - `.git` is version-control metadata
|
|
132
|
+
* - `.git` is version-control metadata;
|
|
133
|
+
* - `.agents` holds the pm tracker, including extension artifacts installed
|
|
134
|
+
* under `.agents/pm/extensions/`. Those are installed output like
|
|
135
|
+
* `node_modules`, and they are gitignored, so scanning them makes the gate
|
|
136
|
+
* depend on whether a developer happens to have extensions installed: the
|
|
137
|
+
* same commit fails `release:check` locally and passes in a clean CI
|
|
138
|
+
* checkout. A release gate must not disagree with itself across machines.
|
|
133
139
|
*/
|
|
134
|
-
const SKIP_DIRS = new Set([
|
|
140
|
+
const SKIP_DIRS = new Set([
|
|
141
|
+
"node_modules",
|
|
142
|
+
"dist",
|
|
143
|
+
"dist-test",
|
|
144
|
+
"coverage",
|
|
145
|
+
"test",
|
|
146
|
+
".git",
|
|
147
|
+
".agents",
|
|
148
|
+
]);
|
|
135
149
|
/**
|
|
136
150
|
* Tokens after which a `/` is a regex rather than division, per the heuristic
|
|
137
151
|
* every JavaScript lexer applies: a `/` is division only when the previous
|