yam-flow 0.1.7 → 0.1.9

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.
@@ -17,6 +17,8 @@ export type EvidenceKind = 'command' | 'evidence' | 'visual' | 'runtime' | 'skip
17
17
  export type RuntimeSubsystem = 'real_runtime' | 'tmux_physical' | 'process_cleanup' | 'browser_visual' | 'db_safety';
18
18
  export type RuntimeBackend = 'none' | 'in_app_browser' | 'playwright' | 'terminal' | 'tmux' | 'zellij' | 'unknown';
19
19
  export type RuntimeClaim = 'not_started' | 'started' | 'observed' | 'stopped' | 'cleanup_verified' | 'blocked' | 'unknown';
20
+ export type LoopStageStatus = 'passed' | 'failed' | 'blocked' | 'skipped' | 'partial' | 'pending' | 'recorded';
21
+ export type ToolIntent = 'read_only' | 'write' | 'destructive' | 'runtime' | 'visual' | 'publish';
20
22
 
21
23
  export interface SafetyHit {
22
24
  level: SafetyLevel;
@@ -149,6 +151,54 @@ export interface RuntimeBackendEvidence {
149
151
  truth_status: TruthStatus;
150
152
  }
151
153
 
154
+ export interface StudyNoteSection {
155
+ code: string;
156
+ role: string;
157
+ symptom?: string;
158
+ summary?: string;
159
+ truth_status: TruthStatus;
160
+ }
161
+
162
+ export interface StudyNote {
163
+ schema: 'yam.study-note.v1';
164
+ problem: StudyNoteSection;
165
+ change: StudyNoteSection;
166
+ why_it_matters: string;
167
+ learning_note: string;
168
+ limits: string[];
169
+ truth_status: TruthStatus;
170
+ }
171
+
172
+ export interface LoopStage {
173
+ id: string;
174
+ status: LoopStageStatus;
175
+ note: string;
176
+ truth_status: TruthStatus;
177
+ }
178
+
179
+ export interface LoopReport {
180
+ schema: 'yam.loop-report.v1';
181
+ generated_at: string;
182
+ route: string;
183
+ intent: string;
184
+ loop_kind: string;
185
+ stages: LoopStage[];
186
+ evidence: string[];
187
+ blockers: string[];
188
+ truth_status: TruthStatus;
189
+ intent_label: ToolIntent;
190
+ tool_intent: ToolIntent;
191
+ next_action: string;
192
+ fix_first_items: string[];
193
+ remaining_tasks: string[];
194
+ recommended_direction: string;
195
+ implementation_notes: string[];
196
+ why_this_next: string;
197
+ blocked_by: string[];
198
+ owner_route: string;
199
+ study_note: StudyNote;
200
+ }
201
+
152
202
  export interface UeyeRunReport {
153
203
  schema: 'yam.ueye-run-report.v1';
154
204
  reference_sources: UeyeVisualProvenance[];
@@ -452,6 +502,78 @@ export function buildRuntimeBackendEvidence(input: Partial<RuntimeBackendEvidenc
452
502
  };
453
503
  }
454
504
 
505
+ export function buildStudyNote(input: Partial<StudyNote> & Record<string, unknown> = {}): StudyNote {
506
+ const problem = buildStudyNoteSection(input.problem, {
507
+ code: input.issue_code,
508
+ role: input.issue_role,
509
+ symptom: input.issue_symptom
510
+ }, 'problem');
511
+ const change = buildStudyNoteSection(input.change, {
512
+ code: input.changed_code,
513
+ role: input.changed_role,
514
+ summary: input.change_summary
515
+ }, 'change');
516
+ const whyItMatters = shortText(input.why_it_matters || input.why_important);
517
+ const learningNote = shortText(input.learning_note);
518
+ const limits = [
519
+ ...asList(input.limits),
520
+ ...missingStudyNoteLimits(problem, change, whyItMatters, learningNote)
521
+ ];
522
+ const hasProblem = Boolean(problem.code || problem.role || problem.symptom);
523
+ const hasChange = Boolean(change.code || change.role || change.summary);
524
+ const truth: TruthStatus = isTruthStatus(input.truth_status) ? input.truth_status
525
+ : hasProblem || hasChange || whyItMatters || learningNote ? 'partial'
526
+ : 'assumed';
527
+ return {
528
+ schema: 'yam.study-note.v1',
529
+ problem,
530
+ change,
531
+ why_it_matters: whyItMatters,
532
+ learning_note: learningNote,
533
+ limits: [...new Set(limits)],
534
+ truth_status: truth
535
+ };
536
+ }
537
+
538
+ export function buildLoopReport(input: Partial<LoopReport> & Record<string, unknown> = {}): LoopReport {
539
+ const stages = Array.isArray(input.stages)
540
+ ? input.stages.map((stage) => buildLoopStage(stage))
541
+ : [];
542
+ const evidence = asList(input.evidence);
543
+ const blockers = asList(input.blockers || input.blocked);
544
+ const hasBlockedStage = stages.some((stage) => stage.status === 'blocked' || stage.status === 'failed');
545
+ const requestedTruth = isTruthStatus(input.truth_status) ? input.truth_status : '';
546
+ const derivedTruth: TruthStatus = blockers.length || hasBlockedStage ? 'blocked'
547
+ : evidence.length && stages.some((stage) => stage.status === 'passed') ? 'verified'
548
+ : evidence.length || stages.length ? 'partial'
549
+ : 'assumed';
550
+ const truth = requestedTruth ? weakestTruth(requestedTruth, derivedTruth) : derivedTruth;
551
+ const studyNote = buildStudyNote(input.study_note && typeof input.study_note === 'object' ? input.study_note as unknown as Record<string, unknown> : input);
552
+ const intentLabel = normalizeToolIntentLabel(input.intent_label || input.tool_intent);
553
+ return {
554
+ schema: 'yam.loop-report.v1',
555
+ generated_at: String(input.generated_at || new Date().toISOString()),
556
+ route: String(input.route || ''),
557
+ intent: shortText(input.intent),
558
+ loop_kind: String(input.loop_kind || 'harness'),
559
+ stages,
560
+ evidence,
561
+ blockers,
562
+ truth_status: truth,
563
+ intent_label: intentLabel,
564
+ tool_intent: intentLabel,
565
+ next_action: shortText(input.next_action || (blockers[0] ? 'resolve the blocker before claiming this loop complete' : 'record the next smallest useful action')),
566
+ fix_first_items: asList(input.fix_first_items || input.fix_first_item),
567
+ remaining_tasks: asList(input.remaining_tasks || input.remaining_task),
568
+ recommended_direction: shortText(input.recommended_direction || input.direction),
569
+ implementation_notes: asList(input.implementation_notes || input.implementation_note),
570
+ why_this_next: shortText(input.why_this_next),
571
+ blocked_by: asList(input.blocked_by),
572
+ owner_route: normalizeOwnerRoute(input.owner_route || input.route),
573
+ study_note: studyNote
574
+ };
575
+ }
576
+
455
577
  export function buildUeyeRunReport(input: UeyeRunReportInput = {}): UeyeRunReport {
456
578
  const references = Array.isArray(input.reference_sources)
457
579
  ? input.reference_sources.map((item) => buildUeyeVisualProvenance(item))
@@ -494,6 +616,90 @@ export function buildUeyeRunReport(input: UeyeRunReportInput = {}): UeyeRunRepor
494
616
  };
495
617
  }
