atris 3.37.1 → 3.38.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 (139) hide show
  1. package/README.md +2 -0
  2. package/atris/PERSONA.md +3 -7
  3. package/atris.md +10 -2
  4. package/ax +158 -33
  5. package/bin/atris.js +49 -2
  6. package/commands/activate.js +16 -3
  7. package/commands/agent-spawn.js +0 -7
  8. package/commands/autoland.js +92 -14
  9. package/commands/autopilot-front.js +0 -4
  10. package/commands/autopilot.js +0 -12
  11. package/commands/brain.js +0 -7
  12. package/commands/brief.js +0 -2
  13. package/commands/business-sync.js +0 -1
  14. package/commands/business.js +107 -12
  15. package/commands/clean.js +31 -2
  16. package/commands/close.js +46 -11
  17. package/commands/cloud.js +0 -1
  18. package/commands/codex-goal.js +0 -2
  19. package/commands/computer.js +6 -5
  20. package/commands/console.js +46 -9
  21. package/commands/deck.js +29 -8
  22. package/commands/dream.js +0 -7
  23. package/commands/drill.js +0 -1
  24. package/commands/engine.js +10 -18
  25. package/commands/experiments.js +0 -4
  26. package/commands/fleet-report.js +12 -4
  27. package/commands/game.js +261 -0
  28. package/commands/gm.js +10 -2
  29. package/commands/goal.js +0 -6
  30. package/commands/harvest.js +0 -1
  31. package/commands/improve.js +29 -8
  32. package/commands/init.js +18 -34
  33. package/commands/integrations.js +0 -7
  34. package/commands/land.js +71 -8
  35. package/commands/launchpad.js +0 -2
  36. package/commands/lesson.js +17 -7
  37. package/commands/loop-front.js +0 -2
  38. package/commands/loops.js +6 -5
  39. package/commands/meet.js +0 -2
  40. package/commands/member.js +68 -3
  41. package/commands/mission.js +359 -102
  42. package/commands/next.js +0 -1
  43. package/commands/now.js +0 -12
  44. package/commands/one-lap.js +8 -8
  45. package/commands/orb.js +0 -4
  46. package/commands/pack-craft.js +0 -1
  47. package/commands/pack.js +670 -77
  48. package/commands/play.js +0 -2
  49. package/commands/probe.js +10 -4
  50. package/commands/pulse.js +73 -48
  51. package/commands/push.js +0 -4
  52. package/commands/radar.js +14 -11
  53. package/commands/recover.js +340 -0
  54. package/commands/report.js +0 -1
  55. package/commands/router.js +319 -0
  56. package/commands/run-front.js +0 -2
  57. package/commands/search.js +0 -3
  58. package/commands/skill.js +7 -2
  59. package/commands/slop.js +225 -7
  60. package/commands/status.js +6 -0
  61. package/commands/stream.js +0 -2
  62. package/commands/study.js +0 -3
  63. package/commands/sync-checkout.js +51 -0
  64. package/commands/sync.js +11 -40
  65. package/commands/task.js +120 -38
  66. package/commands/teach.js +86 -0
  67. package/commands/unknowns.js +0 -10
  68. package/commands/update.js +0 -1
  69. package/commands/visualize.js +0 -5
  70. package/commands/watch.js +0 -6
  71. package/commands/wiki.js +41 -11
  72. package/commands/wish.js +0 -1
  73. package/commands/worktree.js +87 -14
  74. package/commands/xp.js +1 -6
  75. package/commands/youtube.js +0 -2
  76. package/lib/accept-verify-gate.js +122 -0
  77. package/lib/activity-stream.js +0 -2
  78. package/lib/auto-accept-certified.js +145 -2
  79. package/lib/autoland.js +0 -3
  80. package/lib/ax-auto-lane.js +68 -7
  81. package/lib/bench/context.js +0 -2
  82. package/lib/bench/report.js +0 -1
  83. package/lib/bench/runner.js +0 -11
  84. package/lib/brief-ledger.js +0 -6
  85. package/lib/business-simulate.js +382 -0
  86. package/lib/chat-log-scan.js +0 -5
  87. package/lib/checkout-sync.js +52 -0
  88. package/lib/claude-boot-block.js +48 -0
  89. package/lib/cloud-mission.js +0 -6
  90. package/lib/codex-flight.js +0 -1
  91. package/lib/conductor-artifacts.js +25 -0
  92. package/lib/context-gatherer.js +0 -1
  93. package/lib/deck-compose.js +0 -1
  94. package/lib/deck-history.js +0 -1
  95. package/lib/deck-layout.js +0 -5
  96. package/lib/deck-review.js +0 -7
  97. package/lib/deck-schema.js +0 -1
  98. package/lib/default-verifier.js +0 -2
  99. package/lib/engine-registry.js +16 -8
  100. package/lib/experiments/daily.js +0 -6
  101. package/lib/fleet.js +210 -27
  102. package/lib/functional-owner.js +0 -3
  103. package/lib/inspect-fields.js +0 -1
  104. package/lib/known-commands.js +2 -2
  105. package/lib/learnings.js +0 -2
  106. package/lib/loop-doctor.js +11 -17
  107. package/lib/manifest.js +0 -2
  108. package/lib/member-alive.js +0 -1
  109. package/lib/member-switches.js +0 -5
  110. package/lib/mission-artifact.js +0 -1
  111. package/lib/mission-protected-lane.js +293 -0
  112. package/lib/mission-room.js +0 -2
  113. package/lib/mission-runtime-loop.js +0 -1
  114. package/lib/next-moves.js +0 -18
  115. package/lib/official-cli-integration.js +0 -4
  116. package/lib/orb-context.js +6 -3
  117. package/lib/orb-scorecard.js +0 -1
  118. package/lib/outbound-send-gate.js +0 -6
  119. package/lib/permission-grants.js +0 -6
  120. package/lib/policy-lessons.js +0 -2
  121. package/lib/pulse.js +68 -15
  122. package/lib/review-integrity.js +0 -2
  123. package/lib/router-brain.js +0 -2
  124. package/lib/runner-command.js +0 -4
  125. package/lib/runtime-bootstrap.js +0 -1
  126. package/lib/scorecard.js +0 -1
  127. package/lib/self-drive.js +1 -1
  128. package/lib/state-detection.js +0 -6
  129. package/lib/task-db.js +0 -4
  130. package/lib/team-presence.js +0 -1
  131. package/lib/voice-gate.js +63 -2
  132. package/lib/wiki.js +152 -9
  133. package/lib/wish-audit.js +20 -6
  134. package/lib/wish-delegate.js +26 -20
  135. package/lib/wish-stats.js +0 -5
  136. package/lib/wish-store.js +0 -1
  137. package/lib/workspace-safety.js +0 -2
  138. package/lib/zip.js +0 -1
  139. package/package.json +1 -1
