atris 3.37.1 → 3.40.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.
Files changed (164) hide show
  1. package/AGENTS.md +25 -6
  2. package/README.md +2 -0
  3. package/atris/PERSONA.md +11 -11
  4. package/atris.md +17 -2
  5. package/ax +160 -34
  6. package/bin/atris.js +78 -6
  7. package/commands/activate.js +16 -3
  8. package/commands/agent-spawn.js +13 -18
  9. package/commands/autoland.js +76 -47
  10. package/commands/autopilot-front.js +0 -4
  11. package/commands/autopilot.js +0 -12
  12. package/commands/bench.js +10 -12
  13. package/commands/brain.js +0 -7
  14. package/commands/brief.js +0 -2
  15. package/commands/business-sync.js +0 -1
  16. package/commands/business.js +452 -12
  17. package/commands/chat-scan.js +5 -7
  18. package/commands/clean.js +31 -2
  19. package/commands/close.js +46 -11
  20. package/commands/cloud.js +0 -1
  21. package/commands/codex-goal.js +8 -12
  22. package/commands/computer.js +6 -5
  23. package/commands/console.js +64 -11
  24. package/commands/decide.js +166 -0
  25. package/commands/deck.js +30 -12
  26. package/commands/dream.js +0 -7
  27. package/commands/drill.js +14 -25
  28. package/commands/engine.js +206 -26
  29. package/commands/experiments.js +0 -4
  30. package/commands/fleet-report.js +12 -4
  31. package/commands/game.js +261 -0
  32. package/commands/gm.js +17 -7
  33. package/commands/goal.js +0 -6
  34. package/commands/harvest.js +1 -5
  35. package/commands/improve.js +29 -8
  36. package/commands/init.js +41 -37
  37. package/commands/integrations.js +0 -7
  38. package/commands/land.js +77 -20
  39. package/commands/launchpad.js +1 -16
  40. package/commands/lesson.js +17 -7
  41. package/commands/lifecycle.js +5 -5
  42. package/commands/log.js +55 -5
  43. package/commands/loop-front.js +0 -2
  44. package/commands/loops.js +6 -5
  45. package/commands/meet.js +0 -2
  46. package/commands/member.js +622 -573
  47. package/commands/mission.js +790 -314
  48. package/commands/next.js +0 -1
  49. package/commands/now.js +0 -12
  50. package/commands/one-lap.js +8 -8
  51. package/commands/orb.js +0 -4
  52. package/commands/pack-craft.js +0 -1
  53. package/commands/pack.js +3599 -424
  54. package/commands/play.js +6 -6
  55. package/commands/probe.js +12 -6
  56. package/commands/pulse.js +88 -64
  57. package/commands/push.js +0 -4
  58. package/commands/radar.js +14 -11
  59. package/commands/recover.js +340 -0
  60. package/commands/release.js +10 -9
  61. package/commands/report.js +0 -1
  62. package/commands/router.js +320 -0
  63. package/commands/run-front.js +0 -2
  64. package/commands/search.js +0 -3
  65. package/commands/site-deploy.js +870 -0
  66. package/commands/site.js +11 -2
  67. package/commands/skill.js +7 -2
  68. package/commands/slop.js +237 -7
  69. package/commands/status.js +6 -0
  70. package/commands/stream.js +4 -20
  71. package/commands/study.js +0 -3
  72. package/commands/sync-checkout.js +51 -0
  73. package/commands/sync.js +11 -40
  74. package/commands/task.js +999 -595
  75. package/commands/taste.js +101 -0
  76. package/commands/teach.js +86 -0
  77. package/commands/team.js +83 -3
  78. package/commands/unknowns.js +0 -10
  79. package/commands/update.js +0 -1
  80. package/commands/vercel.js +4 -2
  81. package/commands/visualize.js +0 -5
  82. package/commands/voice.js +195 -0
  83. package/commands/watch.js +1 -28
  84. package/commands/wiki.js +42 -15
  85. package/commands/wish.js +0 -1
  86. package/commands/workflow.js +2 -2
  87. package/commands/worktree.js +88 -28
  88. package/commands/xp.js +28 -30
  89. package/commands/youtube.js +0 -2
  90. package/lib/accept-verify-gate.js +126 -0
  91. package/lib/activity-stream.js +0 -2
  92. package/lib/arg-parser.js +41 -0
  93. package/lib/auto-accept-certified.js +261 -3
  94. package/lib/autoland.js +66 -3
  95. package/lib/ax-auto-lane.js +68 -7
  96. package/lib/bench/context.js +0 -2
  97. package/lib/bench/report.js +0 -1
  98. package/lib/bench/runner.js +18 -11
  99. package/lib/brief-ledger.js +0 -6
  100. package/lib/business-simulate.js +382 -0
  101. package/lib/chat-log-scan.js +0 -5
  102. package/lib/checkout-sync.js +52 -0
  103. package/lib/claude-boot-block.js +48 -0
  104. package/lib/cloud-mission.js +0 -6
  105. package/lib/codex-flight.js +0 -1
  106. package/lib/conductor-artifacts.js +25 -0
  107. package/lib/context-gatherer.js +7 -2
  108. package/lib/deck-compose.js +0 -1
  109. package/lib/deck-history.js +0 -1
  110. package/lib/deck-layout.js +0 -5
  111. package/lib/deck-review.js +0 -7
  112. package/lib/deck-schema.js +0 -1
  113. package/lib/default-verifier.js +0 -2
  114. package/lib/engine-registry.js +157 -28
  115. package/lib/experiments/daily.js +0 -6
  116. package/lib/falsifier-probe.js +84 -0
  117. package/lib/fleet.js +275 -42
  118. package/lib/functional-owner.js +0 -3
  119. package/lib/git-spawn.js +15 -0
  120. package/lib/inspect-fields.js +0 -1
  121. package/lib/json-file.js +37 -0
  122. package/lib/known-commands.js +2 -2
  123. package/lib/learnings.js +0 -2
  124. package/lib/lesson-preflight.js +146 -0
  125. package/lib/loop-doctor.js +11 -19
  126. package/lib/manifest.js +0 -2
  127. package/lib/member-alive.js +0 -1
  128. package/lib/member-switches.js +0 -5
  129. package/lib/mission-artifact.js +0 -1
  130. package/lib/mission-human-asks.js +28 -0
  131. package/lib/mission-protected-lane.js +296 -0
  132. package/lib/mission-room.js +0 -2
  133. package/lib/mission-runtime-loop.js +0 -1
  134. package/lib/next-moves.js +0 -18
  135. package/lib/official-cli-integration.js +47 -6
  136. package/lib/orb-context.js +14 -4
  137. package/lib/orb-scorecard.js +0 -1
  138. package/lib/outbound-send-gate.js +0 -6
  139. package/lib/pack-capabilities.js +685 -0
  140. package/lib/permission-grants.js +0 -6
  141. package/lib/policy-lessons.js +0 -2
  142. package/lib/pulse.js +68 -15
  143. package/lib/review-integrity.js +0 -2
  144. package/lib/router-brain.js +51 -3
  145. package/lib/runner-command.js +0 -10
  146. package/lib/runtime-bootstrap.js +0 -1
  147. package/lib/scorecard.js +0 -1
  148. package/lib/self-drive.js +45 -14
  149. package/lib/state-detection.js +0 -6
  150. package/lib/task-db.js +137 -7
  151. package/lib/task-decision.js +50 -0
  152. package/lib/taste-lessons.js +153 -0
  153. package/lib/team-presence.js +0 -1
  154. package/lib/tool-result-encode.js +17 -1
  155. package/lib/voice-gate.js +129 -2
  156. package/lib/wiki.js +152 -9
  157. package/lib/wish-audit.js +21 -7
  158. package/lib/wish-delegate.js +27 -21
  159. package/lib/wish-stats.js +0 -5
  160. package/lib/wish-store.js +0 -1
  161. package/lib/workspace-safety.js +0 -2
  162. package/lib/zip.js +95 -8
  163. package/package.json +2 -1
  164. package/templates/business-starter/persona.md +9 -0