496
618
 
619
+ function buildStudyNoteSection(section: unknown, fallback: Record<string, unknown>, kind: 'problem' | 'change'): StudyNoteSection {
620
+ const source = section && typeof section === 'object' ? section as Record<string, unknown> : fallback;
621
+ const code = shortText(source.code);
622
+ const role = shortText(source.role);
623
+ const symptom = kind === 'problem' ? shortText(source.symptom) : undefined;
624
+ const summary = kind === 'change' ? shortText(source.summary) : undefined;
625
+ return {
626
+ code,
627
+ role,
628
+ ...(symptom !== undefined ? { symptom } : {}),
629
+ ...(summary !== undefined ? { summary } : {}),
630
+ truth_status: code || role || symptom || summary ? 'partial' : 'assumed'
631
+ };
632
+ }
633
+
634
+ function missingStudyNoteLimits(problem: StudyNoteSection, change: StudyNoteSection, whyItMatters = '', learningNote = ''): string[] {
635
+ const limits: string[] = [];
636
+ if (!problem.code) limits.push('issue_code not provided');
637
+ if (!problem.role) limits.push('issue_role not provided');
638
+ if (!problem.symptom) limits.push('issue_symptom not provided');
639
+ if (!change.code) limits.push('changed_code not provided');
640
+ if (!change.role) limits.push('changed_role not provided');
641
+ if (!change.summary) limits.push('change_summary not provided');
642
+ if (!whyItMatters) limits.push('why_it_matters not provided');
643
+ if (!learningNote) limits.push('learning_note not provided');
644
+ return limits;
645
+ }
646
+
647
+ function buildLoopStage(value: unknown = ''): LoopStage {
648
+ if (value && typeof value === 'object') {
649
+ const item = value as Partial<LoopStage>;
650
+ return loopStage(String(item.id || ''), item.status, String(item.note || ''));
651
+ }
652
+ const [id = '', rawStatus = '', ...noteParts] = String(value || '').split(':');
653
+ return loopStage(id, rawStatus, noteParts.join(':'));
654
+ }
655
+
656
+ function loopStage(id = '', rawStatus: unknown = '', note = ''): LoopStage {
657
+ const status = normalizeLoopStageStatus(rawStatus);
658
+ return {
659
+ id: shortText(id || 'stage'),
660
+ status,
661
+ note: shortText(note),
662
+ truth_status: stageTruth(status)
663
+ };
664
+ }
665
+
666
+ function normalizeLoopStageStatus(value: unknown = ''): LoopStageStatus {
667
+ const text = String(value || '').toLowerCase().replace(/[-\s]/g, '_');
668
+ if (text === 'pass' || text === 'passed' || text === 'ok') return 'passed';
669
+ if (text === 'fail' || text === 'failed' || text === 'error') return 'failed';
670
+ if (text === 'block' || text === 'blocked') return 'blocked';
671
+ if (text === 'skip' || text === 'skipped') return 'skipped';
672
+ if (text === 'partial') return 'partial';
673
+ if (text === 'pending') return 'pending';
674
+ return 'recorded';
675
+ }
676
+
677
+ function stageTruth(status: LoopStageStatus): TruthStatus {
678
+ if (status === 'passed') return 'verified';
679
+ if (status === 'failed' || status === 'blocked') return 'blocked';
680
+ if (status === 'skipped') return 'skipped';
681
+ if (status === 'pending') return 'assumed';
682
+ return 'partial';
683
+ }
684
+
685
+ function normalizeToolIntentLabel(value: unknown = ''): ToolIntent {
686
+ const text = String(value || '').toLowerCase().replace(/[-\s]/g, '_');
687
+ if (['read_only', 'write', 'destructive', 'runtime', 'visual', 'publish'].includes(text)) return text as ToolIntent;
688
+ return 'read_only';
689
+ }
690
+
691
+ function normalizeOwnerRoute(value: unknown = ''): string {
692
+ const text = String(value || '').trim().replace(/^\$/, '').replace(/^yam-/, '');
693
+ if (['quick', 'ueye', 'question', 'scout', 'deep', 'mission'].includes(text)) return `$${text}`;
694
+ return text ? shortText(text, 80) : '';
695
+ }
696
+
697
+ function shortText(value: unknown = '', limit = 240): string {
698
+ const text = String(value || '').trim().replace(/\s+/g, ' ');
699
+ if (text.length <= limit) return text;
700
+ return `${text.slice(0, limit - 3)}...`;
701
+ }
702
+
497
703
  export function buildMediaGenerationProof(input: Partial<MediaGenerationProof> = {}): MediaGenerationProof {
498
704
  const requested = Boolean(input.generation_requested);
499
705
  const attempted = Boolean(input.generation_attempted);
package/yam.manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yam",
3
- "version": "0.1.5",
3
+ "version": "0.1.9",
4
4
  "principles": [
5
5
  "Direction before execution.",
6
6
  "Start fast.",
@@ -10,7 +10,9 @@
10
10
  "Heavy proof when risk or user intent calls for it.",
11
11
  "Token-aware by default.",
12
12
  "Context-reuse first.",
13
- "End with remaining tasks and fix-first items."
13
+ "End with remaining tasks and fix-first items.",
14
+ "Explain what failed, what changed, and why it matters in short non-specialist language when useful.",
15
+ "Use handoff notes for fix-first items, remaining tasks, and recommended next direction when useful."
14
16
  ],
15
17
  "defaultHooks": false,
16
18
  "routes": [