atris 3.45.1 → 3.46.1

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.
package/AGENTS.md CHANGED
@@ -86,7 +86,8 @@ Human accept -> task Done + AgentXP awarded
86
86
  ```
87
87
 
88
88
  Always-on agents should move proof-backed work to Review, complete their native
89
- goal, then continue the mission loop with the next goal. They must not run
89
+ goal, then stop that task. The next goal or recurring monitor starts in a new
90
+ dedicated task. They must not run
90
91
  `atris task accept` or claim AgentXP themselves; acceptance comes from a human
91
92
  or from the owner's standing `atris autoland` policy, never from the agent.
92
93
 
@@ -100,8 +101,8 @@ falling back to manual task/worktree flow.
100
101
 
101
102
  Codex agents with native goal tools should run `atris mission goal --json`
102
103
  before choosing work. If the payload includes `goal.visible_goal`, mirror
103
- `goal.objective` into the visible chat goal when the current goal is empty,
104
- complete, or already matching; never complete unrelated active goals just to
104
+ `goal.objective` when this active task has no goal yet or its goal already
105
+ matches; never reuse a completed task or complete unrelated active goals just to
105
106
  make room.
106
107
 
107
108
  ## Workflow
@@ -230,6 +231,21 @@ Purpose: optimize for decision-speed; lead with the move, then use descriptions
230
231
  Shape: `<operator>, today is about <move>` -> `I picked this because <why now>` -> `Ready: <draft/proof/context>` -> `Go deeper: <paths>`.
231
232
  Definitions: operator = current person or agent; move = one concrete high-leverage workflow; why now = business reason; ready = prepared action or proof; paths = 2-4 optional deeper views.
232
233
 
234
+ Keep this voice beside every reply:
235
+ <!-- ATRIS_VOICE_CARD:START -->
236
+ ## Voice card
237
+
238
+ Start with the answer, then give the reader only what helps them act. Name the exact thing in plain words, like you are talking to a person.
239
+
240
+ Keep each paragraph to one or two sentences and leave a blank line between thoughts. Use a comma or period instead of an em dash.
241
+
242
+ Status example:
243
+ The reply check is built. I am running the final checks now, so the result is not ready yet.
244
+
245
+ Landing example:
246
+ Replies now get a plain-language check before they reach you. The checks passed, and the change is ready.
247
+ <!-- ATRIS_VOICE_CARD:END -->
248
+
233
249
  Re-run after meaningful work:
234
250
  `atris brain compile --root .`
235
251
  <!-- ATRIS_BRAIN_COMPILE:END -->
package/FOR_AGENTS.md CHANGED
@@ -50,6 +50,11 @@ atris task ready <id> --proof "command that passed"
50
50
 
51
51
  **Never** `atris task accept` unless a human approved. That's their gate, not yours.
52
52
 
53
+ Every task starts with three plain sentences: what changes, why it matters, and
54
+ what done looks like. Keep the exact title, files, commands, requirements,
55
+ events, proof, and approval rules available underneath. Offer the existing
56
+ approve-or-change actions without weakening any proof or human approval gate.
57
+
53
58
  ---
54
59
 
55
60
  ## Rules that make you trustworthy
package/README.md CHANGED
@@ -217,7 +217,7 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
217
217
  | `atris play` | Enter the AgentXP player loop for one proof-backed mission |
218
218
  | `atris gm` | Enter AgentXP General Manager mode for player missions and review queues |
219
219
  | `atris xp` | Show the local AgentXP card and sync eligible proof to the hosted leaderboard |
220
- | `atris codex-goal` | Inspect or clear a completed native Codex thread goal after writing a backup, row dump, and receipt |
220
+ | `atris codex-goal` | Inspect native Codex task goals and keep completed tasks closed |
221
221
  | `atris learn` | Manage structured learnings |
222
222
  | `atris ingest` | Stage raw evidence into `atris/context/` and compile into `atris/wiki/` |
223
223
  | `atris loop` | Refresh wiki health, stale/orphan signals, and next ingest candidates |
@@ -238,7 +238,7 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
238
238
  - `atris loop` refreshes `atris/wiki/STATUS.md` and `atris/wiki/log.md`, flags stale/orphan pages, and suggests the next ingest
239
239
  - `atris activate` loads the current wiki status so the next session starts with project memory, not just tasks
240
240
  - `atris member` keeps team-member identity and learning local-first: `MEMBER.md` is the role contract, `goals.json` is the machine-readable goal/experiment state, `goals.md` is the human readout, and `logs/YYYY-MM-DD.md` records what happened. Use `atris member goal`, `tick`, `status`, `block`, and `review --value 1..5` to test whether a member is making useful progress or needs the operator/orchestrator.
241
- - `atris codex-goal` is the guarded bridge for native Codex `/goal`: `status` reads `~/.codex/state_5.sqlite`, while `reset --thread <id> --confirm-complete-goal-reset` backs up the DB, dumps the exact completed `thread_goals` row, deletes only that completed row, and writes a receipt. The next native goal must still be created by the live Codex thread; Atris Mission/member goals remain the durable loop state.
241
+ - `atris codex-goal` is the read-only bridge for native Codex `/goal`: `status` inspects the live goal database, while the legacy `reset` command now refuses every mutation. Active tasks continue in place; completed tasks retain their final goal, and new work or recurring monitors use a new dedicated Codex task.
242
242
  - `atris task` keeps durable local task state and append-only events for agents; `atris/TODO.md` is just a regenerated readable board. Run the loop with `atris task new`, `delegate "..." --to <owner>`, `next`, `say`, and `ready <id> --proof "..."`; human approval is `atris task accept <id>` (moves to Done, awards Career XP) or `revise <id> --note "..."`. Final task transitions also append the general daily log and, when a real `atris/team/<member>/MEMBER.md` matches the task owner, that member's daily log. Add `--json` for headless agents, `atris task serve` for the local board, and `atris task show <ref>` / `events --all` for the full ledger. Commands accept semantic refs (`OBL-18`), full IDs, or any unique prefix. In cloud business workspaces, Supabase `tasks` is the source of truth and Swarlo the live claim layer.
243
243
  - `atris mission` is the durable autonomy layer: start with an owner, verifier, runner, and stop condition; record bounded work with `mission tick`; close with `mission complete` only after proof. Runners include `manual`, `claude`, `atris2`, and `codex_goal`.
244
244
  - `atris pulse` is the OS-cron heartbeat for overnight self-improvement. Use `atris pulse status`, `tick`, `run`, `install --cadence "<cron>" --days 7 --verify "npm test" --model claude-opus-4-8`, and `uninstall`.
package/atris/AGENTS.md CHANGED
@@ -56,13 +56,14 @@ Human accept -> task Done + Career XP awarded
56
56
  ```
