@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.
- package/README.md +17 -2
- package/package.json +2 -2
- package/src/agent.js +33 -1
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/hooks/README.md +17 -0
- package/src/codex-plugins/simy/hooks/connection-reminder.mjs +184 -0
- package/src/codex-plugins/simy/hooks/hooks.json +27 -0
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/SKILL.md +56 -0
- package/src/connection-discovery.js +49 -0
- package/src/orchestrator/audit.js +47 -5
- package/src/orchestrator/contract.js +2 -0
- package/src/orchestrator/evidence.js +64 -5
- package/src/orchestrator/independent-audit.js +10 -0
- package/src/orchestrator/index.js +7 -0
- package/src/orchestrator/instruction.js +21 -1
- package/src/orchestrator/loop.js +3 -0
- package/src/orchestrator/pr-preparation.js +420 -0
- package/src/orchestrator/result.js +6 -0
- package/src/runner.js +92 -1
- package/src/sqm/agent-skill.js +39 -7
|
@@ -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:
|
|
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":
|
package/src/sqm/agent-skill.js
CHANGED
|
@@ -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
|
|
108
|
-
const
|
|
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
|
|
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
|
-
:
|
|
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
|
};
|