@awak-app/simy-cli 0.5.15 → 0.5.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 +78 -15
- package/package.json +2 -2
- package/src/agent.js +33 -1
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/.codex-plugin/plugin.json +5 -6
- 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/connection-discovery.js +49 -0
- package/src/console/app.js +2 -2
- package/src/orchestrator/audit.js +47 -5
- package/src/orchestrator/budget.js +1 -1
- package/src/orchestrator/checklist-continuation.js +21 -0
- package/src/orchestrator/completion-contract.js +1 -1
- package/src/orchestrator/contract.js +2 -0
- package/src/{codex-plugins/simy/skills/agentic-delivery-loop/SKILL.md → orchestrator/delivery/WORKFLOW.md} +121 -13
- package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/acceptance-evidence.md +27 -3
- package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/change-boundary-matrix.md +1 -1
- package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/delivery-levels.md +78 -27
- package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/minimum-completion.md +3 -3
- package/src/orchestrator/delivery/references/pr-closure.md +203 -0
- package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/state-and-receipts.md +4 -1
- package/src/orchestrator/delivery/references/ux-specification.md +98 -0
- package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/scripts/production-contract.mjs +1 -1
- package/src/orchestrator/delivery-level.js +19 -0
- package/src/orchestrator/evidence.js +64 -5
- package/src/orchestrator/independent-audit.js +18 -0
- package/src/orchestrator/index.js +7 -0
- package/src/orchestrator/instruction.js +32 -3
- package/src/orchestrator/loop.js +4 -1
- package/src/orchestrator/minimum-completion.js +2 -2
- package/src/orchestrator/pr-closure.js +26 -0
- package/src/orchestrator/pr-preparation.js +420 -0
- package/src/orchestrator/presentation.js +2 -2
- package/src/orchestrator/result.js +8 -2
- package/src/orchestrator/ux-specification.js +20 -0
- package/src/runner.js +104 -1
- package/src/sqm/agent-skill.js +39 -7
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/agents/openai.yaml +0 -5
- /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/chrome-user-verification.md +0 -0
- /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/cross-repository-control.md +0 -0
- /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/local-sqm-evidence.md +0 -0
- /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/shared-system-specifications.md +0 -0
- /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/scripts/check-acceptance.mjs +0 -0
- /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/scripts/check-minimum-completion.mjs +0 -0
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { minimumCharter } from "
|
|
1
|
+
import { minimumCharter } from "./delivery/scripts/check-minimum-completion.mjs";
|
|
2
2
|
import { bullets, looksLikeUiTask, section } from "./shared.js";
|
|
3
3
|
import { problemSolvingResultContract } from "./problem-solving.js";
|
|
4
4
|
import { acceptanceCriterionRows } from "./completion-contract.js";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { buildPrClosureInstruction } from "./pr-closure.js";
|
|
7
|
+
import { buildUxSpecificationInstruction } from "./ux-specification.js";
|
|
8
|
+
import { buildDeliveryLevelInstruction } from "./delivery-level.js";
|
|
9
|
+
import { buildChecklistContinuationInstruction } from "./checklist-continuation.js";
|
|
10
|
+
import { PR_PREPARATION_CHECKS } from "./pr-preparation.js";
|
|
5
11
|
|
|
6
12
|
export function buildCodingInstruction(
|
|
7
13
|
charter,
|
|
@@ -15,6 +21,9 @@ export function buildCodingInstruction(
|
|
|
15
21
|
) {
|
|
16
22
|
const sections = [
|
|
17
23
|
section("role", "You are the local coding executor for a SIMY agentic loop."),
|
|
24
|
+
buildUxSpecificationInstruction(),
|
|
25
|
+
buildDeliveryLevelInstruction(),
|
|
26
|
+
buildChecklistContinuationInstruction(),
|
|
18
27
|
section(
|
|
19
28
|
"requirement_charter",
|
|
20
29
|
[
|
|
@@ -63,7 +72,7 @@ export function buildCodingInstruction(
|
|
|
63
72
|
section("minimum_completion_evidence", [
|
|
64
73
|
"All delivery levels require this floor. Before implementation reconcile every original requirement and frozen experience with the stable criterion-ID list; report omissions instead of silently dropping them.",
|
|
65
74
|
"Save a matrix.json under the local evidence root (ui_evidence_root or repository .artifacts). Return delivery_evidence_path in SIMY_RESULT_JSON. A passed flag or tests_run string is not proof.",
|
|
66
|
-
|
|
75
|
+
`Use the packaged Web/CLI verifier: ${fileURLToPath(new URL("./delivery/scripts/check-minimum-completion.mjs", import.meta.url))}. The contract is ${fileURLToPath(new URL("./delivery/references/minimum-completion.md", import.meta.url))}. No local Codex Delivery Loop skill is required.`,
|
|
67
76
|
"For each stable criterion ID include real implementation_refs and required user/system evidence. Exercise the actual operation and result, then inspect postcondition and integration independently. For saving, reload/return in a new session and verify stored values and ownership. Document-only work uses the real command/consumer and artifact integrity, not imaginary API/DB checks.",
|
|
68
77
|
"First evidence artifact per layer must be JSON: {criterion_id, context_sha256, checks}. User checks: operation (expected=true, actual=observed boolean) and result; system checks: postcondition and integration. Each check includes subject, expected and actual. Derive expected values from the original requirement, not from the candidate output. Include screenshots/logs as additional evidence.",
|
|
69
78
|
"Freeze and bind observations to the current committed revision and charter before verification. Do not rehash old observations to make changed code pass. Use pending/failed for unavailable or mismatching evidence.",
|
|
@@ -72,7 +81,26 @@ export function buildCodingInstruction(
|
|
|
72
81
|
`Context: run_id=${charter.id}, stage=locally_verified, environment=local, fixture_id=${charter.id}; input_sha256 is contextDigest(spec.source); source_revisions maps ${charter.repository} to current full HEAD.`,
|
|
73
82
|
].join("\n")),
|
|
74
83
|
section("expected_tests", bullets(charter.expected_tests)),
|
|
84
|
+
section("pr_preparation_contract", [
|
|
85
|
+
"Before reporting the PR ready, complete every preparation row below against the final committed HEAD. Do not reuse evidence after the head, fetched base, companion revision, manifest, policy, or relevant environment changes.",
|
|
86
|
+
...PR_PREPARATION_CHECKS.map((row) => `- ${row.id}: ${row.label}`),
|
|
87
|
+
"Return one pr_preparation entry per ID. A required row needs passed=true, a concrete observation, evidence_refs that resolve to a checked repository file or an observed:* controller fact, and the current full commit SHA in bound_revisions.",
|
|
88
|
+
"Use applicability=not_applicable only when the changed boundary genuinely excludes the check; include reason, authoritative_source, verified evidence_refs, and the current full commit SHA. Missing tools, failed checks, time, or lack of evidence are pending/failed, not not-applicable.",
|
|
89
|
+
"exact_revision_integration requires companion_revisions entries with repository/full 40-character sha/evidence_ref and/or manifest_revisions entries with manifest/SHA-256/evidence_ref, or a verified N/A boundary when neither exists. e2e_outcome_and_cleanup requires cleanup_residual_count=0.",
|
|
90
|
+
"security_and_privacy requires security_privacy_checks. ui_and_i18n requires browser_artifacts, locales_checked, console_error_count=0, and network_failure_count=0, or a verified N/A boundary for non-UI work.",
|
|
91
|
+
"The final four rows are conditional safeguards. Mark them not_applicable only with current-head-bound verified boundary evidence, never merely because verification is inconvenient.",
|
|
92
|
+
"official_checklist_ownership applies when checklist/chat rendering changes: verify heading levels 1-6, leading spaces, trailing hashes, exactly one controller-owned official checklist, and an explicit unverified label for model-generated lookalikes.",
|
|
93
|
+
"deployment_target_mapping applies when release or live verification is in scope: report deployment_target, a verified deployment_target_source evidence ref, and revision_observation_method. Derive the environment from authoritative topology instead of assuming app-dev or production.",
|
|
94
|
+
"live_data_safety applies to shared/live verification: report live_write_policy as no_persistence, scoped_fixture, or authorized_production_write and cleanup_residual_count=0. Do not infer production-write authority from verification scope.",
|
|
95
|
+
"resume_compatibility applies when Delivery Loop state, derived views, or rendering changes: exercise normal and checkpoint resume; report paid_resume_support as supported or not_supported with a verified paid_resume_support_source evidence ref, exercise paid resume when supported, and prove derived_views_recomputed and artifact_content_preserved.",
|
|
96
|
+
"For current_base_and_head, fetch the remote base immediately before final verification, integrate it, and rerun invalidated checks after conflict resolution.",
|
|
97
|
+
"For current_ci_and_skips, verify the published current HEAD. Return every skipped GitHub check in skipped_checks with name, reason, and impact; a skipped full/live job must not be described as executed E2E.",
|
|
98
|
+
"For exact_revision_integration, pin every companion repository/manifest SHA and exercise the combined path. For e2e_outcome_and_cleanup, inspect expected scenario states and prove residual fixture count is zero; a green job with failed cleanup is not passing evidence.",
|
|
99
|
+
"For security_and_privacy, verify changed trust boundaries and that arbitrary messages, email addresses, URLs, identifiers, credentials, or user data are not retained in logs, breadcrumbs, or telemetry. For UI changes, exercise the actual locale/browser, layout overflow, console/network, and screenshot evidence.",
|
|
100
|
+
"Disclose remaining work, intentional skips, uncertainty, and post-merge migration/deploy/monitoring steps without calling them complete.",
|
|
101
|
+
].join("\n")),
|
|
75
102
|
section("must_not", bullets(charter.must_not)),
|
|
103
|
+
buildPrClosureInstruction(),
|
|
76
104
|
section(
|
|
77
105
|
"active_request_reconciliation",
|
|
78
106
|
[
|
|
@@ -161,11 +189,12 @@ export function buildCodingInstruction(
|
|
|
161
189
|
"result_contract",
|
|
162
190
|
[
|
|
163
191
|
"Finish with exactly one line beginning SIMY_RESULT_JSON: followed by one JSON object.",
|
|
164
|
-
"Required keys: outcome_kind, summary, branch_name, commit_sha, commit_message_headline, pr_url, pr_number, pr_title, pr_base_branch, tests_run, tests_passed, ui_evidence_path, unrelated_changes_detected, secret_scan_passed, changed_files, residual_risks, problem_solving, uncertainty_verification, criterion_evidence, visual_review, delivery_evidence_path.",
|
|
192
|
+
"Required keys: outcome_kind, summary, branch_name, commit_sha, commit_message_headline, pr_url, pr_number, pr_title, pr_base_branch, tests_run, tests_passed, ui_evidence_path, unrelated_changes_detected, secret_scan_passed, changed_files, residual_risks, problem_solving, uncertainty_verification, criterion_evidence, visual_review, delivery_evidence_path, pr_preparation, skipped_checks.",
|
|
165
193
|
problemSolvingResultContract(),
|
|
166
194
|
"uncertainty_verification must contain top_uncertainty, first_verification, evidence_checked, observed_result, resolved, and fallback. Only set resolved=true when concrete observed evidence resolves it.",
|
|
167
195
|
"criterion_evidence must contain one entry for every stable criterion ID with criterion_id, passed, verification_method, evidence_refs, and observed_result. Only set passed=true when the evidence directly proves that criterion.",
|
|
168
196
|
"For UI or browser-extension work, visual_review must contain inspected=true, the local screenshot or video paths in artifacts, and concrete acceptance_observations from viewing the actual content. Capturing a file without viewing it does not count.",
|
|
197
|
+
"pr_preparation must contain the eight stable preparation IDs and four conditional safeguard IDs. skipped_checks must contain name, reason, and impact for every GitHub check that intentionally skipped.",
|
|
169
198
|
"Use null for unavailable scalar values and [] for unavailable arrays. Do not claim evidence that was not observed.",
|
|
170
199
|
].join("\n"),
|
|
171
200
|
),
|
package/src/orchestrator/loop.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { minimumCharter } from "
|
|
1
|
+
import { minimumCharter } from "./delivery/scripts/check-minimum-completion.mjs";
|
|
2
2
|
import { auditAttempt, evaluatePrReadiness } from "./audit.js";
|
|
3
3
|
import {
|
|
4
4
|
buildIndependentAudit,
|
|
@@ -9,6 +9,7 @@ import { canStartProvider, refreshBudgetState } from "./budget.js";
|
|
|
9
9
|
import { buildAttempt, mergeTokenUsage } from "./result.js";
|
|
10
10
|
import { evaluateRetryCircuit } from "./retry.js";
|
|
11
11
|
import { appendEvent, publish } from "./shared.js";
|
|
12
|
+
import { independentAuditEvidenceIdentity } from "./pr-preparation.js";
|
|
12
13
|
|
|
13
14
|
async function runCodingLoopOwned({
|
|
14
15
|
snapshot,
|
|
@@ -212,6 +213,7 @@ async function runCodingLoopOwned({
|
|
|
212
213
|
if (shouldStop()) return stopCodingLoop(snapshot, onUpdate);
|
|
213
214
|
const auditInstruction = buildIndependentAuditInstruction(snapshot.charter, attempt);
|
|
214
215
|
const reviewedMatrix = attempt.observed_evidence?.minimum_completion?.matrix_sha256;
|
|
216
|
+
const reviewedEvidenceIdentity = independentAuditEvidenceIdentity(attempt);
|
|
215
217
|
const auditExecution = await safeExecution(() =>
|
|
216
218
|
executeIndependentAudit({
|
|
217
219
|
attemptNumber,
|
|
@@ -231,6 +233,7 @@ async function runCodingLoopOwned({
|
|
|
231
233
|
attempt.budget = refreshBudgetState(snapshot);
|
|
232
234
|
|
|
233
235
|
attempt.independent_audit.minimum_matrix_sha256 = reviewedMatrix;
|
|
236
|
+
attempt.independent_audit.evidence_identity_sha256 = reviewedEvidenceIdentity;
|
|
234
237
|
|
|
235
238
|
// Re-collect after the read-only auditor to catch any mutated HEAD or working tree.
|
|
236
239
|
attempt.observed_evidence = await safeEvidence(collectEvidence, attempt);
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
minimumContext,
|
|
6
6
|
checkMinimumCompletion,
|
|
7
7
|
loadMinimumJson,
|
|
8
|
-
} from "
|
|
8
|
+
} from "./delivery/scripts/check-minimum-completion.mjs";
|
|
9
9
|
import {
|
|
10
10
|
contextDigest,
|
|
11
11
|
sha256,
|
|
12
|
-
} from "
|
|
12
|
+
} from "./delivery/scripts/check-acceptance.mjs";
|
|
13
13
|
|
|
14
14
|
export function minimumCharterDigest(charter) {
|
|
15
15
|
return sha256(Buffer.from(JSON.stringify(minimumCharter(charter))));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { section } from "./shared.js";
|
|
3
|
+
|
|
4
|
+
export const PR_CLOSURE_GUIDE_PATH = fileURLToPath(
|
|
5
|
+
new URL("./delivery/references/pr-closure.md", import.meta.url),
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
// Both real executor and independent-auditor instructions use this contract.
|
|
9
|
+
// This is not a GitHub permission hook: external mutations still need authority.
|
|
10
|
+
export function buildPrClosureInstruction({ auditor = false } = {}) {
|
|
11
|
+
return section("pr_closure_gate", [
|
|
12
|
+
"Applies only when proposing, performing or auditing PR closure/supersession/retirement. It does not request closing any PR.",
|
|
13
|
+
`Before a closure decision, read the packaged Web/CLI procedure: ${PR_CLOSURE_GUIDE_PATH}`,
|
|
14
|
+
"Freeze the original outcome criteria and exact old head/base, current target base, successor heads, integrated candidate and deployed identity. Preserve intended, observed and unknown behavior separately.",
|
|
15
|
+
"Persist a criterion-ID before/after table: old PR, current base alone, integrated successors and deployed target. Account for all changed files and unintended added behavior; file counts, ancestry and green CI are not UX equivalence.",
|
|
16
|
+
"For every unresolved yellow/red row, create a new remediation PR within explicit implementation/commit/push/PR authority, grouping only coherent changes. Reuse an exact existing open remediation PR rather than duplicating it. Map every criterion ID to a real PR URL, owner and acceptance tests; a broad successor link or uncreated PR plan is insufficient for transfer_ready. Missing authority, unresolved product decisions or budget blocks remain explicit blockers. PR creation alone never makes a row green.",
|
|
17
|
+
"Verify normal production reachability and the complete trigger-to-usable-result path, with independent technical postconditions and recovery. Code surviving only in fixtures is not inherited UX. Distinguish reuse, composition, conversational design, immediate ad-hoc work and background learning, including the no-workflow/no-match fallback.",
|
|
18
|
+
"Unknown, failed or stale applicable evidence blocks close_ready. Require independent review of original requirements and raw evidence before an authorized close. Preserve initial failures; tests changed to pass do not validate the unchanged original candidate.",
|
|
19
|
+
"Unfinished successors require transfer_ready with explicit informed administrative-close authorization, accepted owner, open tracking and remaining gates; never claim full inheritance. Intentional removal requires an explicit retirement decision and safe data/migration handling.",
|
|
20
|
+
"Re-read all bound revisions, successor state, checks and authority immediately before mutation; changed inputs invalidate affected verification/review. Close and comment-posting authority are separate. Audit-only requests authorize neither.",
|
|
21
|
+
"After an authorized action, read back remote state before claiming success or retrying. Persist pre/post state, review/evidence digests, draft/comment identity, pending obligations and recovery references before cleanup. Do not infer branch deletion, successor merge, deployment or delivery completion.",
|
|
22
|
+
auditor
|
|
23
|
+
? "You remain read-only. Independently inspect the closure table and raw proof; report gaps as findings. Closure decisions never turn unmet delivery criteria green. Do not close, reopen, comment or repair the PR."
|
|
24
|
+
: "If any required decision, evidence, independent review or authority is missing, stop the dependent close action and report the blocker. This protocol is not authorization and does not introduce a new loop completion state.",
|
|
25
|
+
].join("\n"));
|
|
26
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -88,9 +88,9 @@ export function summarizeCodingLoopEvent(snapshot, event) {
|
|
|
88
88
|
};
|
|
89
89
|
case "pr_ready_for_review":
|
|
90
90
|
return {
|
|
91
|
-
summary: "
|
|
91
|
+
summary: "The run is unfinished: required PR checks, review or evidence still need verification.",
|
|
92
92
|
result: pendingReasons.length ? `Pending: ${pendingReasons.join("; ")}` : cleanString(event?.message),
|
|
93
|
-
next: "
|
|
93
|
+
next: "Continue authorized verification, resolve the listed pending gates, then refresh evidence from Web or CLI. Obtain human approval only where required.",
|
|
94
94
|
};
|
|
95
95
|
case "merge_ready":
|
|
96
96
|
return {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { minimumCharterDigest } from "./minimum-completion.js";
|
|
2
|
-
import { minimumContext } from "
|
|
3
|
-
import { contextDigest } from "
|
|
2
|
+
import { minimumContext } from "./delivery/scripts/check-minimum-completion.mjs";
|
|
3
|
+
import { contextDigest } from "./delivery/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: [],
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { section } from "./shared.js";
|
|
3
|
+
|
|
4
|
+
export const UX_SPECIFICATION_PATH = fileURLToPath(
|
|
5
|
+
new URL("./delivery/references/ux-specification.md", import.meta.url),
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
export function buildUxSpecificationInstruction({ auditor = false } = {}) {
|
|
9
|
+
return section("ux_specification_contract", [
|
|
10
|
+
`Apply the same behavioral-completeness standard throughout requirements, design, implementation mapping, verification, review, release, closure and handoff. Read: ${UX_SPECIFICATION_PATH}`,
|
|
11
|
+
"Describe observable interaction units, not feature inventories. Reuse stable acceptance IDs. For each unit specify actor/goal, context/preconditions, trigger/reachable entry, perceived information, available action/choice/control, system response/state transition, usable outcome/postconditions, next action/handoff, alternative/failure/recovery paths, and verification oracle/provenance. Treat these as semantic obligations in the existing specification, not new CLI JSON fields.",
|
|
12
|
+
"Decompose when actor, precondition, decision, observable state, postcondition or recovery differs materially, or when any clause could fail independently. Link predecessors/successors and preserve end-to-end criteria. Derive branches and applicable constraints from authoritative requirements and observed system boundaries; do not invent scenarios, defaults, thresholds or product policy to fill gaps.",
|
|
13
|
+
"Description is sufficient only when another implementer can derive the intended behavior and an independent verifier can determine pass/fail without guessing a material requirement. Review source coverage, causal continuity, branch closure, controllability, observability, traceability, consistency and epistemic separation. Word count, row count and verbosity are not quality thresholds.",
|
|
14
|
+
"Keep required/intended/observed/assumed/unknown distinct. Unknown facts stay explicit with affected IDs and resolution evidence; a missing requirement decision blocks dependent design, while missing runtime evidence blocks the affected verification claim. Genuine non-applicability needs a source and reason, not an empty cell.",
|
|
15
|
+
"Persist the complete contract in the canonical specification and link the same IDs from summaries, matrices, PRs and receipts. Summaries may shorten repetition but must not replace or drop behavioral obligations, unresolved gaps or evidence. A phase change revalidates affected descriptions and results; plans never become observed proof by wording alone.",
|
|
16
|
+
auditor
|
|
17
|
+
? "Remain read-only. Independently derive coverage from original sources, identify any material behavior that still requires guessing, and report affected criterion IDs and required clarification/evidence as findings. Do not infer completeness from filled fields or copied executor claims."
|
|
18
|
+
: "Before dependent implementation, resolve material specification ambiguity within authority or report needs_decision; continue independent work. Before verification or completion, repair missing descriptions/evidence rather than relabeling incomplete UX as complete. This procedural contract does not add automatic schema or GitHub enforcement.",
|
|
19
|
+
].join("\n"));
|
|
20
|
+
}
|