@clossys/writer 0.3.8 → 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 CHANGED
@@ -3,6 +3,29 @@
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
+
6
29
  ## [0.3.8] - 2026-09-16
7
30
 
8
31
  ### Added
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.
@@ -613,6 +652,10 @@ The root entry point exports the copy registry and traceability surface:
613
652
  `PathExclusionFindingRule`, and `PathExclusionValidation`.
614
653
  - Traceability: `checkCopyTraceability`, `CopyGateFinding`,
615
654
  `CopyGateIgnored`, `CopyGateResult`, and `CopyGateRule`.
655
+ - Charter close metric: `assessApprovedCopyCoverageRate`,
656
+ `ApprovedCopyCoverageRateAssessment`, `ApprovedCopyCoverageRateFinding`,
657
+ and `ApprovedCopyCoverageRateState`. Not `checkCopyRecord` or
658
+ `checkCopyTraceability`.
616
659
  - Addressability (see above): `scanAddressabilitySources`,
617
660
  `extractAddressabilityCandidates`, `checkAddressability`,
618
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/index.d.ts CHANGED
@@ -30,6 +30,8 @@ export type { CopyResolveIssue, CopyResolveIssueReason, CopyResolveResult } from
30
30
  export { readCopyRecord } from "./registry.js";
31
31
  export type { CopyRegistryReadIssue, CopyRegistryReadIssueReason, CopyRegistryReadResult } from "./registry.js";
32
32
  export { checkCopyRecord } from "./checker.js";
33
+ export { assessApprovedCopyCoverageRate } from "./approved-copy-coverage-rate.js";
34
+ export type { ApprovedCopyCoverageRateAssessment, ApprovedCopyCoverageRateFinding, ApprovedCopyCoverageRateState, } from "./approved-copy-coverage-rate.js";
33
35
  export type { CopyEntryCheckResult, CopyEntrySkip, CopyRecordCheckOptions, CopyRecordCheckReport, CopyRecordFinding, CopyRecordWaivedFinding, } from "./checker.js";
34
36
  export * from "./voice/index.js";
35
37
  export { extractCopyCandidates, PLACEHOLDER_SENTINEL, scanCopySourceTree } from "./scan.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,YAAY,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,WAAW,EACX,UAAU,EACV,OAAO,EACP,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,UAAU,EACV,yBAAyB,EACzB,SAAS,EACT,UAAU,GACX,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEtF,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAErH,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEhG,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEhH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AAKtB,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC5F,YAAY,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,aAAa,GACd,MAAM,WAAW,CAAC;AAOnB,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EACV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAMrG,OAAO,EACL,mBAAmB,EACnB,+BAA+B,EAC/B,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,EAC3B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,OAAO,EACP,qBAAqB,EACrB,0BAA0B,EAC1B,cAAc,EACd,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,wBAAwB,EACxB,8BAA8B,EAC9B,yBAAyB,EACzB,oBAAoB,EACpB,uBAAuB,EACvB,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,YAAY,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,WAAW,EACX,UAAU,EACV,OAAO,EACP,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,UAAU,EACV,yBAAyB,EACzB,SAAS,EACT,UAAU,GACX,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEtF,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAErH,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEhG,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEhH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAClF,YAAY,EACV,kCAAkC,EAClC,+BAA+B,EAC/B,6BAA6B,GAC9B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AAKtB,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC5F,YAAY,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,aAAa,GACd,MAAM,WAAW,CAAC;AAOnB,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EACV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAMrG,OAAO,EACL,mBAAmB,EACnB,+BAA+B,EAC/B,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,EAC3B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,OAAO,EACP,qBAAqB,EACrB,0BAA0B,EAC1B,cAAc,EACd,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,wBAAwB,EACxB,8BAA8B,EAC9B,yBAAyB,EACzB,oBAAoB,EACpB,uBAAuB,EACvB,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ export { parseCopyRecord, parseCopyRegistry, validateCopyRecordShape, validateCo
