atris 3.44.0 → 3.46.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 +19 -3
- package/FOR_AGENTS.md +5 -0
- package/README.md +2 -2
- package/atris/AGENTS.md +4 -3
- package/atris/CLAUDE.md +1 -1
- package/atris/atris.md +21 -1
- package/atris/skills/atris/SKILL.md +7 -3
- package/atris/skills/loop/SKILL.md +6 -3
- package/atris.md +18 -1
- package/bin/atris.js +5 -0
- package/commands/aeo.js +5 -2
- package/commands/align.js +5 -2
- package/commands/autoland.js +25 -3
- package/commands/brain.js +14 -3
- package/commands/ci.js +44 -0
- package/commands/codex-goal.js +21 -119
- package/commands/computer.js +5 -2
- package/commands/improve.js +29 -6
- package/commands/init.js +33 -10
- package/commands/mission.js +116 -53
- package/commands/pack.js +90 -25
- package/commands/pull.js +5 -2
- package/commands/push.js +5 -2
- package/commands/sync.js +34 -6
- package/commands/task.js +239 -31
- package/commands/terminal.js +5 -2
- package/commands/voice.js +12 -2
- package/commands/workflow.js +10 -3
- package/lib/ci-runner.js +397 -0
- package/lib/dispatch-scout.js +4 -1
- package/lib/engine-validate.js +151 -3
- package/lib/known-commands.js +1 -1
- package/lib/task-db.js +22 -3
- package/lib/task-explanation.js +229 -0
- package/lib/todo-fallback.js +6 -0
- package/lib/voice-card.js +258 -0
- package/package.json +1 -1
- package/templates/research-canonical/atris.md +6 -0
- package/utils/auth.js +56 -9
package/commands/codex-goal.js
CHANGED
|
@@ -5,7 +5,6 @@ const { spawnSync } = require('child_process');
|
|
|
5
5
|
const { hasFlag } = require('../lib/arg-parser');
|
|
6
6
|
|
|
7
7
|
const SCHEMA = 'atris.codex_goal.v1';
|
|
8
|
-
const CONFIRM_RESET_FLAG = '--confirm-complete-goal-reset';
|
|
9
8
|
|
|
10
9
|
// Preserve the existing rule that the next token is a value, even if it is a flag.
|
|
11
10
|
function readFollowingFlag(args, name, fallback = '') {
|
|
@@ -21,10 +20,6 @@ function expandHome(filePath) {
|
|
|
21
20
|
return filePath;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
function safeStamp(value = new Date().toISOString()) {
|
|
25
|
-
return value.replace(/[:.]/g, '-');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
23
|
function sqlString(value) {
|
|
29
24
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
30
25
|
}
|
|
@@ -76,27 +71,6 @@ function runGoalQuery(args, buildSql) {
|
|
|
76
71
|
return runSqliteJson(ctx.goalsDb, ctx.prefix + buildSql(ctx.threadsTable));
|
|
77
72
|
}
|
|
78
73
|
|
|
79
|
-
function defaultRunsDir(args = []) {
|
|
80
|
-
return path.resolve(readFollowingFlag(args, '--out-dir', path.join(process.cwd(), '.atris', 'runs')));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function ensurePrivateDir(dir) {
|
|
84
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
85
|
-
try {
|
|
86
|
-
fs.chmodSync(dir, 0o700);
|
|
87
|
-
} catch {
|
|
88
|
-
// Best effort: some filesystems do not support POSIX permissions.
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function chmodPrivate(filePath) {
|
|
93
|
-
try {
|
|
94
|
-
fs.chmodSync(filePath, 0o600);
|
|
95
|
-
} catch {
|
|
96
|
-
// Best effort: some filesystems do not support POSIX permissions.
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
74
|
function runSqliteOnce(dbPath, sql, readonly) {
|
|
101
75
|
const sqliteArgs = [];
|
|
102
76
|
if (readonly) sqliteArgs.push('-readonly');
|
|
@@ -202,25 +176,6 @@ function resolveThreadGoal(args) {
|
|
|
202
176
|
return null;
|
|
203
177
|
}
|
|
204
178
|
|
|
205
|
-
function writeReceipt(outDir, payload) {
|
|
206
|
-
ensurePrivateDir(outDir);
|
|
207
|
-
const receiptPath = path.join(outDir, `codex-goal-${payload.action}-${safeStamp(payload.finished_at || payload.started_at)}.json`);
|
|
208
|
-
const withPath = { ...payload, receipt_path: receiptPath };
|
|
209
|
-
fs.writeFileSync(receiptPath, `${JSON.stringify(withPath, null, 2)}\n`, 'utf8');
|
|
210
|
-
chmodPrivate(receiptPath);
|
|
211
|
-
return withPath;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function backupSqliteDb(dbPath, backupPath) {
|
|
215
|
-
const result = spawnSync('sqlite3', [dbPath, `VACUUM INTO ${sqlString(backupPath)};`], { encoding: 'utf8' });
|
|
216
|
-
if (result.error) throw new Error(`sqlite3 backup failed: ${result.error.message}`);
|
|
217
|
-
if (result.status !== 0) {
|
|
218
|
-
const detail = (result.stderr || result.stdout || '').trim();
|
|
219
|
-
throw new Error(detail || `sqlite3 backup exited with status ${result.status}`);
|
|
220
|
-
}
|
|
221
|
-
chmodPrivate(backupPath);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
179
|
function printJsonOrText(payload, lines, asJson) {
|
|
225
180
|
if (asJson) {
|
|
226
181
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -252,92 +207,41 @@ function statusCommand(args) {
|
|
|
252
207
|
printJsonOrText(payload, [
|
|
253
208
|
`Codex goals: ${goals.length} recent`,
|
|
254
209
|
...goals.map((row) => `- ${row.status} ${row.thread_id}: ${row.objective}`),
|
|
255
|
-
'
|
|
210
|
+
'Completed tasks stay closed. Start new work in a new Codex task.',
|
|
256
211
|
], asJson);
|
|
257
212
|
}
|
|
258
213
|
|
|
259
214
|
function resetCommand(args) {
|
|
260
215
|
const asJson = hasFlag(args, '--json');
|
|
261
216
|
const dbPath = resolveStatePath(args);
|
|
262
|
-
const outDir = defaultRunsDir(args);
|
|
263
217
|
ensureStateDb(dbPath);
|
|
264
218
|
|
|
265
|
-
const startedAt = new Date().toISOString();
|
|
266
219
|
const goal = resolveThreadGoal(args);
|
|
267
220
|
if (!goal) {
|
|
268
221
|
throw new Error('No Codex goal found. Pass --thread <thread-id> or --latest.');
|
|
269
222
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
schema: SCHEMA,
|
|
277
|
-
action: 'reset',
|
|
278
|
-
status: 'needs_confirmation',
|
|
279
|
-
state_path: dbPath,
|
|
280
|
-
goal,
|
|
281
|
-
required_flag: CONFIRM_RESET_FLAG,
|
|
282
|
-
finished_at: new Date().toISOString(),
|
|
283
|
-
};
|
|
284
|
-
printJsonOrText(payload, [
|
|
285
|
-
'Codex goal reset blocked: confirmation required.',
|
|
286
|
-
`Thread: ${goal.thread_id}`,
|
|
287
|
-
`Objective: ${goal.objective}`,
|
|
288
|
-
`Run again with ${CONFIRM_RESET_FLAG} to back up state and clear this completed goal slot.`,
|
|
289
|
-
], asJson);
|
|
290
|
-
process.exitCode = 1;
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
ensurePrivateDir(outDir);
|
|
295
|
-
const stamp = safeStamp(startedAt);
|
|
296
|
-
const backupPath = path.join(outDir, `codex-state-before-goal-reset-${goal.thread_id}-${stamp}.sqlite`);
|
|
297
|
-
const dumpPath = path.join(outDir, `codex-goal-row-before-reset-${goal.thread_id}-${stamp}.json`);
|
|
298
|
-
backupSqliteDb(dbPath, backupPath);
|
|
299
|
-
fs.writeFileSync(dumpPath, `${JSON.stringify(goal, null, 2)}\n`, 'utf8');
|
|
300
|
-
chmodPrivate(dumpPath);
|
|
301
|
-
|
|
302
|
-
const rows = runSqliteJson(dbPath, `
|
|
303
|
-
BEGIN IMMEDIATE;
|
|
304
|
-
DELETE FROM thread_goals
|
|
305
|
-
WHERE thread_id = ${sqlString(goal.thread_id)}
|
|
306
|
-
AND goal_id = ${sqlString(goal.goal_id)}
|
|
307
|
-
AND status = 'complete';
|
|
308
|
-
SELECT changes() AS deleted;
|
|
309
|
-
COMMIT;
|
|
310
|
-
`, { readonly: false });
|
|
311
|
-
const deleted = Number(rows[0]?.deleted || 0);
|
|
312
|
-
const remaining = readGoalByThread(args, goal.thread_id);
|
|
313
|
-
const ok = deleted === 1 && !remaining;
|
|
314
|
-
const payload = writeReceipt(outDir, {
|
|
315
|
-
ok,
|
|
223
|
+
const completed = goal.status === 'complete';
|
|
224
|
+
const nextAction = completed
|
|
225
|
+
? 'Create a new Codex task for new or recurring work. Leave this completed task closed.'
|
|
226
|
+
: 'Continue or hand off the current task without clearing its goal.';
|
|
227
|
+
const payload = {
|
|
228
|
+
ok: false,
|
|
316
229
|
schema: SCHEMA,
|
|
317
230
|
action: 'reset',
|
|
318
|
-
status:
|
|
319
|
-
thread_id: goal.thread_id,
|
|
320
|
-
goal_id: goal.goal_id,
|
|
321
|
-
objective: goal.objective,
|
|
322
|
-
previous_status: goal.status,
|
|
323
|
-
deleted,
|
|
231
|
+
status: completed ? 'completed_task_closed' : 'active_task_unchanged',
|
|
324
232
|
state_path: dbPath,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
started_at: startedAt,
|
|
233
|
+
goal,
|
|
234
|
+
mutated: false,
|
|
328
235
|
finished_at: new Date().toISOString(),
|
|
329
|
-
next_action:
|
|
330
|
-
}
|
|
236
|
+
next_action: nextAction,
|
|
237
|
+
};
|
|
331
238
|
|
|
332
239
|
printJsonOrText(payload, [
|
|
333
|
-
|
|
334
|
-
`
|
|
335
|
-
`
|
|
336
|
-
`Dump: ${path.relative(process.cwd(), dumpPath)}`,
|
|
337
|
-
`Receipt: ${path.relative(process.cwd(), payload.receipt_path)}`,
|
|
338
|
-
`Next: ${payload.next_action}`,
|
|
240
|
+
completed ? 'Completed Codex task stays closed.' : `Codex goal reset refused: this task is ${goal.status}.`,
|
|
241
|
+
`Objective: ${goal.objective}`,
|
|
242
|
+
`Next: ${nextAction}`,
|
|
339
243
|
], asJson);
|
|
340
|
-
|
|
244
|
+
process.exitCode = 1;
|
|
341
245
|
}
|
|
342
246
|
|
|
343
247
|
function usage() {
|
|
@@ -345,19 +249,17 @@ function usage() {
|
|
|
345
249
|
'atris codex-goal - guarded bridge for native Codex thread goals',
|
|
346
250
|
'',
|
|
347
251
|
' atris codex-goal status [--thread <id>|--latest] [--json]',
|
|
348
|
-
|
|
252
|
+
' atris codex-goal reset --thread <id> [--json] Report why the task cannot be reset',
|
|
349
253
|
'',
|
|
350
254
|
'Flags:',
|
|
351
255
|
' --state <path> Codex goals DB (default ~/.codex/goals_1.sqlite, falls back to state_5.sqlite)',
|
|
352
256
|
' --threads-db <path> Codex thread metadata DB for cwd/title (default ~/.codex/state_5.sqlite)',
|
|
353
257
|
' --latest Use the latest Codex goal whose thread cwd matches the current directory',
|
|
354
|
-
' --out-dir <path> Receipt/backup directory (default .atris/runs)',
|
|
355
258
|
'',
|
|
356
|
-
'
|
|
357
|
-
'-
|
|
358
|
-
'-
|
|
359
|
-
'-
|
|
360
|
-
'- the next native goal must still be created by the active Codex thread',
|
|
259
|
+
'Task boundary:',
|
|
260
|
+
'- active tasks continue in their current thread',
|
|
261
|
+
'- completed tasks retain their final goal state',
|
|
262
|
+
'- new work and recurring monitors use a new dedicated Codex task',
|
|
361
263
|
].join('\n');
|
|
362
264
|
}
|
|
363
265
|
|
package/commands/computer.js
CHANGED
|
@@ -22,7 +22,7 @@ const os = require('os');
|
|
|
22
22
|
const path = require('path');
|
|
23
23
|
const readline = require('readline');
|
|
24
24
|
const { spawnSync } = require('child_process');
|
|
25
|
-
const { loadCredentials, decodeJwtClaims, promptUser } = require('../utils/auth');
|
|
25
|
+
const { loadCredentials, decodeJwtClaims, promptUser, abortOnAuthFailure } = require('../utils/auth');
|
|
26
26
|
const { apiRequestJson, getApiBaseUrl, getAppBaseUrl } = require('../utils/api');
|
|
27
27
|
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
28
28
|
const {
|
|
@@ -2258,15 +2258,18 @@ async function runBusinessPromptViaRunnerProxy(token, ctx, prompt, options = {})
|
|
|
2258
2258
|
|
|
2259
2259
|
async function ensureBusinessAwake(token, ctx, maxWaitSec = 90, options = {}) {
|
|
2260
2260
|
const status = await apiRequestJson(`/business/${ctx.businessId}/ai-computer/status`, { method: 'GET', token });
|
|
2261
|
+
abortOnAuthFailure(status);
|
|
2261
2262
|
if (status.ok && status.data && status.data.status === 'running' && status.data.endpoint) {
|
|
2262
2263
|
return true;
|
|
2263
2264
|
}
|
|
2264
2265
|
if (!options.quiet) process.stdout.write(' Waking business computer... ');
|
|
2265
|
-
await apiRequestJson(`/business/${ctx.businessId}/ai-computer/wake`, { method: 'POST', token, body: {} });
|
|
2266
|
+
const wake = await apiRequestJson(`/business/${ctx.businessId}/ai-computer/wake`, { method: 'POST', token, body: {} });
|
|
2267
|
+
abortOnAuthFailure(wake, !options.quiet);
|
|
2266
2268
|
const start = Date.now();
|
|
2267
2269
|
while (Date.now() - start < maxWaitSec * 1000) {
|
|
2268
2270
|
await sleep(3000);
|
|
2269
2271
|
const next = await apiRequestJson(`/business/${ctx.businessId}/ai-computer/status`, { method: 'GET', token });
|
|
2272
|
+
abortOnAuthFailure(next, !options.quiet);
|
|
2270
2273
|
if (next.ok && next.data && next.data.status === 'running' && next.data.endpoint) {
|
|
2271
2274
|
const elapsed = Math.floor((Date.now() - start) / 1000);
|
|
2272
2275
|
if (!options.quiet) console.log(`awake (${elapsed}s)`);
|
package/commands/improve.js
CHANGED
|
@@ -918,10 +918,15 @@ function formatImproveReport(result = {}) {
|
|
|
918
918
|
}
|
|
919
919
|
|
|
920
920
|
// ---------------------------------------------------------------------------
|
|
921
|
-
// atris improve revisions
|
|
922
|
-
// operator revisions after landing = 0. an agent landing is a commit
|
|
923
|
-
//
|
|
924
|
-
//
|
|
921
|
+
// atris improve revisions: the gauge for the north-star metric:
|
|
922
|
+
// operator revisions after landing = 0. an agent landing is a commit whose
|
|
923
|
+
// Co-authored-by trailer matches a known agent signature (atris-builder[bot],
|
|
924
|
+
// claude, cursor, codex, chatgpt, openai), case-insensitive, and only on
|
|
925
|
+
// those trailer lines. commits with no trailer still count as human, so the
|
|
926
|
+
// metric overcounts revisions; accepted on purpose.
|
|
927
|
+
//
|
|
928
|
+
// if a human commit touches any of the same files within 72 hours, that
|
|
929
|
+
// landing failed the guarantee.
|
|
925
930
|
//
|
|
926
931
|
// renames are NOT followed: `git log --follow` is per-file and would cost one
|
|
927
932
|
// subprocess per file per landing, so a post-landing rename reads as "no
|
|
@@ -930,9 +935,26 @@ function formatImproveReport(result = {}) {
|
|
|
930
935
|
const REVISIONS_SCHEMA = 'atris.improve_revisions.v1';
|
|
931
936
|
const REVISION_WINDOW_HOURS = 72;
|
|
932
937
|
const REVISION_WINDOW_MS = REVISION_WINDOW_HOURS * 60 * 60 * 1000;
|
|
933
|
-
const
|
|
938
|
+
const AGENT_TRAILER_MARKERS = [
|
|
939
|
+
'atris-builder[bot]',
|
|
940
|
+
'claude',
|
|
941
|
+
'cursor',
|
|
942
|
+
'codex',
|
|
943
|
+
'chatgpt',
|
|
944
|
+
'openai',
|
|
945
|
+
];
|
|
934
946
|
const DEFAULT_REVISIONS_DAYS = 14;
|
|
935
947
|
|
|
948
|
+
function isAgentCommitBody(body) {
|
|
949
|
+
const markers = AGENT_TRAILER_MARKERS.map((m) => m.toLowerCase());
|
|
950
|
+
for (const line of String(body || '').split(/\r?\n/)) {
|
|
951
|
+
if (!/^\s*co-authored-by\s*:/i.test(line)) continue;
|
|
952
|
+
const lower = line.toLowerCase();
|
|
953
|
+
if (markers.some((m) => lower.includes(m))) return true;
|
|
954
|
+
}
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
|
|
936
958
|
function parseRevisionsArgs(argv = []) {
|
|
937
959
|
const args = Array.isArray(argv) ? argv : [];
|
|
938
960
|
const opts = { days: DEFAULT_REVISIONS_DAYS, json: false, help: false };
|
|
@@ -999,7 +1021,7 @@ function collectRevisionSignals(root, options = {}) {
|
|
|
999
1021
|
at: String(at || '').trim(),
|
|
1000
1022
|
ms: timestampMs(at),
|
|
1001
1023
|
subject: String(subject || '').trim(),
|
|
1002
|
-
isAgent:
|
|
1024
|
+
isAgent: isAgentCommitBody(body),
|
|
1003
1025
|
};
|
|
1004
1026
|
})
|
|
1005
1027
|
.filter((c) => c.hash && c.ms != null);
|
|
@@ -1350,6 +1372,7 @@ module.exports = {
|
|
|
1350
1372
|
runLoopDoctor,
|
|
1351
1373
|
collectRevisionSignals,
|
|
1352
1374
|
formatRevisionsReport,
|
|
1375
|
+
isAgentCommitBody,
|
|
1353
1376
|
runLocalFallback,
|
|
1354
1377
|
summarizeLocalMissionRun,
|
|
1355
1378
|
LOCAL_FALLBACK_ARGS,
|
package/commands/init.js
CHANGED
|
@@ -3,6 +3,12 @@ const path = require('path');
|
|
|
3
3
|
const { ensureExperimentsFramework } = require('./experiments');
|
|
4
4
|
const { ensureWikiScaffold } = require('../lib/wiki');
|
|
5
5
|
const { upsertAtrisClaudeBootBlock } = require('../lib/claude-boot-block');
|
|
6
|
+
const {
|
|
7
|
+
upsertAgentVoiceCard,
|
|
8
|
+
upsertClaudeVoiceHook,
|
|
9
|
+
upsertCursorVoiceCard,
|
|
10
|
+
voiceCardForRoot,
|
|
11
|
+
} = require('../lib/voice-card');
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* Detect project context by scanning project structure
|
|
@@ -732,6 +738,12 @@ Every agent should leave four artifacts another agent can trust:
|
|
|
732
738
|
| Proof ready | \`atris task ready <id> --proof "<commands or receipt>" --result "<day-one PM sentence>"\` |
|
|
733
739
|
| Human accept | \`atris task accept <id>\` |
|
|
734
740
|
|
|
741
|
+
Every created task leads with three plain fields: what changes, why it matters,
|
|
742
|
+
and what done looks like. Keep the exact title, files, commands, constraints,
|
|
743
|
+
events, and proof underneath unchanged. Planned work offers approve or ask for
|
|
744
|
+
a change through the existing Plan/Do gates; finished work uses the existing
|
|
745
|
+
accept/revise gates and never skips proof.
|
|
746
|
+
|
|
735
747
|
Do not rely on chat context. Put the task, file pointers, and proof on disk.
|
|
736
748
|
Do not write new operating doctrine here first; add it to Atris policy, skills,
|
|
737
749
|
wiki, or \`atris/atris.md\`, then regenerate this adapter if needed.
|
|
@@ -758,7 +770,8 @@ Human accept -> task Done + AgentXP awarded
|
|
|
758
770
|
\`\`\`
|
|
759
771
|
|
|
760
772
|
Always-on agents should move proof-backed work to Review, complete their native
|
|
761
|
-
goal, then
|
|
773
|
+
goal, then stop that task. The next goal or recurring monitor starts in a new
|
|
774
|
+
dedicated task. They must not run
|
|
762
775
|
\`atris task accept\` or claim AgentXP unless a human approved the proof.
|
|
763
776
|
|
|
764
777
|
Mission-shaped user intent wins before normal task selection. If the user
|
|
@@ -831,6 +844,8 @@ member -> mission start --verify -> status --status active -> one bounded step -
|
|
|
831
844
|
|
|
832
845
|
**Protocol:** See \`atris/atris.md\` for full spec.`;
|
|
833
846
|
|
|
847
|
+
const voiceCard = voiceCardForRoot(process.cwd());
|
|
848
|
+
|
|
834
849
|
// .cursorrules for Cursor (legacy)
|
|
835
850
|
const cursorRulesFile = path.join(process.cwd(), '.cursorrules');
|
|
836
851
|
if (!fs.existsSync(cursorRulesFile)) {
|
|
@@ -847,12 +862,22 @@ member -> mission start --verify -> status --status active -> one bounded step -
|
|
|
847
862
|
markReady('adapters', '.cursor/rules/atris.mdc', '✓ Created .cursor/rules/atris.mdc (for Cursor)');
|
|
848
863
|
}
|
|
849
864
|
|
|
865
|
+
const cursorVoiceFile = path.join(cursorRulesDir, 'atris-voice.mdc');
|
|
866
|
+
const cursorVoiceResult = upsertCursorVoiceCard(cursorVoiceFile, voiceCard);
|
|
867
|
+
if (cursorVoiceResult.action !== 'unchanged') {
|
|
868
|
+
markReady('adapters', '.cursor/rules/atris-voice.mdc', '✓ Pinned the Atris voice card for Cursor');
|
|
869
|
+
}
|
|
870
|
+
|
|
850
871
|
// AGENTS.md for Codex
|
|
851
872
|
const agentsMdFile = path.join(process.cwd(), 'AGENTS.md');
|
|
852
873
|
if (!fs.existsSync(agentsMdFile)) {
|
|
853
874
|
fs.writeFileSync(agentsMdFile, agentInstructions);
|
|
854
875
|
markReady('adapters', 'AGENTS.md', '✓ Created AGENTS.md (for Codex)');
|
|
855
876
|
}
|
|
877
|
+
const agentsVoiceResult = upsertAgentVoiceCard(agentsMdFile, voiceCard);
|
|
878
|
+
if (agentsVoiceResult.action !== 'unchanged') {
|
|
879
|
+
markReady('adapters', 'AGENTS.md voice card', '✓ Pinned the Atris voice card in AGENTS.md');
|
|
880
|
+
}
|
|
856
881
|
|
|
857
882
|
// .devin/config.local.json for Devin for Terminal
|
|
858
883
|
const devinConfigDir = path.join(process.cwd(), '.devin');
|
|
@@ -1035,14 +1060,11 @@ Read atris/MAP.md. Begin iteration 1.`;
|
|
|
1035
1060
|
markReady('adapters', 'atris/CLAUDE.md', '✓ Created atris/CLAUDE.md (for Claude Code)');
|
|
1036
1061
|
}
|
|
1037
1062
|
|
|
1038
|
-
// .claude/settings.json with
|
|
1063
|
+
// .claude/settings.json with startup and per-prompt Atris hooks
|
|
1039
1064
|
const claudeSettingsDir = path.join(process.cwd(), '.claude');
|
|
1040
1065
|
const claudeSettingsFile = path.join(claudeSettingsDir, 'settings.json');
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
fs.mkdirSync(claudeSettingsDir, { recursive: true });
|
|
1044
|
-
}
|
|
1045
|
-
const claudeSettings = {
|
|
1066
|
+
const claudeSettingsResult = upsertClaudeVoiceHook(claudeSettingsFile, {
|
|
1067
|
+
initialSettings: {
|
|
1046
1068
|
hooks: {
|
|
1047
1069
|
SessionStart: [
|
|
1048
1070
|
{
|
|
@@ -1065,9 +1087,10 @@ Read atris/MAP.md. Begin iteration 1.`;
|
|
|
1065
1087
|
}
|
|
1066
1088
|
]
|
|
1067
1089
|
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
if (claudeSettingsResult.action !== 'unchanged' && claudeSettingsResult.action !== 'skipped') {
|
|
1093
|
+
markReady('adapters', '.claude/settings.json', '✓ Wired Atris into Claude startup and replies');
|
|
1071
1094
|
}
|
|
1072
1095
|
|
|
1073
1096
|
// Co-author trailer: commits in this workspace credit Atris, same as Claude/Cursor do
|