atris 3.15.57 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/AGENTS.md +2 -2
  2. package/GETTING_STARTED.md +1 -1
  3. package/PERSONA.md +4 -4
  4. package/README.md +11 -11
  5. package/atris/skills/copy-editor/SKILL.md +30 -4
  6. package/atris/skills/improve/SKILL.md +18 -20
  7. package/atris/wiki/concepts/agent-activation-contract.md +5 -3
  8. package/atris/wiki/concepts/workspace-initialization-contract.md +4 -4
  9. package/atris/wiki/index.md +1 -0
  10. package/ax +522 -73
  11. package/bin/atris.js +31 -30
  12. package/commands/align.js +0 -14
  13. package/commands/apps.js +102 -1
  14. package/commands/autopilot.js +197 -22
  15. package/commands/brain.js +219 -34
  16. package/commands/brainstorm.js +0 -829
  17. package/commands/computer.js +0 -60
  18. package/commands/improve.js +501 -0
  19. package/commands/integrations.js +233 -71
  20. package/commands/lesson.js +44 -0
  21. package/commands/member.js +4498 -226
  22. package/commands/mission.js +302 -27
  23. package/commands/now.js +89 -1
  24. package/commands/radar.js +181 -56
  25. package/commands/soul.js +0 -4
  26. package/commands/task.js +5582 -517
  27. package/commands/terminal.js +14 -10
  28. package/commands/wiki.js +87 -1
  29. package/commands/workflow.js +288 -73
  30. package/commands/worktree.js +52 -15
  31. package/commands/xp.js +6 -65
  32. package/lib/auto-accept-certified.js +294 -0
  33. package/lib/file-ops.js +0 -184
  34. package/lib/member-alive.js +232 -0
  35. package/lib/policy-lessons.js +280 -0
  36. package/lib/receipt-evidence.js +64 -0
  37. package/lib/state-detection.js +34 -0
  38. package/lib/task-db.js +568 -16
  39. package/lib/task-proof.js +43 -0
  40. package/package.json +1 -1
  41. package/utils/auth.js +13 -4
  42. package/commands/research.js +0 -52
  43. package/lib/section-merge.js +0 -196
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const GENERIC_COMPLETION_PROOF_RE = /^(?:done|done now|complete|completed|finished|fixed|handled|ship|shipped|ok|okay|yes|yep|looks good|looks good to me|all set|should be good|works now|approved|approve|lgtm|failed)$/i;
4
+
5
+ const COMMAND_PROOF_RE = /\b(?:npm\s+run|npm\s+test|node\s+--test|node\s+scripts\/|pnpm\b|yarn\b|npx\b|pytest\b|python\s+-m|tsc\b|vite\s+build|git\s+diff\s+--check|curl\b|atris\s+task|\.\/ax\b|ax\s+--|test\s+-s)\b/i;
6
+ const FILE_PROOF_RE = /(?:^|[\s'"`])(?:\.{0,2}\/|~\/|\/Users\/|src\/|scripts\/|atris\/|backend\/|public\/|resources\/|package[.]json|main[.]js|preload[.]js|AGENTXP_PROOF[.]md)[^\s'"`,;)]*/i;
7
+ const PATH_ONLY_PROOF_RE = /(?:^|[\s'"`])(?:\.{0,2}\/|~\/|\/Users\/|\/private\/|\/var\/|atris\/runs\/|\.atris\/state\/)[^\s'"`,;)]+(?:[.](?:json|jsonl|md|log|txt|png|jpg|jpeg|pdf))?/i;
8
+ const RECEIPT_OR_ARTIFACT_RE = /\b(?:receipt|artifact|screenshot|log|trace|path=|file=|bytes=|model=|opened=|https?:\/\/)\b/i;
9
+ const RESULT_PAIR_RE = /\b(?:typecheck|build|smoke|test|pytest|verifier|validation|validated|verified|render|diff|sync|lineage|projection)\b.{0,80}\b(?:pass|passed|failed|green|ok|exit\s*0|reviewed)\b|\b(?:pass|passed|failed|green|ok|exit\s*0|reviewed)\b.{0,80}\b(?:typecheck|build|smoke|test|pytest|verifier|validation|validated|verified|render|diff|sync|lineage|projection)\b/i;
10
+ const HUMAN_PROOF_RE = /\b(?:team human approved|human approved|human approval|approved by|accepted by|reviewed by|customer replied|customer approved|customer accepted|replied)\b/i;
11
+ const FILE_ACTION_RE = /\b(?:changed|updated|edited|created|deleted|saved|wrote|patched|reviewed|verified|validated|opened|read)\b/i;
12
+
13
+ function compactWhitespace(text) {
14
+ return String(text || '').replace(/\s+/g, ' ').trim();
15
+ }
16
+
17
+ function taskProofState(proof) {
18
+ const text = compactWhitespace(proof);
19
+ if (!text) return { ok: false, reason: 'proof required' };
20
+ if (GENERIC_COMPLETION_PROOF_RE.test(text)) {
21
+ return { ok: false, reason: 'proof must name what was verified, changed, approved, or produced' };
22
+ }
23
+ if (COMMAND_PROOF_RE.test(text)) return { ok: true, reason: 'proof names a command' };
24
+ if (HUMAN_PROOF_RE.test(text)) return { ok: true, reason: 'proof names human/customer approval' };
25
+ if (RESULT_PAIR_RE.test(text)) return { ok: true, reason: 'proof names a verifier result' };
26
+ if (PATH_ONLY_PROOF_RE.test(text)) return { ok: true, reason: 'proof names a receipt or artifact path' };
27
+ if (RECEIPT_OR_ARTIFACT_RE.test(text) && (FILE_PROOF_RE.test(text) || RESULT_PAIR_RE.test(text))) {
28
+ return { ok: true, reason: 'proof names a receipt or artifact' };
29
+ }
30
+ if (FILE_PROOF_RE.test(text) && FILE_ACTION_RE.test(text) && RESULT_PAIR_RE.test(text)) {
31
+ return { ok: true, reason: 'proof names changed files and validation' };
32
+ }
33
+ return { ok: false, reason: 'proof needs concrete evidence: command, verifier result, receipt/artifact path, changed file plus validation, or explicit human/customer approval' };
34
+ }
35
+
36
+ function taskProofLooksMeaningful(proof) {
37
+ return taskProofState(proof).ok;
38
+ }
39
+
40
+ module.exports = {
41
+ taskProofLooksMeaningful,
42
+ taskProofState,
43
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.15.57",
3
+ "version": "3.16.0",
4
4
  "main": "bin/atris.js",
5
5
  "bin": {
6
6
  "atris": "bin/atris.js",
package/utils/auth.js CHANGED
@@ -324,7 +324,15 @@ function saveCredentials(token, refreshToken, email, userId, provider) {
324
324
  }
325
325
 
326
326
  function loadCredentials() {
327
- // Priority: ATRIS_PROFILE env var → per-terminal session file → global credentials.json
327
+ // Priority: ATRIS_TOKEN env var → ATRIS_PROFILE env var → per-terminal session file → global credentials.json
328
+
329
+ // 0. Raw token injection. Headless boxes (cloud business computers) have no
330
+ // browser for `atris login`; the runner injects a scoped token as env
331
+ // instead, so no credentials file ever lands on disk.
332
+ const envToken = process.env.ATRIS_TOKEN;
333
+ if (envToken && envToken.trim()) {
334
+ return { token: envToken.trim(), provider: null, source: 'env' };
335
+ }
328
336
 
329
337
  // 1. Explicit env var override
330
338
  const profileOverride = process.env.ATRIS_PROFILE;
@@ -481,9 +489,10 @@ async function ensureValidCredentials(apiRequestJson, options = {}) {
481
489
  const updatedUserId = user?.id || credentials.user_id;
482
490
 
483
491
  if (
484
- updatedEmail !== credentials.email ||
485
- updatedProvider !== credentials.provider ||
486
- updatedUserId !== credentials.user_id
492
+ credentials.source !== 'env' &&
493
+ (updatedEmail !== credentials.email ||
494
+ updatedProvider !== credentials.provider ||
495
+ updatedUserId !== credentials.user_id)
487
496
  ) {
488
497
  saveCredentials(
489
498
  credentials.token,
@@ -1,52 +0,0 @@
1
- const { initResearchWorkspace } = require('./business');
2
-
3
- async function researchQuickstart() {
4
- console.log(`
5
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6
- Start a Research Lab in 3 Commands
7
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8
-
9
- 1. Create:
10
- atris research init "Frontier Lab"
11
-
12
- 2. Open the local workspace:
13
- cd ~/arena/atris-business/frontier-lab
14
-
15
- 3. Push local state to cloud:
16
- atris align --fix
17
-
18
- The research template starts with:
19
- hypotheses + experiment lanes
20
- eval-first reward policy
21
- literature + findings workflow
22
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
23
- `);
24
- }
25
-
26
- async function researchCommand(subcommand, ...args) {
27
- switch (subcommand) {
28
- case 'init':
29
- case 'workspace':
30
- case 'create':
31
- await initResearchWorkspace(args[0], ...args.slice(1));
32
- break;
33
- case 'quickstart':
34
- case 'start':
35
- case 'guide':
36
- await researchQuickstart();
37
- break;
38
- default:
39
- console.log('Usage: atris research <command> [args]');
40
- console.log('');
41
- console.log(' quickstart ← Start here! 3-command guide');
42
- console.log('');
43
- console.log(' init <name> Create a research lab workspace (cloud + local)');
44
- console.log(' workspace <name> Alias for init');
45
- console.log(' create <name> Alias for init');
46
- }
47
- }
48
-
49
- module.exports = {
50
- researchCommand,
51
- researchQuickstart,
52
- };
@@ -1,196 +0,0 @@
1
- /**
2
- * Section-level three-way merge for structured markdown files.
3
- *
4
- * Parses markdown into sections (split on ## headers + YAML frontmatter).
5
- * Merges non-conflicting section changes. Flags same-section conflicts.
6
- *
7
- * This is what makes us better than git for context files.
8
- * Git merges by line. We merge by section.
9
- */
10
-
11
- /**
12
- * Parse a markdown document into sections.
13
- * Returns: { __frontmatter__: string, __header__: string, sections: [{name, content}] }
14
- */
15
- function parseSections(content) {
16
- if (!content) return { frontmatter: '', header: '', sections: [] };
17
-
18
- const lines = content.split('\n');
19
- let frontmatter = '';
20
- let header = '';
21
- const sections = [];
22
- let current = null;
23
- let inFrontmatter = false;
24
- let frontmatterDone = false;
25
- let headerLines = [];
26
-
27
- for (let i = 0; i < lines.length; i++) {
28
- const line = lines[i];
29
-
30
- // YAML frontmatter
31
- if (i === 0 && line.trim() === '---') {
32
- inFrontmatter = true;
33
- headerLines.push(line);
34
- continue;
35
- }
36
- if (inFrontmatter) {
37
- headerLines.push(line);
38
- if (line.trim() === '---') {
39
- inFrontmatter = false;
40
- frontmatterDone = true;
41
- frontmatter = headerLines.join('\n');
42
- headerLines = [];
43
- }
44
- continue;
45
- }
46
-
47
- // Section headers
48
- if (line.startsWith('## ')) {
49
- // Save previous section or header
50
- if (current) {
51
- sections.push(current);
52
- } else if (headerLines.length > 0) {
53
- header = headerLines.join('\n');
54
- }
55
- current = { name: line.substring(3).trim(), content: line };
56
- continue;
57
- }
58
-
59
- // Content belongs to current section or header
60
- if (current) {
61
- current.content += '\n' + line;
62
- } else {
63
- headerLines.push(line);
64
- }
65
- }
66
-
67
- // Save last section or header
68
- if (current) {
69
- sections.push(current);
70
- } else if (headerLines.length > 0 && !header) {
71
- header = headerLines.join('\n');
72
- }
73
-
74
- return { frontmatter, header, sections };
75
- }
76
-
77
- /**
78
- * Reconstruct a markdown document from parsed sections.
79
- */
80
- function reconstructDocument(parsed) {
81
- const parts = [];
82
- if (parsed.frontmatter) parts.push(parsed.frontmatter);
83
- if (parsed.header) parts.push(parsed.header);
84
- for (const section of parsed.sections) {
85
- parts.push(section.content);
86
- }
87
- return parts.join('\n');
88
- }
89
-
90
- /**
91
- * Three-way section merge.
92
- *
93
- * @param {string} base - Common ancestor content
94
- * @param {string} local - Your version
95
- * @param {string} remote - Their version
96
- * @returns {{ merged: string|null, conflicts: [{section, local, remote}] }}
97
- *
98
- * If merged is non-null, the merge succeeded (conflicts array is empty).
99
- * If merged is null, there are conflicts that need manual resolution.
100
- */
101
- function sectionMerge(base, local, remote) {
102
- const baseParsed = parseSections(base);
103
- const localParsed = parseSections(local);
104
- const remoteParsed = parseSections(remote);
105
-
106
- const conflicts = [];
107
-
108
- // Merge frontmatter (field-by-field if both changed, otherwise take the changed one)
109
- let mergedFrontmatter = baseParsed.frontmatter;
110
- if (localParsed.frontmatter !== baseParsed.frontmatter && remoteParsed.frontmatter === baseParsed.frontmatter) {
111
- mergedFrontmatter = localParsed.frontmatter;
112
- } else if (remoteParsed.frontmatter !== baseParsed.frontmatter && localParsed.frontmatter === baseParsed.frontmatter) {
113
- mergedFrontmatter = remoteParsed.frontmatter;
114
- } else if (localParsed.frontmatter !== remoteParsed.frontmatter && localParsed.frontmatter !== baseParsed.frontmatter) {
115
- conflicts.push({ section: 'frontmatter', local: localParsed.frontmatter, remote: remoteParsed.frontmatter });
116
- }
117
-
118
- // Merge header
119
- let mergedHeader = baseParsed.header;
120
- if (localParsed.header !== baseParsed.header && remoteParsed.header === baseParsed.header) {
121
- mergedHeader = localParsed.header;
122
- } else if (remoteParsed.header !== baseParsed.header && localParsed.header === baseParsed.header) {
123
- mergedHeader = remoteParsed.header;
124
- } else if (localParsed.header !== remoteParsed.header && localParsed.header !== baseParsed.header) {
125
- conflicts.push({ section: 'header', local: localParsed.header, remote: remoteParsed.header });
126
- }
127
-
128
- // Build section maps
129
- const baseMap = {};
130
- for (const s of baseParsed.sections) baseMap[s.name] = s.content;
131
- const localMap = {};
132
- for (const s of localParsed.sections) localMap[s.name] = s.content;
133
- const remoteMap = {};
134
- for (const s of remoteParsed.sections) remoteMap[s.name] = s.content;
135
-
136
- // Get all section names preserving order (base order, then new sections)
137
- const allNames = [];
138
- const seen = new Set();
139
- for (const s of baseParsed.sections) { allNames.push(s.name); seen.add(s.name); }
140
- for (const s of localParsed.sections) { if (!seen.has(s.name)) { allNames.push(s.name); seen.add(s.name); } }
141
- for (const s of remoteParsed.sections) { if (!seen.has(s.name)) { allNames.push(s.name); seen.add(s.name); } }
142
-
143
- // Merge each section
144
- const mergedSections = [];
145
- for (const name of allNames) {
146
- const b = baseMap[name] || null;
147
- const l = localMap[name] || null;
148
- const r = remoteMap[name] || null;
149
-
150
- if (l === r) {
151
- // Both same — take either (or null = both deleted)
152
- if (l !== null) mergedSections.push({ name, content: l });
153
- continue;
154
- }
155
-
156
- if (b === null) {
157
- // New section — exists in one or both
158
- if (l && !r) { mergedSections.push({ name, content: l }); continue; }
159
- if (r && !l) { mergedSections.push({ name, content: r }); continue; }
160
- // Both added same-named section with different content
161
- conflicts.push({ section: name, local: l, remote: r });
162
- mergedSections.push({ name, content: l }); // default to local
163
- continue;
164
- }
165
-
166
- const localChanged = l !== b;
167
- const remoteChanged = r !== b;
168
-
169
- if (!localChanged && remoteChanged) {
170
- if (r !== null) mergedSections.push({ name, content: r });
171
- // else: remote deleted it, local didn't change → accept deletion
172
- } else if (localChanged && !remoteChanged) {
173
- if (l !== null) mergedSections.push({ name, content: l });
174
- // else: local deleted it, remote didn't change → accept deletion
175
- } else {
176
- // Both changed the same section → conflict
177
- conflicts.push({ section: name, local: l, remote: r });
178
- if (l !== null) mergedSections.push({ name, content: l }); // default to local
179
- }
180
- }
181
-
182
- if (conflicts.length > 0) {
183
- return { merged: null, conflicts };
184
- }
185
-
186
- // Reconstruct
187
- const merged = reconstructDocument({
188
- frontmatter: mergedFrontmatter,
189
- header: mergedHeader,
190
- sections: mergedSections,
191
- });
192
-
193
- return { merged, conflicts: [] };
194
- }
195
-
196
- module.exports = { parseSections, reconstructDocument, sectionMerge };