@clossys/writer 0.4.0 → 0.5.1

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.
Files changed (119) hide show
  1. package/README.md +592 -2
  2. package/dist/approval-bypass.d.ts +120 -0
  3. package/dist/approval-bypass.d.ts.map +1 -0
  4. package/dist/approval-bypass.js +2318 -0
  5. package/dist/approval-bypass.js.map +1 -0
  6. package/dist/approval.d.ts +62 -0
  7. package/dist/approval.d.ts.map +1 -0
  8. package/dist/approval.js +421 -0
  9. package/dist/approval.js.map +1 -0
  10. package/dist/approve-cli.d.ts +82 -0
  11. package/dist/approve-cli.d.ts.map +1 -0
  12. package/dist/approve-cli.js +430 -0
  13. package/dist/approve-cli.js.map +1 -0
  14. package/dist/cli.d.ts +15 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +48 -13
  17. package/dist/cli.js.map +1 -1
  18. package/dist/field-options.d.ts +59 -0
  19. package/dist/field-options.d.ts.map +1 -0
  20. package/dist/field-options.js +109 -0
  21. package/dist/field-options.js.map +1 -0
  22. package/dist/front-door-browser.d.ts +5 -0
  23. package/dist/front-door-browser.d.ts.map +1 -0
  24. package/dist/front-door-browser.js +30 -0
  25. package/dist/front-door-browser.js.map +1 -0
  26. package/dist/front-door-core.d.ts +45 -0
  27. package/dist/front-door-core.d.ts.map +1 -0
  28. package/dist/front-door-core.js +224 -0
  29. package/dist/front-door-core.js.map +1 -0
  30. package/dist/front-door.check.d.ts +2 -0
  31. package/dist/front-door.check.d.ts.map +1 -0
  32. package/dist/front-door.check.js +8 -0
  33. package/dist/front-door.check.js.map +1 -0
  34. package/dist/front-door.d.ts +46 -0
  35. package/dist/front-door.d.ts.map +1 -0
  36. package/dist/front-door.js +61 -0
  37. package/dist/front-door.js.map +1 -0
  38. package/dist/generated/advisor-plan-contract.generated.d.ts +3 -0
  39. package/dist/generated/advisor-plan-contract.generated.d.ts.map +1 -0
  40. package/dist/generated/advisor-plan-contract.generated.js +430 -0
  41. package/dist/generated/advisor-plan-contract.generated.js.map +1 -0
  42. package/dist/generated/contract-schema.generated.d.ts +97 -0
  43. package/dist/generated/contract-schema.generated.d.ts.map +1 -0
  44. package/dist/generated/contract-schema.generated.js +496 -0
  45. package/dist/generated/contract-schema.generated.js.map +1 -0
  46. package/dist/index.d.ts +14 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +24 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/messaging-kit.d.ts +113 -0
  51. package/dist/messaging-kit.d.ts.map +1 -0
  52. package/dist/messaging-kit.js +121 -0
  53. package/dist/messaging-kit.js.map +1 -0
  54. package/dist/passage.d.ts +2 -1
  55. package/dist/passage.d.ts.map +1 -1
  56. package/dist/passage.js +2 -1
  57. package/dist/passage.js.map +1 -1
  58. package/dist/plan-authority.d.ts +73 -0
  59. package/dist/plan-authority.d.ts.map +1 -0
  60. package/dist/plan-authority.js +126 -0
  61. package/dist/plan-authority.js.map +1 -0
  62. package/dist/plan-contract.d.ts +16 -0
  63. package/dist/plan-contract.d.ts.map +1 -0
  64. package/dist/plan-contract.js +33 -0
  65. package/dist/plan-contract.js.map +1 -0
  66. package/dist/plan-digest.d.ts +23 -0
  67. package/dist/plan-digest.d.ts.map +1 -0
  68. package/dist/plan-digest.js +82 -0
  69. package/dist/plan-digest.js.map +1 -0
  70. package/dist/plan-rules.d.ts +54 -0
  71. package/dist/plan-rules.d.ts.map +1 -0
  72. package/dist/plan-rules.js +145 -0
  73. package/dist/plan-rules.js.map +1 -0
  74. package/dist/resolve.d.ts +40 -6
  75. package/dist/resolve.d.ts.map +1 -1
  76. package/dist/resolve.js +144 -18
  77. package/dist/resolve.js.map +1 -1
  78. package/dist/scan.d.ts +32 -0
  79. package/dist/scan.d.ts.map +1 -1
  80. package/dist/scan.js +67 -6
  81. package/dist/scan.js.map +1 -1
  82. package/dist/schema.d.ts +1 -0
  83. package/dist/schema.d.ts.map +1 -1
  84. package/dist/schema.js +209 -5
  85. package/dist/schema.js.map +1 -1
  86. package/dist/site-identity.d.ts +88 -0
  87. package/dist/site-identity.d.ts.map +1 -0
  88. package/dist/site-identity.js +82 -0
  89. package/dist/site-identity.js.map +1 -0
  90. package/dist/types.d.ts +65 -7
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js +9 -7
  93. package/dist/types.js.map +1 -1
  94. package/package.json +11 -6
  95. package/skill/SKILL.md +2 -2
  96. package/src/approval-bypass.ts +2263 -0
  97. package/src/approval.ts +491 -0
  98. package/src/approve-cli.ts +474 -0
  99. package/src/cli.ts +46 -13
  100. package/src/field-options.ts +134 -0
  101. package/src/front-door-browser.ts +31 -0
  102. package/src/front-door-core.ts +265 -0
  103. package/src/front-door.check.ts +10 -0
  104. package/src/front-door.ts +65 -0
  105. package/src/generated/advisor-plan-contract.generated.ts +430 -0
  106. package/src/generated/contract-schema.generated.ts +520 -0
  107. package/src/index.ts +97 -1
  108. package/src/messaging-kit.ts +195 -0
  109. package/src/passage.ts +2 -1
  110. package/src/plan-authority.ts +160 -0
  111. package/src/plan-contract.ts +45 -0
  112. package/src/plan-digest.ts +81 -0
  113. package/src/plan-rules.ts +179 -0
  114. package/src/resolve.ts +185 -19
  115. package/src/scan.ts +97 -6
  116. package/src/schema.ts +223 -5
  117. package/src/site-identity.ts +125 -0
  118. package/src/types.ts +72 -7
  119. package/templates/front-door.en.json +423 -0
