atris 3.57.4 → 3.58.6

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 +55 -12
  6. package/atris/skills/x-search/SKILL.md +8 -6
  7. package/atris/skills/youtube/SKILL.md +66 -21
  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 +59 -31
  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 +216 -50
  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 +101 -48
  98. package/commands/worktree.js +1 -1
  99. package/commands/write.js +9 -9
  100. package/commands/x-search.js +93 -22
  101. package/commands/xp.js +6 -3
  102. package/commands/youtube.js +1019 -123
  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/ytnotes +82 -5
  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 +47 -9
  174. package/utils/config.js +0 -1
  175. package/scripts/det/checklist-score.js +0 -191
package/commands/pack.js CHANGED
@@ -25,6 +25,9 @@ const {
25
25
  finalizePackRunReceipt,
26
26
  receiptDirectory,
27
27
  classifyPackRunLifecycle,
28
+ assessPackRecoveryJournal,
29
+ claimPackRecoveryParent,
30
+ confirmPackRecoveryClaim,
28
31
  } = require('../lib/pack-capabilities');
29
32
 
30
33
  const REGISTRY_TIMEOUT_MS = 60000;
@@ -101,7 +104,7 @@ const PACKET = {
101
104
 
102
105
  // ── definitions, not state ────────────────────────────────────────────────
103
106
  // A packet carries what a thing IS, never what it is currently DOING. The
104
- // definition files travel MEMBER.md, MISSION.md, SKILL.md, SOUL.md,
107
+ // definition files travel: MEMBER.md, MISSION.md, SKILL.md, SOUL.md,
105
108
  // README.md, wiki pages, policies. The running state a live workspace keeps
106
109
  // beside them stays home: a stranger cannot use someone else's standup notes,
107
110
  // and project-management exhaust is most of the file count.
@@ -160,7 +163,7 @@ function showHelp() {
160
163
  console.log(' atris pack seal <dir> [--type <t>] [--entrypoint <file>]');
161
164
  console.log(' atris pack publish [--dir atris] [--slug <slug>] [--author "<name>"] [--notes "..."] [--visibility public|unlisted|private] [--minor|--major] [--out <file.zip>] [--push] [--dry-run] [--allow-secrets]');
162
165
  console.log(' atris pack install <file.zip|url|slug> [--dir <target>] [--force]');
163
- console.log(' atris pack run <slug|dir> [--dir <target>] [--input <file>] [--cloud] [--force] [--trust] [--grant <capability>]');
166
+ console.log(' atris pack run <slug|dir> [--dir <target>] [--input <file>] [--cloud] [--force] [--trust] [--grant <capability>] [--recover <receipt.json>]');
164
167
  console.log(' atris pack runs [--dir <receipt-dir>] [--limit <n>] [--json]');
165
168
  console.log(' atris pack share <slug> --for "<Name>" [--days 30]');
166
169
  console.log(' atris pack share <slug> --list');
@@ -1508,7 +1511,7 @@ function formatBytes(value) {
1508
1511
  }
1509
1512
 
1510
1513
  // What a stranger actually receives, in one glance. The tree below is the
1511
- // detail; this is the shape enough to see that the wiki, the skills, the
1514
+ // detail; this is the shape, enough to see that the wiki, the skills, the
1512
1515
  // members and the policies are all still in the box.
1513
1516
  function printPacketComposition(entries) {
1514
1517
  const byTop = new Map();
@@ -2254,7 +2257,7 @@ async function installPack(rawArgs, cwd = process.cwd(), options = {}) {
2254
2257
 
2255
2258
  // ── pack run ────────────────────────────────────────────────────────────────
2256
2259
  // Install is half a product: it leaves a folder and a suggestion. `pack run`
2257
- // is the other half packet in, running workspace out.
2260
+ // is the other half: packet in, running workspace out.
2258
2261
  //
2259
2262
  // It defaults to LOCAL on purpose. The cloud path is gated: activating a
2260
2263
  // business workspace needs auth, an existing business record, and a paid plan
@@ -2450,7 +2453,7 @@ function printCapabilityTrustCard(policy, trust, receiptPath, userDenyRuleCount
2450
2453
  if (options.operatorInput) {
2451
2454
  console.log(` operator input: ${options.operatorInput.bytes} bytes injected; source path withheld from the pack and receipt`);
2452
2455
  }
2453
- console.log(` host shell: ${policy.grantedCapabilities.includes('host.shell') ? 'GRANTED Bash can reach host files and network' : 'denied'}`);
2456
+ console.log(` host shell: ${policy.grantedCapabilities.includes('host.shell') ? 'GRANTED \u2014 Bash can reach host files and network' : 'denied'}`);
2454
2457
  console.log(' receipt coverage: Atris hook tool events; direct slash-skill invocations and later Claude or policy denials may not appear');
2455
2458
  console.log(` receipt: ${receiptPath}`);
2456
2459
  }
@@ -2530,7 +2533,13 @@ function startPackLocal(packDir, deps = {}, options = {}) {
2530
2533
  ? process.stdin.isTTY !== true
2531
2534
  : deps.nonInteractive === true);
2532
2535
  const start = deps.computerLocal || require('./computer').computerLocal;
2533
- const wrappedOpeningPrompt = packOpeningInstruction(openingPrompt, operatorInput);
2536
+ const recovery = options.recovery || null;
2537
+ let wrappedOpeningPrompt = packOpeningInstruction(openingPrompt, operatorInput);
2538
+ if (recovery && Array.isArray(recovery.protectedFiles) && recovery.protectedFiles.length) {
2539
+ const names = recovery.protectedFiles.map((entry) => entry.target).join(', ');
2540
+ const note = `This is a recovered run. These completed files are protected and must not be rewritten: ${names}. Continue with other files.`;
2541
+ wrappedOpeningPrompt = wrappedOpeningPrompt ? `${wrappedOpeningPrompt}\n\n${note}` : note;
2542
+ }
2534
2543
  const runnerArgs = [];
2535
2544
  let pluginDir = null;
2536
2545
  let receipt = null;
@@ -2556,6 +2565,7 @@ function startPackLocal(packDir, deps = {}, options = {}) {
2556
2565
  packSkillsPluginLoaded: Boolean(pluginDir),
2557
2566
  nonInteractive,
2558
2567
  operatorInput: operatorInput ? { bytes: operatorInput.bytes, sha256: operatorInput.sha256 } : null,
2568
+ recovery,
2559
2569
  });
2560
2570
  runnerArgs.push(...buildClaudeCapabilityArgs(capabilityPolicy, { trust, userDenyRules, nonInteractive }));
2561
2571
  runnerOptions.runnerEnv = {
@@ -2563,11 +2573,13 @@ function startPackLocal(packDir, deps = {}, options = {}) {
2563
2573
  ATRIS_PACK_RECEIPT: receipt.receiptPath,
2564
2574
  ATRIS_PACK_RECEIPT_EVENTS: receipt.eventsPath,
2565
2575
  ATRIS_PACK_GRANTED_CAPABILITIES: JSON.stringify(capabilityPolicy.grantedCapabilities),
2576
+ ATRIS_PACK_PROTECTED_FILES: JSON.stringify(recovery && recovery.protectedFiles ? recovery.protectedFiles : []),
2566
2577
  CLAUDE_CODE_DISABLE_CLAUDE_MDS: '1',
2567
2578
  CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1',
2568
2579
  CLAUDE_CODE_SKIP_PROMPT_HISTORY: '1',
2569
2580
  CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: '1',
2570
2581
  };
2582
+ if (typeof options.onReceipt === 'function') options.onReceipt(receipt);
2571
2583
  runnerOptions.cleanupPaths = pluginDir ? [pluginDir] : [];
2572
2584
  runnerOptions.onRunnerExit = ({ status, signal }) => {
2573
2585
  appendReceiptEvent(receipt.eventsPath, {
@@ -2579,6 +2591,10 @@ function startPackLocal(packDir, deps = {}, options = {}) {
2579
2591
  nonInteractive,
2580
2592
  operatorInput,
2581
2593
  });
2594
+ if (recovery) {
2595
+ const names = (recovery.protectedFiles || []).map((entry) => entry.target).join(', ');
2596
+ console.log(`recovery: continuing from ${recovery.parentRunId} with ${(recovery.protectedFiles || []).length} protected file(s): ${names}.`);
2597
+ }
2582
2598
  } else {
2583
2599
  const skillsDir = path.join(packDir, 'skills');
2584
2600
  const hasShippedSkill = fs.existsSync(skillsDir)
@@ -2693,6 +2709,10 @@ async function runPack(rawArgs, cwd = process.cwd(), options = {}) {
2693
2709
  const force = takeFlag(args, '--force');
2694
2710
  const trust = takeFlag(args, '--trust');
2695
2711
  const grants = takeValues(args, '--grant');
2712
+ const recoverArg = takeValue(args, '--recover');
2713
+ if (recoverArg !== null && (!recoverArg || !String(recoverArg).trim())) {
2714
+ throw new Error('pack run --recover needs a receipt path; explicit empty values never fall through to a fresh run');
2715
+ }
2696
2716
  if (args.length) throw new Error(`unknown pack run argument: ${args.join(' ')}`);
2697
2717
 
2698
2718
  const operatorInput = inputArg ? readPackRunInput(inputArg, cwd) : null;
@@ -2710,6 +2730,7 @@ async function runPack(rawArgs, cwd = process.cwd(), options = {}) {
2710
2730
  packDir = path.resolve(cwd, source);
2711
2731
  manifest = assertPacketDir(packDir, cwd);
2712
2732
  } else {
2733
+ if (recoverArg !== null) throw new Error('pack run --recover needs an existing pack directory, not a slug to install');
2713
2734
  packDir = path.resolve(cwd, targetArg || slugify(source));
2714
2735
  const alreadyThere = !force && fs.existsSync(path.join(packDir, 'pack.json'));
2715
2736
  if (alreadyThere) {
@@ -2735,6 +2756,36 @@ async function runPack(rawArgs, cwd = process.cwd(), options = {}) {
2735
2756
  );
2736
2757
  }
2737
2758
  if (capabilityPolicy.status === 'enforced') assertPackExecutionTree(packDir);
2759
+ let recovery = null;
2760
+ let claimPath = null;
2761
+ let childReceiptPath = null;
2762
+ if (recoverArg !== null) {
2763
+ if (cloud) throw new Error('pack run --recover is local-only; cloud recovery is not supported');
2764
+ if (capabilityPolicy.status !== 'enforced') {
2765
+ throw new Error('pack run --recover needs a declared capability ceiling; legacy packs cannot be recovered');
2766
+ }
2767
+ if (capabilityPolicy.grantedCapabilities.includes('host.shell')) {
2768
+ throw new Error('pack run --recover cannot grant host.shell; shell effects cannot be reasoned about');
2769
+ }
2770
+ const parentReceiptPath = path.resolve(cwd, recoverArg);
2771
+ const childReceiptDir = receiptDirectory({ receiptDir: deps.packRunReceiptDir });
2772
+ const assessment = assessPackRecoveryJournal({
2773
+ packDir,
2774
+ manifest,
2775
+ policy: capabilityPolicy,
2776
+ parentReceiptPath,
2777
+ operatorInput,
2778
+ childReceiptDir,
2779
+ });
2780
+ // Claimed before launch so a parent backs at most one child. A crash
2781
+ // from here on leaves a pending claim that needs manual review.
2782
+ claimPath = claimPackRecoveryParent(parentReceiptPath, assessment.parentRunId);
2783
+ recovery = {
2784
+ parentRunId: assessment.parentRunId,
2785
+ parentReceipt: parentReceiptPath,
2786
+ protectedFiles: assessment.protectedFiles,
2787
+ };
2788
+ }
2738
2789
  if (cloud) return startPackCloud(packDir, displayTarget, deps, { capabilityPolicy });
2739
2790
  if (trust && capabilityPolicy.status === 'legacy') {
2740
2791
  throw new Error(
@@ -2749,19 +2800,34 @@ async function runPack(rawArgs, cwd = process.cwd(), options = {}) {
2749
2800
  }
2750
2801
  console.log(`starting local computer in ${displayTarget}`);
2751
2802
  if (capabilityPolicy.status === 'legacy') {
2752
- console.log('capabilities: LEGACY this pack declares no enforceable capability ceiling.');
2803
+ console.log('capabilities: LEGACY, this pack declares no enforceable capability ceiling.');
2753
2804
  }
2754
2805
  if (!trust && capabilityPolicy.status === 'legacy') {
2755
2806
  console.log('permission prompts are on because this legacy pack has no capability ceiling.');
2756
2807
  console.log('for a bounded read-only run, add --grant pack.read; add --trust only to pre-approve that ceiling.');
2757
2808
  }
2758
- return startPackLocal(packDir, deps, {
2809
+ const code = startPackLocal(packDir, deps, {
2759
2810
  trust,
2760
2811
  openingPrompt,
2761
2812
  operatorInput,
2762
2813
  capabilityPolicy,
2763
2814
  manifest,
2815
+ recovery,
2816
+ onReceipt: (receipt) => {
2817
+ childReceiptPath = receipt.receiptPath;
2818
+ if (claimPath) confirmPackRecoveryClaim(claimPath, receipt.receiptPath);
2819
+ },
2764
2820
  });
2821
+ if (claimPath && childReceiptPath) {
2822
+ try {
2823
+ confirmPackRecoveryClaim(claimPath, childReceiptPath);
2824
+ } catch {
2825
+ // Already confirmed in onReceipt before the runner ran; the real
2826
+ // console exits the process there, so this is only a backstop for
2827
+ // injected runners that return normally.
2828
+ }
2829
+ }
2830
+ return code;
2765
2831
  }
2766
2832
 
2767
2833
  async function updatePack(rawArgs, cwd = process.cwd(), options = {}) {
@@ -44,7 +44,7 @@ function generateManifest(skills, projectDir) {
44
44
  return {
45
45
  name: 'atris-workspace',
46
46
  version: pkg.version || '1.0.0',
47
- description: `Atris workspace skills for Cowork ${skills.length} integrations (email, calendar, slack, drive, notion, and more)`,
47
+ description: `Atris workspace skills for Cowork: ${skills.length} integrations (email, calendar, slack, drive, notion, and more)`,
48
48
  author: {
49
49
  name: typeof pkg.author === 'string' ? pkg.author : (pkg.author?.name || 'Atris')
50
50
  },
@@ -89,7 +89,7 @@ If not logged in, run:
89
89
  atris login
90
90
  \`\`\`
91
91
 
92
- This is interactive the user will need to complete OAuth in their browser and paste the CLI code. Guide them through it.
92
+ This is interactive, the user will need to complete OAuth in their browser and paste the CLI code. Guide them through it.
93
93
 
94
94
  ## Step 3: Check integration status
95
95
 
@@ -149,7 +149,7 @@ Brings Atris CLI skills into the Cowork desktop app.
149
149
 
150
150
  1. Install this plugin in Cowork
151
151
  2. Run \`/atris-setup\` to authenticate and connect integrations
152
- 3. Start using skills they trigger automatically based on your requests
152
+ 3. Start using skills: they trigger automatically based on your requests
153
153
 
154
154
  ## Included Skills (${skills.length})
155
155
 
@@ -279,7 +279,7 @@ function buildPlugin(...args) {
279
279
  console.log('');
280
280
 
281
281
  } finally {
282
- // Clean up temp dir (only if ZIP succeeded we skip cleanup on error above)
282
+ // Clean up temp dir (only if ZIP succeeded, we skip cleanup on error above)
283
283
  if (fs.existsSync(tempDir)) {
284
284
  try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (e) { /* ignore */ }
285
285
  }
@@ -346,7 +346,7 @@ function publishPlugin(...args) {
346
346
  process.exit(1);
347
347
  }
348
348
 
349
- // Find the marketplace repo check sibling dir or --repo flag
349
+ // Find the marketplace repo: check sibling dir or --repo flag
350
350
  let marketplaceDir = null;
351
351
  for (const arg of args) {
352
352
  if (typeof arg === 'string' && arg.startsWith('--repo=')) {
@@ -432,7 +432,7 @@ function publishPlugin(...args) {
432
432
  execSync('git add -A', { cwd: marketplaceDir, stdio: 'pipe' });
433
433
  const status = execSync('git status --porcelain', { cwd: marketplaceDir, encoding: 'utf8' }).trim();
434
434
  if (!status) {
435
- console.log('✓ No changes marketplace already up to date.');
435
+ console.log('✓ No changes, marketplace already up to date.');
436
436
  return;
437
437
  }
438
438
  execSync(`git commit -m "chore: Update atris-workspace plugin to v${version}"`, {
package/commands/probe.js CHANGED
@@ -1,6 +1,6 @@
1
- // atris probe chat-lane probe (TRR-22): one REAL /atris2/turn over the full
1
+ // atris probe: chat-lane probe (TRR-22): one REAL /atris2/turn over the full
2
2
  // tool relay, exactly as the surfaces run it. Port of terrace's
3
- // atris/bin/relay-probe and atris/bin/calendar-probe keep the op tables in
3
+ // atris/bin/relay-probe and atris/bin/calendar-probe, so keep the op tables in
4
4
  // lockstep across iOS (votd/GMModeAPI.swift Atris2ToolRelay), web
5
5
  // (atrisos-web orbToolRelay.ts), Obelisk (atris2LocalFileOp.cjs), and the
6
6
  // terrace probes.
@@ -14,7 +14,7 @@
14
14
  //
15
15
  // PASS = >=1 relayed op (with --calendar: >=1 calendar cli op) AND a non-empty
16
16
  // final answer with no dead-end marker; otherwise prints the marker in the
17
- // receipt line and exits 1 a FAIL naming the dead-end is the instrument
17
+ // receipt line and exits 1. a FAIL naming the dead-end is the instrument
18
18
  // working. The receipt line is journal-ready.
19
19
  //
20
20
  // Usage:
@@ -32,7 +32,7 @@ const { loadCredentials } = require('../utils/auth');
32
32
  const { buildToolResultBody } = require('../lib/tool-result-encode');
33
33
 
34
34
  // G2's honest blocked message (tool_policy_bench.MAX_TURNS_EXHAUSTED_MESSAGE)
35
- // starts with this an answer that is only this marker is a dead-end.
35
+ // starts with this. an answer that is only this marker is a dead-end.
36
36
  const MAX_TURNS_MARKER = 'i ran out of tool budget for this turn';
37
37
 
38
38
  function workspaceLabel(businessId) {
@@ -61,7 +61,7 @@ function normalizeBash(cmd, label) {
61
61
 
62
62
  // No write/edit file ops, and the `..` guard only covers args.path. The
63
63
  // `bash` op still executes whatever command the model sends, verbatim, on
64
- // the remote ai-computer that is the production relay contract and the
64
+ // the remote ai-computer, that is the production relay contract and the
65
65
  // table must stay in lockstep with it, so this probe is NOT read-only.
66
66
  function fileOpCommand(args, label) {
67
67
  const op = String(args.type || '').toLowerCase();
@@ -78,7 +78,7 @@ function fileOpCommand(args, label) {
78
78
  return null;
79
79
  }
80
80
 
81
- // Port of Atris2ToolRelay.atrisCLICommand (GMModeAPI.swift) op → `atris …`.
81
+ // Port of Atris2ToolRelay.atrisCLICommand (GMModeAPI.swift): op → `atris …`.
82
82
  const SIMPLE_CLI_OPS = {
83
83
  status: ['atris.md'],
84
84
  integrations_status: ['integrations'],
@@ -122,7 +122,7 @@ function atrisCliCommand(args) {
122
122
 
123
123
  function atrisCliResult(command, term) {
124
124
  // a terminal response without a numeric exit_code is a broken endpoint,
125
- // not a success never let it masquerade as ok with empty stdout
125
+ // not a success. never let it masquerade as ok with empty stdout
126
126
  const code = typeof term.exit_code === 'number' ? term.exit_code : -1;
127
127
  const out = {
128
128
  schema: 'atris.local_cli_result.v1',
@@ -227,7 +227,7 @@ function parseArgs(argv) {
227
227
 
228
228
  // Core /atris2/turn client: one streamed turn over the full local tool relay.
229
229
  // Shared by `atris probe` (the instrument) and `atris mission run --runner atris2`
230
- // (the worker). Transport-level outcome only callers apply their own
230
+ // (the worker). Transport-level outcome only. callers apply their own
231
231
  // pass/fail policy on top of the returned fields.
232
232
  async function runAtris2Turn(opts = {}) {
233
233
  const {
@@ -454,7 +454,7 @@ async function probeCommand(argv) {
454
454
  const detail = ok
455
455
  ? `answer: ${resultText.slice(0, 80).replace(/\n/g, ' ')}`
456
456
  : `dead-end: ${deadEnd}`;
457
- const line = `- **atris-probe** \`${stamp}\` ${where} · ${a.model}`
457
+ const line = `- **atris-probe** \`${stamp}\` \u2014 ${where} · ${a.model}`
458
458
  + (member ? ` · member=${member}` : '')
459
459
  + (engine ? ` · engine=${engine}` : '')
460
460
  + ` · ${ok ? 'PASS' : 'FAIL'} · ${secs}s · tools=${toolsRun}`
package/commands/pull.js CHANGED
@@ -259,14 +259,14 @@ async function pullBusiness(slug) {
259
259
  // Determine output directory.
260
260
  //
261
261
  // We only reuse the current working directory when we can prove it's the
262
- // correct workspace for THIS business i.e. it has a `.atris/business.json`
262
+ // correct workspace for THIS business, i.e. it has a `.atris/business.json`
263
263
  // whose slug matches `slug`. Any other signal (a stray `atris/` folder, a
264
264
  // business.json for a different business, etc.) is NOT enough: pulling
265
265
  // one business on top of another business workspace would mix two businesses into
266
266
  // one directory and write one manifest over the other (or vice
267
267
  // versa), causing the next sync to do strange things.
268
268
  //
269
- // Fallback: create a fresh ./{slug}/ subdir. Always safe even if cwd is
269
+ // Fallback: create a fresh ./{slug}/ subdir. Always safe, even if cwd is
270
270
  // $HOME or /tmp, we land in a dedicated subfolder.
271
271
  const intoIdx = process.argv.indexOf('--into');
272
272
  let outputDir;
@@ -281,7 +281,7 @@ async function pullBusiness(slug) {
281
281
  const cwdSlug = biz.slug || biz.name;
282
282
  cwdMatchesSlug = cwdSlug === slug;
283
283
  } catch {
284
- // Corrupt business.json ignore, treat as no match.
284
+ // Corrupt business.json, ignore, treat as no match.
285
285
  }
286
286
  }
287
287
  outputDir = cwdMatchesSlug ? process.cwd() : path.join(process.cwd(), slug);
@@ -289,7 +289,7 @@ async function pullBusiness(slug) {
289
289
 
290
290
  // Auto-relocate if the resolved outputDir is dangerous ($HOME, /, /Users,
291
291
  // system dirs, reserved home folders). Non-technical users shouldn't have
292
- // to know about mkdir/cd atris picks a safe subdir and tells them.
292
+ // to know about mkdir/cd, atris picks a safe subdir and tells them.
293
293
  // Paired with the manifest-scoped sweep below, this makes it impossible
294
294
  // for a stray cwd to cause atris to delete user files.
295
295
  ({ dir: outputDir } = resolveSafeOutputDir(outputDir, { slug, op: 'pull into' }));
@@ -298,7 +298,7 @@ async function pullBusiness(slug) {
298
298
  // narrowed bound workspace pulls to atris/, which made staging snapshots and
299
299
  // the real workspace diverge while both reported "synced".
300
300
 
301
- // Resolve business ID always refresh from API to avoid stale workspace_id
301
+ // Resolve business ID, always refresh from API to avoid stale workspace_id
302
302
  let businessId, workspaceId, businessName, resolvedSlug;
303
303
  let localSlug = slug;
304
304
  const businesses = loadBusinesses();
@@ -347,14 +347,14 @@ async function pullBusiness(slug) {
347
347
  process.exit(1);
348
348
  }
349
349
 
350
- // Telemetry helper captures wall-clock time including wake.
350
+ // Telemetry helper, captures wall-clock time including wake.
351
351
  let _coldWake = false;
352
352
  const emit = (outcome, extras = {}) =>
353
353
  emitSyncEvent(creds.token, businessId, workspaceId, 'pull', outcome, elapsedMs(), extras);
354
354
 
355
355
  // Auto-wake the EC2 computer if --auto-wake is set.
356
356
  // Without this, pull silently serves stale data from agent_files cache when
357
- // the computer is asleep the bug that confused us all night.
357
+ // the computer is asleep, the bug that confused us all night.
358
358
  const autoWake = process.argv.includes('--auto-wake');
359
359
  if (autoWake) {
360
360
  const statusResult = await apiRequestJson(`/business/${businessId}/ai-computer/status`, { method: 'GET', token: creds.token });
@@ -415,7 +415,7 @@ async function pullBusiness(slug) {
415
415
  const pathsParam = onlyPrefixes ? `&paths=${encodeURIComponent(onlyPrefixes.map(p => p.replace(/\/$/, '')).join(','))}` : '';
416
416
 
417
417
  if (hasManifest) {
418
- // Phase 1: fetch hashes only (fast no file content transferred)
418
+ // Phase 1: fetch hashes only (fast, no file content transferred)
419
419
  const hashUrl = `/business/${businessId}/workspaces/${workspaceId}/snapshot?include_content=false${pathsParam}`;
420
420
  const hashResult = await apiRequestJson(hashUrl, { method: 'GET', token: creds.token, timeoutMs });
421
421
 
@@ -438,13 +438,13 @@ async function pullBusiness(slug) {
438
438
  if (changedPaths.length === 0) {
439
439
  clearInterval(loading);
440
440
  process.stdout.write(`\r Checked ${Object.keys(remoteHashes).length} files in ${Math.floor((Date.now() - startTime) / 1000)}s.${' '.repeat(10)}\n`);
441
- // Still need full result for diff logic below build it from hash-only data
441
+ // Still need full result for diff logic below, build it from hash-only data
442
442
  result = { ok: true, data: { files: hashResult.data.files } };
443
443
  } else {
444
444
  // Phase 2: fetch ONLY changed files via batch endpoint (not full snapshot)
445
445
  clearInterval(loading);
446
446
  const checkSec = Math.floor((Date.now() - startTime) / 1000);
447
- console.log(`\r Checked in ${checkSec}s ${changedPaths.length} changed, ${Object.keys(remoteHashes).length - changedPaths.length} unchanged.${' '.repeat(10)}`);
447
+ console.log(`\r Checked in ${checkSec}s, ${changedPaths.length} changed, ${Object.keys(remoteHashes).length - changedPaths.length} unchanged.${' '.repeat(10)}`);
448
448
 
449
449
  const startPhase2 = Date.now();
450
450
  const loading2 = setInterval(() => {
@@ -452,7 +452,7 @@ async function pullBusiness(slug) {
452
452
  process.stdout.write(`\r Fetching ${changedPaths.length} changed files... ${spinner[spinIdx++ % 4]} ${elapsed}s`);
453
453
  }, 250);
454
454
 
455
- // Try batch file read first (fast only changed files)
455
+ // Try batch file read first (fast, only changed files)
456
456
  const batchUrl = `/business/${businessId}/workspaces/${workspaceId}/files/batch`;
457
457
  const batchResult = await apiRequestJson(batchUrl, {
458
458
  method: 'POST',
@@ -477,7 +477,7 @@ async function pullBusiness(slug) {
477
477
  process.stdout.write(`\r Fetched in ${fullSec}s.${' '.repeat(20)}\n`);
478
478
  }
479
479
  } else {
480
- // Batch not available fall back to full snapshot
480
+ // Batch not available, fall back to full snapshot
481
481
  process.stdout.write(`\r Batch unavailable, fetching full snapshot...${' '.repeat(10)}\n`);
482
482
  const contentUrl = `/business/${businessId}/workspaces/${workspaceId}/snapshot?include_content=true${pathsParam}`;
483
483
  result = await apiRequestJson(contentUrl, { method: 'GET', token: creds.token, timeoutMs });
@@ -486,14 +486,14 @@ async function pullBusiness(slug) {
486
486
  }
487
487
  }
488
488
  } else {
489
- // Hash-only fetch failed fall back to full snapshot
489
+ // Hash-only fetch failed, fall back to full snapshot
490
490
  const fullUrl = `/business/${businessId}/workspaces/${workspaceId}/snapshot?include_content=true${pathsParam}`;
491
491
  result = await apiRequestJson(fullUrl, { method: 'GET', token: creds.token, timeoutMs });
492
492
  clearInterval(loading);
493
493
  process.stdout.write(`\r Fetched in ${Math.floor((Date.now() - startTime) / 1000)}s.${' '.repeat(20)}\n`);
494
494
  }
495
495
  } else {
496
- // First sync or --force full snapshot with content
496
+ // First sync or --force, full snapshot with content
497
497
  const snapshotUrl = `/business/${businessId}/workspaces/${workspaceId}/snapshot?include_content=true${pathsParam}`;
498
498
  result = await apiRequestJson(snapshotUrl, { method: 'GET', token: creds.token, timeoutMs });
499
499
  clearInterval(loading);
@@ -570,7 +570,7 @@ async function pullBusiness(slug) {
570
570
  // Don't early-return: we still need to update the manifest so paths
571
571
  // that USED to be in the scoped subtree but were deleted on cloud
572
572
  // get evicted from the manifest. Without this, the next push freshness
573
- // check would forever flag those paths as drift and demand a pull
573
+ // check would forever flag those paths as drift and demand a pull,
574
574
  // but the pull would early-return again, creating a deadlock.
575
575
  } else {
576
576
  console.log(` Filtered to ${files.length} files matching: ${onlyPrefixes.join(', ')}`);
@@ -580,7 +580,7 @@ async function pullBusiness(slug) {
580
580
  // Build remote file map {path: {hash, size}} and content map {path: content}.
581
581
  //
582
582
  // CRITICAL: smart-pull (hash-only fetch) returns files with `path`+`hash`+`size`
583
- // but no `content`. Phase-2 batch fetch only adds content for CHANGED files
583
+ // but no `content`. Phase-2 batch fetch only adds content for CHANGED files,
584
584
  // unchanged files stay hash-only. We must include hash-only entries in remoteFiles
585
585
  // so threeWayCompare doesn't see them as missing-from-remote (deletedRemote).
586
586
  // The previous version skipped any file without content, which caused every
@@ -598,16 +598,16 @@ async function pullBusiness(slug) {
598
598
  // empty files masquerade as hash-only entries; they'd then be recorded in
599
599
  // the manifest (with the empty-string hash) but never written to disk.
600
600
  // A subsequent push would compare local (file missing) to manifest (file
601
- // present) and try to delete the file from cloud silently undoing the
601
+ // present) and try to delete the file from cloud, silently undoing the
602
602
  // very thing the user just pulled.
603
603
  const hasContent = file.content !== null && file.content !== undefined && typeof file.content === 'string';
604
604
  if (hasContent) {
605
- // Full content available hash from raw bytes (matches computeLocalHashes)
605
+ // Full content available, hash from raw bytes (matches computeLocalHashes)
606
606
  const rawBytes = Buffer.from(file.content, 'utf-8');
607
607
  remoteFiles[normalizedPath] = { hash: crypto.createHash('sha256').update(rawBytes).digest('hex'), size: rawBytes.length };
608
608
  remoteContent[normalizedPath] = file.content;
609
609
  } else if (file.hash) {
610
- // Hash-only entry from smart pull trust the cloud-reported hash
610
+ // Hash-only entry from smart pull, trust the cloud-reported hash
611
611
  remoteFiles[normalizedPath] = { hash: file.hash, size: file.size || 0 };
612
612
  }
613
613
  }
@@ -636,7 +636,7 @@ async function pullBusiness(slug) {
636
636
  };
637
637
  }
638
638
 
639
- // If output dir is empty (fresh clone) or --force, treat as first sync pull everything
639
+ // If output dir is empty (fresh clone) or --force, treat as first sync, pull everything
640
640
  const scopedLocalFiles = filterFilesToPullScope(localFiles);
641
641
  const scopedRemoteFiles = filterFilesToPullScope(remoteFiles);
642
642
  const scopedManifest = filterManifestToPullScope(manifest);
@@ -750,20 +750,20 @@ async function pullBusiness(slug) {
750
750
  }
751
751
  }
752
752
 
753
- // FORCE MIRROR SWEEP local must EXACTLY match cloud after a force pull.
753
+ // FORCE MIRROR SWEEP, local must EXACTLY match cloud after a force pull.
754
754
  // The threeWayCompare path with effectiveManifest=null only computes
755
755
  // newLocal/conflicts/newRemote and never marks files as deletedRemote, so
756
756
  // local-only files (created locally, never on cloud) survive a force pull.
757
757
  // That breaks the "cloud is the source of truth" promise. Sweep them now.
758
758
  //
759
- // SAFETY GUARDS without these the sweep can wipe an entire local copy:
759
+ // SAFETY GUARDS, without these the sweep can wipe an entire local copy:
760
760
  // • Scope the sweep: when --only is set, only sweep paths INSIDE the
761
- // prefix(es). Out-of-scope local files must be left alone the user
761
+ // prefix(es). Out-of-scope local files must be left alone, the user
762
762
  // asked for a partial pull, not a workspace-wide reset.
763
763
  // • Skip when remoteFiles is empty AND local has in-scope content: the
764
764
  // snapshot endpoint has a known server-side bug where it returns 0
765
765
  // files for healthy workspaces. If cloud reports empty but local has
766
- // in-scope content we refuse to sweep the user can re-run with
766
+ // in-scope content we refuse to sweep, the user can re-run with
767
767
  // --keep-local and investigate, or run `atris align --hard` for an
768
768
  // explicit nuke.
769
769
  // • Skip files the server's snapshot filter hides. The warm runner's
@@ -786,11 +786,11 @@ async function pullBusiness(slug) {
786
786
 
787
787
  // SAFETY: only sweep files atris previously wrote (recorded in the
788
788
  // manifest). Local-only files that were never on cloud are the user's
789
- // own atris didn't put them there, atris doesn't delete them. This
789
+ // own, atris didn't put them there, atris doesn't delete them. This
790
790
  // makes it impossible for a stray cwd (e.g. $HOME) to cause atris to
791
791
  // wipe ~/Library, ~/Downloads, etc.
792
792
  //
793
- // If there's no prior manifest (first pull), there's nothing to sweep
793
+ // If there's no prior manifest (first pull), there's nothing to sweep,
794
794
  // threeWayCompare already handled newRemote/conflicts/newLocal, and we
795
795
  // have no basis for claiming ownership of any local-only file.
796
796
  const managedPaths = manifest && manifest.files
@@ -815,7 +815,7 @@ async function pullBusiness(slug) {
815
815
  console.log(` - ${p.replace(/^\//, '')} not on cloud, removed locally`);
816
816
  deleted++;
817
817
  } catch {
818
- // ignore file might already be gone
818
+ // ignore, file might already be gone
819
819
  }
820
820
  }
821
821
  }
@@ -846,7 +846,7 @@ async function pullBusiness(slug) {
846
846
  commitHash = headResult.data.commit;
847
847
  }
848
848
  } catch {
849
- // Git might not be initialized yet that's fine
849
+ // Git might not be initialized yet, that's fine
850
850
  }
851
851
 
852
852
  if (failOnConflict && conflictCount > 0) {
@@ -878,7 +878,7 @@ async function pullBusiness(slug) {
878
878
  // ANTI-WIPE GUARD: if cloud reported zero in-scope files but local still
879
879
  // has in-scope content (i.e. the sweep refused), don't overwrite the
880
880
  // manifest with empty data for the scoped subtree. The manifest is the
881
- // authoritative record of what we last knew was on cloud wiping it
881
+ // authoritative record of what we last knew was on cloud, wiping it
882
882
  // because of a transient empty snapshot would force every subsequent
883
883
  // push to flag every file as drift. Better to leave the manifest stale
884
884
  // than to record a never-actually-true "cloud is empty" state.
@@ -893,7 +893,7 @@ async function pullBusiness(slug) {
893
893
  }
894
894
  }
895
895
 
896
- // Save manifest when using --only, merge into existing manifest so paths
896
+ // Save manifest, when using --only, merge into existing manifest so paths
897
897
  // OUTSIDE the scoped prefix don't get dropped. Inside the scoped prefix,
898
898
  // however, we must replace (not merge) so that files deleted on cloud
899
899
  // since the last sync get evicted from the manifest. Without this, the
@@ -939,7 +939,7 @@ async function pullBusiness(slug) {
939
939
 
940
940
  const skillFile = path.join(fullPath, 'SKILL.md');
941
941
  if (fs.existsSync(skillFile)) {
942
- // This is a leaf skill wire it
942
+ // This is a leaf skill, wire it
943
943
  const skillName = relPrefix ? `${relPrefix}-${entry}` : entry;
944
944
  const symlinkPath = path.join(claudeSkillsDir, skillName);
945
945
  const relativePath = path.relative(path.dirname(symlinkPath), fullPath);
@@ -982,7 +982,7 @@ async function pullBusiness(slug) {
982
982
  }
983
983
  }
984
984
 
985
- // Telemetry only count files where content actually came over the wire
985
+ // Telemetry, only count files where content actually came over the wire
986
986
  // (smart-pull skips unchanged files and just sends a hash). Counting all
987
987
  // remoteFiles here would dwarf the real signal once smart-pull steady-state
988
988
  // is normal (< 1% of files transferred per pull).
@@ -1048,12 +1048,12 @@ async function pullGeneralJournal(token, agentId) {
1048
1048
  if (localContent.trim() === remoteContent.trim()) continue;
1049
1049
 
1050
1050
  if (!localContent || localContent.trim() === '') {
1051
- // No local just write remote
1051
+ // No local, just write remote
1052
1052
  fs.writeFileSync(logFile, remoteContent);
1053
1053
  console.log(` Journal ${date} pulled`);
1054
1054
  synced++;
1055
1055
  } else {
1056
- // Both exist and differ merge
1056
+ // Both exist and differ, merge
1057
1057
  try {
1058
1058
  const localSections = parseJournalSections(localContent);
1059
1059
  const remoteSections = parseJournalSections(remoteContent);
@@ -1065,7 +1065,7 @@ async function pullGeneralJournal(token, agentId) {
1065
1065
  console.log(` Journal ${date} merged`);
1066
1066
  synced++;
1067
1067
  } else {
1068
- // Conflicts keep local, warn
1068
+ // Conflicts, keep local, warn
1069
1069
  console.log(` Journal ${date} has conflicts (kept local, run "atris log sync" to resolve)`);
1070
1070
  }
1071
1071
  } catch {
package/commands/pulse.js CHANGED
@@ -748,7 +748,7 @@ function pulseCommand(argv = []) {
748
748
  const sub = argv[0];
749
749
  const rest = argv.slice(1);
750
750
  if (sub === '--help' || sub === '-h' || sub === 'help') return showHelp();
751
- // `pulse <sub> --help` is a help request, not a run never execute the
751
+ // `pulse <sub> --help` is a help request, not a run, never execute the
752
752
  // subcommand (tick/status/etc. have real side effects) just to show usage.
753
753
  if (rest.includes('--help') || rest.includes('-h')) return showHelp();
754
754
  switch (sub) {