30
30
  export { createCopyResolver, resolveCopyRef } from "./resolve.js";
31
31
  export { readCopyRecord } from "./registry.js";
32
32
  export { checkCopyRecord } from "./checker.js";
33
+ export { assessApprovedCopyCoverageRate } from "./approved-copy-coverage-rate.js";
33
34
  // Voice is a first-class part of copy. It is available from this root entry
34
35
  // point for a single-package experience and from the focused `copy/voice`
35
36
  // subpath for consumers that only need the verbal contract.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAmBH,kEAAkE;AAClE,0EAA0E;AAC1E,4EAA4E;AAC5E,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEtF,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAErH,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAU/C,4EAA4E;AAC5E,0EAA0E;AAC1E,4DAA4D;AAC5D,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAa5F,kEAAkE;AAClE,2EAA2E;AAC3E,mEAAmE;AACnE,sEAAsE;AACtE,aAAa;AACb,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAS9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAGvD,mEAAmE;AACnE,wEAAwE;AACxE,qEAAqE;AACrE,8BAA8B;AAC9B,OAAO,EACL,mBAAmB,EACnB,+BAA+B,EAC/B,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAW7B,2EAA2E;AAC3E,0EAA0E;AAC1E,0EAA0E;AAC1E,iEAAiE;AACjE,oEAAoE;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAQ3D,uEAAuE;AACvE,gEAAgE;AAChE,yEAAyE;AACzE,+DAA+D;AAC/D,sEAAsE;AACtE,kDAAkD;AAClD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAmBH,kEAAkE;AAClE,0EAA0E;AAC1E,4EAA4E;AAC5E,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEtF,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAErH,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAelF,4EAA4E;AAC5E,0EAA0E;AAC1E,4DAA4D;AAC5D,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAa5F,kEAAkE;AAClE,2EAA2E;AAC3E,mEAAmE;AACnE,sEAAsE;AACtE,aAAa;AACb,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAS9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAGvD,mEAAmE;AACnE,wEAAwE;AACxE,qEAAqE;AACrE,8BAA8B;AAC9B,OAAO,EACL,mBAAmB,EACnB,+BAA+B,EAC/B,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAW7B,2EAA2E;AAC3E,0EAA0E;AAC1E,0EAA0E;AAC1E,iEAAiE;AACjE,oEAAoE;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAQ3D,uEAAuE;AACvE,gEAAgE;AAChE,yEAAyE;AACzE,+DAA+D;AAC/D,sEAAsE;AACtE,kDAAkD;AAClD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,cAAc,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clossys/writer",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,8 @@
19
19
  "./voice-record.template.jsonc": "./templates/voice-record.template.jsonc"
20
20
  },
21
21
  "bin": {
22
- "writer-check": "dist/cli.js"
22
+ "writer-check": "dist/cli.js",
23
+ "writer-rate-check": "dist/approved-copy-coverage-rate-cli.js"
23
24
  },
24
25
  "files": [
25
26
  "dist",
@@ -31,6 +32,12 @@
31
32
  "LICENSE"
32
33
  ],
33
34
  "sideEffects": false,
35
+ "foundry": {
36
+ "assessment": {
37
+ "bin": "writer-rate-check",
38
+ "invocation": "single-json-input"
39
+ }
40
+ },
34
41
  "description": "The writer role: is it well said? A consumer-owned language system: voice rules, glossary and claims validation, addressable copy records, source traceability, locale coverage, and passage-layer composition — five gates reachable from one bin. Ships machinery and templates, never a consumer's actual copy or voice.",