57
57
 
58
58
  Always-on agents should move proof-backed work to Review, complete their native
59
- goal, then continue the mission loop with the next goal. They must not run
59
+ goal, then stop that task. The next goal or recurring monitor starts in a new
60
+ dedicated task. They must not run
60
61
  `atris task accept` or claim Career XP unless a human approved the proof.
61
62
 
62
63
  Codex agents with native goal tools should run `atris mission goal --json`
63
64
  before choosing work. If the payload includes `goal.visible_goal`, mirror
64
- `goal.objective` into the visible chat goal when the current goal is empty,
65
- complete, or already matching; never complete unrelated active goals just to
65
+ `goal.objective` when this active task has no goal yet or its goal already
66
+ matches; never reuse a completed task or complete unrelated active goals just to
66
67
  make room.
67
68
 
68
69
  Default to the current checkout for small, clean, single-agent fixes. Use
package/atris/CLAUDE.md CHANGED
@@ -48,7 +48,7 @@ Agent proof ready -> native goal can complete
48
48
  Human accept -> task Done + AgentXP awarded
49
49
  ```
50
50
 
51
- Always-on agents should complete their native goal after proof is in Review, then continue the mission loop with the next goal. They must not run `atris task accept` or claim AgentXP unless a human approved the proof.
51
+ Always-on agents should complete their native goal after proof is in Review, then stop that task. The next goal or recurring monitor starts in a new dedicated task. They must not run `atris task accept` or claim AgentXP unless a human approved the proof.
52
52
 
53
53
  Do not write new operating doctrine here first. Add it to Atris policy, skills, wiki, or `atris/atris.md`, then regenerate this adapter if needed.
54
54
 
package/atris/atris.md CHANGED
@@ -96,7 +96,10 @@ Core loop:
96
96
 
97
97
  ```bash
