@awak-app/simy-cli 0.5.15 → 0.5.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,420 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { cleanString, stringArray } from "./shared.js";
4
+
5
+ export const PR_PREPARATION_CONTRACT_VERSION = 2;
6
+ const LEGACY_PR_PREPARATION_CONTRACT_VERSION = 1;
7
+
8
+ const BASE_PR_PREPARATION_CHECKS = Object.freeze([
9
+ { id: "current_base_and_head", label: "Latest base is integrated into the reported head" },
10
+ { id: "current_ci_and_skips", label: "Current-head CI passed and every skip is disclosed" },
11
+ { id: "exact_revision_integration", label: "Companion revisions and combined-path evidence are exact and current" },
12
+ { id: "e2e_outcome_and_cleanup", label: "E2E outcomes passed and cleanup left no residual fixtures" },
13
+ { id: "security_and_privacy", label: "Security and privacy boundaries were checked at the changed surface" },
14
+ { id: "ui_and_i18n", label: "Applicable browser, layout, console, and localization checks passed" },
15
+ { id: "final_diff_and_scope", label: "The final diff is scoped and reviewed against the request" },
16
+ { id: "residual_status_disclosure", label: "Skipped gates, residual risk, and post-merge work are disclosed" },
17
+ ]);
18
+
19
+ const CONDITIONAL_PR_PREPARATION_CHECKS = Object.freeze([
20
+ { id: "official_checklist_ownership", label: "Generated checklist-like output cannot duplicate or impersonate the controller checklist", conditional: true },
21
+ { id: "deployment_target_mapping", label: "The release target and deployed-revision observation method come from an authoritative topology source", conditional: true },
22
+ { id: "live_data_safety", label: "Live verification has an explicit write policy and leaves no residual test data", conditional: true },
23
+ { id: "resume_compatibility", label: "Applicable saved-run and checkpoint paths recompute derived views without losing artifacts", conditional: true },
24
+ ]);
25
+
26
+ export const PR_PREPARATION_CHECKS = Object.freeze([
27
+ ...BASE_PR_PREPARATION_CHECKS,
28
+ ...CONDITIONAL_PR_PREPARATION_CHECKS,
29
+ ]);
30
+
31
+ export function defaultPrPreparationContract() {
32
+ return {
33
+ version: PR_PREPARATION_CONTRACT_VERSION,
34
+ checks: PR_PREPARATION_CHECKS.map(({ id, label, conditional = false }) => ({
35
+ id,
36
+ label,
37
+ conditional,
38
+ })),
39
+ };
40
+ }
41
+
42
+ export function normalizePrPreparation(value) {
43
+ const rows = Array.isArray(value) ? value : [];
44
+ return rows.map((row) => {
45
+ const source = record(row);
46
+ return {
47
+ id: cleanString(source.id),
48
+ applicability:
49
+ source.applicability === "not_applicable" ? "not_applicable" : "required",
50
+ passed: source.passed === true,
51
+ observation: cleanString(source.observation) || null,
52
+ evidence_refs: stringArray(source.evidence_refs),
53
+ bound_revisions: stringArray(source.bound_revisions),
54
+ reason: cleanString(source.reason) || null,
55
+ authoritative_source: cleanString(source.authoritative_source) || null,
56
+ companion_revisions: normalizeCompanionRevisions(source.companion_revisions),
57
+ manifest_revisions: normalizeManifestRevisions(source.manifest_revisions),
58
+ cleanup_residual_count: Number.isInteger(source.cleanup_residual_count)
59
+ ? source.cleanup_residual_count
60
+ : null,
61
+ security_privacy_checks: normalizeSecurityPrivacyChecks(source.security_privacy_checks),
62
+ browser_artifacts: stringArray(source.browser_artifacts),
63
+ locales_checked: stringArray(source.locales_checked),
64
+ console_error_count: Number.isInteger(source.console_error_count)
65
+ ? source.console_error_count
66
+ : null,
67
+ network_failure_count: Number.isInteger(source.network_failure_count)
68
+ ? source.network_failure_count
69
+ : null,
70
+ heading_levels_checked: integerArray(source.heading_levels_checked),
71
+ leading_space_checked: source.leading_space_checked === true,
72
+ trailing_hash_checked: source.trailing_hash_checked === true,
73
+ single_official_checklist: source.single_official_checklist === true,
74
+ unverified_model_output_labeled: source.unverified_model_output_labeled === true,
75
+ deployment_target: cleanString(source.deployment_target) || null,
76
+ deployment_target_source: cleanString(source.deployment_target_source) || null,
77
+ revision_observation_method: cleanString(source.revision_observation_method) || null,
78
+ live_write_policy: normalizeLiveWritePolicy(source.live_write_policy),
79
+ resume_paths_checked: stringArray(source.resume_paths_checked),
80
+ paid_resume_support: normalizePaidResumeSupport(source.paid_resume_support),
81
+ paid_resume_support_source: cleanString(source.paid_resume_support_source) || null,
82
+ derived_views_recomputed: source.derived_views_recomputed === true,
83
+ artifact_content_preserved: source.artifact_content_preserved === true,
84
+ };
85
+ }).filter((row) => row.id);
86
+ }
87
+
88
+ export function normalizeSkippedChecks(value) {
89
+ const rows = Array.isArray(value) ? value : [];
90
+ return rows.map((row) => {
91
+ const source = record(row);
92
+ return {
93
+ name: cleanString(source.name),
94
+ reason: cleanString(source.reason) || null,
95
+ impact: cleanString(source.impact) || null,
96
+ };
97
+ }).filter((row) => row.name);
98
+ }
99
+
100
+ export function evaluatePrPreparation(charter, attempt) {
101
+ const contract = record(charter?.pr_preparation_contract);
102
+ if (!contract.version) {
103
+ return {
104
+ applicable: false,
105
+ passed: true,
106
+ checks: [],
107
+ detail: "Legacy charter has no structured PR preparation contract.",
108
+ };
109
+ }
110
+ const expectedChecks = contract.version === PR_PREPARATION_CONTRACT_VERSION
111
+ ? PR_PREPARATION_CHECKS
112
+ : contract.version === LEGACY_PR_PREPARATION_CONTRACT_VERSION
113
+ ? BASE_PR_PREPARATION_CHECKS
114
+ : null;
115
+ if (!expectedChecks) {
116
+ return {
117
+ applicable: true,
118
+ passed: false,
119
+ checks: [check(
120
+ "supported_pr_preparation_contract",
121
+ "PR preparation contract version is supported",
122
+ false,
123
+ `Unsupported PR preparation contract version: ${contract.version}.`,
124
+ )],
125
+ detail: "Unsupported PR preparation contract versions fail closed.",
126
+ };
127
+ }
128
+
129
+ const rows = normalizePrPreparation(attempt?.pr_preparation);
130
+ const byId = new Map(rows.map((row) => [row.id, row]));
131
+ const duplicateIds = rows
132
+ .map((row) => row.id)
133
+ .filter((id, index, all) => all.indexOf(id) !== index);
134
+ const unknownIds = rows
135
+ .map((row) => row.id)
136
+ .filter((id) => !expectedChecks.some((expected) => expected.id === id));
137
+ const headSha = cleanString(attempt?.commit_sha);
138
+ const verifiedRefs = new Set(
139
+ (attempt?.observed_evidence?.pr_preparation_refs || [])
140
+ .filter((item) => item?.verified === true)
141
+ .map((item) => cleanString(item.ref)),
142
+ );
143
+ const verifiedRefRecords = new Map(
144
+ (attempt?.observed_evidence?.pr_preparation_refs || [])
145
+ .filter((item) => item?.verified === true)
146
+ .map((item) => [cleanString(item.ref), item]),
147
+ );
148
+ const checks = expectedChecks.map((expected) => {
149
+ const row = byId.get(expected.id);
150
+ if (!row) {
151
+ return check(expected.id, expected.label, false, "Required preparation check is missing.");
152
+ }
153
+ if (row.applicability === "not_applicable") {
154
+ const currentRevisionBound = Boolean(headSha && row.bound_revisions.includes(headSha));
155
+ const evidenceVerified = row.evidence_refs.length > 0 &&
156
+ row.evidence_refs.every((ref) => verifiedRefs.has(ref));
157
+ const valid = Boolean(
158
+ row.reason && row.authoritative_source && currentRevisionBound && evidenceVerified
159
+ );
160
+ return check(
161
+ expected.id,
162
+ expected.label,
163
+ valid,
164
+ valid
165
+ ? "Not-applicable boundary is current-head-bound and supported by verified local/controller evidence."
166
+ : "Not-applicable claims require a reason, an authoritative source, verified evidence references, and the current head SHA binding.",
167
+ );
168
+ }
169
+ const currentRevisionBound = Boolean(headSha && row.bound_revisions.includes(headSha));
170
+ const evidenceVerified = row.evidence_refs.length > 0 &&
171
+ row.evidence_refs.every((ref) => verifiedRefs.has(ref));
172
+ const rowSpecific = rowSpecificValidation(row, verifiedRefRecords);
173
+ const valid = Boolean(
174
+ row.passed &&
175
+ row.observation &&
176
+ evidenceVerified &&
177
+ currentRevisionBound &&
178
+ rowSpecific.passed
179
+ );
180
+ return check(
181
+ expected.id,
182
+ expected.label,
183
+ valid,
184
+ valid
185
+ ? "Required preparation proof is current-head-bound, structurally complete, and backed by verified local/controller evidence."
186
+ : `Required checks need passed=true, a concrete observation, verified evidence references, the current head SHA in bound_revisions, and row-specific proof. ${rowSpecific.detail}`,
187
+ );
188
+ });
189
+ if (duplicateIds.length > 0) {
190
+ checks.push(check(
191
+ "unique_pr_preparation_ids",
192
+ "Preparation check IDs are unique",
193
+ false,
194
+ `Duplicate preparation check IDs were submitted (count: ${new Set(duplicateIds).size}).`,
195
+ ));
196
+ }
197
+ if (unknownIds.length > 0) {
198
+ checks.push(check(
199
+ "known_pr_preparation_ids",
200
+ "Preparation check IDs are recognized",
201
+ false,
202
+ `Unrecognized preparation check IDs were submitted (count: ${new Set(unknownIds).size}).`,
203
+ ));
204
+ }
205
+ return {
206
+ applicable: true,
207
+ passed: checks.every((item) => item.passed),
208
+ checks,
209
+ detail: checks.every((item) => item.passed)
210
+ ? "All applicable PR preparation checks have current revision-bound evidence."
211
+ : "PR preparation evidence is missing, stale, failed, or unjustifiably marked not applicable.",
212
+ };
213
+ }
214
+
215
+ export function independentAuditEvidenceIdentity(attempt) {
216
+ const local = record(attempt?.observed_evidence?.local);
217
+ const github = record(attempt?.observed_evidence?.github);
218
+ const payload = {
219
+ head_sha: cleanString(local.head_sha || attempt?.commit_sha),
220
+ fetched_base_sha: cleanString(local.base_sha),
221
+ github_base_sha: cleanString(github.base_sha),
222
+ github_head_sha: cleanString(github.head_sha),
223
+ github_checks: normalizeIdentityChecks(github.checks),
224
+ skipped_checks: normalizeSkippedChecks(attempt?.skipped_checks),
225
+ pr_preparation: normalizePrPreparation(attempt?.pr_preparation),
226
+ verified_pr_preparation_refs: (attempt?.observed_evidence?.pr_preparation_refs || [])
227
+ .map((item) => ({
228
+ ref: cleanString(item?.ref),
229
+ verified: item?.verified === true,
230
+ kind: cleanString(item?.kind),
231
+ sha256: cleanString(item?.sha256),
232
+ }))
233
+ .sort((a, b) => a.ref.localeCompare(b.ref)),
234
+ minimum_matrix_sha256: cleanString(
235
+ attempt?.observed_evidence?.minimum_completion?.matrix_sha256,
236
+ ),
237
+ };
238
+ return createHash("sha256").update(stableStringify(payload)).digest("hex");
239
+ }
240
+
241
+ export function skippedChecksDisclosure(githubChecks, reportedChecks) {
242
+ const observed = (Array.isArray(githubChecks) ? githubChecks : [])
243
+ .filter((item) => item?.state === "skipped")
244
+ .map((item) => cleanString(item.name))
245
+ .filter(Boolean);
246
+ const reported = normalizeSkippedChecks(reportedChecks);
247
+ const missing = observed.filter((name) => !reported.some((item) =>
248
+ item.name === name && item.reason && item.impact
249
+ ));
250
+ return {
251
+ passed: missing.length === 0,
252
+ observed_count: observed.length,
253
+ missing_count: missing.length,
254
+ detail: missing.length === 0
255
+ ? observed.length > 0
256
+ ? `All ${observed.length} skipped check(s) include reason and impact disclosure.`
257
+ : "No skipped PR checks were observed."
258
+ : `${missing.length} skipped check(s) need reason and impact disclosure.`,
259
+ };
260
+ }
261
+
262
+ function check(id, label, passed, detail) {
263
+ return { id, label, passed: Boolean(passed), detail };
264
+ }
265
+
266
+ function record(value) {
267
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
268
+ }
269
+
270
+ function normalizeCompanionRevisions(value) {
271
+ return (Array.isArray(value) ? value : []).map((item) => {
272
+ const source = record(item);
273
+ return {
274
+ repository: cleanString(source.repository),
275
+ sha: cleanString(source.sha),
276
+ evidence_ref: cleanString(source.evidence_ref),
277
+ };
278
+ }).filter((item) => item.repository || item.sha || item.evidence_ref);
279
+ }
280
+
281
+ function normalizeManifestRevisions(value) {
282
+ return (Array.isArray(value) ? value : []).map((item) => {
283
+ const source = record(item);
284
+ return {
285
+ manifest: cleanString(source.manifest),
286
+ sha256: cleanString(source.sha256),
287
+ evidence_ref: cleanString(source.evidence_ref),
288
+ };
289
+ }).filter((item) => item.manifest || item.sha256 || item.evidence_ref);
290
+ }
291
+
292
+ function normalizeSecurityPrivacyChecks(value) {
293
+ return (Array.isArray(value) ? value : []).map((item) => {
294
+ const source = record(item);
295
+ return {
296
+ boundary: cleanString(source.boundary),
297
+ result: cleanString(source.result),
298
+ evidence_ref: cleanString(source.evidence_ref),
299
+ };
300
+ }).filter((item) => item.boundary || item.result || item.evidence_ref);
301
+ }
302
+
303
+ function rowSpecificValidation(row, verifiedRefs) {
304
+ if (row.id === "exact_revision_integration") {
305
+ const companionsValid = row.companion_revisions.every(
306
+ (item) => item.repository && /^[a-f0-9]{40}$/i.test(item.sha) &&
307
+ item.evidence_ref && row.evidence_refs.includes(item.evidence_ref),
308
+ );
309
+ const manifestsValid = row.manifest_revisions.every(
310
+ (item) => item.manifest && /^[a-f0-9]{64}$/i.test(item.sha256) &&
311
+ item.evidence_ref && row.evidence_refs.includes(item.evidence_ref) &&
312
+ verifiedRefs.get(item.evidence_ref)?.sha256 === item.sha256.toLowerCase(),
313
+ );
314
+ const valid = row.companion_revisions.length + row.manifest_revisions.length > 0 &&
315
+ companionsValid && manifestsValid;
316
+ return { passed: valid, detail: "Exact-revision integration needs a companion repository/full commit SHA or manifest/SHA-256 with a matching evidence reference; use not_applicable when neither exists." };
317
+ }
318
+ if (row.id === "e2e_outcome_and_cleanup") {
319
+ return {
320
+ passed: row.cleanup_residual_count === 0,
321
+ detail: "E2E cleanup must report cleanup_residual_count=0.",
322
+ };
323
+ }
324
+ if (row.id === "security_and_privacy") {
325
+ return {
326
+ passed: row.security_privacy_checks.length > 0 && row.security_privacy_checks.every(
327
+ (item) => item.boundary && item.result === "passed" &&
328
+ item.evidence_ref && row.evidence_refs.includes(item.evidence_ref),
329
+ ),
330
+ detail: "Security/privacy proof must name the checked boundaries; use not_applicable only with verified boundary evidence.",
331
+ };
332
+ }
333
+ if (row.id === "ui_and_i18n") {
334
+ return {
335
+ passed: row.browser_artifacts.length > 0 && row.browser_artifacts.every((ref) =>
336
+ row.evidence_refs.includes(ref) && verifiedRefs.has(ref)
337
+ ) && row.locales_checked.length > 0 &&
338
+ row.console_error_count === 0 && row.network_failure_count === 0,
339
+ detail: "UI/i18n proof needs browser artifacts, checked locales, and zero console/network failures; use not_applicable for non-UI changes.",
340
+ };
341
+ }
342
+ if (row.id === "official_checklist_ownership") {
343
+ const levels = [...new Set(row.heading_levels_checked)].sort((a, b) => a - b);
344
+ return {
345
+ passed: levels.join(",") === "1,2,3,4,5,6" && row.leading_space_checked &&
346
+ row.trailing_hash_checked && row.single_official_checklist &&
347
+ row.unverified_model_output_labeled,
348
+ detail: "Checklist ownership proof must cover heading levels 1-6, leading spaces, trailing hashes, one controller-owned official table, and explicit labeling of model-generated lookalikes.",
349
+ };
350
+ }
351
+ if (row.id === "deployment_target_mapping") {
352
+ return {
353
+ passed: Boolean(
354
+ row.deployment_target && row.deployment_target_source &&
355
+ row.revision_observation_method &&
356
+ row.evidence_refs.includes(row.deployment_target_source) &&
357
+ verifiedRefs.has(row.deployment_target_source)
358
+ ),
359
+ detail: "Deployment mapping proof needs a target, a verified authoritative-source reference, and an exact-revision observation method.",
360
+ };
361
+ }
362
+ if (row.id === "live_data_safety") {
363
+ return {
364
+ passed: Boolean(row.live_write_policy && row.cleanup_residual_count === 0),
365
+ detail: "Live verification needs an explicit no-persistence, scoped-fixture, or authorized-production-write policy and cleanup_residual_count=0.",
366
+ };
367
+ }
368
+ if (row.id === "resume_compatibility") {
369
+ const paidSupportVerified = Boolean(
370
+ row.paid_resume_support && row.paid_resume_support_source &&
371
+ row.evidence_refs.includes(row.paid_resume_support_source) &&
372
+ verifiedRefs.has(row.paid_resume_support_source)
373
+ );
374
+ const paidPathSatisfied = row.paid_resume_support === "not_supported" ||
375
+ row.resume_paths_checked.includes("paid");
376
+ return {
377
+ passed: row.resume_paths_checked.includes("normal") &&
378
+ row.resume_paths_checked.includes("checkpoint") &&
379
+ paidSupportVerified && paidPathSatisfied &&
380
+ row.derived_views_recomputed && row.artifact_content_preserved,
381
+ detail: "Resume compatibility needs normal and checkpoint paths, a verified paid-resume support classification (and the paid path when supported), derived-view recomputation, and artifact-content preservation.",
382
+ };
383
+ }
384
+ return { passed: true, detail: "" };
385
+ }
386
+
387
+ function integerArray(value) {
388
+ return (Array.isArray(value) ? value : []).filter(Number.isInteger);
389
+ }
390
+
391
+ function normalizeLiveWritePolicy(value) {
392
+ const policy = cleanString(value);
393
+ return ["no_persistence", "scoped_fixture", "authorized_production_write"].includes(policy)
394
+ ? policy
395
+ : null;
396
+ }
397
+
398
+ function normalizePaidResumeSupport(value) {
399
+ const support = cleanString(value);
400
+ return ["supported", "not_supported"].includes(support) ? support : null;
401
+ }
402
+
403
+ function normalizeIdentityChecks(value) {
404
+ return (Array.isArray(value) ? value : []).map((item) => ({
405
+ name: cleanString(item?.name),
406
+ state: cleanString(item?.state),
407
+ status: cleanString(item?.status),
408
+ conclusion: cleanString(item?.conclusion),
409
+ })).sort((a, b) => a.name.localeCompare(b.name));
410
+ }
411
+
412
+ function stableStringify(value) {
413
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
414
+ if (value && typeof value === "object") {
415
+ return `{${Object.keys(value).sort().map((key) =>
416
+ `${JSON.stringify(key)}:${stableStringify(value[key])}`
417
+ ).join(",")}}`;
418
+ }
419
+ return JSON.stringify(value);
420
+ }
@@ -3,6 +3,10 @@ import { minimumContext } from "../codex-plugins/simy/skills/agentic-delivery-lo
3
3
  import { contextDigest } from "../codex-plugins/simy/skills/agentic-delivery-loop/scripts/check-acceptance.mjs";
