@awak-app/simy-cli 0.7.15 → 0.7.17
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 +225 -98
- package/package.json +5 -3
- package/src/agent.js +129 -7
- package/src/backend-executable.js +7 -6
- package/src/bounded-local-task-subtask-pool.js +32 -6
- package/src/bounded-local-task-subtasks-contract.js +1 -1
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/.codex-plugin/plugin.json +2 -2
- package/src/codex-plugins/simy/hooks/README.md +8 -3
- package/src/codex-plugins/simy/hooks/delivery-loop.mjs +302 -78
- package/src/codex-plugins/simy/hooks/hooks.json +1 -1
- package/src/codex-plugins/simy/hooks/loop-kernel/conformance.json +136 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/index.mjs +42 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/outcome-review.mjs +31 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/pin.json +6 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/policy.json +141 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/task-pool.mjs +34 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/work-frontier.mjs +132 -0
- package/src/codex-plugins/simy/hooks/loop-kernel.mjs +6 -0
- package/src/codex-plugins/simy/hooks/outcome-progress.mjs +16 -5
- package/src/codex-plugins/simy/hooks/purpose-completion-contract.mjs +10 -0
- package/src/codex-plugins/simy/hooks/purpose-cycle.mjs +12 -11
- package/src/codex-plugins/simy/hooks/purpose-evidence.mjs +28 -38
- package/src/codex-plugins/simy/hooks/purpose-rule-policy.mjs +1 -2
- package/src/codex-plugins/simy/hooks/purpose-rule-review.mjs +5 -63
- package/src/codex-plugins/simy/hooks/purpose-search.mjs +21 -4
- package/src/codex-plugins/simy/hooks/result-cycle.mjs +166 -0
- package/src/codex-plugins/simy/hooks/scene-reasoning.mjs +98 -12
- package/src/codex-plugins/simy/hooks/user-experience.mjs +26 -0
- package/src/codex-plugins/simy/hooks/work-frontier.mjs +2 -0
- package/src/codex-plugins/simy/skills/diagnose/SKILL.md +10 -4
- package/src/coding-run-recovery.js +4 -2
- package/src/delivery-hooks/claude-workflow.md +10 -7
- package/src/delivery-hooks/command.js +76 -10
- package/src/delivery-hooks/consistency-monitor.js +20 -4
- package/src/delivery-hooks/consult.js +60 -0
- package/src/delivery-hooks/diagnosis.js +2 -2
- package/src/delivery-hooks/legacy-hook-routing.js +46 -8
- package/src/delivery-hooks/local-runtime.js +81 -1
- package/src/delivery-hooks/local-supervisor.js +13 -4
- package/src/delivery-hooks/repair-continuation.js +80 -0
- package/src/delivery-hooks/repair.js +164 -0
- package/src/delivery-hooks/supervisor-handover.js +84 -0
- package/src/delivery-hooks/supervisor-owner.js +3 -2
- package/src/delivery-hooks/supervisor-service.js +38 -6
- package/src/delivery-hooks/supervisor-watchdog.js +15 -4
- package/src/delivery-hooks/supervisor.js +151 -18
- package/src/desktop-access.js +2 -1
- package/src/desktop-host.js +35 -2
- package/src/durable-local-task-worker.js +372 -58
- package/src/execution-capability-contract.js +15 -1
- package/src/index.js +3 -3
- package/src/local-photos-read.swift +33 -0
- package/src/local-photos.js +47 -0
- package/src/local-task-attachment-store.js +31 -0
- package/src/local-task-destination.js +174 -0
- package/src/local-task.js +119 -7
- package/src/native-delegation-contract.js +68 -0
- package/src/native-provider-identity.js +60 -0
- package/src/orchestrator/audit.js +24 -323
- package/src/orchestrator/budget.js +22 -11
- package/src/orchestrator/charter-validation.js +3 -41
- package/src/orchestrator/checklist-continuation.js +2 -14
- package/src/orchestrator/completion-contract.js +4 -49
- package/src/orchestrator/contract.js +5 -9
- package/src/orchestrator/delivery/WORKFLOW.md +30 -710
- package/src/orchestrator/delivery/references/acceptance-evidence.md +4 -216
- package/src/orchestrator/delivery/references/change-boundary-matrix.md +4 -103
- package/src/orchestrator/delivery/references/chrome-user-verification.md +5 -133
- package/src/orchestrator/delivery/references/cross-repository-control.md +4 -74
- package/src/orchestrator/delivery/references/delivery-levels.md +3 -117
- package/src/orchestrator/delivery/references/local-sqm-evidence.md +1 -1
- package/src/orchestrator/delivery/references/minimum-completion.md +4 -144
- package/src/orchestrator/delivery/references/pr-closure.md +4 -200
- package/src/orchestrator/delivery/references/shared-system-specifications.md +4 -161
- package/src/orchestrator/delivery/references/state-and-receipts.md +4 -245
- package/src/orchestrator/delivery/references/state-model.md +4 -185
- package/src/orchestrator/delivery/references/ux-specification.md +4 -95
- package/src/orchestrator/delivery-level.js +2 -10
- package/src/orchestrator/evidence.js +2 -3
- package/src/orchestrator/independent-audit.js +15 -164
- package/src/orchestrator/instruction.js +19 -254
- package/src/orchestrator/loop.js +35 -95
- package/src/orchestrator/outcome-monitor.js +21 -0
- package/src/orchestrator/pr-closure.js +2 -16
- package/src/orchestrator/recovery.js +32 -2
- package/src/orchestrator/ux-specification.js +3 -12
- package/src/provider-delegation.js +336 -0
- package/src/runner.js +5 -2
- package/src/session-supervision/acceptance-reads.js +34 -0
- package/src/session-supervision/artifact-observer.js +138 -0
- package/src/session-supervision/browser-cdp.js +59 -0
- package/src/session-supervision/browser-recovery.js +97 -0
- package/src/session-supervision/codex-app-server.js +61 -22
- package/src/session-supervision/completion-artifacts.js +44 -0
- package/src/session-supervision/consistency-auditor.js +8 -11
- package/src/session-supervision/continuation-analysis.js +47 -28
- package/src/session-supervision/final-response-recovery.js +5 -0
- package/src/session-supervision/index.js +22 -5
- package/src/session-supervision/interaction-proxy.js +250 -0
- package/src/session-supervision/interaction-task.js +189 -0
- package/src/session-supervision/native-interaction.js +102 -0
- package/src/session-supervision/provider-failure.js +40 -0
- package/src/session-supervision/purpose-instruction.js +14 -14
- package/src/session-supervision/purpose-recovery.js +32 -12
- package/src/session-supervision/purpose-verification.js +50 -216
- package/src/session-supervision/recovery-frontier.js +55 -0
- package/src/session-supervision/stop-notifications.js +8 -8
- package/src/sqm/agent-skill.js +1 -1
- package/src/sqm/checkers.js +12 -6
- package/src/sqm/claude-plugin-update.js +22 -8
- package/src/sqm/claude-plugin.js +6 -0
- package/src/sqm/local-tests.js +2 -1
- package/src/sqm/plugin-cache.js +67 -0
- package/src/sqm/proof.js +25 -0
- package/src/sqm/report.js +1 -0
- package/src/sqm/workflow-onboarding.js +3 -39
- package/src/sqm/workflow-update.js +17 -6
- package/src/startup-preflight.js +2 -20
- package/src/team-status-fence-store.js +32 -0
- package/src/team-threads.js +457 -0
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
bullets,
|
|
7
|
-
evidenceKind,
|
|
8
|
-
hasAllowedPrefix,
|
|
9
|
-
stringArray,
|
|
10
|
-
} from "./shared.js";
|
|
11
|
-
import { evaluateRetryProblemSolving } from "./problem-solving.js";
|
|
12
|
-
import {
|
|
13
|
-
evaluatePrPreparation,
|
|
14
|
-
independentAuditEvidenceIdentity,
|
|
15
|
-
skippedChecksDisclosure,
|
|
16
|
-
} from "./pr-preparation.js";
|
|
17
|
-
import {
|
|
18
|
-
completionEvidenceChecks,
|
|
19
|
-
hasUserOutcomeProof,
|
|
20
|
-
normalizeVisualReview,
|
|
21
|
-
} from "./completion-contract.js";
|
|
1
|
+
import { bullets } from "./shared.js";
|
|
2
|
+
import { independentAuditEvidenceIdentity } from "./pr-preparation.js";
|
|
3
|
+
import { completionEvidenceChecks } from "./completion-contract.js";
|
|
22
4
|
|
|
23
5
|
export async function auditAttempt(charter, attempt, { previousAttempt = null, sqm = null } = {}) {
|
|
24
6
|
const checks = [];
|
|
@@ -97,127 +79,18 @@ export async function auditAttempt(charter, attempt, { previousAttempt = null, s
|
|
|
97
79
|
addCheck(
|
|
98
80
|
"tests_recorded_and_passing",
|
|
99
81
|
"Expected tests are recorded as passing",
|
|
100
|
-
attempt.tests_run.length
|
|
101
|
-
attempt.tests_passed === true &&
|
|
82
|
+
(charter.expected_tests.length === 0 && attempt.tests_run.length === 0 || attempt.tests_passed === true) &&
|
|
102
83
|
missingExpectedTests.length === 0,
|
|
103
84
|
missingExpectedTests.length > 0
|
|
104
85
|
? `Missing expected tests: ${missingExpectedTests.join(", ")}`
|
|
105
86
|
: attempt.tests_run.join(", ") || "No passing test command recorded.",
|
|
106
87
|
);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
local.commit_headline || "Local commit headline missing.",
|
|
112
|
-
);
|
|
113
|
-
const changedFiles = stringArray(local.changed_files);
|
|
114
|
-
const reportedFiles = stringArray(attempt.raw_output.changed_files);
|
|
115
|
-
addCheck(
|
|
116
|
-
"changed_files_match_git",
|
|
117
|
-
"Reported changed files match the local diff",
|
|
118
|
-
local.available === true && sameStrings(changedFiles, reportedFiles),
|
|
119
|
-
`Reported: ${reportedFiles.join(", ") || "none"}; local: ${changedFiles.join(", ") || "none"}.`,
|
|
120
|
-
);
|
|
121
|
-
const unintendedArtifacts = changedFiles.filter((file) =>
|
|
122
|
-
/(^|\/)(\.evidence|evidence|screenshots?|videos?)(\/|$)|\.(png|jpe?g|webp|gif|mp4|mov|webm)$/i.test(
|
|
123
|
-
file,
|
|
124
|
-
),
|
|
125
|
-
);
|
|
126
|
-
addCheck(
|
|
127
|
-
"no_unintended_artifacts",
|
|
128
|
-
"No local evidence artifacts committed",
|
|
129
|
-
unintendedArtifacts.length === 0,
|
|
130
|
-
unintendedArtifacts.length > 0
|
|
131
|
-
? `Unexpected artifacts: ${unintendedArtifacts.join(", ")}`
|
|
132
|
-
: "No committed local evidence artifacts reported.",
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const retryProblemSolving = evaluateRetryProblemSolving(attempt, previousAttempt);
|
|
136
|
-
for (const check of retryProblemSolving.checks) {
|
|
137
|
-
addCheck(check.id, check.label, check.passed, check.detail, check.severity);
|
|
138
|
-
}
|
|
139
|
-
attempt.retry_problem_solving = retryProblemSolving;
|
|
140
|
-
|
|
141
|
-
const prPreparation = evaluatePrPreparation(charter, attempt);
|
|
142
|
-
for (const preparationCheck of prPreparation.checks) {
|
|
143
|
-
addCheck(
|
|
144
|
-
`pr_preparation_${preparationCheck.id}`,
|
|
145
|
-
preparationCheck.label,
|
|
146
|
-
preparationCheck.passed,
|
|
147
|
-
preparationCheck.detail,
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
attempt.pr_preparation_gate = prPreparation;
|
|
151
|
-
|
|
152
|
-
for (const check of [...completionEvidenceChecks(charter, attempt), minimumCompletionCheck(charter, attempt)]) {
|
|
153
|
-
addCheck(
|
|
154
|
-
check.id,
|
|
155
|
-
check.label,
|
|
156
|
-
check.passed,
|
|
157
|
-
check.detail,
|
|
158
|
-
"blocker",
|
|
159
|
-
"auto",
|
|
160
|
-
check.evidence_refs,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (attempt.independent_audit) {
|
|
165
|
-
addCheck("minimum_review_current", "Independent review covers the current evidence",
|
|
166
|
-
Boolean(attempt.independent_audit.evidence_identity_sha256 &&
|
|
167
|
-
attempt.independent_audit.evidence_identity_sha256 === independentAuditEvidenceIdentity(attempt)),
|
|
168
|
-
"Changed head, base, CI, skip disclosure, PR preparation, companion revision, or minimum evidence requires a fresh independent review.");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const evidenceRequired = charter.expected_evidence.includes("ui_evidence_path");
|
|
172
|
-
const evidenceArtifact = await inspectEvidence(charter, attempt.ui_evidence_path);
|
|
173
|
-
if (evidenceRequired || attempt.ui_evidence_path) {
|
|
174
|
-
addCheck(
|
|
175
|
-
"evidence_exists_local_only",
|
|
176
|
-
"Local evidence exists under the required root",
|
|
177
|
-
Boolean(
|
|
178
|
-
evidenceArtifact?.exists &&
|
|
179
|
-
evidenceArtifact.under_required_root &&
|
|
180
|
-
evidenceArtifact.size_bytes > 0,
|
|
181
|
-
),
|
|
182
|
-
evidenceArtifact?.summary || "Local evidence path missing.",
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
if (charter.evidence_policy?.browser_required) {
|
|
186
|
-
addCheck(
|
|
187
|
-
"browser_visual_evidence_present",
|
|
188
|
-
"Browser screenshot or video evidence is present",
|
|
189
|
-
Boolean(evidenceArtifact?.visual_file_count > 0),
|
|
190
|
-
evidenceArtifact?.visual_file_count > 0
|
|
191
|
-
? `${evidenceArtifact.visual_file_count} screenshot or video artifact(s) found.`
|
|
192
|
-
: "UI and browser-extension work requires a real screenshot or video; a path, text file, or empty directory is not sufficient.",
|
|
193
|
-
);
|
|
194
|
-
const visualReview = normalizeVisualReview(attempt.visual_review);
|
|
195
|
-
const visualArtifactReview = await inspectVisualReviewArtifacts(charter, visualReview);
|
|
196
|
-
addCheck(
|
|
197
|
-
"visual_content_inspected",
|
|
198
|
-
"Visual evidence content was inspected against acceptance criteria",
|
|
199
|
-
Boolean(
|
|
200
|
-
visualReview.inspected &&
|
|
201
|
-
visualReview.artifacts.length > 0 &&
|
|
202
|
-
visualReview.acceptance_observations.length > 0,
|
|
203
|
-
),
|
|
204
|
-
visualReview.inspected && visualReview.acceptance_observations.length > 0
|
|
205
|
-
? visualReview.acceptance_observations.join("; ")
|
|
206
|
-
: "UI evidence must be opened and inspected; record the artifact paths and concrete acceptance observations.",
|
|
207
|
-
"blocker",
|
|
208
|
-
"auto",
|
|
209
|
-
visualReview.artifacts,
|
|
210
|
-
);
|
|
211
|
-
addCheck(
|
|
212
|
-
"visual_review_artifacts_verified",
|
|
213
|
-
"Claimed visual-review artifacts are real local media",
|
|
214
|
-
visualArtifactReview.valid,
|
|
215
|
-
visualArtifactReview.summary,
|
|
216
|
-
"blocker",
|
|
217
|
-
"auto",
|
|
218
|
-
visualArtifactReview.verified,
|
|
219
|
-
);
|
|
88
|
+
// Completion depends on the requested behavior, not preparation matrices,
|
|
89
|
+
// uncertainty forms, screenshots in a prescribed folder or audit receipts.
|
|
90
|
+
for (const check of completionEvidenceChecks(charter, attempt)) {
|
|
91
|
+
addCheck(check.id, check.label, check.passed, check.detail);
|
|
220
92
|
}
|
|
93
|
+
const evidenceArtifact = null;
|
|
221
94
|
|
|
222
95
|
const findings = checks
|
|
223
96
|
.filter((check) => !check.passed && check.severity !== "advisory")
|
|
@@ -234,7 +107,7 @@ export async function auditAttempt(charter, attempt, { previousAttempt = null, s
|
|
|
234
107
|
auto_fix_hint:
|
|
235
108
|
check.id === "executor_succeeded" && attempt.raw_output.blocked
|
|
236
109
|
? null
|
|
237
|
-
: `
|
|
110
|
+
: `Resolve ${check.label.toLowerCase()} and check the result.`,
|
|
238
111
|
}));
|
|
239
112
|
const passed = findings.length === 0;
|
|
240
113
|
const requiresHuman = findings.some((finding) => finding.repairability === "manual");
|
|
@@ -250,17 +123,16 @@ export async function auditAttempt(charter, attempt, { previousAttempt = null, s
|
|
|
250
123
|
return {
|
|
251
124
|
passed,
|
|
252
125
|
summary: passed
|
|
253
|
-
? "
|
|
254
|
-
: "
|
|
126
|
+
? "Requested behavior and relevant tests passed."
|
|
127
|
+
: "Requested behavior or relevant checks remain incomplete.",
|
|
255
128
|
evaluations: [
|
|
256
129
|
{
|
|
257
130
|
kind: "intent_fit",
|
|
258
131
|
passed,
|
|
259
|
-
score:
|
|
260
|
-
checks.length === 0 ? 1 : checks.filter((check) => check.passed).length / checks.length,
|
|
132
|
+
score: null,
|
|
261
133
|
summary: passed
|
|
262
|
-
? "
|
|
263
|
-
: "
|
|
134
|
+
? "Requested behavior checks passed."
|
|
135
|
+
: "Requested behavior checks remain incomplete.",
|
|
264
136
|
findings,
|
|
265
137
|
evidence_refs: [attempt.pr_url, attempt.commit_sha, attempt.ui_evidence_path].filter(
|
|
266
138
|
Boolean,
|
|
@@ -330,26 +202,12 @@ export function evaluatePrReadiness(charter, attempt) {
|
|
|
330
202
|
const github = attempt.observed_evidence?.github || {};
|
|
331
203
|
const local = attempt.observed_evidence?.local || {};
|
|
332
204
|
const independentAudit = attempt.independent_audit;
|
|
333
|
-
const minimum = minimumCompletionCheck(charter, attempt);
|
|
334
|
-
const preparation = evaluatePrPreparation(charter, attempt);
|
|
335
|
-
const skippedDisclosure = skippedChecksDisclosure(
|
|
336
|
-
github.checks,
|
|
337
|
-
attempt.skipped_checks,
|
|
338
|
-
);
|
|
339
205
|
const checks = [
|
|
340
|
-
readinessCheck(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
"Independent review must observe the original user outcome and cite its evidence."),
|
|
346
|
-
readinessCheck("implementation_gate", attempt.audit?.passed === true, "Implementation gate must pass."),
|
|
347
|
-
readinessCheck("pr_preparation_gate", preparation.passed, preparation.detail),
|
|
348
|
-
readinessCheck(
|
|
349
|
-
"independent_ai_audit",
|
|
350
|
-
independentAudit?.passed === true,
|
|
351
|
-
independentAudit?.summary || "Independent AI audit has not passed.",
|
|
352
|
-
),
|
|
206
|
+
readinessCheck("implementation_gate", attempt.audit?.passed === true, "Requested behavior and relevant tests must pass."),
|
|
207
|
+
...(charter.independent_review_required === true ? [
|
|
208
|
+
readinessCheck("requested_review", independentAudit?.passed === true && independentAudit.evidence_identity_sha256 === independentAuditEvidenceIdentity(attempt),
|
|
209
|
+
"The explicitly requested review has not passed."),
|
|
210
|
+
] : []),
|
|
353
211
|
readinessCheck(
|
|
354
212
|
"github_evidence",
|
|
355
213
|
github.available === true,
|
|
@@ -361,11 +219,7 @@ export function evaluatePrReadiness(charter, attempt) {
|
|
|
361
219
|
`Reported ${attempt.pr_url || "missing"}; GitHub reports ${github.url || "missing"}.`,
|
|
362
220
|
),
|
|
363
221
|
readinessCheck("pr_not_draft", github.available && github.is_draft === false, "PR is still draft."),
|
|
364
|
-
|
|
365
|
-
"pr_title_prefix",
|
|
366
|
-
hasAllowedPrefix(github.title),
|
|
367
|
-
github.title || "GitHub PR title is missing.",
|
|
368
|
-
),
|
|
222
|
+
|
|
369
223
|
readinessCheck(
|
|
370
224
|
"pr_repository_matches",
|
|
371
225
|
normalizeRepository(github.repository) === normalizeRepository(charter.repository),
|
|
@@ -408,11 +262,7 @@ export function evaluatePrReadiness(charter, attempt) {
|
|
|
408
262
|
),
|
|
409
263
|
readinessCheck("status_checks_present", github.checks_present === true, "No PR status checks found."),
|
|
410
264
|
readinessCheck("status_checks_passing", github.checks_passing === true, "PR checks are pending or failing."),
|
|
411
|
-
|
|
412
|
-
"skipped_checks_disclosed",
|
|
413
|
-
skippedDisclosure.passed,
|
|
414
|
-
skippedDisclosure.detail,
|
|
415
|
-
),
|
|
265
|
+
|
|
416
266
|
readinessCheck(
|
|
417
267
|
"required_checks_present",
|
|
418
268
|
github.required_checks_present === true,
|
|
@@ -429,8 +279,8 @@ export function evaluatePrReadiness(charter, attempt) {
|
|
|
429
279
|
ready,
|
|
430
280
|
state: ready ? "merge_ready" : "pr_ready_for_review",
|
|
431
281
|
summary: ready
|
|
432
|
-
? "PR
|
|
433
|
-
: attempt.audit?.passed
|
|
282
|
+
? "PR behavior and required checks pass."
|
|
283
|
+
: attempt.audit?.passed
|
|
434
284
|
? "Implementation is verified, but the PR is not merge-ready."
|
|
435
285
|
: "Implementation verification is incomplete; resolve the pending evidence before promotion.",
|
|
436
286
|
checks,
|
|
@@ -442,12 +292,6 @@ function readinessCheck(id, passed, detail) {
|
|
|
442
292
|
return { id, passed: Boolean(passed), detail };
|
|
443
293
|
}
|
|
444
294
|
|
|
445
|
-
function sameStrings(left, right) {
|
|
446
|
-
const a = [...new Set(left)].sort();
|
|
447
|
-
const b = [...new Set(right)].sort();
|
|
448
|
-
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
295
|
function normalizeRepository(value) {
|
|
452
296
|
return String(value || "")
|
|
453
297
|
.trim()
|
|
@@ -455,146 +299,3 @@ function normalizeRepository(value) {
|
|
|
455
299
|
.replace(/\.git$/, "")
|
|
456
300
|
.toLowerCase();
|
|
457
301
|
}
|
|
458
|
-
|
|
459
|
-
async function inspectEvidence(charter, evidencePath) {
|
|
460
|
-
if (!evidencePath) return null;
|
|
461
|
-
const resolved = path.resolve(evidencePath);
|
|
462
|
-
const root = charter.ui_evidence_root ? path.resolve(charter.ui_evidence_root) : null;
|
|
463
|
-
let fileStat = null;
|
|
464
|
-
try {
|
|
465
|
-
fileStat = await stat(resolved);
|
|
466
|
-
} catch {
|
|
467
|
-
// Missing evidence is represented in the returned artifact.
|
|
468
|
-
}
|
|
469
|
-
const underRoot = Boolean(
|
|
470
|
-
root && (resolved === root || resolved.startsWith(`${root}${path.sep}`)),
|
|
471
|
-
);
|
|
472
|
-
const directoryEvidence = fileStat?.isDirectory()
|
|
473
|
-
? await scanEvidenceDirectory(resolved)
|
|
474
|
-
: null;
|
|
475
|
-
const directKind = fileStat?.isFile() ? evidenceKind(resolved) : "unknown";
|
|
476
|
-
const visualFileCount = fileStat?.isFile()
|
|
477
|
-
? (await isVisualEvidenceFile(resolved))
|
|
478
|
-
? 1
|
|
479
|
-
: 0
|
|
480
|
-
: directoryEvidence?.visualFileCount ?? 0;
|
|
481
|
-
const sizeBytes = fileStat?.isDirectory()
|
|
482
|
-
? directoryEvidence?.sizeBytes ?? 0
|
|
483
|
-
: fileStat?.size ?? 0;
|
|
484
|
-
return {
|
|
485
|
-
path: resolved,
|
|
486
|
-
kind: fileStat?.isDirectory() ? "directory" : directKind,
|
|
487
|
-
exists: Boolean(fileStat),
|
|
488
|
-
under_required_root: underRoot,
|
|
489
|
-
size_bytes: sizeBytes,
|
|
490
|
-
file_count: fileStat?.isFile() ? 1 : directoryEvidence?.fileCount ?? 0,
|
|
491
|
-
visual_file_count: visualFileCount,
|
|
492
|
-
git_tracked: false,
|
|
493
|
-
summary:
|
|
494
|
-
fileStat && underRoot && sizeBytes > 0
|
|
495
|
-
? `Evidence exists under the required local root; ${visualFileCount} visual artifact(s) found.`
|
|
496
|
-
: "Evidence is missing, empty, or outside the required local root.",
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
async function scanEvidenceDirectory(directory) {
|
|
501
|
-
const pending = [directory];
|
|
502
|
-
let fileCount = 0;
|
|
503
|
-
let visualFileCount = 0;
|
|
504
|
-
let sizeBytes = 0;
|
|
505
|
-
while (pending.length > 0 && fileCount < 500) {
|
|
506
|
-
const current = pending.shift();
|
|
507
|
-
if (!current) break;
|
|
508
|
-
let entries;
|
|
509
|
-
try {
|
|
510
|
-
entries = await readdir(current, { withFileTypes: true });
|
|
511
|
-
} catch {
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
for (const entry of entries) {
|
|
515
|
-
if (entry.isSymbolicLink()) continue;
|
|
516
|
-
const candidate = path.join(current, entry.name);
|
|
517
|
-
if (entry.isDirectory()) {
|
|
518
|
-
pending.push(candidate);
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
if (!entry.isFile()) continue;
|
|
522
|
-
fileCount += 1;
|
|
523
|
-
if (await isVisualEvidenceFile(candidate)) visualFileCount += 1;
|
|
524
|
-
try {
|
|
525
|
-
sizeBytes += (await stat(candidate)).size;
|
|
526
|
-
} catch {
|
|
527
|
-
// A file disappearing during inspection is simply not counted toward its size.
|
|
528
|
-
}
|
|
529
|
-
if (fileCount >= 500) break;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return { fileCount, visualFileCount, sizeBytes };
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
async function isVisualEvidenceFile(filePath) {
|
|
536
|
-
if (!["image", "video"].includes(evidenceKind(filePath))) return false;
|
|
537
|
-
const header = Buffer.alloc(16);
|
|
538
|
-
let handle;
|
|
539
|
-
try {
|
|
540
|
-
handle = await open(filePath, "r");
|
|
541
|
-
const { bytesRead } = await handle.read(header, 0, header.length, 0);
|
|
542
|
-
if (bytesRead === 0) return false;
|
|
543
|
-
} catch {
|
|
544
|
-
return false;
|
|
545
|
-
} finally {
|
|
546
|
-
try {
|
|
547
|
-
await handle?.close();
|
|
548
|
-
} catch {
|
|
549
|
-
// The evidence file may disappear while it is being inspected.
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const ascii = header.toString("ascii");
|
|
553
|
-
return (
|
|
554
|
-
header.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) ||
|
|
555
|
-
header.subarray(0, 3).equals(Buffer.from([255, 216, 255])) ||
|
|
556
|
-
ascii.startsWith("GIF87a") ||
|
|
557
|
-
ascii.startsWith("GIF89a") ||
|
|
558
|
-
(ascii.startsWith("RIFF") && ascii.slice(8, 12) === "WEBP") ||
|
|
559
|
-
ascii.slice(4, 8) === "ftyp" ||
|
|
560
|
-
header.subarray(0, 4).equals(Buffer.from([26, 69, 223, 163]))
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async function inspectVisualReviewArtifacts(charter, visualReview) {
|
|
565
|
-
const root = charter.ui_evidence_root ? path.resolve(charter.ui_evidence_root) : null;
|
|
566
|
-
const verified = [];
|
|
567
|
-
const invalid = [];
|
|
568
|
-
for (const artifact of visualReview.artifacts) {
|
|
569
|
-
const resolved = path.resolve(artifact);
|
|
570
|
-
const underRoot = Boolean(
|
|
571
|
-
root && (resolved === root || resolved.startsWith(`${root}${path.sep}`)),
|
|
572
|
-
);
|
|
573
|
-
let fileStat = null;
|
|
574
|
-
try {
|
|
575
|
-
fileStat = await stat(resolved);
|
|
576
|
-
} catch {
|
|
577
|
-
// The invalid reason below covers missing files.
|
|
578
|
-
}
|
|
579
|
-
if (
|
|
580
|
-
!underRoot ||
|
|
581
|
-
!fileStat?.isFile() ||
|
|
582
|
-
fileStat.size <= 0 ||
|
|
583
|
-
!(await isVisualEvidenceFile(resolved))
|
|
584
|
-
) {
|
|
585
|
-
invalid.push(artifact);
|
|
586
|
-
continue;
|
|
587
|
-
}
|
|
588
|
-
verified.push(resolved);
|
|
589
|
-
}
|
|
590
|
-
return {
|
|
591
|
-
valid: visualReview.artifacts.length > 0 && invalid.length === 0,
|
|
592
|
-
verified,
|
|
593
|
-
summary:
|
|
594
|
-
visualReview.artifacts.length === 0
|
|
595
|
-
? "No inspected visual artifact paths were recorded."
|
|
596
|
-
: invalid.length > 0
|
|
597
|
-
? `Missing, non-visual, or out-of-root artifacts: ${invalid.join(", ")}`
|
|
598
|
-
: `${verified.length} inspected visual artifact(s) exist under the required local root with valid media signatures.`,
|
|
599
|
-
};
|
|
600
|
-
}
|
|
@@ -67,7 +67,7 @@ export function extendProgressCeilings(snapshot, changes = {}) {
|
|
|
67
67
|
const provided = Object.entries(changes).filter(([, value]) => value !== undefined);
|
|
68
68
|
if (!provided.length) throw new Error("Explicitly increase at least one hard ceiling.");
|
|
69
69
|
for (const [field, value] of provided) {
|
|
70
|
-
const previous = field === "provider_token_ceiling" ?
|
|
70
|
+
const previous = field === "provider_token_ceiling" ? current.hard_token_ceiling : current.hard_attempt_ceiling;
|
|
71
71
|
const used = field === "provider_token_ceiling" ? current.tokens_used : (snapshot.attempts || []).length;
|
|
72
72
|
if (!Number.isSafeInteger(value) || value <= previous || value <= used) {
|
|
73
73
|
throw new Error("Each requested hard ceiling must be an integer higher than its current limit and recorded usage.");
|
|
@@ -98,11 +98,12 @@ export function budgetState(snapshot, pendingAttempt = null) {
|
|
|
98
98
|
const retriesUsed = Math.max(attemptsUsed - 1, 0);
|
|
99
99
|
const policy = progressLimits(snapshot?.charter);
|
|
100
100
|
// Persisted thresholds remain readable for old contracts and usage reports.
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
101
|
+
// Warning thresholds never prevent useful work. Separate safety ceilings
|
|
102
|
+
// bound subsequent provider calls without interrupting an active process.
|
|
103
|
+
const hardTokenCeiling = policy.provider_token_ceiling;
|
|
104
|
+
const hardAttemptCeiling = policy.attempt_ceiling;
|
|
105
|
+
const tokenExhausted = tokensUsed >= hardTokenCeiling;
|
|
106
|
+
const retryExhausted = attemptsUsed >= hardAttemptCeiling;
|
|
106
107
|
return {
|
|
107
108
|
schema_version: 1,
|
|
108
109
|
token_budget: tokenBudget,
|
|
@@ -115,17 +116,18 @@ export function budgetState(snapshot, pendingAttempt = null) {
|
|
|
115
116
|
retry_exhausted: retryExhausted,
|
|
116
117
|
exhausted: tokenExhausted || retryExhausted,
|
|
117
118
|
exhausted_reason: tokenExhausted
|
|
118
|
-
? "
|
|
119
|
+
? "provider_token_ceiling_reached"
|
|
119
120
|
: retryExhausted
|
|
120
|
-
? "
|
|
121
|
+
? "attempt_ceiling_reached"
|
|
121
122
|
: null,
|
|
122
123
|
continuation_policy: policy.continuation_policy,
|
|
123
124
|
local_limits_removed: true,
|
|
125
|
+
safety_ceilings_enforced: true,
|
|
124
126
|
token_warning: tokensUsed >= tokenBudget,
|
|
125
127
|
retry_warning: attemptsUsed >= retryBudget + 1,
|
|
126
128
|
hard_token_ceiling: hardTokenCeiling,
|
|
127
129
|
hard_attempt_ceiling: hardAttemptCeiling,
|
|
128
|
-
hard_tokens_remaining:
|
|
130
|
+
hard_tokens_remaining: Math.max(hardTokenCeiling - tokensUsed, 0),
|
|
129
131
|
};
|
|
130
132
|
}
|
|
131
133
|
|
|
@@ -146,8 +148,17 @@ export function refreshBudgetState(snapshot, pendingAttempt = null) {
|
|
|
146
148
|
return state;
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
export function canStartProvider(snapshot) {
|
|
150
|
-
|
|
151
|
+
export function canStartProvider(snapshot, { attemptNumber, phase = "executor" } = {}) {
|
|
152
|
+
const budget = refreshBudgetState(snapshot);
|
|
153
|
+
if (budget.token_exhausted) {
|
|
154
|
+
return { allowed: false, reason: "provider_token_ceiling_reached", budget };
|
|
155
|
+
}
|
|
156
|
+
// The reviewer belongs to the existing attempt, including the last allowed
|
|
157
|
+
// attempt. Only a new executor attempt consumes another attempt allowance.
|
|
158
|
+
if (phase === "executor" && (budget.retry_exhausted || attemptNumber > budget.hard_attempt_ceiling)) {
|
|
159
|
+
return { allowed: false, reason: "attempt_ceiling_reached", budget };
|
|
160
|
+
}
|
|
161
|
+
return { allowed: true, reason: null, budget };
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
export function codingAttemptCeiling() {
|
|
@@ -1,41 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export function validateCharter(
|
|
4
|
-
const findings = [];
|
|
5
|
-
const objective = verifyObjective(charter);
|
|
6
|
-
const reviewedAI = objective.valid && charter.acceptance_criteria_source === 'ai_reviewed' && charter.ai_preparation?.review?.passed === true &&
|
|
7
|
-
charter.ai_preparation.objective_digest === objective.digest && charter.ai_preparation.context_digest === charterPreparationIdentity(charter);
|
|
8
|
-
if (charter.risk.requires_design_review && charter.acceptance_criteria_source !== "explicit" && !reviewedAI) {
|
|
9
|
-
findings.push({
|
|
10
|
-
code: "EXPLICIT_ACCEPTANCE_CRITERIA_REQUIRED",
|
|
11
|
-
severity: "blocker",
|
|
12
|
-
target: "acceptance_criteria",
|
|
13
|
-
explanation: "AI must derive explicit, source-grounded acceptance criteria from the original objective before implementation.",
|
|
14
|
-
repairability: "auto",
|
|
15
|
-
blocker_kind: "machine",
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
if (charter.design_review.required && charter.design_review.status !== "approved") {
|
|
19
|
-
findings.push({
|
|
20
|
-
code: "DESIGN_REVIEW_REQUIRED",
|
|
21
|
-
severity: "blocker",
|
|
22
|
-
target: "design_review",
|
|
23
|
-
explanation:
|
|
24
|
-
"AI must obtain an independent design review, reviewer identity and evidence before implementation. Do not fabricate review approval.",
|
|
25
|
-
repairability: "auto",
|
|
26
|
-
blocker_kind: "machine",
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
if (charter.risk.requires_design_review && charter.required_checks.length === 0) {
|
|
30
|
-
findings.push({
|
|
31
|
-
code: "REQUIRED_CI_CHECKS_MISSING",
|
|
32
|
-
severity: "blocker",
|
|
33
|
-
target: "required_checks",
|
|
34
|
-
explanation: "High-risk work requires explicit CI and security check names.",
|
|
35
|
-
repairability: "auto",
|
|
36
|
-
blocker_kind: "machine",
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return findings;
|
|
40
|
-
}
|
|
41
|
-
|
|
1
|
+
// A request does not need a design approval, named CI inventory or acceptance
|
|
2
|
+
// paperwork before useful work can begin. Authority is checked at execution.
|
|
3
|
+
export function validateCharter() { return []; }
|
|
@@ -9,18 +9,6 @@ export const DELIVERY_STATE_MODEL_PATH = fileURLToPath(
|
|
|
9
9
|
new URL("./delivery/references/state-model.md", import.meta.url),
|
|
10
10
|
);
|
|
11
11
|
|
|
12
|
-
export function buildChecklistContinuationInstruction(
|
|
13
|
-
return section("
|
|
14
|
-
`Read the integrated delivery state model: ${DELIVERY_STATE_MODEL_PATH}. Keep proven delivery milestones, controller execution states, and final dispositions distinct. Follow the repair/reverify and evidence-invalidation branches; a PR readiness state never proves deployment or overall completion. The model documents the existing controller and executor duties, not an automatic scheduler or new transition validator.`,
|
|
15
|
-
`The table is a progress checkpoint, not the deliverable or a stopping condition. Read: ${ACCEPTANCE_EVIDENCE_PATH}`,
|
|
16
|
-
"Maintain the complete 依頼チェックリスト with columns #, ご依頼, 現状, 判定 in the evidence record after chartering, each evaluated attempt, and in the final receipt. User-facing reporting follows workflow_reporting_contract; show the full table only when requested or when it is the most useful requested answer, never as mandatory commentary. Preserve stable criterion IDs, all rows and both applicable evidence layers. ✅ requires current valid passing evidence for every applicable layer; ❌ means an observed failure; 🟡 means pending, missing, stale, blocked or unverifiable evidence. Retain counts and highest proven state in that record.",
|
|
17
|
-
"After observing any non-green row, classify its cause and immediately take the next safe authorized action: implement missing behavior, repair a failure, exercise an untested path, collect missing evidence, or rerun stale verification. Re-observe the same criterion against the current candidate, run the validator, update the table, and repeat until every applicable requested outcome and gate for the authorized endpoint is verified. A next-action list without performing feasible actions is not completion. Never require another continue request merely because a table was shown.",
|
|
18
|
-
"For CI or other in-progress dependencies, use the available bounded wait/recheck or approved monitoring mechanism and maintain progress communication. Unchanged external state is not itself a blocker. Do not start duplicate jobs or busy-poll. Recheck current revision/results, repair authorized failures, and keep waiting only within the authorized scope and external service availability. If waiting cannot continue, report the concrete limit and explicit resumption condition; do not silently abandon it or promise an unscheduled follow-up.",
|
|
19
|
-
"If a row genuinely requires new authority, credentials, an irreversible or explicitly user-reserved decision, an unavailable dependency or a repeated-failure or no-progress safety circuit, retain it as non-green and record affected IDs, evidence of the blocker, safe alternatives attempted, exact missing input, owner and resumption trigger. Continue independent in-scope work. Stop with blocked, best_available, failed or cancelled as appropriate, explicitly unfinished, not verified_complete. Never bypass a manual stop, approval, or expand authority to force green.",
|
|
20
|
-
"Do not end successfully while a safe authorized repair, verification or bounded wait remains. All-green applies to the frozen authorized endpoint: PR-only work does not authorize merge/deploy, but a PR URL, an improvement PR, an issue, a narrower test or a future plan does not satisfy an unmet outcome. Never drop/merge away rows, weaken criteria, relabel applicable checks as not_applicable, recolor unknowns, or copy new digests onto old evidence to force green. Do not change the requested endpoint to match the work already completed.",
|
|
21
|
-
"Before the final response, independently reconcile the original request with all criterion IDs and current evidence, check remaining endpoint gates, and preserve the full table and explicit disposition in the evidence record. Distinguish a partial artifact from verified completion and disclose material remaining outcomes in the requested response format; keep yellow/red counts in the record. A pr_ready_for_review or blocked controller state is not proof of overall completion. These instructions strengthen continuation and reporting; they do not add a background scheduler or remove approval, manual-stop and progress protections.",
|
|
22
|
-
auditor
|
|
23
|
-
? "Remain read-only. Check that the executor performed feasible follow-up actions after non-green tables. Report premature stopping, omitted rows, unsupported green claims, or a blocker without evidence as findings, alongside exact criterion IDs and the necessary next action. Do not approve from a table's colors or the executor's completion wording."
|
|
24
|
-
: "Execute the continuation within this attempt instead of merely returning a yellow table or delegating feasible remaining work to the requester. Return control honestly at a genuine boundary; never fabricate completion evidence.",
|
|
25
|
-
].join("\n"));
|
|
12
|
+
export function buildChecklistContinuationInstruction() {
|
|
13
|
+
return section("requested_outcome", 'Continue unfinished requested work and repair concrete failures without waiting for another continue request. Do not maintain a mandatory checklist or evidence table. Verify the actual result; if an external dependency blocks progress, continue independent work and state the real resumption condition. Preserve user stops, permissions and budgets.');
|
|
26
14
|
}
|
|
@@ -7,8 +7,7 @@ const criterionIdPattern = /^[A-Za-z][A-Za-z0-9]*(?:[-_.][A-Za-z0-9]+)*$/;
|
|
|
7
7
|
export function hasUserOutcomeProof(coverage) {
|
|
8
8
|
const proof = coverage?.user_outcome;
|
|
9
9
|
return proof?.passed === true && Boolean(cleanString(proof.verification_method)) &&
|
|
10
|
-
Boolean(cleanString(proof.observed_result))
|
|
11
|
-
proof.evidence_refs.some((ref) => Boolean(cleanString(ref)));
|
|
10
|
+
Boolean(cleanString(proof.observed_result));
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export function buildCompletionContract({
|
|
@@ -35,7 +34,6 @@ export function buildCompletionContract({
|
|
|
35
34
|
...productionContract,
|
|
36
35
|
non_goals: stringArray(nonGoals),
|
|
37
36
|
proof_required: uniqueStrings([
|
|
38
|
-
"criterion_evidence",
|
|
39
37
|
...proofRequired,
|
|
40
38
|
...stringArray(expectedEvidence),
|
|
41
39
|
]),
|
|
@@ -135,53 +133,11 @@ export function completionEvidenceChecks(charter, attempt) {
|
|
|
135
133
|
const contract = record(charter?.completion_contract);
|
|
136
134
|
if (contract.version !== COMPLETION_CONTRACT_VERSION) return [check("completion_contract_version", "Supported completion contract required", false, "Missing or unsupported completion contract; re-charter and verify before promotion.")];
|
|
137
135
|
|
|
138
|
-
const uncertainty = normalizeUncertaintyVerification(attempt?.uncertainty_verification);
|
|
139
136
|
const rows = acceptanceCriterionRows(charter);
|
|
140
137
|
const criterionEvidence = normalizeCriterionEvidence(attempt?.criterion_evidence);
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
criterionEvidence
|
|
144
|
-
.map((item) => item.criterion_id)
|
|
145
|
-
.filter((criterionId) => !expectedCriterionIds.has(criterionId)),
|
|
146
|
-
);
|
|
147
|
-
const checks = [
|
|
148
|
-
check(
|
|
149
|
-
"top_uncertainty_recorded",
|
|
150
|
-
"Highest uncertainty is explicit",
|
|
151
|
-
Boolean(uncertainty.top_uncertainty),
|
|
152
|
-
uncertainty.top_uncertainty || "No highest uncertainty was recorded.",
|
|
153
|
-
),
|
|
154
|
-
check(
|
|
155
|
-
"first_verification_recorded",
|
|
156
|
-
"First discriminating verification is explicit",
|
|
157
|
-
Boolean(uncertainty.first_verification),
|
|
158
|
-
uncertainty.first_verification || "No first verification was recorded.",
|
|
159
|
-
),
|
|
160
|
-
check(
|
|
161
|
-
"uncertainty_evidence_recorded",
|
|
162
|
-
"Uncertainty verification records concrete evidence",
|
|
163
|
-
uncertainty.evidence_checked.length > 0 && Boolean(uncertainty.observed_result),
|
|
164
|
-
uncertainty.evidence_checked.length > 0 && uncertainty.observed_result
|
|
165
|
-
? `${uncertainty.evidence_checked.length} evidence item(s): ${uncertainty.observed_result}`
|
|
166
|
-
: "The uncertainty check needs evidence_checked and an observed_result.",
|
|
167
|
-
),
|
|
168
|
-
check(
|
|
169
|
-
"top_uncertainty_resolved",
|
|
170
|
-
"Highest uncertainty is resolved before completion",
|
|
171
|
-
uncertainty.resolved,
|
|
172
|
-
uncertainty.resolved
|
|
173
|
-
? "The executor marked the highest uncertainty resolved with observed evidence."
|
|
174
|
-
: uncertainty.fallback || "The highest uncertainty remains unresolved.",
|
|
175
|
-
),
|
|
176
|
-
check(
|
|
177
|
-
"criterion_evidence_scope",
|
|
178
|
-
"Criterion evidence matches the charter acceptance criteria",
|
|
179
|
-
unexpectedCriterionIds.length === 0,
|
|
180
|
-
unexpectedCriterionIds.length === 0
|
|
181
|
-
? "Every criterion evidence row references a chartered acceptance criterion."
|
|
182
|
-
: `Unexpected criterion evidence: ${unexpectedCriterionIds.join(", ")}.`,
|
|
183
|
-
),
|
|
184
|
-
];
|
|
138
|
+
const known = new Set(rows.map(row => row.id));
|
|
139
|
+
const checks = [check("criterion_evidence_scope", "Results belong to the requested behavior",
|
|
140
|
+
criterionEvidence.every(row => known.has(row.criterion_id)), "Do not substitute an unrelated result for the request.")];
|
|
185
141
|
|
|
186
142
|
for (const row of rows) {
|
|
187
143
|
const matches = criterionEvidence.filter((item) => item.criterion_id === row.id);
|
|
@@ -194,7 +150,6 @@ export function completionEvidenceChecks(charter, attempt) {
|
|
|
194
150
|
matches.length === 1 &&
|
|
195
151
|
evidence?.passed &&
|
|
196
152
|
evidence.verification_method &&
|
|
197
|
-
evidence.evidence_refs.length > 0 &&
|
|
198
153
|
evidence.observed_result,
|
|
199
154
|
),
|
|
200
155
|
matches.length > 1
|