@surlymochan/evalid 0.0.0-stage → 0.4.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.
- package/CHANGELOG.md +94 -0
- package/LICENSE +21 -0
- package/README.md +315 -2
- package/RELEASE_NOTES.md +124 -0
- package/atoms/registry.json +758 -0
- package/bin/xflow.js +7261 -0
- package/docs/adoption/README.md +128 -0
- package/docs/adoption/openflow-release-hardening.md +61 -0
- package/docs/benchmark-openflow-vs-gstack-superpowers-openspec-2026-05-25.md +81 -0
- package/docs/compatibility.md +52 -0
- package/docs/competitive-benchmark.md +108 -0
- package/docs/corps-operator-guide.md +75 -0
- package/docs/demo-proof.md +126 -0
- package/docs/examples-gallery.md +115 -0
- package/docs/fixtures/tracker-item.json +7 -0
- package/docs/goal-vs-codex.md +129 -0
- package/docs/gstack-comparison.md +88 -0
- package/docs/install-upgrade.md +133 -0
- package/docs/integrations.md +49 -0
- package/docs/launch-demo.md +123 -0
- package/docs/launch-dossier.md +180 -0
- package/docs/methodology.md +149 -0
- package/docs/npm-publish-handoff.md +96 -0
- package/docs/openspec-migration.md +106 -0
- package/docs/public-benchmark.md +68 -0
- package/docs/public-release.md +145 -0
- package/docs/quality-assessment.md +139 -0
- package/docs/quickstart.md +156 -0
- package/docs/reviewer-guide.md +43 -0
- package/docs/spec-kit-benchmark.md +73 -0
- package/docs/super-assistant-comparison.md +106 -0
- package/docs/superpowers-comparison.md +82 -0
- package/docs/team-adoption.md +123 -0
- package/docs/tooling-matrix.md +91 -0
- package/docs/walkthrough.md +76 -0
- package/docs/workflow-manual.md +1130 -0
- package/package.json +83 -4
- package/prompts/pencil-draft.md +46 -0
- package/prompts/pencil-refine.md +46 -0
- package/prompts/phase-artifact.md +24 -0
- package/prompts/phase-execution.md +32 -0
- package/schemas/competitor-reconstruction-contract.schema.json +91 -0
- package/schemas/llm-design-review-aggregate.schema.json +83 -0
- package/schemas/visual-benchmark.schema.json +978 -0
- package/schemas/workflow.schema.json +100 -0
- package/scripts/run-tests.mjs +23 -0
- package/scripts/takein_digest.py +139 -0
- package/specs/README.md +118 -0
- package/specs/architecture.md +36 -0
- package/specs/product.md +33 -0
- package/specs/workflow.md +349 -0
- package/src/agent_team/atoms/codex-app-bridge.js +8 -0
- package/src/agent_team/atoms/design/competitor-reconstruction-review.js +44 -0
- package/src/agent_team/atoms/design/contract-freeze.js +8 -0
- package/src/agent_team/atoms/design/generation-contract.js +442 -0
- package/src/agent_team/atoms/design/reconstruction-pack.js +246 -0
- package/src/agent_team/atoms/design/reference-surface-lock.js +49 -0
- package/src/agent_team/atoms/patch-challenge.js +19 -0
- package/src/agent_team/atoms/pencil/accept.js +9 -0
- package/src/agent_team/atoms/pencil/draft.js +365 -0
- package/src/agent_team/atoms/pencil/refine.js +229 -0
- package/src/agent_team/atoms/phase-run-dispatch.js +8 -0
- package/src/agent_team/atoms/team-run.js +380 -0
- package/src/agent_team/atoms/visual/aesthetic-review.js +231 -0
- package/src/agent_team/atoms/visual/benchmark-repair-plan.js +291 -0
- package/src/agent_team/atoms/visual/benchmark.js +1652 -0
- package/src/agent_team/atoms/visual/design-selection.js +23 -0
- package/src/agent_team/atoms/visual/direction-synthesis.js +124 -0
- package/src/agent_team/atoms/visual/dom-rect-export.js +129 -0
- package/src/agent_team/atoms/visual/image-diff.js +186 -0
- package/src/agent_team/atoms/visual/layout-competition.js +8 -0
- package/src/agent_team/atoms/visual/page-evidence-capture.js +806 -0
- package/src/agent_team/atoms/visual/playwright-runtime.js +8 -0
- package/src/agent_team/atoms/visual/review-aggregate.js +87 -0
- package/src/agent_team/atoms/visual/review.js +77 -0
- package/src/agent_team/atoms/visual/visual-report.js +197 -0
- package/src/agent_team/atoms/visual/visual-token-export.js +417 -0
- package/src/agent_team/common.js +311 -0
- package/src/core/atoms/agent-invoke.js +684 -0
- package/src/core/atoms/event/ingest.js +30 -0
- package/src/core/atoms/experience/digest.js +55 -0
- package/src/core/atoms/gate/artifact-verify.js +83 -0
- package/src/core/atoms/gate/competitor-contract-validate.js +119 -0
- package/src/core/atoms/gate/evidence-collect.js +64 -0
- package/src/core/atoms/gate/payload-from-phase-run.js +78 -0
- package/src/core/atoms/gate/visual-benchmark-validate.js +183 -0
- package/src/core/atoms/gate/workbench.js +79 -0
- package/src/core/atoms/intake/classify.js +15 -0
- package/src/core/atoms/intake/design-skill-select.js +16 -0
- package/src/core/atoms/intake/preview-or-refine.js +19 -0
- package/src/core/atoms/intake/recommend.js +20 -0
- package/src/core/atoms/intake/research-same-category.js +19 -0
- package/src/core/atoms/intervention/create.js +30 -0
- package/src/core/atoms/memory/manage.js +27 -0
- package/src/core/atoms/memory/mount.js +13 -0
- package/src/core/atoms/mission/create.js +19 -0
- package/src/core/atoms/mission/list.js +14 -0
- package/src/core/atoms/mission/show.js +26 -0
- package/src/core/atoms/notify/checkpoint.js +46 -0
- package/src/core/atoms/phase-run/complete.js +42 -0
- package/src/core/atoms/phase-run/fail.js +40 -0
- package/src/core/atoms/phase-run/persist.js +39 -0
- package/src/core/atoms/phase-run/start.js +30 -0
- package/src/core/atoms/plugin/run.js +76 -0
- package/src/core/atoms/policy/gate.js +90 -0
- package/src/core/atoms/policy/resolve.js +11 -0
- package/src/core/atoms/storage/sync.js +57 -0
- package/src/core/atoms/workflow/advance.js +25 -0
- package/src/core/atoms/workflow/guard.js +127 -0
- package/src/core/atoms/workflow/state-load.js +37 -0
- package/src/core/bindings.js +37 -0
- package/src/core/change-artifacts.js +134 -0
- package/src/core/competitive-score.js +265 -0
- package/src/core/concurrency-policy.js +35 -0
- package/src/core/execution-log.js +108 -0
- package/src/core/intake-recommendation.js +134 -0
- package/src/core/memory-state.js +62 -0
- package/src/core/mission-state.js +780 -0
- package/src/core/pending-gates.js +66 -0
- package/src/core/phase-cost.js +116 -0
- package/src/core/phase-run-read.js +27 -0
- package/src/core/policy-overlay.js +180 -0
- package/src/core/state-store.js +201 -0
- package/src/core/workflow-executor.js +1014 -0
- package/src/core/workflow-integrity.js +111 -0
- package/src/core/workflow-loader.js +163 -0
- package/src/core/workflow-rerun.js +84 -0
- package/src/core/workflow-state.js +119 -0
- package/src/server.js +1307 -0
- package/workflows/corps.yaml +481 -0
- package/workflows/yolo.yaml +241 -0
- package/xflow/README.md +76 -0
- package/xflow/aha/SKILL.md +82 -0
- package/xflow/aha/scripts/append_aha.py +50 -0
- package/xflow/archive/scripts/archive_change.py +338 -0
- package/xflow/archive/templates/merge-design.md +54 -0
- package/xflow/archive/templates/merge-workflow.md +26 -0
- package/xflow/atoms/__init__.py +6 -0
- package/xflow/atoms/a1_issue_create_or_patch.py +70 -0
- package/xflow/atoms/a2_branch_create_and_link.py +42 -0
- package/xflow/atoms/a3_repo_doctor.py +11 -0
- package/xflow/atoms/a4_project_set_status.py +11 -0
- package/xflow/atoms/a5_archive_commit_push_close.py +11 -0
- package/xflow/atoms/a6_pr_create.py +72 -0
- package/xflow/atoms/b1_change_scaffold.py +11 -0
- package/xflow/atoms/b2_status_read.py +16 -0
- package/xflow/atoms/b2b_status_write.py +20 -0
- package/xflow/atoms/b3_status_transition.py +27 -0
- package/xflow/atoms/b4_handoff_scaffold_or_refresh.py +11 -0
- package/xflow/atoms/b5_aha_append.py +11 -0
- package/xflow/atoms/b6_takein_digest.py +11 -0
- package/xflow/atoms/b6b_lookback_digest.py +11 -0
- package/xflow/atoms/b7_change_mission_bind.py +43 -0
- package/xflow/atoms/c1_mem.py +54 -0
- package/xflow/atoms/e6_gate_local_precheck.py +23 -0
- package/xflow/atoms/h1_design_lite_gate.py +74 -0
- package/xflow/atoms/i6a_tdd_run.py +107 -0
- package/xflow/atoms/i6b_tdd_proof_validate.py +184 -0
- package/xflow/atoms/i6c_tdd_quality_review.py +372 -0
- package/xflow/atoms/j1_tests_run.py +129 -0
- package/xflow/atoms/j4a_spec_consistency_structural.py +105 -0
- package/xflow/atoms/j4b_spec_delta_review.py +316 -0
- package/xflow/atoms/j4c_openspec_migration_map.py +121 -0
- package/xflow/atoms/j4d_spec_clarify_gate.py +373 -0
- package/xflow/atoms/j6_cr_review.py +443 -0
- package/xflow/atoms/k1_artifacts_complete_check.py +39 -0
- package/xflow/atoms/k2_merge_snippets_apply.py +11 -0
- package/xflow/atoms/k3_handoff_refresh.py +14 -0
- package/xflow/atoms/k4_mem_lesson_persist.py +65 -0
- package/xflow/atoms/k5_archive_publish.py +21 -0
- package/xflow/atoms/k6_aha_merge.py +42 -0
- package/xflow/atoms/tdd_lib.py +956 -0
- package/xflow/corps/SKILL.md +109 -0
- package/xflow/exec/scripts/set_issue_in_progress.py +159 -0
- package/xflow/goal/SKILL.md +150 -0
- package/xflow/handoff/SKILL.md +95 -0
- package/xflow/handoff/scripts/scaffold_handoff.py +304 -0
- package/xflow/lookback/SKILL.md +66 -0
- package/xflow/lookback/scripts/lookback_digest.py +46 -0
- package/xflow/openissue/scripts/open_issue_flow.py +438 -0
- package/xflow/plan/SKILL.md +47 -0
- package/xflow/ralph/SKILL.md +123 -0
- package/xflow/scripts/check_installed_xflow_skill_sync.sh +44 -0
- package/xflow/scripts/common.py +483 -0
- package/xflow/scripts/doctor.py +278 -0
- package/xflow/scripts/sync_installed_xflow_skill.sh +134 -0
- package/xflow/takein/SKILL.md +81 -0
- package/xflow/takein/scripts/takein_digest.py +88 -0
- package/xflow/yolo/SKILL.md +75 -0
- package/xflow/yolo/scripts/scaffold_specs_change.py +673 -0
- package/xflow/yolo/scripts/yolo_gate.py +312 -0
- 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;
|