98
98
  atris task list
99
- atris task delegate "<title>" --to <functional-member> --tag <tag>
99
+ atris task delegate "<title>" --to <functional-member> --tag <tag> \
100
+ --what-changes "<plain change>" \
101
+ --why-it-matters "<plain reason>" \
102
+ --done-looks-like "<observable finish>"
100
103
  atris task delegate "<title>" --to <functional-member> --executed-by <engine> --via swarlo --tag <tag>
101
104
  atris task day
102
105
  atris task next
@@ -108,6 +111,9 @@ atris task review <id> --lesson "<what improved>" --next "<next task>"
108
111
 
109
112
  `atris task ready` carries `--result`, the day-one PM sentence of what the human gained; the CLI refuses agent-speak.
110
113
 
114
+ One purpose lives in one task. Once a native Codex task is complete, keep its
115
+ final state intact; new work and recurring monitors start in a dedicated task.
116
+
111
117
  Headless agents should add `--json` where available and read
112
118
  `.atris/state/tasks.projection.json` for a compact board view.
113
119
 
@@ -122,6 +128,9 @@ task row/event stream remains the durable source of truth.
122
128
  Every task record should carry:
123
129
 
124
130
  ```
131
+ What changes: <plain sentence a new teammate understands>
132
+ Why it matters: <plain human or business reason>
133
+ Done looks like: <observable finish, without command syntax>
125
134
  Title: <small work packet>
126
135
  Owner: <functional or feature member, not an engine>
127
136
  Objective: <why this matters>
@@ -131,6 +140,17 @@ Verify: <shell command or concrete proof>
131
140
  Next: <suggested follow-up task>
132
141
  ```
133
142
 
143
+ The three plain fields are the default face on every task view. The exact
144
+ title, context, requirements, files, events, proof, and verifier stay beneath
145
+ them unchanged and inspectable. If a creator omits a plain field, the shared
146
+ task model derives an honest fallback from the existing task and says when no
147
+ reason was recorded; it never invents a benefit.
148
+
149
+ A planned task shows two clear actions: approve the complete plan through the
150
+ existing Plan -> Do gate, or ask for a change. Completed work shows approve
151
+ only when the existing proof and human-accept gates allow it, plus revise for
152
+ requested changes. The plain layer never bypasses a verifier or approval rule.
153
+
134
154
  Task planning preview and landing:
135
155
 
