yam-flow 2.0.0 → 2.2.0
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/AGENTS.md +7 -5
- package/CHANGELOG.md +26 -0
- package/COMMANDS.md +10 -4
- package/DECISIONS.md +3 -0
- package/README.md +17 -9
- package/dist/bin/yam.js +211 -30
- package/dist/lib/trust-kernel.d.ts +31 -1
- package/dist/lib/trust-kernel.js +128 -6
- package/package.json +2 -1
- package/references/final-report.md +17 -11
- package/references/honest-completion.md +13 -0
- package/references/study-note.md +69 -0
- package/references/tool-trust-layer.md +6 -1
- package/references/ueye-proof.md +42 -1
- package/references/ueye.md +29 -3
- package/references/verification-levels.md +130 -25
- package/skills/deep/SKILL.md +10 -2
- package/skills/mission/SKILL.md +10 -1
- package/skills/quick/SKILL.md +6 -2
- package/skills/ueye/SKILL.md +5 -1
- package/src/bin/yam.ts +217 -31
- package/src/lib/trust-kernel.ts +155 -7
- package/templates/yam.project.md +5 -1
- package/yam.manifest.json +6 -3
package/dist/bin/yam.js
CHANGED
|
@@ -50,6 +50,7 @@ const PROJECT_PACK = 'yam.project.md';
|
|
|
50
50
|
const LEGACY_PROJECT_PACK = 'timeto.project.md';
|
|
51
51
|
const PACK_STALE_DAYS = 30;
|
|
52
52
|
const YAM_LITE_HOOK_COMMAND = `node ${path.join(ROOT, 'dist', 'bin', 'yam.js')} hook run lite`;
|
|
53
|
+
const YAM_STUDY_NOTE_HOOK_COMMAND = `node ${path.join(ROOT, 'dist', 'bin', 'yam.js')} hook run study-note`;
|
|
53
54
|
const REQUIRED_PACK_SECTIONS = [
|
|
54
55
|
'Product Direction',
|
|
55
56
|
'UI Direction',
|
|
@@ -122,6 +123,7 @@ Usage:
|
|
|
122
123
|
yam tools doctor [dir]
|
|
123
124
|
yam proof [dir|--from file] [--route route] [--truth status] [--command text] [--evidence text]
|
|
124
125
|
yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
|
|
126
|
+
yam study-note check [dir] [--report file|--text text] [--json]
|
|
125
127
|
yam loop report [--route route] [--intent text] [--stage id:status:note] [--evidence text] [--json]
|
|
126
128
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
127
129
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
@@ -134,7 +136,7 @@ Usage:
|
|
|
134
136
|
yam release report [--json]
|
|
135
137
|
yam safety [text...]
|
|
136
138
|
yam memory <init|add|list|summary|resolve> [dir] [options]
|
|
137
|
-
yam hook <status|enable|disable|run> [lite] [--global|--project dir]
|
|
139
|
+
yam hook <status|enable|disable|run> [lite|study-note] [--global|--project dir]
|
|
138
140
|
yam template <project|ueye|mission|proof|tuning>
|
|
139
141
|
yam tune-log [dir]
|
|
140
142
|
yam install
|
|
@@ -318,6 +320,7 @@ async function verify({ quiet = false } = {}) {
|
|
|
318
320
|
'token-economy.md',
|
|
319
321
|
'context-reuse.md',
|
|
320
322
|
'markdown-management.md',
|
|
323
|
+
'study-note.md',
|
|
321
324
|
'final-report.md',
|
|
322
325
|
'token-budget-reporter.md',
|
|
323
326
|
'memory.md'
|
|
@@ -530,6 +533,12 @@ function normalizeLoopEvidenceLevelFlag(value = '') {
|
|
|
530
533
|
return text;
|
|
531
534
|
return 'none';
|
|
532
535
|
}
|
|
536
|
+
function normalizeReadinessStateFlag(value = '') {
|
|
537
|
+
const text = String(value || '').toLowerCase().replace(/[-\s]/g, '_');
|
|
538
|
+
if (['usable', 'degraded', 'blocked', 'unknown'].includes(text))
|
|
539
|
+
return text;
|
|
540
|
+
return 'unknown';
|
|
541
|
+
}
|
|
533
542
|
function uniqueNextActionDetails(actions = []) {
|
|
534
543
|
const seen = new Set();
|
|
535
544
|
const unique = [];
|
|
@@ -707,6 +716,124 @@ async function cleanup(args = []) {
|
|
|
707
716
|
console.error(`unknown cleanup command: ${subcommand}`);
|
|
708
717
|
return cleanupUsage();
|
|
709
718
|
}
|
|
719
|
+
async function studyNote(args = []) {
|
|
720
|
+
const subcommand = args[0] || 'help';
|
|
721
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
722
|
+
return studyNoteUsage();
|
|
723
|
+
if (subcommand === 'check')
|
|
724
|
+
return studyNoteCheck(args.slice(1));
|
|
725
|
+
console.error(`unknown study-note command: ${subcommand}`);
|
|
726
|
+
return studyNoteUsage();
|
|
727
|
+
}
|
|
728
|
+
function studyNoteUsage() {
|
|
729
|
+
console.log(`yam study-note
|
|
730
|
+
|
|
731
|
+
Usage:
|
|
732
|
+
yam study-note check [dir] [--report file|--text text] [--json]
|
|
733
|
+
|
|
734
|
+
Notes:
|
|
735
|
+
Read-only guard. It checks whether changed work has a Study Note and whether relevant UI/CSS/DB hygiene was reported.
|
|
736
|
+
It does not generate, edit, or infer the Study Note for you.
|
|
737
|
+
`);
|
|
738
|
+
}
|
|
739
|
+
async function studyNoteCheck(args = []) {
|
|
740
|
+
const flags = parseSimpleFlags(args, new Set(['report', 'text', 'json']));
|
|
741
|
+
const dir = path.resolve(firstPositional(args) || process.cwd());
|
|
742
|
+
const changedFiles = await gitChangedFiles(dir);
|
|
743
|
+
const reportText = await readStudyNoteReportText(flags, dir);
|
|
744
|
+
const needsStudyNote = changedFiles.length > 0;
|
|
745
|
+
const hygieneRequirements = studyNoteHygieneRequirements(changedFiles);
|
|
746
|
+
const checks = [
|
|
747
|
+
studyNoteGuardCheck('changed_files', needsStudyNote ? 'pass' : 'skipped', needsStudyNote ? 'changed files detected; Study Note is required before final completion' : 'no changed files detected'),
|
|
748
|
+
studyNoteGuardCheck('study_note_present', !needsStudyNote || hasStudyNoteMarker(reportText) ? 'pass' : 'fail', 'final report should include a Study Note when project artifacts changed'),
|
|
749
|
+
studyNoteGuardCheck('role_or_responsibility', !needsStudyNote || /(\brole\b|\bresponsib|\bdoes\b|역할|기능)/i.test(reportText) ? 'pass' : 'fail', 'Study Note should explain what the touched code/artifact does'),
|
|
750
|
+
studyNoteGuardCheck('before_after_or_change', !needsStudyNote || /(\bbefore\b|\bafter\b|before\/after|changed|change meaning|바뀌|변경|수정)/i.test(reportText) ? 'pass' : 'fail', 'Study Note should explain what changed from before to after'),
|
|
751
|
+
studyNoteGuardCheck('verification_or_limits', !needsStudyNote || /(verification|verified|checked|limits|uncertain|검증|확인|한계|모르는)/i.test(reportText) ? 'pass' : 'fail', 'Study Note should say what was checked and what remains uncertain')
|
|
752
|
+
];
|
|
753
|
+
for (const requirement of hygieneRequirements) {
|
|
754
|
+
checks.push(studyNoteGuardCheck(requirement.id, requirementSatisfied(reportText, requirement), requirement.note));
|
|
755
|
+
}
|
|
756
|
+
const blockers = checks.filter((check) => check.status === 'fail').map((check) => `${check.id}: ${check.next_action}`);
|
|
757
|
+
const result = {
|
|
758
|
+
schema: 'yam.study-note-guard.v1',
|
|
759
|
+
generated_at: new Date().toISOString(),
|
|
760
|
+
project: dir,
|
|
761
|
+
changed_files: changedFiles,
|
|
762
|
+
changed_file_count: changedFiles.length,
|
|
763
|
+
report_source: flags.report ? path.resolve(dir, String(flags.report)) : flags.text ? 'inline_text' : 'not_provided',
|
|
764
|
+
checks,
|
|
765
|
+
blockers,
|
|
766
|
+
next_action: blockers[0] || (needsStudyNote ? 'Study Note guard passed for the supplied report text' : 'no Study Note required because no changed files were detected'),
|
|
767
|
+
truth_status: blockers.length ? 'blocked' : needsStudyNote ? 'verified' : 'skipped'
|
|
768
|
+
};
|
|
769
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Study Note guard');
|
|
770
|
+
if (blockers.length)
|
|
771
|
+
process.exitCode = 1;
|
|
772
|
+
}
|
|
773
|
+
function firstPositional(args = []) {
|
|
774
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
775
|
+
const arg = String(args[index] || '');
|
|
776
|
+
if (arg.startsWith('--')) {
|
|
777
|
+
if (!arg.includes('=') && !['--json'].includes(arg))
|
|
778
|
+
index += 1;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (arg === 'check')
|
|
782
|
+
continue;
|
|
783
|
+
return arg;
|
|
784
|
+
}
|
|
785
|
+
return '';
|
|
786
|
+
}
|
|
787
|
+
async function gitChangedFiles(dir) {
|
|
788
|
+
const output = runReadOnlyCommandIn(dir, 'git', ['status', '--short']);
|
|
789
|
+
return output
|
|
790
|
+
.split(/\r?\n/)
|
|
791
|
+
.filter(Boolean)
|
|
792
|
+
.map((line) => line.replace(/^..\s+/, '').replace(/^.* -> /, ''))
|
|
793
|
+
.map((line) => line.trim())
|
|
794
|
+
.filter((file) => file && !file.startsWith('dist/') && !file.endsWith('.tgz'));
|
|
795
|
+
}
|
|
796
|
+
async function readStudyNoteReportText(flags, dir) {
|
|
797
|
+
if (flags.text)
|
|
798
|
+
return String(flags.text || '');
|
|
799
|
+
if (!flags.report)
|
|
800
|
+
return '';
|
|
801
|
+
const file = path.resolve(dir, String(flags.report));
|
|
802
|
+
try {
|
|
803
|
+
return await fsp.readFile(file, 'utf8');
|
|
804
|
+
}
|
|
805
|
+
catch {
|
|
806
|
+
return '';
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function hasStudyNoteMarker(text = '') {
|
|
810
|
+
return /(study note|study-note|학습\s*노트|스터디\s*노트)/i.test(text);
|
|
811
|
+
}
|
|
812
|
+
function studyNoteGuardCheck(id, status, nextAction) {
|
|
813
|
+
return {
|
|
814
|
+
id,
|
|
815
|
+
status,
|
|
816
|
+
next_action: nextAction,
|
|
817
|
+
truth_status: status === 'pass' ? 'verified' : status === 'fail' ? 'blocked' : status === 'skipped' ? 'skipped' : 'partial'
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function studyNoteHygieneRequirements(files = []) {
|
|
821
|
+
const requirements = [];
|
|
822
|
+
if (files.some((file) => /(^|\/)page\.tsx$/i.test(file))) {
|
|
823
|
+
requirements.push({ id: 'page_tsx_hygiene', terms: ['page.tsx', 'component', 'hook', 'helper', 'server action', 'route'], note: 'Study Note should say whether page.tsx stayed focused or needs component/hook/helper/server-action separation' });
|
|
824
|
+
}
|
|
825
|
+
if (files.some((file) => /(^|\/)global\.css$/i.test(file))) {
|
|
826
|
+
requirements.push({ id: 'global_css_hygiene', terms: ['global.css', 'token', 'component', 'scoped', 'module', 'utility'], note: 'Study Note should say whether CSS belongs globally or should move to tokens/scoped/component styles' });
|
|
827
|
+
}
|
|
828
|
+
if (files.some((file) => /(migration|schema|db|database|sql|prisma|drizzle|supabase)/i.test(file))) {
|
|
829
|
+
requirements.push({ id: 'jsonb_hygiene', terms: ['jsonb', 'column', 'table', 'constraint', 'index', 'schema', 'relation'], note: 'Study Note should say whether structured product data belongs in typed columns/tables/constraints/indexes instead of broad DB jsonb' });
|
|
830
|
+
}
|
|
831
|
+
return requirements;
|
|
832
|
+
}
|
|
833
|
+
function requirementSatisfied(text = '', requirement) {
|
|
834
|
+
const normalized = String(text || '').toLowerCase();
|
|
835
|
+
return requirement.terms.some((term) => normalized.includes(term.toLowerCase())) ? 'pass' : 'fail';
|
|
836
|
+
}
|
|
710
837
|
function cleanupUsage() {
|
|
711
838
|
console.log(`yam cleanup
|
|
712
839
|
|
|
@@ -1270,20 +1397,20 @@ function hookUsage() {
|
|
|
1270
1397
|
|
|
1271
1398
|
Usage:
|
|
1272
1399
|
yam hook status [--global|--project dir]
|
|
1273
|
-
yam hook enable lite [--global|--project dir]
|
|
1274
|
-
yam hook disable [lite] [--global|--project dir]
|
|
1275
|
-
yam hook run lite
|
|
1400
|
+
yam hook enable <lite|study-note> [--global|--project dir]
|
|
1401
|
+
yam hook disable [lite|study-note] [--global|--project dir]
|
|
1402
|
+
yam hook run <lite|study-note>
|
|
1276
1403
|
|
|
1277
1404
|
Notes:
|
|
1278
|
-
|
|
1405
|
+
hooks are opt-in and advisory-only. They do not run tmux, subagents, or proof gates.
|
|
1279
1406
|
`);
|
|
1280
1407
|
}
|
|
1281
1408
|
function parseHookArgs(args = []) {
|
|
1282
1409
|
const result = { mode: 'project', projectDir: process.cwd(), profile: 'lite' };
|
|
1283
1410
|
for (let index = 0; index < args.length; index += 1) {
|
|
1284
1411
|
const arg = args[index];
|
|
1285
|
-
if (arg === 'lite') {
|
|
1286
|
-
result.profile =
|
|
1412
|
+
if (arg === 'lite' || arg === 'study-note') {
|
|
1413
|
+
result.profile = arg;
|
|
1287
1414
|
}
|
|
1288
1415
|
else if (arg === '--global') {
|
|
1289
1416
|
result.mode = 'global';
|
|
@@ -1308,13 +1435,19 @@ function hookPathFor(parsed) {
|
|
|
1308
1435
|
function isYamLiteHook(handler = {}) {
|
|
1309
1436
|
return handler?.type === 'command' && String(handler.command || '').includes('yam.js hook run lite');
|
|
1310
1437
|
}
|
|
1311
|
-
function
|
|
1438
|
+
function isYamStudyNoteHook(handler = {}) {
|
|
1439
|
+
return handler?.type === 'command' && String(handler.command || '').includes('yam.js hook run study-note');
|
|
1440
|
+
}
|
|
1441
|
+
function isYamHookProfile(handler = {}, profile = 'lite') {
|
|
1442
|
+
return profile === 'study-note' ? isYamStudyNoteHook(handler) : isYamLiteHook(handler);
|
|
1443
|
+
}
|
|
1444
|
+
function stripYamHooks(config = {}, profile = 'lite') {
|
|
1312
1445
|
const next = { ...config };
|
|
1313
1446
|
for (const event of Object.keys(next)) {
|
|
1314
1447
|
const entries = Array.isArray(next[event]) ? next[event] : [];
|
|
1315
1448
|
const keptEntries = [];
|
|
1316
1449
|
for (const entry of entries) {
|
|
1317
|
-
const hooks = Array.isArray(entry?.hooks) ? entry.hooks.filter((handler) => !
|
|
1450
|
+
const hooks = Array.isArray(entry?.hooks) ? entry.hooks.filter((handler) => !isYamHookProfile(handler, profile)) : [];
|
|
1318
1451
|
const rest = { ...entry, hooks };
|
|
1319
1452
|
if (hooks.length > 0)
|
|
1320
1453
|
keptEntries.push(rest);
|
|
@@ -1326,14 +1459,14 @@ function stripYamLiteHooks(config = {}) {
|
|
|
1326
1459
|
}
|
|
1327
1460
|
return next;
|
|
1328
1461
|
}
|
|
1329
|
-
function
|
|
1330
|
-
const next =
|
|
1462
|
+
function withYamHook(config = {}, profile = 'lite') {
|
|
1463
|
+
const next = stripYamHooks(config, profile);
|
|
1331
1464
|
const event = 'UserPromptSubmit';
|
|
1332
1465
|
const entry = {
|
|
1333
1466
|
hooks: [
|
|
1334
1467
|
{
|
|
1335
1468
|
type: 'command',
|
|
1336
|
-
command: YAM_LITE_HOOK_COMMAND,
|
|
1469
|
+
command: profile === 'study-note' ? YAM_STUDY_NOTE_HOOK_COMMAND : YAM_LITE_HOOK_COMMAND,
|
|
1337
1470
|
timeout: 5
|
|
1338
1471
|
}
|
|
1339
1472
|
]
|
|
@@ -1345,26 +1478,29 @@ async function hookStatus(args = []) {
|
|
|
1345
1478
|
const parsed = parseHookArgs(args);
|
|
1346
1479
|
const target = hookPathFor(parsed);
|
|
1347
1480
|
const config = await readJsonOrDefault(target, {});
|
|
1348
|
-
|
|
1349
|
-
console.log(`yam-
|
|
1481
|
+
console.log(`yam-lite hook: ${hookConfigHasProfile(config, 'lite') ? 'enabled' : 'disabled'}`);
|
|
1482
|
+
console.log(`yam-study-note hook: ${hookConfigHasProfile(config, 'study-note') ? 'enabled' : 'disabled'}`);
|
|
1350
1483
|
console.log(`scope: ${parsed.mode}`);
|
|
1351
1484
|
console.log(`file: ${target}`);
|
|
1352
1485
|
}
|
|
1353
1486
|
function hookConfigHasYamLite(config = {}) {
|
|
1487
|
+
return hookConfigHasProfile(config, 'lite');
|
|
1488
|
+
}
|
|
1489
|
+
function hookConfigHasProfile(config = {}, profile = 'lite') {
|
|
1354
1490
|
return Object.values(config).some((entries) => Array.isArray(entries) && entries.some((entry) => {
|
|
1355
|
-
return Array.isArray(entry?.hooks) && entry.hooks.some(
|
|
1491
|
+
return Array.isArray(entry?.hooks) && entry.hooks.some((handler) => isYamHookProfile(handler, profile));
|
|
1356
1492
|
}));
|
|
1357
1493
|
}
|
|
1358
1494
|
async function hookEnable(args = []) {
|
|
1359
1495
|
const parsed = parseHookArgs(args);
|
|
1360
|
-
if (parsed.profile
|
|
1361
|
-
console.error('Only
|
|
1496
|
+
if (!['lite', 'study-note'].includes(parsed.profile)) {
|
|
1497
|
+
console.error('Only lite and study-note hooks are supported: yam hook enable <lite|study-note>');
|
|
1362
1498
|
process.exitCode = 1;
|
|
1363
1499
|
return;
|
|
1364
1500
|
}
|
|
1365
1501
|
const target = hookPathFor(parsed);
|
|
1366
1502
|
const current = await readJsonOrDefault(target, {});
|
|
1367
|
-
const next =
|
|
1503
|
+
const next = withYamHook(current, parsed.profile);
|
|
1368
1504
|
await fsp.mkdir(path.dirname(target), { recursive: true });
|
|
1369
1505
|
if (await exists(target)) {
|
|
1370
1506
|
const backup = `${target}.yam-backup-${timestampId()}`;
|
|
@@ -1372,29 +1508,29 @@ async function hookEnable(args = []) {
|
|
|
1372
1508
|
console.log(`backup: ${backup}`);
|
|
1373
1509
|
}
|
|
1374
1510
|
await fsp.writeFile(target, `${JSON.stringify(next, null, 2)}\n`);
|
|
1375
|
-
console.log(`yam
|
|
1511
|
+
console.log(`yam-${parsed.profile} hook enabled (${parsed.mode}): ${target}`);
|
|
1376
1512
|
console.log('Restart Codex or start a new thread if the app does not pick up hook changes immediately.');
|
|
1377
1513
|
}
|
|
1378
1514
|
async function hookDisable(args = []) {
|
|
1379
1515
|
const parsed = parseHookArgs(args);
|
|
1380
1516
|
const target = hookPathFor(parsed);
|
|
1381
1517
|
const current = await readJsonOrDefault(target, {});
|
|
1382
|
-
if (!
|
|
1383
|
-
console.log(`yam
|
|
1518
|
+
if (!hookConfigHasProfile(current, parsed.profile)) {
|
|
1519
|
+
console.log(`yam-${parsed.profile} hook already disabled (${parsed.mode}): ${target}`);
|
|
1384
1520
|
return;
|
|
1385
1521
|
}
|
|
1386
|
-
const next =
|
|
1522
|
+
const next = stripYamHooks(current, parsed.profile);
|
|
1387
1523
|
if (Object.keys(next).length === 0) {
|
|
1388
1524
|
await rmrf(target);
|
|
1389
1525
|
}
|
|
1390
1526
|
else {
|
|
1391
1527
|
await fsp.writeFile(target, `${JSON.stringify(next, null, 2)}\n`);
|
|
1392
1528
|
}
|
|
1393
|
-
console.log(`yam
|
|
1529
|
+
console.log(`yam-${parsed.profile} hook disabled (${parsed.mode}): ${target}`);
|
|
1394
1530
|
}
|
|
1395
1531
|
async function hookRun(args = []) {
|
|
1396
1532
|
const profile = args[0] || 'lite';
|
|
1397
|
-
if (
|
|
1533
|
+
if (!['lite', 'study-note'].includes(profile)) {
|
|
1398
1534
|
console.log(JSON.stringify({ continue: true }));
|
|
1399
1535
|
return;
|
|
1400
1536
|
}
|
|
@@ -1402,7 +1538,9 @@ async function hookRun(args = []) {
|
|
|
1402
1538
|
const event = input?.hook_event_name || input?.hookEventName || input?.event || 'UserPromptSubmit';
|
|
1403
1539
|
const cwd = String(input?.cwd || process.cwd());
|
|
1404
1540
|
const prompt = extractPrompt(input);
|
|
1405
|
-
const additionalContext =
|
|
1541
|
+
const additionalContext = profile === 'study-note'
|
|
1542
|
+
? await buildStudyNoteHookContext({ cwd })
|
|
1543
|
+
: await buildYamLiteContext({ cwd, prompt });
|
|
1406
1544
|
const output = {
|
|
1407
1545
|
continue: true,
|
|
1408
1546
|
hookSpecificOutput: {
|
|
@@ -1460,6 +1598,26 @@ async function buildYamLiteContext({ cwd, prompt }) {
|
|
|
1460
1598
|
lines.push(routeHint);
|
|
1461
1599
|
return lines.join('\n');
|
|
1462
1600
|
}
|
|
1601
|
+
async function buildStudyNoteHookContext({ cwd }) {
|
|
1602
|
+
const dir = path.resolve(cwd || process.cwd());
|
|
1603
|
+
const changedFiles = await gitChangedFiles(dir);
|
|
1604
|
+
const lines = [
|
|
1605
|
+
'yam Study Note guard active: if this turn changes code, config, release metadata, docs, or project artifacts, include a Study Note in the final response.'
|
|
1606
|
+
];
|
|
1607
|
+
if (!changedFiles.length) {
|
|
1608
|
+
lines.push('No changed files were detected at prompt time; if you change artifacts during this turn, add the Study Note before final.');
|
|
1609
|
+
return lines.join('\n');
|
|
1610
|
+
}
|
|
1611
|
+
lines.push(`Changed files detected (${Math.min(changedFiles.length, 8)} shown): ${changedFiles.slice(0, 8).join(', ')}`);
|
|
1612
|
+
lines.push('Study Note minimum: touched code/artifact, role, execution point, before/after change, expected behavior, one syntax/structure insight, verification, and limits.');
|
|
1613
|
+
const hygiene = studyNoteHygieneRequirements(changedFiles);
|
|
1614
|
+
if (hygiene.length) {
|
|
1615
|
+
lines.push(`Architecture hygiene required: ${hygiene.map((item) => item.id).join(', ')}.`);
|
|
1616
|
+
lines.push('Report whether the change avoided dumping unrelated logic into page.tsx, one-off CSS into global.css, or structured product data into broad DB jsonb.');
|
|
1617
|
+
}
|
|
1618
|
+
lines.push('This hook is advisory; it does not generate or edit the Study Note.');
|
|
1619
|
+
return lines.join('\n');
|
|
1620
|
+
}
|
|
1463
1621
|
function yamLiteRouteHint(prompt = '') {
|
|
1464
1622
|
const text = String(prompt || '').toLowerCase();
|
|
1465
1623
|
if (!text)
|
|
@@ -2022,13 +2180,13 @@ function loopUsage() {
|
|
|
2022
2180
|
Read-only loop artifact helpers. A loop report records intent, stages, evidence, blockers, next action, handoff fields, and a short study note. It does not run agents, start processes, publish packages, or write files.
|
|
2023
2181
|
|
|
2024
2182
|
Usage:
|
|
2025
|
-
yam loop report [--route route] [--intent text] [--loop-kind harness|release|ueye|scout|deep|mission] [--stage id:status:note] [--evidence text] [--evidence-level none|fixture|smoke|local|real] [--evidence-stamp text] [--source-digest text] [--covered-requirement text] [--uncovered-requirement text] [--blocked text] [--blocked-kind text] [--failure-cause text] [--safe-retry text] [--recovery-hint text] [--fix-first-item text] [--remaining-task text] [--recommended-direction text] [--implementation-note text] [--why-this-next text] [--blocked-by text] [--owner-route route] [--owner-scope text] [--scope-owner text] [--side-effect text] [--avoidance-note text] [--truth status] [--intent-label read_only|write|destructive|runtime|visual|publish] [--issue-code text] [--issue-role text] [--issue-symptom text] [--changed-code text] [--changed-role text] [--change-summary text] [--why-important text] [--learning-note text] [--json]
|
|
2183
|
+
yam loop report [--route route] [--intent text] [--loop-kind harness|release|ueye|scout|deep|mission] [--stage id:status:note] [--evidence text] [--evidence-level none|fixture|smoke|local|real] [--evidence-stamp text] [--source-digest text] [--touched-file file] [--read-file file] [--verified-file file] [--skipped-check text] [--stop-condition text] [--resume-hint text] [--readiness-state usable|degraded|blocked|unknown] [--covered-requirement text] [--uncovered-requirement text] [--blocked text] [--blocked-kind text] [--failure-cause text] [--safe-retry text] [--recovery-hint text] [--fix-first-item text] [--remaining-task text] [--recommended-direction text] [--implementation-note text] [--why-this-next text] [--blocked-by text] [--owner-route route] [--owner-scope text] [--scope-owner text] [--side-effect text] [--avoidance-note text] [--truth status] [--intent-label read_only|write|destructive|runtime|visual|publish] [--issue-code text] [--issue-role text] [--issue-symptom text] [--changed-code text] [--changed-role text] [--change-summary text] [--why-important text] [--learning-note text] [--json]
|
|
2026
2184
|
`);
|
|
2027
2185
|
}
|
|
2028
2186
|
async function loopReport(args = []) {
|
|
2029
2187
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
2030
2188
|
return loopUsage();
|
|
2031
|
-
const flags = parseSimpleFlags(args, new Set(['route', 'intent', 'loop-kind', 'stage', 'stage-convention', 'evidence', 'evidence-level', 'evidence-stamp', 'source-digest', 'covered-requirement', 'uncovered-requirement', 'blocked', 'blocker', 'blocked-kind', 'failure-cause', 'next-action', 'safe-retry', 'recovery-hint', 'fix-first-item', 'remaining-task', 'recommended-direction', 'direction', 'implementation-note', 'why-this-next', 'blocked-by', 'owner-route', 'owner-scope', 'scope-owner', 'owner', 'scope', 'side-effect', 'avoidance-note', 'truth', 'intent-label', 'tool-intent', 'issue-code', 'issue-role', 'issue-symptom', 'changed-code', 'changed-role', 'change-summary', 'why-important', 'learning-note', 'json']));
|
|
2189
|
+
const flags = parseSimpleFlags(args, new Set(['route', 'intent', 'loop-kind', 'stage', 'stage-convention', 'evidence', 'evidence-level', 'evidence-stamp', 'source-digest', 'touched-file', 'read-file', 'verified-file', 'skipped-check', 'stop-condition', 'resume-hint', 'readiness-state', 'covered-requirement', 'uncovered-requirement', 'blocked', 'blocker', 'blocked-kind', 'failure-cause', 'next-action', 'safe-retry', 'recovery-hint', 'fix-first-item', 'remaining-task', 'recommended-direction', 'direction', 'implementation-note', 'why-this-next', 'blocked-by', 'owner-route', 'owner-scope', 'scope-owner', 'owner', 'scope', 'side-effect', 'avoidance-note', 'truth', 'intent-label', 'tool-intent', 'issue-code', 'issue-role', 'issue-symptom', 'changed-code', 'changed-role', 'change-summary', 'why-important', 'learning-note', 'json']));
|
|
2032
2190
|
const blockers = [...arrayFlag(flags.blocked), ...arrayFlag(flags.blocker)];
|
|
2033
2191
|
const report = buildLoopReport({
|
|
2034
2192
|
route: normalizeRoute(flags.route) || String(flags.route || ''),
|
|
@@ -2040,6 +2198,13 @@ async function loopReport(args = []) {
|
|
|
2040
2198
|
evidence_level: normalizeLoopEvidenceLevelFlag(flags.evidence_level),
|
|
2041
2199
|
evidence_stamp: String(flags.evidence_stamp || ''),
|
|
2042
2200
|
source_digest: String(flags.source_digest || ''),
|
|
2201
|
+
touched_files: arrayFlag(flags.touched_file),
|
|
2202
|
+
read_files: arrayFlag(flags.read_file),
|
|
2203
|
+
verified_files: arrayFlag(flags.verified_file),
|
|
2204
|
+
skipped_checks: arrayFlag(flags.skipped_check),
|
|
2205
|
+
stop_condition: String(flags.stop_condition || ''),
|
|
2206
|
+
resume_hint: String(flags.resume_hint || ''),
|
|
2207
|
+
readiness_state: normalizeReadinessStateFlag(flags.readiness_state),
|
|
2043
2208
|
covered_requirements: arrayFlag(flags.covered_requirement),
|
|
2044
2209
|
uncovered_requirements: arrayFlag(flags.uncovered_requirement),
|
|
2045
2210
|
blockers,
|
|
@@ -2098,7 +2263,7 @@ Usage:
|
|
|
2098
2263
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
2099
2264
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
2100
2265
|
yam ueye preflight [dir] [--json]
|
|
2101
|
-
yam ueye report [--reference ref.png] [--actual screenshot.png] [--preflight-id id] [--p0-risk text] [--quality-gate-note text] [--brief-dimension text] [--constraint text] [--anti-slop text] [--invented-metric] [--placeholder-copy] [--generic-visual] [--review-session-id id] [--provider-context local] [--execution-surface in-app-browser] [--app-surface codex-app] [--browser-surface in-app-browser] [--control-mode manual|automated] [--preserved-state] [--preserved-url URL] [--completion-claim draft|needs-polish|done] [--strict] [--design-score n] [--p0 text] [--p1 text] [--states-checked] [--mobile-checked] [--contrast-checked] [--similar text] [--different text] [--missing text] [--resolved text] [--new-finding text] [--still-open text] [--regression text] [--viewport 1440x900] [--state default] [--design-quality pass|needs-polish|fails|not-checked] [--json]
|
|
2266
|
+
yam ueye report [--reference ref.png] [--actual screenshot.png] [--preflight-id id] [--p0-risk text] [--quality-gate-note text] [--brief-dimension text] [--constraint text] [--anti-slop text] [--invented-metric] [--placeholder-copy] [--generic-visual] [--acceptance-criterion text] [--touched-file file] [--read-file file] [--verified-file file] [--skipped-check text] [--residual-risk text] [--stop-condition text] [--resume-hint text] [--deep-visual-check text] [--design-system-evidence text] [--implementation-evidence text] [--state-check default:pass] [--review-session-id id] [--provider-context local] [--execution-surface in-app-browser] [--app-surface codex-app] [--browser-surface in-app-browser] [--control-mode manual|automated] [--preserved-state] [--preserved-url URL] [--completion-claim draft|needs-polish|done] [--strict] [--design-score n] [--p0 text] [--p1 text] [--states-checked] [--mobile-checked] [--contrast-checked] [--similar text] [--different text] [--missing text] [--resolved text] [--new-finding text] [--still-open text] [--regression text] [--viewport 1440x900] [--state default] [--design-quality pass|needs-polish|fails|not-checked] [--json]
|
|
2102
2267
|
|
|
2103
2268
|
Notes:
|
|
2104
2269
|
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
@@ -2339,7 +2504,7 @@ async function ueyeCompare(args = []) {
|
|
|
2339
2504
|
async function ueyeReport(args = []) {
|
|
2340
2505
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
2341
2506
|
return ueyeUsage();
|
|
2342
|
-
const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'next-visual-action', 'review-session-id', 'preflight-id', 'p0-risk', 'quality-gate-note', 'brief-dimension', 'constraint', 'anti-slop', 'invented-metric', 'placeholder-copy', 'generic-visual', 'reference-id', 'screenshot-id', 'previous-screenshot-id', 'current-screenshot-id', 'previous-report', 'comparison-notes', 'similar', 'different', 'missing', 'resolved', 'new-finding', 'still-open', 'regression', 'viewport', 'state', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'url', 'evidence-id', 'completion-claim', 'completion-status', 'gate-mode', 'strict', 'design-score', 'min-design-score', 'p0', 'p1', 'open-p0', 'open-p1', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'comparison-result', 'json']));
|
|
2507
|
+
const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'next-visual-action', 'review-session-id', 'preflight-id', 'p0-risk', 'quality-gate-note', 'brief-dimension', 'constraint', 'anti-slop', 'invented-metric', 'placeholder-copy', 'generic-visual', 'acceptance-criterion', 'touched-file', 'read-file', 'verified-file', 'skipped-check', 'residual-risk', 'stop-condition', 'resume-hint', 'deep-visual-check', 'design-system-evidence', 'implementation-evidence', 'state-check', 'reference-id', 'screenshot-id', 'previous-screenshot-id', 'current-screenshot-id', 'previous-report', 'comparison-notes', 'similar', 'different', 'missing', 'resolved', 'new-finding', 'still-open', 'regression', 'viewport', 'state', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'url', 'evidence-id', 'completion-claim', 'completion-status', 'gate-mode', 'strict', 'design-score', 'min-design-score', 'p0', 'p1', 'open-p0', 'open-p1', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'comparison-result', 'json']));
|
|
2343
2508
|
const reference = String(flags.reference || flags.ref || '');
|
|
2344
2509
|
const actual = String(flags.actual || flags.screenshot || '');
|
|
2345
2510
|
const referenceSources = [];
|
|
@@ -2445,6 +2610,20 @@ async function ueyeReport(args = []) {
|
|
|
2445
2610
|
direction_locked: Boolean(flags.direction_locked),
|
|
2446
2611
|
reference_read: Boolean(flags.reference_read)
|
|
2447
2612
|
},
|
|
2613
|
+
deep_visual_review: {
|
|
2614
|
+
acceptance_criteria: arrayFlag(flags.acceptance_criterion),
|
|
2615
|
+
touched_files: arrayFlag(flags.touched_file),
|
|
2616
|
+
read_files: arrayFlag(flags.read_file),
|
|
2617
|
+
verified_files: arrayFlag(flags.verified_file),
|
|
2618
|
+
skipped_checks: arrayFlag(flags.skipped_check),
|
|
2619
|
+
residual_risks: arrayFlag(flags.residual_risk),
|
|
2620
|
+
stop_condition: String(flags.stop_condition || ''),
|
|
2621
|
+
resume_hint: String(flags.resume_hint || ''),
|
|
2622
|
+
deep_visual_checks: arrayFlag(flags.deep_visual_check),
|
|
2623
|
+
design_system_evidence: arrayFlag(flags.design_system_evidence),
|
|
2624
|
+
implementation_evidence: arrayFlag(flags.implementation_evidence),
|
|
2625
|
+
state_check: arrayFlag(flags.state_check)
|
|
2626
|
+
},
|
|
2448
2627
|
blocked_reason: blockedReason,
|
|
2449
2628
|
next_action: String(flags.next_visual_action || flags.next_action || '')
|
|
2450
2629
|
});
|
|
@@ -3782,7 +3961,7 @@ function printDetection(result) {
|
|
|
3782
3961
|
}
|
|
3783
3962
|
console.log('');
|
|
3784
3963
|
console.log('Smallest useful checks:');
|
|
3785
|
-
console.log(`- $quick: ${result.commands.typecheck || result.commands.lint || result.commands.test || result.commands.build || '
|
|
3964
|
+
console.log(`- $quick: ${result.commands.typecheck || result.commands.lint || result.commands.test || result.commands.build || 'L1 inspected; no command detected'}`);
|
|
3786
3965
|
console.log(`- $ueye: ${result.commands.typecheck || result.commands.build || 'Browser/screenshot check; no command detected'}`);
|
|
3787
3966
|
console.log(`- $deep: ${[result.commands.typecheck, result.commands.lint, result.commands.test, result.commands.build].filter(Boolean).join(' && ') || `No command detected; define in ${PROJECT_PACK}`}`);
|
|
3788
3967
|
if (result.frameworkChecklist?.detected) {
|
|
@@ -4199,6 +4378,8 @@ async function main() {
|
|
|
4199
4378
|
return tools(process.argv.slice(3));
|
|
4200
4379
|
if (command === 'proof')
|
|
4201
4380
|
return proof(process.argv.slice(3));
|
|
4381
|
+
if (command === 'study-note')
|
|
4382
|
+
return studyNote(process.argv.slice(3));
|
|
4202
4383
|
if (command === 'loop')
|
|
4203
4384
|
return loop(process.argv.slice(3));
|
|
4204
4385
|
if (command === 'ueye')
|
|
@@ -8,6 +8,7 @@ export type RuntimeClaim = 'not_started' | 'started' | 'observed' | 'stopped' |
|
|
|
8
8
|
export type LoopStageStatus = 'passed' | 'failed' | 'blocked' | 'skipped' | 'partial' | 'pending' | 'recorded';
|
|
9
9
|
export type LoopEvidenceLevel = 'none' | 'fixture' | 'smoke' | 'local' | 'real';
|
|
10
10
|
export type ToolIntent = 'read_only' | 'write' | 'destructive' | 'runtime' | 'visual' | 'publish';
|
|
11
|
+
export type ReadinessState = 'usable' | 'degraded' | 'blocked' | 'unknown';
|
|
11
12
|
export interface SafetyHit {
|
|
12
13
|
level: SafetyLevel;
|
|
13
14
|
reason: string;
|
|
@@ -161,6 +162,13 @@ export interface LoopReport {
|
|
|
161
162
|
evidence_level: LoopEvidenceLevel;
|
|
162
163
|
evidence_stamp: string;
|
|
163
164
|
source_digest: string;
|
|
165
|
+
touched_files: string[];
|
|
166
|
+
read_files: string[];
|
|
167
|
+
verified_files: string[];
|
|
168
|
+
skipped_checks: string[];
|
|
169
|
+
stop_condition: string;
|
|
170
|
+
resume_hint: string;
|
|
171
|
+
readiness_state: ReadinessState;
|
|
164
172
|
covered_requirements: string[];
|
|
165
173
|
uncovered_requirements: string[];
|
|
166
174
|
blockers: string[];
|
|
@@ -191,14 +199,35 @@ export interface UeyeRunReport {
|
|
|
191
199
|
implementation_sources: UeyeVisualProvenance[];
|
|
192
200
|
surface_context: UeyeSurfaceContext;
|
|
193
201
|
design_completion_gate: UeyeDesignCompletionGate;
|
|
202
|
+
deep_visual_review: UeyeDeepVisualReview;
|
|
194
203
|
comparison_result: string;
|
|
195
204
|
design_quality: string;
|
|
196
205
|
blocked_reason: string;
|
|
197
206
|
next_action: string;
|
|
198
207
|
truth_status: TruthStatus;
|
|
199
208
|
}
|
|
200
|
-
export interface UeyeRunReportInput extends Partial<Omit<UeyeRunReport, 'design_completion_gate'>> {
|
|
209
|
+
export interface UeyeRunReportInput extends Partial<Omit<UeyeRunReport, 'design_completion_gate' | 'deep_visual_review'>> {
|
|
201
210
|
design_completion_gate?: Partial<UeyeDesignCompletionGate> & Record<string, unknown>;
|
|
211
|
+
deep_visual_review?: Partial<UeyeDeepVisualReview> & Record<string, unknown>;
|
|
212
|
+
}
|
|
213
|
+
export interface UeyeDeepVisualReview {
|
|
214
|
+
schema: 'yam.ueye-deep-visual-review.v1';
|
|
215
|
+
acceptance_criteria: string[];
|
|
216
|
+
touched_files: string[];
|
|
217
|
+
read_files: string[];
|
|
218
|
+
verified_files: string[];
|
|
219
|
+
skipped_checks: string[];
|
|
220
|
+
residual_risks: string[];
|
|
221
|
+
stop_condition: string;
|
|
222
|
+
resume_hint: string;
|
|
223
|
+
deep_visual_checks: string[];
|
|
224
|
+
design_system_evidence: string[];
|
|
225
|
+
state_matrix: Record<string, string>;
|
|
226
|
+
implementation_evidence: string[];
|
|
227
|
+
blockers: string[];
|
|
228
|
+
warnings: string[];
|
|
229
|
+
next_action: string;
|
|
230
|
+
truth_status: TruthStatus;
|
|
202
231
|
}
|
|
203
232
|
export interface MediaGenerationProof {
|
|
204
233
|
schema: 'yam.media-generation-proof.v1';
|
|
@@ -294,6 +323,7 @@ export declare function buildRuntimeBackendEvidence(input?: Partial<RuntimeBacke
|
|
|
294
323
|
export declare function buildStudyNote(input?: Partial<StudyNote> & Record<string, unknown>): StudyNote;
|
|
295
324
|
export declare function buildLoopReport(input?: Partial<LoopReport> & Record<string, unknown>): LoopReport;
|
|
296
325
|
export declare function buildUeyeRunReport(input?: UeyeRunReportInput): UeyeRunReport;
|
|
326
|
+
export declare function buildUeyeDeepVisualReview(input?: Partial<UeyeDeepVisualReview> & Record<string, unknown>): UeyeDeepVisualReview;
|
|
297
327
|
export declare function buildMediaGenerationProof(input?: Partial<MediaGenerationProof>): MediaGenerationProof;
|
|
298
328
|
export declare function isTruthStatus(value?: unknown): value is TruthStatus;
|
|
299
329
|
export declare function weakestTruth(...statuses: unknown[]): TruthStatus;
|