4
4
  import { cleanString, stringArray } from "./shared.js";
5
5
  import { normalizeProblemSolving } from "./problem-solving.js";
6
+ import {
7
+ normalizePrPreparation,
8
+ normalizeSkippedChecks,
9
+ } from "./pr-preparation.js";
6
10
  import {
7
11
  normalizeCriterionEvidence,
8
12
  normalizeUncertaintyVerification,
@@ -43,6 +47,8 @@ export function buildAttempt({ attemptNumber, charter, instruction, promptInterv
43
47
  ),
44
48
  criterion_evidence: normalizeCriterionEvidence(result.criterion_evidence),
45
49
  visual_review: normalizeVisualReview(result.visual_review),
50
+ pr_preparation: normalizePrPreparation(result.pr_preparation),
51
+ skipped_checks: normalizeSkippedChecks(result.skipped_checks),
46
52
  prompt_interventions: promptInterventions,
47
53
  work_log_signals: [],
48
54
  work_log_interventions: [],
package/src/runner.js CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  redactExecutionText,
20
20
  } from "./orchestrator/execution-io.js";
21
21
  import { summarizeCodingLoopEvent } from "./orchestrator/presentation.js";
22
+ import { PR_PREPARATION_CHECKS } from "./orchestrator/pr-preparation.js";
22
23
  import { increaseProviderTokenBudget, refreshBudgetState } from "./orchestrator/budget.js";
23
24
  import { appendEvent } from "./orchestrator/shared.js";
24
25
  import {
@@ -73,6 +74,7 @@ const INTERRUPTED_STATES = new Set([
73
74
  "re_instructing",
74
75
  ]);
75
76
  const RESTORABLE_STATES = new Set([...RESUMABLE_STATES, ...INTERRUPTED_STATES]);
77
+ const PR_PREPARATION_IDS = new Set(PR_PREPARATION_CHECKS.map((row) => row.id));
76
78
 
77
79
  export { LocalRunRegistry };
78
80
 
@@ -1007,9 +1009,90 @@ function redactedAttemptForLedger(attempt, attachments) {
1007
1009
  },
1008
1010
  raw_output: structuredResultForLedger(attempt.raw_output),
1009
1011
  observed_evidence: observedEvidenceForLedger(attempt.observed_evidence),
1012
+ pr_preparation: prPreparationForLedger(attempt.pr_preparation),
1013
+ skipped_checks: skippedChecksForLedger(attempt.skipped_checks),
1010
1014
  };
1011
1015
  }
