atris 3.74.4 → 3.74.6
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/FOR_AGENTS.md +12 -12
- package/atris/AGENTS.md +7 -7
- package/atris/CLAUDE.md +10 -10
- package/atris/policies/atris-backend.md +1 -1
- package/atris/policies/atris-design.md +71 -71
- package/atris/policies/design-seed.md +28 -28
- package/atris/policies/writing.md +1 -1
- package/atris/skills/apps/SKILL.md +3 -3
- package/atris/skills/atris-feedback/SKILL.md +2 -2
- package/atris/skills/autopilot/SKILL.md +10 -10
- package/atris/skills/blocks/SKILL.md +16 -16
- package/atris/skills/calendar/SKILL.md +1 -1
- package/atris/skills/clawhub/atris/SKILL.md +4 -4
- package/atris/skills/clawhub/philosophy-of-work/SKILL.md +7 -7
- package/atris/skills/create-app/SKILL.md +18 -18
- package/atris/skills/create-member/SKILL.md +11 -11
- package/atris/skills/design/SKILL.md +1 -1
- package/atris/skills/drive/SKILL.md +25 -25
- package/atris/skills/email-agent/SKILL.md +11 -11
- package/atris/skills/endgame/SKILL.md +33 -33
- package/atris/skills/magic-inbox/SKILL.md +15 -15
- package/atris/skills/notion/SKILL.md +1 -1
- package/atris/skills/ramp/SKILL.md +7 -7
- package/atris/skills/research-search/SKILL.md +7 -7
- package/atris/skills/slides/SKILL.md +7 -7
- package/atris/skills/tidy/SKILL.md +11 -11
- package/atris/skills/wake/SKILL.md +11 -11
- package/atris/skills/writing/SKILL.md +5 -5
- package/atris/team/_template/SOUL.md +3 -3
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris/team/launcher/MEMBER.md +2 -2
- package/atris/team/mission-lead/MISSION.md +7 -7
- package/atris/team/opus-overnight/MISSION.md +16 -16
- package/atris/team/opus-overnight/STEERING.md +5 -5
- package/atris.md +9 -0
- package/bin/atris.js +16 -3
- package/commands/campaign.js +17 -2
- package/commands/card.js +19 -12
- package/commands/engine.js +92 -17
- package/commands/gm.js +30 -0
- package/commands/idea.js +263 -0
- package/commands/init.js +11 -2
- package/commands/land.js +39 -18
- package/commands/mission.js +4 -3
- package/commands/ninety-nine.js +288 -0
- package/commands/one-lap.js +46 -27
- package/commands/reel.js +12 -7
- package/commands/sync.js +10 -2
- package/commands/task.js +4 -2
- package/commands/xp.js +68 -15
- package/commands/zen.js +10 -3
- package/lib/campaign-keep.js +23 -8
- package/lib/campaign-loop.js +24 -5
- package/lib/career-xp-lock.js +61 -0
- package/lib/career-xp-root.js +61 -0
- package/lib/claude-boot-block.js +7 -4
- package/lib/engine-registry.js +139 -1
- package/lib/fleet.js +11 -2
- package/lib/gm-bet.js +734 -42
- package/lib/gm-card.js +431 -0
- package/lib/gm-phone-page.js +486 -717
- package/lib/gm-phone.js +33 -34
- package/lib/gm-season.js +14 -0
- package/lib/headless-chrome.js +39 -0
- package/lib/idea-gate.js +33 -0
- package/lib/idea-inbox.js +220 -0
- package/lib/idea-ledger.js +196 -0
- package/lib/idea-tick.js +384 -0
- package/lib/known-commands.js +1 -1
- package/lib/member-engine.js +1 -0
- package/lib/ninety-nine-sync.js +156 -0
- package/lib/ninety-nine.js +247 -0
- package/lib/opening-lines.js +94 -0
- package/lib/roster-markdown.js +39 -1
- package/lib/roster-models.js +1 -1
- package/lib/roster-swap.js +353 -0
- package/lib/spawn-env.js +29 -2
- package/lib/task-db.js +8 -4
- package/lib/wish-audit.js +14 -0
- package/lib/yes-lock.js +365 -256
- package/lib/zen-apply.js +6 -3
- package/lib/zen.js +28 -1
- package/package.json +1 -1
- package/templates/business-starter/CLAUDE.md +7 -7
- package/templates/business-starter/MAP.md +1 -1
- package/templates/business-starter/TODO.md +1 -1
- package/templates/business-starter/atris.md +2 -2
- package/templates/business-starter/context/README.md +6 -6
- package/templates/business-starter/context/live-workspace.md +1 -1
- package/templates/business-starter/goals.md +1 -1
- package/templates/business-starter/instructions.md +1 -1
- package/templates/business-starter/memory.md +4 -4
- package/templates/business-starter/persona.md +3 -3
- package/templates/business-starter/policies/LESSONS.md +1 -1
- package/templates/business-starter/reports/README.md +1 -1
- package/templates/business-starter/skills/README.md +1 -1
- package/templates/business-starter/team/README.md +1 -1
- package/templates/business-starter/team/START_HERE.md +1 -1
- package/templates/business-starter/team/_template/SOUL.md +3 -3
- package/templates/business-starter/team/comms/SOUL.md +2 -2
- package/templates/business-starter/team/ops/SOUL.md +4 -4
- package/templates/business-starter/team/research/SOUL.md +3 -3
- package/templates/business-starter/wiki/index.md +1 -1
- package/templates/research-canonical/CLAUDE.md +7 -7
- package/templates/research-canonical/MAP.md +1 -1
- package/templates/research-canonical/TODO.md +1 -1
- package/templates/research-canonical/atris.md +2 -2
- package/templates/research-canonical/context/README.md +8 -8
- package/templates/research-canonical/context/live-workspace.md +1 -1
- package/templates/research-canonical/goals.md +2 -2
- package/templates/research-canonical/instructions.md +1 -1
- package/templates/research-canonical/memory.md +4 -4
- package/templates/research-canonical/persona.md +1 -1
- package/templates/research-canonical/policies/LESSONS.md +1 -1
- package/templates/research-canonical/reports/README.md +1 -1
- package/templates/research-canonical/skills/README.md +1 -1
- package/templates/research-canonical/team/README.md +1 -1
- package/templates/research-canonical/team/eval/SOUL.md +1 -1
- package/templates/research-canonical/team/experiment/SOUL.md +2 -2
- package/templates/research-canonical/team/hypothesis/SOUL.md +3 -3
- package/templates/research-canonical/team/literature/SOUL.md +2 -2
- package/templates/research-canonical/wiki/briefs/research-program.md +1 -1
- package/templates/research-canonical/wiki/index.md +1 -1
- package/utils/auth.js +1 -0
package/commands/idea.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// atris idea: feed ideas, Atris delegates.
|
|
4
|
+
// atris idea "<sentence>" [--repo <path>] [--verify "<cmd>"] save one idea
|
|
5
|
+
// atris idea [--repo <path>] [--json] list ideas
|
|
6
|
+
// atris idea retry <n> [--repo <path>] put one back in line
|
|
7
|
+
// atris idea tick [--repo <path>] [--max-minutes <n>] build the oldest waiting idea
|
|
8
|
+
// atris idea on|off [--repo <path>] [--dry-run] tick every 10 minutes, or stop
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { spawnSync } = require('child_process');
|
|
14
|
+
const { argsWantHelp } = require('../lib/noninteractive');
|
|
15
|
+
const {
|
|
16
|
+
addIdea,
|
|
17
|
+
findIdea,
|
|
18
|
+
ideaRefusal,
|
|
19
|
+
oneLine,
|
|
20
|
+
readIdeas,
|
|
21
|
+
updateIdea,
|
|
22
|
+
} = require('../lib/idea-inbox');
|
|
23
|
+
const { ideaScore, readOutcomes, scoreLine } = require('../lib/idea-ledger');
|
|
24
|
+
|
|
25
|
+
const VALUE_FLAGS = new Set(['--repo', '--verify', '--max-minutes']);
|
|
26
|
+
|
|
27
|
+
function ideaUsage() {
|
|
28
|
+
return [
|
|
29
|
+
'Usage: atris idea "<sentence>" [--repo <path>] [--verify "<cmd>"]',
|
|
30
|
+
' atris idea [--repo <path>] [--json]',
|
|
31
|
+
' atris idea retry <n> [--repo <path>] [--verify "<cmd>"]',
|
|
32
|
+
' atris idea tick [--repo <path>] [--max-minutes <n>]',
|
|
33
|
+
' atris idea on|off [--repo <path>] [--dry-run]',
|
|
34
|
+
].join('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseArgs(args = []) {
|
|
38
|
+
const list = Array.isArray(args) ? args.map(String) : [];
|
|
39
|
+
const flags = {};
|
|
40
|
+
const words = [];
|
|
41
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
42
|
+
const arg = list[i];
|
|
43
|
+
const eq = arg.match(/^(--[a-z-]+)=(.*)$/);
|
|
44
|
+
if (eq && VALUE_FLAGS.has(eq[1])) {
|
|
45
|
+
flags[eq[1].slice(2)] = eq[2];
|
|
46
|
+
} else if (VALUE_FLAGS.has(arg)) {
|
|
47
|
+
flags[arg.slice(2)] = list[i + 1] || '';
|
|
48
|
+
i += 1;
|
|
49
|
+
} else if (arg === '--json') {
|
|
50
|
+
flags.json = true;
|
|
51
|
+
} else if (arg === '--dry-run') {
|
|
52
|
+
flags.dryRun = true;
|
|
53
|
+
} else if (!arg.startsWith('--')) {
|
|
54
|
+
words.push(arg);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { flags, words };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The repo an idea belongs to: --repo when given, else the git top of the
|
|
61
|
+
// current folder, else the folder itself.
|
|
62
|
+
function resolveRepo(repo, cwd = process.cwd()) {
|
|
63
|
+
const start = path.resolve(cwd, repo || '.');
|
|
64
|
+
const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { cwd: start, encoding: 'utf8' });
|
|
65
|
+
return top.status === 0 && String(top.stdout || '').trim() ? String(top.stdout).trim() : start;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const TICK_EVERY_SECONDS = 600;
|
|
69
|
+
|
|
70
|
+
// The timer that runs `atris idea tick` for one repo: launchd on macOS, cron
|
|
71
|
+
// elsewhere, through the same planner `atris campaign keep` uses.
|
|
72
|
+
function ideaTimerPlan(repo, keepDeps = {}) {
|
|
73
|
+
const { planKeep } = require('../lib/campaign-keep');
|
|
74
|
+
return planKeep({
|
|
75
|
+
root: repo,
|
|
76
|
+
home: path.join(os.homedir(), '.atris', 'overnight'),
|
|
77
|
+
atrisBin: path.resolve(__dirname, '..', 'bin', 'atris.js'),
|
|
78
|
+
job: {
|
|
79
|
+
name: 'idea-tick',
|
|
80
|
+
args: ['idea', 'tick'],
|
|
81
|
+
every_seconds: TICK_EVERY_SECONDS,
|
|
82
|
+
marker: 'ATRIS_IDEA_TICK',
|
|
83
|
+
log: `idea-tick-${path.basename(repo)}.log`,
|
|
84
|
+
},
|
|
85
|
+
...(keepDeps.plan || {}),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ideaTimerOn(plan, keepDeps = {}) {
|
|
90
|
+
if (plan.kind === 'launchd') return fs.existsSync(plan.file);
|
|
91
|
+
const spawn = keepDeps.spawn || spawnSync;
|
|
92
|
+
const listed = spawn('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
|
|
93
|
+
return listed.status === 0 && String(listed.stdout || '').includes(plan.marker);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function switchTimer(turnOn, { repo, dryRun, json, log, keepDeps }) {
|
|
97
|
+
const { installKeep, removeKeep } = require('../lib/campaign-keep');
|
|
98
|
+
const plan = ideaTimerPlan(repo, keepDeps);
|
|
99
|
+
const where = plan.kind === 'launchd' ? plan.file : 'your crontab';
|
|
100
|
+
if (dryRun) {
|
|
101
|
+
if (json) log(JSON.stringify({ schema: 'atris.idea_timer.v1', ok: true, on: turnOn, dry_run: true, plan }, null, 2));
|
|
102
|
+
else log(`would ${turnOn ? 'start' : 'stop'} a ${plan.kind} timer in ${where}:\n every ${plan.every_seconds / 60} minutes: ${plan.command}\n in ${repo}\n log ${plan.log_path}`);
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
const result = (turnOn ? installKeep : removeKeep)(plan, keepDeps);
|
|
106
|
+
if (json) log(JSON.stringify({ schema: 'atris.idea_timer.v1', on: turnOn, ...result, plan }, null, 2));
|
|
107
|
+
if (!result.ok) {
|
|
108
|
+
if (!json) log(`could not ${turnOn ? 'start' : 'stop'} the timer: ${result.error}`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
if (json) return 0;
|
|
112
|
+
if (turnOn) {
|
|
113
|
+
log(`on. every ${plan.every_seconds / 60} minutes the oldest idea in ${path.basename(repo)} gets built and checked.`);
|
|
114
|
+
log(`log: ${plan.log_path}`);
|
|
115
|
+
log('stop it: atris idea off');
|
|
116
|
+
} else {
|
|
117
|
+
log(result.removed === false ? 'the idea timer was not on here.' : 'off. ideas stay saved; nothing builds them until you turn it back on.');
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function ideaJson(idea) {
|
|
123
|
+
return {
|
|
124
|
+
n: idea.n,
|
|
125
|
+
id: idea.id,
|
|
126
|
+
text: idea.text,
|
|
127
|
+
status: idea.status,
|
|
128
|
+
reason: idea.reason || null,
|
|
129
|
+
task_id: idea.task_id || null,
|
|
130
|
+
link: idea.link || null,
|
|
131
|
+
at: idea.at,
|
|
132
|
+
tag: idea.tag || null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Ideas the loop files about itself land in the loop's own code, so they go
|
|
137
|
+
// to this atris checkout when it is a git checkout, else to the same repo.
|
|
138
|
+
function loopHome(repo) {
|
|
139
|
+
const home = path.resolve(__dirname, '..');
|
|
140
|
+
return fs.existsSync(path.join(home, '.git')) ? home : repo;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function renderIdeaList(ideas, { repo, timerOn = null, score = null } = {}) {
|
|
144
|
+
const lines = [];
|
|
145
|
+
if (score && score.builds) {
|
|
146
|
+
lines.push(scoreLine(score));
|
|
147
|
+
lines.push('');
|
|
148
|
+
}
|
|
149
|
+
if (!ideas.length) {
|
|
150
|
+
lines.push('no ideas here yet.');
|
|
151
|
+
lines.push('');
|
|
152
|
+
lines.push('drop one: atris idea "make the inbox search faster"');
|
|
153
|
+
return lines.join('\n');
|
|
154
|
+
}
|
|
155
|
+
lines.push(`ideas in ${path.basename(repo || '.')}: ${ideas.length}`);
|
|
156
|
+
lines.push('');
|
|
157
|
+
const width = String(ideas.length).length;
|
|
158
|
+
const pad = ' '.repeat(width + 4 + 10);
|
|
159
|
+
for (const idea of ideas) {
|
|
160
|
+
const tag = idea.tag === 'loop' ? '[loop] ' : '';
|
|
161
|
+
lines.push(` ${String(idea.n).padStart(width)}. ${idea.status.padEnd(9)} ${tag}${oneLine(idea.text, 80)}`);
|
|
162
|
+
if (idea.status === 'landed' && idea.link) lines.push(`${pad}${idea.link}`);
|
|
163
|
+
if (idea.status === 'refused' && idea.reason) lines.push(`${pad}${oneLine(idea.reason, 100)}`);
|
|
164
|
+
if (idea.status === 'building' && idea.task_id) lines.push(`${pad}task ${idea.task_id}`);
|
|
165
|
+
}
|
|
166
|
+
const waiting = ideas.filter((idea) => idea.status === 'waiting').length;
|
|
167
|
+
if (waiting) {
|
|
168
|
+
lines.push('');
|
|
169
|
+
lines.push(`${waiting} waiting. they build one at a time, oldest first.`);
|
|
170
|
+
if (timerOn === true) lines.push('the next one starts within 10 minutes.');
|
|
171
|
+
if (timerOn === false) lines.push('nothing builds them yet. start it: atris idea on');
|
|
172
|
+
}
|
|
173
|
+
return lines.join('\n');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function saveIdea(sentence, { repo, verify, json, log }) {
|
|
177
|
+
const idea = addIdea(repo, sentence, { verify });
|
|
178
|
+
if (json) {
|
|
179
|
+
log(JSON.stringify({ schema: 'atris.idea.v1', ok: true, repo, idea: ideaJson(idea) }, null, 2));
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
if (idea.status === 'refused') {
|
|
183
|
+
log(`saved idea ${idea.n}, but I will not build it alone: ${idea.reason}.`);
|
|
184
|
+
log('');
|
|
185
|
+
log('it stays on the list so you can pick it up yourself.');
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
log(`saved idea ${idea.n}: ${oneLine(idea.text, 80)}`);
|
|
189
|
+
log('');
|
|
190
|
+
log('it gets built and checked when the idea tick runs, one idea at a time.');
|
|
191
|
+
log('see where it is: atris idea');
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function retryIdea(ref, { repo, verify = '', json, log }) {
|
|
196
|
+
const idea = findIdea(readIdeas(repo), ref);
|
|
197
|
+
if (!idea) {
|
|
198
|
+
log(`no idea ${ref || '(missing number)'} here. see the list: atris idea`);
|
|
199
|
+
return 2;
|
|
200
|
+
}
|
|
201
|
+
if (idea.status === 'building' || idea.status === 'landed') {
|
|
202
|
+
log(`idea ${idea.n} is ${idea.status}; nothing to retry.`);
|
|
203
|
+
return 2;
|
|
204
|
+
}
|
|
205
|
+
const refusal = ideaRefusal(idea.text);
|
|
206
|
+
if (refusal) {
|
|
207
|
+
log(`idea ${idea.n} still ${refusal.replace(/, needs owner$/, '')}. it needs you, not a retry.`);
|
|
208
|
+
return 2;
|
|
209
|
+
}
|
|
210
|
+
const next = updateIdea(repo, idea.id, { status: 'waiting', reason: '', attempts: 0, ...(verify ? { verify } : {}) });
|
|
211
|
+
if (json) log(JSON.stringify({ schema: 'atris.idea.v1', ok: true, repo, idea: ideaJson(next) }, null, 2));
|
|
212
|
+
else log(`idea ${next.n} is back in line.`);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function ideaAtris(args = process.argv.slice(3), { cwd = process.cwd(), log = console.log, keepDeps = {} } = {}) {
|
|
217
|
+
const list = Array.isArray(args) ? args : [];
|
|
218
|
+
if (argsWantHelp(list)) {
|
|
219
|
+
log(ideaUsage());
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
const { flags, words } = parseArgs(list);
|
|
223
|
+
const repo = resolveRepo(flags.repo, cwd);
|
|
224
|
+
const json = flags.json === true;
|
|
225
|
+
const verb = words[0];
|
|
226
|
+
|
|
227
|
+
if (verb === 'retry' && words.length <= 2) return retryIdea(words[1], { repo, verify: flags.verify || '', json, log });
|
|
228
|
+
if ((verb === 'on' || verb === 'off') && words.length === 1) {
|
|
229
|
+
return switchTimer(verb === 'on', { repo, dryRun: flags.dryRun === true, json, log, keepDeps });
|
|
230
|
+
}
|
|
231
|
+
if (verb === 'tick' && words.length === 1) {
|
|
232
|
+
const minutes = Number(flags['max-minutes']);
|
|
233
|
+
const { runIdeaTick, DEFAULT_BUILD_SECONDS } = require('../lib/idea-tick');
|
|
234
|
+
return runIdeaTick({
|
|
235
|
+
root: repo,
|
|
236
|
+
log: json ? () => {} : log,
|
|
237
|
+
loopRepo: loopHome(repo),
|
|
238
|
+
maxSeconds: Number.isFinite(minutes) && minutes > 0 ? Math.floor(minutes * 60) : DEFAULT_BUILD_SECONDS,
|
|
239
|
+
}).then((outcome) => {
|
|
240
|
+
if (json) log(JSON.stringify({ schema: 'atris.idea_tick.v1', repo, ...outcome }, null, 2));
|
|
241
|
+
return 0;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (verb === 'list' && words.length === 1) words.length = 0;
|
|
245
|
+
|
|
246
|
+
const sentence = words.join(' ').trim();
|
|
247
|
+
if (sentence) return saveIdea(sentence, { repo, verify: flags.verify || '', json, log });
|
|
248
|
+
|
|
249
|
+
const ideas = readIdeas(repo);
|
|
250
|
+
const score = ideaScore(readOutcomes(repo));
|
|
251
|
+
if (json) {
|
|
252
|
+
log(JSON.stringify({ schema: 'atris.idea_list.v1', repo, score: { ...score, line: scoreLine(score) }, ideas: ideas.map(ideaJson) }, null, 2));
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
let timerOn = null;
|
|
256
|
+
try { timerOn = ideaTimerOn(ideaTimerPlan(repo, keepDeps), keepDeps); } catch {}
|
|
257
|
+
log(renderIdeaList(ideas, { repo, timerOn, score }));
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = {
|
|
262
|
+
ideaAtris,
|
|
263
|
+
};
|
package/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@ const { ensureMemberBundle } = require('../lib/member-scaffold');
|
|
|
4
4
|
const { ensureExperimentsFramework } = require('./experiments');
|
|
5
5
|
const { ensureWorkspaceBrain } = require('../lib/workspace-scaffold');
|
|
6
6
|
const { upsertAtrisClaudeBootBlock } = require('../lib/claude-boot-block');
|
|
7
|
+
const { openingLinesForRoot, renderAgentsOpeningBlock, upsertAgentsOpeningLines } = require('../lib/opening-lines');
|
|
7
8
|
const { ensureRunIgnore } = require('../lib/gitignore');
|
|
8
9
|
const { snapshotBeforeInit, commitInitFiles } = require('../lib/init-commit');
|
|
9
10
|
const { INTENTS } = require('../lib/intents');
|
|
@@ -648,6 +649,8 @@ tools: []
|
|
|
648
649
|
// Create agent instruction files for different tools
|
|
649
650
|
if (withAdapters) {
|
|
650
651
|
const intentTable = renderIntentTable();
|
|
652
|
+
const openingLines = openingLinesForRoot(process.cwd());
|
|
653
|
+
const agentsOpeningBlock = renderAgentsOpeningBlock(openingLines);
|
|
651
654
|
const agentInstructions = `# AGENTS.md: Universal Agent Instructions
|
|
652
655
|
|
|
653
656
|
> Works with: Claude Code, Cursor, Codex, OpenClaw, Windsurf, and any AI coding agent.
|
|
@@ -666,6 +669,8 @@ atris atris.md
|
|
|
666
669
|
|
|
667
670
|
This is the Atris boot sequence. Show the output to the user, then respond naturally.
|
|
668
671
|
|
|
672
|
+
${agentsOpeningBlock}
|
|
673
|
+
|
|
669
674
|
## You translate
|
|
670
675
|
|
|
671
676
|
The person you work for does not know atris words and never has to learn them. Never tell them to run an atris command or learn a term.
|
|
@@ -870,6 +875,10 @@ member -> mission start --verify -> status --status active -> one bounded step -
|
|
|
870
875
|
if (agentsVoiceResult.action !== 'unchanged') {
|
|
871
876
|
markReady('adapters', 'AGENTS.md voice card', '✓ Pinned the Atris voice card in AGENTS.md');
|
|
872
877
|
}
|
|
878
|
+
const agentsOpeningResult = upsertAgentsOpeningLines(agentsMdFile, openingLines);
|
|
879
|
+
if (agentsOpeningResult.action !== 'unchanged') {
|
|
880
|
+
markReady('adapters', 'AGENTS.md operating rules', '✓ Added the atris.md operating rules to AGENTS.md');
|
|
881
|
+
}
|
|
873
882
|
|
|
874
883
|
// .devin/config.local.json for Devin for Terminal
|
|
875
884
|
const devinConfigDir = path.join(process.cwd(), '.devin');
|
|
@@ -1100,7 +1109,7 @@ Read atris/MAP.md. Begin iteration 1.`;
|
|
|
1100
1109
|
const rootClaudeMd = path.join(process.cwd(), 'CLAUDE.md');
|
|
1101
1110
|
if (fs.existsSync(rootClaudeMd)) {
|
|
1102
1111
|
const source = fs.readFileSync(rootClaudeMd, 'utf8');
|
|
1103
|
-
const result = upsertAtrisClaudeBootBlock(source);
|
|
1112
|
+
const result = upsertAtrisClaudeBootBlock(source, { openingLines: openingLinesForRoot(process.cwd()) });
|
|
1104
1113
|
fs.writeFileSync(rootClaudeMd, result.content);
|
|
1105
1114
|
if (result.action === 'repaired') {
|
|
1106
1115
|
markReady('adapters', 'CLAUDE.md', '✓ repaired atris block in CLAUDE.md (missing end marker)');
|
|
@@ -1111,7 +1120,7 @@ Read atris/MAP.md. Begin iteration 1.`;
|
|
|
1111
1120
|
}
|
|
1112
1121
|
} else {
|
|
1113
1122
|
// Create new CLAUDE.md with just Atris block
|
|
1114
|
-
fs.writeFileSync(rootClaudeMd, upsertAtrisClaudeBootBlock().content);
|
|
1123
|
+
fs.writeFileSync(rootClaudeMd, upsertAtrisClaudeBootBlock('', { openingLines: openingLinesForRoot(process.cwd()) }).content);
|
|
1115
1124
|
markReady('adapters', 'CLAUDE.md', '✓ created CLAUDE.md with atris block');
|
|
1116
1125
|
}
|
|
1117
1126
|
}
|
package/commands/land.js
CHANGED
|
@@ -14,7 +14,8 @@ function runGit(args, opts) {
|
|
|
14
14
|
return gitSpawn.runGit(args, opts);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const CHERRY_CACHE_FILE = path.join('
|
|
17
|
+
const CHERRY_CACHE_FILE = path.join('atris', 'cherry-cache.json');
|
|
18
|
+
const LEGACY_CHERRY_CACHE_FILE = path.join('.atris', 'state', 'cherry-cache.json');
|
|
18
19
|
const CHERRY_CACHE_MAX = 2000;
|
|
19
20
|
|
|
20
21
|
// Pluralize a count + noun ("1 change", "2 changes").
|
|
@@ -218,29 +219,49 @@ function cherryStats(root, base, ref) {
|
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
// `git cherry <base> <ref>` depends only on the two commit shas, so its
|
|
221
|
-
// counts are cached per (base sha, ref sha)
|
|
222
|
-
//
|
|
223
|
-
//
|
|
222
|
+
// counts are cached per (base sha, ref sha). The file lives in the shared git
|
|
223
|
+
// dir, so a fresh worktree (which has no .atris yet) reuses what any other
|
|
224
|
+
// worktree of the repo already computed. Only repos that use atris (.atris or
|
|
225
|
+
// atris/ at the root) get a cache; a missing or corrupt file means recompute.
|
|
226
|
+
// Entries for refs not seen this run are dropped on save.
|
|
224
227
|
function cherryCachePath(root) {
|
|
225
|
-
|
|
228
|
+
const result = runGit(['rev-parse', '--git-common-dir'], { cwd: root, check: false });
|
|
229
|
+
if (result.status !== 0 || !result.stdout.trim()) return '';
|
|
230
|
+
return path.join(path.resolve(root, result.stdout.trim()), CHERRY_CACHE_FILE);
|
|
226
231
|
}
|
|
227
232
|
|
|
228
|
-
function
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
if (!cache.writable) return cache;
|
|
236
|
-
try {
|
|
237
|
-
const parsed = JSON.parse(fs.readFileSync(cache.file, 'utf8'));
|
|
238
|
-
if (parsed && parsed.version === 1 && parsed.entries && typeof parsed.entries === 'object') {
|
|
239
|
-
cache.entries = parsed.entries;
|
|
233
|
+
function usesAtris(root) {
|
|
234
|
+
return ['.atris', 'atris'].some((name) => {
|
|
235
|
+
try {
|
|
236
|
+
return fs.statSync(path.join(root, name)).isDirectory();
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
240
239
|
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function readCherryEntries(file) {
|
|
244
|
+
try {
|
|
245
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
246
|
+
if (parsed && parsed.version === 1 && parsed.entries && typeof parsed.entries === 'object') return parsed.entries;
|
|
241
247
|
} catch {
|
|
242
|
-
|
|
248
|
+
// missing or corrupt: recompute
|
|
243
249
|
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function loadCherryCache(root) {
|
|
254
|
+
const cache = { file: '', entries: {}, used: {}, dirty: false, writable: false };
|
|
255
|
+
if (!usesAtris(root)) return cache;
|
|
256
|
+
cache.file = cherryCachePath(root);
|
|
257
|
+
if (!cache.file) return cache;
|
|
258
|
+
cache.writable = true;
|
|
259
|
+
// Older versions kept the cache under .atris/state; read it once so an
|
|
260
|
+
// upgrade does not pay for a cold board.
|
|
261
|
+
const current = readCherryEntries(cache.file);
|
|
262
|
+
const legacy = current ? null : readCherryEntries(path.join(root, LEGACY_CHERRY_CACHE_FILE));
|
|
263
|
+
cache.entries = current || legacy || {};
|
|
264
|
+
cache.dirty = Boolean(legacy);
|
|
244
265
|
return cache;
|
|
245
266
|
}
|
|
246
267
|
|
package/commands/mission.js
CHANGED
|
@@ -4814,11 +4814,12 @@ function startMission(args, options = {}) {
|
|
|
4814
4814
|
lane: saved.lane,
|
|
4815
4815
|
verifier: saved.verifier,
|
|
4816
4816
|
});
|
|
4817
|
-
if (saved.yes_lock_parent && saved.yes_locks && saved.yes_locks.steps) {
|
|
4818
|
-
// the lock record names the job it came from, before any
|
|
4817
|
+
if ((saved.yes_lock_parent || process.env.ATRIS_HANDOFF_YES_LOCK) && saved.yes_locks && saved.yes_locks.steps) {
|
|
4818
|
+
// the lock record names the member and the job it came from, before any
|
|
4819
|
+
// command is held, so a yes card can say who is asking
|
|
4819
4820
|
try {
|
|
4820
4821
|
require('../lib/yes-lock').writeYesLocks(path.join(require('../lib/state-home').atrisHome(), 'state'), saved.id,
|
|
4821
|
-
{ seal: saved.yes_locks.steps, parentMissionId: saved.yes_lock_parent });
|
|
4822
|
+
{ seal: saved.yes_locks.steps, parentMissionId: saved.yes_lock_parent || null, owner: saved.owner || null });
|
|
4822
4823
|
} catch {}
|
|
4823
4824
|
}
|
|
4824
4825
|
const worktreeBaseline = captureMissionWorktreeBaseline(saved, process.cwd(), codeBaseline);
|