atris 3.37.1 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/AGENTS.md +25 -6
  2. package/README.md +2 -0
  3. package/atris/PERSONA.md +11 -11
  4. package/atris.md +17 -2
  5. package/ax +160 -34
  6. package/bin/atris.js +78 -6
  7. package/commands/activate.js +16 -3
  8. package/commands/agent-spawn.js +13 -18
  9. package/commands/autoland.js +76 -47
  10. package/commands/autopilot-front.js +0 -4
  11. package/commands/autopilot.js +0 -12
  12. package/commands/bench.js +10 -12
  13. package/commands/brain.js +0 -7
  14. package/commands/brief.js +0 -2
  15. package/commands/business-sync.js +0 -1
  16. package/commands/business.js +452 -12
  17. package/commands/chat-scan.js +5 -7
  18. package/commands/clean.js +31 -2
  19. package/commands/close.js +46 -11
  20. package/commands/cloud.js +0 -1
  21. package/commands/codex-goal.js +8 -12
  22. package/commands/computer.js +6 -5
  23. package/commands/console.js +64 -11
  24. package/commands/decide.js +166 -0
  25. package/commands/deck.js +30 -12
  26. package/commands/dream.js +0 -7
  27. package/commands/drill.js +14 -25
  28. package/commands/engine.js +206 -26
  29. package/commands/experiments.js +0 -4
  30. package/commands/fleet-report.js +12 -4
  31. package/commands/game.js +261 -0
  32. package/commands/gm.js +17 -7
  33. package/commands/goal.js +0 -6
  34. package/commands/harvest.js +1 -5
  35. package/commands/improve.js +29 -8
  36. package/commands/init.js +41 -37
  37. package/commands/integrations.js +0 -7
  38. package/commands/land.js +77 -20
  39. package/commands/launchpad.js +1 -16
  40. package/commands/lesson.js +17 -7
  41. package/commands/lifecycle.js +5 -5
  42. package/commands/log.js +55 -5
  43. package/commands/loop-front.js +0 -2
  44. package/commands/loops.js +6 -5
  45. package/commands/meet.js +0 -2
  46. package/commands/member.js +622 -573
  47. package/commands/mission.js +790 -314
  48. package/commands/next.js +0 -1
  49. package/commands/now.js +0 -12
  50. package/commands/one-lap.js +8 -8
  51. package/commands/orb.js +0 -4
  52. package/commands/pack-craft.js +0 -1
  53. package/commands/pack.js +3599 -424
  54. package/commands/play.js +6 -6
  55. package/commands/probe.js +12 -6
  56. package/commands/pulse.js +88 -64
  57. package/commands/push.js +0 -4
  58. package/commands/radar.js +14 -11
  59. package/commands/recover.js +340 -0
  60. package/commands/release.js +10 -9
  61. package/commands/report.js +0 -1
  62. package/commands/router.js +320 -0
  63. package/commands/run-front.js +0 -2
  64. package/commands/search.js +0 -3
  65. package/commands/site-deploy.js +870 -0
  66. package/commands/site.js +11 -2
  67. package/commands/skill.js +7 -2
  68. package/commands/slop.js +237 -7
  69. package/commands/status.js +6 -0
  70. package/commands/stream.js +4 -20
  71. package/commands/study.js +0 -3
  72. package/commands/sync-checkout.js +51 -0
  73. package/commands/sync.js +11 -40
  74. package/commands/task.js +999 -595
  75. package/commands/taste.js +101 -0
  76. package/commands/teach.js +86 -0
  77. package/commands/team.js +83 -3
  78. package/commands/unknowns.js +0 -10
  79. package/commands/update.js +0 -1
  80. package/commands/vercel.js +4 -2
  81. package/commands/visualize.js +0 -5
  82. package/commands/voice.js +195 -0
  83. package/commands/watch.js +1 -28
  84. package/commands/wiki.js +42 -15
  85. package/commands/wish.js +0 -1
  86. package/commands/workflow.js +2 -2
  87. package/commands/worktree.js +88 -28
  88. package/commands/xp.js +28 -30
  89. package/commands/youtube.js +0 -2
  90. package/lib/accept-verify-gate.js +126 -0
  91. package/lib/activity-stream.js +0 -2
  92. package/lib/arg-parser.js +41 -0
  93. package/lib/auto-accept-certified.js +261 -3
  94. package/lib/autoland.js +66 -3
  95. package/lib/ax-auto-lane.js +68 -7
  96. package/lib/bench/context.js +0 -2
  97. package/lib/bench/report.js +0 -1
  98. package/lib/bench/runner.js +18 -11
  99. package/lib/brief-ledger.js +0 -6
  100. package/lib/business-simulate.js +382 -0
  101. package/lib/chat-log-scan.js +0 -5
  102. package/lib/checkout-sync.js +52 -0
  103. package/lib/claude-boot-block.js +48 -0
  104. package/lib/cloud-mission.js +0 -6
  105. package/lib/codex-flight.js +0 -1
  106. package/lib/conductor-artifacts.js +25 -0
  107. package/lib/context-gatherer.js +7 -2
  108. package/lib/deck-compose.js +0 -1
  109. package/lib/deck-history.js +0 -1
  110. package/lib/deck-layout.js +0 -5
  111. package/lib/deck-review.js +0 -7
  112. package/lib/deck-schema.js +0 -1
  113. package/lib/default-verifier.js +0 -2
  114. package/lib/engine-registry.js +157 -28
  115. package/lib/experiments/daily.js +0 -6
  116. package/lib/falsifier-probe.js +84 -0
  117. package/lib/fleet.js +275 -42
  118. package/lib/functional-owner.js +0 -3
  119. package/lib/git-spawn.js +15 -0
  120. package/lib/inspect-fields.js +0 -1
  121. package/lib/json-file.js +37 -0
  122. package/lib/known-commands.js +2 -2
  123. package/lib/learnings.js +0 -2
  124. package/lib/lesson-preflight.js +146 -0
  125. package/lib/loop-doctor.js +11 -19
  126. package/lib/manifest.js +0 -2
  127. package/lib/member-alive.js +0 -1
  128. package/lib/member-switches.js +0 -5
  129. package/lib/mission-artifact.js +0 -1
  130. package/lib/mission-human-asks.js +28 -0
  131. package/lib/mission-protected-lane.js +296 -0
  132. package/lib/mission-room.js +0 -2
  133. package/lib/mission-runtime-loop.js +0 -1
  134. package/lib/next-moves.js +0 -18
  135. package/lib/official-cli-integration.js +47 -6
  136. package/lib/orb-context.js +14 -4
  137. package/lib/orb-scorecard.js +0 -1
  138. package/lib/outbound-send-gate.js +0 -6
  139. package/lib/pack-capabilities.js +685 -0
  140. package/lib/permission-grants.js +0 -6
  141. package/lib/policy-lessons.js +0 -2
  142. package/lib/pulse.js +68 -15
  143. package/lib/review-integrity.js +0 -2
  144. package/lib/router-brain.js +51 -3
  145. package/lib/runner-command.js +0 -10
  146. package/lib/runtime-bootstrap.js +0 -1
  147. package/lib/scorecard.js +0 -1
  148. package/lib/self-drive.js +45 -14
  149. package/lib/state-detection.js +0 -6
  150. package/lib/task-db.js +137 -7
  151. package/lib/task-decision.js +50 -0
  152. package/lib/taste-lessons.js +153 -0
  153. package/lib/team-presence.js +0 -1
  154. package/lib/tool-result-encode.js +17 -1
  155. package/lib/voice-gate.js +129 -2
  156. package/lib/wiki.js +152 -9
  157. package/lib/wish-audit.js +21 -7
  158. package/lib/wish-delegate.js +27 -21
  159. package/lib/wish-stats.js +0 -5
  160. package/lib/wish-store.js +0 -1
  161. package/lib/workspace-safety.js +0 -2
  162. package/lib/zip.js +95 -8
  163. package/package.json +2 -1
  164. package/templates/business-starter/persona.md +9 -0