@@ -0,0 +1,421 @@
1
+ /**
2
+ * Approval-record helpers shared by the schema, `writer-check approve` and
3
+ * `writer-check approval-state`.
4
+ */
5
+ import { existsSync, readFileSync, statSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ import { computeCopyFingerprint } from "./fingerprint.js";
8
+ import { checkApprovalBypass, scanApprovalBypass } from "./approval-bypass.js";
9
+ import { parseCopyRegistry } from "./schema.js";
10
+ /**
11
+ * One delegate scope item: a dot-separated entry-id namespace with no
12
+ * wildcard, e.g. `"site"` or `"site.home"`. Unlike `CopyEntryId`, a single
13
+ * segment is allowed, because a namespace may be a top-level one.
14
+ */
15
+ export const COPY_DELEGATE_SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*(\.[a-z0-9]+(-[a-z0-9]+)*)*$/;
16
+ /** True when `value` is a well-formed delegate scope item. */
17
+ export function isDelegateScopeItem(value) {
18
+ return typeof value === "string" && COPY_DELEGATE_SCOPE_RE.test(value);
19
+ }
20
+ /**
21
+ * True when `entryId` falls inside one of `scope`'s namespaces: it equals an
22
+ * item, or starts with `item + "."`. `"site.home"` covers `"site.home"` and
23
+ * `"site.home.title"`, never `"site.homepage.title"`.
24
+ */
25
+ export function isEntryInDelegateScope(entryId, scope) {
26
+ return scope.some((item) => entryId === item || entryId.startsWith(`${item}.`));
27
+ }
28
+ /**
29
+ * True when `entry`'s recorded `textFingerprint` no longer matches its
30
+ * current `text` — the record was made for wording that has since changed.
31
+ * Exported so `resolve.ts` and `assessCopyApprovals` share one predicate
32
+ * rather than two independently-drifting copies of the same comparison.
33
+ */
34
+ export function isApprovalStale(entry) {
35
+ const approval = entry.approval;
36
+ if (!approval)
37
+ return false;
38
+ return computeCopyFingerprint(entry.text) !== approval.textFingerprint;
39
+ }
40
+ /** True when `approval.expiresAt` is set and has passed as of `now`. */
41
+ export function isApprovalExpired(approval, now) {
42
+ return approval.expiresAt !== undefined && now.getTime() >= Date.parse(approval.expiresAt);
43
+ }
44
+ /**
45
+ * Reports the current approval state of every `approved` entry in
46
+ * `registry` — draft and retired entries have nothing to approve and are
47
+ * ignored. An `approved` entry with no record at all is a warning
48
+ * (`approval-record-missing`): the pre-record state every registry started
49
+ * in, not yet a defect. A record that no longer matches its entry's text
50
+ * (`approval-stale`) or has passed its `expiresAt` (`approval-expired`) is
51
+ * an error — the checks are mutually exclusive, staleness taking priority
52
+ * since an expired-but-also-stale record's fingerprint mismatch is the more
53
+ * fundamental problem. A current delegate record still awaiting owner
54
+ * sign-off (`pendingOwnerReview: true`) is a warning, not an error: the
55
+ * entry is legitimately approved for now, just not yet durably so.
56
+ */
57
+ export function assessCopyApprovals(registry, now) {
58
+ const findings = [];
59
+ for (const entry of registry.entries) {
60
+ if (entry.status !== "approved")
61
+ continue;
62
+ const approval = entry.approval;
63
+ if (!approval) {
64
+ findings.push({
65
+ rule: "approval-record-missing",
66
+ severity: "warning",
67
+ entryId: entry.id,
68
+ message: `Entry "${entry.id}" is approved but has no approval record.`,
69
+ });
70
+ continue;
71
+ }
72
+ if (isApprovalStale(entry)) {
73
+ findings.push({
74
+ rule: "approval-stale",
75
+ severity: "error",
76
+ entryId: entry.id,
77
+ message: `Entry "${entry.id}"'s approval record no longer matches its current text.`,
78
+ });
79
+ continue;
80
+ }
81
+ if (isApprovalExpired(approval, now)) {
82
+ findings.push({
83
+ rule: "approval-expired",
84
+ severity: "error",
85
+ entryId: entry.id,
86
+ message: `Entry "${entry.id}"'s approval expired at ${approval.expiresAt}.`,
87
+ });
88
+ continue;
89
+ }
90
+ if (approval.approvedBy === "delegate" && approval.pendingOwnerReview === true) {
91
+ findings.push({
92
+ rule: "approval-pending-owner-review",
93
+ severity: "warning",
94
+ entryId: entry.id,
95
+ message: `Entry "${entry.id}" was approved by a delegate and is still awaiting owner review.`,
96
+ });
97
+ }
98
+ }
99
+ return findings;
100
+ }
101
+ // ============================================================================
102
+ // `writer-check approval-state` — combines TWO independent checks into one
103
+ // three-state verdict: `assessCopyApprovals` above (is the REGISTRY's own
104
+ // approval bookkeeping current — no stale or expired record, no delegate
105
+ // record stuck pending review) and `scanApprovalBypass`/`checkApprovalBypass`
106
+ // (`./approval-bypass.ts`) (does CONSUMER SOURCE actually route copy through
107
+ // that bookkeeping, or around it). Either half can fail independently: a
108
+ // registry can be perfectly current while a component still hard-codes
109
+ // `status: "approved"`, and a codebase can route every read through
110
+ // `createCopyResolver` while the registry itself carries a record that has
111
+ // gone stale. This command is the one place both are reported together,
112
+ // under one exit code.
113
+ //
114
+ // Not exported from `index.ts` (like `mainApproveCommand` in
115
+ // `approve-cli.ts`) — this is CLI surface, dispatched from `cli.ts`'s
116
+ // bottom-of-file `argv[0]` branch, not library surface.
117
+ // ============================================================================
118
+ export const APPROVAL_STATE_USAGE = `Usage: writer-check approval-state <registry-file> <scan-dir> [options]
119
+
120
+ registry-file Path to a CopyRegistry JSON file. Required.
121
+ scan-dir Directory to scan for approval-bypass patterns in consumer source. Required.
122
+
123
+ Reports the current approval state of a copy registry from two angles: is
124
+ every approved entry's own record current (see assessCopyApprovals in
125
+ approval.ts — a stale or expired record is an error; a missing record or a
126
+ delegate record still pending owner review is a warning), and does consumer
127
+ source in scan-dir actually route copy through the registry's approval
128
+ lifecycle rather than around it (see scanApprovalBypass/checkApprovalBypass
129
+ in approval-bypass.ts — approval-set-in-code and copy-read-without-resolver
130
+ are errors; an unchecked registry load or reference is neither a pass nor a
131
+ finding, just an incomplete picture).
132
+
133
+ Options:
134
+ --help Print this message and exit 0.
135
+ --extensions <ext>[,<ext>...] File extension(s) to scan, including the leading dot (for example .mjs). Repeatable, and each occurrence may itself be a comma-separated list; values union across both forms. When omitted, default is .ts, .tsx, .js, .jsx. Every value must include the leading dot; a flag that resolves to zero extensions is a usage error.
136
+ --format <json> When "json", prints exactly one machine-readable object ({ verdict, findings, unchecked, counts }) to stdout instead of the human-readable report. Any other value is a usage error.
137
+ --now <ISO> ISO 8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ, optionally with milliseconds) to evaluate staleness/expiry against. Defaults to the current time.
138
+
139
+ Exit codes: 0 = satisfied (every approved entry's record is current, no
140
+ approval-bypass finding, nothing left unchecked, and at least one file was
141
+ scanned), 1 = violated (at least one error-severity registry finding —
142
+ approval-stale or approval-expired — or at least one approval-bypass finding
143
+ — approval-set-in-code or copy-read-without-resolver), 2 = could not run or
144
+ indeterminate (bad arguments; the registry file is missing/unreadable/not
145
+ valid JSON/fails validateCopyRegistryShape; scan-dir is not a directory; the
146
+ scan itself could not run; or — with zero error-severity finding — at least
147
+ one unchecked registry load/reference, at least one file that failed to
148
+ parse, or zero files scanned). Warnings never change the exit code.
149
+ `;
150
+ /** Bad arguments only — mirrors `approve-cli.ts`'s `ApproveInputError`. */
151
+ class ApprovalStateInputError extends Error {
152
+ }
153
+ const APPROVAL_STATE_EXTENSION_RE = /^\.[a-zA-Z0-9]+$/;
154
+ /** Same shape schema.ts's own (unexported) `ISO_UTC_TIMESTAMP_RE` accepts. */
155
+ const APPROVAL_STATE_NOW_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
156
+ /**
157
+ * Parses `--extensions` with the identical rules `cli.ts`'s
158
+ * `parseAddressabilityArgs` applies to its own `--extensions` flag —
159
+ * repeatable, comma-separated, leading dot required, an empty resolved set
160
+ * is a usage error rather than a silent fallback to the default. Duplicated
161
+ * rather than imported from `cli.ts`, since `cli.ts` imports this file's
162
+ * `mainApprovalStateCommand` — importing back would create a real
163
+ * value-level cycle between the two, unlike the `schema.ts` cycle this
164
+ * file's `parseCopyRegistry` import already tolerates (see this section's
165
+ * top doc comment).
166
+ */
167
+ function parseApprovalStateArgs(argv) {
168
+ let registryFile;
169
+ let scanDir;
170
+ const extensions = [];
171
+ let sawExtensions = false;
172
+ let format = "text";
173
+ let now;
174
+ let help = false;
175
+ for (let i = 0; i < argv.length; i++) {
176
+ const arg = argv[i];
177
+ if (arg === "--help" || arg === "-h") {
178
+ help = true;
179
+ continue;
180
+ }
181
+ if (arg === "--extensions") {
182
+ sawExtensions = true;
183
+ const value = argv[++i];
184
+ if (value === undefined || value.startsWith("-")) {
185
+ throw new ApprovalStateInputError("--extensions requires an extension such as .mjs");
186
+ }
187
+ const tokens = value.split(",");
188
+ if (tokens.length === 0 || tokens.some((token) => token.trim().length === 0)) {
189
+ throw new ApprovalStateInputError(`--extensions requires at least one non-empty extension such as .mjs, got ${JSON.stringify(value)}`);
190
+ }
191
+ for (const rawToken of tokens) {
192
+ const token = rawToken.trim().toLowerCase();
193
+ if (!token.startsWith(".")) {
194
+ throw new ApprovalStateInputError(`--extensions values must include the leading dot, got ${JSON.stringify(token)}`);
195
+ }
196
+ if (!APPROVAL_STATE_EXTENSION_RE.test(token)) {
197
+ throw new ApprovalStateInputError(`invalid --extensions value ${JSON.stringify(token)}`);
198
+ }
199
+ extensions.push(token);
200
+ }
201
+ continue;
202
+ }
203
+ if (arg === "--format") {
204
+ const value = argv[++i];
205
+ if (value !== "json") {
206
+ throw new ApprovalStateInputError(`--format only accepts "json", got ${JSON.stringify(value)}`);
207
+ }
208
+ format = "json";
209
+ continue;
210
+ }
211
+ if (arg === "--now") {
212
+ const value = argv[++i];
213
+ if (value === undefined || value.startsWith("-")) {
214
+ throw new ApprovalStateInputError("--now requires an ISO 8601 UTC timestamp");
215
+ }
216
+ now = value;
217
+ continue;
218
+ }
219
+ if (arg.startsWith("-")) {
220
+ throw new ApprovalStateInputError(`unknown flag "${arg}"`);
221
+ }
222
+ if (registryFile === undefined) {
223
+ registryFile = arg;
224
+ }
225
+ else if (scanDir === undefined) {
226
+ scanDir = arg;
227
+ }
228
+ else {
229
+ throw new ApprovalStateInputError(`unexpected extra argument "${arg}"`);
230
+ }
231
+ }
232
+ return { registryFile, scanDir, extensions: sawExtensions ? extensions : undefined, format, now, help };
233
+ }
234
+ function parseApprovalStateNow(raw) {
235
+ if (raw === undefined)
236
+ return new Date();
237
+ if (!APPROVAL_STATE_NOW_RE.test(raw) || Number.isNaN(Date.parse(raw))) {
238
+ throw new ApprovalStateInputError(`--now must be an ISO 8601 UTC timestamp (e.g. 2026-01-01T00:00:00Z), got ${JSON.stringify(raw)}`);
239
+ }
240
+ return new Date(raw);
241
+ }
242
+ function errorMessage(error) {
243
+ return error instanceof Error ? error.message : String(error);
244
+ }
245
+ function combineUnchecked(scan, bypass) {
246
+ const fromParseFailures = scan.parseFailures.map((p) => ({
247
+ file: p.file,
248
+ line: 0,
249
+ kind: "parse-failure",
250
+ detail: p.detail,
251
+ }));
252
+ return [...bypass.unchecked, ...fromParseFailures];
253
+ }
254
+ function computeApprovalStateVerdict(registryFindings, bypass) {
255
+ const hasRegistryError = registryFindings.some((f) => f.severity === "error");
256
+ if (hasRegistryError || bypass.verdict === "violated")
257
+ return "violated";
258
+ if (bypass.verdict === "indeterminate")
259
+ return "indeterminate";
260
+ return "satisfied";
261
+ }
262
+ function approvalStateExitCode(verdict) {
263
+ return verdict === "violated" ? 1 : verdict === "indeterminate" ? 2 : 0;
264
+ }
265
+ /**
266
+ * Emitted when this command cannot even reach the point of scanning (a bad
267
+ * registry file, an unusable scan-dir, or the scan itself throwing) — always
268
+ * exit 2, always `verdict: "indeterminate"` in JSON mode, with `error`
269
+ * naming the reason. Bad ARGUMENTS never reach here — those are caught in
270
+ * `mainApprovalStateCommand`'s own outer catch, before `--format` is even
271
+ * meaningfully known to matter, and always print usage to stderr regardless
272
+ * of `--format`.
273
+ */
274
+ function emitApprovalStateCouldNotRun(format, reason) {
275
+ if (format === "json") {
276
+ console.log(JSON.stringify({
277
+ verdict: "indeterminate",
278
+ findings: [],
279
+ unchecked: [],
280
+ counts: { errors: 0, warnings: 0, unchecked: 0, filesScanned: 0, coupledFiles: 0 },
281
+ error: reason,
282
+ }));
283
+ }
284
+ else {
285
+ console.error(`[approval-state] could not run: ${reason}`);
286
+ }
287
+ return 2;
288
+ }
289
+ /**
290
+ * Prints the human-text or `--format json` report and returns the exit code
291
+ * — the one place `assessCopyApprovals`' registry findings and
292
+ * `checkApprovalBypass`'s source findings are merged into a single verdict.
293
+ * See this section's top doc comment for why the two are combined rather
294
+ * than reported as two separate commands.
295
+ */
296
+ function reportApprovalState(format, registryPath, scanDirPath, registryFindings, scan, bypass) {
297
+ const verdict = computeApprovalStateVerdict(registryFindings, bypass);
298
+ const exitCode = approvalStateExitCode(verdict);
299
+ const registryErrors = registryFindings.filter((f) => f.severity === "error");
300
+ const registryWarnings = registryFindings.filter((f) => f.severity === "warning");
301
+ const unchecked = combineUnchecked(scan, bypass);
302
+ const errorCount = registryErrors.length + bypass.findings.length;
303
+ if (format === "json") {
304
+ const findings = [
305
+ ...registryFindings.map((f) => ({ source: "registry", rule: f.rule, severity: f.severity, entryId: f.entryId, message: f.message })),
306
+ ...bypass.findings.map((f) => ({ source: "source", rule: f.rule, severity: f.severity, file: f.file, line: f.line, message: f.detail })),
307
+ ];
308
+ console.log(JSON.stringify({
309
+ verdict,
310
+ findings,
311
+ unchecked,
312
+ counts: {
313
+ errors: errorCount,
314
+ warnings: registryWarnings.length,
315
+ unchecked: unchecked.length,
316
+ filesScanned: scan.filesScanned,
317
+ coupledFiles: scan.coupledFiles.length,
318
+ },
319
+ }));
320
+ return exitCode;
321
+ }
322
+ console.log(`[approval-state] registry: ${registryPath}`);
323
+ console.log(`[approval-state] scan dir: ${scanDirPath}`);
324
+ console.log(`[approval-state] ${errorCount} error(s), ${registryWarnings.length} warning(s), ${unchecked.length} unchecked item(s), ${scan.filesScanned} file(s) scanned, ${scan.coupledFiles.length} coupled file(s).`);
325
+ for (const f of registryErrors) {
326
+ console.log(`[approval-state] [${f.rule}] ${f.entryId}: ${f.message}`);
327
+ }
328
+ for (const f of bypass.findings) {
329
+ console.log(`[approval-state] [${f.rule}] ${f.file}:${f.line} ${f.detail}`);
330
+ }
331
+ if (registryWarnings.length > 0) {
332
+ console.log(`[approval-state] warnings:`);
333
+ for (const f of registryWarnings) {
334
+ console.log(`[approval-state] [${f.rule}] ${f.entryId}: ${f.message}`);
335
+ }
336
+ }
337
+ if (unchecked.length > 0) {
338
+ console.log(`[approval-state] unchecked:`);
339
+ for (const u of unchecked) {
340
+ console.log(`[approval-state] [${u.kind}] ${u.file}:${u.line} ${u.detail}`);
341
+ }
342
+ }
343
+ if (bypass.reasons.length > 0) {
344
+ console.log(`[approval-state] reasons:`);
345
+ for (const r of bypass.reasons) {
346
+ console.log(`[approval-state] ${r}`);
347
+ }
348
+ }
349
+ console.log(`[approval-state] verdict: ${verdict}`);
350
+ return exitCode;
351
+ }
352
+ /**
353
+ * Exported (unlike a typical CLI `main`) so this package's tests (which do
354
+ * not ship) can exercise the whole argv-to-exit-code contract directly, without spawning a subprocess —
355
+ * the same discipline `mainApproveCommand` (`approve-cli.ts`) and
356
+ * `mainAddressabilityCheck`/`mainPassagesCheck` (`cli.ts`) already hold to.
357
+ * `argv` excludes the leading `"approval-state"` token. Never throws: every
358
+ * failure, expected or not, is caught here and mapped to exit code 2.
359
+ */
360
+ export function mainApprovalStateCommand(argv) {
361
+ try {
362
+ const args = parseApprovalStateArgs(argv);
363
+ if (args.help) {
364
+ console.log(APPROVAL_STATE_USAGE);
365
+ return 0;
366
+ }
367
+ if (args.registryFile === undefined) {
368
+ throw new ApprovalStateInputError("registry-file is required");
369
+ }
370
+ if (args.scanDir === undefined) {
371
+ throw new ApprovalStateInputError("scan-dir is required");
372
+ }
373
+ const now = parseApprovalStateNow(args.now);
374
+ const registryPath = resolve(process.cwd(), args.registryFile);
375
+ const scanDirPath = resolve(process.cwd(), args.scanDir);
376
+ let raw;
377
+ try {
378
+ raw = readFileSync(registryPath, "utf8");
379
+ }
380
+ catch (error) {
381
+ return emitApprovalStateCouldNotRun(args.format, `registry file "${registryPath}" could not be read: ${errorMessage(error)}`);
382
+ }
383
+ let parsedJson;
384
+ try {
385
+ parsedJson = JSON.parse(raw);
386
+ }
387
+ catch (error) {
388
+ return emitApprovalStateCouldNotRun(args.format, `registry file "${registryPath}" is not valid JSON: ${errorMessage(error)}`);
389
+ }
390
+ let registry;
391
+ try {
392
+ registry = parseCopyRegistry(parsedJson);
393
+ }
394
+ catch (error) {
395
+ return emitApprovalStateCouldNotRun(args.format, errorMessage(error));
396
+ }
397
+ if (!existsSync(scanDirPath) || !statSync(scanDirPath).isDirectory()) {
398
+ return emitApprovalStateCouldNotRun(args.format, `scan-dir "${scanDirPath}" is not a directory`);
399
+ }
400
+ const registryFindings = assessCopyApprovals(registry, now);
401
+ let scan;
402
+ try {
403
+ scan = scanApprovalBypass(scanDirPath, { registryPath, extensions: args.extensions });
404
+ }
405
+ catch (error) {
406
+ return emitApprovalStateCouldNotRun(args.format, errorMessage(error));
407
+ }
408
+ const bypass = checkApprovalBypass(scan);
409
+ return reportApprovalState(args.format, registryPath, scanDirPath, registryFindings, scan, bypass);
410
+ }
411
+ catch (error) {
412
+ if (error instanceof ApprovalStateInputError) {
413
+ console.error(`writer-check approval-state: ${error.message}`);
414
+ console.error(`\n${APPROVAL_STATE_USAGE}`);
415
+ return 2;
416
+ }
417
+ console.error(`writer-check approval-state: unexpected error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
418
+ return 2;
419
+ }
420
+ }
421
+ //# sourceMappingURL=approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `writer-check approve` — writes a `CopyRegistryEntry.approval` record so
3
+ * nobody has to hand-edit one. It exists as a command, not a hand-edited
4
+ * JSON field, for the same reason `textFingerprint` (`fingerprint.ts`) is a
5
+ * content hash rather than a human-maintained counter: a hand-typed record
6
+ * is easy to get wrong — copying yesterday's `approvedAt`, pinning `textFingerprint` to
7
+ * some other entry's text, or leaving `pendingOwnerReview` set after an
8
+ * owner actually reviewed it. Every field this command writes is either
9
+ * computed (`approvedAt` from the clock, `textFingerprint` from the
10
+ * entry's own current `text` via `computeCopyFingerprint`) or copied
11
+ * verbatim from an argument this command itself validated (`--delegate`,
12
+ * `--scope`, `--expires`) — there is no field here a human types free-form
13
+ * into the JSON.
14
+ *
15
+ * This module owns argument parsing, registry-file I/O, and the mutate-
16
+ * then-atomically-rename write; `./approval.ts`'s `isDelegateScopeItem` and
17
+ * `isEntryInDelegateScope` decide whether a requested id is in a delegate's
18
+ * declared scope, and `./schema.ts`'s `validateCopyRegistryShape` decides
19
+ * whether the registry file is trustworthy enough to mutate at all — this
20
+ * file calls both rather than re-deriving either.
21
+ *
22
+ * `main`-equivalent shape is `mainApproveCommand`, exported (unlike a
23
+ * typical CLI `main`) so this package's tests (which do not ship) can exercise the whole
24
+ * argv-to-exit-code contract directly against real `mkdtemp` fixture
25
+ * files, without spawning a subprocess — the same discipline every other
26
+ * subcommand in this package's `cli.ts` already holds to. `argv` excludes
27
+ * the leading `"approve"` token: `cli.ts`'s top-level dispatch calls
28
+ * `mainApproveCommand(argv.slice(1))` for `writer-check approve ...`, the same way `"passages"` and
29
+ * `"addressability"` are already dispatched there.
30
+ *
31
+ * Exit codes — a three-state contract matching every other gate/command in
32
+ * this package:
33
+ *
34
+ * 0 — written: every named entry was approved or revoked, and the
35
+ * registry file on disk now reflects it.
36
+ * 1 — refused: at least one named entry is unknown, is `retired`, or —
37
+ * for a delegate approval — falls outside `--scope`. This is
38
+ * ALL-OR-NOTHING: when any requested id has a problem, EVERY problem
39
+ * is printed and NOTHING is written, even for the other ids that
40
+ * would otherwise have succeeded. A partial write here would leave a
41
+ * consumer's own audit trail unable to tell whether a whole
42
+ * requested batch was actually reviewed together.
43
+ * 2 — could not run: bad arguments (see below), or the registry file is
44
+ * missing/unreadable, not valid JSON, or fails
45
+ * `validateCopyRegistryShape`. Nothing is written in this state
46
+ * either — this command never mutates a registry it does not first
47
+ * fully trust.
48
+ *
49
+ * Bad-argument cases that produce exit 2, all before the registry file is
50
+ * ever opened: no registry-file argument, zero entry ids, neither `--by`
51
+ * nor `--revoke` (or both), `--by` other than `owner`/`delegate`, `--by
52
+ * owner` combined with `--delegate`/`--scope`/`--expires`, `--by delegate`
53
+ * missing `--delegate` or `--scope`, an empty `--delegate` value, a
54
+ * `--scope` item that fails `isDelegateScopeItem` or an empty scope list,
55
+ * `--revoke` combined with any of `--by`/`--delegate`/`--scope`/
56
+ * `--expires`, an `--expires` value that is not a well-formed ISO 8601 UTC
57
+ * timestamp or is not strictly after the current time, or an unknown flag.
58
+ * `--help`/`-h` always prints usage and returns 0, taking precedence over
59
+ * every other check reachable in the same parse. Any other unexpected
60
+ * exception is caught and mapped to exit 2 as well — this command never
61
+ * lets an uncaught error double as a "something went wrong" report.
62
+ */
63
+ export declare const APPROVE_USAGE = "Usage: writer-check approve <registry-file> <entry-id>... --by owner\n or: writer-check approve <registry-file> <entry-id>... --by delegate --delegate <id> --scope <ns>[,<ns>...] [--expires <ISO>]\n or: writer-check approve <registry-file> <entry-id>... --revoke\n\n registry-file Path to a CopyRegistry JSON file. Required.\n entry-id One or more entry ids to approve or revoke (repeatable positional argument; duplicates are deduplicated). Required.\n\nOptions:\n --help Print this message and exit 0.\n --by <owner|delegate> Who is recording this approval. Exactly one of --by or --revoke is required.\n --delegate <id> Opaque delegate identifier, never a personal name. Required (and only allowed) with --by delegate.\n --scope <ns>[,<ns>...] One or more dot-separated entry-id namespaces the delegate may approve (repeatable; a comma-separated value and repeated flags union). Required (and only allowed) with --by delegate.\n --expires <ISO> ISO 8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ, optionally with milliseconds) after which the delegate approval expires. Must be strictly after the current time. Only allowed with --by delegate.\n --revoke Return every named entry to \"draft\" and delete its approval record. Cannot be combined with --by, --delegate, --scope, or --expires.\n\nApproving an entry that already carries a delegate's approval record with\n--by owner replaces that record with the owner's own \u2014 this is how a\npending delegate approval is durably confirmed.\n\nExit codes: 0 = written (every named entry approved or revoked; the\nregistry file was updated), 1 = refused (an unknown, retired, or \u2014 for a\ndelegate approval \u2014 out-of-scope entry id was named; every problem is\nprinted and nothing is written), 2 = could not run (bad arguments, or the\nregistry file is missing/unreadable/not valid JSON/fails schema\nvalidation; nothing is written).\n";
64
+ /**
65
+ * Exported (unlike a typical CLI `main`) so this package's tests (which do
66
+ * not ship) can exercise the whole argv-to-exit-code contract directly against real
67
+ * `mkdtemp` fixture files, without spawning a subprocess — see this file's
68
+ * top doc comment for the full exit-code contract. `argv` excludes the
69
+ * leading `"approve"` subcommand token. `io.now` lets a test pin the clock
70
+ * that `approvedAt` and `--expires` validation are measured against;
71
+ * omitted, it defaults to `new Date()`.
72
+ *
73
+ * Never throws: every failure, expected or not, is caught here and mapped
74
+ * to an exit code — bad arguments and an untrustworthy registry file both
75
+ * become 2 with nothing written, a refused (but well-formed) request
76
+ * becomes 1 with nothing written, and a clean run becomes 0 with the
77
+ * registry file updated on disk.
78
+ */
79
+ export declare function mainApproveCommand(argv: string[], io?: {
80
+ now?: Date;
81
+ }): number;
82
+ //# sourceMappingURL=approve-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve-cli.d.ts","sourceRoot":"","sources":["../src/approve-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AASH,eAAO,MAAM,aAAa,46DAyBzB,CAAC;AA+SF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAA;CAAO,GAAG,MAAM,CA4DlF"}