@surlymochan/evalid 0.0.0-stage → 0.3.1

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 (192) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +315 -2
  4. package/RELEASE_NOTES.md +102 -0
  5. package/atoms/registry.json +758 -0
  6. package/bin/xflow.js +7208 -0
  7. package/docs/adoption/README.md +128 -0
  8. package/docs/adoption/openflow-release-hardening.md +61 -0
  9. package/docs/benchmark-openflow-vs-gstack-superpowers-openspec-2026-05-25.md +81 -0
  10. package/docs/compatibility.md +52 -0
  11. package/docs/competitive-benchmark.md +108 -0
  12. package/docs/corps-operator-guide.md +75 -0
  13. package/docs/demo-proof.md +126 -0
  14. package/docs/examples-gallery.md +115 -0
  15. package/docs/fixtures/tracker-item.json +7 -0
  16. package/docs/goal-vs-codex.md +129 -0
  17. package/docs/gstack-comparison.md +88 -0
  18. package/docs/install-upgrade.md +133 -0
  19. package/docs/integrations.md +49 -0
  20. package/docs/launch-demo.md +123 -0
  21. package/docs/launch-dossier.md +180 -0
  22. package/docs/methodology.md +149 -0
  23. package/docs/npm-publish-handoff.md +83 -0
  24. package/docs/openspec-migration.md +106 -0
  25. package/docs/public-benchmark.md +68 -0
  26. package/docs/public-release.md +145 -0
  27. package/docs/quality-assessment.md +139 -0
  28. package/docs/quickstart.md +156 -0
  29. package/docs/reviewer-guide.md +43 -0
  30. package/docs/spec-kit-benchmark.md +73 -0
  31. package/docs/super-assistant-comparison.md +106 -0
  32. package/docs/superpowers-comparison.md +82 -0
  33. package/docs/team-adoption.md +123 -0
  34. package/docs/tooling-matrix.md +91 -0
  35. package/docs/walkthrough.md +76 -0
  36. package/docs/workflow-manual.md +1125 -0
  37. package/package.json +83 -4
  38. package/prompts/pencil-draft.md +46 -0
  39. package/prompts/pencil-refine.md +46 -0
  40. package/prompts/phase-artifact.md +24 -0
  41. package/prompts/phase-execution.md +32 -0
  42. package/schemas/competitor-reconstruction-contract.schema.json +91 -0
  43. package/schemas/llm-design-review-aggregate.schema.json +83 -0
  44. package/schemas/visual-benchmark.schema.json +978 -0
  45. package/schemas/workflow.schema.json +100 -0
  46. package/scripts/run-tests.mjs +23 -0
  47. package/scripts/takein_digest.py +139 -0
  48. package/specs/README.md +118 -0
  49. package/specs/architecture.md +36 -0
  50. package/specs/product.md +33 -0
  51. package/specs/workflow.md +349 -0
  52. package/src/agent_team/atoms/codex-app-bridge.js +8 -0
  53. package/src/agent_team/atoms/design/competitor-reconstruction-review.js +44 -0
  54. package/src/agent_team/atoms/design/contract-freeze.js +8 -0
  55. package/src/agent_team/atoms/design/generation-contract.js +442 -0
  56. package/src/agent_team/atoms/design/reconstruction-pack.js +246 -0
  57. package/src/agent_team/atoms/design/reference-surface-lock.js +49 -0
  58. package/src/agent_team/atoms/patch-challenge.js +19 -0
  59. package/src/agent_team/atoms/pencil/accept.js +9 -0
  60. package/src/agent_team/atoms/pencil/draft.js +365 -0
  61. package/src/agent_team/atoms/pencil/refine.js +229 -0
  62. package/src/agent_team/atoms/phase-run-dispatch.js +8 -0
  63. package/src/agent_team/atoms/team-run.js +380 -0
  64. package/src/agent_team/atoms/visual/aesthetic-review.js +231 -0
  65. package/src/agent_team/atoms/visual/benchmark-repair-plan.js +291 -0
  66. package/src/agent_team/atoms/visual/benchmark.js +1652 -0
  67. package/src/agent_team/atoms/visual/design-selection.js +23 -0
  68. package/src/agent_team/atoms/visual/direction-synthesis.js +124 -0
  69. package/src/agent_team/atoms/visual/dom-rect-export.js +129 -0
  70. package/src/agent_team/atoms/visual/image-diff.js +186 -0
  71. package/src/agent_team/atoms/visual/layout-competition.js +8 -0
  72. package/src/agent_team/atoms/visual/page-evidence-capture.js +806 -0
  73. package/src/agent_team/atoms/visual/playwright-runtime.js +8 -0
  74. package/src/agent_team/atoms/visual/review-aggregate.js +87 -0
  75. package/src/agent_team/atoms/visual/review.js +77 -0
  76. package/src/agent_team/atoms/visual/visual-report.js +197 -0
  77. package/src/agent_team/atoms/visual/visual-token-export.js +417 -0
  78. package/src/agent_team/common.js +311 -0
  79. package/src/core/atoms/agent-invoke.js +684 -0
  80. package/src/core/atoms/event/ingest.js +30 -0
  81. package/src/core/atoms/experience/digest.js +55 -0
  82. package/src/core/atoms/gate/artifact-verify.js +83 -0
  83. package/src/core/atoms/gate/competitor-contract-validate.js +119 -0
  84. package/src/core/atoms/gate/evidence-collect.js +64 -0
  85. package/src/core/atoms/gate/payload-from-phase-run.js +78 -0
  86. package/src/core/atoms/gate/visual-benchmark-validate.js +183 -0
  87. package/src/core/atoms/gate/workbench.js +79 -0
  88. package/src/core/atoms/intake/classify.js +15 -0
  89. package/src/core/atoms/intake/design-skill-select.js +16 -0
  90. package/src/core/atoms/intake/preview-or-refine.js +19 -0
  91. package/src/core/atoms/intake/recommend.js +20 -0
  92. package/src/core/atoms/intake/research-same-category.js +19 -0
  93. package/src/core/atoms/intervention/create.js +30 -0
  94. package/src/core/atoms/memory/manage.js +27 -0
  95. package/src/core/atoms/memory/mount.js +13 -0
  96. package/src/core/atoms/mission/create.js +19 -0
  97. package/src/core/atoms/mission/list.js +14 -0
  98. package/src/core/atoms/mission/show.js +26 -0
  99. package/src/core/atoms/notify/checkpoint.js +46 -0
  100. package/src/core/atoms/phase-run/complete.js +42 -0
  101. package/src/core/atoms/phase-run/fail.js +40 -0
  102. package/src/core/atoms/phase-run/persist.js +39 -0
  103. package/src/core/atoms/phase-run/start.js +30 -0
  104. package/src/core/atoms/plugin/run.js +76 -0
  105. package/src/core/atoms/policy/gate.js +90 -0
  106. package/src/core/atoms/policy/resolve.js +11 -0
  107. package/src/core/atoms/storage/sync.js +57 -0
  108. package/src/core/atoms/workflow/advance.js +25 -0
  109. package/src/core/atoms/workflow/guard.js +127 -0
  110. package/src/core/atoms/workflow/state-load.js +37 -0
  111. package/src/core/bindings.js +37 -0
  112. package/src/core/change-artifacts.js +134 -0
  113. package/src/core/competitive-score.js +265 -0
  114. package/src/core/concurrency-policy.js +35 -0
  115. package/src/core/execution-log.js +108 -0
  116. package/src/core/intake-recommendation.js +134 -0
  117. package/src/core/memory-state.js +62 -0
  118. package/src/core/mission-state.js +780 -0
  119. package/src/core/pending-gates.js +66 -0
  120. package/src/core/phase-cost.js +116 -0
  121. package/src/core/phase-run-read.js +27 -0
  122. package/src/core/policy-overlay.js +180 -0
  123. package/src/core/state-store.js +201 -0
  124. package/src/core/workflow-executor.js +998 -0
  125. package/src/core/workflow-integrity.js +111 -0
  126. package/src/core/workflow-loader.js +163 -0
  127. package/src/core/workflow-rerun.js +84 -0
  128. package/src/core/workflow-state.js +119 -0
  129. package/src/server.js +1307 -0
  130. package/workflows/corps.yaml +479 -0
  131. package/workflows/yolo.yaml +233 -0
  132. package/xflow/README.md +76 -0
  133. package/xflow/aha/SKILL.md +82 -0
  134. package/xflow/aha/scripts/append_aha.py +50 -0
  135. package/xflow/archive/scripts/archive_change.py +338 -0
  136. package/xflow/archive/templates/merge-design.md +54 -0
  137. package/xflow/archive/templates/merge-workflow.md +26 -0
  138. package/xflow/atoms/__init__.py +6 -0
  139. package/xflow/atoms/a1_issue_create_or_patch.py +70 -0
  140. package/xflow/atoms/a2_branch_create_and_link.py +42 -0
  141. package/xflow/atoms/a3_repo_doctor.py +11 -0
  142. package/xflow/atoms/a4_project_set_status.py +11 -0
  143. package/xflow/atoms/a5_archive_commit_push_close.py +11 -0
  144. package/xflow/atoms/a6_pr_create.py +72 -0
  145. package/xflow/atoms/b1_change_scaffold.py +11 -0
  146. package/xflow/atoms/b2_status_read.py +16 -0
  147. package/xflow/atoms/b2b_status_write.py +20 -0
  148. package/xflow/atoms/b3_status_transition.py +27 -0
  149. package/xflow/atoms/b4_handoff_scaffold_or_refresh.py +11 -0
  150. package/xflow/atoms/b5_aha_append.py +11 -0
  151. package/xflow/atoms/b6_takein_digest.py +11 -0
  152. package/xflow/atoms/b6b_lookback_digest.py +11 -0
  153. package/xflow/atoms/b7_change_mission_bind.py +43 -0
  154. package/xflow/atoms/c1_mem.py +54 -0
  155. package/xflow/atoms/e6_gate_local_precheck.py +23 -0
  156. package/xflow/atoms/h1_design_lite_gate.py +74 -0
  157. package/xflow/atoms/i6a_tdd_run.py +104 -0
  158. package/xflow/atoms/i6b_tdd_proof_validate.py +165 -0
  159. package/xflow/atoms/i6c_tdd_quality_review.py +357 -0
  160. package/xflow/atoms/j1_tests_run.py +129 -0
  161. package/xflow/atoms/j4a_spec_consistency_structural.py +105 -0
  162. package/xflow/atoms/j4b_spec_delta_review.py +316 -0
  163. package/xflow/atoms/j4c_openspec_migration_map.py +121 -0
  164. package/xflow/atoms/j4d_spec_clarify_gate.py +373 -0
  165. package/xflow/atoms/j6_cr_review.py +443 -0
  166. package/xflow/atoms/k1_artifacts_complete_check.py +39 -0
  167. package/xflow/atoms/k2_merge_snippets_apply.py +11 -0
  168. package/xflow/atoms/k3_handoff_refresh.py +14 -0
  169. package/xflow/atoms/k4_mem_lesson_persist.py +65 -0
  170. package/xflow/atoms/k5_archive_publish.py +21 -0
  171. package/xflow/atoms/k6_aha_merge.py +42 -0
  172. package/xflow/atoms/tdd_lib.py +747 -0
  173. package/xflow/corps/SKILL.md +109 -0
  174. package/xflow/exec/scripts/set_issue_in_progress.py +159 -0
  175. package/xflow/goal/SKILL.md +150 -0
  176. package/xflow/handoff/SKILL.md +95 -0
  177. package/xflow/handoff/scripts/scaffold_handoff.py +304 -0
  178. package/xflow/lookback/SKILL.md +66 -0
  179. package/xflow/lookback/scripts/lookback_digest.py +46 -0
  180. package/xflow/openissue/scripts/open_issue_flow.py +438 -0
  181. package/xflow/plan/SKILL.md +38 -0
  182. package/xflow/ralph/SKILL.md +123 -0
  183. package/xflow/scripts/check_installed_xflow_skill_sync.sh +44 -0
  184. package/xflow/scripts/common.py +480 -0
  185. package/xflow/scripts/doctor.py +278 -0
  186. package/xflow/scripts/sync_installed_xflow_skill.sh +134 -0
  187. package/xflow/takein/SKILL.md +81 -0
  188. package/xflow/takein/scripts/takein_digest.py +88 -0
  189. package/xflow/yolo/SKILL.md +66 -0
  190. package/xflow/yolo/scripts/scaffold_specs_change.py +673 -0
  191. package/xflow/yolo/scripts/yolo_gate.py +312 -0
  192. package/xflow/yolo/templates/DESIGN.md +179 -0
