atris 3.57.4 → 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 (174) 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 +55 -12
  6. package/atris/skills/x-search/SKILL.md +8 -6
  7. package/atris/skills/youtube/SKILL.md +37 -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 +153 -23
  16. package/bin/atris.js +78 -30
  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 +3 -3
  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 +90 -82
  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 +1 -1
  99. package/commands/write.js +9 -9
  100. package/commands/x-search.js +89 -17
  101. package/commands/xp.js +6 -3
  102. package/commands/youtube.js +601 -48
  103. package/lib/accept-verify-gate.js +3 -3
  104. package/lib/activity-stream.js +1 -1
  105. package/lib/auto-accept-certified.js +28 -12
  106. package/lib/autoland.js +6 -6
  107. package/lib/bench/engines.js +59 -8
  108. package/lib/bench/report.js +1 -1
  109. package/lib/bench/runner.js +204 -5
  110. package/lib/bench/tree-render.js +76 -0
  111. package/lib/card.js +1 -1
  112. package/lib/chat-log-scan.js +1 -1
  113. package/lib/claude-boot-block.js +2 -0
  114. package/lib/deck-compose.js +1 -1
  115. package/lib/deck-history.js +2 -2
  116. package/lib/deck-layout.js +1 -1
  117. package/lib/deck-review.js +5 -5
  118. package/lib/deck-schema.js +1 -1
  119. package/lib/default-verifier.js +1 -1
  120. package/lib/developer-api.js +3 -0
  121. package/lib/engine-ask.js +45 -7
  122. package/lib/engine-registry.js +15 -2
  123. package/lib/file-ops.js +1 -1
  124. package/lib/fleet.js +71 -35
  125. package/lib/html-render.js +1 -1
  126. package/lib/intents.js +172 -0
  127. package/lib/journal.js +1 -1
  128. package/lib/known-commands.js +2 -2
  129. package/lib/land-green.js +364 -0
  130. package/lib/lesson-contradiction.js +2 -2
  131. package/lib/lesson-preflight.js +1 -1
  132. package/lib/manifest.js +5 -5
  133. package/lib/member-alive.js +9 -5
  134. package/lib/memory-view.js +3 -3
  135. package/lib/mission-protected-lane.js +1 -1
  136. package/lib/mission-root.js +2 -2
  137. package/lib/orb-context.js +10 -10
  138. package/lib/pack-capabilities.js +861 -3
  139. package/lib/permission-grants.js +2 -2
  140. package/lib/policy-lessons.js +6 -6
  141. package/lib/pulse.js +8 -8
  142. package/lib/receipt-evidence.js +1 -1
  143. package/lib/reel.js +1 -1
  144. package/lib/review-integrity.js +1 -1
  145. package/lib/runner-command.js +35 -5
  146. package/lib/scorecard.js +4 -4
  147. package/lib/security-scan.js +5 -5
  148. package/lib/self-drive.js +1 -1
  149. package/lib/site-publish.js +372 -0
  150. package/lib/site.js +1 -1
  151. package/lib/slides-deck.js +2 -2
  152. package/lib/state-detection.js +2 -2
  153. package/lib/sync-telemetry.js +2 -2
  154. package/lib/task-db.js +17 -10
  155. package/lib/task-explanation.js +1 -1
  156. package/lib/task-proof.js +2 -2
  157. package/lib/task-receipt.js +3 -3
  158. package/lib/theme.js +12 -12
  159. package/lib/todo-fallback.js +3 -3
  160. package/lib/todo-sections.js +1 -1
  161. package/lib/tree-hash.js +79 -0
  162. package/lib/usage.js +1 -1
  163. package/lib/wish-delegate.js +1 -1
  164. package/lib/workspace-safety.js +4 -4
  165. package/lib/workspace-scaffold.js +3 -3
  166. package/package.json +1 -1
  167. package/scripts/det/concierge-bakeoff.sh +26 -0
  168. package/scripts/det/ytquote-repair.js +0 -1
  169. package/scripts/det/ytrail-race.js +0 -5
  170. package/scripts/member-operate.mjs +52 -14
  171. package/scripts/outbound-artifact-gate.js +0 -3
  172. package/utils/auth.js +0 -3
  173. package/utils/config.js +0 -1
  174. package/scripts/det/checklist-score.js +0 -191
