@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.
- package/CHANGELOG.md +74 -0
- package/LICENSE +21 -0
- package/README.md +315 -2
- package/RELEASE_NOTES.md +102 -0
- package/atoms/registry.json +758 -0
- package/bin/xflow.js +7208 -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 +83 -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 +1125 -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 +998 -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 +479 -0
- package/workflows/yolo.yaml +233 -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 +104 -0
- package/xflow/atoms/i6b_tdd_proof_validate.py +165 -0
- package/xflow/atoms/i6c_tdd_quality_review.py +357 -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 +747 -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 +38 -0
- package/xflow/ralph/SKILL.md +123 -0
- package/xflow/scripts/check_installed_xflow_skill_sync.sh +44 -0
- package/xflow/scripts/common.py +480 -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 +66 -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,49 @@
|
|
|
1
|
+
import { resolveChangePath, writeJsonFile } from '../../../core/change-artifacts.js';
|
|
2
|
+
|
|
3
|
+
function list(inputValue) {
|
|
4
|
+
return Array.isArray(inputValue) ? inputValue.filter(Boolean) : [];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function referenceSurfaceLock(input = {}, context = {}) {
|
|
8
|
+
const projectRoot = context.projectRoot || process.cwd();
|
|
9
|
+
const changeId = context.changeId || input.change_id || null;
|
|
10
|
+
if (!changeId) throw new Error('change_id is required for H2d.design.reference_surface_lock');
|
|
11
|
+
|
|
12
|
+
const targetSurfaces = list(input.target_surfaces);
|
|
13
|
+
const requiredModules = list(input.required_modules);
|
|
14
|
+
const primaryJourneys = list(input.primary_journeys);
|
|
15
|
+
const primaryReferenceSurface = typeof input.primary_reference_surface === 'string' && input.primary_reference_surface.trim()
|
|
16
|
+
? input.primary_reference_surface.trim()
|
|
17
|
+
: (targetSurfaces[0] || null);
|
|
18
|
+
const supportingReferenceSurfaces = targetSurfaces.filter((surface) => surface && surface !== primaryReferenceSurface);
|
|
19
|
+
const artifactPath = resolveChangePath(projectRoot, changeId, 'reference_surface_lock.json');
|
|
20
|
+
|
|
21
|
+
const payload = {
|
|
22
|
+
version: 1,
|
|
23
|
+
phase: 'reference_surface_lock',
|
|
24
|
+
change_id: changeId,
|
|
25
|
+
generated_at: new Date().toISOString(),
|
|
26
|
+
status: 'locked',
|
|
27
|
+
summary: 'A single primary reference surface was locked before generation to prevent multi-reference drift.',
|
|
28
|
+
reference_surface_lock: {
|
|
29
|
+
status: 'locked',
|
|
30
|
+
primary_reference_surface: primaryReferenceSurface,
|
|
31
|
+
supporting_reference_surfaces: supportingReferenceSurfaces,
|
|
32
|
+
focus_modules: requiredModules,
|
|
33
|
+
focus_journeys: primaryJourneys,
|
|
34
|
+
locking_rationale: primaryReferenceSurface
|
|
35
|
+
? `Generation should mirror ${primaryReferenceSurface} first and only borrow from supporting surfaces when explicitly needed.`
|
|
36
|
+
: 'Primary reference surface is still missing and must be supplied before generation.',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
writeJsonFile(artifactPath, payload);
|
|
41
|
+
return {
|
|
42
|
+
ok: Boolean(primaryReferenceSurface),
|
|
43
|
+
status: primaryReferenceSurface ? 'locked' : 'needs_human',
|
|
44
|
+
output_file: artifactPath,
|
|
45
|
+
payload,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default referenceSurfaceLock;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { writeReviewArtifact } from '../common.js';
|
|
2
|
+
|
|
3
|
+
export async function patchChallenge(input = {}, context = {}) {
|
|
4
|
+
const projectRoot = context.projectRoot || process.cwd();
|
|
5
|
+
const changeId = context.changeId || input.change_id || null;
|
|
6
|
+
const mode = input.mode || context.phase?.id || 'execute';
|
|
7
|
+
if (!changeId) throw new Error('change_id is required for I4.patch.challenge');
|
|
8
|
+
|
|
9
|
+
const findings = mode === 'review'
|
|
10
|
+
? [{ summary: 'No blocking review finding remained after the latest patch.' }]
|
|
11
|
+
: [{ summary: 'Execution patch challenge finished without a structural regression.' }];
|
|
12
|
+
const outputPath = writeReviewArtifact(projectRoot, changeId, `Patch challenge ${mode} completed.`, findings);
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
ok: true,
|
|
16
|
+
output_file: outputPath,
|
|
17
|
+
findings,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { synthesizePhaseArtifact } from '../../common.js';
|
|
2
|
+
|
|
3
|
+
export async function pencilAccept(input = {}, context = {}) {
|
|
4
|
+
return synthesizePhaseArtifact(input, context, {
|
|
5
|
+
phase: 'design_accept',
|
|
6
|
+
summary: 'Design was accepted and attested for downstream execution.',
|
|
7
|
+
writeAttestation: true,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { readJsonFile, resolveChangePath, writeJsonFile } from '../../../core/change-artifacts.js';
|
|
6
|
+
import { synthesizePhaseArtifact } from '../../common.js';
|
|
7
|
+
import { capturePageEvidence } from '../visual/page-evidence-capture.js';
|
|
8
|
+
|
|
9
|
+
async function capturePreview(projectRoot, changeId) {
|
|
10
|
+
const contract = readJsonFile(resolveChangePath(projectRoot, changeId, 'corps-input.json'), {}) || {};
|
|
11
|
+
const workspaceIndex = contract.workspace ? resolve(projectRoot, contract.workspace, 'index.html') : null;
|
|
12
|
+
const captureUrl = typeof contract.capture_url === 'string' && contract.capture_url.trim()
|
|
13
|
+
? contract.capture_url.trim()
|
|
14
|
+
: (workspaceIndex && existsSync(workspaceIndex) ? pathToFileURL(workspaceIndex).href : null);
|
|
15
|
+
if (!captureUrl) return null;
|
|
16
|
+
|
|
17
|
+
const primaryScenario = Array.isArray(contract.reference_scenarios_json) ? contract.reference_scenarios_json[0] : null;
|
|
18
|
+
const viewport = primaryScenario?.viewport || {};
|
|
19
|
+
return capturePageEvidence({
|
|
20
|
+
url: captureUrl,
|
|
21
|
+
screenshotPath: resolveChangePath(projectRoot, changeId, 'pencil_preview.png'),
|
|
22
|
+
snapshotPath: resolveChangePath(projectRoot, changeId, 'pencil_snapshot.json'),
|
|
23
|
+
width: viewport.width || 1440,
|
|
24
|
+
height: viewport.height || 900,
|
|
25
|
+
waitMs: 150,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function unique(values = []) {
|
|
30
|
+
return [...new Set((Array.isArray(values) ? values : []).filter(Boolean))];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function designSystemSourcesFromPack(designSystemPack = {}) {
|
|
34
|
+
return unique((designSystemPack.practice_sources || []).map((source) => source.id));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function deriveHistorySignals(benchmarkRepairPlan = {}, blueprint = []) {
|
|
38
|
+
const scenarioRepairs = Array.isArray(benchmarkRepairPlan?.scenario_repairs) ? benchmarkRepairPlan.scenario_repairs : [];
|
|
39
|
+
const componentToPrimitive = new Map(blueprint.map((component) => [component.id, component.primitive_role]));
|
|
40
|
+
const panelScores = new Map();
|
|
41
|
+
const panelReasons = new Map();
|
|
42
|
+
const primitiveScores = new Map();
|
|
43
|
+
const primitiveReasons = new Map();
|
|
44
|
+
for (const scenario of scenarioRepairs) {
|
|
45
|
+
for (const repair of scenario.repairs || []) {
|
|
46
|
+
const boost = repair.priority === 'high' ? 3 : repair.priority === 'medium' ? 2 : 1;
|
|
47
|
+
for (const panel of repair.impacted_panels || repair.target_modules || []) {
|
|
48
|
+
panelScores.set(panel, (panelScores.get(panel) || 0) + boost);
|
|
49
|
+
panelReasons.set(panel, unique([...(panelReasons.get(panel) || []), repair.id, repair.reason].filter(Boolean)));
|
|
50
|
+
}
|
|
51
|
+
const primitiveRoles = (repair.target_primitive_roles && repair.target_primitive_roles.length > 0)
|
|
52
|
+
? repair.target_primitive_roles
|
|
53
|
+
: unique((repair.target_component_ids || []).map((id) => componentToPrimitive.get(id)).filter(Boolean));
|
|
54
|
+
for (const primitive of primitiveRoles) {
|
|
55
|
+
primitiveScores.set(primitive, (primitiveScores.get(primitive) || 0) + boost);
|
|
56
|
+
primitiveReasons.set(primitive, unique([...(primitiveReasons.get(primitive) || []), repair.id, repair.reason].filter(Boolean)));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { panelScores, panelReasons, primitiveScores, primitiveReasons };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readPreviousPrimitiveFeedback(projectRoot, changeId) {
|
|
64
|
+
const artifact = readJsonFile(resolveChangePath(projectRoot, changeId, 'primitive_feedback_history.json'), null);
|
|
65
|
+
const entries = Array.isArray(artifact?.primitive_feedback_history?.entries)
|
|
66
|
+
? artifact.primitive_feedback_history.entries
|
|
67
|
+
: [];
|
|
68
|
+
return new Map(entries.map((entry) => [`${entry.module}::${entry.primitive_role}`, entry]));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildPrimitiveFeedbackHistory(blueprint = [], benchmarkRepairPlan = {}, previousHistory = new Map()) {
|
|
72
|
+
const { primitiveScores, primitiveReasons } = deriveHistorySignals(benchmarkRepairPlan, blueprint);
|
|
73
|
+
const grouped = new Map();
|
|
74
|
+
for (const component of blueprint) {
|
|
75
|
+
const key = `${component.module}::${component.primitive_role}`;
|
|
76
|
+
const existing = grouped.get(key) || {
|
|
77
|
+
module: component.module,
|
|
78
|
+
primitive_role: component.primitive_role,
|
|
79
|
+
target_component_ids: [],
|
|
80
|
+
};
|
|
81
|
+
existing.target_component_ids.push(component.id);
|
|
82
|
+
grouped.set(key, existing);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [...grouped.values()].map((entry) => {
|
|
86
|
+
const key = `${entry.module}::${entry.primitive_role}`;
|
|
87
|
+
const previous = previousHistory.get(key) || {};
|
|
88
|
+
const currentIssueScore = primitiveScores.get(entry.primitive_role) || 0;
|
|
89
|
+
const previousIssueScore = previous.current_issue_score || 0;
|
|
90
|
+
const improvementScore = Math.max(0, previousIssueScore - currentIssueScore);
|
|
91
|
+
const regressionScore = Math.max(0, currentIssueScore - previousIssueScore);
|
|
92
|
+
const stabilizationScore = currentIssueScore === 0 && previousIssueScore > 0 ? previousIssueScore : 0;
|
|
93
|
+
const attentionScore = Math.max(0, (currentIssueScore * 10) + (regressionScore * 5) - (improvementScore * 3) - stabilizationScore);
|
|
94
|
+
return {
|
|
95
|
+
module: entry.module,
|
|
96
|
+
primitive_role: entry.primitive_role,
|
|
97
|
+
target_component_ids: entry.target_component_ids,
|
|
98
|
+
current_issue_score: currentIssueScore,
|
|
99
|
+
previous_issue_score: previousIssueScore,
|
|
100
|
+
improvement_score: improvementScore,
|
|
101
|
+
regression_score: regressionScore,
|
|
102
|
+
stabilization_score: stabilizationScore,
|
|
103
|
+
attention_score: attentionScore,
|
|
104
|
+
history_repair_reasons: primitiveReasons.get(entry.primitive_role) || [],
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function applyHistoryToPanelFocus(panelFocusSequence = [], benchmarkRepairPlan = {}) {
|
|
110
|
+
const { panelScores, panelReasons } = deriveHistorySignals(benchmarkRepairPlan);
|
|
111
|
+
return [...panelFocusSequence]
|
|
112
|
+
.map((panel) => ({
|
|
113
|
+
...panel,
|
|
114
|
+
history_boost_score: panelScores.get(panel.module) || 0,
|
|
115
|
+
history_repair_reasons: panelReasons.get(panel.module) || [],
|
|
116
|
+
}))
|
|
117
|
+
.sort((a, b) => {
|
|
118
|
+
const historyDelta = (b.history_boost_score || 0) - (a.history_boost_score || 0);
|
|
119
|
+
if (historyDelta !== 0) return historyDelta;
|
|
120
|
+
return (a.priority_order || 0) - (b.priority_order || 0);
|
|
121
|
+
})
|
|
122
|
+
.map((panel, index) => ({
|
|
123
|
+
...panel,
|
|
124
|
+
priority_order: index + 1,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function buildPrimitiveFocusSequence(panelFocusSequence = [], blueprint = [], benchmarkRepairPlan = {}, primitiveFeedbackEntries = []) {
|
|
129
|
+
const feedbackMap = new Map(
|
|
130
|
+
primitiveFeedbackEntries.map((entry) => [`${entry.module}::${entry.primitive_role}`, entry]),
|
|
131
|
+
);
|
|
132
|
+
return panelFocusSequence.flatMap((panel) => {
|
|
133
|
+
const panelComponents = blueprint.filter((component) => component.module === panel.module);
|
|
134
|
+
const grouped = new Map();
|
|
135
|
+
for (const component of panelComponents) {
|
|
136
|
+
const key = component.primitive_role;
|
|
137
|
+
const feedback = feedbackMap.get(`${panel.module}::${key}`) || {};
|
|
138
|
+
const entry = grouped.get(key) || {
|
|
139
|
+
primitive_role: key,
|
|
140
|
+
module: panel.module,
|
|
141
|
+
target_component_ids: [],
|
|
142
|
+
history_boost_score: feedback.current_issue_score || 0,
|
|
143
|
+
history_repair_reasons: feedback.history_repair_reasons || [],
|
|
144
|
+
improvement_score: feedback.improvement_score || 0,
|
|
145
|
+
regression_score: feedback.regression_score || 0,
|
|
146
|
+
stabilization_score: feedback.stabilization_score || 0,
|
|
147
|
+
attention_score: feedback.attention_score || 0,
|
|
148
|
+
};
|
|
149
|
+
entry.target_component_ids.push(component.id);
|
|
150
|
+
grouped.set(key, entry);
|
|
151
|
+
}
|
|
152
|
+
return [...grouped.values()]
|
|
153
|
+
.sort((a, b) => {
|
|
154
|
+
const attentionDelta = (b.attention_score || 0) - (a.attention_score || 0);
|
|
155
|
+
if (attentionDelta !== 0) return attentionDelta;
|
|
156
|
+
const historyDelta = (b.history_boost_score || 0) - (a.history_boost_score || 0);
|
|
157
|
+
if (historyDelta !== 0) return historyDelta;
|
|
158
|
+
return a.primitive_role.localeCompare(b.primitive_role);
|
|
159
|
+
})
|
|
160
|
+
.map((entry, index) => ({
|
|
161
|
+
...entry,
|
|
162
|
+
priority_order_within_panel: index + 1,
|
|
163
|
+
}));
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function buildDraftPenContent(changeId, generationContract = {}, reconstructionPack = {}, benchmarkRepairPlan = {}, primitiveFeedbackEntries = [], designSystemPack = {}) {
|
|
168
|
+
const stages = generationContract?.component_constraints?.staged_generation?.stages || [];
|
|
169
|
+
const panelFocusSequence = applyHistoryToPanelFocus(
|
|
170
|
+
generationContract?.component_constraints?.panel_focus_sequence || [],
|
|
171
|
+
benchmarkRepairPlan,
|
|
172
|
+
);
|
|
173
|
+
const blueprint = generationContract?.component_constraints?.component_blueprint
|
|
174
|
+
|| reconstructionPack?.component_blueprint
|
|
175
|
+
|| [];
|
|
176
|
+
const primitiveFocusSequence = buildPrimitiveFocusSequence(panelFocusSequence, blueprint, benchmarkRepairPlan, primitiveFeedbackEntries);
|
|
177
|
+
const geometryHints = generationContract?.visual_constraints?.geometry_hints || {};
|
|
178
|
+
const tokenHints = generationContract?.visual_constraints?.token_hints || {};
|
|
179
|
+
const aestheticStandard = generationContract?.visual_constraints?.aesthetic_standard || {};
|
|
180
|
+
const imageReferenceGeneration = generationContract?.visual_constraints?.image_reference_generation || {};
|
|
181
|
+
const designSystemSources = unique((designSystemPack.practice_sources || []).map((source) => source.id));
|
|
182
|
+
const designSystemStates = unique(designSystemPack.component_policy?.required_states || []);
|
|
183
|
+
const designSystemPrimitives = unique(designSystemPack.component_policy?.allowed_primitives || []);
|
|
184
|
+
const pencilRoleBoundary = generationContract?.visual_constraints?.pencil_role_boundary || {};
|
|
185
|
+
const primarySurface = generationContract?.primary_reference_surface || reconstructionPack?.primary_reference_surface || 'unknown_surface';
|
|
186
|
+
|
|
187
|
+
const stageLines = stages.map((stage) => {
|
|
188
|
+
const stageComponents = blueprint.filter((component) => (stage.target_component_ids || []).includes(component.id));
|
|
189
|
+
const roles = unique(stageComponents.map((component) => component.primitive_role)).join(', ') || 'none';
|
|
190
|
+
return [
|
|
191
|
+
`- ${stage.order}. ${stage.id}`,
|
|
192
|
+
` - target_component_ids: ${(stage.target_component_ids || []).join(', ') || '(none)'}`,
|
|
193
|
+
` - primitive_roles: ${roles}`,
|
|
194
|
+
` - geometry_profiles: ${(stage.primary_geometry_profiles || []).join(', ') || '(none)'}`,
|
|
195
|
+
` - token_profiles: ${(stage.primary_token_profiles || []).join(', ') || '(none)'}`,
|
|
196
|
+
].join('\n');
|
|
197
|
+
}).join('\n');
|
|
198
|
+
|
|
199
|
+
const blueprintLines = blueprint.map((component) => [
|
|
200
|
+
`- ${component.id}`,
|
|
201
|
+
` - module: ${component.module}`,
|
|
202
|
+
` - primitive_role: ${component.primitive_role}`,
|
|
203
|
+
` - hierarchy_role: ${component.hierarchy_role}`,
|
|
204
|
+
` - stage_bucket: ${component.stage_bucket}`,
|
|
205
|
+
` - geometry_profile: ${component.geometry_profile}`,
|
|
206
|
+
` - token_profile: ${component.token_profile}`,
|
|
207
|
+
].join('\n')).join('\n');
|
|
208
|
+
|
|
209
|
+
const panelLines = panelFocusSequence.map((panel) => [
|
|
210
|
+
`- ${panel.priority_order}. ${panel.module}`,
|
|
211
|
+
` - score: ${panel.score}`,
|
|
212
|
+
` - history_boost_score: ${panel.history_boost_score || 0}`,
|
|
213
|
+
` - history_repair_reasons: ${(panel.history_repair_reasons || []).join(', ') || '(none)'}`,
|
|
214
|
+
` - target_component_ids: ${(panel.target_component_ids || []).join(', ') || '(none)'}`,
|
|
215
|
+
` - dominant_primitive_roles: ${(panel.dominant_primitive_roles || []).join(', ') || '(none)'}`,
|
|
216
|
+
` - stage_ids: ${(panel.stage_ids || []).join(', ') || '(none)'}`,
|
|
217
|
+
` - local_generation_rule: ${panel.local_generation_rule || '(none)'}`,
|
|
218
|
+
].join('\n')).join('\n');
|
|
219
|
+
|
|
220
|
+
const primitiveLines = primitiveFocusSequence.map((entry) => [
|
|
221
|
+
`- ${entry.module} / ${entry.priority_order_within_panel}. ${entry.primitive_role}`,
|
|
222
|
+
` - history_boost_score: ${entry.history_boost_score || 0}`,
|
|
223
|
+
` - improvement_score: ${entry.improvement_score || 0}`,
|
|
224
|
+
` - regression_score: ${entry.regression_score || 0}`,
|
|
225
|
+
` - stabilization_score: ${entry.stabilization_score || 0}`,
|
|
226
|
+
` - attention_score: ${entry.attention_score || 0}`,
|
|
227
|
+
` - history_repair_reasons: ${(entry.history_repair_reasons || []).join(', ') || '(none)'}`,
|
|
228
|
+
` - target_component_ids: ${(entry.target_component_ids || []).join(', ') || '(none)'}`,
|
|
229
|
+
].join('\n')).join('\n');
|
|
230
|
+
|
|
231
|
+
return [
|
|
232
|
+
'PENCIL pencil_draft',
|
|
233
|
+
`change_id: ${changeId}`,
|
|
234
|
+
`primary_reference_surface: ${primarySurface}`,
|
|
235
|
+
'execution_mode: staged_component_generation',
|
|
236
|
+
'',
|
|
237
|
+
'[staged_generation]',
|
|
238
|
+
stageLines || '(none)',
|
|
239
|
+
'',
|
|
240
|
+
'[panel_focus_sequence]',
|
|
241
|
+
panelLines || '(none)',
|
|
242
|
+
'',
|
|
243
|
+
'[primitive_focus_sequence]',
|
|
244
|
+
primitiveLines || '(none)',
|
|
245
|
+
'',
|
|
246
|
+
'[component_blueprint]',
|
|
247
|
+
blueprintLines || '(none)',
|
|
248
|
+
'',
|
|
249
|
+
'[geometry_hints]',
|
|
250
|
+
`alignment_rules: ${(geometryHints.alignment_rules || []).join(' | ') || '(none)'}`,
|
|
251
|
+
`primitive_profiles: ${(geometryHints.primitive_profiles || []).map((profile) => profile.id).join(', ') || '(none)'}`,
|
|
252
|
+
'',
|
|
253
|
+
'[token_hints]',
|
|
254
|
+
`primitive_token_profiles: ${(tokenHints.primitive_token_profiles || []).map((profile) => profile.id).join(', ') || '(none)'}`,
|
|
255
|
+
`typography_roles: ${(tokenHints.typography_roles || []).join(', ') || '(none)'}`,
|
|
256
|
+
`spacing_roles: ${(tokenHints.spacing_roles || []).join(', ') || '(none)'}`,
|
|
257
|
+
`emphasis_roles: ${(tokenHints.emphasis_roles || []).join(', ') || '(none)'}`,
|
|
258
|
+
'',
|
|
259
|
+
'[aesthetic_standard]',
|
|
260
|
+
`level: ${aestheticStandard.level || 'unspecified'}`,
|
|
261
|
+
`min_accept_score: ${aestheticStandard.min_accept_score ?? 'unspecified'}`,
|
|
262
|
+
`dimensions: ${(aestheticStandard.dimensions || []).join(', ') || '(none)'}`,
|
|
263
|
+
`anti_patterns: ${(aestheticStandard.anti_patterns || []).join(', ') || '(none)'}`,
|
|
264
|
+
'',
|
|
265
|
+
'[image_reference_generation]',
|
|
266
|
+
`enabled: ${imageReferenceGeneration.enabled === false ? 'false' : 'true'}`,
|
|
267
|
+
`model_capability: ${imageReferenceGeneration.model_capability || '(none)'}`,
|
|
268
|
+
`role: ${imageReferenceGeneration.role || '(none)'}`,
|
|
269
|
+
`required_outputs: ${(imageReferenceGeneration.required_outputs || []).map((output) => output.id).join(', ') || '(none)'}`,
|
|
270
|
+
'',
|
|
271
|
+
'[design_system_pack]',
|
|
272
|
+
`profile: ${designSystemPack.profile || 'unspecified'}`,
|
|
273
|
+
`practice_sources: ${designSystemSources.join(', ') || '(none)'}`,
|
|
274
|
+
`allowed_primitives: ${designSystemPrimitives.join(', ') || '(none)'}`,
|
|
275
|
+
`required_states: ${designSystemStates.join(', ') || '(none)'}`,
|
|
276
|
+
`preview_artifacts: ${(designSystemPack.preview_loop?.required_artifacts || []).join(', ') || '(none)'}`,
|
|
277
|
+
'',
|
|
278
|
+
'[pencil_role_boundary]',
|
|
279
|
+
`role: ${pencilRoleBoundary.role || 'editable_surface_assembly_and_targeted_refinement'}`,
|
|
280
|
+
`sufficient_alone_for_high_aesthetic: ${pencilRoleBoundary.sufficient_alone_for_high_aesthetic === true ? 'true' : 'false'}`,
|
|
281
|
+
'',
|
|
282
|
+
'[guardrails]',
|
|
283
|
+
'- Build shell first, then high-priority primitives, then supporting primitives, then local polish.',
|
|
284
|
+
'- Complete the first focus panel to stable density and hierarchy before widening to the next panel.',
|
|
285
|
+
'- Do not redraw the full page when a stage only targets a subset of components.',
|
|
286
|
+
'- Preserve the locked primary reference surface and region order through all stages.',
|
|
287
|
+
'- Treat image-model reference frames as aesthetic targets, not as a substitute for editable Pencil/DOM implementation.',
|
|
288
|
+
'- Treat design_system_pack.json as a hard component/state/style contract, not optional inspiration.',
|
|
289
|
+
'',
|
|
290
|
+
].join('\n');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export async function pencilDraft(input = {}, context = {}) {
|
|
294
|
+
const projectRoot = context.projectRoot || process.cwd();
|
|
295
|
+
const changeId = context.changeId || input.change_id || null;
|
|
296
|
+
const generationArtifact = changeId
|
|
297
|
+
? readJsonFile(resolveChangePath(projectRoot, changeId, 'generation_contract.json'), null)
|
|
298
|
+
: null;
|
|
299
|
+
const reconstructionArtifact = changeId
|
|
300
|
+
? readJsonFile(resolveChangePath(projectRoot, changeId, 'reconstruction_pack.json'), null)
|
|
301
|
+
: null;
|
|
302
|
+
const benchmarkRepairArtifact = changeId
|
|
303
|
+
? readJsonFile(resolveChangePath(projectRoot, changeId, 'benchmark_repair_plan.json'), null)
|
|
304
|
+
: null;
|
|
305
|
+
const designSystemPack = changeId
|
|
306
|
+
? readJsonFile(resolveChangePath(projectRoot, changeId, 'design_system_pack.json'), {}) || {}
|
|
307
|
+
: {};
|
|
308
|
+
const generationContract = generationArtifact?.generation_contract || {};
|
|
309
|
+
const reconstructionPack = reconstructionArtifact?.reconstruction_pack || {};
|
|
310
|
+
const benchmarkRepairPlan = benchmarkRepairArtifact?.benchmark_repair_plan || {};
|
|
311
|
+
const previousPrimitiveFeedback = changeId ? readPreviousPrimitiveFeedback(projectRoot, changeId) : new Map();
|
|
312
|
+
const panelFocusSequence = applyHistoryToPanelFocus(
|
|
313
|
+
generationContract?.component_constraints?.panel_focus_sequence || [],
|
|
314
|
+
benchmarkRepairPlan,
|
|
315
|
+
);
|
|
316
|
+
const primitiveFeedbackEntries = buildPrimitiveFeedbackHistory(
|
|
317
|
+
generationContract?.component_constraints?.component_blueprint || reconstructionPack.component_blueprint || [],
|
|
318
|
+
benchmarkRepairPlan,
|
|
319
|
+
previousPrimitiveFeedback,
|
|
320
|
+
);
|
|
321
|
+
const primitiveFocusSequence = buildPrimitiveFocusSequence(
|
|
322
|
+
panelFocusSequence,
|
|
323
|
+
generationContract?.component_constraints?.component_blueprint || reconstructionPack.component_blueprint || [],
|
|
324
|
+
benchmarkRepairPlan,
|
|
325
|
+
primitiveFeedbackEntries,
|
|
326
|
+
);
|
|
327
|
+
const penContent = buildDraftPenContent(
|
|
328
|
+
changeId,
|
|
329
|
+
generationContract,
|
|
330
|
+
reconstructionPack,
|
|
331
|
+
benchmarkRepairPlan,
|
|
332
|
+
primitiveFeedbackEntries,
|
|
333
|
+
designSystemPack,
|
|
334
|
+
);
|
|
335
|
+
const result = await synthesizePhaseArtifact(input, context, {
|
|
336
|
+
phase: 'pencil_draft',
|
|
337
|
+
summary: 'Initial Pencil draft generated from staged component-local generation instructions.',
|
|
338
|
+
writePen: true,
|
|
339
|
+
penContent,
|
|
340
|
+
});
|
|
341
|
+
if (changeId) {
|
|
342
|
+
writeJsonFile(resolveChangePath(projectRoot, changeId, 'pencil_generation_plan.json'), {
|
|
343
|
+
change_id: changeId,
|
|
344
|
+
generated_at: new Date().toISOString(),
|
|
345
|
+
primary_reference_surface: generationContract.primary_reference_surface || reconstructionPack.primary_reference_surface || null,
|
|
346
|
+
staged_generation: generationContract?.component_constraints?.staged_generation || { stages: [] },
|
|
347
|
+
panel_focus_sequence: panelFocusSequence,
|
|
348
|
+
primitive_focus_sequence: primitiveFocusSequence,
|
|
349
|
+
component_blueprint: generationContract?.component_constraints?.component_blueprint || reconstructionPack.component_blueprint || [],
|
|
350
|
+
design_system_pack: {
|
|
351
|
+
profile: designSystemPack.profile || null,
|
|
352
|
+
practice_sources: designSystemSourcesFromPack(designSystemPack),
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
writeJsonFile(resolveChangePath(projectRoot, changeId, 'primitive_feedback_history.json'), {
|
|
356
|
+
change_id: changeId,
|
|
357
|
+
generated_at: new Date().toISOString(),
|
|
358
|
+
primitive_feedback_history: {
|
|
359
|
+
entries: primitiveFeedbackEntries,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
await capturePreview(projectRoot, changeId).catch(() => null);
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { readJsonFile, resolveChangePath, writeJsonFile } from '../../../core/change-artifacts.js';
|
|
6
|
+
import { synthesizePhaseArtifact } from '../../common.js';
|
|
7
|
+
import { capturePageEvidence } from '../visual/page-evidence-capture.js';
|
|
8
|
+
|
|
9
|
+
async function capturePreview(projectRoot, changeId) {
|
|
10
|
+
const contract = readJsonFile(resolveChangePath(projectRoot, changeId, 'corps-input.json'), {}) || {};
|
|
11
|
+
const workspaceIndex = contract.workspace ? resolve(projectRoot, contract.workspace, 'index.html') : null;
|
|
12
|
+
const captureUrl = typeof contract.capture_url === 'string' && contract.capture_url.trim()
|
|
13
|
+
? contract.capture_url.trim()
|
|
14
|
+
: (workspaceIndex && existsSync(workspaceIndex) ? pathToFileURL(workspaceIndex).href : null);
|
|
15
|
+
if (!captureUrl) return null;
|
|
16
|
+
|
|
17
|
+
const primaryScenario = Array.isArray(contract.reference_scenarios_json) ? contract.reference_scenarios_json[0] : null;
|
|
18
|
+
const viewport = primaryScenario?.viewport || {};
|
|
19
|
+
return capturePageEvidence({
|
|
20
|
+
url: captureUrl,
|
|
21
|
+
screenshotPath: resolveChangePath(projectRoot, changeId, 'pencil_preview.png'),
|
|
22
|
+
snapshotPath: resolveChangePath(projectRoot, changeId, 'pencil_snapshot.json'),
|
|
23
|
+
width: viewport.width || 1440,
|
|
24
|
+
height: viewport.height || 900,
|
|
25
|
+
waitMs: 150,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function flattenRepairTargets(benchmarkRepairPlan = {}) {
|
|
30
|
+
const scenarios = Array.isArray(benchmarkRepairPlan?.scenario_repairs) ? benchmarkRepairPlan.scenario_repairs : [];
|
|
31
|
+
return scenarios.flatMap((scenario) => (scenario.repairs || []).map((repair) => ({
|
|
32
|
+
scenario_id: scenario.id || null,
|
|
33
|
+
...repair,
|
|
34
|
+
})));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function unique(values = []) {
|
|
38
|
+
return [...new Set((Array.isArray(values) ? values : []).filter(Boolean))];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function primitiveFeedbackForChange(projectRoot, changeId) {
|
|
42
|
+
const feedbackArtifact = readJsonFile(resolveChangePath(projectRoot, changeId, 'primitive_feedback_history.json'), null);
|
|
43
|
+
const entries = Array.isArray(feedbackArtifact?.primitive_feedback_history?.entries)
|
|
44
|
+
? feedbackArtifact.primitive_feedback_history.entries
|
|
45
|
+
: [];
|
|
46
|
+
return new Map(entries.map((entry) => [`${entry.module}::${entry.primitive_role}`, entry]));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function summarizeScopeDecision(feedback = {}) {
|
|
50
|
+
const current = Number(feedback.current_issue_score) || 0;
|
|
51
|
+
const regression = Number(feedback.regression_score) || 0;
|
|
52
|
+
const improvement = Number(feedback.improvement_score) || 0;
|
|
53
|
+
const stabilization = Number(feedback.stabilization_score) || 0;
|
|
54
|
+
if (current === 0 && stabilization > 0) return 'freeze';
|
|
55
|
+
if (regression > 0 || current >= 4) return 'allow_big_change';
|
|
56
|
+
if (improvement > 0 || current > 0) return 'micro_tune_only';
|
|
57
|
+
return 'freeze';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildRefineScopes(repairTargets = [], blueprint = [], feedbackMap = new Map()) {
|
|
61
|
+
const targetedIds = new Set(repairTargets.flatMap((repair) => repair.target_component_ids || []));
|
|
62
|
+
const scopes = {
|
|
63
|
+
allow_big_change: [],
|
|
64
|
+
micro_tune_only: [],
|
|
65
|
+
freeze: [],
|
|
66
|
+
};
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
|
|
69
|
+
for (const repair of repairTargets) {
|
|
70
|
+
for (const componentId of repair.target_component_ids || []) {
|
|
71
|
+
const component = blueprint.find((entry) => entry.id === componentId);
|
|
72
|
+
if (!component) continue;
|
|
73
|
+
const feedback = feedbackMap.get(`${component.module}::${component.primitive_role}`) || {};
|
|
74
|
+
const decision = summarizeScopeDecision(feedback);
|
|
75
|
+
if (seen.has(component.id)) continue;
|
|
76
|
+
scopes[decision].push({
|
|
77
|
+
component_id: component.id,
|
|
78
|
+
module: component.module,
|
|
79
|
+
primitive_role: component.primitive_role,
|
|
80
|
+
decision,
|
|
81
|
+
current_issue_score: Number(feedback.current_issue_score) || 0,
|
|
82
|
+
improvement_score: Number(feedback.improvement_score) || 0,
|
|
83
|
+
regression_score: Number(feedback.regression_score) || 0,
|
|
84
|
+
stabilization_score: Number(feedback.stabilization_score) || 0,
|
|
85
|
+
});
|
|
86
|
+
seen.add(component.id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const component of blueprint) {
|
|
91
|
+
if (targetedIds.has(component.id) || seen.has(component.id)) continue;
|
|
92
|
+
const feedback = feedbackMap.get(`${component.module}::${component.primitive_role}`) || {};
|
|
93
|
+
scopes.freeze.push({
|
|
94
|
+
component_id: component.id,
|
|
95
|
+
module: component.module,
|
|
96
|
+
primitive_role: component.primitive_role,
|
|
97
|
+
decision: 'freeze',
|
|
98
|
+
current_issue_score: Number(feedback.current_issue_score) || 0,
|
|
99
|
+
improvement_score: Number(feedback.improvement_score) || 0,
|
|
100
|
+
regression_score: Number(feedback.regression_score) || 0,
|
|
101
|
+
stabilization_score: Number(feedback.stabilization_score) || 0,
|
|
102
|
+
});
|
|
103
|
+
seen.add(component.id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return scopes;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildRefinePenContent(changeId, existingPen, benchmarkRepairPlan = {}, generationContract = {}, refineScopes = null, designSystemPack = {}) {
|
|
110
|
+
const repairTargets = flattenRepairTargets(benchmarkRepairPlan);
|
|
111
|
+
const preserveIds = Array.isArray(generationContract?.component_constraints?.component_blueprint)
|
|
112
|
+
? generationContract.component_constraints.component_blueprint
|
|
113
|
+
.map((component) => component.id)
|
|
114
|
+
.filter((id) => !repairTargets.some((repair) => (repair.target_component_ids || []).includes(id)))
|
|
115
|
+
: [];
|
|
116
|
+
|
|
117
|
+
const repairLines = repairTargets.map((repair, index) => [
|
|
118
|
+
`- ${index + 1}. ${repair.id}`,
|
|
119
|
+
` - scenario: ${repair.scenario_id || 'unknown'}`,
|
|
120
|
+
` - reason: ${repair.reason || 'unknown'}`,
|
|
121
|
+
` - priority: ${repair.priority || 'medium'}`,
|
|
122
|
+
` - target_component_ids: ${(repair.target_component_ids || []).join(', ') || '(none)'}`,
|
|
123
|
+
` - primitive_roles: ${(repair.target_primitive_roles || []).join(', ') || '(none)'}`,
|
|
124
|
+
` - focus_dimensions: ${(repair.focus_dimensions || []).join(', ') || '(none)'}`,
|
|
125
|
+
` - evidence_hotspots: ${(repair.evidence_hotspots || []).join(' | ') || '(none)'}`,
|
|
126
|
+
` - target_feedback_summary: ${(repair.target_feedback_summary || []).map((entry) => `${entry.component_id}:${entry.attention_score}/${entry.current_issue_score}`).join(' | ') || '(none)'}`,
|
|
127
|
+
` - instruction: ${repair.instruction || '(none)'}`,
|
|
128
|
+
].join('\n')).join('\n');
|
|
129
|
+
|
|
130
|
+
const scopeLines = refineScopes
|
|
131
|
+
? Object.entries(refineScopes).map(([scope, entries]) => [
|
|
132
|
+
`- ${scope}`,
|
|
133
|
+
...(entries.length > 0
|
|
134
|
+
? entries.map((entry) => ` - ${entry.component_id} (${entry.module}/${entry.primitive_role}) current=${entry.current_issue_score} improvement=${entry.improvement_score} regression=${entry.regression_score} stabilization=${entry.stabilization_score}`)
|
|
135
|
+
: [' - (none)']),
|
|
136
|
+
].join('\n')).join('\n')
|
|
137
|
+
: '(none)';
|
|
138
|
+
const designSystemSources = unique((designSystemPack.practice_sources || []).map((source) => source.id));
|
|
139
|
+
const requiredStates = unique(designSystemPack.component_policy?.required_states || []);
|
|
140
|
+
const allowedPrimitives = unique(designSystemPack.component_policy?.allowed_primitives || []);
|
|
141
|
+
|
|
142
|
+
return [
|
|
143
|
+
'PENCIL pencil_refine',
|
|
144
|
+
`change_id: ${changeId}`,
|
|
145
|
+
'execution_mode: targeted_component_refine',
|
|
146
|
+
'',
|
|
147
|
+
'[preserve_existing_draft]',
|
|
148
|
+
existingPen?.trim() || '(no prior draft content found)',
|
|
149
|
+
'',
|
|
150
|
+
'[repair_targets]',
|
|
151
|
+
repairLines || '(none)',
|
|
152
|
+
'',
|
|
153
|
+
'[refine_scopes]',
|
|
154
|
+
scopeLines,
|
|
155
|
+
'',
|
|
156
|
+
'[preserve_non_targets]',
|
|
157
|
+
preserveIds.join(', ') || '(none)',
|
|
158
|
+
'',
|
|
159
|
+
'[design_system_pack]',
|
|
160
|
+
`profile: ${designSystemPack.profile || 'unspecified'}`,
|
|
161
|
+
`practice_sources: ${designSystemSources.join(', ') || '(none)'}`,
|
|
162
|
+
`allowed_primitives: ${allowedPrimitives.join(', ') || '(none)'}`,
|
|
163
|
+
`required_states: ${requiredStates.join(', ') || '(none)'}`,
|
|
164
|
+
'',
|
|
165
|
+
'[guardrails]',
|
|
166
|
+
'- Apply only the listed repair targets.',
|
|
167
|
+
'- Preserve non-target components unless a repair target explicitly includes them.',
|
|
168
|
+
'- Do not replace the entire shell when only local component families are failing benchmark.',
|
|
169
|
+
'- allow_big_change targets may receive geometry and hierarchy rewrites inside their local bounds.',
|
|
170
|
+
'- micro_tune_only targets may receive token, spacing, and copy-level adjustments only.',
|
|
171
|
+
'- freeze targets must remain visually and structurally unchanged in this refine pass.',
|
|
172
|
+
'- Keep refinements inside design_system_pack primitives, token policy, and required states.',
|
|
173
|
+
'',
|
|
174
|
+
].join('\n');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function pencilRefine(input = {}, context = {}) {
|
|
178
|
+
const projectRoot = context.projectRoot || process.cwd();
|
|
179
|
+
const changeId = context.changeId || input.change_id || null;
|
|
180
|
+
const existingPenText = changeId
|
|
181
|
+
? (() => {
|
|
182
|
+
try {
|
|
183
|
+
return readFileSync(resolveChangePath(projectRoot, changeId, 'pencil_output.pen'), 'utf8');
|
|
184
|
+
} catch {
|
|
185
|
+
return '';
|
|
186
|
+
}
|
|
187
|
+
})()
|
|
188
|
+
: '';
|
|
189
|
+
const benchmarkRepairArtifact = changeId
|
|
190
|
+
? readJsonFile(resolveChangePath(projectRoot, changeId, 'benchmark_repair_plan.json'), null)
|
|
191
|
+
: null;
|
|
192
|
+
const generationArtifact = changeId
|
|
193
|
+
? readJsonFile(resolveChangePath(projectRoot, changeId, 'generation_contract.json'), null)
|
|
194
|
+
: null;
|
|
195
|
+
const designSystemPack = changeId
|
|
196
|
+
? readJsonFile(resolveChangePath(projectRoot, changeId, 'design_system_pack.json'), {}) || {}
|
|
197
|
+
: {};
|
|
198
|
+
const benchmarkRepairPlan = benchmarkRepairArtifact?.benchmark_repair_plan || {};
|
|
199
|
+
const generationContract = generationArtifact?.generation_contract || {};
|
|
200
|
+
const repairTargets = flattenRepairTargets(benchmarkRepairPlan);
|
|
201
|
+
const blueprint = Array.isArray(generationContract?.component_constraints?.component_blueprint)
|
|
202
|
+
? generationContract.component_constraints.component_blueprint
|
|
203
|
+
: [];
|
|
204
|
+
const feedbackMap = changeId ? primitiveFeedbackForChange(projectRoot, changeId) : new Map();
|
|
205
|
+
const refineScopes = buildRefineScopes(repairTargets, blueprint, feedbackMap);
|
|
206
|
+
const preserveNonTargets = refineScopes.freeze.map((entry) => entry.component_id);
|
|
207
|
+
const penContent = buildRefinePenContent(changeId, existingPenText, benchmarkRepairPlan, generationContract, refineScopes, designSystemPack);
|
|
208
|
+
const result = await synthesizePhaseArtifact(input, context, {
|
|
209
|
+
phase: 'pencil_refine',
|
|
210
|
+
summary: 'Pencil artifact refined using component-local repair targets only.',
|
|
211
|
+
writePen: true,
|
|
212
|
+
penContent,
|
|
213
|
+
});
|
|
214
|
+
if (changeId) {
|
|
215
|
+
writeJsonFile(resolveChangePath(projectRoot, changeId, 'pencil_refine_targets.json'), {
|
|
216
|
+
change_id: changeId,
|
|
217
|
+
generated_at: new Date().toISOString(),
|
|
218
|
+
repair_targets: repairTargets,
|
|
219
|
+
refine_scopes: refineScopes,
|
|
220
|
+
preserve_non_targets: preserveNonTargets,
|
|
221
|
+
design_system_pack: {
|
|
222
|
+
profile: designSystemPack.profile || null,
|
|
223
|
+
practice_sources: unique((designSystemPack.practice_sources || []).map((source) => source.id)),
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
await capturePreview(projectRoot, changeId).catch(() => null);
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|