package/README.md CHANGED
@@ -226,6 +226,8 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
226
226
  | `atris experiments` | Run small experiments and compare results |
227
227
  | `atris computer card` | Show or write the local owner/computer card |
228
228
  | `atris release` | Draft or publish a version bump, tag, GitHub release, and launch post |
229
+ | `atris slop` | Deterministic slop detector: frontend/prose tells, plus `slop dead --exports` for dead code |
230
+ | `atris clean` | Housekeeping: heal MAP refs, archive journals, report stale pages and dead code |
229
231
 
230
232
  ## Built-In Systems
231
233
 
package/atris/PERSONA.md CHANGED
@@ -30,19 +30,15 @@ This defines how Atris agents communicate, decide, and work.
30
30
 
31
31
  **Read before you act.** Before planning or building, read the relevant files. Understand the current state. Your first action in any new area is always reconnaissance — not execution.
32
32
 
33
- **Always ask for intent.** Clarify before acting.
33
+ **Ask for intent when it's fuzzy or the blast radius is real.** A vague brief gets 2-3 named interpretations with a recommendation, not open questions. Small, reversible, clearly-scoped work: act, then report. Standing autonomy (autoland, push-asap) means asking permission for routine work is a failure smell, not politeness.
34
34
 
35
- **Use ASCII visualization to confirm understanding:**
35
+ **Use ASCII visualization to confirm understanding when a plan is worth confirming:**
36
36
  - **UI elements:** Show design using ASCII
37
37
  - **Backend:** Use arrows, diagrams, logic gates
38
38
  - **Databases:** Tables and graphs showing relationships
39
39
  - **Other cases:** Use best judgment
40
40
 
41
- **Always confirm understanding in ASCII visualization layer for planning.**
42
-
43
- Then go 3-4 sentences one by one through each task.
44
-
45
- Once every task is confirmed, create a plan.
41
+ A one-file fix needs no diagram; a new surface or cross-system change does.
46
42
 
47
43
  **If a task is too big, break it down.** One job per task. If you can't describe "done" in one sentence, decompose it. Small precise tasks compound into big results.
48
44
 
package/atris.md CHANGED
@@ -7,7 +7,9 @@ leave a trail another agent or human can trust.
7
7
 
8
8
  ## activate
9
9
 
10
- On session start, before responding:
10
+ On session start, before responding (if the harness already booted for you, via a
11
+ SessionStart hook or an `atris` status block in context, do not repeat it; go
12
+ straight to the work):
11
13
 
12
14
  1. Read:
13
15
  - `atris/logs/YYYY/YYYY-MM-DD.md`: today's journal
@@ -39,13 +41,17 @@ If a task was already given, show the box and proceed with that task.
39
41
 
40
42
  You can move fast. You do not get to move blindly.