package/commands/land.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { spawnSync } = require('child_process');
4
-
3
+ const { runGit } = require('../lib/git-spawn');
5
4
  const { listWorktrees, statusCounts } = require('./worktree');
6
5
 
7
6
  const DEFAULT_TTL_DAYS = 7;
@@ -9,12 +8,9 @@ const DEFAULT_STALE_HOURS = 48;
9
8
  const WORKTREE_REAP_GRACE_MS = 60 * 60 * 1000;
10
9
  const PROTECTED_BRANCHES = new Set(['main', 'master']);
11
10
 
12
- function runGit(args, { cwd = process.cwd(), check = true, maxBuffer } = {}) {
13
- const result = spawnSync('git', args, { cwd, encoding: 'utf8', ...(maxBuffer ? { maxBuffer } : {}) });
14
- if (check && result.status !== 0) {
15
- throw new Error(`git ${args.join(' ')} failed: ${(result.stderr || result.stdout || '').trim()}`);
16
- }
17
- return result;
11
+ // Pluralize a count + noun ("1 change", "2 changes").
12
+ function countLabel(n, word) {
13
+ return `${n} ${word}${n === 1 ? '' : 's'}`;
18
14
  }
19
15
 
20
16
  function repoRoot(cwd = process.cwd()) {
@@ -89,6 +85,14 @@ function aheadCount(root, base, ref) {
89
85
  return Number(result.stdout.trim()) || 0;
90
86
  }
91
87
 
88
+ // The subject lines of the commits a ref carries beyond base, oldest last —
89
+ // what a stranded worktree is actually holding, so surfacing it names the work.
90
+ function commitSubjects(cwd, base, ref, limit = 6) {
91
+ const result = runGit(['log', '--format=%s', `${base}..${ref}`], { cwd, check: false });
92
+ if (result.status !== 0) return [];
93
+ return result.stdout.split(/\r?\n/).filter(Boolean).slice(0, limit);
94
+ }
95
+
92
96
  function cherryStats(root, base, ref) {
93
97
  const result = runGit(['cherry', base, ref], { cwd: root, check: false });
94
98
  if (result.status !== 0) return { landedElsewhere: 0, unique: 0 };
@@ -144,11 +148,36 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
144
148
  const counts = (light ? null : statusCounts(wt.path)) || { staged: 0, unstaged: 0, untracked: 0 };
145
149
  const dirty = counts.staged + counts.unstaged + counts.untracked;
146
150
  const entry = branches.find((b) => b.name === branch) || null;
151
+ // Committed-but-unlanded work is invisible in the dirty count — a clean
152
+ // worktree can still hold commits ahead of base that silently never land.
153
+ // Carry the ahead/unique counts (and, off the light path, the subjects)
154
+ // so a stranded worktree is surfaced by what it is actually holding.
155
+ let ahead = entry ? entry.ahead : 0;
156
+ let unlandedCommits = entry ? entry.uniqueChanges : 0;
157
+ let ageDaysVal = entry ? entry.ageDays : null;
158
+ let subjects = [];
159
+ if (!branch) {
160
+ // Detached HEAD has no row in the branch table — ask the worktree itself.
161
+ const cnt = runGit(['rev-list', '--count', `${base}..HEAD`], { cwd: wt.path, check: false });
162
+ ahead = cnt.status === 0 ? Number(cnt.stdout.trim()) || 0 : 0;
163
+ if (ahead > 0) {
164
+ unlandedCommits = cherryStats(wt.path, base, 'HEAD').unique;
165
+ const ts = runGit(['log', '-1', '--format=%ct', 'HEAD'], { cwd: wt.path, check: false });
166
+ if (ts.status === 0) ageDaysVal = ageDays(Number(ts.stdout.trim()), now);
167
+ if (!light) subjects = commitSubjects(wt.path, base, 'HEAD');
168
+ }
169
+ } else if (!light && ahead > 0) {
170
+ subjects = commitSubjects(root, base, branch);
171
+ }
147
172
  worktrees.push({
148
173
  path: wt.path,
149
174
  branch,
150
175
  dirty,
151
- ageDays: entry ? entry.ageDays : null,
176
+ ahead,
177
+ unlandedCommits,
178
+ subjects,
179
+ stranded: unlandedCommits > 0,
180
+ ageDays: ageDaysVal,
152
181
  ageHours: entry ? entry.ageHours : null,
153
182
  state: entry ? entry.state : 'detached',
154
183
  });
@@ -174,12 +203,14 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
174
203
  const landed = branches.filter((b) => b.state === 'landed');
175
204
  const active = branches.filter((b) => b.state === 'active');
176
205
  const stale = active.filter((b) => b.stale);
206
+ const strandedWorktrees = worktrees.filter((w) => w.stranded);
177
207
  return {
178
208
  base,
179
209
  ttlDays,
180
210
  staleHours,
181
211
  branches,
182
212
  worktrees,
213
+ strandedWorktrees,
183
214
  summary: {
184
215
  unlanded: branches.length,
185
216
  active: active.length,
@@ -187,6 +218,7 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
187
218
  landed: landed.length,
188
219
  stale: stale.length,
189
220
  worktrees: worktrees.length,
221
+ stranded: strandedWorktrees.length,
190
222
  },
191
223
  };
192
224
  }
@@ -269,12 +301,12 @@ function salvageWorktree(w, dir, receipt) {
269
301
  // includeDetached: detached-HEAD worktrees have no branch, so the salvage
270
302
  // bundle cannot cover their commits — unattended reaps (autoland) pass false
271
303
  // and leave them for a human reap.
272
- function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOURS, base: baseOverride = '', dryRun = false, remote = true, includeDetached = true, protectCurrent = true, now = Date.now() } = {}) {
304
+ function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOURS, base: baseOverride = '', dryRun = false, remote = true, includeDetached = true, protectCurrent = true, force = false, now = Date.now() } = {}) {
273
305
  const board = collectBoard(root, { ttlDays, staleHours, base: baseOverride, now });
274
306
  const targets = board.branches.filter((b) => b.state === 'landed' || b.state === 'due');
275
307
  const targetNames = new Set(targets.map((b) => b.name));
276
308
  const candidateWorktrees = board.worktrees.filter(
277
- (w) => targetNames.has(w.branch) || (includeDetached && w.state === 'detached') || (typeof w.ageDays === 'number' && w.ageDays > ttlDays)
309
+ (w) => targetNames.has(w.branch) || (includeDetached && w.state === 'detached') || (typeof w.ageDays === 'number' && w.ageDays > ttlDays) || w.unlandedCommits > 0
278
310
  );
279
311
  const protectedWorktrees = [];
280
312
  const worktreeTargets = [];
@@ -293,6 +325,18 @@ function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOU
293
325
  protectedWorktrees.push({ ...w, reason: 'fresh_worktree_grace' });
294
326
  continue;
295
327
  }
