@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.
Files changed (121) hide show
  1. package/README.md +225 -98
  2. package/package.json +5 -3
  3. package/src/agent.js +129 -7
  4. package/src/backend-executable.js +7 -6
  5. package/src/bounded-local-task-subtask-pool.js +32 -6
  6. package/src/bounded-local-task-subtasks-contract.js +1 -1
  7. package/src/cli-contract.js +1 -1
  8. package/src/codex-plugins/simy/.codex-plugin/plugin.json +2 -2
  9. package/src/codex-plugins/simy/hooks/README.md +8 -3
  10. package/src/codex-plugins/simy/hooks/delivery-loop.mjs +302 -78
  11. package/src/codex-plugins/simy/hooks/hooks.json +1 -1
  12. package/src/codex-plugins/simy/hooks/loop-kernel/conformance.json +136 -0
  13. package/src/codex-plugins/simy/hooks/loop-kernel/index.mjs +42 -0
  14. package/src/codex-plugins/simy/hooks/loop-kernel/outcome-review.mjs +31 -0
  15. package/src/codex-plugins/simy/hooks/loop-kernel/pin.json +6 -0
  16. package/src/codex-plugins/simy/hooks/loop-kernel/policy.json +141 -0
  17. package/src/codex-plugins/simy/hooks/loop-kernel/task-pool.mjs +34 -0
  18. package/src/codex-plugins/simy/hooks/loop-kernel/work-frontier.mjs +132 -0
  19. package/src/codex-plugins/simy/hooks/loop-kernel.mjs +6 -0
  20. package/src/codex-plugins/simy/hooks/outcome-progress.mjs +16 -5
  21. package/src/codex-plugins/simy/hooks/purpose-completion-contract.mjs +10 -0
  22. package/src/codex-plugins/simy/hooks/purpose-cycle.mjs +12 -11
  23. package/src/codex-plugins/simy/hooks/purpose-evidence.mjs +28 -38
  24. package/src/codex-plugins/simy/hooks/purpose-rule-policy.mjs +1 -2
  25. package/src/codex-plugins/simy/hooks/purpose-rule-review.mjs +5 -63
  26. package/src/codex-plugins/simy/hooks/purpose-search.mjs +21 -4
  27. package/src/codex-plugins/simy/hooks/result-cycle.mjs +166 -0
  28. package/src/codex-plugins/simy/hooks/scene-reasoning.mjs +98 -12
  29. package/src/codex-plugins/simy/hooks/user-experience.mjs +26 -0
  30. package/src/codex-plugins/simy/hooks/work-frontier.mjs +2 -0
  31. package/src/codex-plugins/simy/skills/diagnose/SKILL.md +10 -4
  32. package/src/coding-run-recovery.js +4 -2
  33. package/src/delivery-hooks/claude-workflow.md +10 -7
  34. package/src/delivery-hooks/command.js +76 -10
  35. package/src/delivery-hooks/consistency-monitor.js +20 -4
  36. package/src/delivery-hooks/consult.js +60 -0
  37. package/src/delivery-hooks/diagnosis.js +2 -2
  38. package/src/delivery-hooks/legacy-hook-routing.js +46 -8
  39. package/src/delivery-hooks/local-runtime.js +81 -1
  40. package/src/delivery-hooks/local-supervisor.js +13 -4
  41. package/src/delivery-hooks/repair-continuation.js +80 -0
  42. package/src/delivery-hooks/repair.js +164 -0
  43. package/src/delivery-hooks/supervisor-handover.js +84 -0
  44. package/src/delivery-hooks/supervisor-owner.js +3 -2
  45. package/src/delivery-hooks/supervisor-service.js +38 -6
  46. package/src/delivery-hooks/supervisor-watchdog.js +15 -4
  47. package/src/delivery-hooks/supervisor.js +151 -18
  48. package/src/desktop-access.js +2 -1
  49. package/src/desktop-host.js +35 -2
  50. package/src/durable-local-task-worker.js +372 -58
  51. package/src/execution-capability-contract.js +15 -1
  52. package/src/index.js +3 -3
  53. package/src/local-photos-read.swift +33 -0
  54. package/src/local-photos.js +47 -0
  55. package/src/local-task-attachment-store.js +31 -0
  56. package/src/local-task-destination.js +174 -0
  57. package/src/local-task.js +119 -7
  58. package/src/native-delegation-contract.js +68 -0
  59. package/src/native-provider-identity.js +60 -0
  60. package/src/orchestrator/audit.js +24 -323
  61. package/src/orchestrator/budget.js +22 -11
  62. package/src/orchestrator/charter-validation.js +3 -41
  63. package/src/orchestrator/checklist-continuation.js +2 -14
  64. package/src/orchestrator/completion-contract.js +4 -49
  65. package/src/orchestrator/contract.js +5 -9
  66. package/src/orchestrator/delivery/WORKFLOW.md +30 -710
  67. package/src/orchestrator/delivery/references/acceptance-evidence.md +4 -216
  68. package/src/orchestrator/delivery/references/change-boundary-matrix.md +4 -103
  69. package/src/orchestrator/delivery/references/chrome-user-verification.md +5 -133
  70. package/src/orchestrator/delivery/references/cross-repository-control.md +4 -74
  71. package/src/orchestrator/delivery/references/delivery-levels.md +3 -117
  72. package/src/orchestrator/delivery/references/local-sqm-evidence.md +1 -1
  73. package/src/orchestrator/delivery/references/minimum-completion.md +4 -144
  74. package/src/orchestrator/delivery/references/pr-closure.md +4 -200
  75. package/src/orchestrator/delivery/references/shared-system-specifications.md +4 -161
  76. package/src/orchestrator/delivery/references/state-and-receipts.md +4 -245
  77. package/src/orchestrator/delivery/references/state-model.md +4 -185
  78. package/src/orchestrator/delivery/references/ux-specification.md +4 -95
  79. package/src/orchestrator/delivery-level.js +2 -10
  80. package/src/orchestrator/evidence.js +2 -3
  81. package/src/orchestrator/independent-audit.js +15 -164
  82. package/src/orchestrator/instruction.js +19 -254
  83. package/src/orchestrator/loop.js +35 -95
  84. package/src/orchestrator/outcome-monitor.js +21 -0
  85. package/src/orchestrator/pr-closure.js +2 -16
  86. package/src/orchestrator/recovery.js +32 -2
  87. package/src/orchestrator/ux-specification.js +3 -12
  88. package/src/provider-delegation.js +336 -0
  89. package/src/runner.js +5 -2
  90. package/src/session-supervision/acceptance-reads.js +34 -0
  91. package/src/session-supervision/artifact-observer.js +138 -0
  92. package/src/session-supervision/browser-cdp.js +59 -0
  93. package/src/session-supervision/browser-recovery.js +97 -0
  94. package/src/session-supervision/codex-app-server.js +61 -22
  95. package/src/session-supervision/completion-artifacts.js +44 -0
  96. package/src/session-supervision/consistency-auditor.js +8 -11
  97. package/src/session-supervision/continuation-analysis.js +47 -28
  98. package/src/session-supervision/final-response-recovery.js +5 -0
  99. package/src/session-supervision/index.js +22 -5
  100. package/src/session-supervision/interaction-proxy.js +250 -0
  101. package/src/session-supervision/interaction-task.js +189 -0
  102. package/src/session-supervision/native-interaction.js +102 -0
  103. package/src/session-supervision/provider-failure.js +40 -0
  104. package/src/session-supervision/purpose-instruction.js +14 -14
  105. package/src/session-supervision/purpose-recovery.js +32 -12
  106. package/src/session-supervision/purpose-verification.js +50 -216
  107. package/src/session-supervision/recovery-frontier.js +55 -0
  108. package/src/session-supervision/stop-notifications.js +8 -8
  109. package/src/sqm/agent-skill.js +1 -1
  110. package/src/sqm/checkers.js +12 -6
  111. package/src/sqm/claude-plugin-update.js +22 -8
  112. package/src/sqm/claude-plugin.js +6 -0
  113. package/src/sqm/local-tests.js +2 -1
  114. package/src/sqm/plugin-cache.js +67 -0
  115. package/src/sqm/proof.js +25 -0
  116. package/src/sqm/report.js +1 -0
  117. package/src/sqm/workflow-onboarding.js +3 -39
  118. package/src/sqm/workflow-update.js +17 -6
  119. package/src/startup-preflight.js +2 -20
  120. package/src/team-status-fence-store.js +32 -0
  121. package/src/team-threads.js +457 -0