41
43
 
42
- Before changing anything, state:
44
+ Scale ceremony to blast radius. Before a nontrivial change (new surface,
45
+ multi-file, migrations, anything hard to roll back), state:
43
46
  - the goal
44
47
  - the files or systems in scope
45
48
  - what "done" means
46
49
  - how it will be checked
47
50
  - what happens if it fails
48
51
 
52
+ For a small bounded fix, the task's Exit and Verify fields carry this; do not
53
+ restate them as ritual.
54
+
49
55
  Then:
50
56
  - when you begin work in a repo that has no `atris/` folder, run `npx -y atris init` before anything else, then read `atris/atris.md`
51
57
  - do not execute if another agent owns the same task or files
@@ -90,6 +96,8 @@ The same discipline for words. Output stays sharp no matter how bloated the cont
90
96
  - "Missions survive everything: one engine starts the work, another picks it up cold, a third lands it. Work is no longer tied to a chat window, a session, or a vendor."
91
97
  - "The system reports in plain English: one daily message with what landed, what waits on you, and who should own what's next. One person can supervise many projects."
92
98
  - **Fit the screen.** An operator-facing report shows three results, air between them, and holds the rest on ask. No scrolling: reading it is one glance, and the reader asks for more if they want more.
99
+ - **Pace with whitespace.** Words for the operator arrive as complete sentences, one or two per paragraph, blank line between. The whitespace does the pacing, never fragments: fast to scan, still flowing.
100
+ - **Assume attention is scarce and ambition is not.** The reader is at the end of a long day and still moving something big forward. Nothing may require held state (remembering step 2 while reading step 5), every surface ends in one obvious next action that starts in under a minute, and each glance should leave the reader one step closer. Engineers and non-engineers get the same plain sentence; precision stays one ask away, never in the way.
93
101
  - **Human surfaces are gated.** Anything rendered for the operator (brief, digest, boot) goes through the operator-voice translator and a deterministic jargon gate; raw ids, commands, paths, and test tallies never reach a page. A gate violation is a build error, not a style note.
94
102
  - **Interview the fuzz.** When the operator hands off a fuzzy build ("make me the best X", "yeah ok this is my thought, go make it"), do not ask open questions and do not dispatch as-is. Offer 2-3 named interpretations with a recommendation ("best could mean capture, resurfacing, or closure: I'd bet resurfacing, right?") so the operator confirms or corrects in five words. Repeat until the brief fits one screen (goal, why now, done-looks-like, open risks), show the brief, then dispatch. The interview lends the operator a structure to think in; the sharpened brief is what makes every downstream lap cheap.
95
103
 