136
156
  ```
@@ -32,6 +32,9 @@ Key behaviors:
32
32
  - Read PERSONA.md (3-4 sentences, ASCII visuals)
33
33
  - Check MAP.md for file:line refs
34
34
  - Use `atris task` for claims, proof, ready, and accept
35
+ - Give every task a plain first layer: what changes, why it matters, and what
36
+ done looks like. Keep the exact task record beneath it and use the existing
37
+ approve/change gates without weakening proof or human acceptance.
35
38
  - Use functional/feature members as task owners; record engines only as `executed_by`
36
39
  - Treat TODO.md as a rendered view; regenerate it instead of hand-editing tasks
37
40
  - In Codex runtimes with native goal tools, mirror `atris mission goal --json` into the visible chat goal via `goal.visible_goal`
@@ -67,9 +70,10 @@ If it returns `action: codex_goal_candidate` or `codex_goal_heartbeat` with
67
70
 
68
71
  1. Call `get_goal` when the runtime exposes it.
69
72
  2. If the active goal already equals `goal.objective`, keep working.
70
- 3. If there is no active goal or the prior goal is complete, call `create_goal` with `goal.objective`.
71
- 4. If a different active goal is still in progress, do not complete it just to make room; report that `goal.visible_goal.status` is waiting for the visible goal slot.
72
- 5. After proof is ready, call `update_goal({ status: "complete" })`, rerun `atris mission goal --json`, then create the next visible goal candidate when a slot is available.
73
+ 3. If this active task has no goal yet, call `create_goal` with `goal.objective`.
74
+ 4. If this task's prior goal is complete, stop. Create a new dedicated Codex task for the next objective or recurring monitor; never reset or reuse the completed task.
75
+ 5. If a different active goal is still in progress, do not complete it just to make room; report that `goal.visible_goal.status` is waiting for the visible goal slot.
76
+ 6. After proof is ready, call `update_goal({ status: "complete" })` and stop this task. Read the next candidate only from its own new Codex task.
73
77
 
74
78
  Native goal completion is not task acceptance. Agents may complete their native
75
79
  goal after proof is ready; only a human should run `atris task accept`.
@@ -19,6 +19,7 @@ Schedules the recurring autopilot heartbeat. One tick fires roughly every 13–1
19
19
  2. The cron prompt first invokes `atris mission run --due --max-ticks 1 --complete-on-pass`; if no mission is due, it invokes `/autopilot`
20
20
  3. Returns the cron job id so the user can stop it later with `CronDelete`
21
21
  4. Lists the active cron jobs via `CronList` so the user can see the heartbeat is alive
22
+ 5. Runs from a dedicated active task for this loop; a completed task is never reused as the scheduler host
22
23
 
23
24
  ## How to invoke
24
25
 
@@ -26,13 +27,14 @@ User says "run /loop", "start the loop", "kick off autonomous mode", or "make au
26
27
 
27
28
  The agent then:
28
29
 
29
- 1. Calls `CronCreate` with these args (use whatever off-clock minute you land on, do not pin to :00 or :30):
30
+ 1. Checks that the current task is active and dedicated to this loop. If it is complete or belongs to other work, stop and create a new dedicated task before scheduling.
31
+ 2. Calls `CronCreate` with these args (use whatever off-clock minute you land on, do not pin to :00 or :30):
30
32
  - `cron`: `"*/13 * * * *"` (every 13 min) for tight loops, or `"7 * * * *"` (hourly at :07) for slow loops
31
33
  - `prompt`: `"First run: atris mission run --due --max-ticks 1 --complete-on-pass. If it reports no_due_mission, run /autopilot for one tick. One bounded goal only, then stop. Do not start a conversation."`
32
34
  - `recurring`: `true`
33
35
  - `durable`: `false` (in-memory only, gone when this Claude session ends)
34
- 2. After creating, calls `CronList` and shows the user the active cron jobs.
35
- 3. Tells the user: "loop is alive. job id <X>. fires roughly every <N> minutes. say 'stop the loop' to kill it. auto-expires after 7 days."
36
+ 3. After creating, calls `CronList` and shows the user the active cron jobs.
37
+ 4. Tells the user: "loop is alive. job id <X>. fires roughly every <N> minutes. say 'stop the loop' to kill it. auto-expires after 7 days."
36
38
 
37
39
  ## Stopping the loop
38
40
 
@@ -43,6 +45,7 @@ If the user says "kill all loops", call `CronList`, then `CronDelete` for every
43
45
  ## Rules
44
46
 
45
47
  - One tick at a time. Never schedule a cron that fires more than once per 10 min.
48
+ - One recurring monitor per dedicated active task. Never attach a new monitor to a completed task.
46
49
  - Always pick an off-clock minute (avoid :00 and :30) to prevent the global fleet from hammering the API at the same instant.
47
50
  - Use `durable: false` by default. Only use `durable: true` if the user explicitly says "make this survive restarts" or "persist this".
48
51
  - Auto-expires after 7 days. Tell the user.
package/atris.md CHANGED
@@ -139,7 +139,10 @@ Core loop:
139
139
 
140
140
  ```bash
141
141
  atris task list
142
- atris task delegate "<title>" --to <functional-member> --tag <tag>
142
+ atris task delegate "<title>" --to <functional-member> --tag <tag> \
143
+ --what-changes "<plain change>" \
144
+ --why-it-matters "<plain reason>" \
145
+ --done-looks-like "<observable finish>"
143
146
  atris task delegate "<title>" --to <functional-member> --executed-by <engine> --via swarlo --tag <tag>
144
147
  atris task day
145
148
  atris task next
@@ -163,6 +166,9 @@ task row/event stream remains the durable source of truth.
163
166
  Every task record should carry:
164
167
 
165
168
  ```
169
+ What changes: <plain sentence a new teammate understands>
170
+ Why it matters: <plain human or business reason>
171
+ Done looks like: <observable finish, without command syntax>
166
172
  Title: <small work packet>
167
173
  Owner: <functional or feature member, not an engine>
168
174
  Objective: <why this matters>
@@ -172,6 +178,17 @@ Verify: <shell command or concrete proof>
172
178
  Next: <suggested follow-up task>
173
179
  ```
