peaks-loop 4.0.0-beta → 4.0.0-beta.3
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 +105 -87
- package/README-en.md +17 -4
- package/README.md +17 -4
- package/dist/src/cli/commands/asset-commands.d.ts +45 -0
- package/dist/src/cli/commands/asset-commands.js +404 -0
- package/dist/src/cli/commands/bee-commands.d.ts +26 -0
- package/dist/src/cli/commands/bee-commands.js +169 -0
- package/dist/src/cli/commands/{solo-commands.d.ts → code-commands.d.ts} +24 -24
- package/dist/src/cli/commands/{solo-commands.js → code-commands.js} +78 -78
- package/dist/src/cli/commands/core/skill-command.js +1 -1
- package/dist/src/cli/commands/dispatch-from-dag.js +1 -1
- package/dist/src/cli/commands/evolution-commands.d.ts +27 -0
- package/dist/src/cli/commands/evolution-commands.js +393 -0
- package/dist/src/cli/commands/hook-handle.js +7 -7
- package/dist/src/cli/commands/job-commands.js +2 -2
- package/dist/src/cli/commands/loop-commands.d.ts +19 -13
- package/dist/src/cli/commands/loop-commands.js +147 -13
- package/dist/src/cli/commands/sediment-commands.js +9 -0
- package/dist/src/cli/commands/session-auto-compact-hook-command.js +2 -2
- package/dist/src/cli/commands/skill-loop-engineering-readiness-commands.d.ts +67 -0
- package/dist/src/cli/commands/skill-loop-engineering-readiness-commands.js +173 -0
- package/dist/src/cli/commands/sub-agent-shared.d.ts +1 -1
- package/dist/src/cli/commands/user-touchpoint-commands.d.ts +4 -4
- package/dist/src/cli/commands/user-touchpoint-commands.js +14 -14
- package/dist/src/cli/commands/workflow-commands.js +22 -22
- package/dist/src/cli/program.js +23 -6
- package/dist/src/services/artifacts/repair-cycle-service.js +1 -1
- package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +1 -1
- package/dist/src/services/audit/enforcers/active-skill-resolver.js +1 -1
- package/dist/src/services/audit/enforcers/{solo-code-ban.d.ts → code-ban.d.ts} +6 -6
- package/dist/src/services/audit/enforcers/{solo-code-ban.js → code-ban.js} +6 -6
- package/dist/src/services/audit/red-line-catalog.d.ts +2 -2
- package/dist/src/services/audit/red-line-catalog.js +5 -5
- package/dist/src/services/audit/types.d.ts +2 -2
- package/dist/src/services/{solo → code}/auto-compact-orchestrator.d.ts +1 -1
- package/dist/src/services/{solo → code}/auto-compact-orchestrator.js +3 -3
- package/dist/src/services/{solo → code}/dag-orchestrator.d.ts +1 -1
- package/dist/src/services/{solo → code}/dag-orchestrator.js +1 -1
- package/dist/src/services/{solo → code}/emit-handoff.d.ts +1 -1
- package/dist/src/services/{solo → code}/emit-handoff.js +1 -1
- package/dist/src/services/{solo → code}/job-shape-decision.d.ts +1 -1
- package/dist/src/services/{solo → code}/job-shape-decision.js +5 -5
- package/dist/src/services/{solo → code}/mode-gate.d.ts +9 -9
- package/dist/src/services/{solo → code}/mode-gate.js +5 -5
- package/dist/src/services/{solo → code}/post-compact-detector.d.ts +3 -3
- package/dist/src/services/{solo → code}/post-compact-detector.js +4 -4
- package/dist/src/services/{solo → code}/step-08-gate.d.ts +4 -4
- package/dist/src/services/{solo → code}/step-08-gate.js +4 -4
- package/dist/src/services/{solo → code}/user-touchpoint-classifier.d.ts +1 -1
- package/dist/src/services/{solo → code}/user-touchpoint-classifier.js +1 -1
- package/dist/src/services/context/auto-compact-dispatcher.d.ts +2 -2
- package/dist/src/services/context/auto-compact-dispatcher.js +2 -2
- package/dist/src/services/context/auto-compact-types.d.ts +1 -1
- package/dist/src/services/crystallization/crystallization-service.d.ts +239 -0
- package/dist/src/services/crystallization/crystallization-service.js +397 -0
- package/dist/src/services/crystallization/crystallization-store.d.ts +75 -0
- package/dist/src/services/crystallization/crystallization-store.js +193 -0
- package/dist/src/services/crystallization/crystallization-types.d.ts +313 -0
- package/dist/src/services/crystallization/crystallization-types.js +299 -0
- package/dist/src/services/crystallization/evidence-brief-builder.d.ts +237 -0
- package/dist/src/services/crystallization/evidence-brief-builder.js +240 -0
- package/dist/src/services/dispatch/slice-dag.d.ts +2 -2
- package/dist/src/services/dispatch/test-tool-detection.d.ts +1 -1
- package/dist/src/services/dispatch/test-tool-detection.js +1 -1
- package/dist/src/services/evolution/evolution-service.d.ts +137 -0
- package/dist/src/services/evolution/evolution-service.js +281 -0
- package/dist/src/services/evolution/evolution-store.d.ts +57 -0
- package/dist/src/services/evolution/evolution-store.js +232 -0
- package/dist/src/services/evolution/evolution-types.d.ts +706 -0
- package/dist/src/services/evolution/evolution-types.js +286 -0
- package/dist/src/services/evolution/independent-evaluator-runner.d.ts +90 -0
- package/dist/src/services/evolution/independent-evaluator-runner.js +106 -0
- package/dist/src/services/evolution/regression-skeptic-runner.d.ts +64 -0
- package/dist/src/services/evolution/regression-skeptic-runner.js +113 -0
- package/dist/src/services/feedback/feedback-promotion-service.js +2 -2
- package/dist/src/services/job/job-progress-store.d.ts +1 -1
- package/dist/src/services/job/job-progress-store.js +1 -1
- package/dist/src/services/loop/loop-bee-relation-service.d.ts +107 -0
- package/dist/src/services/loop/loop-bee-relation-service.js +211 -0
- package/dist/src/services/loop/loop-bee-relation-store.d.ts +53 -0
- package/dist/src/services/loop/loop-bee-relation-store.js +130 -0
- package/dist/src/services/loop/loop-bee-relation-types.d.ts +118 -0
- package/dist/src/services/loop/loop-bee-relation-types.js +114 -0
- package/dist/src/services/loop/loop-release-service.d.ts +46 -0
- package/dist/src/services/loop/loop-release-service.js +66 -0
- package/dist/src/services/loop/loop-release-store.d.ts +55 -0
- package/dist/src/services/loop/loop-release-store.js +134 -0
- package/dist/src/services/loop/loop-release-types.d.ts +250 -0
- package/dist/src/services/loop/loop-release-types.js +162 -0
- package/dist/src/services/preferences/preferences-service.d.ts +1 -1
- package/dist/src/services/preferences/preferences-types.d.ts +1 -1
- package/dist/src/services/rd/standards-diagnostic.d.ts +1 -1
- package/dist/src/services/rd/standards-diagnostic.js +2 -2
- package/dist/src/services/recommendations/capability-seed-items.js +1 -1
- package/dist/src/services/recommendations/capability-seed-mappings.js +1 -1
- package/dist/src/services/recommendations/recommendation-service.js +1 -1
- package/dist/src/services/refactor/refactor-service.d.ts +1 -1
- package/dist/src/services/share/bundle-reader.d.ts +100 -0
- package/dist/src/services/share/bundle-reader.js +434 -0
- package/dist/src/services/share/bundle-types.d.ts +392 -0
- package/dist/src/services/share/bundle-types.js +318 -0
- package/dist/src/services/share/bundle-writer.d.ts +97 -0
- package/dist/src/services/share/bundle-writer.js +429 -0
- package/dist/src/services/share/run-state-contract.d.ts +133 -0
- package/dist/src/services/share/run-state-contract.js +138 -0
- package/dist/src/services/signal/cancel-handler.js +3 -3
- package/dist/src/services/skill/resume-detector.js +1 -1
- package/dist/src/services/skillhub/release-import.js +7 -2
- package/dist/src/services/skillhub/release-retain.js +6 -1
- package/dist/src/services/skillhub/sqlite-store.js +17 -1
- package/dist/src/services/skillhub/types.d.ts +10 -0
- package/dist/src/services/skills/skill-presence-service.d.ts +1 -1
- package/dist/src/services/standards/loop-engineering-lint.d.ts +38 -0
- package/dist/src/services/standards/loop-engineering-lint.js +101 -0
- package/dist/src/services/standards/loop-engineering-readiness-lint.d.ts +111 -0
- package/dist/src/services/standards/loop-engineering-readiness-lint.js +248 -0
- package/dist/src/services/upgrade/1x-detector-service.js +1 -1
- package/dist/src/services/workflow/pipeline-verify-service.d.ts +1 -1
- package/dist/src/services/workflow/pipeline-verify-service.js +3 -3
- package/dist/src/services/workflow/workflow-autonomous-service.d.ts +3 -3
- package/dist/src/services/workflow/workflow-autonomous-service.js +2 -2
- package/dist/src/services/workflow/workflow-router-service.d.ts +6 -6
- package/dist/src/services/workflow/workflow-router-service.js +33 -33
- package/dist/src/services/workspace/claude-settings-template.d.ts +1 -1
- package/dist/src/services/workspace/claude-settings-template.js +5 -5
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/examples/video-demo/README.md +57 -0
- package/examples/video-demo/out/en-closing-960.png +0 -0
- package/examples/video-demo/out/zh-closing-960.png +0 -0
- package/examples/video-demo/preview/peaks-loop-demo-en.mp4 +0 -0
- package/examples/video-demo/preview/peaks-loop-demo.mp4 +0 -0
- package/output-styles/peaks-skill-swarm.md +3 -3
- package/package.json +6 -2
- package/skills/bee/peaks-qa/SKILL.md +1 -1
- package/skills/bee/peaks-qa/references/artifact-per-request.md +3 -3
- package/skills/bee/peaks-qa/references/qa-sub-agent-dispatch.md +6 -6
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +1 -1
- package/skills/bee/peaks-rd/references/rd-runbook.md +1 -1
- package/skills/bee/peaks-rd/references/rd-sub-agent-dispatch.md +11 -11
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +1 -1
- package/skills/bee/peaks-sc/SKILL.md +1 -1
- package/skills/bee/peaks-txt/SKILL.md +3 -3
- package/skills/bee/peaks-ui/SKILL.md +8 -8
- package/skills/peaks-code/SKILL.md +34 -21
- package/skills/peaks-code/references/anchoring-and-session-info.md +2 -2
- package/skills/peaks-code/references/artifact-contracts.md +1 -1
- package/skills/peaks-code/references/boundaries.md +3 -3
- package/skills/peaks-code/references/codegraph-orchestration.md +1 -1
- package/skills/peaks-code/references/dag-orchestrator.md +7 -7
- package/skills/peaks-code/references/external-references.md +2 -2
- package/skills/peaks-code/references/fanout-mandatory.md +3 -3
- package/skills/peaks-code/references/fast-mode.md +2 -2
- package/skills/peaks-code/references/frontend-only-mode.md +9 -9
- package/skills/peaks-code/references/job-loop.md +1 -1
- package/skills/peaks-code/references/local-artifact-workspace.md +2 -2
- package/skills/peaks-code/references/micro-cycle.md +14 -14
- package/skills/peaks-code/references/mode-selection-with-stale-presence.md +5 -5
- package/skills/peaks-code/references/mode-selection.md +3 -3
- package/skills/peaks-code/references/openspec-workflow.md +16 -16
- package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
- package/skills/peaks-code/references/quality-gate-cheatsheet.md +1 -1
- package/skills/peaks-code/references/refactor-mode.md +1 -1
- package/skills/peaks-code/references/runbook.md +15 -15
- package/skills/peaks-code/references/step-0-55-1x-detection.md +1 -1
- package/skills/peaks-code/references/step-0-8-gate.md +11 -11
- package/skills/peaks-code/references/sub-agent-dispatch.md +8 -8
- package/skills/peaks-code/references/swarm-dispatch-contract.md +17 -17
- package/skills/peaks-code/references/workflow-gates-and-types.md +2 -2
- package/skills/peaks-code/references/workflow.md +5 -5
- package/skills/peaks-content/SKILL.md +272 -0
- package/skills/peaks-issue-fix-orchestrator/SKILL.md +307 -0
- package/skills/peaks-resume/SKILL.md +2 -2
- package/skills/peaks-status/SKILL.md +2 -2
- package/skills/peaks-test/SKILL.md +2 -2
- /package/dist/src/services/{solo → code}/batch-heartbeat-poller.d.ts +0 -0
- /package/dist/src/services/{solo → code}/batch-heartbeat-poller.js +0 -0
- /package/dist/src/services/{solo → code}/status-line-renderer.d.ts +0 -0
- /package/dist/src/services/{solo → code}/status-line-renderer.js +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* CrystallizationEvent — spec §4.5 / §4.7 / §5.
|
|
4
|
+
*
|
|
5
|
+
* M5 scope: the crystallization_event table + the 4-section
|
|
6
|
+
* evidence_brief projection (spec §4.7 / §10 RL-7). Every
|
|
7
|
+
* user-facing recommendation is gated on the brief being complete;
|
|
8
|
+
* the schema below enforces the 4-section shape at parse time.
|
|
9
|
+
*
|
|
10
|
+
* Hard rules enforced at the boundary (and re-asserted in the
|
|
11
|
+
* service layer):
|
|
12
|
+
*
|
|
13
|
+
* 1. The 4 brief sections (what_happened / why_it_matters /
|
|
14
|
+
* what_learned / what_action) are ALL required. A partial
|
|
15
|
+
* brief is a hard parse error (`MISSING_BRIEF_SECTION`),
|
|
16
|
+
* and `parseCrystallizationEvent` will throw.
|
|
17
|
+
* 2. The trigger enum mirrors spec §4.5 exactly:
|
|
18
|
+
* user_explicit | llm_suggested | success_default_prompt |
|
|
19
|
+
* similar_task_recurrence.
|
|
20
|
+
* 3. The event's own lifecycle_status is ORTHOGONAL to the
|
|
21
|
+
* created/updated loop_release.lifecycle_status — the same
|
|
22
|
+
* asset can have many crystallization events over its life.
|
|
23
|
+
* 4. source_trace_pointers are stable ids (the workflow trace
|
|
24
|
+
* id space), not path strings — the column is JSON-array of
|
|
25
|
+
* strings.
|
|
26
|
+
* 5. The brief section guard is implemented as a `.refine(...)`
|
|
27
|
+
* on the input schema — the canonical 4 keys must be present
|
|
28
|
+
* with non-empty values (the spec mandates 1-2 sentences per
|
|
29
|
+
* section; an empty string would be a degenerate brief).
|
|
30
|
+
*
|
|
31
|
+
* Out of scope (deferred to later slices):
|
|
32
|
+
* - cross-event aggregation (loop crystallized N times).
|
|
33
|
+
* - brief rewrite-on-update semantics (M8 dogfood).
|
|
34
|
+
*/
|
|
35
|
+
export declare const CrystallizationTriggerSchema: z.ZodEnum<["user_explicit", "llm_suggested", "success_default_prompt", "similar_task_recurrence"]>;
|
|
36
|
+
export type CrystallizationTrigger = z.infer<typeof CrystallizationTriggerSchema>;
|
|
37
|
+
export declare const CRYSTALLIZATION_TRIGGERS: readonly CrystallizationTrigger[];
|
|
38
|
+
export declare const CrystallizationEventStatusSchema: z.ZodEnum<["candidate", "stable", "retired"]>;
|
|
39
|
+
export type CrystallizationEventStatus = z.infer<typeof CrystallizationEventStatusSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* EvidenceBrief — the 4-section brief (spec §4.7).
|
|
42
|
+
*
|
|
43
|
+
* Hard shape invariant: ALL FOUR KEYS must be present at parse
|
|
44
|
+
* time, with non-empty NL content. The `.refine(...)` guard is
|
|
45
|
+
* what makes a brief a "brief" — without it, the projection is
|
|
46
|
+
* not a brief and the recommendation MUST NOT be rendered
|
|
47
|
+
* (service layer enforces this; Zod makes it a parse-time
|
|
48
|
+
* guarantee).
|
|
49
|
+
*
|
|
50
|
+
* Field semantics:
|
|
51
|
+
* - what_happened — 1-2 sentence factual account of the run.
|
|
52
|
+
* - why_it_matters — 1-2 sentence explanation of why this is
|
|
53
|
+
* worth promoting.
|
|
54
|
+
* - what_learned — 1-2 sentence learning: failure modes
|
|
55
|
+
* encoded, preferences extracted.
|
|
56
|
+
* - what_action — 1-sentence recommended action with
|
|
57
|
+
* rationale.
|
|
58
|
+
*/
|
|
59
|
+
export declare const EvidenceBriefSchema: z.ZodEffects<z.ZodObject<{
|
|
60
|
+
what_happened: z.ZodString;
|
|
61
|
+
why_it_matters: z.ZodString;
|
|
62
|
+
what_learned: z.ZodString;
|
|
63
|
+
what_action: z.ZodString;
|
|
64
|
+
}, "strict", z.ZodTypeAny, {
|
|
65
|
+
what_happened: string;
|
|
66
|
+
why_it_matters: string;
|
|
67
|
+
what_learned: string;
|
|
68
|
+
what_action: string;
|
|
69
|
+
}, {
|
|
70
|
+
what_happened: string;
|
|
71
|
+
why_it_matters: string;
|
|
72
|
+
what_learned: string;
|
|
73
|
+
what_action: string;
|
|
74
|
+
}>, {
|
|
75
|
+
what_happened: string;
|
|
76
|
+
why_it_matters: string;
|
|
77
|
+
what_learned: string;
|
|
78
|
+
what_action: string;
|
|
79
|
+
}, {
|
|
80
|
+
what_happened: string;
|
|
81
|
+
why_it_matters: string;
|
|
82
|
+
what_learned: string;
|
|
83
|
+
what_action: string;
|
|
84
|
+
}>;
|
|
85
|
+
export type EvidenceBrief = z.infer<typeof EvidenceBriefSchema>;
|
|
86
|
+
/**
|
|
87
|
+
* Helper for downstream code that needs to know whether a parsed
|
|
88
|
+
* brief satisfies the 4-section rule. Useful for the CLI when a
|
|
89
|
+
* payload arrived from the LLM and the brief-section guard has
|
|
90
|
+
* already passed — it returns the section count, always 4 here.
|
|
91
|
+
*/
|
|
92
|
+
export declare function hasAllFourBriefSections(brief: EvidenceBrief): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Zod schema for the create payload (CrystallizationEventInput).
|
|
95
|
+
*
|
|
96
|
+
* Re-validates the evidence_brief inline; calling code may ALSO call
|
|
97
|
+
* `EvidenceBriefSchema.parse` separately if it builds the brief out
|
|
98
|
+
* of band. The CrystallizationEvent input schema wires the same
|
|
99
|
+
* guard so a brief cannot sneak through via a partial-input path.
|
|
100
|
+
*/
|
|
101
|
+
export declare const CrystallizationEventInputSchema: z.ZodObject<{
|
|
102
|
+
trigger: z.ZodEnum<["user_explicit", "llm_suggested", "success_default_prompt", "similar_task_recurrence"]>;
|
|
103
|
+
evidence_brief: z.ZodEffects<z.ZodObject<{
|
|
104
|
+
what_happened: z.ZodString;
|
|
105
|
+
why_it_matters: z.ZodString;
|
|
106
|
+
what_learned: z.ZodString;
|
|
107
|
+
what_action: z.ZodString;
|
|
108
|
+
}, "strict", z.ZodTypeAny, {
|
|
109
|
+
what_happened: string;
|
|
110
|
+
why_it_matters: string;
|
|
111
|
+
what_learned: string;
|
|
112
|
+
what_action: string;
|
|
113
|
+
}, {
|
|
114
|
+
what_happened: string;
|
|
115
|
+
why_it_matters: string;
|
|
116
|
+
what_learned: string;
|
|
117
|
+
what_action: string;
|
|
118
|
+
}>, {
|
|
119
|
+
what_happened: string;
|
|
120
|
+
why_it_matters: string;
|
|
121
|
+
what_learned: string;
|
|
122
|
+
what_action: string;
|
|
123
|
+
}, {
|
|
124
|
+
what_happened: string;
|
|
125
|
+
why_it_matters: string;
|
|
126
|
+
what_learned: string;
|
|
127
|
+
what_action: string;
|
|
128
|
+
}>;
|
|
129
|
+
evidence_bullets: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
130
|
+
source_trace_pointers: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
131
|
+
evaluator_summary: z.ZodDefault<z.ZodString>;
|
|
132
|
+
user_decision_summary: z.ZodDefault<z.ZodString>;
|
|
133
|
+
created_loop_release_id: z.ZodOptional<z.ZodString>;
|
|
134
|
+
updated_loop_release_id: z.ZodOptional<z.ZodString>;
|
|
135
|
+
created_bee_release_id: z.ZodOptional<z.ZodNumber>;
|
|
136
|
+
updated_bee_release_id: z.ZodOptional<z.ZodNumber>;
|
|
137
|
+
lifecycle_status: z.ZodDefault<z.ZodEnum<["candidate", "stable", "retired"]>>;
|
|
138
|
+
}, "strip", z.ZodTypeAny, {
|
|
139
|
+
lifecycle_status: "candidate" | "stable" | "retired";
|
|
140
|
+
trigger: "user_explicit" | "llm_suggested" | "success_default_prompt" | "similar_task_recurrence";
|
|
141
|
+
evidence_brief: {
|
|
142
|
+
what_happened: string;
|
|
143
|
+
why_it_matters: string;
|
|
144
|
+
what_learned: string;
|
|
145
|
+
what_action: string;
|
|
146
|
+
};
|
|
147
|
+
evidence_bullets: string[];
|
|
148
|
+
source_trace_pointers: string[];
|
|
149
|
+
evaluator_summary: string;
|
|
150
|
+
user_decision_summary: string;
|
|
151
|
+
created_loop_release_id?: string | undefined;
|
|
152
|
+
updated_loop_release_id?: string | undefined;
|
|
153
|
+
created_bee_release_id?: number | undefined;
|
|
154
|
+
updated_bee_release_id?: number | undefined;
|
|
155
|
+
}, {
|
|
156
|
+
trigger: "user_explicit" | "llm_suggested" | "success_default_prompt" | "similar_task_recurrence";
|
|
157
|
+
evidence_brief: {
|
|
158
|
+
what_happened: string;
|
|
159
|
+
why_it_matters: string;
|
|
160
|
+
what_learned: string;
|
|
161
|
+
what_action: string;
|
|
162
|
+
};
|
|
163
|
+
lifecycle_status?: "candidate" | "stable" | "retired" | undefined;
|
|
164
|
+
evidence_bullets?: string[] | undefined;
|
|
165
|
+
source_trace_pointers?: string[] | undefined;
|
|
166
|
+
evaluator_summary?: string | undefined;
|
|
167
|
+
user_decision_summary?: string | undefined;
|
|
168
|
+
created_loop_release_id?: string | undefined;
|
|
169
|
+
updated_loop_release_id?: string | undefined;
|
|
170
|
+
created_bee_release_id?: number | undefined;
|
|
171
|
+
updated_bee_release_id?: number | undefined;
|
|
172
|
+
}>;
|
|
173
|
+
export type CrystallizationEventInput = z.input<typeof CrystallizationEventInputSchema>;
|
|
174
|
+
/**
|
|
175
|
+
* Full persisted row schema. Adds `id` (the event id) and stamps
|
|
176
|
+
* `schema_version` to the fixed literal `peaks.crystallization/1`.
|
|
177
|
+
*
|
|
178
|
+
* Brief section guard is INHERITED from `EvidenceBriefSchema.refine`
|
|
179
|
+
* because `EvidenceBriefSchema` is embedded; the service layer still
|
|
180
|
+
* re-asserts the guard so a hand-crafted row cannot bypass Zod at
|
|
181
|
+
* the store boundary.
|
|
182
|
+
*/
|
|
183
|
+
export declare const CrystallizationEventSchema: z.ZodObject<{
|
|
184
|
+
trigger: z.ZodEnum<["user_explicit", "llm_suggested", "success_default_prompt", "similar_task_recurrence"]>;
|
|
185
|
+
evidence_brief: z.ZodEffects<z.ZodObject<{
|
|
186
|
+
what_happened: z.ZodString;
|
|
187
|
+
why_it_matters: z.ZodString;
|
|
188
|
+
what_learned: z.ZodString;
|
|
189
|
+
what_action: z.ZodString;
|
|
190
|
+
}, "strict", z.ZodTypeAny, {
|
|
191
|
+
what_happened: string;
|
|
192
|
+
why_it_matters: string;
|
|
193
|
+
what_learned: string;
|
|
194
|
+
what_action: string;
|
|
195
|
+
}, {
|
|
196
|
+
what_happened: string;
|
|
197
|
+
why_it_matters: string;
|
|
198
|
+
what_learned: string;
|
|
199
|
+
what_action: string;
|
|
200
|
+
}>, {
|
|
201
|
+
what_happened: string;
|
|
202
|
+
why_it_matters: string;
|
|
203
|
+
what_learned: string;
|
|
204
|
+
what_action: string;
|
|
205
|
+
}, {
|
|
206
|
+
what_happened: string;
|
|
207
|
+
why_it_matters: string;
|
|
208
|
+
what_learned: string;
|
|
209
|
+
what_action: string;
|
|
210
|
+
}>;
|
|
211
|
+
evidence_bullets: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
212
|
+
source_trace_pointers: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
213
|
+
evaluator_summary: z.ZodDefault<z.ZodString>;
|
|
214
|
+
user_decision_summary: z.ZodDefault<z.ZodString>;
|
|
215
|
+
created_loop_release_id: z.ZodOptional<z.ZodString>;
|
|
216
|
+
updated_loop_release_id: z.ZodOptional<z.ZodString>;
|
|
217
|
+
created_bee_release_id: z.ZodOptional<z.ZodNumber>;
|
|
218
|
+
updated_bee_release_id: z.ZodOptional<z.ZodNumber>;
|
|
219
|
+
lifecycle_status: z.ZodDefault<z.ZodEnum<["candidate", "stable", "retired"]>>;
|
|
220
|
+
} & {
|
|
221
|
+
id: z.ZodString;
|
|
222
|
+
schema_version: z.ZodDefault<z.ZodLiteral<"peaks.crystallization/1">>;
|
|
223
|
+
created_at: z.ZodString;
|
|
224
|
+
}, "strip", z.ZodTypeAny, {
|
|
225
|
+
schema_version: "peaks.crystallization/1";
|
|
226
|
+
id: string;
|
|
227
|
+
lifecycle_status: "candidate" | "stable" | "retired";
|
|
228
|
+
trigger: "user_explicit" | "llm_suggested" | "success_default_prompt" | "similar_task_recurrence";
|
|
229
|
+
evidence_brief: {
|
|
230
|
+
what_happened: string;
|
|
231
|
+
why_it_matters: string;
|
|
232
|
+
what_learned: string;
|
|
233
|
+
what_action: string;
|
|
234
|
+
};
|
|
235
|
+
evidence_bullets: string[];
|
|
236
|
+
source_trace_pointers: string[];
|
|
237
|
+
evaluator_summary: string;
|
|
238
|
+
user_decision_summary: string;
|
|
239
|
+
created_at: string;
|
|
240
|
+
created_loop_release_id?: string | undefined;
|
|
241
|
+
updated_loop_release_id?: string | undefined;
|
|
242
|
+
created_bee_release_id?: number | undefined;
|
|
243
|
+
updated_bee_release_id?: number | undefined;
|
|
244
|
+
}, {
|
|
245
|
+
id: string;
|
|
246
|
+
trigger: "user_explicit" | "llm_suggested" | "success_default_prompt" | "similar_task_recurrence";
|
|
247
|
+
evidence_brief: {
|
|
248
|
+
what_happened: string;
|
|
249
|
+
why_it_matters: string;
|
|
250
|
+
what_learned: string;
|
|
251
|
+
what_action: string;
|
|
252
|
+
};
|
|
253
|
+
created_at: string;
|
|
254
|
+
schema_version?: "peaks.crystallization/1" | undefined;
|
|
255
|
+
lifecycle_status?: "candidate" | "stable" | "retired" | undefined;
|
|
256
|
+
evidence_bullets?: string[] | undefined;
|
|
257
|
+
source_trace_pointers?: string[] | undefined;
|
|
258
|
+
evaluator_summary?: string | undefined;
|
|
259
|
+
user_decision_summary?: string | undefined;
|
|
260
|
+
created_loop_release_id?: string | undefined;
|
|
261
|
+
updated_loop_release_id?: string | undefined;
|
|
262
|
+
created_bee_release_id?: number | undefined;
|
|
263
|
+
updated_bee_release_id?: number | undefined;
|
|
264
|
+
}>;
|
|
265
|
+
export type CrystallizationEvent = z.infer<typeof CrystallizationEventSchema>;
|
|
266
|
+
/**
|
|
267
|
+
* Convenience: strict-parse an unknown payload into a
|
|
268
|
+
* CrystallizationEvent row. Throws ZodError on failure — including
|
|
269
|
+
* when the brief is missing a section (the `.refine` guard fires).
|
|
270
|
+
*
|
|
271
|
+
* Use this at every parse boundary (CLI input, store insertion).
|
|
272
|
+
*/
|
|
273
|
+
export declare function parseCrystallizationEvent(input: unknown): CrystallizationEvent;
|
|
274
|
+
/**
|
|
275
|
+
* Convenience: strict-parse an unknown payload as a standalone
|
|
276
|
+
* EvidenceBrief. Throws ZodError on failure.
|
|
277
|
+
*/
|
|
278
|
+
export declare function parseEvidenceBrief(input: unknown): EvidenceBrief;
|
|
279
|
+
/**
|
|
280
|
+
* Safe-parse twin of parseCrystallizationEvent. Returns a Result-like
|
|
281
|
+
* envelope so callers (CLI / tests) can render findings without
|
|
282
|
+
* try/catch noise. The `code` field is `MISSING_BRIEF_SECTION` when
|
|
283
|
+
* the brief refine guard trips OR when ANY brief-section key is
|
|
284
|
+
* missing (spec §10 RL-7) — distinguishes brief failures from
|
|
285
|
+
* generic validation errors.
|
|
286
|
+
*/
|
|
287
|
+
export declare function safeParseCrystallizationEvent(input: unknown): {
|
|
288
|
+
ok: true;
|
|
289
|
+
row: CrystallizationEvent;
|
|
290
|
+
} | {
|
|
291
|
+
ok: false;
|
|
292
|
+
code?: "MISSING_BRIEF_SECTION";
|
|
293
|
+
findings: Array<{
|
|
294
|
+
path: string;
|
|
295
|
+
message: string;
|
|
296
|
+
}>;
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Safe-parse twin of parseEvidenceBrief. Same Result-like envelope
|
|
300
|
+
* shape; same MISSING_BRIEF_SECTION code mapping (covers both
|
|
301
|
+
* refine-guard failures and per-section missing/empty failures).
|
|
302
|
+
*/
|
|
303
|
+
export declare function safeParseEvidenceBrief(input: unknown): {
|
|
304
|
+
ok: true;
|
|
305
|
+
row: EvidenceBrief;
|
|
306
|
+
} | {
|
|
307
|
+
ok: false;
|
|
308
|
+
code?: "MISSING_BRIEF_SECTION";
|
|
309
|
+
findings: Array<{
|
|
310
|
+
path: string;
|
|
311
|
+
message: string;
|
|
312
|
+
}>;
|
|
313
|
+
};
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* CrystallizationEvent — spec §4.5 / §4.7 / §5.
|
|
4
|
+
*
|
|
5
|
+
* M5 scope: the crystallization_event table + the 4-section
|
|
6
|
+
* evidence_brief projection (spec §4.7 / §10 RL-7). Every
|
|
7
|
+
* user-facing recommendation is gated on the brief being complete;
|
|
8
|
+
* the schema below enforces the 4-section shape at parse time.
|
|
9
|
+
*
|
|
10
|
+
* Hard rules enforced at the boundary (and re-asserted in the
|
|
11
|
+
* service layer):
|
|
12
|
+
*
|
|
13
|
+
* 1. The 4 brief sections (what_happened / why_it_matters /
|
|
14
|
+
* what_learned / what_action) are ALL required. A partial
|
|
15
|
+
* brief is a hard parse error (`MISSING_BRIEF_SECTION`),
|
|
16
|
+
* and `parseCrystallizationEvent` will throw.
|
|
17
|
+
* 2. The trigger enum mirrors spec §4.5 exactly:
|
|
18
|
+
* user_explicit | llm_suggested | success_default_prompt |
|
|
19
|
+
* similar_task_recurrence.
|
|
20
|
+
* 3. The event's own lifecycle_status is ORTHOGONAL to the
|
|
21
|
+
* created/updated loop_release.lifecycle_status — the same
|
|
22
|
+
* asset can have many crystallization events over its life.
|
|
23
|
+
* 4. source_trace_pointers are stable ids (the workflow trace
|
|
24
|
+
* id space), not path strings — the column is JSON-array of
|
|
25
|
+
* strings.
|
|
26
|
+
* 5. The brief section guard is implemented as a `.refine(...)`
|
|
27
|
+
* on the input schema — the canonical 4 keys must be present
|
|
28
|
+
* with non-empty values (the spec mandates 1-2 sentences per
|
|
29
|
+
* section; an empty string would be a degenerate brief).
|
|
30
|
+
*
|
|
31
|
+
* Out of scope (deferred to later slices):
|
|
32
|
+
* - cross-event aggregation (loop crystallized N times).
|
|
33
|
+
* - brief rewrite-on-update semantics (M8 dogfood).
|
|
34
|
+
*/
|
|
35
|
+
/* ---------------------------------------------------------------------- */
|
|
36
|
+
/* Trigger — §4.5 / §5.4 */
|
|
37
|
+
/* ---------------------------------------------------------------------- */
|
|
38
|
+
export const CrystallizationTriggerSchema = z.enum([
|
|
39
|
+
"user_explicit",
|
|
40
|
+
"llm_suggested",
|
|
41
|
+
"success_default_prompt",
|
|
42
|
+
"similar_task_recurrence",
|
|
43
|
+
]);
|
|
44
|
+
export const CRYSTALLIZATION_TRIGGERS = [
|
|
45
|
+
"user_explicit",
|
|
46
|
+
"llm_suggested",
|
|
47
|
+
"success_default_prompt",
|
|
48
|
+
"similar_task_recurrence",
|
|
49
|
+
];
|
|
50
|
+
/* ---------------------------------------------------------------------- */
|
|
51
|
+
/* Lifecycle status — §5.6 (event-scoped, not asset-scoped) */
|
|
52
|
+
/* ---------------------------------------------------------------------- */
|
|
53
|
+
export const CrystallizationEventStatusSchema = z.enum([
|
|
54
|
+
"candidate",
|
|
55
|
+
"stable",
|
|
56
|
+
"retired",
|
|
57
|
+
]);
|
|
58
|
+
/* ---------------------------------------------------------------------- */
|
|
59
|
+
/* EvidenceBrief — §4.7 / §10 RL-7. REQUIRED 4-section shape. */
|
|
60
|
+
/* ---------------------------------------------------------------------- */
|
|
61
|
+
/**
|
|
62
|
+
* The four brief sections. Spec §4.7 lists them as NL short
|
|
63
|
+
* sections; the service layer mandates 1-2 sentences (what_action
|
|
64
|
+
* is a single sentence per the spec). The Zod constraints below
|
|
65
|
+
* enforce NON-EMPTY strings. The brief-section guard (see
|
|
66
|
+
* `EvidenceBriefSchema.refine` below) rejects any payload missing
|
|
67
|
+
* one of the four keys, regardless of section content — it is
|
|
68
|
+
* the canonical hard gate per spec §10 RL-7.
|
|
69
|
+
*/
|
|
70
|
+
const BriefSectionSchema = z
|
|
71
|
+
.string()
|
|
72
|
+
.trim()
|
|
73
|
+
.min(1, "brief section must be non-empty natural language")
|
|
74
|
+
.max(4000);
|
|
75
|
+
/**
|
|
76
|
+
* EvidenceBrief — the 4-section brief (spec §4.7).
|
|
77
|
+
*
|
|
78
|
+
* Hard shape invariant: ALL FOUR KEYS must be present at parse
|
|
79
|
+
* time, with non-empty NL content. The `.refine(...)` guard is
|
|
80
|
+
* what makes a brief a "brief" — without it, the projection is
|
|
81
|
+
* not a brief and the recommendation MUST NOT be rendered
|
|
82
|
+
* (service layer enforces this; Zod makes it a parse-time
|
|
83
|
+
* guarantee).
|
|
84
|
+
*
|
|
85
|
+
* Field semantics:
|
|
86
|
+
* - what_happened — 1-2 sentence factual account of the run.
|
|
87
|
+
* - why_it_matters — 1-2 sentence explanation of why this is
|
|
88
|
+
* worth promoting.
|
|
89
|
+
* - what_learned — 1-2 sentence learning: failure modes
|
|
90
|
+
* encoded, preferences extracted.
|
|
91
|
+
* - what_action — 1-sentence recommended action with
|
|
92
|
+
* rationale.
|
|
93
|
+
*/
|
|
94
|
+
export const EvidenceBriefSchema = z
|
|
95
|
+
.object({
|
|
96
|
+
what_happened: BriefSectionSchema,
|
|
97
|
+
why_it_matters: BriefSectionSchema,
|
|
98
|
+
what_learned: BriefSectionSchema,
|
|
99
|
+
what_action: BriefSectionSchema,
|
|
100
|
+
})
|
|
101
|
+
.strict()
|
|
102
|
+
.refine((b) => typeof b.what_happened === "string" &&
|
|
103
|
+
b.what_happened.trim().length > 0 &&
|
|
104
|
+
typeof b.why_it_matters === "string" &&
|
|
105
|
+
b.why_it_matters.trim().length > 0 &&
|
|
106
|
+
typeof b.what_learned === "string" &&
|
|
107
|
+
b.what_learned.trim().length > 0 &&
|
|
108
|
+
typeof b.what_action === "string" &&
|
|
109
|
+
b.what_action.trim().length > 0, {
|
|
110
|
+
message: "evidence_brief must contain all 4 sections (what_happened, why_it_matters, what_learned, what_action) with non-empty content (spec §4.7 / RL-7)",
|
|
111
|
+
path: [],
|
|
112
|
+
});
|
|
113
|
+
/**
|
|
114
|
+
* Helper for downstream code that needs to know whether a parsed
|
|
115
|
+
* brief satisfies the 4-section rule. Useful for the CLI when a
|
|
116
|
+
* payload arrived from the LLM and the brief-section guard has
|
|
117
|
+
* already passed — it returns the section count, always 4 here.
|
|
118
|
+
*/
|
|
119
|
+
export function hasAllFourBriefSections(brief) {
|
|
120
|
+
return (typeof brief.what_happened === "string" &&
|
|
121
|
+
brief.what_happened.trim().length > 0 &&
|
|
122
|
+
typeof brief.why_it_matters === "string" &&
|
|
123
|
+
brief.why_it_matters.trim().length > 0 &&
|
|
124
|
+
typeof brief.what_learned === "string" &&
|
|
125
|
+
brief.what_learned.trim().length > 0 &&
|
|
126
|
+
typeof brief.what_action === "string" &&
|
|
127
|
+
brief.what_action.trim().length > 0);
|
|
128
|
+
}
|
|
129
|
+
/* ---------------------------------------------------------------------- */
|
|
130
|
+
/* CrystallizationEvent — §4.5 */
|
|
131
|
+
/* ---------------------------------------------------------------------- */
|
|
132
|
+
/**
|
|
133
|
+
* Structurally referenced columns: pointers to optional loop_release /
|
|
134
|
+
* bee_release rows. All four are optional — a crystallization event
|
|
135
|
+
* may exist for "trace only" / "discard" choices that touch no durable
|
|
136
|
+
* asset. Set is governed by the service layer's pre-run gate.
|
|
137
|
+
*/
|
|
138
|
+
const OptionalLoopId = z
|
|
139
|
+
.string()
|
|
140
|
+
.min(1)
|
|
141
|
+
.max(64)
|
|
142
|
+
.regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, {
|
|
143
|
+
message: "loop_release_id must be kebab-case starting with a lowercase letter",
|
|
144
|
+
})
|
|
145
|
+
.optional();
|
|
146
|
+
const OptionalBeeId = z
|
|
147
|
+
.number()
|
|
148
|
+
.int()
|
|
149
|
+
.positive()
|
|
150
|
+
.max(2 ** 31 - 1, "bee_release_id out of 32-bit range")
|
|
151
|
+
.optional();
|
|
152
|
+
/**
|
|
153
|
+
* Zod schema for the create payload (CrystallizationEventInput).
|
|
154
|
+
*
|
|
155
|
+
* Re-validates the evidence_brief inline; calling code may ALSO call
|
|
156
|
+
* `EvidenceBriefSchema.parse` separately if it builds the brief out
|
|
157
|
+
* of band. The CrystallizationEvent input schema wires the same
|
|
158
|
+
* guard so a brief cannot sneak through via a partial-input path.
|
|
159
|
+
*/
|
|
160
|
+
export const CrystallizationEventInputSchema = z.object({
|
|
161
|
+
trigger: CrystallizationTriggerSchema,
|
|
162
|
+
evidence_brief: EvidenceBriefSchema,
|
|
163
|
+
evidence_bullets: z
|
|
164
|
+
.array(z.string().trim().min(1).max(1000))
|
|
165
|
+
.default([]),
|
|
166
|
+
source_trace_pointers: z
|
|
167
|
+
.array(z.string().trim().min(1).max(256))
|
|
168
|
+
.default([]),
|
|
169
|
+
evaluator_summary: z
|
|
170
|
+
.string()
|
|
171
|
+
.trim()
|
|
172
|
+
.max(4000)
|
|
173
|
+
.default(""),
|
|
174
|
+
user_decision_summary: z
|
|
175
|
+
.string()
|
|
176
|
+
.trim()
|
|
177
|
+
.max(4000)
|
|
178
|
+
.default(""),
|
|
179
|
+
created_loop_release_id: OptionalLoopId,
|
|
180
|
+
updated_loop_release_id: OptionalLoopId,
|
|
181
|
+
created_bee_release_id: OptionalBeeId,
|
|
182
|
+
updated_bee_release_id: OptionalBeeId,
|
|
183
|
+
lifecycle_status: CrystallizationEventStatusSchema.default("candidate"),
|
|
184
|
+
});
|
|
185
|
+
/**
|
|
186
|
+
* Full persisted row schema. Adds `id` (the event id) and stamps
|
|
187
|
+
* `schema_version` to the fixed literal `peaks.crystallization/1`.
|
|
188
|
+
*
|
|
189
|
+
* Brief section guard is INHERITED from `EvidenceBriefSchema.refine`
|
|
190
|
+
* because `EvidenceBriefSchema` is embedded; the service layer still
|
|
191
|
+
* re-asserts the guard so a hand-crafted row cannot bypass Zod at
|
|
192
|
+
* the store boundary.
|
|
193
|
+
*/
|
|
194
|
+
export const CrystallizationEventSchema = CrystallizationEventInputSchema.extend({
|
|
195
|
+
id: z
|
|
196
|
+
.string()
|
|
197
|
+
.min(1)
|
|
198
|
+
.max(128)
|
|
199
|
+
.regex(/^crys-[0-9a-f]{8,}$/, {
|
|
200
|
+
message: "id must start with 'crys-' followed by a hex suffix (spec §4.5)",
|
|
201
|
+
}),
|
|
202
|
+
schema_version: z
|
|
203
|
+
.literal("peaks.crystallization/1")
|
|
204
|
+
.default("peaks.crystallization/1"),
|
|
205
|
+
created_at: z.string().datetime(),
|
|
206
|
+
});
|
|
207
|
+
/**
|
|
208
|
+
* Convenience: strict-parse an unknown payload into a
|
|
209
|
+
* CrystallizationEvent row. Throws ZodError on failure — including
|
|
210
|
+
* when the brief is missing a section (the `.refine` guard fires).
|
|
211
|
+
*
|
|
212
|
+
* Use this at every parse boundary (CLI input, store insertion).
|
|
213
|
+
*/
|
|
214
|
+
export function parseCrystallizationEvent(input) {
|
|
215
|
+
return CrystallizationEventSchema.parse(input);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Convenience: strict-parse an unknown payload as a standalone
|
|
219
|
+
* EvidenceBrief. Throws ZodError on failure.
|
|
220
|
+
*/
|
|
221
|
+
export function parseEvidenceBrief(input) {
|
|
222
|
+
return EvidenceBriefSchema.parse(input);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Safe-parse twin of parseCrystallizationEvent. Returns a Result-like
|
|
226
|
+
* envelope so callers (CLI / tests) can render findings without
|
|
227
|
+
* try/catch noise. The `code` field is `MISSING_BRIEF_SECTION` when
|
|
228
|
+
* the brief refine guard trips OR when ANY brief-section key is
|
|
229
|
+
* missing (spec §10 RL-7) — distinguishes brief failures from
|
|
230
|
+
* generic validation errors.
|
|
231
|
+
*/
|
|
232
|
+
export function safeParseCrystallizationEvent(input) {
|
|
233
|
+
const r = CrystallizationEventSchema.safeParse(input);
|
|
234
|
+
if (r.success)
|
|
235
|
+
return { ok: true, row: r.data };
|
|
236
|
+
const findings = r.error.issues.map((i) => ({
|
|
237
|
+
path: i.path.join("."),
|
|
238
|
+
message: i.message,
|
|
239
|
+
}));
|
|
240
|
+
const isBrief = isBriefSectionFailure(r.error.issues);
|
|
241
|
+
return isBrief
|
|
242
|
+
? { ok: false, code: "MISSING_BRIEF_SECTION", findings }
|
|
243
|
+
: { ok: false, findings };
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Safe-parse twin of parseEvidenceBrief. Same Result-like envelope
|
|
247
|
+
* shape; same MISSING_BRIEF_SECTION code mapping (covers both
|
|
248
|
+
* refine-guard failures and per-section missing/empty failures).
|
|
249
|
+
*/
|
|
250
|
+
export function safeParseEvidenceBrief(input) {
|
|
251
|
+
const r = EvidenceBriefSchema.safeParse(input);
|
|
252
|
+
if (r.success)
|
|
253
|
+
return { ok: true, row: r.data };
|
|
254
|
+
const findings = r.error.issues.map((i) => ({
|
|
255
|
+
path: i.path.join("."),
|
|
256
|
+
message: i.message,
|
|
257
|
+
}));
|
|
258
|
+
const isBrief = isBriefSectionFailure(r.error.issues);
|
|
259
|
+
return isBrief
|
|
260
|
+
? { ok: false, code: "MISSING_BRIEF_SECTION", findings }
|
|
261
|
+
: { ok: false, findings };
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Decide whether a ZodIssue[] is a "brief section" failure: every
|
|
265
|
+
* issue points at one of the four required keys (the section is
|
|
266
|
+
* missing, or empty after trim), OR the explicit refine-guard issue
|
|
267
|
+
* is present. Used by both `safeParseCrystallizationEvent` and
|
|
268
|
+
* `safeParseEvidenceBrief` so callers see a single
|
|
269
|
+
* `MISSING_BRIEF_SECTION` code regardless of which Zod boundary
|
|
270
|
+
* caught the failure.
|
|
271
|
+
*/
|
|
272
|
+
function isBriefSectionFailure(issues) {
|
|
273
|
+
if (issues.length === 0)
|
|
274
|
+
return false;
|
|
275
|
+
// Explicit refine guard (EvidenceBriefSchema.refine message).
|
|
276
|
+
if (issues.some((i) => i.message.includes("evidence_brief must contain all 4 sections"))) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
// Per-section Zod failures (min(1) on each section key, or
|
|
280
|
+
// too_small, or invalid_type). Each issue's path will be either
|
|
281
|
+
// the section key (`what_happened`) OR the parent `evidence_brief`
|
|
282
|
+
// — Zod reports missing-key issues against the parent object.
|
|
283
|
+
return issues.every((i) => {
|
|
284
|
+
const path = i.path.join(".");
|
|
285
|
+
if (path === "evidence_brief")
|
|
286
|
+
return true;
|
|
287
|
+
if (i.path.length >= 2 && i.path[0] === "evidence_brief") {
|
|
288
|
+
const key = i.path[i.path.length - 1];
|
|
289
|
+
return (key === "what_happened" ||
|
|
290
|
+
key === "why_it_matters" ||
|
|
291
|
+
key === "what_learned" ||
|
|
292
|
+
key === "what_action");
|
|
293
|
+
}
|
|
294
|
+
return (path === "what_happened" ||
|
|
295
|
+
path === "why_it_matters" ||
|
|
296
|
+
path === "what_learned" ||
|
|
297
|
+
path === "what_action");
|
|
298
|
+
});
|
|
299
|
+
}
|