package/ax CHANGED
@@ -12,7 +12,7 @@ const permissionGrants = require(path.join(__dirname, 'lib', 'permission-grants.
12
12
  const { loadCredentials } = require('./utils/auth');
13
13
  const { apiRequestJson, getApiBaseUrl } = require('./utils/api');
14
14
  const missionRuntime = require('./lib/mission-runtime-loop');
15
- const { pickLane } = require('./lib/ax-auto-lane');
15
+ const { loadOverrides, pickLane } = require('./lib/ax-auto-lane');
16
16
 
17
17
  const EXIT_WORDS = new Set(['exit', 'quit', ':q']);
18
18
  const BACKEND = {
@@ -957,16 +957,92 @@ function normalizeMode(mode) {
957
957
  return mode === 'fast' ? 'fast' : 'pro';
958
958
  }
959
959
 
960
+ function autoPickLogPath(options = {}) {
961
+ return options.pickLogPath
962
+ || process.env.ATRIS_ROUTER_PICKS_PATH
963
+ || path.join(os.homedir(), '.atris', 'ax-auto-picks.jsonl');
964
+ }
965
+
966
+ function autoMessageWords(message) {
967
+ return new Set(String(message || '').toLowerCase().match(/[a-z0-9]+/g) || []);
968
+ }
969
+
970
+ function autoMessageOverlap(left, right) {
971
+ const leftWords = autoMessageWords(left);
972
+ const rightWords = autoMessageWords(right);
973
+ const union = new Set([...leftWords, ...rightWords]);
974
+ if (!union.size) return 0;
975
+ let shared = 0;
976
+ for (const word of leftWords) {
977
+ if (rightWords.has(word)) shared += 1;
978
+ }
979
+ return shared / union.size;
980
+ }
981
+
982
+ function appendRephrasedOutcome(message, logPath) {
983
+ try {
984
+ const rows = fs.readFileSync(logPath, 'utf8').split(/\r?\n/).filter((line) => line.trim());
985
+ let previous = null;
986
+ for (let index = rows.length - 1; index >= 0; index -= 1) {
987
+ try {
988
+ const row = JSON.parse(rows[index]);
989
+ if (row && row.pick_id && row.lane && row.message) {
990
+ previous = row;
991
+ break;
992
+ }
993
+ } catch (_) {
994
+ // Ignore malformed historical lines.
995
+ }
996
+ }
997
+ if (!previous) return;
998
+ const ageMs = Date.now() - Date.parse(previous.at);
999
+ if (ageMs < 0 || ageMs >= 10 * 60 * 1000) return;
1000
+ if (autoMessageOverlap(previous.message, message) < 0.5) return;
1001
+ fs.appendFileSync(logPath, `${JSON.stringify({
1002
+ event: 'outcome',
1003
+ pick_id: previous.pick_id,
1004
+ outcome: 'rephrased',
1005
+ })}\n`);
1006
+ } catch (_) {
1007
+ // Rephrase detection is best-effort and must never block a turn.
1008
+ }
1009
+ }
1010
+
960
1011
  function appendAutoPick(message, picked, options = {}) {
1012
+ const pick = { ...picked, pick_id: crypto.randomUUID() };
1013
+ const logPath = autoPickLogPath(options);
1014
+ Object.defineProperty(pick, '_logPath', { value: logPath, enumerable: false });
961
1015
  try {
962
- const dir = path.join(os.homedir(), '.atris');
963
- fs.mkdirSync(dir, { recursive: true });
964
- fs.appendFileSync(path.join(dir, 'ax-auto-picks.jsonl'), `${JSON.stringify({
1016
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
1017
+ appendRephrasedOutcome(message, logPath);
1018
+ fs.appendFileSync(logPath, `${JSON.stringify({
965
1019
  at: new Date().toISOString(),
966
- lane: picked.lane,
967
- reason: picked.reason,
1020
+ pick_id: pick.pick_id,
1021
+ lane: pick.lane,
1022
+ reason: pick.reason,
1023
+ message: String(message || ''),
968
1024
  message_chars: String(message || '').length,
969
1025
  print: Boolean(options.print),
1026
+ ...(pick.override_id ? { override_id: pick.override_id } : {}),
1027
+ })}\n`);
1028
+ } catch (_) {
1029
+ // Lane telemetry is best-effort and must never block a turn.
1030
+ }
1031
+ return pick;
1032
+ }
1033
+
1034
+ function appendAutoOutcome(picked, result, options = {}) {
1035
+ if (!picked || !picked.pick_id) return;
1036
+ try {
1037
+ const logPath = autoPickLogPath({ pickLogPath: options.pickLogPath || picked._logPath });
1038
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
1039
+ fs.appendFileSync(logPath, `${JSON.stringify({
1040
+ event: 'outcome',
1041
+ pick_id: picked.pick_id,
1042
+ ok: Boolean(result && result.ok),
1043
+ model: String((result && result.model) || modelForMode(picked.lane)),
1044
+ duration_ms: Number((result && (result.duration_ms || result.durationMs)) || 0),
1045
+ error: result && result.error ? String(result.error) : null,
970
1046
  })}\n`);
971
1047
  } catch (_) {
972
1048
  // Lane telemetry is best-effort and must never block a turn.
@@ -974,8 +1050,10 @@ function appendAutoPick(message, picked, options = {}) {
974
1050
  }
975
1051
 
976
1052
  function autoLaneForMessage(message, options = {}) {
977
- const picked = pickLane(message);
978
- appendAutoPick(message, picked, options);
1053
+ const overrides = Array.isArray(options.overrides)
1054
+ ? options.overrides
1055
+ : loadOverrides(options.overridesPath);
1056
+ const picked = appendAutoPick(message, pickLane(message, { overrides }), options);
979
1057
  const errorOutput = options.errorOutput || process.stderr;
980
1058
  errorOutput.write(`${picked.reason}\n`);
981
1059
  return picked;
@@ -3016,8 +3094,11 @@ async function runHeadlessTurn(message, options = {}) {
3016
3094
  const startedAt = Date.now();
3017
3095
  const sink = options.output || bufferedOutput();
3018
3096
  const turnFunction = options.turnFunction || turnFunctionForMode(mode);
3019
- const autoFields = options.autoLane && options.autoReason
3020
- ? { auto_lane: normalizeMode(options.autoLane), auto_reason: String(options.autoReason) }
3097
+ const autoPick = options.autoPick || null;
3098
+ const autoFields = autoPick
3099
+ ? { auto_lane: normalizeMode(autoPick.lane), auto_reason: String(autoPick.reason) }
3100
+ : options.autoLane && options.autoReason
3101
+ ? { auto_lane: normalizeMode(options.autoLane), auto_reason: String(options.autoReason) }
3021
3102
  : {};
3022
3103
 
3023
3104
  try {
@@ -3030,15 +3111,17 @@ async function runHeadlessTurn(message, options = {}) {
3030
3111
  output: sink,
3031
3112
  showProgress: false,
3032
3113
  });
3033
- return {
3114
+ const payload = {
3034
3115
  ok: true,
3035
3116
  model: modelForMode(mode),
3036
3117
  output: String((result && result.output) || '').trim(),
3037
3118
  durationMs: Number((result && result.durationMs) || 0) || (Date.now() - startedAt),
3038
3119
  ...autoFields,
3039
3120
  };
3121
+ appendAutoOutcome(autoPick, payload);
3122
+ return payload;
3040
3123
  } catch (error) {
3041
- return {
3124
+ const payload = {
3042
3125
  ok: false,
3043
3126
  model: modelForMode(mode),
3044
3127
  output: '',
@@ -3046,6 +3129,8 @@ async function runHeadlessTurn(message, options = {}) {
3046
3129
  error: String((error && error.message) || error || 'headless turn failed'),
3047
3130
  ...autoFields,
3048
3131
  };
3132
+ appendAutoOutcome(autoPick, payload);
3133
+ return payload;
3049
3134
  }
3050
3135
  }
3051
3136
 
@@ -3125,11 +3210,16 @@ async function chat(options = {}) {
3125
3210
  return false;
3126
3211
  }
3127
3212
 
3213
+ let autoPick = null;
3128
3214
  if (options.auto) {
3129
- mode = autoLaneForMessage(trimmed, {
3215
+ autoPick = autoLaneForMessage(trimmed, {
3130
3216
  errorOutput: options.errorOutput,
3131
3217
  print: false,
3132
- }).lane;
3218
+ overrides: options.overrides,
3219
+ overridesPath: options.overridesPath,
3220
+ pickLogPath: options.pickLogPath,
3221
+ });
3222
+ mode = autoPick.lane;
3133
3223
  }
3134
3224
 
3135
3225
  if (logger) logger.write(`${formatPrompt(mode)}${trimmed}\n`);
@@ -3230,22 +3320,38 @@ async function chat(options = {}) {
3230
3320
  lastCompactionNotice = compactedTurns;
3231
3321
  output.write(`${paint(`· context: ${compactedTurns} earlier turn${compactedTurns === 1 ? '' : 's'} compacted into a digest`, [ANSI.muted], output)}\n`);
3232
3322
  }
3233
- const result = await turnFunction(trimmed, {
3234
- mode,
3235
- cwd,
3236
- history,
3237
- input,
3238
- output,
3239
- route,
3240
- business: options.business,
3241
- verify: options.verify,
3242
- conversationId,
3243
- turnId,
3244
- approveBackendApi: options.approveBackendApi,
3245
- apiRequestJson: options.apiRequestJson,
3246
- loadCredentials: options.loadCredentials,
3247
- getApiBaseUrl: options.getApiBaseUrl,
3248
- });
3323
+ let result;
3324
+ try {
3325
+ result = await turnFunction(trimmed, {
3326
+ mode,
3327
+ cwd,
3328
+ history,
3329
+ input,
3330
+ output,
3331
+ route,
3332
+ business: options.business,
3333
+ verify: options.verify,
3334
+ conversationId,
3335
+ turnId,
3336
+ approveBackendApi: options.approveBackendApi,
3337
+ apiRequestJson: options.apiRequestJson,
3338
+ loadCredentials: options.loadCredentials,
3339
+ getApiBaseUrl: options.getApiBaseUrl,
3340
+ });
3341
+ appendAutoOutcome(autoPick, {
3342
+ ok: true,
3343
+ model: modelForMode(mode),
3344
+ durationMs: Number(result && result.durationMs) || (Date.now() - turnStartedAt),
3345
+ });
3346
+ } catch (error) {
3347
+ appendAutoOutcome(autoPick, {
3348
+ ok: false,
3349
+ model: modelForMode(mode),
3350
+ durationMs: Date.now() - turnStartedAt,
3351
+ error: String((error && error.message) || error),
3352
+ });
3353
+ throw error;
3354
+ }
3249
3355
  if (result.output && !result.output.endsWith('\n')) output.write('\n');
3250
3356
  const approvalExecution = normalizeMode(mode) === 'code-fast'
3251
3357
  ? null
@@ -4288,8 +4394,7 @@ async function main() {
4288
4394
  route: route === 'auto' ? undefined : route,
4289
4395
  business,
4290
4396
  verify,
4291
- autoLane: autoPick && autoPick.lane,
4292
- autoReason: autoPick && autoPick.reason,
4397
+ autoPick,
4293
4398
  })
4294
4399
  : { ok: false, model: modelForMode(mode), output: '', durationMs: 0, error: 'missing prompt' };
4295
4400
  console.log(JSON.stringify(payload));
@@ -4305,7 +4410,23 @@ async function main() {
4305
4410
  console.log('');
4306
4411
  maybeWriteAtrisGoalBanner(process.stdout, process.cwd());
4307
4412
  const turnStartedAt = Date.now();
4308
- const result = await turnFunctionForMode(mode)(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
4413
+ let result;
4414
+ try {
4415
+ result = await turnFunctionForMode(mode)(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
4416
+ appendAutoOutcome(autoPick, {
4417
+ ok: true,
4418
+ model: modelForMode(mode),
4419
+ durationMs: Number(result && result.durationMs) || (Date.now() - turnStartedAt),
4420
+ });
4421
+ } catch (error) {
4422
+ appendAutoOutcome(autoPick, {
4423
+ ok: false,
4424
+ model: modelForMode(mode),
4425
+ durationMs: Date.now() - turnStartedAt,
4426
+ error: String((error && error.message) || error),
4427
+ });
4428
+ throw error;
4429
+ }
4309
4430
  const receipt = receiptFromState(result);
4310
4431
  const approvalRequest = normalizeMode(mode) === 'code-fast' ? null : approvalRequestFromReceipt(receipt);
4311
4432
  const approvalRecord = approvalRequest
@@ -4338,6 +4459,10 @@ if (require.main === module) {
4338
4459
  }
4339
4460
 
4340
4461
  module.exports = {
4462
+ appendAutoOutcome,
4463
+ appendAutoPick,
4464
+ autoLaneForMessage,
4465
+ autoMessageOverlap,
4341
4466
  authToken,
4342
4467
  authUserId,
4343
4468
  approvalExecutionRequestFromReceipt,
package/bin/atris.js CHANGED
@@ -519,6 +519,8 @@ function showHelp() {
519
519
  console.log(' close - track open loops with deadlines and daily escalation');
520
520
  console.log(' harvest - Find bugs and next actions from receipts, run logs, and thinking');
521
521
  console.log(' verify - Validate work is done (tests, MAP.md, changes)');
522
+ console.log(' slop - Deterministic slop detector: frontend/prose tells, dead code (slop dead --exports)');
523
+ console.log(' recover - Explain stopped missions; --apply performs safe recovery only');
522
524
  console.log(' task - Local agent task plane (atomic claims, TODO import)');
523
525
  console.log(' golden path (zero human turns):');
524
526
  console.log(' atris task delegate "fix the login bug" --to <member>');
@@ -535,10 +537,11 @@ function showHelp() {
535
537
  console.log(' avail - Booking availability (/book/{username} weekly windows)');
536
538
  console.log(' brain - Compile MAP/TODO/wiki/state into a loadable agent brain');
537
539
  console.log(' lesson - Append a one-line lesson to atris/lessons.md (mine: distill receipts/episodes/scorecards into policy lessons)');
540
+ console.log(' teach - Turn a bad turn into a failing benchmark, then a permanent guard (red gate: no promotion without a proven failure)');
538
541
  console.log(' ingest - Local-first wiki ingest into atris/wiki/');
539
542
  console.log(' query - Local-first wiki query against atris/wiki/');
540
543
  console.log(' lint - Local-first wiki lint for atris/wiki/');
541
- console.log(' loop - Local wiki upkeep loop (stale pages, orphans, next ingest)');
544
+ console.log(' loop - Recurring loops front door (wiki upkeep lives under: loop wiki)');
542
545
  console.log(' write - Guided writing sessions: you write every word, atris structures + reviews');
543
546
  console.log('');
544
547
  console.log('Optional helpers:');
@@ -550,6 +553,7 @@ function showHelp() {
550
553
  console.log(' drive - One self-driving tick: mission doctor -> auto-fix -> count disengagements');
551
554
  console.log(' autoland - Approve the policy once; certified work lands itself, you keep irreversible calls');
552
555
  console.log(' engine - Engine registry: list/resolve roles, health flips, default engine, `engine test`, and dispatch flights');
556
+ console.log(' router - inspect ax lane outcomes and promote gated reflex overrides');
553
557
  console.log(' sign - Co-author trailer on every commit in an atris workspace (on/off/status)');
554
558
  console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
555
559
  console.log(' youtube - Process YouTube videos with timestamped transcript-first analysis');
@@ -575,6 +579,7 @@ function showHelp() {
575
579
  console.log('Sync:');
576
580
  console.log(' pull - Pull journals + member data from cloud');
577
581
  console.log(' push - Push workspace files to cloud');
582
+ console.log(' sync-checkout - fetch and fast-forward a clean current branch');
578
583
  console.log(' cloud - Delete cloud files not present locally (cloud clean --dry-run|--yes)');
579
584
  console.log(' live - Keep a business brain fresh (doctor, pull, watch, push)');
580
585
  console.log(' clean-workspace <slug> - Analyze & remove junk files from a workspace (alias: cw)');
@@ -1617,6 +1622,11 @@ function showWelcomeVisualization() {
1617
1622
  console.log(` learned \"${latestBriefTitle}\" overnight -> atris/wiki/briefs/${latestBriefName}`);
1618
1623
  }
1619
1624
 
1625
+ const checkoutWarning = require('../lib/checkout-sync').checkoutBehindMessage(cwd);
1626
+ if (checkoutWarning) {
1627
+ console.log(` ${checkoutWarning}`);
1628
+ }
1629
+
1620
1630
  // Show the work itself, not counts. A newcomer in any domain (code, docs,
1621
1631
  // a travel plan) should read actual task names and know what's happening.
1622
1632
  // Waiting-on-you comes first: the one thing only a human can do.
@@ -1694,6 +1704,16 @@ function showWelcomeVisualization() {
1694
1704
  console.log(row('tidy', tidyBits.join(', ')));
1695
1705
  }
1696
1706
 
1707
+ let wikiNudge = '';
1708
+ try {
1709
+ wikiNudge = require('../lib/wiki').wikiMetabolismNudge(cwd);
1710
+ } catch {
1711
+ wikiNudge = '';
1712
+ }
1713
+ if (wikiNudge) {
1714
+ console.log(` ${wikiNudge}`);
1715
+ }
1716
+
1697
1717
  console.log(row('logs', journalEntries > 0
1698
1718
  ? `${journalEntries} note${journalEntries === 1 ? '' : 's'} today`
1699
1719
  : 'nothing yet today'));
@@ -1792,6 +1812,9 @@ if (command === 'init') {
1792
1812
  Promise.resolve(require('../commands/bench').benchCommand(process.argv.slice(3)))
1793
1813
  .then((code) => process.exit(code || 0))
1794
1814
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(2); });
1815
+ } else if (command === 'router') {
1816
+ const code = require('../commands/router').routerCommand(process.argv.slice(3));
1817
+ if (Number.isInteger(code) && code !== 0) process.exit(code);
1795
1818
  } else if (command === 'mission') {
1796
1819
  // process.exit() can outrun a piped stdout: writes beyond the 64KB pipe
1797
1820
  // buffer are async, so large --json payloads truncate at 64KB multiples.
@@ -1804,6 +1827,10 @@ if (command === 'init') {
1804
1827
  Promise.resolve(require('../commands/mission').missionCommand(process.argv.slice(3)))
1805
1828
  .then(() => exitAfterStdoutDrain(process.exitCode || 0))
1806
1829
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); exitAfterStdoutDrain(1); });
1830
+ } else if (command === 'recover') {
1831
+ Promise.resolve(require('../commands/recover').recoverCommand(process.argv.slice(3)))
1832
+ .then((code) => process.exit(code || 0))
1833
+ .catch((err) => { console.error(`\nerror: ${err.message || err}`); process.exit(1); });
1807
1834
  } else if (command === 'agents') {
1808
1835
  // Glanceable view of every member's state: stuck, waiting on you, working, resting.
1809
1836
  const code = require('../commands/agents').agentsCommand(process.argv.slice(3));
@@ -1931,8 +1958,17 @@ if (command === 'init') {
1931
1958
  logCmd();
1932
1959
  }
1933
1960
  } else if (command === 'logs') {
1961
+ const logsArgs = process.argv.slice(3);
1962
+ if (logsArgs.includes('--help') || logsArgs.includes('-h') || logsArgs[0] === 'help') {
1963
+ console.log('Usage: atris logs [--date YYYY-MM-DD] [--json]');
1964
+ console.log('');
1965
+ console.log(' atris logs Show today\'s workspace + team journal digest');
1966
+ console.log(' atris logs --date <day> Show the digest for a specific day');
1967
+ console.log(' atris logs --json Emit the digest as JSON');
1968
+ process.exit(0);
1969
+ }
1934
1970
  try {
1935
- require('../commands/log').logsDigest(process.argv.slice(3));
1971
+ require('../commands/log').logsDigest(logsArgs);
1936
1972
  process.exit(0);
1937
1973
  } catch (error) {
1938
1974
  console.error(error.message || String(error));
@@ -1956,6 +1992,9 @@ if (command === 'init') {
1956
1992
  activateCmd();
1957
1993
  } else if (command === 'watch') {
1958
1994
  require('../commands/watch').watchAtris();
1995
+ } else if (command === 'sync-checkout') {
1996
+ const code = require('../commands/sync-checkout').syncCheckoutCommand(process.argv.slice(3));
1997
+ process.exit(code);
1959
1998
  } else if (command === 'update' || command === 'sync') {
1960
1999
  const args = process.argv.slice(3);
1961
2000
  const firstSyncArg = process.argv[3];
@@ -2514,6 +2553,10 @@ if (command === 'init') {
2514
2553
  require('../commands/gm').gmCommand(...process.argv.slice(3))
2515
2554
  .then(() => process.exit(0))
2516
2555
  .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
2556
+ } else if (command === 'game') {
2557
+ require('../commands/game').gameCommand(process.argv.slice(3))
2558
+ .then((status) => process.exit(status === 1 ? 1 : 0))
2559
+ .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
2517
2560
  } else if (command === 'github') {
2518
2561
  const status = require('../commands/github').githubCommand(process.argv.slice(3));
2519
2562
  process.exit(status);
@@ -2590,6 +2633,10 @@ if (command === 'init') {
2590
2633
  const subcommand = process.argv[3];
2591
2634
  const args = process.argv.slice(4);
2592
2635
  require('../commands/lesson')(subcommand, ...args);
2636
+ } else if (command === 'teach') {
2637
+ const subcommand = process.argv[3];
2638
+ const args = process.argv.slice(4);
2639
+ require('../commands/teach')(subcommand, ...args);
2593
2640
  } else if (command === 'skill') {
2594
2641
  const subcommand = process.argv[3];
2595
2642
  const args = process.argv.slice(4);
@@ -110,6 +110,15 @@ function completionSentence(completions) {
110
110
  return humanSentence(`since last time: ${landed}; the biggest: ${biggest}.`);
111
111
  }
112
112
 
113
+ // STATUS.md bullets can carry paths and counts the gate would reject, so the
114
+ // narration only says that attention is needed, never quotes the bullet.
115
+ function wikiHealthSentence(wikiStatus) {
116
+ if (!wikiStatus || !Array.isArray(wikiStatus.bullets)) return null;
117
+ const needsAttention = wikiStatus.bullets.some((b) => /stale|orphan/i.test(b));
118
+ if (!needsAttention) return null;
119
+ return humanSentence('the wiki has pages going stale; refresh them: atris loop wiki');
120
+ }
121
+
113
122
  function activeWorkSentence(state, context) {
114
123
  const tasks = Array.isArray(context?.inProgressTasks) ? context.inProgressTasks : [];
115
124
  if (tasks.length) {
@@ -237,13 +246,14 @@ function activateAtris() {
237
246
  } catch {}
238
247
 
239
248
  let moves = [];
249
+ let clarityLine = null;
240
250
  try {
241
251
  const { nextMoves } = require('../lib/next-moves');
242
252
  const { profilePaths } = require('../lib/clarity');
243
253
  const root = process.cwd();
244
254
  moves = nextMoves(root, 3);
245
255
  const { profile, mdPath } = readActivationClarityProfile(root, profilePaths);
246
- formatClarityLine(profile, rel(mdPath));
256
+ clarityLine = formatClarityLine(profile, rel(mdPath));
247
257
  } catch { /* alive onboarding is best-effort; never block activate */ }
248
258
 
249
259
  let briefData = null;
@@ -258,7 +268,6 @@ function activateAtris() {
258
268
 
259
269
  void dateFormatted;
260
270
  void learningCount;
261
- void wikiStatus;
262
271
  fs.existsSync(personaFile);
263
272
  fs.existsSync(mapFile);
264
273
  void taskFilePath;
@@ -269,6 +278,7 @@ function activateAtris() {
269
278
  handoffSentence(handoffContent),
270
279
  completionSentence(recentCompletions),
271
280
  activeWorkSentence(state, context),
281
+ wikiHealthSentence(wikiStatus),
272
282
  ].filter(Boolean);
273
283
  if (Array.isArray(briefData?.waiting)) {
274
284
  const approvalCount = briefData.waiting.length;
@@ -284,6 +294,9 @@ function activateAtris() {
284
294
  statusLines.forEach((line) => console.log(line));
285
295
  }
286
296
 
297
+ console.log('');
298
+ console.log(clarityLine || 'Tip: run atris clarity once so agents learn how you work.');
299
+
287
300
  const nextMove = moves[0] || briefData?.moves?.[0];
288
301
  const nextMoveTitle = focusFragment(nextMove?.title, 100);
289
302
  if (nextMoveTitle) {
@@ -292,4 +305,4 @@ function activateAtris() {
292
305
  }
293
306
  }
294
307
 
295
- module.exports = { activateAtris };
308
+ module.exports = { activateAtris, wikiHealthSentence };
@@ -463,18 +463,11 @@ function agentSpawnStatusCommand(args = [], deps = {}) {
463
463
  }
464
464
 
465
465
  module.exports = {
466
- SPAWN_SCHEMA,
467
- SPAWN_STATE_REL,
468
466
  parseSpawnArgs,
469
- buildDelegatePrompt,
470
467
  commandForEngine,
471
468
  createSpawnRequest,
472
469
  agentDogfoodCommand,
473
- parseDogfoodArgs,
474
- runDogfoodEngine,
475
- loadSpawnRequests,
476
470
  agentSpawnCommand,
477
471
  agentSpawnListCommand,
478
472
  agentSpawnStatusCommand,
479
- showSpawnHelp,
480
473
  };