@@ -0,0 +1,30 @@
1
+ import { ingestEvent } from '../../mission-state.js';
2
+ import { mutateStore, resolveDataFile } from '../../state-store.js';
3
+ import { resolveBoundMissionId } from '../../bindings.js';
4
+
5
+ export async function eventIngest(input = {}, context = {}) {
6
+ const projectRoot = context.projectRoot || process.cwd();
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const missionId = resolveBoundMissionId({
9
+ projectRoot,
10
+ changeId: context.changeId || null,
11
+ missionId: input.mission_id || null,
12
+ runtimeMissionId: context.runtime?.missionId || null,
13
+ });
14
+
15
+ if (!missionId) {
16
+ throw new Error('mission_id is required for D4.event.ingest');
17
+ }
18
+
19
+ const result = await mutateStore(
20
+ (state) => ingestEvent(state, { ...input, mission_id: missionId }),
21
+ dataFile,
22
+ );
23
+
24
+ return {
25
+ ok: true,
26
+ data_file: dataFile,
27
+ mission_id: missionId,
28
+ ...result,
29
+ };
30
+ }
@@ -0,0 +1,55 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
4
+ import { readJsonFile, resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
5
+
6
+ export async function experienceDigest(input = {}, context = {}) {
7
+ const projectRoot = context.projectRoot || process.cwd();
8
+ const changeId = context.changeId || input.change_id;
9
+ if (!changeId) throw new Error('change_id is required for experience.digest');
10
+
11
+ const entries = collectExperienceEntries(projectRoot);
12
+ const digest = {
13
+ version: 1,
14
+ change_id: changeId,
15
+ generated_at: new Date().toISOString(),
16
+ advisory_only: true,
17
+ entry_count: entries.length,
18
+ entries: entries.map((entry) => ({
19
+ title: entry.title || entry.summary || 'Untitled experience',
20
+ category: entry.category || 'workflow',
21
+ content: entry.content || entry.summary || '',
22
+ confidence: entry.confidence ?? null,
23
+ source: entry.source || null,
24
+ })),
25
+ };
26
+ const outputPath = resolveChangePath(projectRoot, changeId, 'experience_digest.json');
27
+ writeJsonFile(outputPath, digest);
28
+ return {
29
+ ok: true,
30
+ experience_digest_file: outputPath,
31
+ digest,
32
+ };
33
+ }
34
+
35
+ function collectExperienceEntries(projectRoot) {
36
+ const sources = [];
37
+ const localDir = resolve(projectRoot, '.xflow', 'experience');
38
+ if (existsSync(localDir)) {
39
+ for (const file of readdirSync(localDir)) {
40
+ if (file.endsWith('.json')) sources.push(resolve(localDir, file));
41
+ }
42
+ }
43
+ const teamExperience = resolve(projectRoot, '.xflow', 'storage', 'team', 'experience.json');
44
+ if (existsSync(teamExperience)) sources.push(teamExperience);
45
+
46
+ const entries = [];
47
+ for (const source of sources) {
48
+ const payload = readJsonFile(source, {});
49
+ const sourceEntries = Array.isArray(payload.entries) ? payload.entries : Array.isArray(payload) ? payload : [];
50
+ for (const entry of sourceEntries) {
51
+ entries.push({ ...entry, source });
52
+ }
53
+ }
54
+ return entries;
55
+ }
@@ -0,0 +1,83 @@
1
+ import { existsSync } from 'node:fs';
2
+
3
+ import { makeArtifactEnvelope, readJsonFile, resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
4
+ import { resolveBoundMissionId } from '../../bindings.js';
5
+ import { getLatestPhaseEntry, getLatestPhaseRun } from '../../phase-run-read.js';
6
+ import { readStore, resolveDataFile } from '../../state-store.js';
7
+
8
+ function deriveQaStatus({ verifyProof, qaPacket, phaseEntry }) {
9
+ const testsPassed = verifyProof?.all_passed === true;
10
+ const packetStatus = qaPacket?.status || null;
11
+ const phaseStatus = phaseEntry?.effective_status || phaseEntry?.status || null;
12
+ const failedChecks = Array.isArray(qaPacket?.failed_checks) ? qaPacket.failed_checks : [];
13
+ const journeyCovered = qaPacket?.primary_journey_covered === true;
14
+ const moduleCoverage = qaPacket?.module_coverage || null;
15
+ const modulesCovered = Number(moduleCoverage?.covered_module_count || 0);
16
+ const modulesExpected = Number(moduleCoverage?.expected_module_count || 0);
17
+ const modulesSatisfied = modulesExpected > 0 && modulesCovered >= modulesExpected;
18
+
19
+ if (packetStatus === 'accepted' && testsPassed && journeyCovered && modulesSatisfied && failedChecks.length === 0) return 'accepted';
20
+ if (packetStatus === 'needs_human') return 'needs_human';
21
+ if (packetStatus === 'rejected') return 'rejected';
22
+ if (phaseStatus === 'completed' && testsPassed && qaPacket) return 'needs_human';
23
+ if (testsPassed) return 'needs_human';
24
+ return 'needs_human';
25
+ }
26
+
27
+ export async function artifactVerify(input = {}, context = {}) {
28
+ const projectRoot = context.projectRoot || process.cwd();
29
+ const changeId = context.changeId || input.change_id || null;
30
+ if (!changeId) {
31
+ throw new Error('change_id is required for E5.artifact.verify');
32
+ }
33
+
34
+ const phase = input.phase || context.phase?.id || 'qa';
35
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
36
+ const missionId = resolveBoundMissionId({
37
+ projectRoot,
38
+ changeId,
39
+ missionId: input.mission_id || null,
40
+ runtimeMissionId: context.runtime?.missionId || null,
41
+ });
42
+
43
+ if (!missionId) {
44
+ throw new Error('mission_id is required for E5.artifact.verify');
45
+ }
46
+
47
+ const state = await readStore(dataFile);
48
+ const phaseRun = getLatestPhaseRun(state, missionId);
49
+ const phaseEntry = getLatestPhaseEntry(phaseRun, phase);
50
+ const verifyProofPath = resolveChangePath(projectRoot, changeId, 'verify_proof.json');
51
+ const verifyProof = readJsonFile(verifyProofPath, null);
52
+ const qaPacket = phaseEntry?.team_run?.qa_acceptance_packet || null;
53
+ const status = deriveQaStatus({ verifyProof, qaPacket, phaseEntry });
54
+ const packet = makeArtifactEnvelope({
55
+ phase,
56
+ changeId,
57
+ missionId,
58
+ phaseRun,
59
+ phaseEntry,
60
+ extra: {
61
+ verify_proof: verifyProof,
62
+ qa_acceptance_packet: qaPacket,
63
+ manifest_valid: existsSync(verifyProofPath),
64
+ qa_status: status,
65
+ primary_journey_covered: qaPacket?.primary_journey_covered ?? (verifyProof?.all_passed === true),
66
+ module_coverage: qaPacket?.module_coverage || null,
67
+ cross_module_continuity: qaPacket?.cross_module_continuity || null,
68
+ failed_checks: qaPacket?.failed_checks || [],
69
+ },
70
+ });
71
+
72
+ const outputPath = resolveChangePath(projectRoot, changeId, 'qa_acceptance.json');
73
+ writeJsonFile(outputPath, packet);
74
+
75
+ return {
76
+ ok: status === 'accepted',
77
+ verdict: status,
78
+ mission_id: missionId,
79
+ phase_run_id: phaseRun?.phase_run_id || null,
80
+ qa_acceptance_file: outputPath,
81
+ packet,
82
+ };
83
+ }
@@ -0,0 +1,119 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import Ajv from 'ajv';
6
+
7
+ import { readJsonFile, resolveChangePath } from '../../change-artifacts.js';
8
+
9
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
10
+ const CONTRACT_SCHEMA = JSON.parse(readFileSync(
11
+ resolve(__dirname, '../../../../schemas/competitor-reconstruction-contract.schema.json'),
12
+ 'utf8',
13
+ ));
14
+ const ajv = new Ajv({ allErrors: true });
15
+ const validateContractSchema = ajv.compile(CONTRACT_SCHEMA);
16
+
17
+ function ensureNonEmptyArray(value) {
18
+ return Array.isArray(value) && value.length > 0;
19
+ }
20
+
21
+ function loadArtifact(projectRoot, changeId, filename) {
22
+ return readJsonFile(resolveChangePath(projectRoot, changeId, filename), null);
23
+ }
24
+
25
+ export async function competitorContractValidate(input = {}, context = {}) {
26
+ const projectRoot = context.projectRoot || process.cwd();
27
+ const changeId = context.changeId || input.change_id || null;
28
+ if (!changeId) {
29
+ throw new Error('change_id is required for H2c.design.competitor_contract_validate');
30
+ }
31
+
32
+ const artifactPath = resolveChangePath(projectRoot, changeId, 'competitor_reconstruction_review.json');
33
+ const artifact = readJsonFile(artifactPath, null);
34
+ const contract = artifact?.competitor_reconstruction_contract || null;
35
+ const referenceLockArtifact = loadArtifact(projectRoot, changeId, 'reference_surface_lock.json');
36
+ const reconstructionPackArtifact = loadArtifact(projectRoot, changeId, 'reconstruction_pack.json');
37
+ const generationContractArtifact = loadArtifact(projectRoot, changeId, 'generation_contract.json');
38
+ const referenceLock = referenceLockArtifact?.reference_surface_lock || null;
39
+ const reconstructionPack = reconstructionPackArtifact?.reconstruction_pack || null;
40
+ const generationContract = generationContractArtifact?.generation_contract || null;
41
+ const issues = [];
42
+
43
+ if (!artifact) issues.push('competitor_reconstruction_review_missing');
44
+ if (!contract) issues.push('competitor_reconstruction_contract_missing');
45
+ if (contract && !validateContractSchema(contract)) {
46
+ issues.push('competitor_reconstruction_contract_schema_invalid');
47
+ }
48
+ if (contract?.status !== 'frozen') issues.push('competitor_reconstruction_contract_not_frozen');
49
+ if (!contract?.competitor_product || contract.competitor_product === 'required') issues.push('competitor_product_unspecified');
50
+ if (!ensureNonEmptyArray(contract?.target_surfaces)) issues.push('target_surfaces_missing');
51
+ if (!contract?.primary_reference_surface || !contract.target_surfaces?.includes(contract.primary_reference_surface)) {
52
+ issues.push('primary_reference_surface_missing');
53
+ }
54
+ if (!ensureNonEmptyArray(contract?.required_modules)) issues.push('required_modules_missing');
55
+ if (!ensureNonEmptyArray(contract?.primary_journeys)) issues.push('primary_journeys_missing');
56
+ if (!ensureNonEmptyArray(contract?.business_logic_invariants)) issues.push('business_logic_invariants_missing');
57
+ if (!contract?.decomposition_requirements || typeof contract.decomposition_requirements !== 'object') {
58
+ issues.push('decomposition_requirements_missing');
59
+ }
60
+
61
+ if (!referenceLock) issues.push('reference_surface_lock_missing');
62
+ if (referenceLock?.status !== 'locked') issues.push('reference_surface_lock_not_locked');
63
+ if (referenceLock?.primary_reference_surface !== contract?.primary_reference_surface) {
64
+ issues.push('reference_surface_lock_mismatch');
65
+ }
66
+
67
+ if (!reconstructionPack) issues.push('reconstruction_pack_missing');
68
+ if (reconstructionPack?.status !== 'ready') issues.push('reconstruction_pack_not_ready');
69
+ if (!ensureNonEmptyArray(reconstructionPack?.surface_map)) issues.push('reconstruction_pack_surface_map_missing');
70
+ if (!ensureNonEmptyArray(reconstructionPack?.layout_map?.regions)) issues.push('reconstruction_pack_layout_map_missing');
71
+ if (!ensureNonEmptyArray(reconstructionPack?.component_inventory)) issues.push('reconstruction_pack_component_inventory_missing');
72
+ if (!ensureNonEmptyArray(reconstructionPack?.component_blueprint)) issues.push('reconstruction_pack_component_blueprint_missing');
73
+ if (!ensureNonEmptyArray(reconstructionPack?.state_inventory)) issues.push('reconstruction_pack_state_inventory_missing');
74
+ if (!reconstructionPack?.visual_token_map || typeof reconstructionPack.visual_token_map !== 'object') {
75
+ issues.push('reconstruction_pack_visual_token_map_missing');
76
+ }
77
+ if (!reconstructionPack?.reference_intermediate_model || typeof reconstructionPack.reference_intermediate_model !== 'object') {
78
+ issues.push('reconstruction_pack_reference_intermediate_model_missing');
79
+ }
80
+
81
+ if (!generationContract) issues.push('generation_contract_missing');
82
+ if (generationContract?.status !== 'ready') issues.push('generation_contract_not_ready');
83
+ if (!generationContract?.layout_constraints || typeof generationContract.layout_constraints !== 'object') {
84
+ issues.push('generation_contract_layout_constraints_missing');
85
+ }
86
+ if (!generationContract?.component_constraints || typeof generationContract.component_constraints !== 'object') {
87
+ issues.push('generation_contract_component_constraints_missing');
88
+ }
89
+ if (!ensureNonEmptyArray(generationContract?.component_constraints?.component_blueprint)) {
90
+ issues.push('generation_contract_component_blueprint_missing');
91
+ }
92
+ if (!ensureNonEmptyArray(generationContract?.component_constraints?.staged_generation?.stages)) {
93
+ issues.push('generation_contract_staged_generation_missing');
94
+ }
95
+ if (!generationContract?.state_constraints || typeof generationContract.state_constraints !== 'object') {
96
+ issues.push('generation_contract_state_constraints_missing');
97
+ }
98
+ if (!generationContract?.visual_constraints || typeof generationContract.visual_constraints !== 'object') {
99
+ issues.push('generation_contract_visual_constraints_missing');
100
+ }
101
+ if (!generationContract?.visual_constraints?.geometry_hints || typeof generationContract.visual_constraints.geometry_hints !== 'object') {
102
+ issues.push('generation_contract_geometry_hints_missing');
103
+ }
104
+ if (!generationContract?.visual_constraints?.token_hints || typeof generationContract.visual_constraints.token_hints !== 'object') {
105
+ issues.push('generation_contract_token_hints_missing');
106
+ }
107
+ if (!generationContract?.repair_policy || typeof generationContract.repair_policy !== 'object') {
108
+ issues.push('generation_contract_repair_policy_missing');
109
+ }
110
+
111
+ return {
112
+ ok: issues.length === 0,
113
+ status: issues.length === 0 ? 'accepted' : 'needs_human',
114
+ artifact_file: artifactPath,
115
+ issues,
116
+ };
117
+ }
118
+
119
+ export default competitorContractValidate;
@@ -0,0 +1,64 @@
1
+ import { resolveBoundMissionId } from '../../bindings.js';
2
+ import { collectExistingFiles, makeArtifactEnvelope, resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
3
+ import { getLatestPhaseEntry, getLatestPhaseRun } from '../../phase-run-read.js';
4
+ import { readStore, resolveDataFile } from '../../state-store.js';
5
+
6
+ export async function gateEvidenceCollect(input = {}, context = {}) {
7
+ const projectRoot = context.projectRoot || process.cwd();
8
+ const changeId = context.changeId || input.change_id || null;
9
+ if (!changeId) {
10
+ throw new Error('change_id is required for J5.gate.evidence_collect');
11
+ }
12
+
13
+ const phase = input.phase || context.phase?.id || null;
14
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
15
+ const missionId = resolveBoundMissionId({
16
+ projectRoot,
17
+ changeId,
18
+ missionId: input.mission_id || null,
19
+ runtimeMissionId: context.runtime?.missionId || null,
20
+ });
21
+
22
+ const state = missionId ? await readStore(dataFile) : null;
23
+ const phaseRun = missionId ? getLatestPhaseRun(state, missionId) : null;
24
+ const phaseEntry = phase ? getLatestPhaseEntry(phaseRun, phase) : null;
25
+ const baseFiles = collectExistingFiles([
26
+ resolveChangePath(projectRoot, changeId, 'proposal.json'),
27
+ resolveChangePath(projectRoot, changeId, 'proposal.md'),
28
+ resolveChangePath(projectRoot, changeId, 'plan.json'),
29
+ resolveChangePath(projectRoot, changeId, 'plan.md'),
30
+ resolveChangePath(projectRoot, changeId, 'verify_proof.json'),
31
+ resolveChangePath(projectRoot, changeId, 'qa_acceptance.json'),
32
+ resolveChangePath(projectRoot, changeId, 'review.json'),
33
+ resolveChangePath(projectRoot, changeId, 'gate_final.json'),
34
+ ]);
35
+
36
+ if (phaseRun?.phase_run_file) {
37
+ baseFiles.push(phaseRun.phase_run_file);
38
+ }
39
+
40
+ const packet = makeArtifactEnvelope({
41
+ phase,
42
+ changeId,
43
+ missionId,
44
+ phaseRun,
45
+ phaseEntry,
46
+ extra: {
47
+ evidence_refs: [...new Set(baseFiles)],
48
+ evidence_count: baseFiles.length,
49
+ phase_run_session_id: context.runtime?.phaseRunSessionId || null,
50
+ },
51
+ });
52
+
53
+ const outputPath = resolveChangePath(projectRoot, changeId, `evidence/${phase || 'latest'}.json`);
54
+ writeJsonFile(outputPath, packet);
55
+
56
+ return {
57
+ ok: true,
58
+ mission_id: missionId,
59
+ phase_run_id: phaseRun?.phase_run_id || null,
60
+ evidence_file: outputPath,
61
+ evidence_count: packet.evidence_count,
62
+ packet,
63
+ };
64
+ }
@@ -0,0 +1,78 @@
1
+ import {
2
+ makeArtifactEnvelope,
3
+ renderMarkdownArtifact,
4
+ resolveChangePath,
5
+ writeJsonFile,
6
+ writeTextFile,
7
+ } from '../../change-artifacts.js';
8
+ import { resolveBoundMissionId } from '../../bindings.js';
9
+ import { getLatestPhaseEntry, getLatestPhaseRun } from '../../phase-run-read.js';
10
+ import { readStore, resolveDataFile } from '../../state-store.js';
11
+
12
+ function titleForPhase(phase) {
13
+ if (phase === 'proposal') return 'Proposal';
14
+ if (phase === 'plan') return 'Plan';
15
+ return phase ? phase.replace(/_/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase()) : 'Phase';
16
+ }
17
+
18
+ export async function gatePayloadFromPhaseRun(input = {}, context = {}) {
19
+ const projectRoot = context.projectRoot || process.cwd();
20
+ const changeId = context.changeId || input.change_id || null;
21
+ if (!changeId) {
22
+ throw new Error('change_id is required for E4.gate.payload_from_phase_run');
23
+ }
24
+
25
+ const phase = input.phase || context.phase?.id || null;
26
+ if (!phase) {
27
+ throw new Error('phase is required for E4.gate.payload_from_phase_run');
28
+ }
29
+
30
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
31
+ const missionId = resolveBoundMissionId({
32
+ projectRoot,
33
+ changeId,
34
+ missionId: input.mission_id || null,
35
+ runtimeMissionId: context.runtime?.missionId || null,
36
+ });
37
+
38
+ if (!missionId) {
39
+ throw new Error('mission_id is required for E4.gate.payload_from_phase_run');
40
+ }
41
+
42
+ const state = await readStore(dataFile);
43
+ const phaseRun = getLatestPhaseRun(state, missionId);
44
+ if (!phaseRun) {
45
+ throw new Error(`No phase run found for mission ${missionId}`);
46
+ }
47
+
48
+ const phaseEntry = getLatestPhaseEntry(phaseRun, phase);
49
+ const envelope = makeArtifactEnvelope({
50
+ phase,
51
+ changeId,
52
+ missionId,
53
+ phaseRun,
54
+ phaseEntry,
55
+ extra: {
56
+ qa_acceptance_packet: phaseEntry?.team_run?.qa_acceptance_packet || null,
57
+ workspace_evidence: phaseEntry?.team_run?.workspace_evidence || phaseRun.workspace_evidence || null,
58
+ },
59
+ });
60
+
61
+ const jsonPath = resolveChangePath(projectRoot, changeId, `${phase}.json`);
62
+ writeJsonFile(jsonPath, envelope);
63
+
64
+ let mdPath = null;
65
+ if (phase === 'proposal' || phase === 'plan') {
66
+ mdPath = resolveChangePath(projectRoot, changeId, `${phase}.md`);
67
+ writeTextFile(mdPath, renderMarkdownArtifact({ title: titleForPhase(phase), phase, envelope }));
68
+ }
69
+
70
+ return {
71
+ ok: true,
72
+ mission_id: missionId,
73
+ phase_run_id: phaseRun.phase_run_id,
74
+ json_file: jsonPath,
75
+ markdown_file: mdPath,
76
+ payload: envelope,
77
+ };
78
+ }
@@ -0,0 +1,183 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { createHash } from 'node:crypto';
6
+
7
+ import Ajv from 'ajv';
8
+
9
+ import { readJsonFile, resolveChangePath } from '../../change-artifacts.js';
10
+
11
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
12
+ const VISUAL_BENCHMARK_SCHEMA = JSON.parse(readFileSync(
13
+ resolve(__dirname, '../../../../schemas/visual-benchmark.schema.json'),
14
+ 'utf8',
15
+ ));
16
+ const ajv = new Ajv({ allErrors: true });
17
+ const validateBenchmarkSchema = ajv.compile(VISUAL_BENCHMARK_SCHEMA);
18
+
19
+ function resolveEvidencePath(projectRoot, filePath) {
20
+ if (!filePath) return null;
21
+ return isAbsolute(filePath) ? filePath : resolve(projectRoot, filePath);
22
+ }
23
+
24
+ function sameResolvedEvidencePath(projectRoot, left, right) {
25
+ const leftPath = resolveEvidencePath(projectRoot, left);
26
+ const rightPath = resolveEvidencePath(projectRoot, right);
27
+ if (!leftPath || !rightPath) return false;
28
+ if (leftPath === rightPath) return true;
29
+ if (!existsSync(leftPath) || !existsSync(rightPath)) return false;
30
+ return hashFile(leftPath) === hashFile(rightPath);
31
+ }
32
+
33
+ function hashFile(filePath) {
34
+ return createHash('sha256').update(readFileSync(filePath)).digest('hex');
35
+ }
36
+
37
+ export async function visualBenchmarkValidate(input = {}, context = {}) {
38
+ const projectRoot = context.projectRoot || process.cwd();
39
+ const changeId = context.changeId || input.change_id || null;
40
+ const advisory = String(input.mode || '').toLowerCase() === 'advisory';
41
+ if (!changeId) {
42
+ throw new Error('change_id is required for H6b.visual.benchmark_validate');
43
+ }
44
+
45
+ const artifactPath = resolveChangePath(projectRoot, changeId, 'visual_benchmark.json');
46
+ const artifact = readJsonFile(artifactPath, null);
47
+ const issues = [];
48
+
49
+ if (!artifact) {
50
+ issues.push('visual_benchmark_missing');
51
+ } else {
52
+ if (!validateBenchmarkSchema(artifact)) {
53
+ issues.push('visual_benchmark_schema_invalid');
54
+ }
55
+ const scenarios = Array.isArray(artifact.scenarios) ? artifact.scenarios : [];
56
+ const requiredModules = Array.isArray(artifact.required_modules) ? artifact.required_modules : [];
57
+ if (!artifact.competitor_product) issues.push('visual_benchmark_competitor_missing');
58
+ if (requiredModules.length === 0) issues.push('visual_benchmark_required_modules_missing');
59
+ if (artifact.benchmark_mode !== 'reference_backed') issues.push('visual_benchmark_not_reference_backed');
60
+ if (!['reference_scenarios', 'capture_url'].includes(artifact.benchmark_input_mode)) {
61
+ issues.push('visual_benchmark_input_contract_missing');
62
+ }
63
+ if (artifact.report_file) {
64
+ const reportPath = resolveEvidencePath(projectRoot, artifact.report_file);
65
+ if (!reportPath || !existsSync(reportPath)) issues.push('visual_benchmark_report_unresolved');
66
+ }
67
+ if (scenarios.length === 0) issues.push('visual_benchmark_scenarios_missing');
68
+ if (scenarios.some((scenario) => !scenario.viewport)) issues.push('visual_benchmark_viewport_missing');
69
+ if (scenarios.some((scenario) => !scenario.reference_image)) issues.push('visual_benchmark_reference_image_missing');
70
+ if (scenarios.some((scenario) => !scenario.screenshot_image)) issues.push('visual_benchmark_screenshot_image_missing');
71
+ if (scenarios.some((scenario) => sameResolvedEvidencePath(projectRoot, scenario.reference_image, scenario.screenshot_image))) {
72
+ issues.push('visual_benchmark_self_referential_scenario');
73
+ }
74
+ if (scenarios.some((scenario) => {
75
+ const referencePath = resolveEvidencePath(projectRoot, scenario.reference_image);
76
+ return !referencePath || !existsSync(referencePath);
77
+ })) {
78
+ issues.push('visual_benchmark_reference_image_unresolved');
79
+ }
80
+ if (scenarios.some((scenario) => {
81
+ const screenshotPath = resolveEvidencePath(projectRoot, scenario.screenshot_image);
82
+ return !screenshotPath || !existsSync(screenshotPath);
83
+ })) {
84
+ issues.push('visual_benchmark_screenshot_image_unresolved');
85
+ }
86
+ if (scenarios.some((scenario) => !scenario.diff_metrics || typeof scenario.diff_metrics !== 'object')) {
87
+ issues.push('visual_benchmark_diff_metrics_missing');
88
+ }
89
+ if (scenarios.some((scenario) => scenario.diff_metrics?.status !== 'pass')) {
90
+ issues.push('visual_benchmark_diff_metrics_unresolved');
91
+ }
92
+ if (scenarios.some((scenario) => !Array.isArray(scenario.structure_checks) || scenario.structure_checks.length === 0)) {
93
+ issues.push('visual_benchmark_structure_checks_missing');
94
+ }
95
+ if (scenarios.some((scenario) =>
96
+ Array.isArray(scenario.structure_checks) && scenario.structure_checks.some((check) => check?.status !== 'pass')
97
+ )) {
98
+ issues.push('visual_benchmark_structure_checks_unresolved');
99
+ }
100
+ if (scenarios.some((scenario) =>
101
+ scenario.visual_token_contract
102
+ && (!Array.isArray(scenario.token_checks) || scenario.token_checks.length === 0)
103
+ )) {
104
+ issues.push('visual_benchmark_token_checks_missing');
105
+ }
106
+ if (scenarios.some((scenario) =>
107
+ scenario.visual_token_contract
108
+ && Array.isArray(scenario.token_checks)
109
+ && scenario.token_checks.some((check) => check?.status !== 'pass')
110
+ )) {
111
+ issues.push('visual_benchmark_token_checks_unresolved');
112
+ }
113
+ if (scenarios.some((scenario) =>
114
+ Array.isArray(scenario.state_evidence)
115
+ && scenario.state_evidence.length > 0
116
+ && (!Array.isArray(scenario.state_transition_checks) || scenario.state_transition_checks.length === 0)
117
+ )) {
118
+ issues.push('visual_benchmark_state_transition_checks_missing');
119
+ }
120
+ if (scenarios.some((scenario) =>
121
+ Array.isArray(scenario.state_transition_checks)
122
+ && scenario.state_transition_checks.some((check) => check?.status !== 'pass')
123
+ )) {
124
+ issues.push('visual_benchmark_state_transition_checks_unresolved');
125
+ }
126
+ if (scenarios.some((scenario) =>
127
+ Array.isArray(scenario.state_evidence)
128
+ && scenario.state_evidence.some((state) => Array.isArray(state?.frame_screenshot_images) && state.frame_screenshot_images.length > 0)
129
+ && (!Array.isArray(scenario.motion_transition_checks) || scenario.motion_transition_checks.length === 0)
130
+ )) {
131
+ issues.push('visual_benchmark_motion_transition_checks_missing');
132
+ }
133
+ if (scenarios.some((scenario) =>
134
+ Array.isArray(scenario.motion_transition_checks)
135
+ && scenario.motion_transition_checks.some((check) => check?.status !== 'pass')
136
+ )) {
137
+ issues.push('visual_benchmark_motion_transition_checks_unresolved');
138
+ }
139
+ if (scenarios.some((scenario) =>
140
+ scenario.state_contract
141
+ && (!Array.isArray(scenario.state_contract_checks) || scenario.state_contract_checks.length === 0)
142
+ )) {
143
+ issues.push('visual_benchmark_state_contract_checks_missing');
144
+ }
145
+ if (scenarios.some((scenario) =>
146
+ scenario.state_contract
147
+ && Array.isArray(scenario.state_contract_checks)
148
+ && scenario.state_contract_checks.some((check) => check?.status !== 'pass')
149
+ )) {
150
+ issues.push('visual_benchmark_state_contract_checks_unresolved');
151
+ }
152
+ if (scenarios.some((scenario) =>
153
+ scenario.state_family_contract
154
+ && (!Array.isArray(scenario.state_family_checks) || scenario.state_family_checks.length === 0)
155
+ )) {
156
+ issues.push('visual_benchmark_state_family_checks_missing');
157
+ }
158
+ if (scenarios.some((scenario) =>
159
+ scenario.state_family_contract
160
+ && Array.isArray(scenario.state_family_checks)
161
+ && scenario.state_family_checks.some((check) => check?.status !== 'pass')
162
+ )) {
163
+ issues.push('visual_benchmark_state_family_checks_unresolved');
164
+ }
165
+ if (artifact.benchmark_matrix_contract && (!Array.isArray(artifact.matrix_checks) || artifact.matrix_checks.length === 0)) {
166
+ issues.push('visual_benchmark_matrix_checks_missing');
167
+ }
168
+ if (Array.isArray(artifact.matrix_checks) && artifact.matrix_checks.some((check) => check?.status !== 'pass')) {
169
+ issues.push('visual_benchmark_matrix_checks_unresolved');
170
+ }
171
+ if (scenarios.some((scenario) => scenario.status !== 'pass')) issues.push('visual_benchmark_has_unresolved_scenarios');
172
+ }
173
+
174
+ return {
175
+ ok: advisory ? true : issues.length === 0,
176
+ status: issues.length === 0 ? 'accepted' : 'needs_human',
177
+ artifact_file: artifactPath,
178
+ issues,
179
+ advisory,
180
+ };
181
+ }
182
+
183
+ export default visualBenchmarkValidate;