174
180
 
181
+ The three plain fields are the default face on every task view. The exact
182
+ title, context, requirements, files, events, proof, and verifier stay beneath
183
+ them unchanged and inspectable. If a creator omits a plain field, the shared
184
+ task model derives an honest fallback from the existing task and says when no
185
+ reason was recorded; it never invents a benefit.
186
+
187
+ A planned task shows two clear actions: approve the complete plan through the
188
+ existing Plan -> Do gate, or ask for a change. Completed work shows approve
189
+ only when the existing proof and human-accept gates allow it, plus revise for
190
+ requested changes. The plain layer never bypasses a verifier or approval rule.
191
+
175
192
  Task planning preview and landing:
176
193
 
177
194
  ```
package/bin/atris.js CHANGED
@@ -565,6 +565,7 @@ function showHelp() {
565
565
  console.log(' drive - One self-driving tick: mission doctor -> auto-fix -> count disengagements');
566
566
  console.log(` autoland - Approve the policy once; ${require('../lib/autoland').certifiedWorkLandsPhrase(process.cwd())}, you keep irreversible calls`);
567
567
  console.log(' engine - engine registry, answer validation, dispatch flights, and live progress');
568
+ console.log(' ci - run github actions jobs locally with runs-on: atris');
568
569
  console.log(' router - inspect ax lane outcomes and promote gated reflex overrides');
569
570
  console.log(' sign - Co-author trailer on every commit in an atris workspace (on/off/status)');
570
571
  console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
@@ -2619,6 +2620,10 @@ if (command === 'init') {
2619
2620
  require('../commands/game').gameCommand(process.argv.slice(3))
2620
2621
  .then((status) => process.exit(status === 1 ? 1 : 0))
2621
2622
  .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
2623
+ } else if (command === 'ci') {
2624
+ Promise.resolve(require('../commands/ci').ciCommand(process.argv.slice(3)))
2625
+ .then((status) => process.exit(status || 0))
2626
+ .catch((err) => { console.error(String(err.message || err).replace(/\s+/g, ' ').toLowerCase()); process.exit(1); });
2622
2627
  } else if (command === 'github') {
2623
2628
  const status = require('../commands/github').githubCommand(process.argv.slice(3));
2624
2629
  process.exit(status);
@@ -9,6 +9,7 @@ const autoland = require('../lib/autoland');
9
9
  const { gateForHuman, plainLandingReason } = require('../lib/voice-gate');
10
10
  const { evaluateAutoAccept } = require('../lib/auto-accept-certified');
11
11
  const { sweepEngineAskReceipts } = require('../lib/engine-receipt-sweep');
12
+ const { validateRecentAskReceipts } = require('../lib/engine-validate');
12
13
  const { operatorReady, hasAgentJargon, explainResult } = autoland;
13
14
  const MISSION_AUTO_VERIFY_STATUSES = new Set(['planning', 'paused', 'ready']);
14
15
  const CLOSED_TASK_STATUSES = new Set(['done', 'archived']);
@@ -640,7 +641,7 @@ function pidAlive(pid) {
640
641
  }
641
642
  }
642
643
 
643
- function runTick(root, args) {
644
+ async function runTick(root, args) {
644
645
  const json = args.includes('--json');
645
646
  const policy = autoland.readPolicy(root);
646
647
  const receipt = { at: new Date().toISOString(), landed: [], alarms: 0, digest_due: false, digest_sent: false, enabled: Boolean(policy && policy.enabled) };
@@ -670,7 +671,7 @@ function runTick(root, args) {
670
671
  fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, at: Date.now() }));
671
672
  } catch {}
672
673
  try {
673
- return runTickBody(root, { json, policy, receipt });
674
+ return await runTickBody(root, { json, policy, receipt });
674
675
  } finally {
675
676
  try { fs.unlinkSync(lockPath); } catch {}
676
677
  }
@@ -720,7 +721,22 @@ function engineReceiptSweepNote(receipt) {
720
721
  return `, finalized ${finalized} stale engine receipt${finalized === 1 ? '' : 's'} as presumed dead`;
721
722
  }
722
723
 
723
- function runTickBody(root, { json, policy, receipt }) {
724
+ async function runEngineAnswerValidation(root, receipt, deps = {}) {
725
+ try {
726
+ receipt.engine_answer_validation = await validateRecentAskReceipts(root, deps);
727
+ } catch (err) {
728
+ receipt.engine_answer_validation_error = String((err && err.message) || err).slice(0, 200);
729
+ }
730
+ return receipt.engine_answer_validation || null;
731
+ }
732
+
733
+ function engineAnswerValidationLine(receipt) {
734
+ const graded = Number(receipt.engine_answer_validation?.graded_answers) || 0;
735
+ if (!graded) return '';
736
+ return `graded ${graded} engine answer${graded === 1 ? '' : 's'}, scoreboard updated`;
737
+ }
738
+
739
+ async function runTickBody(root, { json, policy, receipt, engineValidationDeps = {} }) {
724
740
 
725
741
  try {
726
742
  receipt.engine_receipt_sweep = sweepEngineAskReceipts(root);
@@ -728,6 +744,8 @@ function runTickBody(root, { json, policy, receipt }) {
728
744
  receipt.engine_receipt_sweep_error = String((err && err.message) || err).slice(0, 200);
729
745
  }
730
746
 
747
+ await runEngineAnswerValidation(root, receipt, engineValidationDeps);
748
+
731
749
  // 1. certify and land in one task process. Keeping both phases together lets
732
750
  // the landing gate reuse the live certification verifier result without
733
751
  // persisting trust across heartbeats. Denied lanes and check-less proofs wait.
@@ -1004,6 +1022,8 @@ function runTickBody(root, { json, policy, receipt }) {
1004
1022
  // gate and still did not land — that is the shape of a stuck loop.
1005
1023
  const stuckNote = stuckBacklogNote(receipt);
1006
1024
  const summary = `autoland tick: ${receipt.reviews_certified ?? 0} reviews certified, ${receipt.landed.length} landed${receipt.landed.length ? ` (${receipt.landed.join(', ')})` : ''}, ${receipt.alarms} alarms${stuckNote}, digest ${digestTickStatus(receipt)}${reapNote}${janitorNote}${heldNote}${wishNote}${engineSweepNote}${receiptNote}`;
1025
+ const validationLine = engineAnswerValidationLine(receipt);
1026
+ if (validationLine) console.log(validationLine);
1007
1027
  console.log(gateForHuman(summary).text);
1008
1028
  for (const fulfilled of receipt.wish_dispatch?.fulfilled_results || []) {
1009
1029
  if (fulfilled.review_ask) console.log(fulfilled.review_ask);
@@ -1064,10 +1084,12 @@ module.exports = {
1064
1084
  digestNextMoves,
1065
1085
  digestStoryRows,
1066
1086
  digestTickStatus,
1087
+ engineAnswerValidationLine,
1067
1088
  explainResult,
1068
1089
  operatorReady,
1069
1090
  readAcceptedTaskHistory,
1070
1091
  hasAgentJargon,
1092
+ runEngineAnswerValidation,
1071
1093
  runTickBody,
1072
1094
  sweepLanding,
1073
1095
  verifyClosedTaskMissions,
package/commands/brain.js CHANGED
@@ -5,6 +5,11 @@ const { spawnSync } = require('child_process');
5
5
  const { refreshNowFile } = require('./now');
6
6
  const escapeRegExp = require('../lib/escape-regexp');
7
7
  const { hasRenderedSections, isOpenSection, isDoneSection } = require('../lib/todo-sections');
8
+ const {
9
+ renderVoiceCardBlock,
10
+ upsertCursorVoiceCard,
11
+ voiceCardForRoot,
12
+ } = require('../lib/voice-card');
8
13
 
9
14
  const GENERATED_START = '<!-- ATRIS_BRAIN_COMPILE:START -->';
10
15
  const GENERATED_END = '<!-- ATRIS_BRAIN_COMPILE:END -->';
@@ -1424,7 +1429,7 @@ ${nextMove(state)}
1424
1429
  `;
