yam-flow 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COMMANDS.md CHANGED
@@ -135,12 +135,20 @@ yam tools doctor /path/to/project --json
135
135
  yam proof /path/to/project
136
136
  yam proof --route deep --truth verified --command "npm run build: pass"
137
137
  yam proof --route ueye --truth verified --visual "reference image only"
138
+ yam proof --route ueye --truth partial --visual-provenance '{"source_kind":"reference","source_hash":"unknown","comparison_result":"not-verified","truth_status":"partial"}'
139
+ yam proof --route mission --mission-envelope '{"agent_id":"implementer","assigned_scope":"target component","changed_files":["src/file.ts"],"verification_hint":"npm run typecheck","truth_status":"partial"}'
138
140
  yam proof --route deep --truth proven --require-runtime --runtime "mock server fixture"
139
141
  yam proof write /path/to/project --route deep --truth partial --command "npm run build: pass"
140
142
  yam proof write /path/to/project --format md --truth assumed --assumption "No runtime was started"
141
143
  yam safety "supabase db reset --linked"
142
144
  ```
143
145
 
146
+ Release reporting intentionally runs the release checks and returns a machine-readable summary.
147
+
148
+ ```bash
149
+ yam release report --json
150
+ ```
151
+
144
152
  ## Lite Hook
145
153
 
146
154
  Advisory-only hook. It does not run checks or force routes.
package/README.md CHANGED
@@ -126,6 +126,9 @@ yam tools doctor /path/to/project --json
126
126
  yam proof /path/to/project
127
127
  yam proof write /path/to/project --route quick --truth verified --command "npm run verify:self: pass"
128
128
  yam proof --route ueye --truth verified --visual "reference image only"
129
+ yam proof --route ueye --truth partial --visual-provenance '{"source_kind":"reference","source_hash":"unknown","comparison_result":"not-verified","truth_status":"partial"}'
130
+ yam proof --route mission --mission-envelope '{"agent_id":"implementer","assigned_scope":"target component","changed_files":["src/file.ts"],"verification_hint":"npm run typecheck","truth_status":"partial"}'
131
+ yam release report --json
129
132
  yam safety "supabase db reset"
130
133
  yam detect /path/to/project
131
134
  yam pack /path/to/project
package/ROADMAP.md CHANGED
@@ -311,3 +311,113 @@ Tasks:
311
311
  - Add `yam proof write`. Done.
312
312
  - Prepare npm/npx package metadata and dry-run packaging. Done.
313
313
  - Add small route-scoped trust kernel: truth caps, fake/real policy, runtime truth matrix, and completion proof object. Done.
314
+
315
+ ### 16. Deferred Triggers
316
+
317
+ Goal: keep useful future trust-layer ideas visible without turning them into always-on gates too early.
318
+
319
+ Rules:
320
+
321
+ - Do not implement deferred items just because they are listed here.
322
+ - Mention an item again when its trigger appears in real use.
323
+ - Prefer lite metadata before runtime systems.
324
+ - Keep ordinary `$quick`, `$question`, and `$scout` flows free of these gates.
325
+
326
+ #### Mission Patch Queue Lite
327
+
328
+ Status: next candidate for `$mission` only.
329
+
330
+ Trigger:
331
+
332
+ - A mission uses two or more real subagent/lane outputs.
333
+ - Multiple lanes touch nearby files or the same subsystem.
334
+ - It becomes hard to tell who changed what, why, and how to revert it.
335
+ - Mission rollback or apply order starts to matter.
336
+
337
+ First shape:
338
+
339
+ - pending / applied / verified / reverted / blocked state.
340
+ - lane id and assigned scope.
341
+ - changed files.
342
+ - verification hint.
343
+ - rollback hint.
344
+ - truth status.
345
+
346
+ Keep out for now:
347
+
348
+ - automatic merge engine.
349
+ - persistent locks.
350
+ - parallel apply worker.
351
+ - broad runtime orchestration.
352
+
353
+ #### Tool Intent Label / Tool Trust Metadata
354
+
355
+ Status: near-term lightweight metadata.
356
+
357
+ Trigger:
358
+
359
+ - A route uses several tool classes together, such as browser, package manager, GitHub, filesystem, database, or deployment tools.
360
+ - Read-only and write actions need to be distinguished in proof.
361
+ - Destructive actions need a visible route recommendation before execution.
362
+ - Release or mission reports need clearer evidence about what kind of tool ran.
363
+
364
+ First shape:
365
+
366
+ - tool intent: read_only / write / destructive / runtime / visual.
367
+ - parallel safe: true / false / unknown.
368
+ - approval required: true / false / unknown.
369
+ - evidence kind and truth status.
370
+
371
+ Keep out for now:
372
+
373
+ - automatic scheduling.
374
+ - automatic permission mutation.
375
+ - mandatory tool graph.
376
+
377
+ #### MCP Scheduler Runtime
378
+
379
+ Status: later.
380
+
381
+ Trigger:
382
+
383
+ - Tool intent labels are no longer enough.
384
+ - The harness starts coordinating multiple external tools inside one route.
385
+ - Write or destructive actions could run near read-only checks.
386
+ - Parallel tool execution becomes common and needs ordering.
387
+ - A mission needs deterministic tool sequence proof.
388
+
389
+ First shape:
390
+
391
+ - scheduler proof object.
392
+ - read-only overlap allowed.
393
+ - write/destructive serialization.
394
+ - blocked/approved/skipped status.
395
+
396
+ Keep out for now:
397
+
398
+ - default scheduler for normal work.
399
+ - route-wide mandatory scheduler.
400
+ - background runtime service.
401
+
402
+ #### Appshots Attachment Gate
403
+
404
+ Status: deferred.
405
+
406
+ Trigger:
407
+
408
+ - Ueye visual provenance cannot answer which exact visual source was used.
409
+ - A team workflow needs thread-level or attachment-level audit trails.
410
+ - Reference and implementation screenshots are easy to confuse.
411
+ - External review requires source image lineage beyond path/hash/id.
412
+
413
+ First shape:
414
+
415
+ - optional attachment source fields.
416
+ - no mandatory gate.
417
+ - preserve Ueye visual provenance as the default.
418
+
419
+ Keep out for now:
420
+
421
+ - required thread attachment ids.
422
+ - app-specific visual gates.
423
+ - blocking ordinary Ueye work when attachment metadata is absent.
package/dist/bin/yam.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
2
3
  import fs from 'node:fs';
3
4
  import fsp from 'node:fs/promises';
4
5
  import os from 'node:os';
5
6
  import path from 'node:path';
6
7
  import process from 'node:process';
7
8
  import { fileURLToPath } from 'node:url';
8
- import { TRUTH_STATUSES, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
9
+ import { TRUTH_STATUSES, buildMissionPatchEnvelope, buildRollbackHint, buildUeyeVisualProvenance, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
9
10
  function errorMessage(error) {
10
11
  return error instanceof Error ? error.message : String(error);
11
12
  }
@@ -117,6 +118,7 @@ Usage:
117
118
  yam tools doctor [dir]
118
119
  yam proof [dir|--from file] [--route route] [--truth status] [--command text] [--evidence text]
119
120
  yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
121
+ yam release report [--json]
120
122
  yam safety [text...]
121
123
  yam memory <init|add|list|summary|resolve> [dir] [options]
122
124
  yam hook <status|enable|disable|run> [lite] [--global|--project dir]
@@ -927,6 +929,86 @@ async function safety(args = []) {
927
929
  const result = detectDbSafetyText(text);
928
930
  printSafetyResult(result);
929
931
  }
932
+ async function release(args = []) {
933
+ const subcommand = args[0] || 'help';
934
+ if (subcommand !== 'report') {
935
+ console.error('usage: yam release report [--json]');
936
+ process.exitCode = 1;
937
+ return;
938
+ }
939
+ const asJson = args.includes('--json');
940
+ const report = runReleaseReport();
941
+ if (asJson) {
942
+ console.log(JSON.stringify(report, null, 2));
943
+ if (!report.ok)
944
+ process.exitCode = 1;
945
+ return;
946
+ }
947
+ console.log('yam release report');
948
+ console.log(`- Package: ${report.packageName}@${report.version}`);
949
+ console.log(`- Truth status: ${report.truth_status}`);
950
+ for (const check of report.checks) {
951
+ console.log(`- ${check.id}: ${check.status} (${check.duration_ms}ms)`);
952
+ if (check.note)
953
+ console.log(` ${check.note}`);
954
+ }
955
+ if (report.failed.length) {
956
+ console.log('- Failed:');
957
+ for (const id of report.failed)
958
+ console.log(` - ${id}`);
959
+ }
960
+ if (!report.ok)
961
+ process.exitCode = 1;
962
+ }
963
+ function runReleaseReport() {
964
+ const startedAt = new Date().toISOString();
965
+ const checks = [
966
+ ['typecheck', ['npm', ['run', 'typecheck']]],
967
+ ['forbidden_names', ['npm', ['run', 'forbidden-names:check']]],
968
+ ['package_boundary', ['npm', ['run', 'package-boundary:check']]],
969
+ ['registry_status', ['npm', ['run', 'registry:check']]],
970
+ ['cli_smoke', ['npm', ['run', 'cli-smoke']]],
971
+ ['dist_freshness', ['npm', ['run', 'dist:freshness']]]
972
+ ].map(([id, command]) => runReleaseCheck(id, command));
973
+ const failed = checks.filter((check) => check.status !== 'passed').map((check) => check.id);
974
+ return {
975
+ schema: 'yam.release-report.v1',
976
+ generated_at: startedAt,
977
+ packageName: PACKAGE_JSON.name,
978
+ version: VERSION,
979
+ ok: failed.length === 0,
980
+ truth_status: failed.length === 0 ? 'verified' : 'blocked',
981
+ checks,
982
+ failed
983
+ };
984
+ }
985
+ function runReleaseCheck(id, commandSpec) {
986
+ const [command, args] = commandSpec;
987
+ const start = Date.now();
988
+ const result = spawnSync(command, args, {
989
+ cwd: ROOT,
990
+ encoding: 'utf8',
991
+ env: { ...process.env, npm_config_cache: path.join(os.tmpdir(), 'yam-npm-cache') },
992
+ timeout: 120000
993
+ });
994
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
995
+ const timedOut = result.error?.message?.includes('ETIMEDOUT') || result.signal === 'SIGTERM';
996
+ return {
997
+ id,
998
+ command: [command, ...args].join(' '),
999
+ status: result.status === 0 ? 'passed' : timedOut ? 'blocked' : 'failed',
1000
+ exit_code: typeof result.status === 'number' ? result.status : null,
1001
+ duration_ms: Date.now() - start,
1002
+ note: summarizeCheckOutput(output || result.error?.message || '')
1003
+ };
1004
+ }
1005
+ function summarizeCheckOutput(output = '') {
1006
+ const lines = String(output || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1007
+ if (!lines.length)
1008
+ return '';
1009
+ const highSignal = lines.filter((line) => !line.startsWith('>')).slice(-4);
1010
+ return highSignal.join(' | ').slice(0, 600);
1011
+ }
930
1012
  async function readStdinTextIfAvailable() {
931
1013
  if (process.stdin.isTTY)
932
1014
  return '';
@@ -975,6 +1057,9 @@ async function proof(args = []) {
975
1057
  printProofList('Evidence', summary.evidence);
976
1058
  printProofList('Visual evidence', summary.visual);
977
1059
  printProofList('Runtime evidence', summary.runtime);
1060
+ printProofList('Visual provenance', summary.visualProvenance || []);
1061
+ printProofList('Mission patch envelope', summary.missionEnvelope || []);
1062
+ printProofList('Rollback hint', summary.rollbackHint || []);
978
1063
  printProofList('Changed surfaces', summary.changed);
979
1064
  printProofList('Skipped', summary.skipped);
980
1065
  printProofList('Blocked', summary.blocked);
@@ -1050,6 +1135,18 @@ function buildProofSummary(parsed, artifact) {
1050
1135
  evidence: [...artifact.evidence, ...arrayFlag(parsed.flags.evidence)],
1051
1136
  visual: [...artifact.visual, ...arrayFlag(parsed.flags.visual)],
1052
1137
  runtime: [...artifact.runtime, ...arrayFlag(parsed.flags.runtime)],
1138
+ visualProvenance: [
1139
+ ...structuredEvidenceList(artifact.visualProvenance),
1140
+ ...structuredEvidenceList(parsed.flags.visual_provenance)
1141
+ ],
1142
+ missionEnvelope: [
1143
+ ...structuredEvidenceList(artifact.missionEnvelope),
1144
+ ...structuredEvidenceList(parsed.flags.mission_envelope)
1145
+ ],
1146
+ rollbackHint: [
1147
+ ...structuredEvidenceList(artifact.rollbackHint),
1148
+ ...structuredEvidenceList(parsed.flags.rollback_hint)
1149
+ ],
1053
1150
  cleanup: parsed.flags.cleanup || artifact.cleanup || '',
1054
1151
  changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
1055
1152
  skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
@@ -1099,6 +1196,15 @@ function renderProofMarkdown(summary) {
1099
1196
  '## Runtime evidence',
1100
1197
  ...renderProofMarkdownList(summary.runtime),
1101
1198
  '',
1199
+ '## Visual provenance',
1200
+ ...renderProofMarkdownList(summary.visualProvenance || []),
1201
+ '',
1202
+ '## Mission patch envelope',
1203
+ ...renderProofMarkdownList(summary.missionEnvelope || []),
1204
+ '',
1205
+ '## Rollback hint',
1206
+ ...renderProofMarkdownList(summary.rollbackHint || []),
1207
+ '',
1102
1208
  '## Changed surfaces',
1103
1209
  ...renderProofMarkdownList(summary.changed),
1104
1210
  '',
@@ -1129,7 +1235,7 @@ function renderProofMarkdownList(values) {
1129
1235
  function parseProofArgs(args = []) {
1130
1236
  const flags = {};
1131
1237
  const positionals = [];
1132
- const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
1238
+ const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
1133
1239
  for (let index = 0; index < args.length; index += 1) {
1134
1240
  const arg = args[index];
1135
1241
  if (arg.startsWith('--')) {
@@ -1145,7 +1251,7 @@ function parseProofArgs(args = []) {
1145
1251
  const value = inlineValue ?? args[index + 1] ?? '';
1146
1252
  if (inlineValue === undefined)
1147
1253
  index += 1;
1148
- if (['command', 'evidence', 'visual', 'runtime', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
1254
+ if (['command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
1149
1255
  flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
1150
1256
  }
1151
1257
  else {
@@ -1184,6 +1290,9 @@ function emptyProofArtifact() {
1184
1290
  evidence: [],
1185
1291
  visual: [],
1186
1292
  runtime: [],
1293
+ visualProvenance: [],
1294
+ missionEnvelope: [],
1295
+ rollbackHint: [],
1187
1296
  cleanup: '',
1188
1297
  changed: [],
1189
1298
  skipped: [],
@@ -1205,6 +1314,9 @@ async function parseProofJson(file, empty) {
1205
1314
  evidence: arrayFlag(data.evidence),
1206
1315
  visual: arrayFlag(data.visual || data.visualEvidence),
1207
1316
  runtime: arrayFlag(data.runtime || data.runtimeEvidence),
1317
+ visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
1318
+ missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
1319
+ rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
1208
1320
  cleanup: String(data.cleanup || data.cleanupStatus || ''),
1209
1321
  changed: arrayFlag(data.changed || data.files),
1210
1322
  skipped: arrayFlag(data.skipped),
@@ -1230,6 +1342,9 @@ async function parseProofMarkdown(file, empty) {
1230
1342
  evidence: readProofList(text, 'Evidence'),
1231
1343
  visual: readProofList(text, 'Visual evidence'),
1232
1344
  runtime: firstProofList(text, ['Runtime evidence', 'Processes', 'tmux']),
1345
+ visualProvenance: readProofList(text, 'Visual provenance'),
1346
+ missionEnvelope: readProofList(text, 'Mission patch envelope'),
1347
+ rollbackHint: readProofList(text, 'Rollback hint'),
1233
1348
  cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
1234
1349
  changed: firstProofList(text, ['Changed surfaces', 'Files']),
1235
1350
  skipped: readProofList(text, 'Skipped'),
@@ -1274,9 +1389,39 @@ function arrayFlag(value) {
1274
1389
  return [];
1275
1390
  return Array.isArray(value) ? value.filter(Boolean) : [value].filter(Boolean);
1276
1391
  }
1392
+ function structuredEvidenceList(value) {
1393
+ return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
1394
+ }
1395
+ function formatStructuredEvidence(value) {
1396
+ if (!value)
1397
+ return '';
1398
+ if (typeof value === 'object')
1399
+ return JSON.stringify(value);
1400
+ const text = String(value).trim();
1401
+ if (!text)
1402
+ return '';
1403
+ if (!text.startsWith('{'))
1404
+ return text;
1405
+ try {
1406
+ const data = JSON.parse(text);
1407
+ if (data.schema === 'yam.ueye-visual-provenance.v1' || data.source_kind || data.reference_id || data.screenshot_id) {
1408
+ return JSON.stringify(buildUeyeVisualProvenance(data));
1409
+ }
1410
+ if (data.schema === 'yam.mission-patch-envelope.v1' || data.agent_id || data.assigned_scope) {
1411
+ return JSON.stringify(buildMissionPatchEnvelope(data));
1412
+ }
1413
+ if (data.schema === 'yam.rollback-hint.v1' || data.touched_files || data.safe_revert_note) {
1414
+ return JSON.stringify(buildRollbackHint(data));
1415
+ }
1416
+ return JSON.stringify(data);
1417
+ }
1418
+ catch {
1419
+ return text;
1420
+ }
1421
+ }
1277
1422
  function hasProofEvidence(parsed) {
1278
1423
  const flags = parsed.flags || {};
1279
- return ['command', 'evidence', 'visual', 'runtime', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup);
1424
+ return ['command', 'evidence', 'visual', 'runtime', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup);
1280
1425
  }
1281
1426
  function printProofList(label, values) {
1282
1427
  console.log(`- ${label}:`);
@@ -1967,6 +2112,8 @@ async function main() {
1967
2112
  return tools(process.argv.slice(3));
1968
2113
  if (command === 'proof')
1969
2114
  return proof(process.argv.slice(3));
2115
+ if (command === 'release')
2116
+ return release(process.argv.slice(3));
1970
2117
  if (command === 'safety')
1971
2118
  return safety(process.argv.slice(3));
1972
2119
  if (command === 'memory')
@@ -29,6 +29,35 @@ export interface RuntimeTruthMatrix {
29
29
  rows: RuntimeTruthRow[];
30
30
  blockers: string[];
31
31
  }
32
+ export interface UeyeVisualProvenance {
33
+ schema: 'yam.ueye-visual-provenance.v1';
34
+ source_kind: string;
35
+ source_path: string;
36
+ source_hash: string;
37
+ reference_id: string;
38
+ screenshot_id: string;
39
+ local_only: boolean;
40
+ redacted: boolean;
41
+ operator_provided: boolean;
42
+ comparison_result: string;
43
+ truth_status: TruthStatus;
44
+ }
45
+ export interface RollbackHint {
46
+ schema: 'yam.rollback-hint.v1';
47
+ touched_files: string[];
48
+ generated_files: string[];
49
+ before_check: string;
50
+ safe_revert_note: string;
51
+ }
52
+ export interface MissionPatchEnvelope {
53
+ schema: 'yam.mission-patch-envelope.v1';
54
+ agent_id: string;
55
+ assigned_scope: string;
56
+ changed_files: string[];
57
+ verification_hint: string;
58
+ rollback_hint: RollbackHint;
59
+ truth_status: TruthStatus;
60
+ }
32
61
  export interface FakeRealPolicyResult {
33
62
  schema: 'yam.fake-real-policy.v1';
34
63
  ok: boolean;
@@ -52,6 +81,9 @@ export interface ProofSummary {
52
81
  blocked?: unknown;
53
82
  assumptions?: unknown;
54
83
  unverified?: unknown;
84
+ visualProvenance?: unknown;
85
+ missionEnvelope?: unknown;
86
+ rollbackHint?: unknown;
55
87
  }
56
88
  export interface ProofOptions {
57
89
  requireRealRuntime?: boolean;
@@ -79,6 +111,9 @@ export interface YamCompletionProof {
79
111
  evidence: string[];
80
112
  visual: string[];
81
113
  runtime: string[];
114
+ visualProvenance: string[];
115
+ missionEnvelope: string[];
116
+ rollbackHint: string[];
82
117
  cleanup: unknown;
83
118
  changed: string[];
84
119
  skipped: string[];
@@ -90,6 +125,9 @@ export interface YamCompletionProof {
90
125
  fakeReal: FakeRealPolicyResult;
91
126
  runtimeTruth: RuntimeTruthMatrix;
92
127
  }
128
+ export declare function buildUeyeVisualProvenance(input?: Partial<UeyeVisualProvenance>): UeyeVisualProvenance;
129
+ export declare function buildRollbackHint(input?: Partial<RollbackHint>): RollbackHint;
130
+ export declare function buildMissionPatchEnvelope(input?: Partial<MissionPatchEnvelope>): MissionPatchEnvelope;
93
131
  export declare function isTruthStatus(value?: unknown): value is TruthStatus;
94
132
  export declare function weakestTruth(...statuses: unknown[]): TruthStatus;
95
133
  export declare function detectDbSafetyText(text?: unknown): DbSafetyScanResult;
@@ -10,6 +10,45 @@ export const TRUTH_STATUSES = Object.freeze([
10
10
  'blocked',
11
11
  'assumed'
12
12
  ]);
13
+ export function buildUeyeVisualProvenance(input = {}) {
14
+ const truth = isTruthStatus(input.truth_status) ? input.truth_status : 'partial';
15
+ return {
16
+ schema: 'yam.ueye-visual-provenance.v1',
17
+ source_kind: String(input.source_kind || ''),
18
+ source_path: String(input.source_path || ''),
19
+ source_hash: String(input.source_hash || 'unknown'),
20
+ reference_id: String(input.reference_id || ''),
21
+ screenshot_id: String(input.screenshot_id || ''),
22
+ local_only: Boolean(input.local_only),
23
+ redacted: Boolean(input.redacted),
24
+ operator_provided: Boolean(input.operator_provided),
25
+ comparison_result: String(input.comparison_result || 'not-verified'),
26
+ truth_status: truth
27
+ };
28
+ }
29
+ export function buildRollbackHint(input = {}) {
30
+ return {
31
+ schema: 'yam.rollback-hint.v1',
32
+ touched_files: asList(input.touched_files),
33
+ generated_files: asList(input.generated_files),
34
+ before_check: String(input.before_check || ''),
35
+ safe_revert_note: String(input.safe_revert_note || '')
36
+ };
37
+ }
38
+ export function buildMissionPatchEnvelope(input = {}) {
39
+ const rollback = input.rollback_hint && typeof input.rollback_hint === 'object'
40
+ ? buildRollbackHint(input.rollback_hint)
41
+ : buildRollbackHint();
42
+ return {
43
+ schema: 'yam.mission-patch-envelope.v1',
44
+ agent_id: String(input.agent_id || ''),
45
+ assigned_scope: String(input.assigned_scope || ''),
46
+ changed_files: asList(input.changed_files),
47
+ verification_hint: String(input.verification_hint || ''),
48
+ rollback_hint: rollback,
49
+ truth_status: isTruthStatus(input.truth_status) ? input.truth_status : 'partial'
50
+ };
51
+ }
13
52
  const TRUTH_RANK = Object.freeze({
14
53
  blocked: 0,
15
54
  real_required_missing: 0,
@@ -81,6 +120,15 @@ export function classifyEvidenceTruth(summary = {}, options = {}) {
81
120
  for (const visual of asList(summary.visual)) {
82
121
  rows.push(evidenceRow('visual', visual, classifyVisualEvidence(visual)));
83
122
  }
123
+ for (const provenance of asList(summary.visualProvenance)) {
124
+ rows.push(evidenceRow('visual', provenance, metadataTruth(provenance, 'partial')));
125
+ }
126
+ for (const envelope of asList(summary.missionEnvelope)) {
127
+ rows.push(evidenceRow('evidence', envelope, metadataTruth(envelope, 'partial')));
128
+ }
129
+ for (const rollback of asList(summary.rollbackHint)) {
130
+ rows.push(evidenceRow('evidence', rollback, metadataTruth(rollback, 'partial')));
131
+ }
84
132
  for (const runtime of asList(summary.runtime)) {
85
133
  rows.push(evidenceRow('runtime', runtime, classifyRuntimeEvidence(runtime, options)));
86
134
  }
@@ -166,6 +214,9 @@ export function buildYamCompletionProof(summary = {}, options = {}) {
166
214
  evidence: asList(summary.evidence),
167
215
  visual: asList(summary.visual),
168
216
  runtime: asList(summary.runtime),
217
+ visualProvenance: asList(summary.visualProvenance),
218
+ missionEnvelope: asList(summary.missionEnvelope),
219
+ rollbackHint: asList(summary.rollbackHint),
169
220
  cleanup: summary.cleanup || '',
170
221
  changed: asList(summary.changed),
171
222
  skipped: asList(summary.skipped),
@@ -223,6 +274,24 @@ function classifyCleanupEvidence(value = '') {
223
274
  function commandPassed(value = '') {
224
275
  return /pass|passed|ok|success|exit\s*0|green/i.test(String(value || '')) && !/fail|failed|error|blocked/i.test(String(value || ''));
225
276
  }
277
+ function metadataTruth(value = '', fallback = 'partial') {
278
+ return weakestTruth(readStructuredTruth(value, fallback), 'partial');
279
+ }
280
+ function readStructuredTruth(value = '', fallback = 'partial') {
281
+ if (typeof value === 'object' && value) {
282
+ const candidate = value.truth_status || value.truth;
283
+ return isTruthStatus(candidate) ? candidate : fallback;
284
+ }
285
+ const text = String(value || '');
286
+ try {
287
+ const data = JSON.parse(text);
288
+ const candidate = data.truth_status || data.truth;
289
+ return isTruthStatus(candidate) ? candidate : fallback;
290
+ }
291
+ catch {
292
+ return fallback;
293
+ }
294
+ }
226
295
  function strongestSupportedTruth(rows = []) {
227
296
  if (rows.some((row) => row.truth === 'blocked'))
228
297
  return 'blocked';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yam-flow",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Progressive proof-first agent flow: start fast, deepen deliberately, stay honest by design.",
5
5
  "type": "module",
6
6
  "author": "0kim0bos",
@@ -36,6 +36,30 @@ Subagent policy:
36
36
  - If the user explicitly insists on mission despite missing subagents, mark the result `partial` or `blocked`; do not treat self-review as team execution.
37
37
  - The final proof must record the subagent decision: `used`, `downgraded_to_deep`, `unavailable_partial`, or `blocked`, with the reason.
38
38
 
39
+ Patch envelope:
40
+
41
+ Use a small envelope when a real mission lane changes code:
42
+
43
+ - `agent_id`: who or which lane made the change.
44
+ - `assigned_scope`: the bounded work area.
45
+ - `changed_files`: files the lane touched.
46
+ - `verification_hint`: the smallest relevant check for the lane.
47
+ - `rollback_hint`: what to inspect or revert if the change is wrong.
48
+ - `truth_status`: the lane's honest status.
49
+
50
+ This is only a record shape. It is not a queue, lock manager, or parallel apply engine.
51
+
52
+ Rollback hint:
53
+
54
+ Record:
55
+
56
+ - touched files
57
+ - generated files
58
+ - before check
59
+ - safe revert note
60
+
61
+ Do not claim rollback safety unless the relevant check or revert path was actually inspected.
62
+
39
63
  Runtime use:
40
64
 
41
65
  - Mission may use deep runtime verification when needed, but runtime proof alone does not make a mission if subagents were not used.
@@ -110,6 +110,19 @@ On demand:
110
110
 
111
111
  - command evidence
112
112
  - browser/screenshot evidence
113
+ - visual provenance for reference-based UI work
114
+ - mission patch envelopes for real team lanes
115
+ - rollback hints for risky changes
113
116
  - runtime/tmux/process cleanup evidence
114
117
  - truth status
115
118
  - skipped/blocked/assumed items
119
+
120
+ `yam release report --json` should collect release readiness into a machine-readable artifact:
121
+
122
+ - typecheck
123
+ - forbidden names
124
+ - package boundary
125
+ - registry status
126
+ - CLI smoke
127
+ - dist freshness
128
+ - final truth status
@@ -41,6 +41,25 @@ Bounds:
41
41
  - Record missing dimensions or hashes as `unknown`, not as failure.
42
42
  - Use generated annotations only as derivative aids.
43
43
 
44
+ ### Visual Provenance
45
+
46
+ Purpose: prove the visual source path from reference reading to implementation recheck.
47
+
48
+ Record these fields when a reference or screenshot materially affects the claim:
49
+
50
+ - `source_kind`
51
+ - `source_path`
52
+ - `source_hash`
53
+ - `reference_id`
54
+ - `screenshot_id`
55
+ - `local_only`
56
+ - `redacted`
57
+ - `operator_provided`
58
+ - `comparison_result`
59
+ - `truth_status`
60
+
61
+ Use `unknown` for missing hashes and `not-verified` for comparisons that were not actually rechecked. Do not upgrade a visual claim only because a reference was supplied.
62
+
44
63
  ### Reference Read Proof
45
64
 
46
65
  Purpose: separate reading the reference from judging the implementation.
@@ -49,12 +49,14 @@ Do not use for:
49
49
  - UX/browser verifier: checks screen behavior when relevant.
50
50
  - Doctor/scanner: checks direction fit, scope control, verification, cleanup, stale context, and false-completion risk.
51
51
  5. Execute the implementation in bounded steps.
52
- 6. Use `$deep`-style runtime verification when the mission needs dev server, tmux, test watcher, browser QA, cleanup, or before/after evidence.
53
- 7. Cross-check findings and resolve contradictions.
54
- 8. Run the smallest honest final verification set.
55
- 9. Run doctor scan with `references/doctor-scan.md`.
56
- 10. Confirm cleanup or explicitly report intentionally running processes.
57
- 11. Produce final proof summary, truth status, remaining tasks, and fix-first items.
52
+ 6. Record a compact patch envelope for each real lane that changes code.
53
+ 7. Add rollback hints for touched files, generated files, before checks, and safe revert notes.
54
+ 8. Use `$deep`-style runtime verification when the mission needs dev server, tmux, test watcher, browser QA, cleanup, or before/after evidence.
55
+ 9. Cross-check findings and resolve contradictions.
56
+ 10. Run the smallest honest final verification set.
57
+ 11. Run doctor scan with `references/doctor-scan.md`.
58
+ 12. Confirm cleanup or explicitly report intentionally running processes.
59
+ 13. Produce final proof summary, truth status, remaining tasks, and fix-first items.
58
60
 
59
61
  ## Proof Summary
60
62
 
@@ -64,6 +66,8 @@ Include:
64
66
  - Role work completed.
65
67
  - Subagent decision: used / downgraded_to_deep / unavailable_partial / blocked, with reason.
66
68
  - Files or surfaces changed.
69
+ - Patch envelope for each real code-changing lane.
70
+ - Rollback hint for risky or multi-file changes.
67
71
  - Runtime/tmux/browser evidence when used.
68
72
  - Cross-verification result.
69
73
  - Doctor/scanner result.
@@ -77,13 +77,16 @@ Use these artifacts for reference-led implementation or serious visual review. K
77
77
  - reference, before, and after evidence when available.
78
78
  - path or URL, source type, state, viewport, and visual verification cap.
79
79
  - sha256 and dimensions when an image file is locally available.
80
- 2. Reference Read Proof:
80
+ 2. Visual Provenance:
81
+ - source kind, path, hash, reference id, screenshot id, redaction/locality flags, operator-provided flag, comparison result, and truth status.
82
+ - use this to prove the route from reference reading to implementation recheck.
83
+ 3. Reference Read Proof:
81
84
  - concrete observations from the reference before implementation.
82
85
  - no implementation claim should depend on an unrecorded reference observation.
83
- 3. Reference vs Implementation Matrix:
86
+ 4. Reference vs Implementation Matrix:
84
87
  - compare the implemented result against the reference by aspect.
85
88
  - record matched, similar, different, not-applicable, or not-verified.
86
- 4. Design Quality Review:
89
+ 5. Design Quality Review:
87
90
  - judge whether the result is good UI/UX/design after reference comparison.
88
91
  - use pass, needs-polish, or fails for each relevant design dimension.
89
92
 
package/src/bin/yam.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
2
3
  import fs from 'node:fs';
3
4
  import fsp from 'node:fs/promises';
4
5
  import os from 'node:os';
@@ -7,6 +8,9 @@ import process from 'node:process';
7
8
  import { fileURLToPath } from 'node:url';
8
9
  import {
9
10
  TRUTH_STATUSES,
11
+ buildMissionPatchEnvelope,
12
+ buildRollbackHint,
13
+ buildUeyeVisualProvenance,
10
14
  buildYamCompletionProof,
11
15
  detectDbSafetyText as detectTrustDbSafetyText,
12
16
  isTruthStatus
@@ -128,6 +132,7 @@ Usage:
128
132
  yam tools doctor [dir]
129
133
  yam proof [dir|--from file] [--route route] [--truth status] [--command text] [--evidence text]
130
134
  yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
135
+ yam release report [--json]
131
136
  yam safety [text...]
132
137
  yam memory <init|add|list|summary|resolve> [dir] [options]
133
138
  yam hook <status|enable|disable|run> [lite] [--global|--project dir]
@@ -919,6 +924,85 @@ async function safety(args = []) {
919
924
  printSafetyResult(result);
920
925
  }
921
926
 
927
+ async function release(args = []) {
928
+ const subcommand = args[0] || 'help';
929
+ if (subcommand !== 'report') {
930
+ console.error('usage: yam release report [--json]');
931
+ process.exitCode = 1;
932
+ return;
933
+ }
934
+ const asJson = args.includes('--json');
935
+ const report = runReleaseReport();
936
+ if (asJson) {
937
+ console.log(JSON.stringify(report, null, 2));
938
+ if (!report.ok) process.exitCode = 1;
939
+ return;
940
+ }
941
+ console.log('yam release report');
942
+ console.log(`- Package: ${report.packageName}@${report.version}`);
943
+ console.log(`- Truth status: ${report.truth_status}`);
944
+ for (const check of report.checks) {
945
+ console.log(`- ${check.id}: ${check.status} (${check.duration_ms}ms)`);
946
+ if (check.note) console.log(` ${check.note}`);
947
+ }
948
+ if (report.failed.length) {
949
+ console.log('- Failed:');
950
+ for (const id of report.failed) console.log(` - ${id}`);
951
+ }
952
+ if (!report.ok) process.exitCode = 1;
953
+ }
954
+
955
+ function runReleaseReport() {
956
+ const startedAt = new Date().toISOString();
957
+ const checks = [
958
+ ['typecheck', ['npm', ['run', 'typecheck']]],
959
+ ['forbidden_names', ['npm', ['run', 'forbidden-names:check']]],
960
+ ['package_boundary', ['npm', ['run', 'package-boundary:check']]],
961
+ ['registry_status', ['npm', ['run', 'registry:check']]],
962
+ ['cli_smoke', ['npm', ['run', 'cli-smoke']]],
963
+ ['dist_freshness', ['npm', ['run', 'dist:freshness']]]
964
+ ].map(([id, command]) => runReleaseCheck(id, command));
965
+ const failed = checks.filter((check) => check.status !== 'passed').map((check) => check.id);
966
+ return {
967
+ schema: 'yam.release-report.v1',
968
+ generated_at: startedAt,
969
+ packageName: PACKAGE_JSON.name,
970
+ version: VERSION,
971
+ ok: failed.length === 0,
972
+ truth_status: failed.length === 0 ? 'verified' : 'blocked',
973
+ checks,
974
+ failed
975
+ };
976
+ }
977
+
978
+ function runReleaseCheck(id, commandSpec) {
979
+ const [command, args] = commandSpec;
980
+ const start = Date.now();
981
+ const result = spawnSync(command, args, {
982
+ cwd: ROOT,
983
+ encoding: 'utf8',
984
+ env: { ...process.env, npm_config_cache: path.join(os.tmpdir(), 'yam-npm-cache') },
985
+ timeout: 120000
986
+ });
987
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
988
+ const timedOut = result.error?.message?.includes('ETIMEDOUT') || result.signal === 'SIGTERM';
989
+ return {
990
+ id,
991
+ command: [command, ...args].join(' '),
992
+ status: result.status === 0 ? 'passed' : timedOut ? 'blocked' : 'failed',
993
+ exit_code: typeof result.status === 'number' ? result.status : null,
994
+ duration_ms: Date.now() - start,
995
+ note: summarizeCheckOutput(output || result.error?.message || '')
996
+ };
997
+ }
998
+
999
+ function summarizeCheckOutput(output = '') {
1000
+ const lines = String(output || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1001
+ if (!lines.length) return '';
1002
+ const highSignal = lines.filter((line) => !line.startsWith('>')).slice(-4);
1003
+ return highSignal.join(' | ').slice(0, 600);
1004
+ }
1005
+
922
1006
  async function readStdinTextIfAvailable() {
923
1007
  if (process.stdin.isTTY) return '';
924
1008
  const chunks = [];
@@ -966,6 +1050,9 @@ async function proof(args = []) {
966
1050
  printProofList('Evidence', summary.evidence);
967
1051
  printProofList('Visual evidence', summary.visual);
968
1052
  printProofList('Runtime evidence', summary.runtime);
1053
+ printProofList('Visual provenance', summary.visualProvenance || []);
1054
+ printProofList('Mission patch envelope', summary.missionEnvelope || []);
1055
+ printProofList('Rollback hint', summary.rollbackHint || []);
969
1056
  printProofList('Changed surfaces', summary.changed);
970
1057
  printProofList('Skipped', summary.skipped);
971
1058
  printProofList('Blocked', summary.blocked);
@@ -1040,6 +1127,18 @@ function buildProofSummary(parsed, artifact) {
1040
1127
  evidence: [...artifact.evidence, ...arrayFlag(parsed.flags.evidence)],
1041
1128
  visual: [...artifact.visual, ...arrayFlag(parsed.flags.visual)],
1042
1129
  runtime: [...artifact.runtime, ...arrayFlag(parsed.flags.runtime)],
1130
+ visualProvenance: [
1131
+ ...structuredEvidenceList(artifact.visualProvenance),
1132
+ ...structuredEvidenceList(parsed.flags.visual_provenance)
1133
+ ],
1134
+ missionEnvelope: [
1135
+ ...structuredEvidenceList(artifact.missionEnvelope),
1136
+ ...structuredEvidenceList(parsed.flags.mission_envelope)
1137
+ ],
1138
+ rollbackHint: [
1139
+ ...structuredEvidenceList(artifact.rollbackHint),
1140
+ ...structuredEvidenceList(parsed.flags.rollback_hint)
1141
+ ],
1043
1142
  cleanup: parsed.flags.cleanup || artifact.cleanup || '',
1044
1143
  changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
1045
1144
  skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
@@ -1090,6 +1189,15 @@ function renderProofMarkdown(summary) {
1090
1189
  '## Runtime evidence',
1091
1190
  ...renderProofMarkdownList(summary.runtime),
1092
1191
  '',
1192
+ '## Visual provenance',
1193
+ ...renderProofMarkdownList(summary.visualProvenance || []),
1194
+ '',
1195
+ '## Mission patch envelope',
1196
+ ...renderProofMarkdownList(summary.missionEnvelope || []),
1197
+ '',
1198
+ '## Rollback hint',
1199
+ ...renderProofMarkdownList(summary.rollbackHint || []),
1200
+ '',
1093
1201
  '## Changed surfaces',
1094
1202
  ...renderProofMarkdownList(summary.changed),
1095
1203
  '',
@@ -1122,7 +1230,7 @@ function renderProofMarkdownList(values) {
1122
1230
  function parseProofArgs(args = []) {
1123
1231
  const flags: AnyRecord = {};
1124
1232
  const positionals: string[] = [];
1125
- const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
1233
+ const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
1126
1234
  for (let index = 0; index < args.length; index += 1) {
1127
1235
  const arg = args[index];
1128
1236
  if (arg.startsWith('--')) {
@@ -1136,7 +1244,7 @@ function parseProofArgs(args = []) {
1136
1244
  }
1137
1245
  const value = inlineValue ?? args[index + 1] ?? '';
1138
1246
  if (inlineValue === undefined) index += 1;
1139
- if (['command', 'evidence', 'visual', 'runtime', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
1247
+ if (['command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
1140
1248
  flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
1141
1249
  } else {
1142
1250
  flags[normalizedKey] = value;
@@ -1174,6 +1282,9 @@ function emptyProofArtifact() {
1174
1282
  evidence: [],
1175
1283
  visual: [],
1176
1284
  runtime: [],
1285
+ visualProvenance: [],
1286
+ missionEnvelope: [],
1287
+ rollbackHint: [],
1177
1288
  cleanup: '',
1178
1289
  changed: [],
1179
1290
  skipped: [],
@@ -1196,6 +1307,9 @@ async function parseProofJson(file, empty) {
1196
1307
  evidence: arrayFlag(data.evidence),
1197
1308
  visual: arrayFlag(data.visual || data.visualEvidence),
1198
1309
  runtime: arrayFlag(data.runtime || data.runtimeEvidence),
1310
+ visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
1311
+ missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
1312
+ rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
1199
1313
  cleanup: String(data.cleanup || data.cleanupStatus || ''),
1200
1314
  changed: arrayFlag(data.changed || data.files),
1201
1315
  skipped: arrayFlag(data.skipped),
@@ -1221,6 +1335,9 @@ async function parseProofMarkdown(file, empty) {
1221
1335
  evidence: readProofList(text, 'Evidence'),
1222
1336
  visual: readProofList(text, 'Visual evidence'),
1223
1337
  runtime: firstProofList(text, ['Runtime evidence', 'Processes', 'tmux']),
1338
+ visualProvenance: readProofList(text, 'Visual provenance'),
1339
+ missionEnvelope: readProofList(text, 'Mission patch envelope'),
1340
+ rollbackHint: readProofList(text, 'Rollback hint'),
1224
1341
  cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
1225
1342
  changed: firstProofList(text, ['Changed surfaces', 'Files']),
1226
1343
  skipped: readProofList(text, 'Skipped'),
@@ -1267,9 +1384,36 @@ function arrayFlag(value) {
1267
1384
  return Array.isArray(value) ? value.filter(Boolean) : [value].filter(Boolean);
1268
1385
  }
1269
1386
 
1387
+ function structuredEvidenceList(value) {
1388
+ return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
1389
+ }
1390
+
1391
+ function formatStructuredEvidence(value) {
1392
+ if (!value) return '';
1393
+ if (typeof value === 'object') return JSON.stringify(value);
1394
+ const text = String(value).trim();
1395
+ if (!text) return '';
1396
+ if (!text.startsWith('{')) return text;
1397
+ try {
1398
+ const data = JSON.parse(text);
1399
+ if (data.schema === 'yam.ueye-visual-provenance.v1' || data.source_kind || data.reference_id || data.screenshot_id) {
1400
+ return JSON.stringify(buildUeyeVisualProvenance(data));
1401
+ }
1402
+ if (data.schema === 'yam.mission-patch-envelope.v1' || data.agent_id || data.assigned_scope) {
1403
+ return JSON.stringify(buildMissionPatchEnvelope(data));
1404
+ }
1405
+ if (data.schema === 'yam.rollback-hint.v1' || data.touched_files || data.safe_revert_note) {
1406
+ return JSON.stringify(buildRollbackHint(data));
1407
+ }
1408
+ return JSON.stringify(data);
1409
+ } catch {
1410
+ return text;
1411
+ }
1412
+ }
1413
+
1270
1414
  function hasProofEvidence(parsed) {
1271
1415
  const flags = parsed.flags || {};
1272
- return ['command', 'evidence', 'visual', 'runtime', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup);
1416
+ return ['command', 'evidence', 'visual', 'runtime', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup);
1273
1417
  }
1274
1418
 
1275
1419
  function printProofList(label, values) {
@@ -1961,6 +2105,7 @@ async function main() {
1961
2105
  if (command === 'measure') return measure(process.argv[3], process.argv.slice(4));
1962
2106
  if (command === 'tools') return tools(process.argv.slice(3));
1963
2107
  if (command === 'proof') return proof(process.argv.slice(3));
2108
+ if (command === 'release') return release(process.argv.slice(3));
1964
2109
  if (command === 'safety') return safety(process.argv.slice(3));
1965
2110
  if (command === 'memory') return memory(process.argv.slice(3));
1966
2111
  if (command === 'hook') return hook(process.argv.slice(3));
@@ -47,6 +47,38 @@ export interface RuntimeTruthMatrix {
47
47
  blockers: string[];
48
48
  }
49
49
 
50
+ export interface UeyeVisualProvenance {
51
+ schema: 'yam.ueye-visual-provenance.v1';
52
+ source_kind: string;
53
+ source_path: string;
54
+ source_hash: string;
55
+ reference_id: string;
56
+ screenshot_id: string;
57
+ local_only: boolean;
58
+ redacted: boolean;
59
+ operator_provided: boolean;
60
+ comparison_result: string;
61
+ truth_status: TruthStatus;
62
+ }
63
+
64
+ export interface RollbackHint {
65
+ schema: 'yam.rollback-hint.v1';
66
+ touched_files: string[];
67
+ generated_files: string[];
68
+ before_check: string;
69
+ safe_revert_note: string;
70
+ }
71
+
72
+ export interface MissionPatchEnvelope {
73
+ schema: 'yam.mission-patch-envelope.v1';
74
+ agent_id: string;
75
+ assigned_scope: string;
76
+ changed_files: string[];
77
+ verification_hint: string;
78
+ rollback_hint: RollbackHint;
79
+ truth_status: TruthStatus;
80
+ }
81
+
50
82
  export interface FakeRealPolicyResult {
51
83
  schema: 'yam.fake-real-policy.v1';
52
84
  ok: boolean;
@@ -71,6 +103,9 @@ export interface ProofSummary {
71
103
  blocked?: unknown;
72
104
  assumptions?: unknown;
73
105
  unverified?: unknown;
106
+ visualProvenance?: unknown;
107
+ missionEnvelope?: unknown;
108
+ rollbackHint?: unknown;
74
109
  }
75
110
 
76
111
  export interface ProofOptions {
@@ -101,6 +136,9 @@ export interface YamCompletionProof {
101
136
  evidence: string[];
102
137
  visual: string[];
103
138
  runtime: string[];
139
+ visualProvenance: string[];
140
+ missionEnvelope: string[];
141
+ rollbackHint: string[];
104
142
  cleanup: unknown;
105
143
  changed: string[];
106
144
  skipped: string[];
@@ -113,6 +151,48 @@ export interface YamCompletionProof {
113
151
  runtimeTruth: RuntimeTruthMatrix;
114
152
  }
115
153
 
154
+ export function buildUeyeVisualProvenance(input: Partial<UeyeVisualProvenance> = {}): UeyeVisualProvenance {
155
+ const truth = isTruthStatus(input.truth_status) ? input.truth_status : 'partial';
156
+ return {
157
+ schema: 'yam.ueye-visual-provenance.v1',
158
+ source_kind: String(input.source_kind || ''),
159
+ source_path: String(input.source_path || ''),
160
+ source_hash: String(input.source_hash || 'unknown'),
161
+ reference_id: String(input.reference_id || ''),
162
+ screenshot_id: String(input.screenshot_id || ''),
163
+ local_only: Boolean(input.local_only),
164
+ redacted: Boolean(input.redacted),
165
+ operator_provided: Boolean(input.operator_provided),
166
+ comparison_result: String(input.comparison_result || 'not-verified'),
167
+ truth_status: truth
168
+ };
169
+ }
170
+
171
+ export function buildRollbackHint(input: Partial<RollbackHint> = {}): RollbackHint {
172
+ return {
173
+ schema: 'yam.rollback-hint.v1',
174
+ touched_files: asList(input.touched_files),
175
+ generated_files: asList(input.generated_files),
176
+ before_check: String(input.before_check || ''),
177
+ safe_revert_note: String(input.safe_revert_note || '')
178
+ };
179
+ }
180
+
181
+ export function buildMissionPatchEnvelope(input: Partial<MissionPatchEnvelope> = {}): MissionPatchEnvelope {
182
+ const rollback = input.rollback_hint && typeof input.rollback_hint === 'object'
183
+ ? buildRollbackHint(input.rollback_hint)
184
+ : buildRollbackHint();
185
+ return {
186
+ schema: 'yam.mission-patch-envelope.v1',
187
+ agent_id: String(input.agent_id || ''),
188
+ assigned_scope: String(input.assigned_scope || ''),
189
+ changed_files: asList(input.changed_files),
190
+ verification_hint: String(input.verification_hint || ''),
191
+ rollback_hint: rollback,
192
+ truth_status: isTruthStatus(input.truth_status) ? input.truth_status : 'partial'
193
+ };
194
+ }
195
+
116
196
  const TRUTH_RANK: Readonly<Record<TruthStatus, number>> = Object.freeze({
117
197
  blocked: 0,
118
198
  real_required_missing: 0,
@@ -184,6 +264,15 @@ export function classifyEvidenceTruth(summary: ProofSummary = {}, options: Proof
184
264
  for (const visual of asList(summary.visual)) {
185
265
  rows.push(evidenceRow('visual', visual, classifyVisualEvidence(visual)));
186
266
  }
267
+ for (const provenance of asList(summary.visualProvenance)) {
268
+ rows.push(evidenceRow('visual', provenance, metadataTruth(provenance, 'partial')));
269
+ }
270
+ for (const envelope of asList(summary.missionEnvelope)) {
271
+ rows.push(evidenceRow('evidence', envelope, metadataTruth(envelope, 'partial')));
272
+ }
273
+ for (const rollback of asList(summary.rollbackHint)) {
274
+ rows.push(evidenceRow('evidence', rollback, metadataTruth(rollback, 'partial')));
275
+ }
187
276
  for (const runtime of asList(summary.runtime)) {
188
277
  rows.push(evidenceRow('runtime', runtime, classifyRuntimeEvidence(runtime, options)));
189
278
  }
@@ -267,6 +356,9 @@ export function buildYamCompletionProof(summary: ProofSummary = {}, options: Pro
267
356
  evidence: asList(summary.evidence),
268
357
  visual: asList(summary.visual),
269
358
  runtime: asList(summary.runtime),
359
+ visualProvenance: asList(summary.visualProvenance),
360
+ missionEnvelope: asList(summary.missionEnvelope),
361
+ rollbackHint: asList(summary.rollbackHint),
270
362
  cleanup: summary.cleanup || '',
271
363
  changed: asList(summary.changed),
272
364
  skipped: asList(summary.skipped),
@@ -317,6 +409,25 @@ function commandPassed(value: unknown = ''): boolean {
317
409
  return /pass|passed|ok|success|exit\s*0|green/i.test(String(value || '')) && !/fail|failed|error|blocked/i.test(String(value || ''));
318
410
  }
319
411
 
412
+ function metadataTruth(value: unknown = '', fallback: TruthStatus = 'partial'): TruthStatus {
413
+ return weakestTruth(readStructuredTruth(value, fallback), 'partial');
414
+ }
415
+
416
+ function readStructuredTruth(value: unknown = '', fallback: TruthStatus = 'partial'): TruthStatus {
417
+ if (typeof value === 'object' && value) {
418
+ const candidate = (value as { truth_status?: unknown; truth?: unknown }).truth_status || (value as { truth?: unknown }).truth;
419
+ return isTruthStatus(candidate) ? candidate : fallback;
420
+ }
421
+ const text = String(value || '');
422
+ try {
423
+ const data = JSON.parse(text);
424
+ const candidate = data.truth_status || data.truth;
425
+ return isTruthStatus(candidate) ? candidate : fallback;
426
+ } catch {
427
+ return fallback;
428
+ }
429
+ }
430
+
320
431
  function strongestSupportedTruth(rows: EvidenceRow[] = []): TruthStatus {
321
432
  if (rows.some((row) => row.truth === 'blocked')) return 'blocked';
322
433
  if (rows.some((row) => row.truth === 'real_required_missing')) return 'real_required_missing';
@@ -29,6 +29,19 @@ Subagent 판단:
29
29
  - 이유:
30
30
  - subagent가 불가능하거나 불필요하면 기본적으로 $deep으로 전환:
31
31
 
32
+ Patch envelope:
33
+ - agent_id:
34
+ - assigned_scope:
35
+ - changed_files:
36
+ - verification_hint:
37
+ - truth_status: verified / partial / skipped / blocked / assumed
38
+
39
+ Rollback hint:
40
+ - touched_files:
41
+ - generated_files:
42
+ - before_check:
43
+ - safe_revert_note:
44
+
32
45
  검증:
33
46
  - 필요한 가장 작은 검증 명령을 우선 실행
34
47
  - 필요하면 tmux/dev server/browser QA/process cleanup proof 사용
@@ -14,6 +14,21 @@
14
14
  | implementation | | | unknown | unknown | | | proof |
15
15
  | reference | | | unknown | unknown | | | reference direction |
16
16
 
17
+ ## Visual Provenance
18
+
19
+ | Field | Value |
20
+ | --- | --- |
21
+ | source_kind | |
22
+ | source_path | |
23
+ | source_hash | unknown |
24
+ | reference_id | |
25
+ | screenshot_id | |
26
+ | local_only | true / false |
27
+ | redacted | true / false |
28
+ | operator_provided | true / false |
29
+ | comparison_result | matched / similar / different / not-verified / not-applicable |
30
+ | truth_status | verified / partial / skipped / blocked / assumed |
31
+
17
32
  ## Reference Read Proof
18
33
 
19
34
  - Layout:
@@ -25,6 +25,21 @@
25
25
  - Role: proof / reference direction / annotation / partial evidence
26
26
  - Visual verification cap:
27
27
 
28
+ ## Visual Provenance
29
+
30
+ | Field | Value |
31
+ | --- | --- |
32
+ | source_kind | |
33
+ | source_path | |
34
+ | source_hash | unknown |
35
+ | reference_id | |
36
+ | screenshot_id | |
37
+ | local_only | true / false |
38
+ | redacted | true / false |
39
+ | operator_provided | true / false |
40
+ | comparison_result | matched / similar / different / not-verified / not-applicable |
41
+ | truth_status | verified / partial / skipped / blocked / assumed |
42
+
28
43
  ## Reference Read Proof
29
44
 
30
45
  - Layout:
package/yam.manifest.json CHANGED
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "id": "ueye",
24
24
  "stage": "v0.3",
25
- "purpose": "Tight UI/UX/design implementation and visual review with reference-read proof, visual comparison, design quality review, and P0-P3 issue tracking."
25
+ "purpose": "Tight UI/UX/design implementation and visual review with visual provenance, reference-read proof, visual comparison, design quality review, and P0-P3 issue tracking."
26
26
  },
27
27
  {
28
28
  "id": "question",
@@ -42,7 +42,7 @@
42
42
  {
43
43
  "id": "mission",
44
44
  "stage": "v0.2",
45
- "purpose": "Explicit real-subagent/team execution route for approved plans, cross-verification, doctor scan, and final proof."
45
+ "purpose": "Explicit real-subagent/team execution route for approved plans, patch envelopes, rollback hints, cross-verification, doctor scan, and final proof."
46
46
  }
47
47
  ]
48
48
  }