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/avail.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Booking availability read/update the windows that power /book/{username}.
4
+ * Booking availability: read/update the windows that power /book/{username}.
5
5
  *
6
6
  * atris avail # show current settings
7
7
  * atris avail set --wake 2:30pm --sleep 9pm --where Ibiza --days all --apply
@@ -172,13 +172,13 @@ function summarizeHours(availableHours) {
172
172
  }
173
173
 
174
174
  // The booking link is the funnel: an operator shares /book/{username} so a
175
- // stranger can pick a slot. With no claimed handle there is no link say so and
175
+ // stranger can pick a slot. With no claimed handle there is no link, say so and
176
176
  // name the fix instead of silently dropping the line.
177
177
  function formatBookingLink(username, appBase) {
178
178
  const handle = String(username || '').trim();
179
179
  const base = String(appBase || '').replace(/\/+$/, '');
180
180
  if (!handle) {
181
- return ' link: none yet claim a handle with `atris signup` to get your /book link';
181
+ return ' link: none yet, claim a handle with `atris signup` to get your /book link';
182
182
  }
183
183
  return ` link: ${base}/book/${handle}`;
184
184
  }
@@ -263,7 +263,7 @@ async function setAvail(argv) {
263
263
  console.log(summarizeHours(payload.available_hours));
264
264
 
265
265
  if (!opts.apply) {
266
- console.log('\n Dry run add --apply to push to your profile.');
266
+ console.log('\n Dry run, add --apply to push to your profile.');
267
267
  return 0;
268
268
  }
269
269
 
@@ -308,7 +308,7 @@ async function slotsAvail(argv) {
308
308
  if (!username) {
309
309
  const identity = await apiRequestJson('/identity/me', { method: 'GET', token });
310
310
  if (!identity.ok || !identity.data?.username) {
311
- console.error('Could not resolve username pass --user keshav');
311
+ console.error('Could not resolve username, pass --user keshav');
312
312
  return 1;
313
313
  }
314
314
  username = identity.data.username;
package/commands/bench.js CHANGED
@@ -8,9 +8,11 @@ const {
8
8
  packMetadata,
9
9
  readResultRecords,
10
10
  runBench,
11
+ runBenchPair,
11
12
  taskMetadata,
12
13
  } = require('../lib/bench/runner');
13
14
  const { buildBenchReport, renderBenchReportText } = require('../lib/bench/report');
15
+ const { materializeCandidate } = require('../lib/bench/tree-render');
14
16
  const { hasFlag } = require('../lib/arg-parser');
15
17
 
16
18
  function isAtrisCliRepo(cwd = process.cwd()) {
@@ -51,16 +53,32 @@ function readRepeatedFlag(args, name) {
51
53
  }
52
54
 
53
55
  function printHelp() {
54
- console.log('Usage: atris bench run [--pack <id>] [--engine <name>] [--task <id> ...] [--label baseline|candidate] [--experiment <id>] [--update-baseline] [--json]');
56
+ console.log('Usage: atris bench run [--pack <id>] [--engine <name>] [--model <id>] [--task <id> ...] [--label baseline|candidate] [--experiment <id>] [--update-baseline] [--json]');
57
+ console.log('Usage: atris bench pair --candidate <dir|git-ref> [--pack <id>] [--engine <name>] [--model <id>] [--task <id> ...] [--min-win-margin <n>] [--repeats <n>] [--current <dir>] [--json]');
55
58
  console.log('Usage: atris bench results [--last N] [--json]');
56
59
  console.log('Usage: atris bench tasks [--pack <id>] [--json]');
57
60
  console.log('Usage: atris bench packs [--json]');
58
61
  console.log('Usage: atris bench report [--pack agents-v1] [--json]');
62
+ console.log('packs: core-v1, agents-v1, harness-v1');
59
63
  console.log('');
60
64
  console.log('Bench runs product gates and only belongs in the atris CLI repo.');
61
65
  console.log('Pass --here to allow running outside that repo.');
62
66
  }
63
67
 
68
+ function pairScoreText(score) {
69
+ if (score === null) return 'could-not-run';
70
+ return score === 1 ? 'pass' : 'fail';
71
+ }
72
+
73
+ function printPairText(record) {
74
+ const width = Math.max(0, ...record.tasks.map((task) => task.id.length)) + 3;
75
+ for (const task of record.tasks) {
76
+ console.log(`${task.id.padEnd(width)}current ${pairScoreText(task.current)} candidate ${pairScoreText(task.candidate)} ${task.outcome}`);
77
+ }
78
+ console.log(`wins ${record.wins} losses ${record.losses} ties ${record.ties}`);
79
+ console.log(`verdict: ${record.verdict}, ${record.reason}`);
80
+ }
81
+
64
82
  function printRunText(record) {
65
83
  console.log(record.summary);
66
84
  for (const task of record.tasks) {
@@ -76,6 +94,7 @@ async function runCommand(args) {
76
94
  const result = await runBench({
77
95
  pack: readInlineFirstFlag(args, '--pack') || undefined,
78
96
  engine: readInlineFirstFlag(args, '--engine') || undefined,
97
+ model: readInlineFirstFlag(args, '--model') || undefined,
79
98
  taskIds: readRepeatedFlag(args, '--task'),
80
99
  label: readInlineFirstFlag(args, '--label'),
81
100
  experiment: readInlineFirstFlag(args, '--experiment'),
@@ -102,6 +121,48 @@ async function runCommand(args) {
102
121
  }
103
122
  }
104
123
 
124
+ async function pairCommand(args) {
125
+ const asJson = hasFlag(args, '--json');
126
+ const candidateSource = readInlineFirstFlag(args, '--candidate');
127
+ let candidateRoot = null;
128
+ let removeCandidate = false;
129
+ try {
130
+ if (!candidateSource) throw new BenchInfraError('candidate is required');
131
+ try {
132
+ removeCandidate = !fs.statSync(path.resolve(candidateSource)).isDirectory();
133
+ } catch {
134
+ removeCandidate = true;
135
+ }
136
+ candidateRoot = materializeCandidate(candidateSource, process.cwd());
137
+ const result = await runBenchPair({
138
+ pack: readInlineFirstFlag(args, '--pack') || undefined,
139
+ engine: readInlineFirstFlag(args, '--engine') || undefined,
140
+ model: readInlineFirstFlag(args, '--model') || undefined,
141
+ taskIds: readRepeatedFlag(args, '--task'),
142
+ minWinMargin: readInlineFirstFlag(args, '--min-win-margin') || undefined,
143
+ repeats: readInlineFirstFlag(args, '--repeats') || undefined,
144
+ currentRoot: readInlineFirstFlag(args, '--current') || undefined,
145
+ candidateRoot,
146
+ stateRoot: process.cwd(),
147
+ });
148
+ if (asJson) console.log(JSON.stringify(result.record));
149
+ else printPairText(result.record);
150
+ return result.exitCode;
151
+ } catch (err) {
152
+ const message = err && err.message ? err.message : String(err);
153
+ if (asJson) {
154
+ console.log(JSON.stringify({ schema: 'atris.bench.error.v1', error: message }));
155
+ } else {
156
+ console.error(`atris bench: ${message}`);
157
+ }
158
+ return 2;
159
+ } finally {
160
+ if (removeCandidate && candidateRoot) {
161
+ fs.rmSync(candidateRoot, { recursive: true, force: true });
162
+ }
163
+ }
164
+ }
165
+
105
166
  function tasksCommand(args) {
106
167
  const pack = readInlineFirstFlag(args, '--pack') || undefined;
107
168
  const tasks = taskMetadata({ pack });
@@ -200,6 +261,7 @@ async function benchCommand(args = []) {
200
261
  return 2;
201
262
  }
202
263
  if (subcommand === 'run') return runCommand(rest);
264
+ if (subcommand === 'pair') return pairCommand(rest);
203
265
  if (subcommand === 'tasks') return tasksCommand(rest);
204
266
  if (subcommand === 'packs') return packsCommand(rest);
205
267
  if (subcommand === 'results') return resultsCommand(rest);
@@ -32,7 +32,7 @@ async function browseAtris() {
32
32
  const result = await apiRequestJson(endpoint, { method: 'GET', token: creds.token });
33
33
 
34
34
  if (!result.ok) {
35
- // API failed try local templates
35
+ // API failed, try local templates
36
36
  console.log(' API unavailable, checking local templates...');
37
37
  const localDir = path.join(os.homedir(), '.atris', 'templates');
38
38
  if (!fs.existsSync(localDir)) {
@@ -61,7 +61,7 @@ async function browseAtris() {
61
61
  desc = meta.description || '';
62
62
  } catch {}
63
63
  }
64
- console.log(` ${name}${desc ? '' + desc : ''}`);
64
+ console.log(` ${name}${desc ? ': ' + desc : ''}`);
65
65
  }
66
66
  console.log('');
67
67
  return;
@@ -58,14 +58,14 @@ function validateBusinessCreateName(name) {
58
58
  return {
59
59
  ok: false,
60
60
  usage: BUSINESS_CREATE_USAGE,
61
- detail: `Refusing to create a business named "${name}" looks like a flag, not a name.`,
61
+ detail: `Refusing to create a business named "${name}", looks like a flag, not a name.`,
62
62
  };
63
63
  }
64
64
  if (isAccidentalHelpBusiness(name)) {
65
65
  return {
66
66
  ok: false,
67
67
  usage: BUSINESS_CREATE_USAGE,
68
- detail: `Refusing to create a business named "${name}" looks like accidental help output, not a real business.`,
68
+ detail: `Refusing to create a business named "${name}", looks like accidental help output, not a real business.`,
69
69
  };
70
70
  }
71
71
  return { ok: true };
@@ -883,7 +883,7 @@ function collectBusinessOperatingState(cwd = process.cwd(), nowMs = Date.now())
883
883
  }
884
884
 
885
885
  function seedBusinessStarterTask(cwd, todoPath, starterAction) {
886
- const title = `${starterAction.title} ${starterAction.action}`;
886
+ const title = `${starterAction.title}: ${starterAction.action}`;
887
887
  try {
888
888
  const taskDb = require('../lib/task-db');
889
889
  const db = taskDb.open();
@@ -1626,12 +1626,12 @@ ${stagedSources.length > 0 ? `- Local evidence files: ${stagedSources.length}` :
1626
1626
  '- Before sharing the workspace, run `atris business share --write` and send the handoff.',
1627
1627
  ].filter(Boolean).join('\n') + '\n';
1628
1628
  fs.writeFileSync(cheatSheetPath, operatorSummary, 'utf8');
1629
- fs.writeFileSync(onePagerPath, operatorSummary.replace('# ', '# One Pager '), 'utf8');
1629
+ fs.writeFileSync(onePagerPath, operatorSummary.replace('# ', '# One Pager: '), 'utf8');
1630
1630
 
1631
1631
  const todoPath = path.join(cwd, 'atris', 'TODO.md');
1632
1632
  if (fs.existsSync(todoPath)) {
1633
1633
  let todoContent = fs.readFileSync(todoPath, 'utf8');
1634
- const taskLine = `- **Onboard:** ${starterAction.title} ${starterAction.action} [execute]\n`;
1634
+ const taskLine = `- **Onboard:** ${starterAction.title}: ${starterAction.action} [execute]\n`;
1635
1635
  const backlogMatch = todoContent.match(/^## Backlog\s*$/m);
1636
1636
  if (backlogMatch) {
1637
1637
  const insertAt = backlogMatch.index + backlogMatch[0].length;
@@ -1761,7 +1761,7 @@ function detectBusinessSlug(explicitSlug) {
1761
1761
  async function findExistingBusinessBySlug(slug, token) {
1762
1762
  if (!slug) return null;
1763
1763
 
1764
- // Local cache first no network round-trip needed.
1764
+ // Local cache first, no network round-trip needed.
1765
1765
  const local = loadBusinesses();
1766
1766
  if (local[slug]) {
1767
1767
  return { id: local[slug].business_id, name: local[slug].name, slug, source: 'local' };
@@ -1774,7 +1774,7 @@ async function findExistingBusinessBySlug(slug, token) {
1774
1774
 
1775
1775
  if (!token) return null;
1776
1776
 
1777
- // Cloud lookup covers businesses the user is a member of but hasn't added.
1777
+ // Cloud lookup, covers businesses the user is a member of but hasn't added.
1778
1778
  const direct = await apiRequestJson(`/business/by-slug/${encodeURIComponent(slug)}`, {
1779
1779
  method: 'GET',
1780
1780
  token,
@@ -1878,7 +1878,7 @@ async function listBusinesses(opts = {}) {
1878
1878
 
1879
1879
  /**
1880
1880
  * Walk ~/arena/atris-business/ and print a fleet status table for every
1881
- * customer workspace. Pure local no API calls, no rate-limit risk.
1881
+ * customer workspace. Pure local, no API calls, no rate-limit risk.
1882
1882
  *
1883
1883
  * Classifies each dir as: ready, flat, unbound, nested, bare, or superseded.
1884
1884
  *
@@ -1986,7 +1986,7 @@ function listBusinessesLocal(opts = {}) {
1986
1986
  }
1987
1987
 
1988
1988
  console.log('');
1989
- console.log(`Atris Fleet ${rootDir}`);
1989
+ console.log(`Atris Fleet: ${rootDir}`);
1990
1990
  console.log('═'.repeat(86));
1991
1991
  console.log(' CUSTOMER STATE FILES BIZ.JSON ATRIS/ ACTION');
1992
1992
  console.log(' ' + '─'.repeat(83));
@@ -2016,7 +2016,7 @@ function listBusinessesLocal(opts = {}) {
2016
2016
  const counts = {};
2017
2017
  for (const c of customers) counts[c.state] = (counts[c.state] || 0) + 1;
2018
2018
  const summary = order.filter((s) => counts[s]).map((s) => `${counts[s]} ${s}`).join(', ');
2019
- console.log(` ${customers.length} customers ${summary}`);
2019
+ console.log(` ${customers.length} customers: ${summary}`);
2020
2020
  console.log('');
2021
2021
 
2022
2022
  const needsWork = customers.filter((c) => ['flat', 'unbound', 'flat-unbound', 'nested'].includes(c.state));
@@ -3086,7 +3086,7 @@ async function connectService(connector, ...flags) {
3086
3086
  for (const s of integrations) {
3087
3087
  console.log(` ${s}`);
3088
3088
  }
3089
- if (integrations.length === 0) console.log(' (none found install skills first)');
3089
+ if (integrations.length === 0) console.log(' (none found, install skills first)');
3090
3090
  return;
3091
3091
  }
3092
3092
 
@@ -3540,7 +3540,7 @@ function printBusinessHelp() {
3540
3540
  }
3541
3541
 
3542
3542
  async function businessCommand(subcommand, ...args) {
3543
- // Help intercept without this, `atris business init --help` would treat
3543
+ // Help intercept, without this, `atris business init --help` would treat
3544
3544
  // `--help` as a business name and create one. Same for any subcommand that
3545
3545
  // takes a positional name/slug.
3546
3546
  if (!subcommand || isHelpToken(subcommand)) {
package/commands/card.js CHANGED
@@ -1,4 +1,4 @@
1
- // atris card one line of text -> a beautiful, on-brand image (your theme).
1
+ // atris card, one line of text -> a beautiful, on-brand image (your theme).
2
2
  //
3
3
  // atris card "Ship faster" --kind statement --theme brand --size og
4
4
  // atris card "It just works" --kind quote --by "a happy user"
@@ -64,7 +64,7 @@ function run(argv) {
64
64
  const { flags, pos } = parseFlags(argv);
65
65
 
66
66
  if (pos[0] === 'help' || flags.help) {
67
- console.log(`\n atris card one line of text into an on-brand image\n
67
+ console.log(`\n atris card, one line of text into an on-brand image\n
68
68
  atris card "Your headline" [--kind statement|quote|stat] [--theme <name>] [--size og|wide|square|story]
69
69
  flags: --sub --kicker --by --number --label --brand --version --out <file.png> --html-only\n
70
70
  examples:
package/commands/clean.js CHANGED
@@ -122,7 +122,7 @@ function cleanAtris(options = {}) {
122
122
  if (unhealable.length > 0) {
123
123
  console.log(`⚠ ${unhealable.length} MAP.md ${unhealable.length === 1 ? 'ref' : 'refs'} couldn't be healed:`);
124
124
  unhealable.slice(0, 3).forEach(ref => {
125
- console.log(` • ${ref.file}:${ref.line} ${ref.reason}`);
125
+ console.log(` • ${ref.file}:${ref.line}: ${ref.reason}`);
126
126
  });
127
127
  if (unhealable.length > 3) {
128
128
  console.log(` ... and ${unhealable.length - 3} more`);
@@ -156,7 +156,7 @@ function cleanAtris(options = {}) {
156
156
  if (stalePages.length > 0) {
157
157
  console.log(`⚠ ${stalePages.length} stale ${stalePages.length === 1 ? 'page' : 'pages'} (source changed since last compiled):`);
158
158
  stalePages.forEach(sp => {
159
- console.log(` • ${path.relative(cwd, sp.page)} stale source: ${sp.staleSource}`);
159
+ console.log(` • ${path.relative(cwd, sp.page)}: stale source: ${sp.staleSource}`);
160
160
  });
161
161
  console.log('');
162
162
  } else {
@@ -165,7 +165,7 @@ function cleanAtris(options = {}) {
165
165
 
166
166
  // Dead code
167
167
  if ((results.deadFiles || []).length > 0) {
168
- console.log(`⚠ ${results.deadFiles.length} dead ${results.deadFiles.length === 1 ? 'file' : 'files'} (unreachable and unreferenced atris slop dead for detail):`);
168
+ console.log(`⚠ ${results.deadFiles.length} dead ${results.deadFiles.length === 1 ? 'file' : 'files'} (unreachable and unreferenced, atris slop dead for detail):`);
169
169
  results.deadFiles.slice(0, 5).forEach((f) => console.log(` • ${f}`));
170
170
  if (results.deadFiles.length > 5) console.log(` ... and ${results.deadFiles.length - 5} more`);
171
171
  console.log('');
@@ -252,8 +252,8 @@ function firstLine(text) {
252
252
  function mapScriptLine(step) {
253
253
  const script = MAP_SCRIPTS.find(s => step.script.endsWith(s.file));
254
254
  if (step.status === 'would_run') return `✓ ${script.dryLabel} (${step.script})`;
255
- if (step.status === 'skipped') return `○ Skipped ${step.script} ${step.detail}`;
256
- if (step.status === 'failed') return `✗ ${script.failLabel} ${step.detail}`;
255
+ if (step.status === 'skipped') return `○ Skipped ${step.script}: ${step.detail}`;
256
+ if (step.status === 'failed') return `✗ ${script.failLabel}: ${step.detail}`;
257
257
  return `✓ ${script.ranLabel}`;
258
258
  }
259
259
 
@@ -368,8 +368,8 @@ function healBrokenMapRefs(cwd, atrisDir, dryRun = false, homeDir = os.homedir()
368
368
 
369
369
  // Match both `file.js:123` and `file.js:123-456` with surrounding context
370
370
  // [^\S\n] = horizontal whitespace only (no newlines)
371
- // Required delimiter [(,[,—,-] prevents bleeding into adjacent refs on same line
372
- const refPattern = /(`?)([a-zA-Z0-9_~\-./\\]+\.(js|ts|py|go|rs|rb|java|c|cpp|h|hpp|md|json|yaml|yml)):(\d+)(?:-(\d+))?(`?)([^\S\n]*[\(\[—\-][^\S\n]*([^)\]\n]+))?/g;
371
+ // Required delimiter set, including the u2014 escape, prevents bleeding into adjacent refs on same line
372
+ const refPattern = /(`?)([a-zA-Z0-9_~\-./\\]+\.(js|ts|py|go|rs|rb|java|c|cpp|h|hpp|md|json|yaml|yml)):(\d+)(?:-(\d+))?(`?)([^\S\n]*[\(\[\u2014\-][^\S\n]*([^)\]\n]+))?/g;
373
373
 
374
374
  // Function Inventory form: `symbolName()` → `file:line[-line]`
375
375
  // Pre-scan to build a (file:line) → symbol map so refs with the symbol BEFORE them still verify.
@@ -518,7 +518,7 @@ function findFunctionEnd(lines, startLine) {
518
518
  }
519
519
 
520
520
  /**
521
- * Extract a symbol name from context like "(interactiveEntry function)" or "Main entry"
521
+ * Extract a symbol name from context like "(interactiveEntry function)" or "Main entry" after punctuation
522
522
  */
523
523
  function extractSymbol(context) {
524
524
  if (!context) return null;
@@ -529,7 +529,7 @@ function extractSymbol(context) {
529
529
  const unquoted = context.trim().replace(/`/g, '');
530
530
  const wrapper = unquoted.match(/^([\(\[])/)?.[1];
531
531
  let cleaned = unquoted
532
- .replace(/^[\(\[—\-:]+\s*/, '')
532
+ .replace(/^[\(\[\u2014\-:]+\s*/, '')
533
533
  .trim();
534
534
  if (wrapper === '(') cleaned = cleaned.replace(/\)$/, '').trim();
535
535
  if (wrapper === '[') cleaned = cleaned.replace(/\]$/, '').trim();
@@ -553,13 +553,13 @@ function extractSymbol(context) {
553
553
 
554
554
  /**
555
555
  * Find the line number where a symbol is defined (strict patterns only)
556
- * Returns null if only loose matches found prevents healing to wrong locations
556
+ * Returns null if only loose matches found, prevents healing to wrong locations
557
557
  */
558
558
  function findSymbolLine(fileContent, symbol) {
559
559
  const lines = fileContent.split('\n');
560
560
  const esc = escapeRegExp(symbol);
561
561
 
562
- // Strict definition patterns only no loose fallback
562
+ // Strict definition patterns only, no loose fallback
563
563
  const patterns = [
564
564
  new RegExp(`^\\s*(async\\s+)?function\\s+${esc}\\s*\\(`), // function name(
565
565
  new RegExp(`^\\s*(const|let|var)\\s+${esc}\\s*=`), // const/let/var name =
@@ -652,7 +652,7 @@ function checkPageStaleness(cwd, filePath) {
652
652
 
653
653
  // Check each source's mtime against last_compiled.
654
654
  // Source entries can include a line range and/or a parenthesized annotation,
655
- // e.g. "bin/atris.js:199-340 (showHelp function)" strip both before stat.
655
+ // e.g. "bin/atris.js:199-340 (showHelp function)". Strip both before stat.
656
656
  for (const source of sources) {
657
657
  const filePart = source
658
658
  .replace(/\s*\([^)]*\)\s*$/, '') // drop trailing "(annotation)"
@@ -670,7 +670,7 @@ function checkPageStaleness(cwd, filePath) {
670
670
  };
671
671
  }
672
672
  } catch {
673
- // Source file doesn't exist that's also a staleness signal
673
+ // Source file doesn't exist, that's also a staleness signal
674
674
  return {
675
675
  page: filePath,
676
676
  staleSource: source + ' (missing)',