1425
1430
  }
1426
1431
 
1427
- function generatedBootBlock(state) {
1432
+ function generatedBootBlock(state, voiceCard = voiceCardForRoot(state.root)) {
1428
1433
  return `${GENERATED_START}
1429
1434
  ## Atris Brain Compile
1430
1435
 
@@ -1441,6 +1446,9 @@ Purpose: optimize for decision-speed; lead with the move, then use descriptions
1441
1446
  Shape: \`<operator>, today is about <move>\` -> \`I picked this because <why now>\` -> \`Ready: <draft/proof/context>\` -> \`Go deeper: <paths>\`.
1442
1447
  Definitions: operator = current person or agent; move = one concrete high-leverage workflow; why now = business reason; ready = prepared action or proof; paths = 2-4 optional deeper views.
1443
1448
 
1449
+ Keep this voice beside every reply:
1450
+ ${renderVoiceCardBlock(voiceCard)}
1451
+
1444
1452
  Re-run after meaningful work:
1445
1453
  \`atris brain compile --root .\`
1446
1454
  ${GENERATED_END}
@@ -1475,10 +1483,13 @@ function writeBrain(state) {
1475
1483
  fs.writeFileSync(ledgerPath, renderLedger(state), 'utf8');
1476
1484
  fs.writeFileSync(jsonPath, JSON.stringify(state, null, 2) + '\n', 'utf8');
1477
1485
 
1478
- const bootBlock = generatedBootBlock(state);
1486
+ const voiceCard = voiceCardForRoot(state.root);
1487
+ const bootBlock = generatedBootBlock(state, voiceCard);
1479
1488
  for (const fileName of ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md']) {
1480
1489
  upsertGeneratedBlock(path.join(state.root, fileName), fileName.replace(/\.md$/, ''), bootBlock);
1481
1490
  }
1491
+ const cursorVoicePath = path.join(state.root, '.cursor', 'rules', 'atris-voice.mdc');
1492
+ upsertCursorVoiceCard(cursorVoicePath, voiceCard);
1482
1493
 
1483
1494
  const wikiStatusPath = path.join(state.root, 'atris', 'wiki', 'STATUS.md');
1484
1495
  if (fs.existsSync(wikiStatusPath)) {
@@ -1495,7 +1506,7 @@ function writeBrain(state) {
1495
1506
  `);
1496
1507
  }