package/commands/wiki.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const { hasFlag } = require('../lib/arg-parser');
3
4
  const { loadCredentials } = require('../utils/auth');
4
5
  const { apiRequestJson } = require('../utils/api');
5
6
  const { loadBusinesses, saveBusinesses } = require('./business');
@@ -14,8 +15,11 @@ const {
14
15
  buildIngestPrompt,
15
16
  buildQueryPrompt,
16
17
  buildLintPrompt,
18
+ buildConsolidatePrompt,
17
19
  writeWikiStatus,
18
20
  appendWikiLog,
21
+ findWikiMetabolismFindings,
22
+ recordWikiConsolidation,
19
23
  validateAgentReadableWikiPages,
20
24
  } = require('../lib/wiki');
21
25
 
@@ -171,7 +175,7 @@ function hasHelpFlag(args) {
171
175
  }
172
176
 
173
177
  function printWikiHelp(scope = null) {
174
- const normalized = scope === 'ingest' || scope === 'query' || scope === 'lint' || scope === 'search' || scope === 'log' || scope === 'loop' || scope === 'verify'
178
+ const normalized = scope === 'ingest' || scope === 'query' || scope === 'lint' || scope === 'consolidate' || scope === 'search' || scope === 'log' || scope === 'loop' || scope === 'verify'
175
179
  ? scope
176
180
  : null;
177
181
 
@@ -191,12 +195,17 @@ function printWikiHelp(scope = null) {
191
195
  console.log('Usage: atris wiki lint [--private|--cloud] [business]');
192
196
  console.log('');
193
197
  console.log('Build a local or cloud wiki lint prompt.');
198
+ } else if (normalized === 'consolidate') {
199
+ console.log('Usage: atris wiki consolidate [--private]');
200
+ console.log('');
201
+ console.log('Build a local wiki pruning prompt and reset the growth marker.');
194
202
  } else {
195
- console.log('Usage: atris wiki <ingest|query|lint|search|log|loop|verify|entities|related> [business] [args]');
203
+ console.log('Usage: atris wiki <ingest|query|lint|consolidate|search|log|loop|verify|entities|related> [business] [args]');
196
204
  console.log('');
197
205
  console.log(' ingest <path> Local-first ingest into atris/wiki/');
198
206
  console.log(' query "question" Local-first query against atris/wiki/');
199
207
  console.log(' lint Local-first lint for atris/wiki/');
208
+ console.log(' consolidate Build a local pruning prompt and reset wiki growth');
200
209
  console.log(' search [business] <term> Search local atris/wiki/index.md');
201
210
  console.log(' log [business] [N] Show recent atris/wiki/log.md entries');
202
211
  console.log(' loop Run local wiki upkeep analysis and refresh STATUS/log');
@@ -215,10 +224,6 @@ function printWikiHelp(scope = null) {
215
224
  console.log('');
216
225
  }
217
226
 
218
- function hasFlag(args, name) {
219
- return args.includes(name);
220
- }
221
-
222
227
  function optionValue(args, name, fallback = null) {
223
228
  const index = args.indexOf(name);
224
229
  if (index === -1 || index + 1 >= args.length) return fallback;
@@ -366,7 +371,11 @@ async function wikiLint(mode, slug) {
366
371
  console.error(`No local wiki found at ${getWikiRoot(wikiMode)}. Run: atris wiki ingest${wikiMode === 'private' ? ' --private' : ''} <path>`);
367
372
  process.exit(1);
368
373
  }
369
- printLocalPrompt(mode === 'private' ? 'Private wiki lint' : 'Local wiki lint', buildLintPrompt(wikiMode), getWikiRoot(wikiMode), [`Wiki dir: ${wikiDir}`]);
374
+ const metabolismFindings = findWikiMetabolismFindings(process.cwd(), wikiMode);
375
+ printLocalPrompt(mode === 'private' ? 'Private wiki lint' : 'Local wiki lint', buildLintPrompt(wikiMode), getWikiRoot(wikiMode), [
376
+ `Wiki dir: ${wikiDir}`,
377
+ ...metabolismFindings.map((finding) => `${finding.code}: ${finding.message}`),
378
+ ]);
370
379
  return;
371
380
  }
372
381
 
@@ -376,6 +385,28 @@ async function wikiLint(mode, slug) {
376
385
  await runChat(business, buildLintPrompt(), creds.token);
377
386
  }
378
387
 
388
+ function wikiConsolidate(mode, slug) {
389
+ if (mode === 'cloud') {
390
+ console.error('Wiki consolidation is local-only. Pull the wiki before consolidating it.');
391
+ process.exit(1);
392
+ }
393
+
394
+ const wikiMode = mode === 'private' ? 'private' : 'public';
395
+ const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
396
+ if (!wikiDir) {
397
+ console.error(`No local wiki found at ${getWikiRoot(wikiMode)}.`);
398
+ process.exit(1);
399
+ }
400
+
401
+ printLocalPrompt(
402
+ mode === 'private' ? 'private wiki consolidate' : 'local wiki consolidate',
403
+ buildConsolidatePrompt(wikiMode),
404
+ getWikiRoot(wikiMode),
405
+ [`wiki dir: ${wikiDir}`]
406
+ );
407
+ recordWikiConsolidation(process.cwd(), wikiMode);
408
+ }
409
+
379
410
  function wikiSearch(mode, slug, query) {
380
411
  if (!query) {
381
412
  console.error('Usage: atris wiki search [business] <term>');
@@ -513,6 +544,10 @@ async function wikiCommand(subcommand, ...args) {
513
544
  await wikiLint(mode, slug);
514
545
  break;
515
546
  }
547
+ case 'consolidate': {
548
+ wikiConsolidate(mode, null);
549
+ break;
550
+ }
516
551
  case 'search': {
517
552
  if (mode === 'private') {
518
553
  wikiSearch(mode, null, cleanArgs.join(' '));
@@ -572,12 +607,4 @@ async function wikiCommand(subcommand, ...args) {
572
607
 
573
608
  module.exports = {
574
609
  wikiCommand,
575
- wikiIngest,
576
- wikiQuery,
577
- wikiLint,
578
- wikiSearch,
579
- wikiLog,
580
- wikiVerify,
581
- wikiEntities,
582
- wikiRelated,
583
610
  };
package/commands/wish.js CHANGED
@@ -487,7 +487,6 @@ module.exports = {
487
487
  printRewards,
488
488
  readWishEvents,
489
489
  readWishes,
490
- runCloudWish,
491
490
  runAgainWish,
492
491
  sayWish,
493
492
  sharesMeaningfulWords,
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { getLogPath } = require('../lib/journal');
4
- const { encodeToolResult } = require('../lib/tool-result-encode');
4
+ const { buildToolResultBody } = require('../lib/tool-result-encode');
5
5
 
6
6
  function wrapWorkflowText(text, width = 76) {
7
7
  const normalized = String(text || '').replace(/\s+/g, ' ').trim();
@@ -140,7 +140,7 @@ function postToolResult(callId, result, base = 'http://127.0.0.1:8000') {
140
140
  const url = new URL('/api/atris2/turn/tool-result', base);
141
141
  const transport = url.protocol === 'https:' ? require('https') : require('http');
142
142
  return new Promise((resolve, reject) => {
143
- const postData = JSON.stringify({ call_id: callId, result_base64: encodeToolResult(result) });
143
+ const postData = JSON.stringify(buildToolResultBody(callId, result));
144
144
  const req = transport.request({
145
145
  hostname: url.hostname,
146
146
  port: url.port || (url.protocol === 'https:' ? 443 : 80),
@@ -3,7 +3,9 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { spawnSync } = require('child_process');
6
+ const { hasFlag, readFlag } = require('../lib/arg-parser');
6
7
  const { stampLatestOpenBriefForWorktree } = require('../lib/brief-ledger');
8
+ const { isConductorArtifact } = require('../lib/conductor-artifacts');
7
9
 
8
10
  const REGEN_ADAPTER_FILES = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'];
9
11
  const COMMAND_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
@@ -261,11 +263,16 @@ function baseBranchName(ref) {
261
263
  return String(ref || '').replace(/^refs\/heads\//, '').replace(/^origin\//, '');
262
264
  }
263
265
 
264
- function statusCounts(root, { ignoredUnstagedFiles = new Set(), ignoredUntrackedFiles = new Set() } = {}) {
266
+ function statusCounts(root, {
267
+ ignoredUnstagedFiles = new Set(),
268
+ ignoredUntrackedFiles = new Set(),
269
+ ignoreUntracked = null,
270
+ } = {}) {
265
271
  if (!fs.existsSync(root)) return null;
266
272
  // -uall expands untracked directories to individual files so ignoredUntrackedFiles
267
- // can match exact paths (plain porcelain collapses them to "?? dir/").
268
- const statusArgs = ignoredUntrackedFiles.size ? ['status', '--porcelain', '-uall'] : ['status', '--porcelain'];
273
+ // and ignoreUntracked can match exact paths (plain porcelain collapses them to "?? dir/").
274
+ const expandUntracked = ignoredUntrackedFiles.size || typeof ignoreUntracked === 'function';
275
+ const statusArgs = expandUntracked ? ['status', '--porcelain', '-uall'] : ['status', '--porcelain'];
269
276
  const result = runGit(statusArgs, { cwd: root, check: false });
270
277
  if (result.status !== 0) return null;
271
278
  let staged = 0;
@@ -276,6 +283,7 @@ function statusCounts(root, { ignoredUnstagedFiles = new Set(), ignoredUntracked
276
283
  if (ignoredUnstagedFiles.has(file) && line[0] === ' ' && line[1] !== ' ') continue;
277
284
  if (line.startsWith('??')) {
278
285
  if (ignoredUntrackedFiles.has(file)) continue;
286
+ if (typeof ignoreUntracked === 'function' && ignoreUntracked(file)) continue;
279
287
  untracked += 1;
280
288
  continue;
281
289
  }
@@ -285,6 +293,16 @@ function statusCounts(root, { ignoredUnstagedFiles = new Set(), ignoredUntracked
285
293
  return { staged, unstaged, untracked };
286
294
  }
287
295
 
296
+ // Remove conductor plumbing from the index after a broad `git add -A`, so the commit
297
+ // records engine output only. Paths that were never staged are simply not listed.
298
+ function unstageConductorArtifacts(root) {
299
+ const staged = runGit(['diff', '--cached', '--name-only'], { cwd: root, check: false });
300
+ if (staged.status !== 0) return [];
301
+ const drop = String(staged.stdout || '').split(/\r?\n/).filter(Boolean).filter(isConductorArtifact);
302
+ if (drop.length) runGit(['reset', '-q', 'HEAD', '--', ...drop], { cwd: root, check: false });
303
+ return drop;
304
+ }
305
+
288
306
  function changedFiles(root, args) {
289
307
  const result = runGit([...args, '--', ...REGEN_ADAPTER_FILES], { cwd: root, check: false });
290
308
  if (result.status !== 0) return new Set();
@@ -304,20 +322,6 @@ function restoreRegeneratedAdapterChurn(root, message, { dryRun = false } = {})
304
322
  return skipped;
305
323
  }
306
324
 
307
- function readFlag(args, name, fallback = '') {
308
- const prefix = `${name}=`;
309
- for (let i = 0; i < args.length; i += 1) {
310
- const arg = String(args[i]);
311
- if (arg === name && args[i + 1] && !String(args[i + 1]).startsWith('--')) return String(args[i + 1]);
312
- if (arg.startsWith(prefix)) return arg.slice(prefix.length);
313
- }
314
- return fallback;
315
- }
316
-
317
- function hasFlag(args, name) {
318
- return args.includes(name);
319
- }
320
-
321
325
  function swarloClaim(root, { channel, taskKey, content }) {
322
326
  const script = path.join(root, 'scripts', 'swarlo.py');
323
327
  if (!fs.existsSync(script)) return 'skip: scripts/swarlo.py not found';
@@ -472,6 +476,14 @@ function createOrFindPr(root, branch, targetRef, title, dryRun) {
472
476
  `Target: ${targetBranch}`,
473
477
  ].join('\n');
474
478
  if (dryRun) return `dry-run: gh pr create --base ${targetBranch} --head ${branch}`;
479
+ // An engine that produced nothing leaves a branch level with its base. Asking
480
+ // GitHub for a PR there fails with "No commits between ...", and that throw used
481
+ // to escape the flight and abort the whole run. Say it plainly instead.
482
+ const ahead = runGit(['rev-list', '--count', `${targetRef}..HEAD`], { cwd: root, check: false });
483
+ if (ahead.status === 0 && String(ahead.stdout || '').trim() === '0') {
484
+ console.log('pr: skipped (no commits ahead of base)');
485
+ return '';
486
+ }
475
487
  const created = spawnSync(
476
488
  'gh',
477
489
  ['pr', 'create', '--base', targetBranch, '--head', branch, '--title', title, '--body', body],
@@ -498,6 +510,36 @@ function shipHelp() {
498
510
  console.log(' recommended flight verify: npm run test:fast && node --test <focused files>');
499
511
  }
500
512
 
513
+ function deniedLaneForShip(root, branch, message) {
514
+ try {
515
+ const { DENIED_TAGS } = require('../lib/fleet');
516
+ const refs = new Set();
517
+ for (const source of [String(branch || ''), String(message || '')]) {
518
+ for (const m of source.toUpperCase().matchAll(/([A-Z]{2,6}-\d+)/g)) refs.add(m[1]);
519
+ for (const m of source.toLowerCase().matchAll(/([a-z]{2,6})-(\d+)/g)) refs.add(`${m[1].toUpperCase()}-${m[2]}`);
520
+ }
521
+ if (!refs.size) return null;
522
+ const projectionPath = path.join(findPrimaryRoot(root), '.atris', 'state', 'tasks.projection.json');
523
+ if (!fs.existsSync(projectionPath)) return null;
524
+ const projection = JSON.parse(fs.readFileSync(projectionPath, 'utf8'));
525
+ for (const task of projection.tasks || []) {
526
+ const ids = [task.display_id, task.legacy_ref].filter(Boolean).map(v => String(v).toUpperCase());
527
+ if (!ids.some(id => refs.has(id))) continue;
528
+ const tags = [task.tag, ...(task.metadata?.todo_tags || [])].filter(Boolean).map(t => String(t).toLowerCase());
529
+ const hit = tags.find(t => DENIED_TAGS.includes(t));
530
+ if (hit) return { taskId: ids[0], tag: hit };
531
+ }
532
+ return null;
533
+ } catch {
534
+ return null;
535
+ }
536
+ }
537
+
538
+ function findPrimaryRoot(root) {
539
+ const worktrees = listWorktrees(root);
540
+ return worktrees[0]?.path || root;
541
+ }
542
+
501
543
  function shipWorktree(args) {
502
544
  if (hasFlag(args, '--help') || hasFlag(args, '-h') || args[0] === 'help') {
503
545
  shipHelp();
@@ -531,6 +573,17 @@ function shipWorktree(args) {
531
573
  console.error('blocked: ship from an isolated worktree, not the primary checkout');
532
574
  return 2;
533
575
  }
576
+ // Denied lanes are checked at dispatch, but a flight that was staffed
577
+ // before a task entered a denied lane (or killed mid-flight) could still
578
+ // ship: a payments command self-landed twice this way on 2026-07-26. The
579
+ // ship gate is the last door, so it re-checks.
580
+ if (!hasFlag(args, '--allow-denied-lane')) {
581
+ const denied = deniedLaneForShip(root, branch, message);
582
+ if (denied) {
583
+ console.error(`blocked: this branch carries ${denied.taskId} which sits in the protected ${denied.tag} lane; a human lands it (or pass --allow-denied-lane on written order)`);
584
+ return 2;
585
+ }
586
+ }
534
587
 
535
588
  let oneLapProof = null;
536
589
  try {
@@ -552,7 +605,10 @@ function shipWorktree(args) {
552
605
  refreshRemoteRef(root, targetRef);
553
606
  const skippedAdapters = restoreRegeneratedAdapterChurn(root, message, { dryRun });
554
607
  const ignoredUnstagedFiles = dryRun ? new Set(skippedAdapters) : new Set();
555
- const counts = statusCounts(root, { ignoredUnstagedFiles }) || { staged: 0, unstaged: 0, untracked: 0 };
608
+ // Conductor plumbing must not make the checkout look dirty, or a flight where the
609
+ // engine wrote nothing commits the fleet's own prompt file and calls it work.
610
+ const counts = statusCounts(root, { ignoredUnstagedFiles, ignoreUntracked: isConductorArtifact })
611
+ || { staged: 0, unstaged: 0, untracked: 0 };
556
612
  const dirty = counts.staged || counts.unstaged || counts.untracked;
557
613
  if (dirty && !message) {
558
614
  console.error('blocked: --message is required when there are local changes to commit');
@@ -563,6 +619,9 @@ function shipWorktree(args) {
563
619
  console.log(`commit: ${message}`);
564
620
  if (!dryRun) {
565
621
  runGit(['add', '-A'], { cwd: root });
622
+ // `add -A` sweeps in the fleet's own prompt file and friends. Drop them from the
623
+ // index so a work commit carries work, not harness plumbing. Safe when absent.
624
+ unstageConductorArtifacts(root);
566
625
  if (!proofTreeMatches('index')) return 3;
567
626
  runGit(['commit', '-m', message], { cwd: root });
568
627
  if (!proofTreeMatches('head')) return 3;
@@ -578,7 +637,12 @@ function shipWorktree(args) {
578
637
  if (!dryRun) runCommand(verify, { cwd: root });
579
638
  const afterVerifyRows = dryRun ? [] : statusSnapshot(root);
580
639
  const verifyDelta = newStatusRows(beforeVerify, afterVerifyRows);
581
- const afterVerify = dryRun ? { staged: 0, unstaged: 0, untracked: 0 } : statusCounts(root) || { staged: 0, unstaged: 0, untracked: 0 };
640
+ // This check exists to catch a verifier that writes files. Conductor plumbing was
641
+ // already present before the verifier ran, so counting it here blocks a clean ship.
642
+ const afterVerify = dryRun
643
+ ? { staged: 0, unstaged: 0, untracked: 0 }
644
+ : statusCounts(root, { ignoreUntracked: isConductorArtifact })
645
+ || { staged: 0, unstaged: 0, untracked: 0 };
582
646
  if (!dryRun && (afterVerify.staged || afterVerify.unstaged || afterVerify.untracked)) {
583
647
  console.error(
584
648
  `blocked: verifier left checkout dirty staged=${afterVerify.staged} ` +
@@ -687,8 +751,10 @@ function guard(args) {
687
751
  console.error('run: atris worktree start --member <member>|--agent <name> --task "<short task>" --claim');
688
752
  return 2;
689
753
  }
690
- // The CLI's own worktree metadata (written by `worktree start`) must not count as dirt.
691
- const counts = statusCounts(root, { ignoredUntrackedFiles: new Set(['.atris/agent-worktree.json']) });
754
+ // Conductor plumbing (worktree metadata, the fleet prompt written for this very
755
+ // engine, runtime scratch, the brief ledger) must not count as dirt — otherwise the
756
+ // harness blocks the engine with the harness's own files.
757
+ const counts = statusCounts(root, { ignoreUntracked: isConductorArtifact });
692
758
  if (counts && (counts.staged || counts.unstaged || counts.untracked) && !hasFlag(args, '--allow-dirty')) {
693
759
  console.error(`blocked: checkout is dirty staged=${counts.staged} unstaged=${counts.unstaged} untracked=${counts.untracked}`);
694
760
  return 3;
@@ -880,20 +946,14 @@ function worktreeCommand(args = []) {
880
946
 
881
947
  module.exports = {
882
948
  branchName,
883
- statusSnapshot,
884
- newStatusRows,
885
949
  createAgentWorktree,
950
+ deniedLaneForShip,
886
951
  createOrFindPr,
887
952
  cleanupWorktrees,
888
- defaultShipTarget,
889
953
  defaultStartBase,
890
- defaultWorktreePath,
891
954
  listWorktrees,
892
955
  parseWorktrees,
893
- REGEN_ADAPTER_FILES,
894
956
  normalizeTargetRef,
895
- restoreRegeneratedAdapterChurn,
896
- prMergeRef,
897
957
  slugify,
898
958
  statusCounts,
899
959
  swarloClaim,
package/commands/xp.js CHANGED
@@ -5,6 +5,7 @@ const os = require('os');
5
5
  const path = require('path');
6
6
  const crypto = require('crypto');
7
7
  const { spawnSync } = require('child_process');
8
+ const { hasFlag: hasExactFlag } = require('../lib/arg-parser');
8
9
 
9
10
  const DEFAULT_GRAPH_DAYS = 365;
10
11
  const MAX_SYNC_GRAPH_DAYS = 370;
@@ -277,7 +278,8 @@ function currentForm(payload) {
277
278
  };
278
279
  }
279
280
 
280
- function readFlag(args, name, fallback = null) {
281
+ // Preserve the existing rule that any inline value wins over a split value.
282
+ function readInlineFirstFlag(args, name, fallback = null) {
281
283
  const inline = args.find(arg => arg.startsWith(`${name}=`));
282
284
  if (inline) {
283
285
  return inline.slice(name.length + 1);
@@ -291,7 +293,7 @@ function readFlag(args, name, fallback = null) {
291
293
 
292
294
  function readFirstFlag(args, names, fallback = null) {
293
295
  for (const name of names) {
294
- const value = readFlag(args, name, null);
296
+ const value = readInlineFirstFlag(args, name, null);
295
297
  if (value !== null && value !== undefined && value !== '') return value;
296
298
  }
297
299
  return fallback;
@@ -313,8 +315,9 @@ function readFlagValues(args, names) {
313
315
  return values.filter(Boolean);
314
316
  }
315
317
 
316
- function hasFlag(args, name) {
317
- return args.includes(name) || args.some(arg => arg.startsWith(`${name}=`));
318
+ // This command treats --name=value as flag presence as well as a standalone flag.
319
+ function hasFlagOrValue(args, name) {
320
+ return hasExactFlag(args, name) || args.some(arg => arg.startsWith(`${name}=`));
318
321
  }
319
322
 
320
323
  function levelFromXp(careerXp) {
@@ -1040,7 +1043,7 @@ function buildCareerXpProjection(receipts, workspace, integrity = {}) {
1040
1043
  }
1041
1044
 
1042
1045
  function collectLocalXpProjectionState(args = [], { write = true } = {}) {
1043
- const workspace = path.resolve(readFlag(args, '--workspace', defaultXpWorkspace()));
1046
+ const workspace = path.resolve(readInlineFirstFlag(args, '--workspace', defaultXpWorkspace()));
1044
1047
  const episodePath = path.join(workspace, TASK_EPISODES_FILE);
1045
1048
  const receiptsPath = path.join(workspace, CAREER_XP_RECEIPTS_FILE);
1046
1049
  const projectionPath = path.join(workspace, CAREER_XP_PROJECTION_FILE);
@@ -1170,7 +1173,7 @@ function defaultAllSearchRoots(args = []) {
1170
1173
  if (explicitRoots.length) return uniquePaths(explicitRoots);
1171
1174
 
1172
1175
  const roots = [];
1173
- const workspace = readFlag(args, '--workspace', null);
1176
+ const workspace = readInlineFirstFlag(args, '--workspace', null);
1174
1177
  if (workspace) roots.push(workspace);
1175
1178
  roots.push(process.cwd());
1176
1179
  roots.push(path.join(os.homedir(), 'arena'));
@@ -1575,7 +1578,7 @@ function codexHasThreadsTable(dbPath) {
1575
1578
  }
1576
1579
 
1577
1580
  function readCodexGoalsForSession(args, workspace, sinceMs, untilMs) {
1578
- const explicitDb = readFlag(args, '--codex-state', readFlag(args, '--state', process.env.CODEX_STATE_DB || ''));
1581
+ const explicitDb = readInlineFirstFlag(args, '--codex-state', readInlineFirstFlag(args, '--state', process.env.CODEX_STATE_DB || ''));
1579
1582
  const dbPath = explicitDb
1580
1583
  ? path.resolve(expandHome(explicitDb))
1581
1584
  : path.resolve(fs.existsSync(CODEX_GOALS_FILE) ? CODEX_GOALS_FILE : CODEX_STATE_FILE);
@@ -1592,7 +1595,7 @@ function readCodexGoalsForSession(args, workspace, sinceMs, untilMs) {
1592
1595
  }
1593
1596
  }
1594
1597
 
1595
- const threadId = readFlag(args, '--thread', process.env.CODEX_THREAD_ID || '');
1598
+ const threadId = readInlineFirstFlag(args, '--thread', process.env.CODEX_THREAD_ID || '');
1596
1599
  const clauses = [];
1597
1600
  if (threadId) clauses.push(`tg.thread_id = ${sqlString(threadId)}`);
1598
1601
  const workspaceCandidates = workspacePathCandidates(workspace);
@@ -1632,9 +1635,9 @@ LIMIT 25
1632
1635
  }
1633
1636
 
1634
1637
  function buildCareerXpSessionCapsule(args = []) {
1635
- const workspace = path.resolve(readFlag(args, '--workspace', defaultXpWorkspace()));
1636
- const sinceInput = readFlag(args, '--since', 'today');
1637
- const untilInput = readFlag(args, '--until', null);
1638
+ const workspace = path.resolve(readInlineFirstFlag(args, '--workspace', defaultXpWorkspace()));
1639
+ const sinceInput = readInlineFirstFlag(args, '--since', 'today');
1640
+ const untilInput = readInlineFirstFlag(args, '--until', null);
1638
1641
  const since = parseSessionBoundary(sinceInput, startOfToday());
1639
1642
  const until = parseSessionBoundary(untilInput, new Date());
1640
1643
  const sinceMs = since.getTime();
@@ -1643,7 +1646,7 @@ function buildCareerXpSessionCapsule(args = []) {
1643
1646
  throw new Error('--since must be before --until');
1644
1647
  }
1645
1648
 
1646
- const writeEnabled = !hasFlag(args, '--no-write') && !hasFlag(args, '--dry-run');
1649
+ const writeEnabled = !hasFlagOrValue(args, '--no-write') && !hasFlagOrValue(args, '--dry-run');
1647
1650
  const projectionState = collectLocalXpProjectionState(['--workspace', workspace], { write: writeEnabled });
1648
1651
  const projection = projectionState.projection;
1649
1652
  const receiptsPath = path.join(workspace, CAREER_XP_RECEIPTS_FILE);
@@ -1675,7 +1678,7 @@ function buildCareerXpSessionCapsule(args = []) {
1675
1678
  });
1676
1679
  const windowXp = acceptedReceipts.reduce((sum, receipt) => sum + asNumber(receipt.xp), 0);
1677
1680
  const afterTotalXp = asNumber(projection.total_xp);
1678
- const missionFlag = readFlag(args, '--mission', null);
1681
+ const missionFlag = readInlineFirstFlag(args, '--mission', null);
1679
1682
  const episodeGoals = readJsonl(path.join(workspace, TASK_EPISODES_FILE), { allowPartialTail: true })
1680
1683
  .filter(episode => inWindow(episode?.created_at, sinceMs, untilMs))
1681
1684
  .map(episode => episode.goal);
@@ -1890,8 +1893,8 @@ function syncScopeFields(attribution = {}) {
1890
1893
  }
1891
1894
 
1892
1895
  function publicAgentXpOverride(args = []) {
1893
- if (hasFlag(args, '--public')) return true;
1894
- if (hasFlag(args, '--private') || hasFlag(args, '--internal')) return false;
1896
+ if (hasFlagOrValue(args, '--public')) return true;
1897
+ if (hasFlagOrValue(args, '--private') || hasFlagOrValue(args, '--internal')) return false;
1895
1898
  return null;
1896
1899
  }
1897
1900
 
@@ -1946,9 +1949,9 @@ function syncPlayer(args, projection) {
1946
1949
  }
1947
1950
 
1948
1951
  function buildAgentXpSyncPacket(args = []) {
1949
- const localMode = hasFlag(args, '--local') || hasFlag(args, '--workspace') || hasFlag(args, '--operator');
1952
+ const localMode = hasFlagOrValue(args, '--local') || hasFlagOrValue(args, '--workspace') || hasFlagOrValue(args, '--operator');
1950
1953
  const projectionArgs = args.filter(arg => !['--dry-run', '--no-post', '--packet', '--public', '--private', '--internal'].includes(arg));
1951
- const projection = hasFlag(args, '--all') || !localMode
1954
+ const projection = hasFlagOrValue(args, '--all') || !localMode
1952
1955
  ? collectAllLocalXpProjection(projectionArgs)
1953
1956
  : collectLocalXpProjection(projectionArgs);
1954
1957
  const player = syncPlayer(args, projection);
@@ -2086,10 +2089,10 @@ function buildAgentXpSyncPacket(args = []) {
2086
2089
 
2087
2090
  async function syncAgentXp(args = []) {
2088
2091
  const preview = buildAgentXpSyncPacket(args);
2089
- const dryRun = hasFlag(args, '--dry-run') || hasFlag(args, '--no-post') || hasFlag(args, '--packet');
2092
+ const dryRun = hasFlagOrValue(args, '--dry-run') || hasFlagOrValue(args, '--no-post') || hasFlagOrValue(args, '--packet');
2090
2093
  if (dryRun) return preview;
2091
2094
 
2092
- const token = readFlag(args, '--token', process.env.ATRIS_AGENTXP_SYNC_TOKEN || process.env.AGENTXP_SYNC_TOKEN || '');
2095
+ const token = readInlineFirstFlag(args, '--token', process.env.ATRIS_AGENTXP_SYNC_TOKEN || process.env.AGENTXP_SYNC_TOKEN || '');
2093
2096
  const envToken = process.env.ATRIS_TOKEN && process.env.ATRIS_TOKEN.trim() ? process.env.ATRIS_TOKEN.trim() : '';
2094
2097
  const options = {
2095
2098
  method: 'POST',
@@ -2298,11 +2301,11 @@ async function xpCommand(...args) {
2298
2301
  const commandArgs = args.slice(1);
2299
2302
  let payload;
2300
2303
  try {
2301
- const explicitLocal = hasFlag(commandArgs, '--local')
2302
- || hasFlag(commandArgs, '--workspace')
2303
- || hasFlag(commandArgs, '--operator');
2304
+ const explicitLocal = hasFlagOrValue(commandArgs, '--local')
2305
+ || hasFlagOrValue(commandArgs, '--workspace')
2306
+ || hasFlagOrValue(commandArgs, '--operator');
2304
2307
  const accountStatus = (subcommand === 'status' || subcommand === 'card') && !explicitLocal;
2305
- payload = hasFlag(commandArgs, '--all') || accountStatus
2308
+ payload = hasFlagOrValue(commandArgs, '--all') || accountStatus
2306
2309
  ? collectAllLocalXpProjection(commandArgs)
2307
2310
  : collectLocalXpProjection(commandArgs);
2308
2311
  } catch (error) {
@@ -2324,7 +2327,7 @@ async function xpCommand(...args) {
2324
2327
  }
2325
2328
 
2326
2329
  const jsonMode = args.includes('--json');
2327
- const localMode = hasFlag(args, '--local') || hasFlag(args, '--workspace') || hasFlag(args, '--operator');
2330
+ const localMode = hasFlagOrValue(args, '--local') || hasFlagOrValue(args, '--workspace') || hasFlagOrValue(args, '--operator');
2328
2331
  if (localMode) {
2329
2332
  let payload;
2330
2333
  try {
@@ -2353,6 +2356,7 @@ async function xpCommand(...args) {
2353
2356
  });
2354
2357
  if (!result.ok) {
2355
2358
  console.error(`Failed to load XP graph: ${result.error || result.status}`);
2359
+ console.error('Run: atris xp --local to render your AgentXP from proof receipts in this workspace.');
2356
2360
  process.exit(1);
2357
2361
  }
2358
2362
 
@@ -2366,14 +2370,8 @@ async function xpCommand(...args) {
2366
2370
 
2367
2371
  module.exports = {
2368
2372
  xpCommand,
2369
- buildContributionRows,
2370
2373
  buildCareerXpProjection,
2371
- buildAllCareerXpProjection,
2372
- buildCareerXpSessionCapsule,
2373
- collectAllLocalXpProjection,
2374
2374
  collectLocalXpProjection,
2375
- buildAgentXpSyncPacket,
2376
2375
  syncAgentXp,
2377
- receiptFromTaskEpisode,
2378
2376
  render,
2379
2377
  };
@@ -452,13 +452,11 @@ async function youtubeCommand(argv = process.argv.slice(3), deps = {}) {
452
452
  module.exports = {
453
453
  DEFAULT_QUERY,
454
454
  DEFAULT_TIMEOUT_MS,
455
- showYoutubeHelp,
456
455
  parseYoutubeArgs,
457
456
  buildYoutubePayload,
458
457
  extractLocalTranscript,
459
458
  processYoutube,
460
459
  shouldRetryWithLocalTranscript,
461
460
  formatYoutubeResult,
462
- formatTimestampFromMs,
463
461
  youtubeCommand,
464
462
  };