@tiic-tech/openworkflow 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/adapters/codex/src/cleanCodexAdapter.d.ts +7 -0
- package/dist/adapters/codex/src/cleanCodexAdapter.js +99 -0
- package/dist/adapters/codex/src/cleanCodexAdapter.js.map +1 -0
- package/dist/adapters/codex/src/constants.d.ts +1 -0
- package/dist/adapters/codex/src/constants.js +2 -0
- package/dist/adapters/codex/src/constants.js.map +1 -0
- package/dist/adapters/codex/src/generateCommands.js +14 -9
- package/dist/adapters/codex/src/generateCommands.js.map +1 -1
- package/dist/adapters/codex/src/generateSkills.js +13 -0
- package/dist/adapters/codex/src/generateSkills.js.map +1 -1
- package/dist/adapters/codex/src/generatedFiles.js +1 -1
- package/dist/adapters/codex/src/manifest.js +11 -2
- package/dist/adapters/codex/src/manifest.js.map +1 -1
- package/dist/adapters/codex/src/templates.d.ts +0 -1
- package/dist/adapters/codex/src/templates.js +0 -1
- package/dist/adapters/codex/src/templates.js.map +1 -1
- package/dist/adapters/src/registry.d.ts +20 -0
- package/dist/adapters/src/registry.js +81 -0
- package/dist/adapters/src/registry.js.map +1 -0
- package/dist/cli/src/commands/brief.d.ts +46 -0
- package/dist/cli/src/commands/brief.js +294 -0
- package/dist/cli/src/commands/brief.js.map +1 -0
- package/dist/cli/src/commands/check.d.ts +42 -0
- package/dist/cli/src/commands/check.js +326 -0
- package/dist/cli/src/commands/check.js.map +1 -0
- package/dist/cli/src/commands/clean.d.ts +1 -0
- package/dist/cli/src/commands/clean.js +98 -0
- package/dist/cli/src/commands/clean.js.map +1 -0
- package/dist/cli/src/commands/context.d.ts +1 -0
- package/dist/cli/src/commands/context.js +471 -0
- package/dist/cli/src/commands/context.js.map +1 -0
- package/dist/cli/src/commands/doctor.js +122 -12
- package/dist/cli/src/commands/doctor.js.map +1 -1
- package/dist/cli/src/commands/draft.d.ts +1 -0
- package/dist/cli/src/commands/draft.js +175 -0
- package/dist/cli/src/commands/draft.js.map +1 -0
- package/dist/cli/src/commands/gitAutomation.d.ts +1 -0
- package/dist/cli/src/commands/gitAutomation.js +378 -0
- package/dist/cli/src/commands/gitAutomation.js.map +1 -0
- package/dist/cli/src/commands/handoff.d.ts +22 -0
- package/dist/cli/src/commands/handoff.js +122 -0
- package/dist/cli/src/commands/handoff.js.map +1 -0
- package/dist/cli/src/commands/init.js +52 -1
- package/dist/cli/src/commands/init.js.map +1 -1
- package/dist/cli/src/commands/inspect.d.ts +23 -0
- package/dist/cli/src/commands/inspect.js +157 -0
- package/dist/cli/src/commands/inspect.js.map +1 -0
- package/dist/cli/src/commands/register.d.ts +1 -0
- package/dist/cli/src/commands/register.js +251 -0
- package/dist/cli/src/commands/register.js.map +1 -0
- package/dist/cli/src/commands/resume.d.ts +59 -0
- package/dist/cli/src/commands/resume.js +280 -0
- package/dist/cli/src/commands/resume.js.map +1 -0
- package/dist/cli/src/commands/shared.js +6 -2
- package/dist/cli/src/commands/shared.js.map +1 -1
- package/dist/cli/src/commands/summaries.d.ts +1 -0
- package/dist/cli/src/commands/summaries.js +77 -0
- package/dist/cli/src/commands/summaries.js.map +1 -0
- package/dist/cli/src/commands/summarize.d.ts +1 -0
- package/dist/cli/src/commands/summarize.js +316 -0
- package/dist/cli/src/commands/summarize.js.map +1 -0
- package/dist/cli/src/commands/sync.js +135 -12
- package/dist/cli/src/commands/sync.js.map +1 -1
- package/dist/cli/src/commands/validate.js +25 -1
- package/dist/cli/src/commands/validate.js.map +1 -1
- package/dist/cli/src/dev/verifyAgentE2E.d.ts +2 -0
- package/dist/cli/src/dev/verifyAgentE2E.js +391 -0
- package/dist/cli/src/dev/verifyAgentE2E.js.map +1 -0
- package/dist/cli/src/dev/verifyCleanCommand.d.ts +2 -0
- package/dist/cli/src/dev/verifyCleanCommand.js +338 -0
- package/dist/cli/src/dev/verifyCleanCommand.js.map +1 -0
- package/dist/cli/src/dev/verifyRuntimeSurface.js +4940 -54
- package/dist/cli/src/dev/verifyRuntimeSurface.js.map +1 -1
- package/dist/cli/src/dev/verifyWorkflowE2E.js +477 -45
- package/dist/cli/src/dev/verifyWorkflowE2E.js.map +1 -1
- package/dist/cli/src/index.js +189 -5
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/report.d.ts +26 -0
- package/dist/cli/src/report.js +17 -0
- package/dist/cli/src/report.js.map +1 -0
- package/dist/core/src/artifacts/readiness.d.ts +7 -0
- package/dist/core/src/artifacts/readiness.js +240 -0
- package/dist/core/src/artifacts/readiness.js.map +1 -0
- package/dist/core/src/artifacts/registry.d.ts +9 -2
- package/dist/core/src/artifacts/registry.js +687 -60
- package/dist/core/src/artifacts/registry.js.map +1 -1
- package/dist/core/src/commands/registry.js +1425 -146
- package/dist/core/src/commands/registry.js.map +1 -1
- package/dist/core/src/contracts/index.d.ts +1 -1
- package/dist/core/src/fs/index.d.ts +24 -0
- package/dist/core/src/fs/index.js +48 -1
- package/dist/core/src/fs/index.js.map +1 -1
- package/dist/core/src/git/autonomousSimulator.d.ts +46 -0
- package/dist/core/src/git/autonomousSimulator.js +163 -0
- package/dist/core/src/git/autonomousSimulator.js.map +1 -0
- package/dist/core/src/git/branchIdentity.d.ts +19 -0
- package/dist/core/src/git/branchIdentity.js +75 -0
- package/dist/core/src/git/branchIdentity.js.map +1 -0
- package/dist/core/src/git/draftPrPilot.d.ts +47 -0
- package/dist/core/src/git/draftPrPilot.js +196 -0
- package/dist/core/src/git/draftPrPilot.js.map +1 -0
- package/dist/core/src/git/localEvidenceReader.d.ts +21 -0
- package/dist/core/src/git/localEvidenceReader.js +142 -0
- package/dist/core/src/git/localEvidenceReader.js.map +1 -0
- package/dist/core/src/git/localGitAutomation.d.ts +68 -0
- package/dist/core/src/git/localGitAutomation.js +470 -0
- package/dist/core/src/git/localGitAutomation.js.map +1 -0
- package/dist/core/src/git/mergeReadinessCheckpoint.d.ts +31 -0
- package/dist/core/src/git/mergeReadinessCheckpoint.js +110 -0
- package/dist/core/src/git/mergeReadinessCheckpoint.js.map +1 -0
- package/dist/core/src/git/prReadySummary.d.ts +16 -0
- package/dist/core/src/git/prReadySummary.js +144 -0
- package/dist/core/src/git/prReadySummary.js.map +1 -0
- package/dist/core/src/git/remoteReadonlyPlanner.d.ts +60 -0
- package/dist/core/src/git/remoteReadonlyPlanner.js +223 -0
- package/dist/core/src/git/remoteReadonlyPlanner.js.map +1 -0
- package/dist/core/src/onboarding/agentsGuide.d.ts +32 -0
- package/dist/core/src/onboarding/agentsGuide.js +164 -0
- package/dist/core/src/onboarding/agentsGuide.js.map +1 -0
- package/dist/core/src/validators/validateOpenWorkflow.js +1331 -15
- package/dist/core/src/validators/validateOpenWorkflow.js.map +1 -1
- package/dist/core/src/validators/validateRepositoryContracts.js +2327 -306
- package/dist/core/src/validators/validateRepositoryContracts.js.map +1 -1
- package/dist/core/src/workflow/cleanOpenWorkflow.d.ts +18 -0
- package/dist/core/src/workflow/cleanOpenWorkflow.js +124 -0
- package/dist/core/src/workflow/cleanOpenWorkflow.js.map +1 -0
- package/dist/core/src/workflow/doctorOpenWorkflow.d.ts +7 -0
- package/dist/core/src/workflow/doctorOpenWorkflow.js +26 -0
- package/dist/core/src/workflow/doctorOpenWorkflow.js.map +1 -0
- package/dist/core/src/workflow/initOpenWorkflow.d.ts +7 -0
- package/dist/core/src/workflow/initOpenWorkflow.js +96 -8
- package/dist/core/src/workflow/initOpenWorkflow.js.map +1 -1
- package/dist/core/src/workflow/planningQueueResume.d.ts +105 -0
- package/dist/core/src/workflow/planningQueueResume.js +596 -0
- package/dist/core/src/workflow/planningQueueResume.js.map +1 -0
- package/dist/core/src/workflow/readWorkflowConfig.d.ts +6 -0
- package/dist/core/src/workflow/readWorkflowConfig.js +28 -0
- package/dist/core/src/workflow/readWorkflowConfig.js.map +1 -0
- package/dist/core/src/workflow/summaryHealth.d.ts +60 -0
- package/dist/core/src/workflow/summaryHealth.js +713 -0
- package/dist/core/src/workflow/summaryHealth.js.map +1 -0
- package/dist/core/src/workflow/syncOpenWorkflow.d.ts +22 -0
- package/dist/core/src/workflow/syncOpenWorkflow.js +235 -0
- package/dist/core/src/workflow/syncOpenWorkflow.js.map +1 -0
- package/package.json +4 -2
- package/references/artifact-authoring-templates.md +14 -12
- package/references/artifact-instruction-envelope.md +133 -0
- package/references/coder-continuous-growth-loop.md +68 -0
- package/references/gh-operation-governance.md +114 -0
- package/references/git-automation-governance.md +324 -0
- package/references/git-version-control-governance.md +227 -0
- package/references/internal-coder-protocol.md +202 -0
- package/references/issue-governance.md +115 -0
- package/references/planning-artifact-contracts.md +595 -0
- package/references/planning-skill-runtime-exposure.md +159 -0
- package/references/proto-redesign-artifact-contracts.md +217 -0
- package/references/proto2html-artifact-contracts.md +113 -0
- package/references/skill-system-lifecycle.md +198 -0
- package/references/validation-trust-domains.md +286 -0
- package/references/workflow-blueprint-runtime-alignment.md +287 -0
- package/schemas/atom-tasks.schema.json +101 -0
- package/schemas/candidate-changes.schema.json +323 -0
- package/schemas/current-state.schema.json +113 -0
- package/schemas/html-prototype.schema.json +288 -0
- package/schemas/openworkflow-contract.schema.json +9 -1
- package/schemas/proto-prompt-pack.schema.json +1333 -0
- package/schemas/prototype-evidence.schema.json +684 -142
- package/schemas/selected-change.schema.json +104 -0
- package/schemas/validation-target.schema.json +187 -1
- package/schemas/validation.schema.json +187 -1
- package/schemas/vision-session.schema.json +151 -0
- package/skills/analyze-changes/SKILL.md +92 -0
- package/skills/analyze-changes/agents/openai.yaml +4 -0
- package/skills/analyze-changes/references/analysis-protocol.md +116 -0
- package/skills/build-proto-prompt/SKILL.md +125 -0
- package/skills/build-proto-prompt/references/output-boundary.md +54 -0
- package/skills/build-proto-prompt/references/prompt-pack-compiler-protocol.md +80 -0
- package/skills/build-prototype/SKILL.md +162 -38
- package/skills/build-prototype/agents/openai.yaml +2 -2
- package/skills/build-prototype/references/philosophy-engine.md +61 -0
- package/skills/build-prototype/references/strategic-prompt-pack-protocol.md +365 -0
- package/skills/build-prototype/references/vision2prompt/01_input_contract.md +84 -0
- package/skills/build-prototype/references/vision2prompt/02_vision_decomposition.md +108 -0
- package/skills/build-prototype/references/vision2prompt/03_strategy_hypothesis_generation.md +89 -0
- package/skills/build-prototype/references/vision2prompt/04_product_system_extraction.md +78 -0
- package/skills/build-prototype/references/vision2prompt/05_prototype_prompt_schema.md +189 -0
- package/skills/build-prototype/references/vision2prompt/06_output_templates.md +125 -0
- package/skills/build-prototype/references/vision2prompt/07_quality_rubric.md +171 -0
- package/skills/build-validation/SKILL.md +136 -54
- package/skills/build-validation/references/prototype-validation-target-rubric.md +35 -0
- package/skills/build-validation/references/return-to-vision-gate.md +32 -0
- package/skills/build-vision/SKILL.md +192 -0
- package/skills/build-vision/references/proto-readiness-rubric.md +48 -0
- package/skills/build-vision/references/vision-interview-protocol.md +48 -0
- package/skills/coder/SKILL.md +204 -0
- package/skills/decompose-to-changes/SKILL.md +176 -0
- package/skills/decompose-to-changes/agents/openai.yaml +4 -0
- package/skills/decompose-to-changes/references/decomposition-protocol.md +278 -0
- package/skills/prompt2proto/SKILL.md +157 -0
- package/skills/prompt2proto/agents/openai.yaml +4 -0
- package/skills/prompt2proto/references/00_role_philosophy_engine.md +96 -0
- package/skills/prompt2proto/references/01_input_contract.md +53 -0
- package/skills/prompt2proto/references/02_prompt_pack_readiness.md +50 -0
- package/skills/prompt2proto/references/03_visual_translation_workflow.md +64 -0
- package/skills/prompt2proto/references/04_output_contract.md +67 -0
- package/skills/prompt2proto/references/05_quality_rubric.md +46 -0
- package/skills/proto2html/SKILL.md +136 -0
- package/skills/proto2html/agents/openai.yaml +4 -0
- package/skills/proto2html/references/proto2html-protocol.md +115 -0
- package/skills/run-team/SKILL.md +4 -0
- package/skills/select-change/SKILL.md +200 -0
- package/skills/select-change/agents/openai.yaml +4 -0
- package/skills/select-change/references/selection-protocol.md +281 -0
- package/skills/tune-prototype/SKILL.md +121 -0
- package/skills/tune-prototype/agents/openai.yaml +4 -0
- package/skills/tune-prototype/references/refined-prompt-pack-protocol.md +161 -0
|
@@ -6,6 +6,7 @@ import { parseYaml } from "../contracts/yaml.js";
|
|
|
6
6
|
import { isNotFound, readTextFile } from "../fs/index.js";
|
|
7
7
|
const REQUIRED_OPENWORKFLOW_FILES = [
|
|
8
8
|
".openworkflow/config.yaml",
|
|
9
|
+
".openworkflow/CURRENT_STATE.yaml",
|
|
9
10
|
".openworkflow/workflow/WORKFLOW_INDEX.yaml",
|
|
10
11
|
".openworkflow/audit/COMMAND_AUDIT_INDEX.yaml",
|
|
11
12
|
".openworkflow/audit/CONTEXT_PACKETS.yaml",
|
|
@@ -34,11 +35,50 @@ export async function validateOpenWorkflow(root) {
|
|
|
34
35
|
validateContractGraph(root, file, data, errors);
|
|
35
36
|
validateArtifactContracts(root, file, data, errors);
|
|
36
37
|
validateDisclosureLevels(root, file, data, errors);
|
|
38
|
+
validateConfig(root, file, data, errors);
|
|
39
|
+
validateCurrentState(root, file, data, errors);
|
|
37
40
|
validateActivePointer(root, file, data, errors);
|
|
38
41
|
validateDiscoveryArtifact(root, file, data, errors);
|
|
39
42
|
}
|
|
40
43
|
return { ok: errors.length === 0, errors };
|
|
41
44
|
}
|
|
45
|
+
function validateConfig(root, file, data, errors) {
|
|
46
|
+
if (!file.endsWith("config.yaml") || !isRecord(data)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const label = relative(root, file);
|
|
50
|
+
if (!nonEmptyString(data.project_slug) || data.project_slug === "project") {
|
|
51
|
+
errors.push(`${label} project_slug must be a useful non-empty slug`);
|
|
52
|
+
}
|
|
53
|
+
if (!nonEmptyString(data.project_title) || data.project_title === ".") {
|
|
54
|
+
errors.push(`${label} project_title must be a useful non-empty title`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function validateCurrentState(root, file, data, errors) {
|
|
58
|
+
if (!file.endsWith("CURRENT_STATE.yaml") || !isRecord(data)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const label = relative(root, file);
|
|
62
|
+
for (const key of ["active_stage", "next_command", "blocked_by", "read_this_first", "last_decision"]) {
|
|
63
|
+
if (!(key in data)) {
|
|
64
|
+
errors.push(`${label} missing current state key ${key}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!nonEmptyString(data.active_stage)) {
|
|
68
|
+
errors.push(`${label} active_stage must be a non-empty string`);
|
|
69
|
+
}
|
|
70
|
+
if (typeof data.next_command !== "string" && data.next_command !== null) {
|
|
71
|
+
errors.push(`${label} next_command must be a string or null`);
|
|
72
|
+
}
|
|
73
|
+
for (const key of ["blocked_by", "read_this_first"]) {
|
|
74
|
+
if (!Array.isArray(data[key])) {
|
|
75
|
+
errors.push(`${label} ${key} must be an array`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!isRecord(data.last_decision)) {
|
|
79
|
+
errors.push(`${label} last_decision must be a mapping`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
42
82
|
function validateArtifactContracts(root, file, data, errors) {
|
|
43
83
|
if (!file.endsWith("ARTIFACT_CONTRACTS.yaml") || !isRecord(data)) {
|
|
44
84
|
return;
|
|
@@ -54,6 +94,9 @@ function validateArtifactContracts(root, file, data, errors) {
|
|
|
54
94
|
"prototype_evidence",
|
|
55
95
|
"decision_record",
|
|
56
96
|
"product_design",
|
|
97
|
+
"production_spec",
|
|
98
|
+
"production_change",
|
|
99
|
+
"team_runtime",
|
|
57
100
|
]);
|
|
58
101
|
for (const artifact of artifacts) {
|
|
59
102
|
if (!isRecord(artifact)) {
|
|
@@ -114,6 +157,19 @@ function validateArtifactContractMetadata(root, file, artifact, errors) {
|
|
|
114
157
|
else {
|
|
115
158
|
errors.push(`${label} ${String(artifact.artifact_type)} active_pointer must be a mapping`);
|
|
116
159
|
}
|
|
160
|
+
const summaryPolicy = artifact.summary_policy;
|
|
161
|
+
if (summaryPolicy !== null && summaryPolicy !== undefined) {
|
|
162
|
+
if (!isRecord(summaryPolicy)) {
|
|
163
|
+
errors.push(`${label} ${String(artifact.artifact_type)} summary_policy must be null or a mapping`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
for (const key of ["strategy", "path", "load_before_full", "refresh_when"]) {
|
|
167
|
+
if (!(key in summaryPolicy)) {
|
|
168
|
+
errors.push(`${label} ${String(artifact.artifact_type)} summary_policy missing ${key}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
117
173
|
}
|
|
118
174
|
function validateActivePointer(root, file, data, errors) {
|
|
119
175
|
if (!isRecord(data)) {
|
|
@@ -126,6 +182,9 @@ function validateActivePointer(root, file, data, errors) {
|
|
|
126
182
|
pointerRule("PROTOTYPE_INDEX.yaml", "current_prototype", "prototypes", "prototype_id", "path"),
|
|
127
183
|
pointerRule("DECISION_INDEX.yaml", "current_decision", "decisions", "decision_id", "path"),
|
|
128
184
|
pointerRule("DESIGN_INDEX.yaml", "current_design", "designs", "design_id", "path"),
|
|
185
|
+
pointerRule("SPEC_INDEX.yaml", "current_spec", "specs", "spec_id", "path"),
|
|
186
|
+
pointerRule("CHANGE_INDEX.yaml", "current_change", "changes", "change_id", "path"),
|
|
187
|
+
pointerRule("RUNTIME_INDEX.yaml", "current_run", "runs", "run_id", "path"),
|
|
129
188
|
];
|
|
130
189
|
const rule = rules.find((item) => file.endsWith(item.fileName));
|
|
131
190
|
if (!rule) {
|
|
@@ -207,6 +266,9 @@ function validateDisclosureLevels(root, file, data, errors) {
|
|
|
207
266
|
}
|
|
208
267
|
}
|
|
209
268
|
function validateDiscoveryArtifact(root, file, data, errors) {
|
|
269
|
+
if (file.endsWith("SUMMARY.yaml")) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
210
272
|
if (!isRecord(data) || typeof data.artifact_type !== "string") {
|
|
211
273
|
return;
|
|
212
274
|
}
|
|
@@ -233,31 +295,49 @@ function validateDiscoveryArtifact(root, file, data, errors) {
|
|
|
233
295
|
if (data.artifact_type === "product_design") {
|
|
234
296
|
validateProductDesign(label, data, errors);
|
|
235
297
|
}
|
|
298
|
+
if (data.artifact_type === "production_spec") {
|
|
299
|
+
validateProductionSpec(label, data, errors);
|
|
300
|
+
}
|
|
301
|
+
if (data.artifact_type === "production_change") {
|
|
302
|
+
validateProductionChange(label, data, errors);
|
|
303
|
+
}
|
|
304
|
+
if (data.artifact_type === "team_runtime") {
|
|
305
|
+
validateTeamRuntime(label, data, errors);
|
|
306
|
+
}
|
|
236
307
|
}
|
|
237
308
|
function artifactRequiredKeys(artifactType) {
|
|
238
309
|
if (artifactType === "vision_session") {
|
|
239
310
|
return ["current_question", "stable_answers", "unresolved_questions", "vision_delta", "handoff"];
|
|
240
311
|
}
|
|
241
312
|
if (artifactType === "validation_target") {
|
|
242
|
-
return [
|
|
313
|
+
return [
|
|
314
|
+
"trigger",
|
|
315
|
+
"core_question",
|
|
316
|
+
"central_uncertainty",
|
|
317
|
+
"hypothesis",
|
|
318
|
+
"target_behavior",
|
|
319
|
+
"feature_classification",
|
|
320
|
+
"critical_assumptions",
|
|
321
|
+
"prototype_scope",
|
|
322
|
+
"prototype_experiment",
|
|
323
|
+
"observable_signals",
|
|
324
|
+
"acceptance",
|
|
325
|
+
"decision_rules",
|
|
326
|
+
"decision_options",
|
|
327
|
+
"vision_gaps",
|
|
328
|
+
"agent_readiness_gate",
|
|
329
|
+
];
|
|
243
330
|
}
|
|
244
331
|
if (artifactType === "prototype_evidence") {
|
|
245
332
|
return [
|
|
246
333
|
"validation_target",
|
|
247
334
|
"core_question",
|
|
248
335
|
"prototype_mode",
|
|
249
|
-
"
|
|
250
|
-
"
|
|
251
|
-
"
|
|
252
|
-
"
|
|
253
|
-
"
|
|
254
|
-
"run",
|
|
255
|
-
"implementation_evidence",
|
|
256
|
-
"observations",
|
|
257
|
-
"evidence",
|
|
258
|
-
"verification",
|
|
259
|
-
"self_critique",
|
|
260
|
-
"known_limits",
|
|
336
|
+
"prompt_pack_type",
|
|
337
|
+
"validation_input",
|
|
338
|
+
"source",
|
|
339
|
+
"negative_constraints",
|
|
340
|
+
"review_plan",
|
|
261
341
|
"result",
|
|
262
342
|
"handoff",
|
|
263
343
|
];
|
|
@@ -290,9 +370,61 @@ function artifactRequiredKeys(artifactType) {
|
|
|
290
370
|
"spec_readiness",
|
|
291
371
|
];
|
|
292
372
|
}
|
|
373
|
+
if (artifactType === "production_spec") {
|
|
374
|
+
return [
|
|
375
|
+
"source_design",
|
|
376
|
+
"goal",
|
|
377
|
+
"scope",
|
|
378
|
+
"requirements",
|
|
379
|
+
"interfaces",
|
|
380
|
+
"acceptance",
|
|
381
|
+
"verification",
|
|
382
|
+
"risks",
|
|
383
|
+
"change_readiness",
|
|
384
|
+
];
|
|
385
|
+
}
|
|
386
|
+
if (artifactType === "production_change") {
|
|
387
|
+
return [
|
|
388
|
+
"source_spec",
|
|
389
|
+
"problem",
|
|
390
|
+
"goals",
|
|
391
|
+
"non_goals",
|
|
392
|
+
"affected_paths",
|
|
393
|
+
"acceptance",
|
|
394
|
+
"validation",
|
|
395
|
+
"work_items",
|
|
396
|
+
"risks",
|
|
397
|
+
"runtime_readiness",
|
|
398
|
+
];
|
|
399
|
+
}
|
|
400
|
+
if (artifactType === "team_runtime") {
|
|
401
|
+
return [
|
|
402
|
+
"source_change",
|
|
403
|
+
"active_work_item",
|
|
404
|
+
"execution_mode",
|
|
405
|
+
"work_queue",
|
|
406
|
+
"agents",
|
|
407
|
+
"verification",
|
|
408
|
+
"issues",
|
|
409
|
+
"checkpoints",
|
|
410
|
+
"handoff",
|
|
411
|
+
];
|
|
412
|
+
}
|
|
293
413
|
return null;
|
|
294
414
|
}
|
|
295
415
|
function validateValidationTarget(label, data, errors) {
|
|
416
|
+
const trigger = data.trigger;
|
|
417
|
+
if (isRecord(trigger)) {
|
|
418
|
+
for (const key of ["mode", "requested_command", "reason"]) {
|
|
419
|
+
if (!(key in trigger)) {
|
|
420
|
+
errors.push(`${label} trigger missing ${key}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const mode = String(trigger.mode ?? "");
|
|
424
|
+
if (mode && !["user_explicit", "agent_auto"].includes(mode)) {
|
|
425
|
+
errors.push(`${label} trigger.mode has invalid value ${mode}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
296
428
|
const featureClassification = data.feature_classification;
|
|
297
429
|
if (isRecord(featureClassification)) {
|
|
298
430
|
for (const key of ["existential", "supporting", "later", "out_of_scope"]) {
|
|
@@ -309,10 +441,42 @@ function validateValidationTarget(label, data, errors) {
|
|
|
309
441
|
}
|
|
310
442
|
}
|
|
311
443
|
}
|
|
444
|
+
const prototypeExperiment = data.prototype_experiment;
|
|
445
|
+
if (isRecord(prototypeExperiment)) {
|
|
446
|
+
for (const key of ["scenario", "must_show", "must_not_show"]) {
|
|
447
|
+
if (!(key in prototypeExperiment)) {
|
|
448
|
+
errors.push(`${label} prototype_experiment missing ${key}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
validateSignalSet(label, "observable_signals", data.observable_signals, ["pass", "fail", "ambiguous"], errors);
|
|
453
|
+
validateSignalSet(label, "decision_rules", data.decision_rules, ["continue", "revise", "pivot", "stop", "needs_more_evidence"], errors);
|
|
454
|
+
const agentReadinessGate = data.agent_readiness_gate;
|
|
455
|
+
if (isRecord(agentReadinessGate)) {
|
|
456
|
+
for (const key of ["status", "blockers", "warnings", "write_authority"]) {
|
|
457
|
+
if (!(key in agentReadinessGate)) {
|
|
458
|
+
errors.push(`${label} agent_readiness_gate missing ${key}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const status = String(agentReadinessGate.status ?? "");
|
|
462
|
+
if (status && !["missing_validation", "thin_validation", "stale_validation", "ready_for_proto", "return_to_vision"].includes(status)) {
|
|
463
|
+
errors.push(`${label} agent_readiness_gate.status has invalid value ${status}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function validateSignalSet(label, field, value, keys, errors) {
|
|
468
|
+
if (!isRecord(value)) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
for (const key of keys) {
|
|
472
|
+
if (!(key in value)) {
|
|
473
|
+
errors.push(`${label} ${field} missing ${key}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
312
476
|
}
|
|
313
477
|
function validatePrototypeEvidence(root, label, data, errors) {
|
|
314
478
|
if (typeof data.prototype_mode === "string" &&
|
|
315
|
-
!["visual", "interaction", "technical_feasibility", "3d_material", "workflow", "data_logic"].includes(data.prototype_mode)) {
|
|
479
|
+
!["image_prompt_pack", "visual", "interaction", "technical_feasibility", "3d_material", "workflow", "data_logic"].includes(data.prototype_mode)) {
|
|
316
480
|
errors.push(`${label} has invalid prototype_mode ${data.prototype_mode}`);
|
|
317
481
|
}
|
|
318
482
|
for (const key of ["reference_analysis", "concept_evidence", "implementation_evidence", "known_limits"]) {
|
|
@@ -323,7 +487,9 @@ function validatePrototypeEvidence(root, label, data, errors) {
|
|
|
323
487
|
if ("visual_direction" in data && !isRecord(data.visual_direction)) {
|
|
324
488
|
errors.push(`${label} visual_direction must be a mapping`);
|
|
325
489
|
}
|
|
326
|
-
|
|
490
|
+
if ("visual_concept_policy" in data) {
|
|
491
|
+
validateVisualConceptPolicy(label, data, errors);
|
|
492
|
+
}
|
|
327
493
|
if ("verification" in data && !isRecord(data.verification)) {
|
|
328
494
|
errors.push(`${label} verification must be a mapping`);
|
|
329
495
|
}
|
|
@@ -343,10 +509,1114 @@ function validatePrototypeEvidence(root, label, data, errors) {
|
|
|
343
509
|
validateLocalRef(root, label, "prototype_artifact.path", prototypeArtifact.path, errors);
|
|
344
510
|
}
|
|
345
511
|
validateEvidenceRefs(root, label, data, errors);
|
|
512
|
+
if ("validation_input" in data && !isRecord(data.validation_input)) {
|
|
513
|
+
errors.push(`${label} validation_input must be a mapping`);
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
validatePrototypeValidationInput(label, data.validation_input, errors);
|
|
517
|
+
}
|
|
518
|
+
if ("source" in data && !isRecord(data.source)) {
|
|
519
|
+
errors.push(`${label} source must be a mapping`);
|
|
520
|
+
}
|
|
521
|
+
if ("negative_constraints" in data && !Array.isArray(data.negative_constraints)) {
|
|
522
|
+
errors.push(`${label} negative_constraints must be an array`);
|
|
523
|
+
}
|
|
524
|
+
if ("review_plan" in data && !isRecord(data.review_plan)) {
|
|
525
|
+
errors.push(`${label} review_plan must be a mapping`);
|
|
526
|
+
}
|
|
527
|
+
if ("directions" in data && !Array.isArray(data.directions)) {
|
|
528
|
+
errors.push(`${label} directions must be an array`);
|
|
529
|
+
}
|
|
530
|
+
if ("screen_manifest" in data && !Array.isArray(data.screen_manifest)) {
|
|
531
|
+
errors.push(`${label} screen_manifest must be an array`);
|
|
532
|
+
}
|
|
533
|
+
if ("screen_prompts" in data && !Array.isArray(data.screen_prompts)) {
|
|
534
|
+
errors.push(`${label} screen_prompts must be an array`);
|
|
535
|
+
}
|
|
536
|
+
if ("prompt_pack_type" in data && !["strategic_proto_prompt_pack", "refined_proto_prompt_pack", "proto_review_evidence"].includes(String(data.prompt_pack_type))) {
|
|
537
|
+
errors.push(`${label} has invalid prompt_pack_type ${String(data.prompt_pack_type)}`);
|
|
538
|
+
}
|
|
539
|
+
if (data.prompt_pack_type === "strategic_proto_prompt_pack") {
|
|
540
|
+
validateStrategicPrototypePromptPack(label, data, errors);
|
|
541
|
+
}
|
|
542
|
+
if (data.prompt_pack_type === "refined_proto_prompt_pack") {
|
|
543
|
+
validateRefinedPrototypePromptPack(label, data, errors);
|
|
544
|
+
}
|
|
346
545
|
if (typeof data.result === "string" && !["pass", "fail", "unclear", "not_reviewed"].includes(data.result)) {
|
|
347
546
|
errors.push(`${label} has invalid result ${data.result}`);
|
|
348
547
|
}
|
|
349
548
|
}
|
|
549
|
+
function validateRefinedPrototypePromptPack(label, data, errors) {
|
|
550
|
+
validateRequiredObjectFields(label, "tune_input", data.tune_input, ["baseline_source_type", "baseline_refs", "tune_request", "regeneration_scope"], errors);
|
|
551
|
+
validateRefinedBaselineResolution(label, data.baseline_resolution, errors);
|
|
552
|
+
validateRefinedCarryForward(label, data.carry_forward, errors);
|
|
553
|
+
validateRefinedBaselineAudit(label, data.baseline_audit, errors);
|
|
554
|
+
validateRequiredObjectFields(label, "product_system", data.product_system, REFINED_PRODUCT_SYSTEM_FIELDS, errors);
|
|
555
|
+
validateRefinedDeltaRules(label, data.delta_rules, data.tune_input, errors);
|
|
556
|
+
validateRefinedScreenDeltaMatrix(label, data.screen_delta_matrix, errors);
|
|
557
|
+
const manifestIds = validateRefinedScreenManifest(label, data.screen_manifest, errors);
|
|
558
|
+
validateRefinedScreenPrompts(label, data.screen_prompts, manifestIds, errors);
|
|
559
|
+
if (!Array.isArray(data.generation_order) || data.generation_order.length === 0) {
|
|
560
|
+
errors.push(`${label} generation_order must list target screen ids`);
|
|
561
|
+
}
|
|
562
|
+
if (!Array.isArray(data.acceptance_checklist) || data.acceptance_checklist.length === 0) {
|
|
563
|
+
errors.push(`${label} acceptance_checklist must be non-empty`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const REFINED_BASELINE_AUDIT_FIELDS = [
|
|
567
|
+
"source_screen_id",
|
|
568
|
+
"screen_name",
|
|
569
|
+
"journey_stage",
|
|
570
|
+
"user_goal",
|
|
571
|
+
"system_state",
|
|
572
|
+
"components",
|
|
573
|
+
"must_preserve",
|
|
574
|
+
];
|
|
575
|
+
const REFINED_BASELINE_RESOLUTION_FIELDS = [
|
|
576
|
+
"latest_approved_baseline_group_id",
|
|
577
|
+
"latest_approved_baseline_ref",
|
|
578
|
+
"baseline_lineage",
|
|
579
|
+
"resolution_rule",
|
|
580
|
+
"stale_source_guard",
|
|
581
|
+
];
|
|
582
|
+
const REFINED_CARRY_FORWARD_FIELDS = [
|
|
583
|
+
"locked_screens",
|
|
584
|
+
"locked_elements",
|
|
585
|
+
"preserved_improvements",
|
|
586
|
+
"explicit_unlocks",
|
|
587
|
+
"cumulative_drift_guard",
|
|
588
|
+
];
|
|
589
|
+
const REFINED_PRODUCT_SYSTEM_FIELDS = [
|
|
590
|
+
"product_thesis",
|
|
591
|
+
"primary_loop",
|
|
592
|
+
"component_vocabulary",
|
|
593
|
+
"copywriting_style",
|
|
594
|
+
"trust_and_boundary_system",
|
|
595
|
+
"stable_constants",
|
|
596
|
+
"adaptable_variables",
|
|
597
|
+
];
|
|
598
|
+
const REFINED_DELTA_RULE_KEYS = ["must_inherit", "must_add", "must_remove", "flexible_change"];
|
|
599
|
+
const REFINED_SCREEN_DELTA_FIELDS = [
|
|
600
|
+
"target_screen_id",
|
|
601
|
+
"source_screen_ids",
|
|
602
|
+
"preserve",
|
|
603
|
+
"add",
|
|
604
|
+
"remove",
|
|
605
|
+
"transform",
|
|
606
|
+
"flexible",
|
|
607
|
+
"acceptance_criteria",
|
|
608
|
+
];
|
|
609
|
+
const REFINED_SCREEN_MANIFEST_FIELDS = ["target_screen_id", "source_screen_ids", "screen_name", "generation_scope"];
|
|
610
|
+
const REFINED_SCREEN_PROMPT_FIELDS = ["prompt_id", "target_screen_id", "source_screen_ids", "screen_name", "prompt", "negative_prompt", "acceptance_criteria"];
|
|
611
|
+
function validateRefinedBaselineAudit(label, value, errors) {
|
|
612
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
613
|
+
errors.push(`${label} baseline_audit must contain source screen audits`);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
value.forEach((item, index) => {
|
|
617
|
+
validateRequiredObjectFields(label, `baseline_audit[${index}]`, item, REFINED_BASELINE_AUDIT_FIELDS, errors);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
function validateRefinedBaselineResolution(label, value, errors) {
|
|
621
|
+
validateRequiredObjectFields(label, "baseline_resolution", value, REFINED_BASELINE_RESOLUTION_FIELDS, errors);
|
|
622
|
+
if (!isRecord(value)) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (!Array.isArray(value.baseline_lineage)) {
|
|
626
|
+
errors.push(`${label} baseline_resolution.baseline_lineage must be an array`);
|
|
627
|
+
}
|
|
628
|
+
if (String(value.stale_source_guard ?? "").trim().length === 0) {
|
|
629
|
+
errors.push(`${label} baseline_resolution.stale_source_guard must forbid stale source-screen fallback`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function validateRefinedCarryForward(label, value, errors) {
|
|
633
|
+
if (!isRecord(value)) {
|
|
634
|
+
errors.push(`${label} carry_forward must be a mapping`);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
for (const key of REFINED_CARRY_FORWARD_FIELDS) {
|
|
638
|
+
if (!Object.hasOwn(value, key)) {
|
|
639
|
+
errors.push(`${label} carry_forward.${key} must be present`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
for (const key of ["locked_screens", "locked_elements", "preserved_improvements", "explicit_unlocks"]) {
|
|
643
|
+
if (!Array.isArray(value[key])) {
|
|
644
|
+
errors.push(`${label} carry_forward.${key} must be an array`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (String(value.cumulative_drift_guard ?? "").trim().length === 0) {
|
|
648
|
+
errors.push(`${label} carry_forward.cumulative_drift_guard must forbid cumulative tune drift`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function validateRefinedDeltaRules(label, value, tuneInput, errors) {
|
|
652
|
+
if (!isRecord(value)) {
|
|
653
|
+
errors.push(`${label} delta_rules must be a mapping`);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
for (const key of REFINED_DELTA_RULE_KEYS) {
|
|
657
|
+
if (!Array.isArray(value[key])) {
|
|
658
|
+
errors.push(`${label} delta_rules.${key} must be an array`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (Array.isArray(value.must_inherit) && value.must_inherit.length === 0) {
|
|
662
|
+
errors.push(`${label} delta_rules.must_inherit must preserve baseline product-system constants`);
|
|
663
|
+
}
|
|
664
|
+
const tuneRequest = isRecord(tuneInput) ? String(tuneInput.tune_request ?? "").toLowerCase() : "";
|
|
665
|
+
if (/\b(remove|delete|drop|eliminate|hide)\b/.test(tuneRequest) && Array.isArray(value.must_remove) && value.must_remove.length === 0) {
|
|
666
|
+
errors.push(`${label} delta_rules.must_remove must name requested removals from tune_input.tune_request`);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function validateRefinedScreenDeltaMatrix(label, value, errors) {
|
|
670
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
671
|
+
errors.push(`${label} screen_delta_matrix must contain target screen delta rows`);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
value.forEach((item, index) => {
|
|
675
|
+
validateRequiredObjectFields(label, `screen_delta_matrix[${index}]`, item, REFINED_SCREEN_DELTA_FIELDS, errors);
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function validateRefinedScreenManifest(label, value, errors) {
|
|
679
|
+
const ids = new Set();
|
|
680
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
681
|
+
errors.push(`${label} screen_manifest must contain target screens`);
|
|
682
|
+
return ids;
|
|
683
|
+
}
|
|
684
|
+
value.forEach((item, index) => {
|
|
685
|
+
validateRequiredObjectFields(label, `screen_manifest[${index}]`, item, REFINED_SCREEN_MANIFEST_FIELDS, errors);
|
|
686
|
+
if (isRecord(item) && nonEmptyString(item.target_screen_id)) {
|
|
687
|
+
ids.add(String(item.target_screen_id));
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
return ids;
|
|
691
|
+
}
|
|
692
|
+
function validateRefinedScreenPrompts(label, value, manifestIds, errors) {
|
|
693
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
694
|
+
errors.push(`${label} screen_prompts must contain screen-bound refined prompts`);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
value.forEach((item, index) => {
|
|
698
|
+
validateRequiredObjectFields(label, `screen_prompts[${index}]`, item, REFINED_SCREEN_PROMPT_FIELDS, errors);
|
|
699
|
+
if (isRecord(item) && nonEmptyString(item.target_screen_id) && manifestIds.size > 0 && !manifestIds.has(String(item.target_screen_id))) {
|
|
700
|
+
errors.push(`${label} screen_prompts[${index}].target_screen_id must exist in screen_manifest`);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
const STRATEGIC_NORMALIZED_FIELDS = [
|
|
705
|
+
"product_domain",
|
|
706
|
+
"primary_user",
|
|
707
|
+
"usage_context",
|
|
708
|
+
"current_alternative",
|
|
709
|
+
"core_pain",
|
|
710
|
+
"desired_behavior_change",
|
|
711
|
+
"strongest_success_signal",
|
|
712
|
+
"core_differentiator",
|
|
713
|
+
"emotional_value",
|
|
714
|
+
"functional_value",
|
|
715
|
+
"trust_requirements",
|
|
716
|
+
"privacy_requirements",
|
|
717
|
+
"non_goals",
|
|
718
|
+
"future_opportunities",
|
|
719
|
+
"validation_target",
|
|
720
|
+
];
|
|
721
|
+
const STRATEGIC_CORE_FIELDS = [
|
|
722
|
+
"target_user",
|
|
723
|
+
"behavior_change",
|
|
724
|
+
"mechanism",
|
|
725
|
+
"differentiator",
|
|
726
|
+
"boundary_conditions",
|
|
727
|
+
"central_uncertainty",
|
|
728
|
+
];
|
|
729
|
+
const PRODUCT_EXPERIENCE_MODEL_FIELDS = [
|
|
730
|
+
"product_archetype",
|
|
731
|
+
"primary_canvas",
|
|
732
|
+
"information_architecture",
|
|
733
|
+
"domain_object_model",
|
|
734
|
+
"primary_task_loop",
|
|
735
|
+
"interaction_state_model",
|
|
736
|
+
"data_realism_requirements",
|
|
737
|
+
"visual_language",
|
|
738
|
+
"anti_generic_constraints",
|
|
739
|
+
];
|
|
740
|
+
const PROTOTYPE_SYSTEM_CONTRACT_FIELDS = [
|
|
741
|
+
"stable_app_shell",
|
|
742
|
+
"navigation_taxonomy",
|
|
743
|
+
"data_vocabulary",
|
|
744
|
+
"domain_object_anatomy",
|
|
745
|
+
"object_detail_anatomy",
|
|
746
|
+
"action_bar_contract",
|
|
747
|
+
"audit_trust_pattern",
|
|
748
|
+
"copy_tone",
|
|
749
|
+
"allowed_screen_deltas",
|
|
750
|
+
];
|
|
751
|
+
const PROTOTYPE_REALITY_GATE_DIMENSIONS = [
|
|
752
|
+
"product_category_fit",
|
|
753
|
+
"primary_canvas_fit",
|
|
754
|
+
"domain_object_realism",
|
|
755
|
+
"task_loop_completeness",
|
|
756
|
+
"interaction_state_coverage",
|
|
757
|
+
"data_realism",
|
|
758
|
+
"anti_generic_constraints",
|
|
759
|
+
];
|
|
760
|
+
const PROMPT_PACK_INTEGRITY_GATE_DIMENSIONS = [
|
|
761
|
+
"direction_count_matches",
|
|
762
|
+
"prompt_text_refs_resolve",
|
|
763
|
+
"generated_image_refs_resolve",
|
|
764
|
+
];
|
|
765
|
+
const STRATEGIC_PROTOTYPE_BRIEF_FIELDS = [
|
|
766
|
+
"product_name",
|
|
767
|
+
"positioning",
|
|
768
|
+
"target_user",
|
|
769
|
+
"current_alternative",
|
|
770
|
+
"core_idea",
|
|
771
|
+
"primary_loop",
|
|
772
|
+
"trust_boundaries",
|
|
773
|
+
"non_goals",
|
|
774
|
+
"desired_feeling",
|
|
775
|
+
];
|
|
776
|
+
const STRATEGIC_SCREEN_MANIFEST_FIELDS = [
|
|
777
|
+
"target_screen_id",
|
|
778
|
+
"screen_name",
|
|
779
|
+
"journey_stage",
|
|
780
|
+
"user_goal",
|
|
781
|
+
"system_state",
|
|
782
|
+
"required_components",
|
|
783
|
+
"required_data_fields",
|
|
784
|
+
"primary_actions",
|
|
785
|
+
"trust_controls",
|
|
786
|
+
"example_copy",
|
|
787
|
+
"acceptance_criteria",
|
|
788
|
+
];
|
|
789
|
+
const STRATEGIC_GLOBAL_DESIGN_SYSTEM_PROMPT_FIELDS = [
|
|
790
|
+
"visual_language",
|
|
791
|
+
"layout_system",
|
|
792
|
+
"component_vocabulary",
|
|
793
|
+
"information_density",
|
|
794
|
+
"copy_tone",
|
|
795
|
+
"responsive_canvas_rules",
|
|
796
|
+
"negative_visual_patterns",
|
|
797
|
+
];
|
|
798
|
+
const STRATEGIC_SCREEN_PROMPT_FIELDS = [
|
|
799
|
+
"prompt_id",
|
|
800
|
+
"target_screen_id",
|
|
801
|
+
"screen_name",
|
|
802
|
+
"image_role",
|
|
803
|
+
"negative_prompt",
|
|
804
|
+
"example_copy",
|
|
805
|
+
"acceptance_criteria",
|
|
806
|
+
];
|
|
807
|
+
const STRATEGIC_QUALITY_RUBRIC_FIELDS = [
|
|
808
|
+
"prompt_executability",
|
|
809
|
+
"strategic_distinctness",
|
|
810
|
+
"product_specificity",
|
|
811
|
+
"state_coverage",
|
|
812
|
+
"trust_boundary_coverage",
|
|
813
|
+
];
|
|
814
|
+
const STRATEGIC_DIRECTION_FIELDS = [
|
|
815
|
+
"direction_id",
|
|
816
|
+
"name",
|
|
817
|
+
"strategic_hypothesis",
|
|
818
|
+
"validates",
|
|
819
|
+
"main_risk",
|
|
820
|
+
"distinctness_rationale",
|
|
821
|
+
"prototype_prompt",
|
|
822
|
+
"screen_prompts",
|
|
823
|
+
"pm_judgment",
|
|
824
|
+
];
|
|
825
|
+
const STRATEGIC_DISTINCTNESS_SIGNALS = [
|
|
826
|
+
"product form",
|
|
827
|
+
"trigger",
|
|
828
|
+
"interaction model",
|
|
829
|
+
"emotional driver",
|
|
830
|
+
"retention mechanism",
|
|
831
|
+
"metric",
|
|
832
|
+
"main risk",
|
|
833
|
+
"risk",
|
|
834
|
+
"user behavior",
|
|
835
|
+
"workflow",
|
|
836
|
+
"trust",
|
|
837
|
+
"privacy",
|
|
838
|
+
];
|
|
839
|
+
const STRATEGIC_FINGERPRINT_DIMENSIONS = [
|
|
840
|
+
"product_form",
|
|
841
|
+
"trigger",
|
|
842
|
+
"interaction_model",
|
|
843
|
+
"emotional_driver",
|
|
844
|
+
"retention_mechanism",
|
|
845
|
+
"metric",
|
|
846
|
+
"main_risk",
|
|
847
|
+
"trust_model",
|
|
848
|
+
"privacy_model",
|
|
849
|
+
];
|
|
850
|
+
const PROTOTYPE_PROMPT_PARAGRAPH_DIMENSIONS = [
|
|
851
|
+
"product_context",
|
|
852
|
+
"target_user",
|
|
853
|
+
"journey",
|
|
854
|
+
"screens_or_components",
|
|
855
|
+
"interaction_or_system_response",
|
|
856
|
+
"concrete_content",
|
|
857
|
+
"trust_or_user_control",
|
|
858
|
+
"visual_direction",
|
|
859
|
+
"anti_goals",
|
|
860
|
+
"desired_user_feeling",
|
|
861
|
+
];
|
|
862
|
+
const SCREEN_PROMPT_PARAGRAPH_DIMENSIONS = [
|
|
863
|
+
"journey_or_screen_purpose",
|
|
864
|
+
"user_goal_or_system_state",
|
|
865
|
+
"components_or_domain_objects",
|
|
866
|
+
"actions_or_system_response",
|
|
867
|
+
"concrete_content",
|
|
868
|
+
"trust_or_user_control",
|
|
869
|
+
"negative_constraints",
|
|
870
|
+
"acceptance_or_user_feeling",
|
|
871
|
+
];
|
|
872
|
+
function validatePrototypeValidationInput(label, value, errors) {
|
|
873
|
+
if (!isRecord(value)) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const mode = String(value.mode ?? "");
|
|
877
|
+
if (mode && !["validation_present", "agent_auto_generated"].includes(mode)) {
|
|
878
|
+
errors.push(`${label} validation_input.mode has invalid value ${mode}`);
|
|
879
|
+
}
|
|
880
|
+
if (mode === "vision_only" || mode === "internally_derived") {
|
|
881
|
+
errors.push(`${label} validation_input.mode must reference durable validation, not ${mode}`);
|
|
882
|
+
}
|
|
883
|
+
if (!Array.isArray(value.refs)) {
|
|
884
|
+
errors.push(`${label} validation_input.refs must be an array`);
|
|
885
|
+
}
|
|
886
|
+
else if (mode && value.refs.length === 0) {
|
|
887
|
+
errors.push(`${label} validation_input.refs must include durable validation artifact refs`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
function validateStrategicPrototypePromptPack(label, data, errors) {
|
|
891
|
+
validatePreflightQualityGate(label, data.preflight_quality_gate, errors);
|
|
892
|
+
validateInternalPipeline(label, data.internal_pipeline, errors);
|
|
893
|
+
validateDirectionCountPolicy(label, data.direction_count_policy, errors);
|
|
894
|
+
validateRequiredObjectFields(label, "normalized_input", data.normalized_input, STRATEGIC_NORMALIZED_FIELDS, errors);
|
|
895
|
+
validateRequiredObjectFields(label, "strategic_core", data.strategic_core, STRATEGIC_CORE_FIELDS, errors);
|
|
896
|
+
validateProductExperienceModel(label, data.product_experience_model, data, errors);
|
|
897
|
+
validatePrototypeSystemContract(label, data.prototype_system_contract, data, errors);
|
|
898
|
+
validatePrototypeRealityGate(label, data.prototype_reality_gate, data, errors);
|
|
899
|
+
validatePromptPackIntegrityGate(label, data.prompt_pack_integrity_gate, data, errors);
|
|
900
|
+
validateScreenBoundExecutability(label, data, errors);
|
|
901
|
+
validatePromptParagraphQuality(label, data, errors);
|
|
902
|
+
validateStrategicDirections(label, data.directions, data.direction_count_policy, errors);
|
|
903
|
+
validateBuildRecommendation(label, data.build_recommendation, errors);
|
|
904
|
+
validatePromptTextManifest(label, data.prompt_text_manifest, errors);
|
|
905
|
+
validatePostValidate(label, data.post_validate, data.direction_count_policy, data.prompt_text_manifest, data.image_generation, data.directions, errors);
|
|
906
|
+
validateImageGeneration(label, data.image_generation, errors);
|
|
907
|
+
}
|
|
908
|
+
function validatePreflightQualityGate(label, value, errors) {
|
|
909
|
+
if (!isRecord(value)) {
|
|
910
|
+
errors.push(`${label} preflight_quality_gate must be a mapping`);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
for (const key of ["vision_status", "validation_status", "can_proceed", "blockers", "next_command_when_blocked"]) {
|
|
914
|
+
if (!(key in value)) {
|
|
915
|
+
errors.push(`${label} preflight_quality_gate missing ${key}`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
for (const key of ["vision_status", "validation_status"]) {
|
|
919
|
+
const status = String(value[key] ?? "");
|
|
920
|
+
if (status && !["missing", "thin", "ready"].includes(status)) {
|
|
921
|
+
errors.push(`${label} preflight_quality_gate.${key} has invalid value ${status}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (value.can_proceed !== true && value.next_command_when_blocked !== "/ow:vision") {
|
|
925
|
+
errors.push(`${label} preflight_quality_gate must route blocked prototype work back to /ow:vision`);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function validateInternalPipeline(label, value, errors) {
|
|
929
|
+
if (!isRecord(value)) {
|
|
930
|
+
errors.push(`${label} internal_pipeline must be a mapping`);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (value.orchestrator_command !== "/ow:proto" || value.user_visible_command !== "/ow:proto") {
|
|
934
|
+
errors.push(`${label} internal_pipeline must keep /ow:proto as the user-visible orchestrator`);
|
|
935
|
+
}
|
|
936
|
+
const stages = value.stages;
|
|
937
|
+
if (!Array.isArray(stages)) {
|
|
938
|
+
errors.push(`${label} internal_pipeline.stages must be an array`);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const stageIds = new Set();
|
|
942
|
+
for (const item of stages) {
|
|
943
|
+
if (!isRecord(item)) {
|
|
944
|
+
errors.push(`${label} internal_pipeline.stages entries must be mappings`);
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
const stageId = String(item.stage_id ?? "");
|
|
948
|
+
stageIds.add(stageId);
|
|
949
|
+
for (const key of ["stage_id", "command", "visibility", "status", "outputs"]) {
|
|
950
|
+
if (!(key in item)) {
|
|
951
|
+
errors.push(`${label} internal_pipeline stage missing ${key}`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if ((stageId === "vision2prompt" || stageId === "prompt2proto") && item.visibility !== "internal") {
|
|
955
|
+
errors.push(`${label} internal pipeline stage ${stageId} must be internal`);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
for (const required of ["proto-preflight", "vision2prompt", "prompt2proto"]) {
|
|
959
|
+
if (!stageIds.has(required)) {
|
|
960
|
+
errors.push(`${label} internal_pipeline missing stage ${required}`);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function validateDirectionCountPolicy(label, value, errors) {
|
|
965
|
+
if (!isRecord(value)) {
|
|
966
|
+
errors.push(`${label} direction_count_policy must be a mapping`);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const source = String(value.source ?? "");
|
|
970
|
+
if (source && !["user_input", "agent_default_after_user_delegation"].includes(source)) {
|
|
971
|
+
errors.push(`${label} direction_count_policy.source has invalid value ${source}`);
|
|
972
|
+
}
|
|
973
|
+
if (typeof value.resolved_count !== "number" || value.resolved_count < 1) {
|
|
974
|
+
errors.push(`${label} direction_count_policy.resolved_count must be a positive number`);
|
|
975
|
+
}
|
|
976
|
+
if (source === "agent_default_after_user_delegation" && value.resolved_count !== 3) {
|
|
977
|
+
errors.push(`${label} direction_count_policy delegated default must resolve to 3`);
|
|
978
|
+
}
|
|
979
|
+
if (value.ask_user_question_required === true && !nonEmptyString(value.ask_user_question)) {
|
|
980
|
+
errors.push(`${label} direction_count_policy.ask_user_question must be set when askUserQuestion is required`);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function validateRequiredObjectFields(label, field, value, keys, errors) {
|
|
984
|
+
if (!isRecord(value)) {
|
|
985
|
+
errors.push(`${label} ${field} must be a mapping`);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
for (const key of keys) {
|
|
989
|
+
if (!hasUsefulValue(value[key])) {
|
|
990
|
+
errors.push(`${label} ${field}.${key} must be non-empty`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function validateProductExperienceModel(label, value, data, errors) {
|
|
995
|
+
if (!strategicPromptPackRequiresProductExperienceModel(data)) {
|
|
996
|
+
if ("product_experience_model" in data && !isRecord(value)) {
|
|
997
|
+
errors.push(`${label} product_experience_model must be a mapping when present`);
|
|
998
|
+
}
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
validateRequiredObjectFields(label, "product_experience_model", value, PRODUCT_EXPERIENCE_MODEL_FIELDS, errors);
|
|
1002
|
+
}
|
|
1003
|
+
function strategicPromptPackRequiresProductExperienceModel(data) {
|
|
1004
|
+
const promptTextManifest = isRecord(data.prompt_text_manifest) ? data.prompt_text_manifest : {};
|
|
1005
|
+
const imageGeneration = isRecord(data.image_generation) ? data.image_generation : {};
|
|
1006
|
+
return (data.status !== "draft" ||
|
|
1007
|
+
promptTextManifest.status === "ready_for_image_generation" ||
|
|
1008
|
+
promptTextManifest.status === "generated" ||
|
|
1009
|
+
(typeof imageGeneration.status === "string" && imageGeneration.status !== "not_started"));
|
|
1010
|
+
}
|
|
1011
|
+
function validatePrototypeSystemContract(label, value, data, errors) {
|
|
1012
|
+
const required = strategicPromptPackRequiresProductExperienceModel(data);
|
|
1013
|
+
if (!required) {
|
|
1014
|
+
if ("prototype_system_contract" in data && !isRecord(value)) {
|
|
1015
|
+
errors.push(`${label} prototype_system_contract must be a mapping when present`);
|
|
1016
|
+
}
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
validateRequiredObjectFields(label, "prototype_system_contract", value, PROTOTYPE_SYSTEM_CONTRACT_FIELDS, errors);
|
|
1020
|
+
if (!isRecord(value)) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
for (const key of PROTOTYPE_SYSTEM_CONTRACT_FIELDS) {
|
|
1024
|
+
if (key === "copy_tone") {
|
|
1025
|
+
if (!nonEmptyString(value[key])) {
|
|
1026
|
+
errors.push(`${label} prototype_system_contract.copy_tone must be a non-empty string`);
|
|
1027
|
+
}
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
if (!Array.isArray(value[key]) || value[key].length === 0) {
|
|
1031
|
+
errors.push(`${label} prototype_system_contract.${key} must be a non-empty array`);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function validatePrototypeRealityGate(label, value, data, errors) {
|
|
1036
|
+
const required = strategicPromptPackRequiresProductExperienceModel(data);
|
|
1037
|
+
if (!required) {
|
|
1038
|
+
if ("prototype_reality_gate" in data && !isRecord(value)) {
|
|
1039
|
+
errors.push(`${label} prototype_reality_gate must be a mapping when present`);
|
|
1040
|
+
}
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
if (!isRecord(value)) {
|
|
1044
|
+
errors.push(`${label} prototype_reality_gate must be a mapping`);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
for (const key of ["status", "trigger", "required_when_prompt_text_ready", "dimensions", "failures", "outcome_notes", "repair_route"]) {
|
|
1048
|
+
if (!(key in value)) {
|
|
1049
|
+
errors.push(`${label} prototype_reality_gate missing ${key}`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const status = String(value.status ?? "");
|
|
1053
|
+
if (status && !["pending", "pass", "fail"].includes(status)) {
|
|
1054
|
+
errors.push(`${label} prototype_reality_gate.status has invalid value ${status}`);
|
|
1055
|
+
}
|
|
1056
|
+
if (value.trigger !== "before_image_generation") {
|
|
1057
|
+
errors.push(`${label} prototype_reality_gate.trigger must be before_image_generation`);
|
|
1058
|
+
}
|
|
1059
|
+
if (value.required_when_prompt_text_ready !== true) {
|
|
1060
|
+
errors.push(`${label} prototype_reality_gate.required_when_prompt_text_ready must be true`);
|
|
1061
|
+
}
|
|
1062
|
+
if (value.repair_route !== "/ow:vision2prompt") {
|
|
1063
|
+
errors.push(`${label} prototype_reality_gate.repair_route must be /ow:vision2prompt`);
|
|
1064
|
+
}
|
|
1065
|
+
if (!Array.isArray(value.dimensions)) {
|
|
1066
|
+
errors.push(`${label} prototype_reality_gate.dimensions must be an array`);
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
for (const dimension of PROTOTYPE_REALITY_GATE_DIMENSIONS) {
|
|
1070
|
+
if (!value.dimensions.includes(dimension)) {
|
|
1071
|
+
errors.push(`${label} prototype_reality_gate.dimensions missing ${dimension}`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
for (const key of ["failures", "outcome_notes"]) {
|
|
1076
|
+
if (!Array.isArray(value[key])) {
|
|
1077
|
+
errors.push(`${label} prototype_reality_gate.${key} must be an array`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const promptTextManifest = isRecord(data.prompt_text_manifest) ? data.prompt_text_manifest : {};
|
|
1081
|
+
const imageGeneration = isRecord(data.image_generation) ? data.image_generation : {};
|
|
1082
|
+
const promptTextReady = promptTextManifest.status === "ready_for_image_generation" || promptTextManifest.status === "generated";
|
|
1083
|
+
if (promptTextReady && status !== "pass") {
|
|
1084
|
+
errors.push(`${label} prototype_reality_gate.status must be pass before image generation`);
|
|
1085
|
+
}
|
|
1086
|
+
if (status === "pass" && Array.isArray(value.failures) && value.failures.length > 0) {
|
|
1087
|
+
errors.push(`${label} prototype_reality_gate.failures must be empty when status is pass`);
|
|
1088
|
+
}
|
|
1089
|
+
if (status === "fail" && typeof imageGeneration.status === "string" && imageGeneration.status !== "not_started") {
|
|
1090
|
+
errors.push(`${label} prototype_reality_gate failed gates must not start image_generation`);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
function validatePromptPackIntegrityGate(label, value, data, errors) {
|
|
1094
|
+
const required = strategicPromptPackRequiresProductExperienceModel(data);
|
|
1095
|
+
if (!required) {
|
|
1096
|
+
if ("prompt_pack_integrity_gate" in data && !isRecord(value)) {
|
|
1097
|
+
errors.push(`${label} prompt_pack_integrity_gate must be a mapping when present`);
|
|
1098
|
+
}
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if (!isRecord(value)) {
|
|
1102
|
+
errors.push(`${label} prompt_pack_integrity_gate must be a mapping`);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
for (const key of ["status", "trigger", "required_when_prompt_text_ready", "dimensions", "failures", "outcome_notes", "repair_route"]) {
|
|
1106
|
+
if (!(key in value)) {
|
|
1107
|
+
errors.push(`${label} prompt_pack_integrity_gate missing ${key}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const status = String(value.status ?? "");
|
|
1111
|
+
if (status && !["pending", "pass", "fail"].includes(status)) {
|
|
1112
|
+
errors.push(`${label} prompt_pack_integrity_gate.status has invalid value ${status}`);
|
|
1113
|
+
}
|
|
1114
|
+
if (value.trigger !== "before_image_generation") {
|
|
1115
|
+
errors.push(`${label} prompt_pack_integrity_gate.trigger must be before_image_generation`);
|
|
1116
|
+
}
|
|
1117
|
+
if (value.required_when_prompt_text_ready !== true) {
|
|
1118
|
+
errors.push(`${label} prompt_pack_integrity_gate.required_when_prompt_text_ready must be true`);
|
|
1119
|
+
}
|
|
1120
|
+
if (value.repair_route !== "/ow:vision2prompt") {
|
|
1121
|
+
errors.push(`${label} prompt_pack_integrity_gate.repair_route must be /ow:vision2prompt`);
|
|
1122
|
+
}
|
|
1123
|
+
if (!Array.isArray(value.dimensions)) {
|
|
1124
|
+
errors.push(`${label} prompt_pack_integrity_gate.dimensions must be an array`);
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
for (const dimension of PROMPT_PACK_INTEGRITY_GATE_DIMENSIONS) {
|
|
1128
|
+
if (!value.dimensions.includes(dimension)) {
|
|
1129
|
+
errors.push(`${label} prompt_pack_integrity_gate.dimensions missing ${dimension}`);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
for (const key of ["failures", "outcome_notes"]) {
|
|
1134
|
+
if (!Array.isArray(value[key])) {
|
|
1135
|
+
errors.push(`${label} prompt_pack_integrity_gate.${key} must be an array`);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
validatePromptPackIntegrity(label, data, errors);
|
|
1139
|
+
const promptTextManifest = isRecord(data.prompt_text_manifest) ? data.prompt_text_manifest : {};
|
|
1140
|
+
const imageGeneration = isRecord(data.image_generation) ? data.image_generation : {};
|
|
1141
|
+
const promptTextReady = promptTextManifest.status === "ready_for_image_generation" || promptTextManifest.status === "generated";
|
|
1142
|
+
if (promptTextReady && status !== "pass") {
|
|
1143
|
+
errors.push(`${label} prompt_pack_integrity_gate.status must be pass before image generation`);
|
|
1144
|
+
}
|
|
1145
|
+
if (status === "pass" && Array.isArray(value.failures) && value.failures.length > 0) {
|
|
1146
|
+
errors.push(`${label} prompt_pack_integrity_gate.failures must be empty when status is pass`);
|
|
1147
|
+
}
|
|
1148
|
+
if (status === "fail" && typeof imageGeneration.status === "string" && imageGeneration.status !== "not_started") {
|
|
1149
|
+
errors.push(`${label} prompt_pack_integrity_gate failed gates must not start image_generation`);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function validatePromptPackIntegrity(label, data, errors) {
|
|
1153
|
+
const directions = Array.isArray(data.directions) ? data.directions.filter(isRecord) : [];
|
|
1154
|
+
const directionIds = new Set();
|
|
1155
|
+
const promptIds = new Set();
|
|
1156
|
+
for (const direction of directions) {
|
|
1157
|
+
if (nonEmptyString(direction.direction_id)) {
|
|
1158
|
+
directionIds.add(String(direction.direction_id));
|
|
1159
|
+
}
|
|
1160
|
+
if (Array.isArray(direction.screen_prompts)) {
|
|
1161
|
+
for (const prompt of direction.screen_prompts) {
|
|
1162
|
+
if (isRecord(prompt) && nonEmptyString(prompt.prompt_id)) {
|
|
1163
|
+
promptIds.add(String(prompt.prompt_id));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const countPolicy = isRecord(data.direction_count_policy) ? data.direction_count_policy : {};
|
|
1169
|
+
const promptTextManifest = isRecord(data.prompt_text_manifest) ? data.prompt_text_manifest : {};
|
|
1170
|
+
const promptTextReady = promptTextManifest.status === "ready_for_image_generation" || promptTextManifest.status === "generated";
|
|
1171
|
+
const resolvedCount = typeof countPolicy.resolved_count === "number" ? countPolicy.resolved_count : null;
|
|
1172
|
+
if (promptTextReady && resolvedCount !== null && directions.length !== resolvedCount) {
|
|
1173
|
+
errors.push(`${label} directions length must equal direction_count_policy.resolved_count before image generation`);
|
|
1174
|
+
}
|
|
1175
|
+
if (promptTextReady && typeof promptTextManifest.direction_count !== "number") {
|
|
1176
|
+
errors.push(`${label} prompt_text_manifest.direction_count must be a number before image generation`);
|
|
1177
|
+
}
|
|
1178
|
+
if (typeof promptTextManifest.direction_count === "number" && directions.length > 0 && promptTextManifest.direction_count !== directions.length) {
|
|
1179
|
+
errors.push(`${label} prompt_text_manifest.direction_count must equal directions length`);
|
|
1180
|
+
}
|
|
1181
|
+
if (promptTextReady) {
|
|
1182
|
+
const refs = Array.isArray(promptTextManifest.prompt_text_refs) ? promptTextManifest.prompt_text_refs : [];
|
|
1183
|
+
if (refs.length === 0) {
|
|
1184
|
+
errors.push(`${label} prompt_text_manifest.prompt_text_refs must include prompt refs before image generation`);
|
|
1185
|
+
}
|
|
1186
|
+
refs.forEach((ref, index) => {
|
|
1187
|
+
if (!stringReferencesKnownPrompt(String(ref), directionIds, promptIds)) {
|
|
1188
|
+
errors.push(`${label} prompt_text_manifest.prompt_text_refs[${index}] must reference an existing direction_id or prompt_id`);
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
const imageGeneration = isRecord(data.image_generation) ? data.image_generation : {};
|
|
1193
|
+
const generatedImages = Array.isArray(imageGeneration.generated_images) ? imageGeneration.generated_images : [];
|
|
1194
|
+
generatedImages.forEach((image, index) => {
|
|
1195
|
+
if (!isRecord(image)) {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
const directionId = String(image.direction_id ?? "");
|
|
1199
|
+
const promptId = String(image.prompt_id ?? "");
|
|
1200
|
+
if (directionId && !directionIds.has(directionId)) {
|
|
1201
|
+
errors.push(`${label} image_generation.generated_images[${index}].direction_id must exist in directions`);
|
|
1202
|
+
}
|
|
1203
|
+
if (promptId && !promptIds.has(promptId)) {
|
|
1204
|
+
errors.push(`${label} image_generation.generated_images[${index}].prompt_id must exist in directions[].screen_prompts`);
|
|
1205
|
+
}
|
|
1206
|
+
const metadata = isRecord(image.metadata) ? image.metadata : {};
|
|
1207
|
+
const sourcePromptRef = String(metadata.source_prompt_ref ?? "");
|
|
1208
|
+
if (sourcePromptRef && !stringReferencesKnownPrompt(sourcePromptRef, directionIds, promptIds)) {
|
|
1209
|
+
errors.push(`${label} image_generation.generated_images[${index}].metadata.source_prompt_ref must reference an existing direction_id or prompt_id`);
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
function validateScreenBoundExecutability(label, data, errors) {
|
|
1214
|
+
if (!strategicPromptPackRequiresScreenExecutability(data)) {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
validateRequiredObjectFields(label, "prototype_brief", data.prototype_brief, STRATEGIC_PROTOTYPE_BRIEF_FIELDS, errors);
|
|
1218
|
+
validateRequiredObjectFields(label, "global_design_system_prompt", data.global_design_system_prompt, STRATEGIC_GLOBAL_DESIGN_SYSTEM_PROMPT_FIELDS, errors);
|
|
1219
|
+
validateRequiredObjectFields(label, "quality_rubric", data.quality_rubric, STRATEGIC_QUALITY_RUBRIC_FIELDS, errors);
|
|
1220
|
+
const manifestIds = validateStrategicScreenManifest(label, data.screen_manifest, errors);
|
|
1221
|
+
validateStrategicDirectionScreenPrompts(label, data.directions, manifestIds, errors);
|
|
1222
|
+
}
|
|
1223
|
+
function strategicPromptPackRequiresScreenExecutability(data) {
|
|
1224
|
+
const promptTextManifest = isRecord(data.prompt_text_manifest) ? data.prompt_text_manifest : {};
|
|
1225
|
+
const imageGeneration = isRecord(data.image_generation) ? data.image_generation : {};
|
|
1226
|
+
return (promptTextManifest.status === "ready_for_image_generation" ||
|
|
1227
|
+
promptTextManifest.status === "generated" ||
|
|
1228
|
+
(typeof imageGeneration.status === "string" && imageGeneration.status !== "not_started"));
|
|
1229
|
+
}
|
|
1230
|
+
function validateStrategicScreenManifest(label, value, errors) {
|
|
1231
|
+
const ids = new Set();
|
|
1232
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1233
|
+
errors.push(`${label} screen_manifest must contain screen-bound product states before image generation`);
|
|
1234
|
+
return ids;
|
|
1235
|
+
}
|
|
1236
|
+
value.forEach((item, index) => {
|
|
1237
|
+
validateRequiredObjectFields(label, `screen_manifest[${index}]`, item, STRATEGIC_SCREEN_MANIFEST_FIELDS, errors);
|
|
1238
|
+
if (!isRecord(item)) {
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
if (nonEmptyString(item.target_screen_id)) {
|
|
1242
|
+
ids.add(String(item.target_screen_id));
|
|
1243
|
+
}
|
|
1244
|
+
if (!hasUsefulValue(item.ai_behavior) && !hasUsefulValue(item.non_ai_rationale)) {
|
|
1245
|
+
errors.push(`${label} screen_manifest[${index}] must include ai_behavior or non_ai_rationale`);
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
return ids;
|
|
1249
|
+
}
|
|
1250
|
+
function validateStrategicDirectionScreenPrompts(label, directions, manifestIds, errors) {
|
|
1251
|
+
if (!Array.isArray(directions)) {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
directions.forEach((direction, directionIndex) => {
|
|
1255
|
+
if (!isRecord(direction)) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
const screenPrompts = direction.screen_prompts;
|
|
1259
|
+
if (!Array.isArray(screenPrompts) || screenPrompts.length === 0) {
|
|
1260
|
+
errors.push(`${label} directions[${directionIndex}].screen_prompts must contain screen-bound prompt text before image generation`);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
screenPrompts.forEach((prompt, promptIndex) => {
|
|
1264
|
+
const fieldLabel = `directions[${directionIndex}].screen_prompts[${promptIndex}]`;
|
|
1265
|
+
validateRequiredObjectFields(label, fieldLabel, prompt, STRATEGIC_SCREEN_PROMPT_FIELDS, errors);
|
|
1266
|
+
if (!isRecord(prompt)) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
if (!hasUsefulValue(prompt.prompt) && !hasUsefulValue(prompt.standalone_prompt)) {
|
|
1270
|
+
errors.push(`${label} ${fieldLabel} must include prompt or standalone_prompt`);
|
|
1271
|
+
}
|
|
1272
|
+
if (nonEmptyString(prompt.target_screen_id) && manifestIds.size > 0 && !manifestIds.has(String(prompt.target_screen_id))) {
|
|
1273
|
+
errors.push(`${label} ${fieldLabel}.target_screen_id must exist in screen_manifest`);
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
function validatePromptParagraphQuality(label, data, errors) {
|
|
1279
|
+
if (!strategicPromptPackRequiresScreenExecutability(data)) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
const directions = Array.isArray(data.directions) ? data.directions : [];
|
|
1283
|
+
directions.forEach((direction, directionIndex) => {
|
|
1284
|
+
if (!isRecord(direction)) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const prototypePrompt = String(direction.prototype_prompt ?? "");
|
|
1288
|
+
const missingPrototypeDimensions = promptParagraphMissingDimensions(prototypePrompt, PROTOTYPE_PROMPT_PARAGRAPH_DIMENSIONS, 70);
|
|
1289
|
+
if (missingPrototypeDimensions.length > 0) {
|
|
1290
|
+
errors.push(`${label} directions[${directionIndex}].prototype_prompt missing prompt paragraph quality dimensions: ${missingPrototypeDimensions.join(", ")}`);
|
|
1291
|
+
}
|
|
1292
|
+
const screenPrompts = Array.isArray(direction.screen_prompts) ? direction.screen_prompts : [];
|
|
1293
|
+
screenPrompts.forEach((prompt, promptIndex) => {
|
|
1294
|
+
if (!isRecord(prompt)) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
const text = String(prompt.prompt ?? prompt.standalone_prompt ?? prompt.prompt_text ?? "");
|
|
1298
|
+
const missingScreenDimensions = promptParagraphMissingDimensions(text, SCREEN_PROMPT_PARAGRAPH_DIMENSIONS, 55);
|
|
1299
|
+
if (missingScreenDimensions.length > 0) {
|
|
1300
|
+
errors.push(`${label} directions[${directionIndex}].screen_prompts[${promptIndex}].prompt missing prompt paragraph quality dimensions: ${missingScreenDimensions.join(", ")}`);
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
function promptParagraphMissingDimensions(text, dimensions, minimumWords) {
|
|
1306
|
+
const normalized = normalizePromptParagraphText(text);
|
|
1307
|
+
const missing = [];
|
|
1308
|
+
if (wordCount(normalized) < minimumWords) {
|
|
1309
|
+
missing.push("minimum_substance");
|
|
1310
|
+
}
|
|
1311
|
+
for (const dimension of dimensions) {
|
|
1312
|
+
if (!promptTextCoversDimension(normalized, dimension)) {
|
|
1313
|
+
missing.push(dimension);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return missing;
|
|
1317
|
+
}
|
|
1318
|
+
function normalizePromptParagraphText(text) {
|
|
1319
|
+
return text.toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1320
|
+
}
|
|
1321
|
+
function wordCount(text) {
|
|
1322
|
+
return text.length === 0 ? 0 : text.split(/\s+/).filter(Boolean).length;
|
|
1323
|
+
}
|
|
1324
|
+
function promptTextCoversDimension(text, dimension) {
|
|
1325
|
+
const groups = {
|
|
1326
|
+
product_context: ["product", "called", "positioning", "prototype", "dashboard", "app", "workflow", "platform"],
|
|
1327
|
+
target_user: ["target user", "user", "operator", "reviewer", "adult", "lead", "planner", "coordinator"],
|
|
1328
|
+
journey: ["journey", "flow", "step", "screen", "stage", "loop", "from", "then", "after"],
|
|
1329
|
+
screens_or_components: ["screen", "component", "panel", "drawer", "card", "button", "control", "map", "list", "navigation"],
|
|
1330
|
+
interaction_or_system_response: ["action", "respond", "response", "when the user", "after the user", "system", "ai", "copilot", "confirm", "revise"],
|
|
1331
|
+
concrete_content: ["example", "copy", "metric", "data", "id", "timestamp", "owner", "confidence", "message", "label"],
|
|
1332
|
+
trust_or_user_control: ["trust", "privacy", "control", "approval", "consent", "delete", "edit", "human", "audit", "citation"],
|
|
1333
|
+
visual_direction: ["visual", "layout", "canvas", "density", "style", "component vocabulary", "map first", "voice first", "mobile", "desktop"],
|
|
1334
|
+
anti_goals: ["do not", "avoid", "must not", "not a", "not an", "anti goal", "negative"],
|
|
1335
|
+
desired_user_feeling: ["feel", "feeling", "confidence", "safe", "credible", "calm", "control", "trust", "relief"],
|
|
1336
|
+
journey_or_screen_purpose: ["journey", "purpose", "screen", "stage", "flow", "entry", "recap", "review", "response", "monitor"],
|
|
1337
|
+
user_goal_or_system_state: ["goal", "user", "operator", "state", "selected", "pending", "stale", "blocked", "ready", "active"],
|
|
1338
|
+
components_or_domain_objects: ["component", "panel", "drawer", "card", "button", "control", "map", "object", "incident", "parcel", "route", "memory"],
|
|
1339
|
+
actions_or_system_response: ["action", "tap", "select", "confirm", "revise", "respond", "response", "system", "ai", "copilot"],
|
|
1340
|
+
negative_constraints: ["do not", "avoid", "must not", "negative", "not show", "not turn"],
|
|
1341
|
+
acceptance_or_user_feeling: ["acceptance", "must show", "should make", "feel", "confidence", "credible", "safe", "control"],
|
|
1342
|
+
};
|
|
1343
|
+
return (groups[dimension] ?? []).some((token) => text.includes(token));
|
|
1344
|
+
}
|
|
1345
|
+
function stringReferencesKnownPrompt(value, directionIds, promptIds) {
|
|
1346
|
+
const normalized = normalizePromptRef(value);
|
|
1347
|
+
if (normalized.length === 0) {
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1350
|
+
for (const id of [...directionIds, ...promptIds]) {
|
|
1351
|
+
const token = normalizePromptRef(id);
|
|
1352
|
+
if (token.length > 0 && normalized.includes(token)) {
|
|
1353
|
+
return true;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return false;
|
|
1357
|
+
}
|
|
1358
|
+
function normalizePromptRef(value) {
|
|
1359
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
1360
|
+
}
|
|
1361
|
+
function validateStrategicDirections(label, value, countPolicy, errors) {
|
|
1362
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1363
|
+
errors.push(`${label} directions must contain strategic prompt directions`);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
const resolvedCount = isRecord(countPolicy) && typeof countPolicy.resolved_count === "number" ? countPolicy.resolved_count : null;
|
|
1367
|
+
if (resolvedCount !== null && value.length < resolvedCount) {
|
|
1368
|
+
errors.push(`${label} directions must include at least direction_count_policy.resolved_count items`);
|
|
1369
|
+
}
|
|
1370
|
+
value.forEach((item, index) => {
|
|
1371
|
+
if (!isRecord(item)) {
|
|
1372
|
+
errors.push(`${label} directions[${index}] must be a mapping`);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
for (const key of STRATEGIC_DIRECTION_FIELDS) {
|
|
1376
|
+
if (!hasUsefulValue(item[key])) {
|
|
1377
|
+
errors.push(`${label} directions[${index}].${key} must be non-empty`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
const screenPrompts = item.screen_prompts;
|
|
1381
|
+
if (Array.isArray(screenPrompts) && screenPrompts.length < 2) {
|
|
1382
|
+
errors.push(`${label} directions[${index}].screen_prompts must include multi-image prompt text`);
|
|
1383
|
+
}
|
|
1384
|
+
const distinctness = String(item.distinctness_rationale ?? "").toLowerCase();
|
|
1385
|
+
if (!STRATEGIC_DISTINCTNESS_SIGNALS.some((signal) => distinctness.includes(signal))) {
|
|
1386
|
+
errors.push(`${label} directions[${index}].distinctness_rationale must name a strategic difference, not only visual style`);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
function validateBuildRecommendation(label, value, errors) {
|
|
1391
|
+
validateRequiredObjectFields(label, "build_recommendation", value, ["first_direction_id", "why_first", "success_signals", "failure_signals", "next_test_if_it_works"], errors);
|
|
1392
|
+
}
|
|
1393
|
+
function validatePromptTextManifest(label, value, errors) {
|
|
1394
|
+
if (!isRecord(value)) {
|
|
1395
|
+
errors.push(`${label} prompt_text_manifest must be a mapping`);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const status = String(value.status ?? "");
|
|
1399
|
+
if (status && !["draft", "ready_for_image_generation", "generated"].includes(status)) {
|
|
1400
|
+
errors.push(`${label} prompt_text_manifest.status has invalid value ${status}`);
|
|
1401
|
+
}
|
|
1402
|
+
if (status !== "draft" && value.directions_ready !== true) {
|
|
1403
|
+
errors.push(`${label} prompt_text_manifest.directions_ready must be true before image generation`);
|
|
1404
|
+
}
|
|
1405
|
+
if (!Array.isArray(value.prompt_text_refs)) {
|
|
1406
|
+
errors.push(`${label} prompt_text_manifest.prompt_text_refs must be an array`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
function validatePostValidate(label, value, countPolicy, promptTextManifest, imageGeneration, directions, errors) {
|
|
1410
|
+
if (!isRecord(value)) {
|
|
1411
|
+
errors.push(`${label} post_validate must be a mapping`);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
for (const key of ["status", "trigger", "required_when_direction_count_gte", "skip_when_resolved_count", "threshold_policy", "fingerprint_dimensions", "comparisons", "failures", "outcome_notes", "repair_route"]) {
|
|
1415
|
+
if (!(key in value)) {
|
|
1416
|
+
errors.push(`${label} post_validate missing ${key}`);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
const status = String(value.status ?? "");
|
|
1420
|
+
if (status && !["pending", "pass", "fail", "skipped"].includes(status)) {
|
|
1421
|
+
errors.push(`${label} post_validate.status has invalid value ${status}`);
|
|
1422
|
+
}
|
|
1423
|
+
if (value.trigger !== "after_prompt_assets_ready") {
|
|
1424
|
+
errors.push(`${label} post_validate.trigger must be after_prompt_assets_ready`);
|
|
1425
|
+
}
|
|
1426
|
+
if (value.required_when_direction_count_gte !== 2) {
|
|
1427
|
+
errors.push(`${label} post_validate.required_when_direction_count_gte must be 2`);
|
|
1428
|
+
}
|
|
1429
|
+
if (value.skip_when_resolved_count !== 1) {
|
|
1430
|
+
errors.push(`${label} post_validate.skip_when_resolved_count must be 1`);
|
|
1431
|
+
}
|
|
1432
|
+
if (value.repair_route !== "/ow:vision2prompt") {
|
|
1433
|
+
errors.push(`${label} post_validate.repair_route must be /ow:vision2prompt`);
|
|
1434
|
+
}
|
|
1435
|
+
const thresholdPolicy = value.threshold_policy;
|
|
1436
|
+
if (!isRecord(thresholdPolicy)) {
|
|
1437
|
+
errors.push(`${label} post_validate.threshold_policy must be a mapping`);
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
if (thresholdPolicy.method !== "strategic_fingerprint_similarity") {
|
|
1441
|
+
errors.push(`${label} post_validate.threshold_policy.method must be strategic_fingerprint_similarity`);
|
|
1442
|
+
}
|
|
1443
|
+
if (thresholdPolicy.comparison !== "pairwise") {
|
|
1444
|
+
errors.push(`${label} post_validate.threshold_policy.comparison must be pairwise`);
|
|
1445
|
+
}
|
|
1446
|
+
if (typeof thresholdPolicy.max_pairwise_similarity !== "number" || thresholdPolicy.max_pairwise_similarity <= 0 || thresholdPolicy.max_pairwise_similarity >= 1) {
|
|
1447
|
+
errors.push(`${label} post_validate.threshold_policy.max_pairwise_similarity must be between 0 and 1`);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (!Array.isArray(value.fingerprint_dimensions)) {
|
|
1451
|
+
errors.push(`${label} post_validate.fingerprint_dimensions must be an array`);
|
|
1452
|
+
}
|
|
1453
|
+
else {
|
|
1454
|
+
for (const dimension of STRATEGIC_FINGERPRINT_DIMENSIONS) {
|
|
1455
|
+
if (!value.fingerprint_dimensions.includes(dimension)) {
|
|
1456
|
+
errors.push(`${label} post_validate.fingerprint_dimensions missing ${dimension}`);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
for (const key of ["comparisons", "failures", "outcome_notes"]) {
|
|
1461
|
+
if (!Array.isArray(value[key])) {
|
|
1462
|
+
errors.push(`${label} post_validate.${key} must be an array`);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
const resolvedCount = isRecord(countPolicy) && typeof countPolicy.resolved_count === "number" ? countPolicy.resolved_count : null;
|
|
1466
|
+
const promptStatus = isRecord(promptTextManifest) ? String(promptTextManifest.status ?? "") : "";
|
|
1467
|
+
const promptReady = promptStatus === "ready_for_image_generation" || promptStatus === "generated";
|
|
1468
|
+
if (resolvedCount === 1 && status !== "skipped") {
|
|
1469
|
+
errors.push(`${label} post_validate.status must be skipped when direction_count_policy.resolved_count is 1`);
|
|
1470
|
+
}
|
|
1471
|
+
if (promptReady && resolvedCount !== null && resolvedCount >= 2 && !["pass", "fail"].includes(status)) {
|
|
1472
|
+
errors.push(`${label} post_validate.status must be pass or fail before /ow:prompt2proto when resolved_count is 2 or more`);
|
|
1473
|
+
}
|
|
1474
|
+
if (status === "skipped" && resolvedCount !== 1) {
|
|
1475
|
+
errors.push(`${label} post_validate.status can be skipped only when resolved_count is 1`);
|
|
1476
|
+
}
|
|
1477
|
+
if (status === "fail") {
|
|
1478
|
+
const imageStatus = isRecord(imageGeneration) ? String(imageGeneration.status ?? "") : "";
|
|
1479
|
+
if (["queued", "in_progress", "complete"].includes(imageStatus)) {
|
|
1480
|
+
errors.push(`${label} post_validate failed gates must not start image_generation`);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (status === "pass" && promptReady && resolvedCount !== null && resolvedCount >= 2) {
|
|
1484
|
+
validateStrategicFingerprintSimilarity(label, directions, value.fingerprint_dimensions, thresholdPolicy, errors);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
function validateStrategicFingerprintSimilarity(label, directions, dimensions, thresholdPolicy, errors) {
|
|
1488
|
+
if (!Array.isArray(directions) || !Array.isArray(dimensions) || !isRecord(thresholdPolicy)) {
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
const threshold = typeof thresholdPolicy.max_pairwise_similarity === "number" ? thresholdPolicy.max_pairwise_similarity : null;
|
|
1492
|
+
if (threshold === null) {
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
const records = directions
|
|
1496
|
+
.filter(isRecord)
|
|
1497
|
+
.map((direction, index) => {
|
|
1498
|
+
const directionId = nonEmptyString(direction.direction_id) ? String(direction.direction_id) : `index-${index}`;
|
|
1499
|
+
const fingerprint = isRecord(direction.strategic_fingerprint) ? direction.strategic_fingerprint : null;
|
|
1500
|
+
if (fingerprint === null) {
|
|
1501
|
+
errors.push(`${label} directions[${index}].strategic_fingerprint must be set when post_validate.status is pass`);
|
|
1502
|
+
}
|
|
1503
|
+
return { directionId, index, fingerprint };
|
|
1504
|
+
});
|
|
1505
|
+
for (let left = 0; left < records.length; left += 1) {
|
|
1506
|
+
for (let right = left + 1; right < records.length; right += 1) {
|
|
1507
|
+
const leftRecord = records[left];
|
|
1508
|
+
const rightRecord = records[right];
|
|
1509
|
+
if (!leftRecord?.fingerprint || !rightRecord?.fingerprint) {
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
const result = compareStrategicFingerprints(leftRecord.fingerprint, rightRecord.fingerprint, dimensions);
|
|
1513
|
+
if (result.comparedCount === 0) {
|
|
1514
|
+
errors.push(`${label} post_validate cannot compare ${leftRecord.directionId} and ${rightRecord.directionId}: no populated strategic_fingerprint dimensions`);
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
if (result.score > threshold) {
|
|
1518
|
+
errors.push(`${label} post_validate pair ${leftRecord.directionId}/${rightRecord.directionId} exceeds strategic fingerprint similarity threshold ${threshold}: score ${formatSimilarity(result.score)} shared dimensions ${result.sharedDimensions.join(", ")}`);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
function compareStrategicFingerprints(left, right, dimensions) {
|
|
1524
|
+
let total = 0;
|
|
1525
|
+
let comparedCount = 0;
|
|
1526
|
+
const sharedDimensions = [];
|
|
1527
|
+
for (const rawDimension of dimensions) {
|
|
1528
|
+
const dimension = String(rawDimension);
|
|
1529
|
+
const leftTokens = fingerprintTokens(left[dimension]);
|
|
1530
|
+
const rightTokens = fingerprintTokens(right[dimension]);
|
|
1531
|
+
if (leftTokens.size === 0 || rightTokens.size === 0) {
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
const similarity = jaccardSimilarity(leftTokens, rightTokens);
|
|
1535
|
+
total += similarity;
|
|
1536
|
+
comparedCount += 1;
|
|
1537
|
+
if (similarity >= 0.8) {
|
|
1538
|
+
sharedDimensions.push(dimension);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return {
|
|
1542
|
+
score: comparedCount === 0 ? 0 : total / comparedCount,
|
|
1543
|
+
comparedCount,
|
|
1544
|
+
sharedDimensions,
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function fingerprintTokens(value) {
|
|
1548
|
+
const raw = Array.isArray(value)
|
|
1549
|
+
? value.join(" ")
|
|
1550
|
+
: isRecord(value)
|
|
1551
|
+
? Object.values(value).join(" ")
|
|
1552
|
+
: String(value ?? "");
|
|
1553
|
+
return new Set(raw.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length > 1));
|
|
1554
|
+
}
|
|
1555
|
+
function jaccardSimilarity(left, right) {
|
|
1556
|
+
let intersection = 0;
|
|
1557
|
+
for (const token of left) {
|
|
1558
|
+
if (right.has(token)) {
|
|
1559
|
+
intersection += 1;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
const union = left.size + right.size - intersection;
|
|
1563
|
+
return union === 0 ? 0 : intersection / union;
|
|
1564
|
+
}
|
|
1565
|
+
function formatSimilarity(value) {
|
|
1566
|
+
return value.toFixed(2);
|
|
1567
|
+
}
|
|
1568
|
+
function validateImageGeneration(label, value, errors) {
|
|
1569
|
+
if (!isRecord(value)) {
|
|
1570
|
+
errors.push(`${label} image_generation must be a mapping`);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
const status = String(value.status ?? "");
|
|
1574
|
+
if (status && !["not_started", "queued", "in_progress", "complete", "blocked"].includes(status)) {
|
|
1575
|
+
errors.push(`${label} image_generation.status has invalid value ${status}`);
|
|
1576
|
+
}
|
|
1577
|
+
if (!nonEmptyString(value.batch_strategy)) {
|
|
1578
|
+
errors.push(`${label} image_generation.batch_strategy must be non-empty`);
|
|
1579
|
+
}
|
|
1580
|
+
if (!Array.isArray(value.generated_images)) {
|
|
1581
|
+
errors.push(`${label} image_generation.generated_images must be an array`);
|
|
1582
|
+
}
|
|
1583
|
+
else {
|
|
1584
|
+
value.generated_images.forEach((item, index) => validateGeneratedImageMetadata(label, item, index, errors));
|
|
1585
|
+
}
|
|
1586
|
+
if (!Array.isArray(value.collection_notes)) {
|
|
1587
|
+
errors.push(`${label} image_generation.collection_notes must be an array`);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
function validateGeneratedImageMetadata(label, value, index, errors) {
|
|
1591
|
+
if (!isRecord(value)) {
|
|
1592
|
+
errors.push(`${label} image_generation.generated_images[${index}] must be a mapping`);
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
for (const key of ["image_id", "direction_id", "prompt_id", "screen_name", "path", "metadata"]) {
|
|
1596
|
+
if (!hasUsefulValue(value[key])) {
|
|
1597
|
+
errors.push(`${label} image_generation.generated_images[${index}].${key} must be non-empty`);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const metadata = value.metadata;
|
|
1601
|
+
if (!isRecord(metadata)) {
|
|
1602
|
+
errors.push(`${label} image_generation.generated_images[${index}].metadata must be a mapping`);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
for (const key of ["source_prompt_ref", "generated_at", "generator", "generation_status", "review_status"]) {
|
|
1606
|
+
if (!hasUsefulValue(metadata[key])) {
|
|
1607
|
+
errors.push(`${label} image_generation.generated_images[${index}].metadata.${key} must be non-empty`);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function hasUsefulValue(value) {
|
|
1612
|
+
if (Array.isArray(value)) {
|
|
1613
|
+
return value.length > 0;
|
|
1614
|
+
}
|
|
1615
|
+
if (isRecord(value)) {
|
|
1616
|
+
return Object.keys(value).length > 0;
|
|
1617
|
+
}
|
|
1618
|
+
return nonEmptyString(value);
|
|
1619
|
+
}
|
|
350
1620
|
function validateVisualConceptPolicy(label, data, errors) {
|
|
351
1621
|
const policy = data.visual_concept_policy;
|
|
352
1622
|
if (!isRecord(policy)) {
|
|
@@ -442,6 +1712,52 @@ function validateProductDesign(label, data, errors) {
|
|
|
442
1712
|
}
|
|
443
1713
|
}
|
|
444
1714
|
}
|
|
1715
|
+
function validateProductionSpec(label, data, errors) {
|
|
1716
|
+
for (const key of ["scope", "requirements", "interfaces", "verification", "change_readiness"]) {
|
|
1717
|
+
if (key in data && !isRecord(data[key])) {
|
|
1718
|
+
errors.push(`${label} ${key} must be a mapping`);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
const changeReadiness = data.change_readiness;
|
|
1722
|
+
if (isRecord(changeReadiness)) {
|
|
1723
|
+
for (const key of ["ready", "next_command"]) {
|
|
1724
|
+
if (!(key in changeReadiness)) {
|
|
1725
|
+
errors.push(`${label} change_readiness missing ${key}`);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
function validateProductionChange(label, data, errors) {
|
|
1731
|
+
for (const key of ["goals", "non_goals", "affected_paths", "acceptance", "validation", "risks"]) {
|
|
1732
|
+
if (key in data && !Array.isArray(data[key])) {
|
|
1733
|
+
errors.push(`${label} ${key} must be an array`);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
const runtimeReadiness = data.runtime_readiness;
|
|
1737
|
+
if (isRecord(runtimeReadiness)) {
|
|
1738
|
+
for (const key of ["ready", "next_command"]) {
|
|
1739
|
+
if (!(key in runtimeReadiness)) {
|
|
1740
|
+
errors.push(`${label} runtime_readiness missing ${key}`);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
function validateTeamRuntime(label, data, errors) {
|
|
1746
|
+
if (typeof data.execution_mode === "string" && !["single_agent", "agent_team", "reconcile", "qa_fix"].includes(data.execution_mode)) {
|
|
1747
|
+
errors.push(`${label} has invalid execution_mode ${data.execution_mode}`);
|
|
1748
|
+
}
|
|
1749
|
+
for (const key of ["work_queue", "agents", "issues", "checkpoints"]) {
|
|
1750
|
+
if (key in data && !Array.isArray(data[key])) {
|
|
1751
|
+
errors.push(`${label} ${key} must be an array`);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
if ("verification" in data && !isRecord(data.verification)) {
|
|
1755
|
+
errors.push(`${label} verification must be a mapping`);
|
|
1756
|
+
}
|
|
1757
|
+
if ("handoff" in data && !isRecord(data.handoff)) {
|
|
1758
|
+
errors.push(`${label} handoff must be a mapping`);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
445
1761
|
async function exists(path) {
|
|
446
1762
|
try {
|
|
447
1763
|
await stat(path);
|