1497
1508
 
1498
- return { statusPath, ledgerPath, jsonPath };
1509
+ return { statusPath, ledgerPath, jsonPath, cursorVoicePath };
1499
1510
  }
1500
1511
 
1501
1512
  function verifyBrain(root) {
package/commands/ci.js ADDED
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ formatUsageSummary,
5
+ parseRunnerArgs,
6
+ parseUsageArgs,
7
+ readUsageRecords,
8
+ runCiRunner,
9
+ summarizeUsage,
10
+ usageFilePath,
11
+ } = require('../lib/ci-runner');
12
+
13
+ function showCiHelp() {
14
+ console.log('atris ci runs github actions jobs on this machine with a warm local work folder.');
15
+ console.log('change runs-on: ubuntu-latest to runs-on: atris.');
16
+ console.log('a GITHUB_TOKEN or an authenticated gh cli is required.');
17
+ console.log('usage: atris ci runner --repo <owner/name> [--label <name>] [--once]');
18
+ console.log(' atris ci usage [--repo <owner/name>]');
19
+ }
20
+
21
+ async function ciCommand(argv, dependencies = {}) {
22
+ if (argv.length === 0 || argv[0] === 'help' || argv.includes('--help') || argv.includes('-h')) {
23
+ showCiHelp();
24
+ return 0;
25
+ }
26
+ if (argv[0] === 'usage') {
27
+ const options = parseUsageArgs(argv.slice(1));
28
+ const records = (dependencies.readUsageRecords || readUsageRecords)(
29
+ dependencies.usagePath || usageFilePath(),
30
+ dependencies.readFile,
31
+ );
32
+ const clock = dependencies.clock || (() => new Date());
33
+ const log = dependencies.log || console.log;
34
+ log(formatUsageSummary(summarizeUsage(records, { ...options, now: clock() })));
35
+ return 0;
36
+ }
37
+ if (argv[0] === 'runner') {
38
+ await runCiRunner(parseRunnerArgs(argv.slice(1)), dependencies);
39
+ return 0;
40
+ }
41
+ throw new Error(`unknown ci subcommand: ${argv[0]}`);
42
+ }
43
+
44
+ module.exports = { ciCommand, showCiHelp };