atris 3.57.3 → 3.58.5

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.
Files changed (175) hide show
  1. package/README.md +12 -1
  2. package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
  3. package/atris/skills/create-member/SKILL.md +16 -13
  4. package/atris/skills/design/SKILL.md +27 -4
  5. package/atris/skills/engines/SKILL.md +57 -13
  6. package/atris/skills/x-search/SKILL.md +13 -5
  7. package/atris/skills/youtube/SKILL.md +39 -8
  8. package/atris/team/brainstormer/MEMBER.md +2 -2
  9. package/atris/team/executor/MEMBER.md +3 -3
  10. package/atris/team/launcher/MEMBER.md +4 -4
  11. package/atris/team/navigator/MEMBER.md +3 -3
  12. package/atris/team/researcher/MEMBER.md +2 -2
  13. package/atris/team/validator/MEMBER.md +1 -1
  14. package/atris.md +14 -0
  15. package/ax +157 -24
  16. package/bin/atris.js +79 -31
  17. package/commands/aeo.js +4 -4
  18. package/commands/agents.js +1 -1
  19. package/commands/align.js +12 -12
  20. package/commands/analytics.js +3 -3
  21. package/commands/app.js +5 -5
  22. package/commands/ask.js +175 -0
  23. package/commands/auth.js +7 -7
  24. package/commands/autoland.js +29 -5
  25. package/commands/autopilot-front.js +6 -6
  26. package/commands/autopilot.js +90 -90
  27. package/commands/avail.js +5 -5
  28. package/commands/bench.js +63 -1
  29. package/commands/browse.js +2 -2
  30. package/commands/business.js +12 -12
  31. package/commands/card.js +2 -2
  32. package/commands/clean.js +13 -13
  33. package/commands/close.js +297 -15
  34. package/commands/compile.js +13 -13
  35. package/commands/computer.js +94 -27
  36. package/commands/console.js +5 -5
  37. package/commands/decide.js +1 -1
  38. package/commands/deck.js +9 -9
  39. package/commands/drive.js +4 -4
  40. package/commands/engine.js +173 -19
  41. package/commands/errors.js +3 -3
  42. package/commands/experiments.js +18 -1
  43. package/commands/feed.js +2 -2
  44. package/commands/feedback.js +4 -4
  45. package/commands/fleet.js +12 -12
  46. package/commands/game.js +1 -1
  47. package/commands/guide.js +49 -0
  48. package/commands/improve.js +19 -17
  49. package/commands/init.js +67 -19
  50. package/commands/interview.js +7 -7
  51. package/commands/land.js +40 -15
  52. package/commands/learn.js +12 -12
  53. package/commands/lesson.js +2 -2
  54. package/commands/log.js +1 -1
  55. package/commands/loops.js +4 -4
  56. package/commands/member.js +75 -50
  57. package/commands/mission.js +97 -98
  58. package/commands/one-lap.js +1 -1
  59. package/commands/pack.js +74 -8
  60. package/commands/plugin.js +6 -6
  61. package/commands/probe.js +9 -9
  62. package/commands/pull.js +35 -35
  63. package/commands/pulse.js +1 -1
  64. package/commands/push.js +21 -21
  65. package/commands/rainmaker.js +1 -1
  66. package/commands/recap.js +7 -7
  67. package/commands/reel.js +2 -2
  68. package/commands/release.js +3 -3
  69. package/commands/report.js +1 -1
  70. package/commands/review.js +5 -5
  71. package/commands/revisions.js +1 -1
  72. package/commands/run.js +1 -1
  73. package/commands/scout.js +3 -3
  74. package/commands/security-review.js +7 -7
  75. package/commands/serve.js +9 -9
  76. package/commands/setup.js +1 -1
  77. package/commands/sign.js +4 -4
  78. package/commands/signup.js +4 -4
  79. package/commands/site.js +8 -3
  80. package/commands/skill.js +6 -6
  81. package/commands/slop.js +18 -18
  82. package/commands/social.js +3 -3
  83. package/commands/soul.js +14 -14
  84. package/commands/spaceship.js +1 -1
  85. package/commands/status.js +2 -2
  86. package/commands/strings.js +13 -13
  87. package/commands/study.js +4 -4
  88. package/commands/sync.js +8 -8
  89. package/commands/task.js +50 -25
  90. package/commands/teach.js +2 -2
  91. package/commands/team.js +1 -1
  92. package/commands/theme.js +2 -2
  93. package/commands/tree.js +34 -0
  94. package/commands/truth.js +7 -7
  95. package/commands/verify.js +12 -12
  96. package/commands/visualize.js +2 -2
  97. package/commands/workflow.js +97 -40
  98. package/commands/worktree.js +57 -1
  99. package/commands/write.js +9 -9
  100. package/commands/x-search.js +304 -15
  101. package/commands/xp.js +6 -3
  102. package/commands/youtube.js +916 -65
  103. package/lib/accept-verify-gate.js +3 -3
  104. package/lib/activity-stream.js +1 -1
  105. package/lib/apply-gate.js +13 -0
  106. package/lib/auto-accept-certified.js +28 -12
  107. package/lib/autoland.js +6 -6
  108. package/lib/bench/engines.js +59 -8
  109. package/lib/bench/report.js +1 -1
  110. package/lib/bench/runner.js +204 -5
  111. package/lib/bench/tree-render.js +76 -0
  112. package/lib/card.js +1 -1
  113. package/lib/chat-log-scan.js +1 -1
  114. package/lib/claude-boot-block.js +2 -0
  115. package/lib/deck-compose.js +1 -1
  116. package/lib/deck-history.js +2 -2
  117. package/lib/deck-layout.js +1 -1
  118. package/lib/deck-review.js +5 -5
  119. package/lib/deck-schema.js +1 -1
  120. package/lib/default-verifier.js +1 -1
  121. package/lib/developer-api.js +3 -0
  122. package/lib/engine-ask.js +66 -9
  123. package/lib/engine-registry.js +15 -2
  124. package/lib/file-ops.js +1 -1
  125. package/lib/fleet.js +71 -35
  126. package/lib/html-render.js +1 -1
  127. package/lib/intents.js +172 -0
  128. package/lib/journal.js +1 -1
  129. package/lib/known-commands.js +2 -2
  130. package/lib/land-green.js +364 -0
  131. package/lib/lesson-contradiction.js +2 -2
  132. package/lib/lesson-preflight.js +1 -1
  133. package/lib/manifest.js +5 -5
  134. package/lib/member-alive.js +9 -5
  135. package/lib/memory-view.js +3 -3
  136. package/lib/mission-protected-lane.js +1 -1
  137. package/lib/mission-root.js +2 -2
  138. package/lib/orb-context.js +10 -10
  139. package/lib/pack-capabilities.js +861 -3
  140. package/lib/permission-grants.js +2 -2
  141. package/lib/policy-lessons.js +6 -6
  142. package/lib/pulse.js +8 -8
  143. package/lib/receipt-evidence.js +1 -1
  144. package/lib/reel.js +1 -1
  145. package/lib/review-integrity.js +1 -1
  146. package/lib/runner-command.js +35 -5
  147. package/lib/scorecard.js +4 -4
  148. package/lib/security-scan.js +5 -5
  149. package/lib/self-drive.js +1 -1
  150. package/lib/site-publish.js +372 -0
  151. package/lib/site.js +1 -1
  152. package/lib/slides-deck.js +2 -2
  153. package/lib/state-detection.js +2 -2
  154. package/lib/sync-telemetry.js +2 -2
  155. package/lib/task-db.js +17 -10
  156. package/lib/task-explanation.js +1 -1
  157. package/lib/task-proof.js +2 -2
  158. package/lib/task-receipt.js +3 -3
  159. package/lib/theme.js +12 -12
  160. package/lib/todo-fallback.js +3 -3
  161. package/lib/todo-sections.js +1 -1
  162. package/lib/tree-hash.js +79 -0
  163. package/lib/usage.js +1 -1
  164. package/lib/wish-delegate.js +1 -1
  165. package/lib/workspace-safety.js +4 -4
  166. package/lib/workspace-scaffold.js +3 -3
  167. package/package.json +1 -1
  168. package/scripts/det/concierge-bakeoff.sh +26 -0
  169. package/scripts/det/ytquote-repair.js +0 -1
  170. package/scripts/det/ytrail-race.js +0 -5
  171. package/scripts/member-operate.mjs +52 -14
  172. package/scripts/outbound-artifact-gate.js +0 -3
  173. package/utils/auth.js +0 -3
  174. package/utils/config.js +0 -1
  175. package/scripts/det/checklist-score.js +0 -191