@@ -1,24 +1,6 @@
1
- import { minimumCompletionCheck } from "./minimum-completion.js";
2
- import { open, readdir, stat } from "node:fs/promises";
3
- import path from "node:path";
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 > 0 &&
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
- addCheck(
108
- "commit_headline_prefix",
109
- "Observed commit headline uses an allowed prefix",
110
- hasAllowedPrefix(local.commit_headline),
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
- : `Repair ${check.label.toLowerCase()} and report fresh evidence.`,
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
- ? "Implementation is ready for independent audit."
254
- : "Implementation did not satisfy local evidence gates.",
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
- ? "Structured evidence matches the charter."
263
- : "Structured evidence is incomplete.",
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(minimum.id, minimum.passed, minimum.detail),
341
- readinessCheck("minimum_independent_binding",
342
- Boolean(independentAudit?.evidence_identity_sha256 && independentAudit.evidence_identity_sha256 === independentAuditEvidenceIdentity(attempt)),
343
- "Independent review must cover the current head, base, CI, skips, preparation rows, companion revisions, and minimum evidence."),
344
- readinessCheck("user_outcome_proven", hasUserOutcomeProof(independentAudit?.coverage_review),
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
- readinessCheck(
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
- readinessCheck(
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 has independently verified evidence, passing checks, and required approval."
433
- : attempt.audit?.passed && minimum.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" ? snapshot.charter.provider_token_ceiling : snapshot.charter.attempt_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
- // Local work stops on failed progress/approval, never on consumption counters.
102
- const hardTokenCeiling = null;
103
- const hardAttemptCeiling = null;
104
- const tokenExhausted = false;
105
- const retryExhausted = false;
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
- ? "token_budget_exhausted"
119
+ ? "provider_token_ceiling_reached"
119
120
  : retryExhausted
120
- ? "retry_budget_exhausted"
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: null,
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
- return { allowed: true, reason: null, budget: refreshBudgetState(snapshot) };
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
- import { verifyObjective } from './outcome-monitor.js';
2
- import { charterPreparationIdentity } from './charter-repair.js';
3
- export function validateCharter(charter) {
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({ auditor = false } = {}) {
13
- return section("checklist_continuation_contract", [
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)) && Array.isArray(proof.evidence_refs) &&
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 expectedCriterionIds = new Set(rows.map((row) => row.id));
142
- const unexpectedCriterionIds = uniqueStrings(
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