1012
1016
 
1017
+ function prPreparationForLedger(value) {
1018
+ return (Array.isArray(value) ? value : []).map((row) => ({
1019
+ id: PR_PREPARATION_IDS.has(row?.id) ? row.id : "unrecognized",
1020
+ applicability: row?.applicability || null,
1021
+ passed: row?.passed === true,
1022
+ observation_present: Boolean(row?.observation),
1023
+ reason_present: Boolean(row?.reason),
1024
+ authoritative_source_present: Boolean(row?.authoritative_source),
1025
+ evidence_ref_count: Array.isArray(row?.evidence_refs) ? row.evidence_refs.length : 0,
1026
+ bound_revisions: validatedHashesForLedger(row?.bound_revisions, 40),
1027
+ companion_revisions: (Array.isArray(row?.companion_revisions) ? row.companion_revisions : [])
1028
+ .map((item) => ({
1029
+ repository_present: Boolean(item?.repository),
1030
+ sha: validatedHashForLedger(item?.sha, 40),
1031
+ })),
1032
+ manifest_revisions: (Array.isArray(row?.manifest_revisions) ? row.manifest_revisions : [])
1033
+ .map((item) => ({
1034
+ manifest_present: Boolean(item?.manifest),
1035
+ sha256: validatedHashForLedger(item?.sha256, 64),
1036
+ })),
1037
+ cleanup_residual_count: Number.isInteger(row?.cleanup_residual_count)
1038
+ ? row.cleanup_residual_count
1039
+ : null,
1040
+ security_privacy_check_count: Array.isArray(row?.security_privacy_checks)
1041
+ ? row.security_privacy_checks.length
1042
+ : 0,
1043
+ browser_artifact_count: Array.isArray(row?.browser_artifacts)
1044
+ ? row.browser_artifacts.length
1045
+ : 0,
1046
+ locale_count: Array.isArray(row?.locales_checked) ? row.locales_checked.length : 0,
1047
+ console_error_count: Number.isInteger(row?.console_error_count)
1048
+ ? row.console_error_count
1049
+ : null,
1050
+ network_failure_count: Number.isInteger(row?.network_failure_count)
1051
+ ? row.network_failure_count
1052
+ : null,
1053
+ heading_levels_checked: (Array.isArray(row?.heading_levels_checked)
1054
+ ? row.heading_levels_checked
1055
+ : []).filter((item) => Number.isInteger(item) && item >= 1 && item <= 6),
1056
+ leading_space_checked: row?.leading_space_checked === true,
1057
+ trailing_hash_checked: row?.trailing_hash_checked === true,
1058
+ single_official_checklist: row?.single_official_checklist === true,
1059
+ unverified_model_output_labeled: row?.unverified_model_output_labeled === true,
1060
+ deployment_target_present: Boolean(row?.deployment_target),
1061
+ deployment_target_source_present: Boolean(row?.deployment_target_source),
1062
+ revision_observation_method_present: Boolean(row?.revision_observation_method),
1063
+ live_write_policy: ["no_persistence", "scoped_fixture", "authorized_production_write"]
1064
+ .includes(row?.live_write_policy) ? row.live_write_policy : null,
1065
+ resume_paths_checked: (Array.isArray(row?.resume_paths_checked)
1066
+ ? row.resume_paths_checked
1067
+ : []).filter((item) => ["normal", "paid", "checkpoint"].includes(item)),
1068
+ paid_resume_support: ["supported", "not_supported"].includes(row?.paid_resume_support)
1069
+ ? row.paid_resume_support
1070
+ : null,
1071
+ paid_resume_support_source_present: Boolean(row?.paid_resume_support_source),
1072
+ derived_views_recomputed: row?.derived_views_recomputed === true,
1073
+ artifact_content_preserved: row?.artifact_content_preserved === true,
1074
+ }));
1075
+ }
1076
+
1077
+ function skippedChecksForLedger(value) {
1078
+ return (Array.isArray(value) ? value : []).map((row) => ({
1079
+ name_present: Boolean(row?.name),
1080
+ reason_present: Boolean(row?.reason),
1081
+ impact_present: Boolean(row?.impact),
1082
+ }));
1083
+ }
1084
+
1085
+ function validatedHashesForLedger(value, length) {
1086
+ return stringArrayForLedger(value)
1087
+ .map((item) => validatedHashForLedger(item, length))
1088
+ .filter(Boolean);
1089
+ }
1090
+
1091
+ function validatedHashForLedger(value, length) {
1092
+ const candidate = typeof value === "string" ? value.trim().toLowerCase() : "";
1093
+ return new RegExp(`^[a-f0-9]{${length}}$`).test(candidate) ? candidate : null;
1094
+ }
1095
+
1013
1096
  function redactedEventDetail(value, lifecycleState, attachments) {
1014
1097
  const detail = value && typeof value === "object" ? value : {};
1015
1098
  const result = {};
@@ -1070,7 +1153,7 @@ function observedEvidenceForLedger(value) {
1070
1153
  head_branch: github.head_branch || null,
1071
1154
  head_sha: github.head_sha || null,
1072
1155
  base_branch: github.base_branch || null,
1073
- checks: Array.isArray(github.checks) ? github.checks : [],
1156
+ checks: githubChecksForLedger(github.checks),
1074
1157
  checks_present: github.checks_present === true,
1075
1158
  checks_passing: github.checks_passing === true,
1076
1159
  required_checks_present: github.required_checks_present === true,
@@ -1082,6 +1165,14 @@ function stringArrayForLedger(value) {
1082
1165
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
1083
1166
  }
1084
1167
 
1168
+ function githubChecksForLedger(value) {
1169
+ const allowedStates = new Set(["success", "skipped", "failure", "pending"]);
1170
+ return (Array.isArray(value) ? value : []).map((item) => ({
1171
+ name_present: Boolean(item?.name),
1172
+ state: allowedStates.has(item?.state) ? item.state : "pending",
1173
+ }));
1174
+ }
1175
+
1085
1176
  export function toCompatibleCodingLoopState(state) {
1086
1177
  switch (state) {
1087
1178
  case "risk_classifying":
@@ -104,8 +104,12 @@ export async function installSqmAgentSkill({ home = homedir(), platforms = Objec
104
104
  }
105
105
 
106
106
  export async function inspectCodexPlugin({ home = homedir() } = {}) {
107
- const manifest = await bundledPluginManifest();
108
- const expectedDigest = await directoryDigest(BUNDLED_CODEX_PLUGIN_ROOT, { manifest });
107
+ const overrides = await installedPluginOverrides();
108
+ const manifest = await bundledPluginManifest({ overrides });
109
+ const expectedDigest = await directoryDigest(BUNDLED_CODEX_PLUGIN_ROOT, {
110
+ manifest,
111
+ overrides,
112
+ });
109
113
  const target = codexPluginPath(home);
110
114
  const installedDigest = await directoryDigest(target, { missing: null });
111
115
  const marketplacePath = codexMarketplacePath(home);
@@ -152,8 +156,12 @@ async function installCodexPlugin({ home }) {
152
156
  await rm(staged, { recursive: true, force: true });
153
157
  await rm(backup, { recursive: true, force: true });
154
158
  await cp(BUNDLED_CODEX_PLUGIN_ROOT, staged, { recursive: true, errorOnExist: true });
159
+ const overrides = await installedPluginOverrides();
160
+ for (const [relative, content] of overrides) {
161
+ await writeFile(path.join(staged, relative), content);
162
+ }
155
163
  await writeFile(path.join(staged, ".codex-plugin", "plugin.json"),
156
- `${JSON.stringify(await bundledPluginManifest(), null, 2)}\n`);
164
+ `${JSON.stringify(await bundledPluginManifest({ overrides }), null, 2)}\n`);
157
165
  let backedUp = false;
158
166
  try {
159
167
  if (await pathExists(target)) {
@@ -303,14 +311,36 @@ async function fileDigest(target) {
303
311
  }
304
312
  }
305
313
 
306
- async function bundledPluginManifest() {
314
+ async function bundledPluginManifest({ overrides = new Map() } = {}) {
307
315
  const manifest = JSON.parse(await readFile(path.join(BUNDLED_CODEX_PLUGIN_ROOT, ".codex-plugin", "plugin.json"), "utf8"));
308
316
  // A stable content-derived version refreshes Codex's cache only when the bundle changes.
309
- const digest = await directoryDigest(BUNDLED_CODEX_PLUGIN_ROOT);
317
+ const digest = await directoryDigest(BUNDLED_CODEX_PLUGIN_ROOT, { overrides });
310
318
  return { ...manifest, version: `${manifest.version.split("+")[0]}+codex.${digest}` };
311
319
  }
312
320
 
313
- async function directoryDigest(root, { missing = "throw", manifest } = {}) {
321
+ async function installedPluginOverrides() {
322
+ const relative = "hooks/hooks.json";
323
+ const source = JSON.parse(await readFile(path.join(BUNDLED_CODEX_PLUGIN_ROOT, relative), "utf8"));
324
+ const commandPrefix = "node ";
325
+ const runtime = shellQuote(process.execPath);
326
+ for (const groups of Object.values(source.hooks || {})) {
327
+ for (const group of groups || []) {
328
+ for (const hook of group.hooks || []) {
329
+ if (typeof hook.command === "string" && hook.command.startsWith(commandPrefix)) {
330
+ hook.command = `${runtime} ${hook.command.slice(commandPrefix.length)}`;
331
+ }
332
+ }
333
+ }
334
+ }
335
+ return new Map([[relative, `${JSON.stringify(source, null, 2)}\n`]]);
336
+ }
337
+
338
+ function shellQuote(value) {
339
+ if (process.platform === "win32") return `"${value.replaceAll('"', '""')}"`;
340
+ return `'${value.replaceAll("'", "'\\''")}'`;
341
+ }
342
+
343
+ async function directoryDigest(root, { missing = "throw", manifest, overrides = new Map() } = {}) {
314
344
  let rootStat;
315
345
  try {
316
346
  rootStat = await lstat(root);
@@ -336,7 +366,9 @@ async function directoryDigest(root, { missing = "throw", manifest } = {}) {
336
366
  if (!entry.isFile()) throw new Error(`${target} must be a regular file.`);
337
367
  const content = manifest && relative === ".codex-plugin/plugin.json"
338
368
  ? `${JSON.stringify(manifest, null, 2)}\n`
339
- : await readFile(target);
369
+ : overrides.has(relative)
370
+ ? overrides.get(relative)
371
+ : await readFile(target);
340
372
  hash.update(relative).update("\0").update(content).update("\0");
341
373
  }
342
374
  };