@clossys/writer 0.3.6 → 0.3.9
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 +89 -0
- package/README.md +89 -0
- package/dist/approved-copy-coverage-rate-cli.d.ts +10 -0
- package/dist/approved-copy-coverage-rate-cli.d.ts.map +1 -0
- package/dist/approved-copy-coverage-rate-cli.js +59 -0
- package/dist/approved-copy-coverage-rate-cli.js.map +1 -0
- package/dist/approved-copy-coverage-rate.d.ts +23 -0
- package/dist/approved-copy-coverage-rate.d.ts.map +1 -0
- package/dist/approved-copy-coverage-rate.js +153 -0
- package/dist/approved-copy-coverage-rate.js.map +1 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +105 -29
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +30 -0
- package/dist/scan.js.map +1 -1
- package/package.json +10 -3
- package/src/approved-copy-coverage-rate-cli.ts +55 -0
- package/src/approved-copy-coverage-rate.ts +201 -0
- package/src/cli.ts +179 -26
- package/src/index.ts +6 -0
- package/src/scan.ts +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,95 @@
|
|
|
3
3
|
All notable changes to this package are documented here. Format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## [0.3.9] - 2026-09-18
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Documented installation against the public npm registry
|
|
11
|
+
(`https://registry.npmjs.org`) and that installing needs no authentication.
|
|
12
|
+
- Stated the charter close condition in the README: independent consumer
|
|
13
|
+
evidence of `approved copy coverage rate`, computed by
|
|
14
|
+
`assessApprovedCopyCoverageRate()`. An empty evaluated set is
|
|
15
|
+
indeterminate, never a perfect rate of 1. `checkCopyRecord` and
|
|
16
|
+
`checkCopyTraceability` remain the gates they are; neither is this rate.
|
|
17
|
+
- Declared `foundry.assessment` against a new mapped `writer-rate-check`
|
|
18
|
+
bin with `invocation: "single-json-input"`. `writer-check` remains the
|
|
19
|
+
multi-mode CLI and is not the assessment surface. Advisor remains the
|
|
20
|
+
only required first-day role.
|
|
21
|
+
- `writer-rate-check assessment.json`: prints the `approved copy coverage
|
|
22
|
+
rate` report and exits on the `0` / `1` / `2` ternary.
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
|
|
26
|
+
- This does not claim the position is closed. Qualification of `0.3.9` is
|
|
27
|
+
deferred under #833.
|
|
28
|
+
|
|
29
|
+
## [0.3.8] - 2026-09-16
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- README now documents the `writer-check --format json` contract added in
|
|
34
|
+
0.3.7: the exact object shape, and the guarantee that `verdict`, `findings`
|
|
35
|
+
and `unchecked` are present together on every path — clean, findings, and
|
|
36
|
+
total failure alike. The flag was documented in `--help` and in this
|
|
37
|
+
changelog but not in the README, where this package documents its other
|
|
38
|
+
CLI exit-code contracts (#880).
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- Version bumped from 0.3.7 to 0.3.8 without a functional change to the
|
|
43
|
+
shipped code. 0.3.7 was never published: its retained qualification record
|
|
44
|
+
bound a tarball that could not be reproduced from the tree, because the
|
|
45
|
+
record was generated against an incrementally-built `dist/` that carried
|
|
46
|
+
stale artifacts. Qualification records are immutable — one introduction per
|
|
47
|
+
path, hash-pinned — so the 0.3.7 record could not be corrected and 0.3.7
|
|
48
|
+
could never be published. It is skipped deliberately rather than reused.
|
|
49
|
+
|
|
50
|
+
The mechanism is filed as #893; this release's own record was generated
|
|
51
|
+
from a clean build on the pinned release runtime specifically because of it.
|
|
52
|
+
|
|
53
|
+
## [0.3.7] - 2026-09-15
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- The default `writer-check` command's scanner could not classify a `//`
|
|
58
|
+
line comment or a `/* */` block comment placed BETWEEN a JSX element's
|
|
59
|
+
attributes — legal TSX every mainstream formatter produces for a
|
|
60
|
+
commented-out or explained prop. The attribute-parsing loop inside
|
|
61
|
+
`tryScanJsxElement` had no case for either comment form, so it fell
|
|
62
|
+
through to the loop's own "not committed, silently not-JSX" backtrack;
|
|
63
|
+
for a NESTED element (the realistic shape in a real component tree)
|
|
64
|
+
that backtrack surfaced as an `"unrecognized-jsx-child"` `unchecked`
|
|
65
|
+
entry, and this package's own indeterminate-fold ("one unreadable
|
|
66
|
+
region must not let a scan report itself clean") turned that ONE
|
|
67
|
+
construct into exit code 2 for the WHOLE run — discarding every real
|
|
68
|
+
finding the same scan had already produced (issue #753: a 270-entry
|
|
69
|
+
`CopyRecord` scan against a real application tree extracted 294
|
|
70
|
+
candidates, produced 292 findings, and still exited 2 over this one
|
|
71
|
+
construct). Comment trivia between attributes is now skipped exactly
|
|
72
|
+
like the whitespace surrounding it, before any commit decision is
|
|
73
|
+
made — the same "silent, pre-commit trivia" treatment this loop
|
|
74
|
+
already gives TSX's generic-arrow-function syntax.
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- `writer-check --format json`: the default command's own structured,
|
|
79
|
+
machine-readable report (`CopyTraceabilityReport`), matching the
|
|
80
|
+
`--format json` shape `inspector`'s CLI already publishes. Exit code 2
|
|
81
|
+
(indeterminate) is unchanged and still wins whenever any JSX construct
|
|
82
|
+
is `unchecked` — an indeterminate result must never read as clean, and
|
|
83
|
+
this does not touch that rule. What changes is that a consumer no
|
|
84
|
+
longer has to choose between reading the exit code and discarding
|
|
85
|
+
everything the run DID measure: `--format json` prints exactly one
|
|
86
|
+
object to stdout, always carrying `verdict` (the same
|
|
87
|
+
`"clean" | "findings" | "indeterminate"` states this package uses
|
|
88
|
+
everywhere), `findings`, and `unchecked` together, regardless of which
|
|
89
|
+
verdict resulted — so a real finding produced alongside a genuinely
|
|
90
|
+
unclassifiable construct in the same run is still reachable by a
|
|
91
|
+
consumer that reads past the exit code, exactly as #753 asked for,
|
|
92
|
+
while a consumer that only checks `exitCode`/`verdict` still, correctly,
|
|
93
|
+
never sees an indeterminate run reported as a pass.
|
|
94
|
+
|
|
6
95
|
## [0.3.6] - 2026-09-14
|
|
7
96
|
|
|
8
97
|
### Changed
|
package/README.md
CHANGED
|
@@ -16,6 +16,45 @@ template; each consumer supplies its own language and facts.
|
|
|
16
16
|
npm install @clossys/writer
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
This package is published to the public npm registry, `https://registry.npmjs.org`.
|
|
20
|
+
Installing it needs no authentication: no npm token, no `.npmrc` registry
|
|
21
|
+
override, and no GitHub credential of any kind.
|
|
22
|
+
|
|
23
|
+
## Approved copy coverage rate
|
|
24
|
+
|
|
25
|
+
Independent consumer evidence shows the position's owned metric meets its
|
|
26
|
+
setpoint over the declared review cadence. The owned metric is `approved
|
|
27
|
+
copy coverage rate`, computed by `assessApprovedCopyCoverageRate()`. An
|
|
28
|
+
empty evaluated set is `indeterminate`, never a perfect rate of 1.
|
|
29
|
+
`checkCopyRecord` and `checkCopyTraceability` remain the gates they are;
|
|
30
|
+
neither is this rate. This package does not measure consumer evidence and
|
|
31
|
+
does not close the loop. A green run of this package's tests is not a close.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { assessApprovedCopyCoverageRate } from "@clossys/writer";
|
|
35
|
+
|
|
36
|
+
const report = assessApprovedCopyCoverageRate(input);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
writer-rate-check assessment.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The command prints JSON and exits `0` for satisfied, `1` for violated, and
|
|
44
|
+
`2` for indeterminate, unreadable, or invalid input.
|
|
45
|
+
|
|
46
|
+
This package declares that command as its first-day assessment surface in
|
|
47
|
+
its own manifest:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
"foundry": { "assessment": { "bin": "writer-rate-check", "invocation": "single-json-input" } }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Onboarding discovers that declaration from the installed manifest and never
|
|
54
|
+
infers a surface. `writer-check` remains the multi-mode CLI and is not the
|
|
55
|
+
assessment surface. Writer is not a required first-day role; Advisor
|
|
56
|
+
remains the only required first-day assessment.
|
|
57
|
+
|
|
19
58
|
## Public entry points
|
|
20
59
|
|
|
21
60
|
- `@clossys/writer` exposes both voice and copy-record APIs.
|
|
@@ -388,6 +427,52 @@ this run is reported too, as a `"warning"`, since a stale exclusion (the
|
|
|
388
427
|
file it named was renamed or deleted) is otherwise indistinguishable from
|
|
389
428
|
one still doing real work.
|
|
390
429
|
|
|
430
|
+
## Machine-readable output: `writer-check --format json`
|
|
431
|
+
|
|
432
|
+
The default `writer-check` command takes `--format <text|json>`, defaulting to
|
|
433
|
+
`text`. Under `json` it prints exactly one object to stdout and nothing else —
|
|
434
|
+
every diagnostic that would normally go to stdout is suppressed, so the output
|
|
435
|
+
is always parseable — and writes nothing to stderr on a successful run.
|
|
436
|
+
|
|
437
|
+
The contract that matters is what the object carries, and when:
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
{
|
|
441
|
+
recordFile, scanDir,
|
|
442
|
+
verdict: "clean" | "findings" | "indeterminate",
|
|
443
|
+
exitCode: 0 | 1 | 2,
|
|
444
|
+
reason?, // present when the verdict needs explaining
|
|
445
|
+
filesScanned, candidatesScanned, matched,
|
|
446
|
+
findings: CopyGateFinding[],
|
|
447
|
+
ignored: CopyGateIgnored[],
|
|
448
|
+
unchecked: UncheckedItem[],
|
|
449
|
+
parseFailures: { file, detail }[]
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**`verdict`, `findings` and `unchecked` are always present together, on every
|
|
454
|
+
path** — the clean path, the findings path, and the total-failure path alike.
|
|
455
|
+
That is the whole point of the shape. A single construct the scanner cannot
|
|
456
|
+
classify makes the run `indeterminate` and exits `2`, but the findings it did
|
|
457
|
+
produce are still in `findings`, recoverable by any caller that reads past the
|
|
458
|
+
exit code.
|
|
459
|
+
|
|
460
|
+
That mattered concretely: before this shape existed, one unclassifiable JSX
|
|
461
|
+
comment between attributes forced exit `2` and discarded **292 real findings**
|
|
462
|
+
from the same scan (issue #753). The exit code and the verdict still refuse to
|
|
463
|
+
read as clean — an indeterminate must never look like a pass — but "I could
|
|
464
|
+
not evaluate THIS ITEM" is no longer collapsed into "I could not evaluate
|
|
465
|
+
ANYTHING."
|
|
466
|
+
|
|
467
|
+
A caller keying only on `exitCode` or `verdict` therefore never mistakes an
|
|
468
|
+
indeterminate run for a clean one; a caller that reads `findings` recovers
|
|
469
|
+
everything the run measured regardless of how it ended.
|
|
470
|
+
|
|
471
|
+
This mirrors `@clossys/inspector`'s `VerifyStandardsReport` (per-row verdict
|
|
472
|
+
plus one overall verdict and exit code) and `@clossys/observer`'s
|
|
473
|
+
`CoverageCellState` three-state union, rather than inventing a fourth
|
|
474
|
+
convention.
|
|
475
|
+
|
|
391
476
|
## Copy addressability — is prose resolved from the registry, or typed inline?
|
|
392
477
|
|
|
393
478
|
`checkCopyTraceability` above answers "does this literal match a registered
|
|
@@ -567,6 +652,10 @@ The root entry point exports the copy registry and traceability surface:
|
|
|
567
652
|
`PathExclusionFindingRule`, and `PathExclusionValidation`.
|
|
568
653
|
- Traceability: `checkCopyTraceability`, `CopyGateFinding`,
|
|
569
654
|
`CopyGateIgnored`, `CopyGateResult`, and `CopyGateRule`.
|
|
655
|
+
- Charter close metric: `assessApprovedCopyCoverageRate`,
|
|
656
|
+
`ApprovedCopyCoverageRateAssessment`, `ApprovedCopyCoverageRateFinding`,
|
|
657
|
+
and `ApprovedCopyCoverageRateState`. Not `checkCopyRecord` or
|
|
658
|
+
`checkCopyTraceability`.
|
|
570
659
|
- Addressability (see above): `scanAddressabilitySources`,
|
|
571
660
|
`extractAddressabilityCandidates`, `checkAddressability`,
|
|
572
661
|
`AddressabilityGateResult`, `AddressabilityPosition`,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare class WriterRateCliInputError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/** Reads caller-owned assessment evidence without treating it as validated. */
|
|
5
|
+
export declare function readWriterAssessmentJson(path: string): unknown;
|
|
6
|
+
/** Testable CLI dispatcher. Invalid arguments throw; the executable maps them to exit 2. */
|
|
7
|
+
export declare function main(argv: readonly string[]): number;
|
|
8
|
+
/** Resolves an npm/POSIX bin symlink before deciding whether this module is the entrypoint. */
|
|
9
|
+
export declare function isDirectInvocation(moduleUrl: string, argvPath: string | undefined): boolean;
|
|
10
|
+
//# sourceMappingURL=approved-copy-coverage-rate-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approved-copy-coverage-rate-cli.d.ts","sourceRoot":"","sources":["../src/approved-copy-coverage-rate-cli.ts"],"names":[],"mappings":";AAQA,qBAAa,uBAAwB,SAAQ,KAAK;CAAG;AAErD,+EAA+E;AAC/E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAU9D;AAED,4FAA4F;AAC5F,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CASpD;AAWD,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAO3F"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { assessApprovedCopyCoverageRate } from "./approved-copy-coverage-rate.js";
|
|
6
|
+
const USAGE = `Usage: writer-rate-check <assessment.json>\n\nAssess approved copy coverage rate from consumer-supplied independent observations.\nExit codes: 0 = satisfied, 1 = violated, 2 = indeterminate or unreadable.\nwriter-check remains the multi-mode CLI and is not this assessment.`;
|
|
7
|
+
export class WriterRateCliInputError extends Error {
|
|
8
|
+
}
|
|
9
|
+
/** Reads caller-owned assessment evidence without treating it as validated. */
|
|
10
|
+
export function readWriterAssessmentJson(path) {
|
|
11
|
+
const resolved = resolve(path);
|
|
12
|
+
if (!existsSync(resolved))
|
|
13
|
+
throw new WriterRateCliInputError(`assessment file "${path}" does not exist`);
|
|
14
|
+
try {
|
|
15
|
+
if (!statSync(resolved).isFile())
|
|
16
|
+
throw new WriterRateCliInputError(`assessment file "${path}" is not a file`);
|
|
17
|
+
return JSON.parse(readFileSync(resolved, "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch (cause) {
|
|
20
|
+
if (cause instanceof WriterRateCliInputError)
|
|
21
|
+
throw cause;
|
|
22
|
+
throw new WriterRateCliInputError(`assessment file "${path}" is unreadable JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Testable CLI dispatcher. Invalid arguments throw; the executable maps them to exit 2. */
|
|
26
|
+
export function main(argv) {
|
|
27
|
+
if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
|
|
28
|
+
console.log(USAGE);
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
if (argv.length !== 1)
|
|
32
|
+
throw new WriterRateCliInputError("exactly one assessment.json file is required");
|
|
33
|
+
const report = assessApprovedCopyCoverageRate(readWriterAssessmentJson(argv[0]));
|
|
34
|
+
console.log(JSON.stringify(report, null, 2));
|
|
35
|
+
return report.state === "satisfied" ? 0 : report.state === "violated" ? 1 : 2;
|
|
36
|
+
}
|
|
37
|
+
function run() {
|
|
38
|
+
try {
|
|
39
|
+
process.exitCode = main(process.argv.slice(2));
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
console.error(`writer-rate-check: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
43
|
+
process.exitCode = 2;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Resolves an npm/POSIX bin symlink before deciding whether this module is the entrypoint. */
|
|
47
|
+
export function isDirectInvocation(moduleUrl, argvPath) {
|
|
48
|
+
if (argvPath === undefined)
|
|
49
|
+
return false;
|
|
50
|
+
try {
|
|
51
|
+
return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(resolve(argvPath));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (isDirectInvocation(import.meta.url, process.argv[1]))
|
|
58
|
+
run();
|
|
59
|
+
//# sourceMappingURL=approved-copy-coverage-rate-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approved-copy-coverage-rate-cli.js","sourceRoot":"","sources":["../src/approved-copy-coverage-rate-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ApprovedCopyCoverageRateState = "satisfied" | "violated" | "indeterminate";
|
|
2
|
+
export interface ApprovedCopyCoverageRateFinding {
|
|
3
|
+
readonly rule: string;
|
|
4
|
+
readonly severity: "error";
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly path?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ApprovedCopyCoverageRateAssessment {
|
|
9
|
+
readonly metric: "approved copy coverage rate";
|
|
10
|
+
readonly state: ApprovedCopyCoverageRateState;
|
|
11
|
+
readonly rate: number | null;
|
|
12
|
+
readonly evaluatedCopy: number;
|
|
13
|
+
readonly approvedCoveredCopy: number;
|
|
14
|
+
readonly findings: readonly ApprovedCopyCoverageRateFinding[];
|
|
15
|
+
readonly proposedPositions: readonly unknown[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Computes `approved copy coverage rate`: shipped audience-facing copy
|
|
19
|
+
* resolved from approved traceable registry entries / all shipped
|
|
20
|
+
* audience-facing copy evaluated.
|
|
21
|
+
*/
|
|
22
|
+
export declare function assessApprovedCopyCoverageRate(input: unknown): ApprovedCopyCoverageRateAssessment;
|
|
23
|
+
//# sourceMappingURL=approved-copy-coverage-rate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approved-copy-coverage-rate.d.ts","sourceRoot":"","sources":["../src/approved-copy-coverage-rate.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,6BAA6B,GAAG,WAAW,GAAG,UAAU,GAAG,eAAe,CAAC;AAEvF,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,MAAM,EAAE,6BAA6B,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,6BAA6B,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,+BAA+B,EAAE,CAAC;IAC9D,QAAQ,CAAC,iBAAiB,EAAE,SAAS,OAAO,EAAE,CAAC;CAChD;AAyDD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,kCAAkC,CA4GjG"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes the Writer charter metric `approved copy coverage rate` from
|
|
3
|
+
* consumer-supplied independent observations.
|
|
4
|
+
*
|
|
5
|
+
* This module does not invent observations and does not relabel
|
|
6
|
+
* `checkCopyRecord` or `checkCopyTraceability`. An empty evaluated set is
|
|
7
|
+
* indeterminate, never a perfect rate of 1. `writer-check` remains the
|
|
8
|
+
* multi-mode CLI.
|
|
9
|
+
*/
|
|
10
|
+
const METRIC = "approved copy coverage rate";
|
|
11
|
+
const RESERVED_OBSERVERS = new Set(["writer", "@clossys/writer"]);
|
|
12
|
+
const PROPOSED_POSITIONS = [];
|
|
13
|
+
function record(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function text(value) {
|
|
17
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
18
|
+
}
|
|
19
|
+
function timestamp(value) {
|
|
20
|
+
return text(value) && !Number.isNaN(Date.parse(value));
|
|
21
|
+
}
|
|
22
|
+
function finding(rule, message, path) {
|
|
23
|
+
return path === undefined ? { rule, severity: "error", message } : { rule, severity: "error", message, path };
|
|
24
|
+
}
|
|
25
|
+
function evidenceCounts(value) {
|
|
26
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
27
|
+
return false;
|
|
28
|
+
return value.every((item) => record(item) && text(item.id) && text(item.description));
|
|
29
|
+
}
|
|
30
|
+
function reservedObserver(value) {
|
|
31
|
+
return RESERVED_OBSERVERS.has(value.trim().toLowerCase());
|
|
32
|
+
}
|
|
33
|
+
function report(state, evaluatedCopy, approvedCoveredCopy, rate, findings) {
|
|
34
|
+
return {
|
|
35
|
+
metric: METRIC,
|
|
36
|
+
state,
|
|
37
|
+
rate,
|
|
38
|
+
evaluatedCopy,
|
|
39
|
+
approvedCoveredCopy,
|
|
40
|
+
findings,
|
|
41
|
+
proposedPositions: PROPOSED_POSITIONS,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function isCovered(entry) {
|
|
45
|
+
return entry.approved && entry.traceable && entry.resolvedFromRegistry;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Computes `approved copy coverage rate`: shipped audience-facing copy
|
|
49
|
+
* resolved from approved traceable registry entries / all shipped
|
|
50
|
+
* audience-facing copy evaluated.
|
|
51
|
+
*/
|
|
52
|
+
export function assessApprovedCopyCoverageRate(input) {
|
|
53
|
+
if (!record(input)) {
|
|
54
|
+
return report("indeterminate", 0, 0, null, [finding("assessment-shape", "Assessment input must be an object.", "$")]);
|
|
55
|
+
}
|
|
56
|
+
const findings = [];
|
|
57
|
+
if (!timestamp(input.asOf)) {
|
|
58
|
+
findings.push(finding("assessment-as-of", "asOf must be an interpretable timestamp.", "asOf"));
|
|
59
|
+
}
|
|
60
|
+
const declaredCopy = input.declaredCopy;
|
|
61
|
+
if (!Array.isArray(declaredCopy)) {
|
|
62
|
+
findings.push(finding("declared-copy-required", "declaredCopy must be an array of { id } objects.", "declaredCopy"));
|
|
63
|
+
return report("indeterminate", 0, 0, null, findings);
|
|
64
|
+
}
|
|
65
|
+
const declaredIds = [];
|
|
66
|
+
const declaredSet = new Set();
|
|
67
|
+
declaredCopy.forEach((item, index) => {
|
|
68
|
+
const path = `declaredCopy[${index}]`;
|
|
69
|
+
if (!record(item) || !text(item.id)) {
|
|
70
|
+
findings.push(finding("copy-id-required", "id must be a non-empty string.", `${path}.id`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (declaredSet.has(item.id)) {
|
|
74
|
+
findings.push(finding("duplicate-copy-id", `Duplicate id "${item.id}".`, `${path}.id`));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
declaredSet.add(item.id);
|
|
78
|
+
declaredIds.push(item.id);
|
|
79
|
+
});
|
|
80
|
+
if (declaredIds.length === 0) {
|
|
81
|
+
findings.push(finding("declared-copy-empty", "No declared copy is available for the approved-copy-coverage metric; the rate is indeterminate, never 1.", "declaredCopy"));
|
|
82
|
+
}
|
|
83
|
+
const observations = input.observations;
|
|
84
|
+
if (observations !== undefined && !Array.isArray(observations)) {
|
|
85
|
+
findings.push(finding("observations-shape", "observations must be an array.", "observations"));
|
|
86
|
+
}
|
|
87
|
+
const counting = new Map();
|
|
88
|
+
if (Array.isArray(observations)) {
|
|
89
|
+
observations.forEach((item, index) => {
|
|
90
|
+
const path = `observations[${index}]`;
|
|
91
|
+
if (!record(item)) {
|
|
92
|
+
findings.push(finding("observation-shape", "An observation must be an object.", path));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (text(item.observerRef) && reservedObserver(item.observerRef)) {
|
|
96
|
+
findings.push(finding("self-observation", "Writer cannot observe itself; reserved observerRef values are not independent.", `${path}.observerRef`));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (text(item.copyId) && !declaredSet.has(item.copyId)) {
|
|
100
|
+
findings.push(finding("unknown-copy", `Observation copyId "${item.copyId}" is not declared copy.`, `${path}.copyId`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (item.independent !== true)
|
|
104
|
+
return;
|
|
105
|
+
if (typeof item.approved !== "boolean" ||
|
|
106
|
+
typeof item.traceable !== "boolean" ||
|
|
107
|
+
typeof item.resolvedFromRegistry !== "boolean")
|
|
108
|
+
return;
|
|
109
|
+
if (!text(item.observerRef) || !evidenceCounts(item.evidence) || !text(item.copyId) || !declaredSet.has(item.copyId))
|
|
110
|
+
return;
|
|
111
|
+
const bucket = counting.get(item.copyId) ?? [];
|
|
112
|
+
bucket.push({
|
|
113
|
+
approved: item.approved,
|
|
114
|
+
traceable: item.traceable,
|
|
115
|
+
resolvedFromRegistry: item.resolvedFromRegistry,
|
|
116
|
+
});
|
|
117
|
+
counting.set(item.copyId, bucket);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
let evaluatedCopy = 0;
|
|
121
|
+
let approvedCoveredCopy = 0;
|
|
122
|
+
for (const id of declaredIds) {
|
|
123
|
+
const observed = counting.get(id) ?? [];
|
|
124
|
+
if (observed.length === 0) {
|
|
125
|
+
findings.push(finding("copy-unevaluated", `Declared copy "${id}" has no counting independent observation.`, `declaredCopy.${id}`));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
evaluatedCopy += 1;
|
|
129
|
+
const allCovered = observed.every((entry) => isCovered(entry));
|
|
130
|
+
if (allCovered)
|
|
131
|
+
approvedCoveredCopy += 1;
|
|
132
|
+
else {
|
|
133
|
+
findings.push(finding("copy-not-covered", `Declared copy "${id}" was independently observed not resolved from an approved traceable registry entry.`, `declaredCopy.${id}`));
|
|
134
|
+
}
|
|
135
|
+
if (observed.length > 1) {
|
|
136
|
+
const first = observed[0];
|
|
137
|
+
const disagree = observed.some((entry) => entry.approved !== first.approved ||
|
|
138
|
+
entry.traceable !== first.traceable ||
|
|
139
|
+
entry.resolvedFromRegistry !== first.resolvedFromRegistry);
|
|
140
|
+
if (disagree) {
|
|
141
|
+
findings.push(finding("observation-disagreement", `Counting observations for "${id}" disagree.`, `observations.${id}`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const rate = evaluatedCopy === 0 ? null : approvedCoveredCopy / evaluatedCopy;
|
|
146
|
+
if (evaluatedCopy === 0)
|
|
147
|
+
return report("indeterminate", 0, 0, null, findings);
|
|
148
|
+
const coverageComplete = evaluatedCopy === declaredIds.length;
|
|
149
|
+
const noErrorFindings = findings.length === 0;
|
|
150
|
+
const state = coverageComplete && approvedCoveredCopy === evaluatedCopy && noErrorFindings ? "satisfied" : "violated";
|
|
151
|
+
return report(state, evaluatedCopy, approvedCoveredCopy, rate, findings);
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=approved-copy-coverage-rate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approved-copy-coverage-rate.js","sourceRoot":"","sources":["../src/approved-copy-coverage-rate.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -120,6 +120,8 @@
|
|
|
120
120
|
* anyway so the accounting/exit-code contract stays correct for a future
|
|
121
121
|
* CLI flag without a second pass over this logic.
|
|
122
122
|
*/
|
|
123
|
+
import { type CopyGateFinding, type CopyGateIgnored } from "./copy-gate.js";
|
|
124
|
+
import { type UncheckedItem } from "./scan.js";
|
|
123
125
|
/** Exported for `cli.test.ts` — anything wrong with the arguments themselves always maps to exit code 2, never 1. */
|
|
124
126
|
export declare class CliInputError extends Error {
|
|
125
127
|
}
|
|
@@ -131,6 +133,59 @@ export declare class CliInputError extends Error {
|
|
|
131
133
|
* that reason — `run()` below is the only caller that reads the real
|
|
132
134
|
* `process.argv`.
|
|
133
135
|
*/
|
|
136
|
+
/**
|
|
137
|
+
* The default command's own structured, machine-readable report — issue
|
|
138
|
+
* #753's second half. `printGateReport`/`printScanAccounting` already put
|
|
139
|
+
* every finding a run DID produce ahead of any exit-code decision in the
|
|
140
|
+
* text output; the gap #753 found is that a consumer keying on the exit
|
|
141
|
+
* code alone (2 = indeterminate) cannot reach those findings at all,
|
|
142
|
+
* because nothing besides that human-readable text carries them. This
|
|
143
|
+
* type is that carrier: `--format json` prints EXACTLY ONE of these,
|
|
144
|
+
* nothing else, to stdout, whatever `exitCode` turns out to be — the same
|
|
145
|
+
* "one parseable object regardless of verdict" contract `inspector`'s own
|
|
146
|
+
* `VerifyStandardsReport`/`--format json` already publishes (see
|
|
147
|
+
* `packages/inspector/src/cli.ts`), and the same shape `observer`'s
|
|
148
|
+
* fleet-coverage grader and `addressability.ts`'s own `AddressabilityGateResult`
|
|
149
|
+
* use: a per-item verdict (here, each `CopyGateFinding`/`UncheckedItem`) is
|
|
150
|
+
* a DIFFERENT question from the run's one overall verdict, and collapsing
|
|
151
|
+
* the two into a bare exit code is exactly what discarded 292 real
|
|
152
|
+
* findings alongside one unclassifiable construct in #753's own repro.
|
|
153
|
+
*
|
|
154
|
+
* `verdict` is the SAME three states this whole package uses everywhere
|
|
155
|
+
* else (`"clean" | "findings" | "indeterminate"`), computed with the
|
|
156
|
+
* identical precedence `main()`'s own exit-code decision already applies
|
|
157
|
+
* below — `unchecked.length > 0` wins over `findings.length > 0`, which
|
|
158
|
+
* wins over a clean pass. Never a fourth state, and never inferred
|
|
159
|
+
* separately from `exitCode` by a consumer — read `verdict` (or
|
|
160
|
+
* `exitCode`) and treat `findings`/`unchecked` as what was actually
|
|
161
|
+
* measured, present or empty independent of which verdict resulted.
|
|
162
|
+
*
|
|
163
|
+
* `reason` is populated ONLY for the "could not run at all" shape of
|
|
164
|
+
* indeterminate — a missing/invalid copy record, an untrustworthy
|
|
165
|
+
* exclusion list, or a scan that matched/parsed nothing — where `findings`
|
|
166
|
+
* and `unchecked` are necessarily both `[]` because `checkCopyTraceability`
|
|
167
|
+
* never ran. It is `undefined` whenever a real scan DID run, including the
|
|
168
|
+
* `"indeterminate"` case #753 is about (real `findings`, plus one or more
|
|
169
|
+
* `unchecked` entries) — that case needs no extra prose beyond the data
|
|
170
|
+
* itself, which is the entire point of shipping it as data.
|
|
171
|
+
*/
|
|
172
|
+
export interface CopyTraceabilityReport {
|
|
173
|
+
recordFile: string;
|
|
174
|
+
scanDir: string;
|
|
175
|
+
verdict: "clean" | "findings" | "indeterminate";
|
|
176
|
+
exitCode: 0 | 1 | 2;
|
|
177
|
+
reason?: string;
|
|
178
|
+
filesScanned: number;
|
|
179
|
+
candidatesScanned: number;
|
|
180
|
+
matched: number;
|
|
181
|
+
findings: CopyGateFinding[];
|
|
182
|
+
ignored: CopyGateIgnored[];
|
|
183
|
+
unchecked: UncheckedItem[];
|
|
184
|
+
parseFailures: {
|
|
185
|
+
file: string;
|
|
186
|
+
detail: string;
|
|
187
|
+
}[];
|
|
188
|
+
}
|
|
134
189
|
export declare function main(argv: string[]): number;
|
|
135
190
|
/**
|
|
136
191
|
* Exported (unlike a typical CLI `main`) for the identical reason `main`
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwHG;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwHG;AAWH,OAAO,EAAyB,KAAK,eAAe,EAAE,KAAK,eAAe,EAAuB,MAAM,gBAAgB,CAAC;AAIxH,OAAO,EAAuC,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAkEpF,qHAAqH;AACrH,qBAAa,aAAc,SAAQ,KAAK;CAAG;AAsgB3C;;;;;;;GAOG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,eAAe,CAAC;IAChD,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,aAAa,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACnD;AAmBD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAyL3C;AAsGD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAqCxD;AAkFD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CA6B9D"}
|