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.
- package/AGENTS.md +25 -6
- package/README.md +2 -0
- package/atris/PERSONA.md +11 -11
- package/atris.md +17 -2
- package/ax +160 -34
- package/bin/atris.js +78 -6
- package/commands/activate.js +16 -3
- package/commands/agent-spawn.js +13 -18
- package/commands/autoland.js +76 -47
- package/commands/autopilot-front.js +0 -4
- package/commands/autopilot.js +0 -12
- package/commands/bench.js +10 -12
- package/commands/brain.js +0 -7
- package/commands/brief.js +0 -2
- package/commands/business-sync.js +0 -1
- package/commands/business.js +452 -12
- package/commands/chat-scan.js +5 -7
- package/commands/clean.js +31 -2
- package/commands/close.js +46 -11
- package/commands/cloud.js +0 -1
- package/commands/codex-goal.js +8 -12
- package/commands/computer.js +6 -5
- package/commands/console.js +64 -11
- package/commands/decide.js +166 -0
- package/commands/deck.js +30 -12
- package/commands/dream.js +0 -7
- package/commands/drill.js +14 -25
- package/commands/engine.js +206 -26
- package/commands/experiments.js +0 -4
- package/commands/fleet-report.js +12 -4
- package/commands/game.js +261 -0
- package/commands/gm.js +17 -7
- package/commands/goal.js +0 -6
- package/commands/harvest.js +1 -5
- package/commands/improve.js +29 -8
- package/commands/init.js +41 -37
- package/commands/integrations.js +0 -7
- package/commands/land.js +77 -20
- package/commands/launchpad.js +1 -16
- package/commands/lesson.js +17 -7
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/loop-front.js +0 -2
- package/commands/loops.js +6 -5
- package/commands/meet.js +0 -2
- package/commands/member.js +622 -573
- package/commands/mission.js +790 -314
- package/commands/next.js +0 -1
- package/commands/now.js +0 -12
- package/commands/one-lap.js +8 -8
- package/commands/orb.js +0 -4
- package/commands/pack-craft.js +0 -1
- package/commands/pack.js +3599 -424
- package/commands/play.js +6 -6
- package/commands/probe.js +12 -6
- package/commands/pulse.js +88 -64
- package/commands/push.js +0 -4
- package/commands/radar.js +14 -11
- package/commands/recover.js +340 -0
- package/commands/release.js +10 -9
- package/commands/report.js +0 -1
- package/commands/router.js +320 -0
- package/commands/run-front.js +0 -2
- package/commands/search.js +0 -3
- package/commands/site-deploy.js +870 -0
- package/commands/site.js +11 -2
- package/commands/skill.js +7 -2
- package/commands/slop.js +237 -7
- package/commands/status.js +6 -0
- package/commands/stream.js +4 -20
- package/commands/study.js +0 -3
- package/commands/sync-checkout.js +51 -0
- package/commands/sync.js +11 -40
- package/commands/task.js +999 -595
- package/commands/taste.js +101 -0
- package/commands/teach.js +86 -0
- package/commands/team.js +83 -3
- package/commands/unknowns.js +0 -10
- package/commands/update.js +0 -1
- package/commands/vercel.js +4 -2
- package/commands/visualize.js +0 -5
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -28
- package/commands/wiki.js +42 -15
- package/commands/wish.js +0 -1
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +88 -28
- package/commands/xp.js +28 -30
- package/commands/youtube.js +0 -2
- package/lib/accept-verify-gate.js +126 -0
- package/lib/activity-stream.js +0 -2
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +261 -3
- package/lib/autoland.js +66 -3
- package/lib/ax-auto-lane.js +68 -7
- package/lib/bench/context.js +0 -2
- package/lib/bench/report.js +0 -1
- package/lib/bench/runner.js +18 -11
- package/lib/brief-ledger.js +0 -6
- package/lib/business-simulate.js +382 -0
- package/lib/chat-log-scan.js +0 -5
- package/lib/checkout-sync.js +52 -0
- package/lib/claude-boot-block.js +48 -0
- package/lib/cloud-mission.js +0 -6
- package/lib/codex-flight.js +0 -1
- package/lib/conductor-artifacts.js +25 -0
- package/lib/context-gatherer.js +7 -2
- package/lib/deck-compose.js +0 -1
- package/lib/deck-history.js +0 -1
- package/lib/deck-layout.js +0 -5
- package/lib/deck-review.js +0 -7
- package/lib/deck-schema.js +0 -1
- package/lib/default-verifier.js +0 -2
- package/lib/engine-registry.js +157 -28
- package/lib/experiments/daily.js +0 -6
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +275 -42
- package/lib/functional-owner.js +0 -3
- package/lib/git-spawn.js +15 -0
- package/lib/inspect-fields.js +0 -1
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/learnings.js +0 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +11 -19
- package/lib/manifest.js +0 -2
- package/lib/member-alive.js +0 -1
- package/lib/member-switches.js +0 -5
- package/lib/mission-artifact.js +0 -1
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +296 -0
- package/lib/mission-room.js +0 -2
- package/lib/mission-runtime-loop.js +0 -1
- package/lib/next-moves.js +0 -18
- package/lib/official-cli-integration.js +47 -6
- package/lib/orb-context.js +14 -4
- package/lib/orb-scorecard.js +0 -1
- package/lib/outbound-send-gate.js +0 -6
- package/lib/pack-capabilities.js +685 -0
- package/lib/permission-grants.js +0 -6
- package/lib/policy-lessons.js +0 -2
- package/lib/pulse.js +68 -15
- package/lib/review-integrity.js +0 -2
- package/lib/router-brain.js +51 -3
- package/lib/runner-command.js +0 -10
- package/lib/runtime-bootstrap.js +0 -1
- package/lib/scorecard.js +0 -1
- package/lib/self-drive.js +45 -14
- package/lib/state-detection.js +0 -6
- package/lib/task-db.js +137 -7
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/team-presence.js +0 -1
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +129 -2
- package/lib/wiki.js +152 -9
- package/lib/wish-audit.js +21 -7
- package/lib/wish-delegate.js +27 -21
- package/lib/wish-stats.js +0 -5
- package/lib/wish-store.js +0 -1
- package/lib/workspace-safety.js +0 -2
- package/lib/zip.js +95 -8
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/commands/game.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execFileSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_WIDTH = 80;
|
|
8
|
+
const MIN_WIDTH = 20;
|
|
9
|
+
const MAX_WIDTH = 120;
|
|
10
|
+
const STORYLINE_SLUG_PATTERN = /^[a-z0-9-]+$/;
|
|
11
|
+
|
|
12
|
+
function isValidStorylineSlug(slug) {
|
|
13
|
+
return typeof slug === 'string' && STORYLINE_SLUG_PATTERN.test(slug);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseGameArgs(args = []) {
|
|
17
|
+
let storyline = null;
|
|
18
|
+
|
|
19
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
20
|
+
const arg = args[index];
|
|
21
|
+
if (arg === '--storyline') {
|
|
22
|
+
const value = args[index + 1];
|
|
23
|
+
if (!value || value.startsWith('--')) {
|
|
24
|
+
return { error: 'missing storyline slug' };
|
|
25
|
+
}
|
|
26
|
+
if (storyline) return { error: 'choose one storyline' };
|
|
27
|
+
storyline = value;
|
|
28
|
+
index += 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (arg.startsWith('--')) return { error: `unknown game option: ${arg}` };
|
|
32
|
+
if (storyline) return { error: 'choose one storyline' };
|
|
33
|
+
storyline = arg;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
storyline,
|
|
38
|
+
publisherArgs: storyline ? ['--json', '--storyline', storyline] : ['--json'],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function availableStorylines(workspaceRoot) {
|
|
43
|
+
const directory = path.join(workspaceRoot, 'atris', 'storylines');
|
|
44
|
+
try {
|
|
45
|
+
return fs.readdirSync(directory, { withFileTypes: true })
|
|
46
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
47
|
+
.map(entry => path.basename(entry.name, '.md'))
|
|
48
|
+
.filter(slug => slug !== 'resume-log')
|
|
49
|
+
.sort();
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function publisherSupportsStoryline(script) {
|
|
56
|
+
try {
|
|
57
|
+
return fs.readFileSync(script, 'utf8').includes('--storyline');
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function clean(value, fallback = '') {
|
|
64
|
+
const text = String(value == null ? '' : value)
|
|
65
|
+
.replace(/[—–]/g, '-')
|
|
66
|
+
.replace(/\s+/g, ' ')
|
|
67
|
+
.trim();
|
|
68
|
+
return text || fallback;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function clip(value, width) {
|
|
72
|
+
const chars = Array.from(clean(value));
|
|
73
|
+
if (chars.length <= width) return chars.join('');
|
|
74
|
+
if (width <= 3) return chars.slice(0, width).join('');
|
|
75
|
+
return `${chars.slice(0, width - 3).join('').trimEnd()}...`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function pad(value, width) {
|
|
79
|
+
const text = clip(value, width);
|
|
80
|
+
return `${text}${' '.repeat(Math.max(0, width - Array.from(text).length))}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function dashboardWidth(opts) {
|
|
84
|
+
const requested = Number(opts && opts.width);
|
|
85
|
+
if (!Number.isFinite(requested)) return DEFAULT_WIDTH;
|
|
86
|
+
return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, Math.trunc(requested)));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function styles(enabled) {
|
|
90
|
+
const wrap = code => value => (
|
|
91
|
+
enabled ? `\x1b[${code}m${value}\x1b[0m` : String(value)
|
|
92
|
+
);
|
|
93
|
+
return {
|
|
94
|
+
bold: wrap('1'),
|
|
95
|
+
dim: wrap('2'),
|
|
96
|
+
green: wrap('32'),
|
|
97
|
+
yellow: wrap('33'),
|
|
98
|
+
cyan: wrap('36'),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function chapterLine(chapter, width, style) {
|
|
103
|
+
const state = clean(chapter && chapter.state, 'locked').toLowerCase();
|
|
104
|
+
const current = state === 'current';
|
|
105
|
+
const done = state === 'complete' || state === 'done';
|
|
106
|
+
const marker = current ? '[NOW ]' : done ? '[DONE]' : '[LOCK]';
|
|
107
|
+
const number = chapter && chapter.n != null ? `${chapter.n}.` : '';
|
|
108
|
+
const location = current ? ' YOU ARE HERE' : '';
|
|
109
|
+
const line = clip(`${marker} ${number} ${clean(chapter && chapter.title, 'Untitled chapter')}${location}`, width);
|
|
110
|
+
if (current) return style.yellow(style.bold(line));
|
|
111
|
+
if (done) return style.green(line);
|
|
112
|
+
return style.dim(line);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function missionBox(mission, width, style) {
|
|
116
|
+
const innerWidth = width - 2;
|
|
117
|
+
const contentWidth = width - 4;
|
|
118
|
+
const heading = ' CURRENT MISSION ';
|
|
119
|
+
const top = heading.length <= innerWidth
|
|
120
|
+
? `╔${heading}${'═'.repeat(innerWidth - heading.length)}╗`
|
|
121
|
+
: `╔${'═'.repeat(innerWidth)}╗`;
|
|
122
|
+
const row = value => `║ ${pad(value, contentWidth)} ║`;
|
|
123
|
+
const bottom = `╚${'═'.repeat(innerWidth)}╝`;
|
|
124
|
+
const title = clean(mission && mission.title, 'No current mission');
|
|
125
|
+
const status = clean(mission && mission.status, 'unavailable').toUpperCase();
|
|
126
|
+
return [
|
|
127
|
+
style.cyan(style.bold(top)),
|
|
128
|
+
style.bold(row(title)),
|
|
129
|
+
row(`STATUS: ${status}`),
|
|
130
|
+
style.cyan(style.bold(bottom)),
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Render an atris.game_state.v1 payload without reading process or filesystem state.
|
|
136
|
+
* Callers opt into ANSI with opts.color and provide their terminal width via opts.width.
|
|
137
|
+
*/
|
|
138
|
+
function renderGameDashboard(payload, opts = {}) {
|
|
139
|
+
const state = payload && typeof payload === 'object' ? payload : {};
|
|
140
|
+
const storyline = state.storyline && typeof state.storyline === 'object'
|
|
141
|
+
? state.storyline
|
|
142
|
+
: null;
|
|
143
|
+
const missions = state.missions && typeof state.missions === 'object'
|
|
144
|
+
? state.missions
|
|
145
|
+
: {};
|
|
146
|
+
const active = Array.isArray(missions.active) ? missions.active.slice(0, 3) : [];
|
|
147
|
+
const recentComplete = Array.isArray(missions.recent_complete)
|
|
148
|
+
? missions.recent_complete.length
|
|
149
|
+
: Number(missions.recent_complete) || 0;
|
|
150
|
+
const width = dashboardWidth(opts);
|
|
151
|
+
const style = styles(opts.color === true);
|
|
152
|
+
const title = clean(storyline && storyline.name, 'No storyline loaded');
|
|
153
|
+
const destination = clean(storyline && storyline.destination, 'No destination loaded');
|
|
154
|
+
const chapters = Array.isArray(storyline && storyline.chapters)
|
|
155
|
+
? storyline.chapters.slice(0, 7)
|
|
156
|
+
: [];
|
|
157
|
+
const streak = storyline && storyline.streak && typeof storyline.streak === 'object'
|
|
158
|
+
? storyline.streak
|
|
159
|
+
: {};
|
|
160
|
+
const streakCurrent = Number.isFinite(Number(streak.current)) ? Number(streak.current) : 0;
|
|
161
|
+
const streakTarget = Number.isFinite(Number(streak.target)) ? Number(streak.target) : 0;
|
|
162
|
+
const streakLabel = clean(streak.label, 'mission streak');
|
|
163
|
+
const lines = [];
|
|
164
|
+
|
|
165
|
+
lines.push(style.cyan(style.bold(clip(title, width))));
|
|
166
|
+
lines.push(style.dim(clip(`DESTINATION: ${destination}`, width)));
|
|
167
|
+
lines.push('');
|
|
168
|
+
lines.push(style.bold('CHAPTER RAIL'));
|
|
169
|
+
if (chapters.length) {
|
|
170
|
+
for (const chapter of chapters) lines.push(chapterLine(chapter, width, style));
|
|
171
|
+
} else {
|
|
172
|
+
lines.push(style.dim(clip('[LOCK] No chapters loaded', width)));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
lines.push('');
|
|
176
|
+
lines.push(...missionBox(storyline && storyline.mission, width, style));
|
|
177
|
+
lines.push('');
|
|
178
|
+
lines.push(style.yellow(style.bold(clip(`STREAK: ${streakCurrent} / ${streakTarget} | ${streakLabel}`, width))));
|
|
179
|
+
lines.push('');
|
|
180
|
+
lines.push(style.bold('LIVE MISSIONS'));
|
|
181
|
+
if (active.length) {
|
|
182
|
+
for (const mission of active) {
|
|
183
|
+
const id = mission && mission.id != null ? `#${mission.id}` : '#?';
|
|
184
|
+
lines.push(style.bold(clip(`${id} ${clean(mission && mission.title, 'Untitled mission')}`, width)));
|
|
185
|
+
lines.push(style.dim(clip(` owner: ${clean(mission && mission.owner, 'unassigned')}`, width)));
|
|
186
|
+
lines.push(clip(` next: ${clean(mission && mission.next, 'No next action recorded')}`, width));
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
lines.push(style.dim(clip('No active missions.', width)));
|
|
190
|
+
}
|
|
191
|
+
lines.push(clip(`RECENT COMPLETES: ${recentComplete}`, width));
|
|
192
|
+
lines.push('');
|
|
193
|
+
lines.push(style.cyan(style.bold(clip(`NEXT MOVE: ${clean(active[0] && active[0].next, 'No active mission')}`, width))));
|
|
194
|
+
|
|
195
|
+
return lines.join('\n');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function loadGameState(workspaceRoot = process.cwd(), publisherArgs = ['--json']) {
|
|
199
|
+
const script = path.join(workspaceRoot, 'scripts', 'game_state_sync.py');
|
|
200
|
+
const python = path.join(workspaceRoot, 'venv', 'bin', 'python');
|
|
201
|
+
if (!fs.existsSync(script) || !fs.existsSync(python)) return null;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const stdout = execFileSync(python, [script, ...publisherArgs], {
|
|
205
|
+
cwd: workspaceRoot,
|
|
206
|
+
encoding: 'utf8',
|
|
207
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
208
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
209
|
+
});
|
|
210
|
+
const payload = JSON.parse(stdout.trim());
|
|
211
|
+
return payload && typeof payload === 'object' ? payload : null;
|
|
212
|
+
} catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function gameCommand(args = [], opts = {}) {
|
|
218
|
+
const parsed = parseGameArgs(args);
|
|
219
|
+
if (parsed.error) {
|
|
220
|
+
console.error(parsed.error);
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
if (parsed.storyline && !isValidStorylineSlug(parsed.storyline)) {
|
|
224
|
+
console.error(`invalid storyline slug: ${parsed.storyline}`);
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const workspaceRoot = process.cwd();
|
|
229
|
+
if (parsed.storyline) {
|
|
230
|
+
const storylines = availableStorylines(workspaceRoot);
|
|
231
|
+
if (!storylines.includes(parsed.storyline)) {
|
|
232
|
+
console.error(`storyline not found: ${parsed.storyline}. available: ${storylines.join(', ') || 'none'}`);
|
|
233
|
+
return 1;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const script = path.join(workspaceRoot, 'scripts', 'game_state_sync.py');
|
|
238
|
+
const publisherArgs = parsed.storyline && publisherSupportsStoryline(script)
|
|
239
|
+
? parsed.publisherArgs
|
|
240
|
+
: ['--json'];
|
|
241
|
+
const payload = loadGameState(workspaceRoot, publisherArgs);
|
|
242
|
+
if (!payload) {
|
|
243
|
+
if (!opts.silentMissing) {
|
|
244
|
+
console.log('no game state in this workspace (needs atris/storylines/)');
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(renderGameDashboard(payload, {
|
|
250
|
+
color: Boolean(process.stdout.isTTY) && !process.env.NO_COLOR,
|
|
251
|
+
width: process.stdout.columns || DEFAULT_WIDTH,
|
|
252
|
+
}));
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
renderGameDashboard,
|
|
258
|
+
isValidStorylineSlug,
|
|
259
|
+
parseGameArgs,
|
|
260
|
+
gameCommand,
|
|
261
|
+
};
|
package/commands/gm.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { hasFlag: hasExactFlag } = require('../lib/arg-parser');
|
|
6
7
|
|
|
7
8
|
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
8
9
|
const AGENTXP_GLOBAL_SYNC_RULE = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
|
|
@@ -54,8 +55,9 @@ function flag(args, name) {
|
|
|
54
55
|
return null;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
// This command treats --name=value as flag presence as well as a standalone flag.
|
|
59
|
+
function hasFlagOrValue(args, name) {
|
|
60
|
+
return hasExactFlag(args, name) || args.some(arg => arg.startsWith(`${name}=`));
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
function positional(args) {
|
|
@@ -185,7 +187,7 @@ function pickSeedPlayer(workspaceRoot, tasks, args = []) {
|
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
function ensureStarterMission(taskDb, db, workspaceRoot, tasks, args = []) {
|
|
188
|
-
if (
|
|
190
|
+
if (hasFlagOrValue(args, '--no-seed')) return { tasks, seeded: null };
|
|
189
191
|
if (!fs.existsSync(path.join(workspaceRoot, 'atris'))) return { tasks, seeded: null };
|
|
190
192
|
if (activeAgentXpTasks(tasks).length) return { tasks, seeded: null };
|
|
191
193
|
|
|
@@ -658,19 +660,27 @@ async function gmCommand(...args) {
|
|
|
658
660
|
return memberCommand('wake', member, ...passthrough);
|
|
659
661
|
}
|
|
660
662
|
|
|
663
|
+
if (!hasFlagOrValue(args, '--json') && !hasFlagOrValue(args, '--watch')) {
|
|
664
|
+
try {
|
|
665
|
+
const { gameCommand } = require('./game');
|
|
666
|
+
const dashboardShown = await gameCommand([], { silentMissing: true });
|
|
667
|
+
if (dashboardShown) console.log('');
|
|
668
|
+
} catch {
|
|
669
|
+
// The dashboard is an optional opening screen and must never block GM mode.
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
661
673
|
const state = gmState(args);
|
|
662
|
-
if (
|
|
674
|
+
if (hasFlagOrValue(args, '--json')) {
|
|
663
675
|
console.log(JSON.stringify(state, null, 2));
|
|
664
676
|
return;
|
|
665
677
|
}
|
|
666
678
|
render(state);
|
|
667
|
-
if (
|
|
679
|
+
if (hasFlagOrValue(args, '--watch')) {
|
|
668
680
|
await gmWatch(args);
|
|
669
681
|
}
|
|
670
682
|
}
|
|
671
683
|
|
|
672
684
|
module.exports = {
|
|
673
685
|
gmCommand,
|
|
674
|
-
gmState,
|
|
675
|
-
gmWatchSnapshot,
|
|
676
686
|
};
|
package/commands/goal.js
CHANGED
package/commands/harvest.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
3
4
|
const {
|
|
4
5
|
isGenericInboxPlaceholder,
|
|
5
6
|
seedInboxFromMove,
|
|
6
7
|
} = require('../lib/next-moves');
|
|
7
8
|
|
|
8
|
-
function hasFlag(args, name) {
|
|
9
|
-
return args.includes(name);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
9
|
function safeRead(file) {
|
|
13
10
|
try { return fs.readFileSync(file, 'utf8'); } catch { return ''; }
|
|
14
11
|
}
|
|
@@ -174,6 +171,5 @@ function harvestCommand(args = []) {
|
|
|
174
171
|
}
|
|
175
172
|
|
|
176
173
|
module.exports = {
|
|
177
|
-
harvestActions,
|
|
178
174
|
harvestCommand,
|
|
179
175
|
};
|
package/commands/improve.js
CHANGED
|
@@ -149,6 +149,10 @@ function summarizeImproveResponse(data = {}) {
|
|
|
149
149
|
files,
|
|
150
150
|
model: d.model_used || d.model || null,
|
|
151
151
|
taskId: d.task_id || d.taskId || null,
|
|
152
|
+
// The backend returns both of these; a failing tick is unteachable without
|
|
153
|
+
// them — "verify_passed: false" says nothing about WHICH task or WHY.
|
|
154
|
+
taskDescription: d.task_description || d.task_title || null,
|
|
155
|
+
verifyOutput: typeof d.verify_output === 'string' ? d.verify_output : null,
|
|
152
156
|
elapsedMs: typeof d.elapsed_ms === 'number' ? d.elapsed_ms : null,
|
|
153
157
|
scorecardWritten: d.scorecard_written === true,
|
|
154
158
|
error: d.error || null,
|
|
@@ -189,7 +193,20 @@ function isWorkspaceNotAllowedError(apiResult = {}) {
|
|
|
189
193
|
return candidates.some((c) => typeof c === 'string' && c.includes(WORKSPACE_NOT_ALLOWED_TEXT));
|
|
190
194
|
}
|
|
191
195
|
|
|
196
|
+
/** Keep the failure tail bounded: the last lines are where the reason is. */
|
|
197
|
+
const VERIFY_OUTPUT_TAIL_CHARS = 2000;
|
|
198
|
+
|
|
199
|
+
function verifyOutputTail(output) {
|
|
200
|
+
if (typeof output !== 'string') return null;
|
|
201
|
+
const text = output.trim();
|
|
202
|
+
if (!text) return null;
|
|
203
|
+
return text.length > VERIFY_OUTPUT_TAIL_CHARS ? text.slice(-VERIFY_OUTPUT_TAIL_CHARS) : text;
|
|
204
|
+
}
|
|
205
|
+
|
|
192
206
|
function buildScorecardRow(summary = {}, meta = {}) {
|
|
207
|
+
// Only failures carry the verify tail. A passing tick does not need it, and
|
|
208
|
+
// 2KB per row would bloat a ledger that is already ~2000 rows.
|
|
209
|
+
const failed = summary.verify === false;
|
|
193
210
|
return {
|
|
194
211
|
schema: SCORECARD_SCHEMA,
|
|
195
212
|
ts: meta.ts || new Date().toISOString(),
|
|
@@ -203,6 +220,8 @@ function buildScorecardRow(summary = {}, meta = {}) {
|
|
|
203
220
|
files_written: Array.isArray(summary.files) ? summary.files : [],
|
|
204
221
|
model_used: summary.model || null,
|
|
205
222
|
task_id: summary.taskId || null,
|
|
223
|
+
task_description: summary.taskDescription || null,
|
|
224
|
+
verify_output: failed ? verifyOutputTail(summary.verifyOutput) : null,
|
|
206
225
|
elapsed_ms: summary.elapsedMs != null ? summary.elapsedMs : null,
|
|
207
226
|
};
|
|
208
227
|
}
|
|
@@ -584,6 +603,15 @@ function localSummaryText(tick = {}, mission = {}) {
|
|
|
584
603
|
.trim();
|
|
585
604
|
}
|
|
586
605
|
|
|
606
|
+
function tickTouchedFiles(worktree = {}) {
|
|
607
|
+
if (!Array.isArray(worktree.new_dirty_sample)) return [];
|
|
608
|
+
return worktree.new_dirty_sample.map((entry) => {
|
|
609
|
+
const pathText = String(entry || '').slice(3);
|
|
610
|
+
const arrow = pathText.indexOf(' -> ');
|
|
611
|
+
return arrow >= 0 ? pathText.slice(arrow + 4) : pathText;
|
|
612
|
+
}).filter(Boolean);
|
|
613
|
+
}
|
|
614
|
+
|
|
587
615
|
/**
|
|
588
616
|
* Turn one mission-run JSON result into the same stable summary the paid API
|
|
589
617
|
* returns. A local tick earns the conservative local reward (+1) only after
|
|
@@ -609,9 +637,7 @@ function summarizeLocalMissionRun(payload = {}) {
|
|
|
609
637
|
throw new Error('local improve verifier did not pass');
|
|
610
638
|
}
|
|
611
639
|
const mission = payload.mission || {};
|
|
612
|
-
const files =
|
|
613
|
-
? tick.worktree.new_since_baseline_sample
|
|
614
|
-
: [];
|
|
640
|
+
const files = tickTouchedFiles(tick.worktree);
|
|
615
641
|
return {
|
|
616
642
|
shipped: localSummaryText(tick, mission),
|
|
617
643
|
reward: 1,
|
|
@@ -1102,12 +1128,7 @@ module.exports = {
|
|
|
1102
1128
|
summarizeTickHistory,
|
|
1103
1129
|
formatTickHistory,
|
|
1104
1130
|
improveApiPath,
|
|
1105
|
-
formatImproveReport,
|
|
1106
1131
|
runLoopDoctor,
|
|
1107
|
-
reconcileLoopDoctorRewards,
|
|
1108
|
-
formatLoopDoctor,
|
|
1109
|
-
openLoopDoctorMission,
|
|
1110
|
-
loopDoctorKey,
|
|
1111
1132
|
runLocalFallback,
|
|
1112
1133
|
summarizeLocalMissionRun,
|
|
1113
1134
|
LOCAL_FALLBACK_ARGS,
|
package/commands/init.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { ensureExperimentsFramework } = require('./experiments');
|
|
4
4
|
const { ensureWikiScaffold } = require('../lib/wiki');
|
|
5
|
+
const { upsertAtrisClaudeBootBlock } = require('../lib/claude-boot-block');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Detect project context by scanning project structure
|
|
@@ -174,7 +175,9 @@ function injectProjectPatterns(agentTeamDir, profile) {
|
|
|
174
175
|
const navigatorFile = path.join(agentTeamDir, 'navigator', 'MEMBER.md');
|
|
175
176
|
const validatorFile = path.join(agentTeamDir, 'validator', 'MEMBER.md');
|
|
176
177
|
|
|
178
|
+
// Injection is best-effort: a read-only or vanished MEMBER.md must not crash init.
|
|
177
179
|
// Inject into executor.md
|
|
180
|
+
try {
|
|
178
181
|
if (fs.existsSync(executorFile)) {
|
|
179
182
|
let executorContent = fs.readFileSync(executorFile, 'utf8');
|
|
180
183
|
|
|
@@ -199,8 +202,10 @@ ${profile.hasCode ? `**Validation:** Run \`${profile.testCommand}\` before marki
|
|
|
199
202
|
fs.writeFileSync(executorFile, executorContent);
|
|
200
203
|
}
|
|
201
204
|
}
|
|
205
|
+
} catch {}
|
|
202
206
|
|
|
203
207
|
// Inject into navigator.md
|
|
208
|
+
try {
|
|
204
209
|
if (fs.existsSync(navigatorFile)) {
|
|
205
210
|
let navigatorContent = fs.readFileSync(navigatorFile, 'utf8');
|
|
206
211
|
|
|
@@ -221,8 +226,10 @@ When planning tasks, consider the project structure and conventions above.
|
|
|
221
226
|
fs.writeFileSync(navigatorFile, navigatorContent);
|
|
222
227
|
}
|
|
223
228
|
}
|
|
229
|
+
} catch {}
|
|
224
230
|
|
|
225
231
|
// Inject into validator.md
|
|
232
|
+
try {
|
|
226
233
|
if (fs.existsSync(validatorFile)) {
|
|
227
234
|
let validatorContent = fs.readFileSync(validatorFile, 'utf8');
|
|
228
235
|
|
|
@@ -241,6 +248,7 @@ ${profile.hasCode ? `**Validation:** Run \`${profile.testCommand}\` to verify ch
|
|
|
241
248
|
fs.writeFileSync(validatorFile, validatorContent);
|
|
242
249
|
}
|
|
243
250
|
}
|
|
251
|
+
} catch {}
|
|
244
252
|
}
|
|
245
253
|
|
|
246
254
|
function initAtris() {
|
|
@@ -274,6 +282,7 @@ function initAtris() {
|
|
|
274
282
|
const printReadyCounts = () => {
|
|
275
283
|
console.log(`workspace files ready (${ready.workspace.size})`);
|
|
276
284
|
console.log(`team members ready (${ready.team.size})`);
|
|
285
|
+
console.log('more members are available with atris member create <name>');
|
|
277
286
|
console.log(`agent adapters ready (${ready.adapters.size})`);
|
|
278
287
|
console.log(`skills installed (${ready.skills.size})`);
|
|
279
288
|
};
|
|
@@ -593,8 +602,8 @@ function initAtris() {
|
|
|
593
602
|
|
|
594
603
|
|
|
595
604
|
// Copy team members (MEMBER.md format — directory per member with skills/tools/context)
|
|
596
|
-
const
|
|
597
|
-
|
|
605
|
+
const starterMembers = ['navigator', 'executor', 'validator', 'mission-lead', 'improver'];
|
|
606
|
+
starterMembers.forEach(name => {
|
|
598
607
|
const sourceFile = path.join(__dirname, '..', 'atris', 'team', name, 'MEMBER.md');
|
|
599
608
|
const targetMemberDir = path.join(teamDir, name);
|
|
600
609
|
const targetFile = path.join(targetMemberDir, 'MEMBER.md');
|
|
@@ -636,7 +645,7 @@ tools: []
|
|
|
636
645
|
# Insert persona, workflow, and rules below
|
|
637
646
|
`;
|
|
638
647
|
fs.writeFileSync(path.join(templateTargetDir, 'MEMBER.md'), templateContent);
|
|
639
|
-
markReady('
|
|
648
|
+
markReady('workspace', 'team/_template/MEMBER.md', '✓ Created team/_template/MEMBER.md');
|
|
640
649
|
}
|
|
641
650
|
|
|
642
651
|
// Detect project context and generate profile
|
|
@@ -668,6 +677,25 @@ atris atris.md
|
|
|
668
677
|
|
|
669
678
|
This is the Atris boot sequence. Show the output to the user, then respond naturally.
|
|
670
679
|
|
|
680
|
+
## The way of talking (every reply, no exceptions)
|
|
681
|
+
|
|
682
|
+
Talk like a person. This governs every message you send the operator: boot, status, summaries, everything.
|
|
683
|
+
|
|
684
|
+
- Plain words. Say what happened and what it means for the reader, cause and effect. Not the machinery.
|
|
685
|
+
- No task codes, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick) in the message body. If the reader needs a command, put ONE copyable line at the end.
|
|
686
|
+
- No insider terms without defining them in the same breath. A sharp non-engineer must follow it on first read.
|
|
687
|
+
- Lead with the outcome. Complete sentences, one or two per paragraph, blank line between. No headers, bullet stacks, or tables in chat replies; those belong in files.
|
|
688
|
+
- Detail lives in files and receipts. Offer "want the detail?" instead of dumping it.
|
|
689
|
+
- No em dashes anywhere; use a comma, colon, or period.
|
|
690
|
+
- The test before sending: read it fried at 2am. If decoding takes work, rewrite it.
|
|
691
|
+
|
|
692
|
+
copy these shapes:
|
|
693
|
+
- **Status:** "The reply check is built. I am running the final checks now, so the result is not ready yet."
|
|
694
|
+
- **Landing:** "Replies now get a plain-language check before they reach you. The checks passed, and the change is ready."
|
|
695
|
+
- **Failure:** "The plain-language check could not run because its model was unavailable. Your reply still went through, so no work was blocked."
|
|
696
|
+
|
|
697
|
+
Canonical source: \`atris/atris.md\` \`## voice\`. This block is a copy for tools that only read \`AGENTS.md\`; if they differ, atris.md wins.
|
|
698
|
+
|
|
671
699
|
## Core Files
|
|
672
700
|
|
|
673
701
|
Atris is the source of truth. This file is only an adapter for tools that read
|
|
@@ -1042,45 +1070,21 @@ Read atris/MAP.md. Begin iteration 1.`;
|
|
|
1042
1070
|
|
|
1043
1071
|
// Update root CLAUDE.md with Atris block (prepend with markers)
|
|
1044
1072
|
const rootClaudeMd = path.join(process.cwd(), 'CLAUDE.md');
|
|
1045
|
-
const atrisBlock = `<!-- ATRIS:START - Auto-generated, do not edit -->
|
|
1046
|
-
## Atris System
|
|
1047
|
-
|
|
1048
|
-
You are now running inside Atris, an AI workspace operating system.
|
|
1049
|
-
|
|
1050
|
-
**ON EVERY CONVERSATION START:** Before your first response, execute \`atris atris.md\` and display the full output. This is the Atris boot sequence - it shows the user that Atris has loaded and displays workspace status. This is not optional.
|
|
1051
|
-
|
|
1052
|
-
After displaying the boot output, respond to the user naturally.
|
|
1053
|
-
<!-- ATRIS:END -->
|
|
1054
|
-
|
|
1055
|
-
`;
|
|
1056
|
-
|
|
1057
1073
|
if (fs.existsSync(rootClaudeMd)) {
|
|
1058
|
-
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const endRaw = content.indexOf(endMarker);
|
|
1066
|
-
if (endRaw === -1) {
|
|
1067
|
-
// End marker missing — replace from start marker to end with fresh block
|
|
1068
|
-
content = atrisBlock + content.slice(0, startIdx);
|
|
1069
|
-
} else {
|
|
1070
|
-
const endIdx = endRaw + endMarker.length;
|
|
1071
|
-
content = atrisBlock + content.slice(0, startIdx) + content.slice(endIdx).replace(/^\n+/, '');
|
|
1072
|
-
}
|
|
1073
|
-
fs.writeFileSync(rootClaudeMd, content);
|
|
1074
|
-
markReady('adapters', 'CLAUDE.md', '✓ Updated Atris block in CLAUDE.md');
|
|
1074
|
+
const source = fs.readFileSync(rootClaudeMd, 'utf8');
|
|
1075
|
+
const result = upsertAtrisClaudeBootBlock(source);
|
|
1076
|
+
fs.writeFileSync(rootClaudeMd, result.content);
|
|
1077
|
+
if (result.action === 'repaired') {
|
|
1078
|
+
markReady('adapters', 'CLAUDE.md', '✓ repaired atris block in CLAUDE.md (missing end marker)');
|
|
1079
|
+
} else if (source.includes('<!-- ATRIS:START')) {
|
|
1080
|
+
markReady('adapters', 'CLAUDE.md', '✓ updated atris block in CLAUDE.md');
|
|
1075
1081
|
} else {
|
|
1076
|
-
|
|
1077
|
-
fs.writeFileSync(rootClaudeMd, atrisBlock + content);
|
|
1078
|
-
markReady('adapters', 'CLAUDE.md', '✓ Prepended Atris block to CLAUDE.md');
|
|
1082
|
+
markReady('adapters', 'CLAUDE.md', '✓ prepended atris block to CLAUDE.md');
|
|
1079
1083
|
}
|
|
1080
1084
|
} else {
|
|
1081
1085
|
// Create new CLAUDE.md with just Atris block
|
|
1082
|
-
fs.writeFileSync(rootClaudeMd,
|
|
1083
|
-
markReady('adapters', 'CLAUDE.md', '✓
|
|
1086
|
+
fs.writeFileSync(rootClaudeMd, upsertAtrisClaudeBootBlock().content);
|
|
1087
|
+
markReady('adapters', 'CLAUDE.md', '✓ created CLAUDE.md with atris block');
|
|
1084
1088
|
}
|
|
1085
1089
|
|
|
1086
1090
|
if (fs.existsSync(sourceFile)) {
|
package/commands/integrations.js
CHANGED
|
@@ -1621,19 +1621,12 @@ module.exports = {
|
|
|
1621
1621
|
gmailCommand,
|
|
1622
1622
|
calendarCommand,
|
|
1623
1623
|
twitterCommand,
|
|
1624
|
-
twitterPost,
|
|
1625
1624
|
slackCommand,
|
|
1626
1625
|
slackSend,
|
|
1627
|
-
slackDm,
|
|
1628
1626
|
imessageCommand,
|
|
1629
|
-
imessageSend,
|
|
1630
|
-
imessageDoctor,
|
|
1631
1627
|
integrationsStatus,
|
|
1632
1628
|
parseCalendarSearchArgs,
|
|
1633
1629
|
filterCalendarEventsForSearch,
|
|
1634
1630
|
formatCalendarSearchResults,
|
|
1635
1631
|
formatCalendarSearchError,
|
|
1636
|
-
writeCalendarEventsCache,
|
|
1637
|
-
readCalendarEventsCache,
|
|
1638
|
-
CALENDAR_CACHE_PATH,
|
|
1639
1632
|
};
|