35
42
  "keywords": [
36
43
  "writer",
@@ -0,0 +1,55 @@
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
+
7
+ 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.`;
8
+
9
+ export class WriterRateCliInputError extends Error {}
10
+
11
+ /** Reads caller-owned assessment evidence without treating it as validated. */
12
+ export function readWriterAssessmentJson(path: string): unknown {
13
+ const resolved = resolve(path);
14
+ if (!existsSync(resolved)) throw new WriterRateCliInputError(`assessment file "${path}" does not exist`);
15
+ try {
16
+ if (!statSync(resolved).isFile()) throw new WriterRateCliInputError(`assessment file "${path}" is not a file`);
17
+ return JSON.parse(readFileSync(resolved, "utf8"));
18
+ } catch (cause) {
19
+ if (cause instanceof WriterRateCliInputError) throw cause;
20
+ throw new WriterRateCliInputError(`assessment file "${path}" is unreadable JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
21
+ }
22
+ }
23
+
24
+ /** Testable CLI dispatcher. Invalid arguments throw; the executable maps them to exit 2. */
25
+ export function main(argv: readonly string[]): number {
26
+ if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
27
+ console.log(USAGE);
28
+ return 0;
29
+ }
30
+ if (argv.length !== 1) throw new WriterRateCliInputError("exactly one assessment.json file is required");
31
+ const report = assessApprovedCopyCoverageRate(readWriterAssessmentJson(argv[0] as string));
32
+ console.log(JSON.stringify(report, null, 2));
33
+ return report.state === "satisfied" ? 0 : report.state === "violated" ? 1 : 2;
34
+ }
35
+
36
+ function run(): void {
37
+ try {
38
+ process.exitCode = main(process.argv.slice(2));
39
+ } catch (cause) {
40
+ console.error(`writer-rate-check: ${cause instanceof Error ? cause.message : String(cause)}`);
41
+ process.exitCode = 2;
42
+ }
43
+ }
44
+
45
+ /** Resolves an npm/POSIX bin symlink before deciding whether this module is the entrypoint. */
46
+ export function isDirectInvocation(moduleUrl: string, argvPath: string | undefined): boolean {
47
+ if (argvPath === undefined) return false;
48
+ try {
49
+ return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(resolve(argvPath));
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ if (isDirectInvocation(import.meta.url, process.argv[1])) run();
@@ -0,0 +1,201 @@
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" as const;
11
+ const RESERVED_OBSERVERS = new Set(["writer", "@clossys/writer"]);
12
+ const PROPOSED_POSITIONS: readonly unknown[] = [];
13
+
14
+ export type ApprovedCopyCoverageRateState = "satisfied" | "violated" | "indeterminate";
15
+
16
+ export interface ApprovedCopyCoverageRateFinding {
17
+ readonly rule: string;
18
+ readonly severity: "error";
19
+ readonly message: string;
20
+ readonly path?: string;
21
+ }
22
+
23
+ export interface ApprovedCopyCoverageRateAssessment {
24
+ readonly metric: "approved copy coverage rate";
25
+ readonly state: ApprovedCopyCoverageRateState;
26
+ readonly rate: number | null;
27
+ readonly evaluatedCopy: number;
28
+ readonly approvedCoveredCopy: number;
29
+ readonly findings: readonly ApprovedCopyCoverageRateFinding[];
30
+ readonly proposedPositions: readonly unknown[];
31
+ }
32
+
33
+ type UnknownRecord = Record<string, unknown>;
34
+
35
+ function record(value: unknown): value is UnknownRecord {
36
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37
+ }
38
+
39
+ function text(value: unknown): value is string {
40
+ return typeof value === "string" && value.trim().length > 0;
41
+ }
42
+
43
+ function timestamp(value: unknown): value is string {
44
+ return text(value) && !Number.isNaN(Date.parse(value));
45
+ }
46
+
47
+ function finding(rule: string, message: string, path?: string): ApprovedCopyCoverageRateFinding {
48
+ return path === undefined ? { rule, severity: "error", message } : { rule, severity: "error", message, path };
49
+ }
50
+
51
+ function evidenceCounts(value: unknown): boolean {
52
+ if (!Array.isArray(value) || value.length === 0) return false;
53
+ return value.every((item) => record(item) && text(item.id) && text(item.description));
54
+ }
55
+
56
+ function reservedObserver(value: string): boolean {
57
+ return RESERVED_OBSERVERS.has(value.trim().toLowerCase());
58
+ }
59
+
60
+ function report(
61
+ state: ApprovedCopyCoverageRateState,
62
+ evaluatedCopy: number,
63
+ approvedCoveredCopy: number,
64
+ rate: number | null,
65
+ findings: readonly ApprovedCopyCoverageRateFinding[],
66
+ ): ApprovedCopyCoverageRateAssessment {
67
+ return {
68
+ metric: METRIC,
69
+ state,
70
+ rate,
71
+ evaluatedCopy,
72
+ approvedCoveredCopy,
73
+ findings,
74
+ proposedPositions: PROPOSED_POSITIONS,
75
+ };
76
+ }
77
+
78
+ interface CountingObservation {
79
+ readonly approved: boolean;
80
+ readonly traceable: boolean;
81
+ readonly resolvedFromRegistry: boolean;
82
+ }
83
+
84
+ function isCovered(entry: CountingObservation): boolean {
85
+ return entry.approved && entry.traceable && entry.resolvedFromRegistry;
86
+ }
87
+
88
+ /**
89
+ * Computes `approved copy coverage rate`: shipped audience-facing copy
90
+ * resolved from approved traceable registry entries / all shipped
91
+ * audience-facing copy evaluated.
92
+ */
93
+ export function assessApprovedCopyCoverageRate(input: unknown): ApprovedCopyCoverageRateAssessment {
94
+ if (!record(input)) {
95
+ return report("indeterminate", 0, 0, null, [finding("assessment-shape", "Assessment input must be an object.", "$")]);
96
+ }
97
+
98
+ const findings: ApprovedCopyCoverageRateFinding[] = [];
99
+ if (!timestamp(input.asOf)) {
100
+ findings.push(finding("assessment-as-of", "asOf must be an interpretable timestamp.", "asOf"));
101
+ }
102
+
103
+ const declaredCopy = input.declaredCopy;
104
+ if (!Array.isArray(declaredCopy)) {
105
+ findings.push(finding("declared-copy-required", "declaredCopy must be an array of { id } objects.", "declaredCopy"));
106
+ return report("indeterminate", 0, 0, null, findings);
107
+ }
108
+
109
+ const declaredIds: string[] = [];
110
+ const declaredSet = new Set<string>();
111
+ declaredCopy.forEach((item, index) => {
112
+ const path = `declaredCopy[${index}]`;
113
+ if (!record(item) || !text(item.id)) {
114
+ findings.push(finding("copy-id-required", "id must be a non-empty string.", `${path}.id`));
115
+ return;
116
+ }
117
+ if (declaredSet.has(item.id)) {
118
+ findings.push(finding("duplicate-copy-id", `Duplicate id "${item.id}".`, `${path}.id`));
119
+ return;
120
+ }
121
+ declaredSet.add(item.id);
122
+ declaredIds.push(item.id);
123
+ });
124
+
125
+ if (declaredIds.length === 0) {
126
+ findings.push(finding("declared-copy-empty", "No declared copy is available for the approved-copy-coverage metric; the rate is indeterminate, never 1.", "declaredCopy"));
127
+ }
128
+
129
+ const observations = input.observations;
130
+ if (observations !== undefined && !Array.isArray(observations)) {
131
+ findings.push(finding("observations-shape", "observations must be an array.", "observations"));
132
+ }
133
+
134
+ const counting = new Map<string, CountingObservation[]>();
135
+ if (Array.isArray(observations)) {
136
+ observations.forEach((item, index) => {
137
+ const path = `observations[${index}]`;
138
+ if (!record(item)) {
139
+ findings.push(finding("observation-shape", "An observation must be an object.", path));
140
+ return;
141
+ }
142
+ if (text(item.observerRef) && reservedObserver(item.observerRef)) {
143
+ findings.push(finding("self-observation", "Writer cannot observe itself; reserved observerRef values are not independent.", `${path}.observerRef`));
144
+ return;
145
+ }
146
+ if (text(item.copyId) && !declaredSet.has(item.copyId)) {
147
+ findings.push(finding("unknown-copy", `Observation copyId "${item.copyId}" is not declared copy.`, `${path}.copyId`));
148
+ return;
149
+ }
150
+ if (item.independent !== true) return;
151
+ if (
152
+ typeof item.approved !== "boolean" ||
153
+ typeof item.traceable !== "boolean" ||
154
+ typeof item.resolvedFromRegistry !== "boolean"
155
+ ) return;
156
+ if (!text(item.observerRef) || !evidenceCounts(item.evidence) || !text(item.copyId) || !declaredSet.has(item.copyId)) return;
157
+ const bucket = counting.get(item.copyId) ?? [];
158
+ bucket.push({
159
+ approved: item.approved,
160
+ traceable: item.traceable,
161
+ resolvedFromRegistry: item.resolvedFromRegistry,
162
+ });
163
+ counting.set(item.copyId, bucket);
164
+ });
165
+ }
166
+
167
+ let evaluatedCopy = 0;
168
+ let approvedCoveredCopy = 0;
169
+ for (const id of declaredIds) {
170
+ const observed = counting.get(id) ?? [];
171
+ if (observed.length === 0) {
172
+ findings.push(finding("copy-unevaluated", `Declared copy "${id}" has no counting independent observation.`, `declaredCopy.${id}`));
173
+ continue;
174
+ }
175
+ evaluatedCopy += 1;
176
+ const allCovered = observed.every((entry) => isCovered(entry));
177
+ if (allCovered) approvedCoveredCopy += 1;
178
+ else {
179
+ findings.push(finding("copy-not-covered", `Declared copy "${id}" was independently observed not resolved from an approved traceable registry entry.`, `declaredCopy.${id}`));
180
+ }
181
+ if (observed.length > 1) {
182
+ const first = observed[0]!;
183
+ const disagree = observed.some((entry) =>
184
+ entry.approved !== first.approved ||
185
+ entry.traceable !== first.traceable ||
186
+ entry.resolvedFromRegistry !== first.resolvedFromRegistry
187
+ );
188
+ if (disagree) {
189
+ findings.push(finding("observation-disagreement", `Counting observations for "${id}" disagree.`, `observations.${id}`));
190
+ }
191
+ }
192
+ }
193
+
194
+ const rate = evaluatedCopy === 0 ? null : approvedCoveredCopy / evaluatedCopy;
195
+ if (evaluatedCopy === 0) return report("indeterminate", 0, 0, null, findings);
196
+ const coverageComplete = evaluatedCopy === declaredIds.length;
197
+ const noErrorFindings = findings.length === 0;
198
+ const state: ApprovedCopyCoverageRateState =
199
+ coverageComplete && approvedCoveredCopy === evaluatedCopy && noErrorFindings ? "satisfied" : "violated";
200
+ return report(state, evaluatedCopy, approvedCoveredCopy, rate, findings);
201
+ }
package/src/index.ts CHANGED
@@ -54,6 +54,12 @@ export { readCopyRecord } from "./registry.js";
54
54
  export type { CopyRegistryReadIssue, CopyRegistryReadIssueReason, CopyRegistryReadResult } from "./registry.js";
55
55
 
56
56
  export { checkCopyRecord } from "./checker.js";
57
+ export { assessApprovedCopyCoverageRate } from "./approved-copy-coverage-rate.js";
58
+ export type {
59
+ ApprovedCopyCoverageRateAssessment,
60
+ ApprovedCopyCoverageRateFinding,
61
+ ApprovedCopyCoverageRateState,
62
+ } from "./approved-copy-coverage-rate.js";
57
63
  export type {
58
64
  CopyEntryCheckResult,
59
65
  CopyEntrySkip,