package/commands/close.js CHANGED
@@ -23,6 +23,7 @@ const FAILED_TASK_BATCH_PREFIX = 'tasks:failed:';
23
23
  const PROBE_TIMEOUT_MS = 60 * 1000;
24
24
  const PROBE_HOLD_GAP_MS = 60 * 60 * 1000;
25
25
  const PROBE_STOP_AFTER_FAILED_CHECKS = 2;
26
+ const MAX_OPEN_FLAGS = 10;
26
27
 
27
28
  function ledgerPath(cwd = process.cwd()) {
28
29
  return path.join(cwd, LEDGER_RELATIVE_PATH);
@@ -144,6 +145,15 @@ function foldEvents(events, options = {}) {
144
145
  flag.why = event.why || '';
145
146
  } else if (event.kind === 'snoozed') {
146
147
  flag.snoozed_until = event.until || null;
148
+ } else if (event.kind === 'parked') {
149
+ flag.status = 'parked';
150
+ flag.parked_at = event.parked_at || eventTime(event);
151
+ flag.why = normalizeSpaces(event.why || 'parked by request');
152
+ } else if (event.kind === 'reopened') {
153
+ flag.status = 'open';
154
+ flag.reopened_at = eventTime(event);
155
+ delete flag.parked_at;
156
+ delete flag.why;
147
157
  } else if (event.kind === 'escalated') {
148
158
  const day = event.day || (event.at ? String(event.at).slice(0, 10) : null);
149
159
  if (day && !flag.escalated_days.includes(day)) flag.escalated_days.push(day);
@@ -223,6 +233,12 @@ function openFlags(cwd = process.cwd(), options = {}) {
223
233
  return foldEvents(readEvents(cwd), options).filter((flag) => flag.status === 'open');
224
234
  }
225
235
 
236
+ function parkedFlags(cwd = process.cwd(), options = {}) {
237
+ return foldEvents(readEvents(cwd), options)
238
+ .filter((flag) => flag.status === 'parked')
239
+ .sort((a, b) => (parseDate(b.parked_at) || 0) - (parseDate(a.parked_at) || 0));
240
+ }
241
+
226
242
  function ownerWaitRank(flag) {
227
243
  return /^(you|me|operator|human|keshav)$/.test(String(flag.owner || '').toLowerCase()) ? 0 : 1;
228
244
  }
@@ -264,6 +280,16 @@ function listLine(flag) {
264
280
  return parts.join(', ');
265
281
  }
266
282
 
283
+ function parkedLine(flag) {
284
+ const what = trimSentenceEnd(flag.what.toLowerCase());
285
+ const day = flag.parked_at ? String(flag.parked_at).slice(0, 10) : 'unknown date';
286
+ return `${what}, parked ${day}, ${trimSentenceEnd(flag.why || 'parked by request').toLowerCase()}`;
287
+ }
288
+
289
+ function migratedParkLine(flag) {
290
+ return `parked ${trimSentenceEnd(flag.what).toLowerCase()}: ${trimSentenceEnd(flag.why).toLowerCase()}.`;
291
+ }
292
+
267
293
  function sweepLine(flag) {
268
294
  const what = trimSentenceEnd(flag.what.toLowerCase());
269
295
  const closeCondition = trimSentenceEnd(flag.close_condition.toLowerCase());
@@ -325,9 +351,12 @@ function publicFlag(flag) {
325
351
  }
326
352
 
327
353
  function printHelp() {
328
- console.log('usage: atris close <add|list|done|dissolve|snooze|sweep|scan|check>');
329
- console.log('add "<what>" [--owner x] [--lane life|business|code] [--ttl 7] [--when "<condition>"] [--source y] [--probe "<cmd that fails now>"]');
354
+ console.log('usage: atris close <add|list|park|parked|reopen|done|dissolve|snooze|sweep|scan|check>');
355
+ console.log('add "<what>" [--owner x] [--lane life|business|code] [--ttl 7] [--when "<condition>"] [--source y] [--probe "<cmd that fails now>"] [--replace <id>]');
330
356
  console.log('list [--json] [--lane x]');
357
+ console.log('park <id> [--why "..."]');
358
+ console.log('parked [--json]');
359
+ console.log('reopen <id> [--replace <id>]');
331
360
  console.log('done <id> [--proof "..."] (a probed incident closes only after its probe holds)');
332
361
  console.log('check [id] [--json] (run live probes; failing probe = old theory still live)');
333
362
  console.log('dissolve <id> --why "..."');
@@ -336,6 +365,58 @@ function printHelp() {
336
365
  console.log('scan [--json]');
337
366
  }
338
367
 
368
+ function appendParkedEvent(flag, cwd, now, why, options = {}) {
369
+ const at = now.toISOString();
370
+ const event = {
371
+ kind: 'parked',
372
+ at,
373
+ id: flag.id,
374
+ parked_at: at,
375
+ why: normalizeSpaces(why || 'parked by request'),
376
+ };
377
+ if (options.automatic) event.automatic = true;
378
+ appendEvent(event, cwd);
379
+ return {
380
+ id: flag.id,
381
+ what: flag.what,
382
+ parked_at: at,
383
+ why: event.why,
384
+ };
385
+ }
386
+
387
+ function slotCapError(flags) {
388
+ const fullest = flags
389
+ .slice()
390
+ .sort(compareOpenFlags)
391
+ .slice(0, 3)
392
+ .map((flag) => trimSentenceEnd(flag.what).toLowerCase())
393
+ .join(', ');
394
+ const names = fullest ? ` the fullest loops are ${fullest}.` : '';
395
+ return new Error(`ten open loops is the cap.${names} park one first or use --replace <id>`);
396
+ }
397
+
398
+ function makeRoomForOpenFlag(cwd, now, replaceId, incomingWhat) {
399
+ const flags = openFlags(cwd, { now });
400
+ const hasReplacement = replaceId !== undefined;
401
+ if (hasReplacement && (replaceId === true || !normalizeSpaces(replaceId))) {
402
+ throw new Error('--replace needs an id');
403
+ }
404
+ if (!hasReplacement) {
405
+ if (flags.length >= MAX_OPEN_FLAGS) throw slotCapError(flags);
406
+ return null;
407
+ }
408
+ if (flags.length > MAX_OPEN_FLAGS) throw slotCapError(flags);
409
+
410
+ const replacement = flags.find((flag) => flag.id === replaceId);
411
+ if (!replacement) throw new Error(`${replaceId} is not open`);
412
+ return appendParkedEvent(
413
+ replacement,
414
+ cwd,
415
+ now,
416
+ `replaced to make room for ${trimSentenceEnd(incomingWhat).toLowerCase()}`
417
+ );
418
+ }
419
+
339
420
  function commandAdd(args, context = {}) {
340
421
  const { positional, options } = parseArgs(args);
341
422
  const what = normalizeSpaces(positional.join(' '));
@@ -366,6 +447,8 @@ function commandAdd(args, context = {}) {
366
447
  probeExit = result.exit_code;
367
448
  }
368
449
 
450
+ makeRoomForOpenFlag(context.cwd, now, options.replace, what);
451
+
369
452
  const openedAt = now.toISOString();
370
453
  const event = {
371
454
  kind: 'opened',
@@ -389,7 +472,7 @@ function commandAdd(args, context = {}) {
389
472
  return 0;
390
473
  }
391
474
 
392
- // atris close check [id] run the live probes on open incidents. This is the
475
+ // atris close check [id]: run the live probes on open incidents. This is the
393
476
  // first move for any session entering an area: if a probe still fails, the
394
477
  // recorded theory is still live; do not diagnose fresh. After
395
478
  // PROBE_STOP_AFTER_FAILED_CHECKS failing re-checks the verdict is automatic:
@@ -461,21 +544,88 @@ function commandCheck(args, context = {}) {
461
544
  return anyFailing ? 1 : 0;
462
545
  }
463
546
 
547
+ function hasRunnableNextAction(flag) {
548
+ if (flag.probe) return true;
549
+ return /\b(accept|add|approve|book|build|call|check|choose|close|confirm|deploy|dissolve|email|fix|merge|pay|publish|reply|resolve|retry|review|run|send|ship|sign|test|update|verify|write)\b/.test(canonicalWhat(flag.what));
550
+ }
551
+
552
+ function hasMachineCheckableClose(flag) {
553
+ if (flag.probe) return true;
554
+ if (canonicalWhat(flag.close_condition).includes('source store resolves')) return true;
555
+ return /^(experiment|lesson|mission|pulse|task|usage|watch):/.test(canonicalWhat(flag.source));
556
+ }
557
+
558
+ function migrationReason(flag, duplicateIds) {
559
+ if (ownerWaitRank(flag) === 0) return 'waiting on a human';
560
+ if (!hasRunnableNextAction(flag)) return 'no runnable next action';
561
+ if (!hasMachineCheckableClose(flag)) return 'no machine-checkable close condition';
562
+ if (duplicateIds.has(flag.id)) return 'duplicates another open loop';
563
+ return '';
564
+ }
565
+
566
+ function compareOldestFlags(a, b) {
567
+ const aOpened = parseDate(a.opened_at) || 0;
568
+ const bOpened = parseDate(b.opened_at) || 0;
569
+ if (aOpened !== bOpened) return aOpened - bOpened;
570
+ return a.id.localeCompare(b.id);
571
+ }
572
+
573
+ function migrateOpenSlots(cwd = process.cwd(), now = new Date(), options = {}) {
574
+ const flags = openFlags(cwd, { now });
575
+ const excess = flags.length - MAX_OPEN_FLAGS;
576
+ const shouldWrite = options.dryRun !== true && options.write !== false && options.readOnly !== true;
577
+ if (excess <= 0 || !shouldWrite) return [];
578
+
579
+ const byWhat = new Map();
580
+ for (const flag of flags) {
581
+ const key = canonicalWhat(flag.what);
582
+ if (!byWhat.has(key)) byWhat.set(key, []);
583
+ byWhat.get(key).push(flag);
584
+ }
585
+ const duplicateIds = new Set(
586
+ Array.from(byWhat.values())
587
+ .filter((group) => group.length > 1)
588
+ .flat()
589
+ .map((flag) => flag.id)
590
+ );
591
+
592
+ const preferred = [];
593
+ const fallback = [];
594
+ for (const flag of flags.slice().sort(compareOldestFlags)) {
595
+ const why = migrationReason(flag, duplicateIds);
596
+ if (why) preferred.push({ flag, why });
597
+ else fallback.push({ flag, why: 'oldest loop above the active slot cap' });
598
+ }
599
+
600
+ return preferred.concat(fallback).slice(0, excess).map(({ flag, why }) => (
601
+ appendParkedEvent(flag, cwd, now, why, { automatic: true })
602
+ ));
603
+ }
604
+
464
605
  function commandList(args, context = {}) {
465
606
  const { options } = parseArgs(args);
466
607
  const lane = options.lane ? String(options.lane).toLowerCase() : null;
467
608
  if (lane && !VALID_LANES.has(lane)) throw new Error('lane must be business, life, or code');
468
609
 
610
+ const now = context.now ? new Date(context.now) : new Date();
611
+ const autoClosed = autoCloseResolvedTaskReviewFlags(context.cwd, now);
612
+ const autoParked = migrateOpenSlots(context.cwd, now);
469
613
  let flags = openFlags(context.cwd, { now: context.now });
470
614
  if (lane) flags = flags.filter((flag) => flag.lane === lane);
471
615
  flags.sort(compareOpenFlags);
472
616
 
473
617
  if (options.json) {
474
- printJson({ open: flags.map(publicFlag) });
618
+ const payload = { open: flags.map(publicFlag) };
619
+ if (autoClosed.length > 0) payload.auto_closed = autoClosed.map(publicTaskReviewClosed);
620
+ if (autoParked.length > 0) payload.auto_parked = autoParked;
621
+ printJson(payload);
475
622
  return 0;
476
623
  }
477
624
 
625
+ autoClosed.forEach((item) => console.log(taskReviewClosedLine(item)));
626
+ autoParked.forEach((flag) => console.log(migratedParkLine(flag)));
478
627
  if (flags.length === 0) {
628
+ if (autoClosed.length > 0) return 0;
479
629
  console.log('nothing open.');
480
630
  return 0;
481
631
  }
@@ -484,6 +634,50 @@ function commandList(args, context = {}) {
484
634
  return 0;
485
635
  }
486
636
 
637
+ function commandPark(args, context = {}) {
638
+ const { positional, options } = parseArgs(args);
639
+ const id = positional[0];
640
+ if (!id) throw new Error('id is required');
641
+ const flag = requireOpenFlag(id, context);
642
+ const now = context.now ? new Date(context.now) : new Date();
643
+ const why = options.why === true ? undefined : options.why;
644
+ appendParkedEvent(flag, context.cwd, now, why);
645
+ console.log(`parked ${id}`);
646
+ return 0;
647
+ }
648
+
649
+ function commandParked(args, context = {}) {
650
+ const { options } = parseArgs(args);
651
+ const flags = parkedFlags(context.cwd, { now: context.now });
652
+ if (options.json) {
653
+ printJson({ parked: flags.map(publicFlag) });
654
+ return 0;
655
+ }
656
+ if (flags.length === 0) {
657
+ console.log('nothing parked.');
658
+ return 0;
659
+ }
660
+ flags.forEach((flag) => console.log(parkedLine(flag)));
661
+ return 0;
662
+ }
663
+
664
+ function commandReopen(args, context = {}) {
665
+ const { positional, options } = parseArgs(args);
666
+ const id = positional[0];
667
+ if (!id) throw new Error('id is required');
668
+ const flag = parkedFlags(context.cwd, { now: context.now }).find((item) => item.id === id);
669
+ if (!flag) throw new Error(`${id} is not parked`);
670
+ const now = context.now ? new Date(context.now) : new Date();
671
+ makeRoomForOpenFlag(context.cwd, now, options.replace, flag.what);
672
+ appendEvent({
673
+ kind: 'reopened',
674
+ at: now.toISOString(),
675
+ id,
676
+ }, context.cwd);
677
+ console.log(`reopened ${id}`);
678
+ return 0;
679
+ }
680
+
487
681
  function requireOpenFlag(id, context = {}) {
488
682
  const flag = openFlags(context.cwd, { now: context.now }).find((item) => item.id === id);
489
683
  if (!flag) throw new Error(`${id} is not open`);
@@ -559,6 +753,8 @@ function commandSnooze(args, context = {}) {
559
753
  // one escalated event per overdue flag per day, return the resulting state.
560
754
  function sweepState(cwd = process.cwd(), now = new Date(), options = {}) {
561
755
  const today = isoDateOnly(now);
756
+ const autoClosed = autoCloseResolvedTaskReviewFlags(cwd, now);
757
+ const autoParked = migrateOpenSlots(cwd, now, options);
562
758
  let flags = openFlags(cwd, { now });
563
759
  const overdue = flags.filter((flag) => flag.overdue).sort(compareSweepFlags);
564
760
  const escalatedToday = [];
@@ -578,24 +774,39 @@ function sweepState(cwd = process.cwd(), now = new Date(), options = {}) {
578
774
  }
579
775
 
580
776
  flags = openFlags(cwd, { now });
581
- return { open: flags.length, overdue, escalated_today: escalatedToday };
777
+ const result = { open: flags.length, overdue, escalated_today: escalatedToday };
778
+ if (autoClosed.length > 0) result.auto_closed = autoClosed;
779
+ if (autoParked.length > 0) result.auto_parked = autoParked;
780
+ return result;
582
781
  }
583
782
 
584
783
  function commandSweep(args, context = {}) {
585
784
  const { options } = parseArgs(args);
586
785
  const now = context.now ? new Date(context.now) : new Date();
587
- const { open, overdue, escalated_today: escalatedToday } = sweepState(context.cwd, now);
786
+ const {
787
+ open,
788
+ overdue,
789
+ escalated_today: escalatedToday,
790
+ auto_closed: autoClosed = [],
791
+ auto_parked: autoParked = [],
792
+ } = sweepState(context.cwd, now);
588
793
 
589
794
  if (options.json) {
590
- printJson({
795
+ const payload = {
591
796
  open,
592
797
  overdue: overdue.length,
593
798
  escalated_today: escalatedToday,
594
- });
799
+ };
800
+ if (autoClosed.length > 0) payload.auto_closed = autoClosed.map(publicTaskReviewClosed);
801
+ if (autoParked.length > 0) payload.auto_parked = autoParked;
802
+ printJson(payload);
595
803
  return 0;
596
804
  }
597
805
 
806
+ autoClosed.forEach((item) => console.log(taskReviewClosedLine(item)));
807
+ autoParked.forEach((flag) => console.log(migratedParkLine(flag)));
598
808
  if (overdue.length === 0) {
809
+ if (autoClosed.length > 0) return 0;
599
810
  console.log('nothing is overdue.');
600
811
  return 0;
601
812
  }
@@ -682,7 +893,7 @@ function pulseLivenessState(cwd, now) {
682
893
  // decommissioned or intermittent loop leaves stale receipts frozen; without a
683
894
  // guard the last N finished ticks stay net-negative forever and the flag can
684
895
  // never clear (no fresh positive receipt arrives). It also must not treat a
685
- // scattered set e.g. one tick today plus five from days ago as a "run":
896
+ // scattered set, e.g. one tick today plus five from days ago, as a "run":
686
897
  // summing rewards across a multi-day gap is meaningless. So we only consider
687
898
  // finished ticks from the last few days and require a full window of them; a
688
899
  // silent or sparse loop is the pulse-liveness sensor's concern, and here it
@@ -830,7 +1041,11 @@ function taskSource(task) {
830
1041
 
831
1042
  function taskResolved(task) {
832
1043
  const status = normalizeSpaces(task && task.status).toLowerCase();
833
- return status === 'done' || status === 'accepted';
1044
+ const review = reviewObject(task);
1045
+ const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
1046
+ const approval = normalizeSpaces(review.approval_status || metadata.approval_status).toLowerCase();
1047
+ return ['done', 'accepted', 'closed'].includes(status)
1048
+ || ['accepted', 'approved', 'done', 'closed'].includes(approval);
834
1049
  }
835
1050
 
836
1051
  function reviewObject(task) {
@@ -938,7 +1153,15 @@ function shortText(text, maxLength = 70) {
938
1153
  return clipped || value.slice(0, maxLength).trim();
939
1154
  }
940
1155
 
941
- function appendOpenedFromSource({ what, owner = 'operator', lane = 'code', ttlDays = 3, source }, cwd, now) {
1156
+ function appendOpenedFromSource({
1157
+ what,
1158
+ owner = 'operator',
1159
+ lane = 'code',
1160
+ ttlDays = 3,
1161
+ source,
1162
+ probe,
1163
+ closeCondition = 'the source store resolves it',
1164
+ }, cwd, now) {
942
1165
  const event = {
943
1166
  kind: 'opened',
944
1167
  at: now.toISOString(),
@@ -948,25 +1171,79 @@ function appendOpenedFromSource({ what, owner = 'operator', lane = 'code', ttlDa
948
1171
  lane: VALID_LANES.has(lane) ? lane : 'code',
949
1172
  opened_at: now.toISOString(),
950
1173
  ttl_days: Number(ttlDays) || 3,
951
- close_condition: 'the source store resolves it',
1174
+ close_condition: normalizeSpaces(closeCondition) || 'the source store resolves it',
952
1175
  source,
953
1176
  };
1177
+ const normalizedProbe = normalizeSpaces(probe || '');
1178
+ if (normalizedProbe) event.probe = normalizedProbe;
954
1179
  appendEvent(event, cwd);
955
1180
  return event;
956
1181
  }
957
1182
 
958
- function appendClosedFromSource(flag, cwd, now, note) {
1183
+ function upsertSourceFlag(candidate, cwd = process.cwd(), options = {}) {
1184
+ const now = options.now ? new Date(options.now) : new Date();
1185
+ const source = normalizeSpaces(candidate && candidate.source);
1186
+ if (!source) throw new Error('source is required');
1187
+
1188
+ const flags = openFlags(cwd, { now });
1189
+ const existing = flags.find((flag) => flag.source === source);
1190
+ if (!existing && flags.length >= MAX_OPEN_FLAGS) {
1191
+ return { filed: false, refreshed: false, reason: 'full' };
1192
+ }
1193
+
1194
+ const event = appendOpenedFromSource({ ...candidate, source }, cwd, now);
1195
+ return { filed: true, refreshed: Boolean(existing), event };
1196
+ }
1197
+
1198
+ function appendClosedFromSource(flag, cwd, now, note, proof = RESOLVED_IN_SOURCE_PROOF) {
959
1199
  const event = {
960
1200
  kind: 'closed',
961
1201
  at: now.toISOString(),
962
1202
  id: flag.id,
963
- proof: RESOLVED_IN_SOURCE_PROOF,
1203
+ proof,
964
1204
  };
965
1205
  if (note) event.note = note;
966
1206
  appendEvent(event, cwd);
967
1207
  return event;
968
1208
  }
969
1209
 
1210
+ function taskReviewFlag(flag) {
1211
+ return flag && flag.source && flag.source.startsWith('task:')
1212
+ && canonicalWhat(flag.what).includes('has waited in review too long');
1213
+ }
1214
+
1215
+ function autoCloseResolvedTaskReviewFlags(cwd = process.cwd(), now = new Date()) {
1216
+ const tasksById = latestById(readTaskProjection(cwd));
1217
+ if (tasksById.size === 0) return [];
1218
+
1219
+ const closed = [];
1220
+ const flags = foldEvents(readEvents(cwd), { now })
1221
+ .filter((flag) => flag.status === 'open' || flag.status === 'parked')
1222
+ .filter(taskReviewFlag);
1223
+ for (const flag of flags) {
1224
+ const watchedTaskId = flag.source.slice('task:'.length);
1225
+ const task = tasksById.get(watchedTaskId);
1226
+ if (!task || !taskResolved(task)) continue;
1227
+ const ref = taskRef(task) || watchedTaskId;
1228
+ const proof = `task ${ref.toLowerCase()} is closed in task projection`;
1229
+ const event = appendClosedFromSource(flag, cwd, now, null, proof);
1230
+ closed.push({ flag, event, task_ref: ref });
1231
+ }
1232
+ return closed;
1233
+ }
1234
+
1235
+ function taskReviewClosedLine(item) {
1236
+ return `auto-closed review reminder for task ${item.task_ref.toLowerCase()}.`;
1237
+ }
1238
+
1239
+ function publicTaskReviewClosed(item) {
1240
+ return {
1241
+ id: item.event.id,
1242
+ task_ref: item.task_ref,
1243
+ proof: item.event.proof,
1244
+ };
1245
+ }
1246
+
970
1247
  function scanState(cwd = process.cwd(), options = {}) {
971
1248
  const now = options.now ? new Date(options.now) : new Date();
972
1249
  const tasks = readTaskProjection(cwd);
@@ -1247,7 +1524,7 @@ function run(args = [], context = {}) {
1247
1524
  printHelp();
1248
1525
  return 0;
1249
1526
  }
1250
- // `close <sub> --help` is a help request, not a run sweep and scan mutate
1527
+ // `close <sub> --help` is a help request, not a run, sweep and scan mutate
1251
1528
  // the ledger (escalations, auto-closes), so never execute a subcommand just
1252
1529
  // to show usage.
1253
1530
  if (rest.includes('--help') || rest.includes('-h')) {
@@ -1256,6 +1533,9 @@ function run(args = [], context = {}) {
1256
1533
  }
1257
1534
  if (subcommand === 'add') return commandAdd(rest, context);
1258
1535
  if (subcommand === 'list') return commandList(rest, context);
1536
+ if (subcommand === 'park') return commandPark(rest, context);
1537
+ if (subcommand === 'parked') return commandParked(rest, context);
1538
+ if (subcommand === 'reopen') return commandReopen(rest, context);
1259
1539
  if (subcommand === 'done') return commandDone(rest, context);
1260
1540
  if (subcommand === 'dissolve') return commandDissolve(rest, context);
1261
1541
  if (subcommand === 'snooze') return commandSnooze(rest, context);
@@ -1277,6 +1557,8 @@ module.exports = {
1277
1557
  closeIdForWhat,
1278
1558
  foldEvents,
1279
1559
  openFlags,
1560
+ parkedFlags,
1561
+ upsertSourceFlag,
1280
1562
  ledgerPath,
1281
1563
  readEvents,
1282
1564
  sweepLine,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Atris Compile learn like AI, run like code.
2
+ * Atris Compile: learn like AI, run like code.
3
3
  *
4
4
  * The compile loop: execution records → compile-to-deterministic-code →
5
5
  * statistical backtest → gated promote. The LLM is only in the build path;
@@ -328,7 +328,7 @@ function deepEqual(a, b) {
328
328
  function loadRunner(root, name) {
329
329
  const p = runnerPath(root, name);
330
330
  if (!fs.existsSync(p)) {
331
- throw new Error(`no compiled artifact at ${path.relative(root, p)} run "atris compile build ${name}" first`);
331
+ throw new Error(`no compiled artifact at ${path.relative(root, p)}, run "atris compile build ${name}" first`);
332
332
  }
333
333
  const resolved = require.resolve(p);
334
334
  delete require.cache[resolved]; // always load the latest compiled version
@@ -344,7 +344,7 @@ function loadRunner(root, name) {
344
344
  async function runBacktest(root, name, options = {}) {
345
345
  const records = readRecords(root, name);
346
346
  if (records.length === 0) {
347
- throw new Error(`no execution records for "${name}" add some with "atris compile record ${name} --input ... --output ..."`);
347
+ throw new Error(`no execution records for "${name}", add some with "atris compile record ${name} --input ... --output ..."`);
348
348
  }
349
349
  const runner = loadRunner(root, name);
350
350
  const manifest = readManifest(root, name) || defaultManifest(name);
@@ -395,7 +395,7 @@ async function runBacktest(root, name, options = {}) {
395
395
  function promoteProcess(root, name, options = {}) {
396
396
  const manifest = readManifest(root, name);
397
397
  if (!manifest) {
398
- throw new Error(`no manifest for "${name}" compile and backtest it first`);
398
+ throw new Error(`no manifest for "${name}", compile and backtest it first`);
399
399
  }
400
400
  if (options.threshold !== undefined) {
401
401
  // the only way to change the standing gate: deliberate, at promote time
@@ -403,13 +403,13 @@ function promoteProcess(root, name, options = {}) {
403
403
  }
404
404
  const bt = manifest.backtest;
405
405
  if (!bt) {
406
- throw new Error(`"${name}" has no backtest run "atris compile backtest ${name}" first`);
406
+ throw new Error(`"${name}" has no backtest, run "atris compile backtest ${name}" first`);
407
407
  }
408
408
  if (bt.version !== manifest.version) {
409
- throw new Error(`backtest is for version ${bt.version} but current version is ${manifest.version} re-run the backtest`);
409
+ throw new Error(`backtest is for version ${bt.version} but current version is ${manifest.version}, re-run the backtest`);
410
410
  }
411
411
  if (bt.accuracy < manifest.threshold) {
412
- throw new Error(`backtest accuracy ${(bt.accuracy * 100).toFixed(2)}% is below the ${(manifest.threshold * 100).toFixed(2)}% gate (${bt.passed}/${bt.total}) not promoting`);
412
+ throw new Error(`backtest accuracy ${(bt.accuracy * 100).toFixed(2)}% is below the ${(manifest.threshold * 100).toFixed(2)}% gate (${bt.passed}/${bt.total}), not promoting`);
413
413
  }
414
414
  manifest.status = 'active';
415
415
  manifest.promotedAt = new Date().toISOString();
@@ -436,10 +436,10 @@ Write the compiled artifact to ${runRel} with this exact contract:
436
436
  - CommonJS module exporting { run }: \`module.exports = { run };\`
437
437
  - \`run(input)\` takes one input value and returns the output (sync return or promise both fine)
438
438
  - deterministic: no network calls, no Date.now()/new Date() in outputs, no randomness, no environment reads
439
- - Node.js built-ins only zero npm dependencies
439
+ - Node.js built-ins only, zero npm dependencies
440
440
  - handle malformed input by throwing a clear Error, never by guessing
441
441
 
442
- The artifact will be verified by replaying every execution record through run() and requiring near-perfect accuracy, so generalize the rules do not hardcode a lookup table of the sample records.
442
+ The artifact will be verified by replaying every execution record through run() and requiring near-perfect accuracy, so generalize the rules, do not hardcode a lookup table of the sample records.
443
443
 
444
444
  Reply [COMPILE_COMPLETE] when ${runRel} is written.`;
445
445
  }
@@ -448,7 +448,7 @@ function executeBuild(root, name, options = {}) {
448
448
  const { verbose = false, timeout = 600000, notes = '', cmdOverride } = options;
449
449
  const records = readRecords(root, name);
450
450
  if (records.length === 0) {
451
- throw new Error(`no execution records for "${name}" record real runs first, then compile`);
451
+ throw new Error(`no execution records for "${name}", record real runs first, then compile`);
452
452
  }
453
453
 
454
454
  if (!cmdOverride) {
@@ -526,7 +526,7 @@ function formatAccuracy(accuracy) {
526
526
  function printBacktest(name, result, failureCount, manifest) {
527
527
  console.log(`backtest ${name} v${result.version}: ${result.passed}/${result.total} passed (${formatAccuracy(result.accuracy)}), gate ${formatAccuracy(result.threshold)}`);
528
528
  if (result.total < LOW_RECORD_WARNING) {
529
- console.log(`note: only ${result.total} record${result.total === 1 ? '' : 's'} accuracy means more with ${LOW_RECORD_WARNING}+`);
529
+ console.log(`note: only ${result.total} record${result.total === 1 ? '' : 's'}, accuracy means more with ${LOW_RECORD_WARNING}+`);
530
530
  }
531
531
  if (failureCount > 0) {
532
532
  console.log(`${failureCount} mismatch${failureCount === 1 ? '' : 'es'}. first ${Math.min(failureCount, 3)}:`);
@@ -675,7 +675,7 @@ function showCompileHelp() {
675
675
  console.log('Server commands require --agent <id> and Atris auth from atris login or ATRIS_TOKEN.');
676
676
  console.log('Loop: record real runs -> build -> backtest -> promote -> exec --record');
677
677
  console.log('When backtest accuracy drops below the gate, the process is marked drifted');
678
- console.log('and a recompile is suggested self-healing against process drift.');
678
+ console.log('and a recompile is suggested, self-healing against process drift.');
679
679
  console.log('');
680
680
  }
681
681
 
@@ -830,7 +830,7 @@ async function compileCommand(subcommand, ...rawArgs) {
830
830
  }
831
831
  const manifest = readManifest(root, name);
832
832
  if (manifest && manifest.status === 'drifted') {
833
- console.error(`warning: ${name} is marked drifted outputs may be stale. recompile with "atris compile build ${name}"`);
833
+ console.error(`warning: ${name} is marked drifted, outputs may be stale. recompile with "atris compile build ${name}"`);
834
834
  }
835
835
  const runner = loadRunner(root, name);
836
836
  const output = await runner.run(input);