package/commands/skill.js CHANGED
@@ -33,7 +33,7 @@ function parseFrontmatter(content) {
33
33
  currentKey = kvMatch[1];
34
34
  const val = kvMatch[2].trim();
35
35
  if (val === '') {
36
- // Could be start of a list or nested object — leave empty, next lines fill it
36
+ // Could be start of a list or nested object, leave empty, next lines fill it
37
37
  result[currentKey] = {};
38
38
  } else if (val.startsWith('[') && val.endsWith(']')) {
39
39
  // Inline array: [a, b, "c d"]
@@ -317,7 +317,7 @@ function autoFix(skill, dryRun) {
317
317
 
318
318
  let newFmLines = rawFm.split('\n');
319
319
 
320
- // Fix 1: name mismatch — replace name value with folder name
320
+ // Fix 1: name mismatch, replace name value with folder name
321
321
  if (fm.name && fm.name !== skill.leafFolder) {
322
322
  const idx = newFmLines.findIndex(l => l.match(/^name:\s/));
323
323
  if (idx !== -1) {
@@ -718,7 +718,7 @@ function skillCreate(nameArg, ...flags) {
718
718
  const descFlag = flags.find(f => f.startsWith('--description='));
719
719
  const description = descFlag ? descFlag.split('=').slice(1).join('=').replace(/^["']|["']$/g, '') : '';
720
720
 
721
- // Parse name — supports "customer/skill-name" format
721
+ // Parse name, supports "customer/skill-name" format
722
722
  let skillDir, skillName, customerName;
723
723
  if (nameArg.includes('/')) {
724
724
  const parts = nameArg.split('/');
@@ -798,7 +798,7 @@ function skillCreate(nameArg, ...flags) {
798
798
  }
799
799
  }
800
800
  if (linked.length > 0) {
801
- console.log(`✓ Linked to ${linked.join(', ')} (system-level — all tools)`);
801
+ console.log(`✓ Linked to ${linked.join(', ')} (system-level, all tools)`);
802
802
  }
803
803
  }
804
804
 
@@ -923,7 +923,7 @@ function skillDelete(name) {
923
923
  }
924
924
  }
925
925
 
926
- // Remove symlinks — use unlinkSync for symlinks, rmSync for directories
926
+ // Remove symlinks, use unlinkSync for symlinks, rmSync for directories
927
927
  function removeLink(linkPath, label) {
928
928
  try {
929
929
  const stat = fs.lstatSync(linkPath);
@@ -939,7 +939,7 @@ function skillDelete(name) {
939
939
  // Project-level
940
940
  removeLink(path.join(process.cwd(), '.claude', 'skills', name), `.claude/skills/${name}`);
941
941
 
942
- // System-level — all tool directories
942
+ // System-level, all tool directories
943
943
  const toolDirs = [
944
944
  { dir: path.join(home, '.claude', 'skills', name), label: '~/.claude' },
945
945
  { dir: path.join(home, '.codex', 'skills', name), label: '~/.codex' },
package/commands/slop.js CHANGED
@@ -1,12 +1,12 @@
1
- // atris slop — deterministic frontend-slop detector (no LLM).
1
+ // atris slop, deterministic frontend-slop detector (no LLM).
2
2
  //
3
3
  // Steal-from-Impeccable, the Atris way: makes "looks AI-generated" concrete and
4
- // CHECKABLE. A failure is a fact (file:line + rule), not a taste opinion — so it
4
+ // CHECKABLE. A failure is a fact (file:line + rule), not a taste opinion, so it
5
5
  // drops straight into the autopilot/review verification gate and CI. Each finding
6
6
  // is the seed of a typed lesson; the ruleset is meant to GROW from lessons.md
7
7
  // rather than be hand-curated forever.
8
8
  //
9
- // Zero external deps (Node built-ins only) — repo contract.
9
+ // Zero external deps (Node built-ins only), repo contract.
10
10
  //
11
11
  // Usage:
12
12
  // atris slop detect [path...] # scan one or more files or dirs (default: .)
@@ -25,7 +25,7 @@ const SCAN_EXTS = new Set(['.css', '.scss', '.sass', '.less', '.tsx', '.jsx', '.
25
25
  const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.astro', 'coverage', '.cache', 'out', 'vendor']);
26
26
 
27
27
  // Each rule is deterministic: a regex + a one-line why. severity drives the icon.
28
- // Kept high-precision on purpose — a noisy gate gets muted, and a muted gate is dead.
28
+ // Kept high-precision on purpose, a noisy gate gets muted, and a muted gate is dead.
29
29
  const RULES = [
30
30
  { id: 'ai-gradient-text', sev: 'error',
31
31
  re: /(text-transparent[^"'`]{0,40}bg-clip-text|bg-clip-text[^"'`]{0,40}text-transparent|-webkit-text-fill-color:\s*transparent|background-clip:\s*text)/i,
@@ -79,8 +79,8 @@ const RULES = [
79
79
  re: /[✨\u{1F680}\u{1F4A1}\u{1F525}\u{1F389}⚡\u{1F31F}\u{1FA84}\u{1F4AB}\u{1F44B}]/u,
80
80
  why: 'decorative emoji in UI copy' },
81
81
  { id: 'em-dash', sev: 'warn',
82
- re: /—/,
83
- fix: (s) => s.replace(/\s*—\s*/g, ', '), // safe deterministic repair (prose)
82
+ re: /\u2014/,
83
+ fix: (s) => s.replace(/\s*\u2014\s*/g, ', '), // safe deterministic repair (prose)
84
84
  why: 'em dash: a top AI-writing tell; use a comma, colon, or period' },
85
85
  { id: 'hype-copy', sev: 'error',
86
86
  re: /\b(boost your productivity|supercharge|unleash|game[- ]?chang(?:er|ing)|seamlessly|effortlessly|revolutioniz(?:e|ing)|take your .{1,30} to the next level|elevate your|cutting[- ]edge|powered by ai|next[- ]generation)\b/i,
@@ -321,7 +321,7 @@ function detect(argv) {
321
321
  const rel = (f) => path.relative(process.cwd(), f);
322
322
  if (!quiet) {
323
323
  if (!findings.length) {
324
- console.log(`\n ✓ clean — no slop tells in ${files.length} file${files.length === 1 ? '' : 's'}`);
324
+ console.log(`\n ✓ clean, no slop tells in ${files.length} file${files.length === 1 ? '' : 's'}`);
325
325
  } else {
326
326
  console.log('');
327
327
  const w = Math.max(...findings.map((f) => `${rel(f.file)}:${f.line}`.length));
@@ -440,8 +440,8 @@ function findDeadCode(root = process.cwd(), opts = {}) {
440
440
 
441
441
  // 3) string-mention safety net: basename (sans ext) named as a string literal
442
442
  // outside itself. Split by scope: a mention in a NON-test file (prod code, a
443
- // bin entry like `ax`) means the file is dynamically dispatched — e.g.
444
- // require(path.join(__dirname, 'lib', 'permission-grants.js')) — and is
443
+ // bin entry like `ax`) means the file is dynamically dispatched, e.g.
444
+ // require(path.join(__dirname, 'lib', 'permission-grants.js')), and is
445
445
  // genuinely reachable even though the static edge parser and the test BFS
446
446
  // can't see the edge. That must outrank the test-only classification, or a
447
447
  // live feature imported by both `ax` and a test reads as "feature gone".
@@ -462,9 +462,9 @@ function findDeadCode(root = process.cwd(), opts = {}) {
462
462
  const dead = [], testOnly = [];
463
463
  for (const c of candidates) {
464
464
  if (reached.has(c)) continue;
465
- if (mentionedInProd(c)) continue; // dynamically dispatched from prod (e.g. the `ax` bin) — reachable
465
+ if (mentionedInProd(c)) continue; // dynamically dispatched from prod (e.g. the `ax` bin), reachable
466
466
  if (testReached.has(c)) { testOnly.push(c); continue; }
467
- if (mentioned(c)) continue; // docs-driven / other dynamic dispatch — not provably dead
467
+ if (mentioned(c)) continue; // docs-driven / other dynamic dispatch, not provably dead
468
468
  dead.push(c);
469
469
  }
470
470
  return { root, candidates: candidates.length, dead: dead.sort(), testOnly: testOnly.sort() };
@@ -476,7 +476,7 @@ function findDeadCode(root = process.cwd(), opts = {}) {
476
476
  function exportedNames(file) {
477
477
  let text; try { text = fs.readFileSync(file, 'utf8'); } catch { return []; }
478
478
  const names = new Set();
479
- // module.exports = { a, b: c, ... } — shorthand entries and keys
479
+ // module.exports = { a, b: c, ... }, shorthand entries and keys
480
480
  const block = text.match(/module\.exports\s*=\s*\{([\s\S]*?)\n\}/);
481
481
  if (block) {
482
482
  for (const line of block[1].split('\n')) {
@@ -543,13 +543,13 @@ function deadCommand(argv) {
543
543
  return bad ? 1 : 0;
544
544
  }
545
545
  if (!bad && !res.testOnly.length) {
546
- console.log(`\n ✓ no dead code — all ${res.candidates} files reachable from the entrypoints\n`);
546
+ console.log(`\n ✓ no dead code, all ${res.candidates} files reachable from the entrypoints\n`);
547
547
  return 0;
548
548
  }
549
549
  console.log('');
550
- for (const f of res.dead) console.log(` ✗ ${rel(f).padEnd(40)} unreachable and unreferenced — delete it`);
551
- for (const f of res.testOnly) console.log(` ⚠ ${rel(f).padEnd(40)} only tests import it — feature gone, test lingers?`);
552
- if (orphans) for (const o of orphans) console.log(` ⚠ ${`${rel(o.file)} → ${o.name}`.padEnd(40)} exported but nothing anywhere names it — drop the export entry`);
550
+ for (const f of res.dead) console.log(` ✗ ${rel(f).padEnd(40)} unreachable and unreferenced, delete it`);
551
+ for (const f of res.testOnly) console.log(` ⚠ ${rel(f).padEnd(40)} only tests import it, feature gone, test lingers?`);
552
+ if (orphans) for (const o of orphans) console.log(` ⚠ ${`${rel(o.file)} → ${o.name}`.padEnd(40)} exported but nothing anywhere names it, drop the export entry`);
553
553
  const parts = [`${res.dead.length} dead`, `${res.testOnly.length} test-only`];
554
554
  if (orphans) parts.push(`${orphans.length} orphaned export${orphans.length === 1 ? '' : 's'}`);
555
555
  console.log(`\n ${parts.join(', ')} of ${res.candidates} files · exit ${bad ? 1 : 0}\n`);
@@ -588,7 +588,7 @@ function slopCommand(argv) {
588
588
  return 0;
589
589
  }
590
590
  const project = loadProjectRules();
591
- console.log('\n atris slop — deterministic rules:\n');
591
+ console.log('\n atris slop, deterministic rules:\n');
592
592
  for (const r of RULES) console.log(` ${ICON[r.sev]} ${r.id.padEnd(20)} ${r.why}`);
593
593
  for (const r of project) console.log(` ${ICON[r.sev]} ${r.id.padEnd(20)} ${r.why} (project)`);
594
594
  console.log(`\n ${RULES.length} built-in${project.length ? ` + ${project.length} project` : ''} rule${RULES.length + project.length === 1 ? '' : 's'}\n`);
@@ -596,7 +596,7 @@ function slopCommand(argv) {
596
596
  }
597
597
  // help
598
598
  console.log(`
599
- atris slop — deterministic slop detector + repairer (no LLM)
599
+ atris slop, deterministic slop detector + repairer (no LLM)
600
600
 
601
601
  atris slop detect [path...] scan one or more files or dirs (default: .)
602
602
  atris slop detect --diff scan only changed lines (commit/PR gate)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Social commands for Atris CLI — find people, add friends, send messages.
2
+ * Social commands for Atris CLI, find people, add friends, send messages.
3
3
  *
4
4
  * Usage:
5
5
  * atris social My social overview (stats, unread, suggestions)
@@ -413,7 +413,7 @@ async function join(code, rest = []) {
413
413
  console.log('');
414
414
  if (!inv.can_claim) fail('This invite has no claims left.');
415
415
 
416
- // No account yet? Create one right here — signup is browser-free
416
+ // No account yet? Create one right here, signup is browser-free
417
417
  // (commands/signup.js), so a headless terminal can claim in one run.
418
418
  const creds = loadCredentials();
419
419
  if (!creds || !creds.token) {
@@ -488,7 +488,7 @@ async function join(code, rest = []) {
488
488
  }
489
489
 
490
490
  function printHelp() {
491
- console.log(`atris social — people, friends, and messages on Atris
491
+ console.log(`atris social, people, friends, and messages on Atris
492
492
 
493
493
  atris social Your overview
494
494
  atris people <query> Find people
package/commands/soul.js CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Atris Soul — The persona as a living artifact
2
+ * Atris Soul, The persona as a living artifact
3
3
  *
4
4
  * Every atris project has a soul: persona + policies + learnings + context.
5
5
  * This command lets you see it, evolve it, fork it.
6
6
  *
7
- * atris soul — Show your project's soul state
8
- * atris soul snapshot — Export full soul to JSON
9
- * atris soul distill — Compress learnings into persona
10
- * atris soul fork <target> — Copy soul to another project
7
+ * atris soul , Show your project's soul state
8
+ * atris soul snapshot , Export full soul to JSON
9
+ * atris soul distill , Compress learnings into persona
10
+ * atris soul fork <target>, Copy soul to another project
11
11
  */
12
12
 
13
13
  const fs = require('fs');
@@ -59,7 +59,7 @@ function snapshotSoul(atrisDir) {
59
59
  soul.identity.team = members;
60
60
  }
61
61
 
62
- // Knowledge — features
62
+ // Knowledge, features
63
63
  const featuresDir = path.join(atrisDir, 'features');
64
64
  if (fs.existsSync(featuresDir)) {
65
65
  const features = fs.readdirSync(featuresDir).filter(f => {
@@ -69,31 +69,31 @@ function snapshotSoul(atrisDir) {
69
69
  soul.knowledge.feature_count = features.length;
70
70
  }
71
71
 
72
- // Knowledge — research
72
+ // Knowledge, research
73
73
  const researchDir = path.join(atrisDir, 'research');
74
74
  if (fs.existsSync(researchDir)) {
75
75
  soul.knowledge.research_files = countFiles(researchDir);
76
76
  }
77
77
 
78
- // Knowledge — refs
78
+ // Knowledge, refs
79
79
  const refsDir = path.join(atrisDir, 'refs');
80
80
  if (fs.existsSync(refsDir)) {
81
81
  soul.knowledge.refs = fs.readdirSync(refsDir).filter(f => f.endsWith('.md'));
82
82
  }
83
83
 
84
- // Learned — policies
84
+ // Learned, policies
85
85
  const policiesDir = path.join(atrisDir, 'policies');
86
86
  if (fs.existsSync(policiesDir)) {
87
87
  soul.learned.policies = fs.readdirSync(policiesDir).filter(f => f.endsWith('.md'));
88
88
  }
89
89
 
90
- // Learned — logs (journal depth)
90
+ // Learned, logs (journal depth)
91
91
  const logsDir = path.join(atrisDir, 'logs');
92
92
  if (fs.existsSync(logsDir)) {
93
93
  soul.learned.journal_entries = countFiles(logsDir);
94
94
  }
95
95
 
96
- // Learned — lessons
96
+ // Learned, lessons
97
97
  const lessons = readFile(path.join(atrisDir, 'lessons.md'));
98
98
  if (lessons) {
99
99
  const lessonCount = (lessons.match(/^-/gm) || []).length;
@@ -298,7 +298,7 @@ function distillSoul(atrisDir) {
298
298
 
299
299
  function showSoulHelp() {
300
300
  console.log('');
301
- console.log(' atris soul — see what your project has learned');
301
+ console.log(' atris soul, see what your project has learned');
302
302
  console.log('');
303
303
  console.log(' soul show identity, knowledge, learnings');
304
304
  console.log(' soul snapshot export full soul to JSON (auto-gitignored)');
@@ -338,11 +338,11 @@ async function soul(args = []) {
338
338
  try {
339
339
  const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
340
340
  if (!existing.includes('soul-snapshot.json')) {
341
- fs.appendFileSync(gitignorePath, '\n# Atris soul — private, never commit\natris/soul-snapshot.json\n');
341
+ fs.appendFileSync(gitignorePath, '\n# Atris soul, private, never commit\natris/soul-snapshot.json\n');
342
342
  }
343
343
  } catch {}
344
344
  console.log(`✓ Soul snapshot saved to ${outPath}`);
345
- console.log(` (auto-added to .gitignore — this stays private)`);
345
+ console.log(` (auto-added to .gitignore, this stays private)`);
346
346
  break;
347
347
  }
348
348
  case 'fork': {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * atris spaceship — bounded, self-reporting overnight runner.
4
+ * atris spaceship, bounded, self-reporting overnight runner.
5
5
  *
6
6
  * Thin wrapper over scripts/spaceship.sh. The script is the implementation
7
7
  * (a supervised loop that survives bad ticks and emails Keshav on every
@@ -234,7 +234,7 @@ function statusAtris(isQuick = false, jsonMode = false, verbose = false) {
234
234
  const teamActivity = getTeamActivity(targetDir);
235
235
  const checkoutWarning = checkoutBehindMessage(process.cwd());
236
236
 
237
- // JSON mode — structured output for scripting
237
+ // JSON mode: structured output for scripting
238
238
  if (jsonMode) {
239
239
  const output = {
240
240
  date: dateFormatted,
@@ -324,7 +324,7 @@ function statusAtris(isQuick = false, jsonMode = false, verbose = false) {
324
324
 
325
325
  o('');
326
326
  o(`┌─${'─'.repeat(W)}─┐`);
327
- o(`│ ${pad(`TASK BOARD — ${dateFormatted}`)} │`);
327
+ o(`│ ${pad(`TASK BOARD, ${dateFormatted}`)} │`);
328
328
  o(`├─${'─'.repeat(W)}─┤`);
329
329
 
330
330
  // Backlog
@@ -1,4 +1,4 @@
1
- // atris strings — a content design system built from live codebase content (no LLM).
1
+ // atris strings, a content design system built from live codebase content (no LLM).
2
2
  //
3
3
  // The missing pillar next to `atris slop`: slop catches HOW copy is written (tells,
4
4
  // hype, em-dashes); strings governs WHAT words you ship. It scans the repo for
@@ -7,7 +7,7 @@
7
7
  // enforces preferred terms at the commit/PR gate so you rename "live" -> "active"
8
8
  // once and it holds everywhere.
9
9
  //
10
- // Zero external deps (Node built-ins only) — repo contract. Deterministic: a finding
10
+ // Zero external deps (Node built-ins only), repo contract. Deterministic: a finding
11
11
  // is a fact (file:line + term), not a taste opinion, so it drops into CI + the gate.
12
12
  //
13
13
  // Usage:
@@ -21,7 +21,7 @@
21
21
 
22
22
  const fs = require('fs');
23
23
  const path = require('path');
24
- const { gitChangedLines } = require('./slop'); // reuse the diff parser — DRY
24
+ const { gitChangedLines } = require('./slop'); // reuse the diff parser, DRY
25
25
  const escapeRegExp = require('../lib/escape-regexp');
26
26
 
27
27
  const CODE_EXTS = new Set(['.tsx', '.jsx', '.ts', '.js', '.mjs', '.vue', '.svelte', '.astro', '.html']);
@@ -160,7 +160,7 @@ function scan(argv) {
160
160
  }
161
161
  console.log(`\n scanned ${files.length} file${files.length === 1 ? '' : 's'} -> ${reg.strings.length} unique string${reg.strings.length === 1 ? '' : 's'}`);
162
162
  console.log(` registry: ${path.relative(process.cwd(), file)}`);
163
- if (clusters.length) console.log(`\n ⚠ ${clusters.length} variant cluster${clusters.length === 1 ? '' : 's'} (same string, different casing/punctuation) — run: atris strings variants`);
163
+ if (clusters.length) console.log(`\n ⚠ ${clusters.length} variant cluster${clusters.length === 1 ? '' : 's'} (same string, different casing/punctuation), run: atris strings variants`);
164
164
  else console.log(`\n ✓ no inconsistent variants`);
165
165
  console.log('');
166
166
  return 0;
@@ -169,11 +169,11 @@ function scan(argv) {
169
169
  function variants(argv) {
170
170
  const json = argv.includes('--json');
171
171
  const reg = loadRegistry();
172
- if (!reg.strings.length) { console.error(' no registry yet — run: atris strings scan'); return 2; }
172
+ if (!reg.strings.length) { console.error(' no registry yet, run: atris strings scan'); return 2; }
173
173
  const clusters = variantClusters(reg.strings);
174
174
  if (json) { console.log(JSON.stringify({ ok: clusters.length === 0, clusters }, null, 2)); return clusters.length ? 1 : 0; }
175
- if (!clusters.length) { console.log(`\n ✓ clean — every string is written one way\n`); return 0; }
176
- console.log(`\n ${clusters.length} variant cluster${clusters.length === 1 ? '' : 's'} — same string, inconsistent surface form:\n`);
175
+ if (!clusters.length) { console.log(`\n ✓ clean, every string is written one way\n`); return 0; }
176
+ console.log(`\n ${clusters.length} variant cluster${clusters.length === 1 ? '' : 's'}, same string, inconsistent surface form:\n`);
177
177
  for (const c of clusters) {
178
178
  console.log(` ⚠ ${c.surfaces.map((s) => JSON.stringify(s)).join(' vs ')} (${c.count}×)`);
179
179
  }
@@ -188,7 +188,7 @@ function term(argv) {
188
188
  const why = get('--why') || '';
189
189
  if (argv.includes('--list') || (!ban && !argv.includes('--remove'))) {
190
190
  const reg = loadRegistry();
191
- if (!reg.terms.length) { console.log('\n no terms yet — add one: atris strings term --ban "live" --prefer "active"\n'); return 0; }
191
+ if (!reg.terms.length) { console.log('\n no terms yet, add one: atris strings term --ban "live" --prefer "active"\n'); return 0; }
192
192
  console.log('\n preferred terms (enforced by: atris strings check):\n');
193
193
  for (const t of reg.terms) console.log(` ✗ "${t.ban}" → "${t.prefer}"${t.why ? ` (${t.why})` : ''}`);
194
194
  console.log('');
@@ -219,7 +219,7 @@ function check(argv) {
219
219
  const reg = loadRegistry();
220
220
  if (!reg.terms.length) {
221
221
  if (json) { console.log(JSON.stringify({ ok: true, terms: 0, findings: [] }, null, 2)); }
222
- else if (!quiet) console.log('\n no terms to enforce — add one: atris strings term --ban "live" --prefer "active"\n');
222
+ else if (!quiet) console.log('\n no terms to enforce, add one: atris strings term --ban "live" --prefer "active"\n');
223
223
  return 0;
224
224
  }
225
225
 
@@ -248,7 +248,7 @@ function check(argv) {
248
248
  }
249
249
 
250
250
  if (json) { console.log(JSON.stringify({ ok: findings.length === 0, scanned: files.length, terms: reg.terms.length, findings }, null, 2)); return findings.length ? 1 : 0; }
251
- if (!findings.length) { if (!quiet) console.log(`\n ✓ clean — no banned terms in ${files.length} file${files.length === 1 ? '' : 's'}\n`); else console.log(' ✓ clean · exit 0'); return 0; }
251
+ if (!findings.length) { if (!quiet) console.log(`\n ✓ clean, no banned terms in ${files.length} file${files.length === 1 ? '' : 's'}\n`); else console.log(' ✓ clean · exit 0'); return 0; }
252
252
  if (!quiet) {
253
253
  console.log('');
254
254
  const w = Math.max(...findings.map((f) => `${f.file}:${f.line}`.length));
@@ -262,7 +262,7 @@ function list(argv) {
262
262
  const json = argv.includes('--json');
263
263
  const reg = loadRegistry();
264
264
  if (json) { console.log(JSON.stringify(reg, null, 2)); return 0; }
265
- if (!reg.strings.length) { console.error(' no registry yet — run: atris strings scan'); return 2; }
265
+ if (!reg.strings.length) { console.error(' no registry yet, run: atris strings scan'); return 2; }
266
266
  const top = Number((argv[argv.indexOf('--top') + 1]) || 30);
267
267
  console.log(`\n ${reg.strings.length} strings (scanned ${reg.scannedAt || '?'}), top ${Math.min(top, reg.strings.length)} by use:\n`);
268
268
  for (const s of reg.strings.slice(0, top)) console.log(` ${String(s.count).padStart(3)}× ${JSON.stringify(s.text)}`);
@@ -279,7 +279,7 @@ function stringsCommand(argv) {
279
279
  if (sub === 'check' || sub === 'gate') return check(rest);
280
280
  if (sub === 'list' || sub === 'ls') return list(rest);
281
281
  console.log(`
282
- atris strings — a content design system from your live codebase (no LLM)
282
+ atris strings, a content design system from your live codebase (no LLM)
283
283
 
284
284
  atris strings scan [path] extract user-facing strings -> .atris/strings.json
285
285
  atris strings variants the same string written N different ways (pick one)
@@ -289,7 +289,7 @@ function stringsCommand(argv) {
289
289
  atris strings list [--top N] the registry, most-used first
290
290
  add --json to scan/variants/check/list for machine output
291
291
 
292
- Pairs with 'atris slop' (how copy reads) — strings governs what words you ship.
292
+ Pairs with 'atris slop' (how copy reads), strings governs what words you ship.
293
293
  The registry lives in .atris/strings.json. Wire 'check --staged' into the pre-commit gate.
294
294
  `);
295
295
  return 0;
package/commands/study.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * atris study — on-demand learning feed + tutor-loop routing + waiting-room cards.
4
+ * atris study, on-demand learning feed + tutor-loop routing + waiting-room cards.
5
5
  *
6
6
  * atris study <topic…> [--personal]
7
7
  * atris study --while "<cmd>" [topic…] [--personal]
@@ -33,7 +33,7 @@ const LANGUAGE_TOPICS = new Set([
33
33
  ]);
34
34
 
35
35
  function showHelp() {
36
- console.log(`\n atris study — learn a topic on demand (learning feed + tutor loop)\n
36
+ console.log(`\n atris study, learn a topic on demand (learning feed + tutor loop)\n
37
37
  Usage:
38
38
  atris study <topic…> [--personal]
39
39
  atris study --while "<cmd>" [topic…] [--personal]
@@ -48,7 +48,7 @@ function showHelp() {
48
48
  Space advances. q stops cards and live-streams build output.
49
49
  Ctrl+C kills the child and replays output so far (exit 130).
50
50
  Non-TTY stdin = plain passthrough (no cards). If the feed
51
- server is down, the command still runs — cards are skipped.
51
+ server is down, the command still runs, cards are skipped.
52
52
 
53
53
  --personal also scan workspace briefs for personal-edge cards
54
54
 
@@ -146,7 +146,7 @@ function printTutorLoopBlock(backendRoot, language) {
146
146
  console.log(' venv/bin/python experiments/tutor-loop/tutor.py scan');
147
147
 
148
148
  if (language !== 'spanish') {
149
- console.log('\n curriculum not yet built — spanish ships today');
149
+ console.log('\n curriculum not yet built, spanish ships today');
150
150
  }
151
151
  console.log('');
152
152
  }
package/commands/sync.js CHANGED
@@ -372,7 +372,7 @@ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
372
372
 
373
373
  // Skills: sync the canonical skill set from atris-cli package into the
374
374
  // customer workspace. Business-starter template ships skill infra (README,
375
- // folders) but skill files live in atris-cli/atris/skills/ — single source
375
+ // folders) but skill files live in atris-cli/atris/skills/, single source
376
376
  // of truth. Any new skill (e.g. AEO) auto-propagates to every customer.
377
377
  const skillsUpdated = syncPackageSkills(targetAtrisDir, { verbose: false, dryRun });
378
378
 
@@ -730,8 +730,8 @@ function syncRecursiveCount(src, dest, label, silent) {
730
730
 
731
731
  /**
732
732
  * Lightweight skill-only sync. Syncs skills from the npm package to:
733
- * 1. Global skill dirs (~/.claude/skills/, ~/.codex/skills/) — always, if they exist
734
- * 2. Project-level (atris/skills/ + .claude/skills/ symlinks) — if in a project
733
+ * 1. Global skill dirs (~/.claude/skills/, ~/.codex/skills/), always, if they exist
734
+ * 2. Project-level (atris/skills/ + .claude/skills/ symlinks), if in a project
735
735
  *
736
736
  * Global = baseline truth. Project = optional override.
737
737
  * Returns number of files updated (0 = already current).
@@ -776,7 +776,7 @@ function syncSkills({ silent = false } = {}) {
776
776
  // contain ONLY business-specific custom skills in atris/skills/.
777
777
  const businessJson = path.join(process.cwd(), '.atris', 'business.json');
778
778
  if (fs.existsSync(businessJson)) {
779
- // We're inside a business workspace — skip project-level skill sync.
779
+ // We're inside a business workspace, skip project-level skill sync.
780
780
  return updated;
781
781
  }
782
782
 
@@ -867,7 +867,7 @@ const SYNC_ALL_FILES = [
867
867
  * No console output, no writes. Returns { projects, plan } for inspection.
868
868
  * Plan entries: { projectRoot, isBusiness, isCustomized, changes }.
869
869
  *
870
- * Exported for tests — the production syncAtrisAll wraps this.
870
+ * Exported for tests, the production syncAtrisAll wraps this.
871
871
  */
872
872
  function buildSyncAllPlan({ root, pkgRoot, filesToSync = SYNC_ALL_FILES } = {}) {
873
873
  const projects = _findAtrisProjects(root);
@@ -935,16 +935,16 @@ function syncAtrisAll({ dryRun = false, force = false, yes = false } = {}) {
935
935
  for (const p of plan) {
936
936
  const rel = path.relative(root, p.projectRoot) || '.';
937
937
  if (p.isBusiness) {
938
- console.log(` ⏭ ${rel} (business workspace — run "atris update" in that dir)`);
938
+ console.log(` ⏭ ${rel} (business workspace, run "atris update" in that dir)`);
939
939
  skipped++;
940
940
  } else if (p.isCustomized) {
941
- console.log(` ⏭ ${rel} (customized atris.md — review manually)`);
941
+ console.log(` ⏭ ${rel} (customized atris.md, review manually)`);
942
942
  skipped++;
943
943
  } else if (p.changes.length === 0) {
944
944
  console.log(` · ${rel} (up to date)`);
945
945
  unchanged++;
946
946
  } else {
947
- console.log(` → ${rel} — ${p.changes.length} ${p.changes.length === 1 ? 'file' : 'files'}: ${p.changes.join(', ')}`);
947
+ console.log(` → ${rel}: ${p.changes.length} ${p.changes.length === 1 ? 'file' : 'files'}: ${p.changes.join(', ')}`);
948
948
  wouldUpdate++;
949
949
  }
950
950
  }