328
+ // Unlanded commits that no branch salvage bundle will cover are lost by a
329
+ // force-remove: a detached worktree's commits ride no branch, and an active
330
+ // branch worktree is not a reap target so its commits are never bundled.
331
+ // Keep the worktree (and name what it holds) unless the operator forces it.
332
+ // A due/landed branch worktree still reaps normally — its commits ride the
333
+ // branch bundle, so removal is loss-free.
334
+ const bundleCovered = w.branch && targetNames.has(w.branch);
335
+ if (w.unlandedCommits > 0 && !force && !bundleCovered) {
336
+ const oldest = typeof w.ageDays === 'number' ? `, oldest ${w.ageDays}d` : '';
337
+ protectedWorktrees.push({ ...w, reason: `unlanded commits: ${w.unlandedCommits} never landed${oldest}, needs landing` });
338
+ continue;
339
+ }
296
340
  worktreeTargets.push(w);
297
341
  }
298
342
  for (const w of protectedWorktrees) {
@@ -472,7 +516,7 @@ function printStory(story) {
472
516
  const who = [...new Set(story.changes.map((c) => c.author))].join(', ');
473
517
  const when = story.changes[story.changes.length - 1].date;
474
518
  const last = story.changes[0].date;
475
- const age = story.ageDays === null ? '' : ` (${story.ageDays} days ago)`;
519
+ const age = story.ageDays === null ? '' : ` (${countLabel(story.ageDays, 'day')} ago)`;
476
520
  console.log(` started ${when}, last touched ${last}${age}, by ${who}.`);
477
521
  console.log('');
478
522
  console.log(' what it tried to do:');
@@ -488,7 +532,7 @@ function printStory(story) {
488
532
  console.log('');
489
533
  }
490
534
  if (story.uniqueChanges === 0) {
491
- console.log(` bottom line: all ${story.changes.length} changes made it into ${story.base}`);
535
+ console.log(` bottom line: all ${countLabel(story.changes.length, 'change')} made it into ${story.base}`);
492
536
  console.log(' some other way. nothing here would be lost by clearing it.');
493
537
  } else if (story.landedElsewhere > 0) {
494
538
  console.log(` bottom line: ${story.landedElsewhere} of ${story.changes.length} changes made it into ${story.base} another`);
@@ -522,8 +566,15 @@ function printBoard(board) {
522
566
  console.log(` ${label.padEnd(11)} ${String(b.ageDays).padStart(3)}d ${changes} ${b.name}`);
523
567
  }
524
568
  for (const w of board.worktrees) {
525
- const edits = w.dirty === 1 ? '1 unsaved edit' : `${w.dirty} unsaved edits`;
526
- console.log(` side copy ${w.path} (${edits})`);
569
+ const bits = [];
570
+ if (w.stranded) bits.push(w.unlandedCommits === 1 ? '1 commit never landed' : `${w.unlandedCommits} commits never landed`);
571
+ if (w.dirty > 0) bits.push(w.dirty === 1 ? '1 unsaved edit' : `${w.dirty} unsaved edits`);
572
+ const age = typeof w.ageDays === 'number' ? `, ${countLabel(w.ageDays, 'day')} old` : '';
573
+ const detail = bits.length ? ` (${bits.join(', ')}${age})` : '';
574
+ console.log(` side copy ${w.path}${detail}`);
575
+ if (w.stranded) {
576
+ for (const s of w.subjects) console.log(` - ${s}`);
577
+ }
527
578
  }
528
579
  console.log('');
529
580
  const s = board.summary;
@@ -554,7 +605,9 @@ function printReceipt(receipt) {
554
605
  // fresh-grace keeps are healthy active work, not a call to action —
555
606
  // "needs a human" on those trained readers to ignore the real ones.
556
607
  for (const k of receipt.keptWorktrees || []) {
608
+ // The receipt keeps the raw codes; the human line speaks in sentences.
557
609
  if (String(k).includes('(fresh_worktree_grace)')) console.log(` in use, left alone: ${k.replace(' (fresh_worktree_grace)', '')}`);
610
+ else if (String(k).includes('(current_checkout)')) console.log(` you are standing in it, left alone: ${k.replace(' (current_checkout)', '')}`);
558
611
  else console.log(` ✋ kept, needs a human: ${k}`);
559
612
  }
560
613
  for (const m of receipt.keptMovedBranches || []) console.log(` moved since scan, left alone: ${m}`);
@@ -564,7 +617,8 @@ function printReceipt(receipt) {
564
617
  console.log('');
565
618
  }
566
619
 
567
- function readFlag(args, name, fallback = '') {
620
+ // Preserve split-only parsing and its fallback for missing or falsey values.
621
+ function readFollowingFlag(args, name, fallback = '') {
568
622
  const idx = args.indexOf(name);
569
623
  if (idx === -1) return fallback;
570
624
  return args[idx + 1] || fallback;
@@ -586,6 +640,8 @@ function showHelp() {
586
640
  console.log(' atris land --reap back up, then clear anything overdue');
587
641
  console.log(' or already landed (here and on github)');
588
642
  console.log(' atris land --reap --dry-run preview what would be cleared');
643
+ console.log(' atris land --reap --force also clear side copies holding');
644
+ console.log(' commits that never landed (backs up first)');
589
645
  console.log(' atris land --ttl <days> change the 7-day overdue line');
590
646
  console.log(' atris land --json machine-readable output');
591
647
  console.log('');
@@ -603,21 +659,21 @@ function landCommand(args = []) {
603
659
  console.error('not a git repository');
604
660
  return 1;
605
661
  }
606
- const ttlRaw = readFlag(args, '--ttl', '');
662
+ const ttlRaw = readFollowingFlag(args, '--ttl', '');
607
663
  const ttlParsed = Number(ttlRaw);
608
664
  const ttlDays = ttlRaw !== '' && Number.isFinite(ttlParsed) && ttlParsed >= 0 ? ttlParsed : DEFAULT_TTL_DAYS;
609
- const base = readFlag(args, '--base', '');
665
+ const base = readFollowingFlag(args, '--base', '');
610
666
  const json = args.includes('--json');
611
667
 
612
668
  if (args.includes('--reap')) {
613
- const receipt = reap(root, { ttlDays, base, dryRun: args.includes('--dry-run'), remote: !args.includes('--no-remote') });
669
+ const receipt = reap(root, { ttlDays, base, dryRun: args.includes('--dry-run'), remote: !args.includes('--no-remote'), force: args.includes('--force') });
614
670
  if (json) console.log(JSON.stringify(receipt, null, 2));
615
671
  else printReceipt(receipt);
616
672
  return 0;
617
673
  }
618
674
 
619
675
  const lookupArgs = args[0] === 'status' ? args.slice(1) : args;
620
- const name = lookupArgs.find((a) => !a.startsWith('-') && a !== readFlag(lookupArgs, '--ttl') && a !== readFlag(lookupArgs, '--base'));
676
+ const name = lookupArgs.find((a) => !a.startsWith('-') && a !== readFollowingFlag(lookupArgs, '--ttl') && a !== readFollowingFlag(lookupArgs, '--base'));
621
677
  if (name) {
622
678
  const story = collectStory(root, name, { base });
623
679
  if (!story) {
@@ -637,6 +693,7 @@ function landCommand(args = []) {
637
693
 
638
694
  module.exports = {
639
695
  collectBoard,
696
+ countLabel,
640
697
  landCommand,
641
698
  landSummary,
642
699
  reap,
@@ -2,23 +2,10 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const { hasFlag, readFlag } = require('../lib/arg-parser');
5
6
 
6
7
  const ACTIVE_MISSION_STATUSES = new Set(['planning', 'running', 'ready']);
7
8
 
8
- function hasFlag(args, name) {
9
- return args.includes(name);
10
- }
11
-
12
- function readFlag(args, name, fallback = '') {
13
- const prefix = `${name}=`;
14
- for (let i = 0; i < args.length; i += 1) {
15
- const arg = String(args[i]);
16
- if (arg === name && args[i + 1] && !String(args[i + 1]).startsWith('--')) return String(args[i + 1]);
17
- if (arg.startsWith(prefix)) return arg.slice(prefix.length);
18
- }
19
- return fallback;
20
- }
21
-
22
9
  function readJson(file) {
23
10
  try {
24
11
  return JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -632,7 +619,5 @@ function launchpadCommand(args = []) {
632
619
  }
633
620
 
634
621
  module.exports = {
635
- collectLaunchpad,
636
622
  launchpadCommand,
637
- renderLaunchpad,
638
623
  };
@@ -213,7 +213,23 @@ function mineLessons(args) {
213
213
  }
214
214
  }
215
215
 
216
+ function printLessonUsage() {
217
+ console.log('');
218
+ console.log(' Usage: atris lesson add <slug> <pass|fail> "<text>"');
219
+ console.log(' atris lesson mine [--json] [--dry-run]');
220
+ console.log(' atris lesson sweep [--json] [--dry-run]');
221
+ console.log(' atris lesson resolve [--json] [--dry-run]');
222
+ console.log('');
223
+ }
224
+
216
225
  function lessonAtris(subcommand, ...args) {
226
+ // -h/--help is an explicit help request: print usage and exit 0, before any
227
+ // filesystem check, so `atris lesson --help` works everywhere.
228
+ if (subcommand === '-h' || subcommand === '--help') {
229
+ printLessonUsage();
230
+ process.exit(0);
231
+ }
232
+
217
233
  const atrisDir = path.join(process.cwd(), 'atris');
218
234
  if (!fs.existsSync(atrisDir)) {
219
235
  console.error(' ✗ atris/ folder not found. Run "atris init" first.');
@@ -236,12 +252,7 @@ function lessonAtris(subcommand, ...args) {
236
252
  }
237
253
 
238
254
  if (subcommand !== 'add') {
239
- console.log('');
240
- console.log(' Usage: atris lesson add <slug> <pass|fail> "<text>"');
241
- console.log(' atris lesson mine [--json] [--dry-run]');
242
- console.log(' atris lesson sweep [--json] [--dry-run]');
243
- console.log(' atris lesson resolve [--json] [--dry-run]');
244
- console.log('');
255
+ printLessonUsage();
245
256
  process.exit(subcommand ? 1 : 0);
246
257
  }
247
258
 
@@ -269,4 +280,3 @@ function lessonAtris(subcommand, ...args) {
269
280
 
270
281
  module.exports = lessonAtris;
271
282
  module.exports.autoResolveLessons = autoResolveLessons;
272
- module.exports.tagLessonResolvedInMd = tagLessonResolvedInMd;
@@ -168,7 +168,7 @@ async function sleepAtris() {
168
168
  }
169
169
 
170
170
  /**
171
- * Wake a sleeping workspace so agents resume automatically.
171
+ * Wake a sleeping workspace: remote wake request, or local wake flag.
172
172
  * @returns {Promise<void>}
173
173
  */
174
174
  async function wakeAtris() {
@@ -179,7 +179,7 @@ async function wakeAtris() {
179
179
  if (!slug || slug === '--help' || slug === '-h' || slug === 'help') {
180
180
  console.log('Usage: atris wake [business|member] [--loop <loop-id>]');
181
181
  console.log('');
182
- console.log(' Wake a sleeping workspace. Agents resume automatically.');
182
+ console.log(' Wake a sleeping workspace. Wake signal sent; loops pick it up on their next pass.');
183
183
  console.log(' Or flip a local member / member-loop switch awake.');
184
184
  process.exit(0);
185
185
  }
@@ -207,13 +207,13 @@ async function wakeAtris() {
207
207
  console.log(`Waking business computer '${business.name || slug}'...`);
208
208
 
209
209
  if (result.data && result.data.status === 'running' && result.data.endpoint) {
210
- console.log(`Business computer '${business.name || slug}' is alive. Agents resuming.`);
210
+ console.log(`Business computer '${business.name || slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
211
211
  return;
212
212
  }
213
213
 
214
214
  const running = await waitForBusinessComputer(creds.token, business);
215
215
  if (running) {
216
- console.log(`Business computer '${business.name || slug}' is alive. Agents resuming.`);
216
+ console.log(`Business computer '${business.name || slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
217
217
  return;
218
218
  }
219
219
 
@@ -246,7 +246,7 @@ async function wakeAtris() {
246
246
  });
247
247
 
248
248
  if (status.ok && status.data && status.data.status === 'running') {
249
- console.log(`Workspace '${slug}' is alive. Agents resuming.`);
249
+ console.log(`Workspace '${slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
250
250
  return;
251
251
  }
252
252
  }
package/commands/log.js CHANGED
@@ -97,6 +97,56 @@ function readDigestSection(root, title, relativePath, maxLines = null) {
97
97
  };
98
98
  }
99
99
 
100
+ // Splits a journal body into its ## groups so the digest can show
101
+ // completed / inbox / notes as short labeled blocks instead of a raw dump.
102
+ function journalGroups(lines) {
103
+ const groups = [];
104
+ let current = null;
105
+ for (const line of lines) {
106
+ const heading = line.match(/^##\s+(.+?)\s*$/);
107
+ if (heading) {
108
+ current = { heading: heading[1], lines: [] };
109
+ groups.push(current);
110
+ } else if (current) {
111
+ current.lines.push(line);
112
+ }
113
+ }
114
+ return groups;
115
+ }
116
+
117
+ function bulletCount(group) {
118
+ return group.lines.filter((line) => /^\s*-\s+/.test(line)).length;
119
+ }
120
+
121
+ function digestSummaryLine(date, sections) {
122
+ let completed = 0;
123
+ let inbox = 0;
124
+ for (const section of sections) {
125
+ for (const group of journalGroups(section.lines)) {
126
+ if (/^completed/i.test(group.heading)) completed += bulletCount(group);
127
+ else if (/^inbox/i.test(group.heading)) inbox += bulletCount(group);
128
+ }
129
+ }
130
+ const parts = [`${sections.length} log${sections.length === 1 ? '' : 's'}`];
131
+ if (completed) parts.push(`${completed} completed`);
132
+ if (inbox) parts.push(`${inbox} inbox`);
133
+ return `${date}: ${parts.join(', ')}`;
134
+ }
135
+
136
+ function renderDigestSection(section) {
137
+ const groups = journalGroups(section.lines);
138
+ if (!groups.length) {
139
+ return [`=== ${section.title} ===`, section.path, ...section.lines].join('\n');
140
+ }
141
+ const out = [`=== ${section.title} ===`, section.path];
142
+ for (const group of groups) {
143
+ const body = group.lines.filter((line) => line.trim() && line.trim() !== '---');
144
+ if (!body.length) continue;
145
+ out.push('', `${group.heading.toLowerCase()} (${bulletCount(group)})`, ...body);
146
+ }
147
+ return out.join('\n');
148
+ }
149
+
100
150
  function logsDigest(args = [], options = {}) {
101
151
  const root = options.cwd || process.cwd();
102
152
  const date = digestDate(args);
@@ -125,11 +175,11 @@ function logsDigest(args = [], options = {}) {
125
175
  } else if (sections.length === 0) {
126
176
  console.log(`No logs for ${date} yet.`);
127
177
  } else {
128
- console.log(sections.map((section) => [
129
- `=== ${section.title} ===`,
130
- section.path,
131
- ...section.lines,
132
- ].join('\n')).join('\n\n'));
178
+ console.log([
179
+ digestSummaryLine(date, sections),
180
+ '',
181
+ ...sections.map(renderDigestSection),
182
+ ].join('\n'));
133
183
  }
134
184
 
135
185
  return payload;
@@ -396,11 +396,9 @@ function loopFront(argv = []) {
396
396
  module.exports = {
397
397
  routeLoop,
398
398
  renderLoopHome,
399
- printLoopHome,
400
399
  startLocalOptions,
401
400
  localRunSummary,
402
401
  loopStatusJson,
403
- loopSeedMove,
404
402
  createNextLoopTask,
405
403
  loopReport,
406
404
  renderLoopReport,
package/commands/loops.js CHANGED
@@ -382,6 +382,12 @@ function showLoopsHelp() {
382
382
  }
383
383
 
384
384
  function loopsCommand(subcommand, ...args) {
385
+ // `loops <sub> --help` is a help request, not a run — tick/start/stop/init
386
+ // mutate loop state, so never execute a subcommand just to show usage.
387
+ if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help'
388
+ || args.includes('--help') || args.includes('-h')) {
389
+ return showLoopsHelp();
390
+ }
385
391
  switch (subcommand) {
386
392
  case undefined:
387
393
  case 'audit':
@@ -408,9 +414,4 @@ function loopsCommand(subcommand, ...args) {
408
414
 
409
415
  module.exports = {
410
416
  loopsCommand,
411
- showBoard,
412
- initLoops,
413
- auditLoops,
414
- tickLoops,
415
- structuralProblems,
416
417
  };
package/commands/meet.js CHANGED
@@ -215,6 +215,4 @@ async function meetCommand(args = [], root = process.cwd()) {
215
215
  module.exports = {
216
216
  meetCommand,
217
217
  slugify,
218
- buildBookingProfile,
219
- renderProfile,
220
218
  };