atris 3.38.0 → 3.41.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/atris/PERSONA.md +8 -4
- package/atris.md +7 -0
- package/ax +2 -1
- package/bin/atris.js +31 -6
- package/commands/agent-spawn.js +13 -11
- package/commands/autoland.js +28 -77
- package/commands/bench.js +10 -12
- package/commands/business.js +345 -0
- package/commands/chat-scan.js +5 -7
- package/commands/codex-goal.js +8 -10
- package/commands/computer.js +20 -0
- package/commands/console.js +19 -3
- package/commands/decide.js +166 -0
- package/commands/deck.js +1 -4
- package/commands/drill.js +14 -24
- package/commands/engine.js +196 -8
- package/commands/gm.js +8 -6
- package/commands/harvest.js +1 -4
- package/commands/init.js +23 -3
- package/commands/land.js +8 -14
- package/commands/launchpad.js +1 -14
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/member.js +558 -574
- package/commands/mission.js +456 -237
- package/commands/pack.js +2746 -164
- package/commands/play.js +6 -4
- package/commands/probe.js +2 -2
- package/commands/pulse.js +15 -16
- package/commands/release.js +10 -9
- package/commands/router.js +5 -4
- package/commands/site-deploy.js +885 -0
- package/commands/site.js +11 -2
- package/commands/slop.js +14 -2
- package/commands/stream.js +4 -18
- package/commands/task.js +880 -558
- package/commands/taste.js +101 -0
- package/commands/team.js +176 -3
- package/commands/vercel.js +4 -2
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -22
- package/commands/wiki.js +1 -4
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +1 -14
- package/commands/xp.js +27 -24
- package/lib/accept-verify-gate.js +5 -1
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +116 -1
- package/lib/autoland.js +66 -0
- package/lib/bench/runner.js +19 -1
- package/lib/context-gatherer.js +7 -1
- package/lib/engine-registry.js +141 -20
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +65 -15
- package/lib/git-spawn.js +15 -0
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +0 -2
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +4 -1
- package/lib/official-cli-integration.js +47 -2
- package/lib/orb-context.js +8 -1
- package/lib/pack-capabilities.js +685 -0
- package/lib/router-brain.js +51 -1
- package/lib/runner-command.js +0 -6
- package/lib/self-drive.js +44 -13
- package/lib/task-db.js +137 -3
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +66 -0
- package/lib/wish-audit.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/zip.js +95 -7
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const ACTIVE_STATUSES = new Set(['observed', 'open', 'attempted']);
|
|
7
|
+
const KEYWORD_STOP_WORDS = new Set([
|
|
8
|
+
'and', 'are', 'for', 'from', 'into', 'not', 'of', 'on', 'or', 'the', 'this', 'to', 'with', 'without',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
function readJson(filePath) {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
14
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
15
|
+
} catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readLessonText(filePath) {
|
|
21
|
+
let content = '';
|
|
22
|
+
try {
|
|
23
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
24
|
+
} catch {
|
|
25
|
+
return new Map();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const bySlug = new Map();
|
|
29
|
+
for (const line of content.split(/\r?\n/)) {
|
|
30
|
+
const match = line.match(/^\s*-\s*\*\*\[([^\]]+)\]\s*([a-z0-9][a-z0-9-]*)\*\*\s*[—:-]+\s*(?:pass|fail)?\s*[—:-]*\s*(.*)$/i);
|
|
31
|
+
if (!match || bySlug.has(match[2])) continue;
|
|
32
|
+
const text = match[3].replace(/^\[resolved\]\s*/i, '').trim();
|
|
33
|
+
if (text) bySlug.set(match[2], text);
|
|
34
|
+
}
|
|
35
|
+
return bySlug;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isPromptRule(mechanism) {
|
|
39
|
+
if (mechanism && typeof mechanism === 'object') {
|
|
40
|
+
const kind = String(mechanism.type || mechanism.kind || '').toLowerCase();
|
|
41
|
+
return kind === 'prompt-rule' || kind === 'prompt_rule' || mechanism.prompt_rule === true;
|
|
42
|
+
}
|
|
43
|
+
return /^\s*prompt[-_\s]?rule\b/i.test(String(mechanism || ''));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function lessonIsEligible(lesson) {
|
|
47
|
+
const status = String(lesson && lesson.status || '').toLowerCase();
|
|
48
|
+
if (ACTIVE_STATUSES.has(status)) return true;
|
|
49
|
+
return status === 'resolved' && isPromptRule(lesson.mechanism);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeFile(file) {
|
|
53
|
+
return String(file || '')
|
|
54
|
+
.trim()
|
|
55
|
+
.replace(/\\/g, '/')
|
|
56
|
+
.replace(/^\.\//, '')
|
|
57
|
+
.replace(/\/{2,}/g, '/')
|
|
58
|
+
.replace(/\/$/, '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function pathSpecificity(file) {
|
|
62
|
+
const normalized = normalizeFile(file);
|
|
63
|
+
const segments = normalized.split('/').filter(Boolean).length;
|
|
64
|
+
return (segments * 100) + normalized.length;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function overlapStrength(appliesTo, file) {
|
|
68
|
+
const lessonPath = normalizeFile(appliesTo).toLowerCase();
|
|
69
|
+
const taskPath = normalizeFile(file).toLowerCase();
|
|
70
|
+
if (!lessonPath || !taskPath) return 0;
|
|
71
|
+
if (lessonPath === taskPath) return 3;
|
|
72
|
+
if (taskPath.endsWith(`/${lessonPath}`) || lessonPath.endsWith(`/${taskPath}`)) return 3;
|
|
73
|
+
if (taskPath.startsWith(`${lessonPath}/`) || lessonPath.startsWith(`${taskPath}/`)) return 2;
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function slugKeywords(slug) {
|
|
78
|
+
return [...new Set(String(slug || '').toLowerCase().split(/[^a-z0-9]+/)
|
|
79
|
+
.filter((word) => word.length >= 3 && !KEYWORD_STOP_WORDS.has(word)))];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function keywordAppears(text, keyword) {
|
|
83
|
+
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
84
|
+
return new RegExp(`(^|[^a-z0-9])${escaped}([^a-z0-9]|$)`, 'i').test(text);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function bestMatch({ slug, lesson, briefText, files }) {
|
|
88
|
+
const brief = String(briefText || '');
|
|
89
|
+
const briefLower = brief.toLowerCase().replace(/\\/g, '/');
|
|
90
|
+
const appliesTo = Array.isArray(lesson.applies_to) ? lesson.applies_to : [];
|
|
91
|
+
let best = null;
|
|
92
|
+
|
|
93
|
+
for (const lessonFile of appliesTo) {
|
|
94
|
+
const normalizedLessonFile = normalizeFile(lessonFile);
|
|
95
|
+
if (!normalizedLessonFile) continue;
|
|
96
|
+
for (const taskFile of files) {
|
|
97
|
+
const strength = overlapStrength(normalizedLessonFile, taskFile);
|
|
98
|
+
if (!strength) continue;
|
|
99
|
+
const score = 300000 + (strength * 10000) + pathSpecificity(normalizedLessonFile) - appliesTo.length;
|
|
100
|
+
if (!best || score > best.score) {
|
|
101
|
+
best = { score, why: `file overlaps ${normalizedLessonFile}` };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (briefLower.includes(normalizedLessonFile.toLowerCase())) {
|
|
105
|
+
const score = 200000 + pathSpecificity(normalizedLessonFile) - appliesTo.length;
|
|
106
|
+
if (!best || score > best.score) {
|
|
107
|
+
best = { score, why: `brief mentions ${normalizedLessonFile}` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const matchedKeywords = slugKeywords(slug).filter((keyword) => keywordAppears(brief, keyword));
|
|
113
|
+
if (matchedKeywords.length) {
|
|
114
|
+
const keywordLength = matchedKeywords.reduce((total, keyword) => total + keyword.length, 0);
|
|
115
|
+
const score = 100000 + (matchedKeywords.length * 1000) + keywordLength;
|
|
116
|
+
if (!best || score > best.score) {
|
|
117
|
+
best = { score, why: `brief mentions ${matchedKeywords.join(', ')}` };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return best;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function matchLessons({ briefText = '', files = [], root = process.cwd() } = {}) {
|
|
125
|
+
const metadata = readJson(path.join(root, 'atris', 'lessons.json'));
|
|
126
|
+
const lessonText = readLessonText(path.join(root, 'atris', 'lessons.md'));
|
|
127
|
+
const normalizedFiles = (Array.isArray(files) ? files : [files]).map(normalizeFile).filter(Boolean);
|
|
128
|
+
const matches = [];
|
|
129
|
+
|
|
130
|
+
for (const [slug, lesson] of Object.entries(metadata)) {
|
|
131
|
+
if (slug === '_schema' || !lesson || typeof lesson !== 'object') continue;
|
|
132
|
+
if (!lessonIsEligible(lesson)) continue;
|
|
133
|
+
const text = lessonText.get(slug);
|
|
134
|
+
if (!text) continue;
|
|
135
|
+
const matched = bestMatch({ slug, lesson, briefText, files: normalizedFiles });
|
|
136
|
+
if (!matched) continue;
|
|
137
|
+
matches.push({ slug, text, why_matched: matched.why, score: matched.score });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return matches
|
|
141
|
+
.sort((left, right) => right.score - left.score || left.slug.localeCompare(right.slug))
|
|
142
|
+
.slice(0, 5)
|
|
143
|
+
.map(({ slug, text, why_matched }) => ({ slug, text, why_matched }));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { matchLessons };
|
package/lib/loop-doctor.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function normalizeHumanAsk(ask) {
|
|
4
|
+
if (typeof ask === 'string') {
|
|
5
|
+
return { text: ask, answered_at: null };
|
|
6
|
+
}
|
|
7
|
+
if (!ask || typeof ask !== 'object' || Array.isArray(ask)) {
|
|
8
|
+
return { text: String(ask || ''), answered_at: null };
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
...ask,
|
|
12
|
+
text: String(ask.text || ''),
|
|
13
|
+
answered_at: ask.answered_at || null,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeHumanAsks(asks) {
|
|
18
|
+
return Array.isArray(asks) ? asks.map(normalizeHumanAsk) : [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function openHumanAsks(asks) {
|
|
22
|
+
return normalizeHumanAsks(asks).filter((ask) => ask.text.trim() && !ask.answered_at);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
normalizeHumanAsks,
|
|
27
|
+
openHumanAsks,
|
|
28
|
+
};
|
|
@@ -127,6 +127,7 @@ function defaultGit(gitBin = 'git') {
|
|
|
127
127
|
cwd: root,
|
|
128
128
|
encoding: 'utf8',
|
|
129
129
|
timeout: 10000,
|
|
130
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
130
131
|
});
|
|
131
132
|
}
|
|
132
133
|
|
|
@@ -167,7 +168,8 @@ function protectedPause(reason, detail = {}) {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
function unreadableMissionGuard(error) {
|
|
170
|
-
return protectedPause('mission diff could not be read;
|
|
171
|
+
return protectedPause('mission diff could not be read; repair the tooling failure and rerun', {
|
|
172
|
+
status: 'paused-for-retry',
|
|
171
173
|
unreadable: true,
|
|
172
174
|
detail: error?.message || String(error || 'unknown diff read failure'),
|
|
173
175
|
surfaces: ['unreadable diff'],
|
|
@@ -285,6 +287,7 @@ function prepareMissionGitGuard({
|
|
|
285
287
|
}
|
|
286
288
|
|
|
287
289
|
module.exports = {
|
|
290
|
+
defaultGit,
|
|
288
291
|
matchProtectedMissionDiff,
|
|
289
292
|
unreadableMissionGuard,
|
|
290
293
|
inspectMissionProtectedDiff,
|
|
@@ -67,6 +67,10 @@ function printHelp(config) {
|
|
|
67
67
|
for (const command of config.commands) {
|
|
68
68
|
console.log(` ${command.usage.padEnd(13)} ${command.description}`);
|
|
69
69
|
}
|
|
70
|
+
if (config.commands.some((command) => command.summarize)) {
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log(' --raw print the full cli output instead of the grouped summary');
|
|
73
|
+
}
|
|
70
74
|
console.log('');
|
|
71
75
|
console.log(`binary: ${config.binary}`);
|
|
72
76
|
console.log(`install: ${config.installHint}`);
|
|
@@ -115,14 +119,54 @@ function matchCommand(config, args) {
|
|
|
115
119
|
});
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
const SUMMARY_SAMPLE_LINES = 5;
|
|
123
|
+
|
|
124
|
+
function summaryLines(result) {
|
|
125
|
+
const stdout = String(result.stdout || '').split(/\r?\n/);
|
|
126
|
+
const stderr = String(result.stderr || '').split(/\r?\n/);
|
|
127
|
+
return { stdout, stderr, all: [...stdout, ...stderr].filter((line) => line.trim()) };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function printOutputSummary(config, cliArgs, result) {
|
|
131
|
+
const status = result.status ?? 0;
|
|
132
|
+
const { all } = summaryLines(result);
|
|
133
|
+
const errors = all.filter((line) => /\berror\b|\bfailed\b|\bfatal\b/i.test(line));
|
|
134
|
+
const warnings = all.filter((line) => /\bwarn/i.test(line));
|
|
135
|
+
const links = [...new Set(all.flatMap((line) => line.match(/https?:\/\/\S+/g) || []))];
|
|
136
|
+
const label = `${config.name} ${cliArgs.join(' ')}`.trim();
|
|
137
|
+
|
|
138
|
+
console.log(`${label}: ${status === 0 ? 'ok' : `failed (exit ${status})`}, ${all.length} output lines`);
|
|
139
|
+
console.log('');
|
|
140
|
+
if (links.length) {
|
|
141
|
+
console.log(`links (${links.length})`);
|
|
142
|
+
for (const link of links.slice(0, SUMMARY_SAMPLE_LINES)) console.log(` ${link}`);
|
|
143
|
+
console.log('');
|
|
144
|
+
}
|
|
145
|
+
console.log(errors.length ? `errors (${errors.length})` : 'errors: none');
|
|
146
|
+
for (const line of errors.slice(0, SUMMARY_SAMPLE_LINES)) console.log(` ${line.trim()}`);
|
|
147
|
+
if (errors.length) console.log('');
|
|
148
|
+
console.log(warnings.length ? `warnings (${warnings.length})` : 'warnings: none');
|
|
149
|
+
for (const line of warnings.slice(0, SUMMARY_SAMPLE_LINES)) console.log(` ${line.trim()}`);
|
|
150
|
+
if (warnings.length) console.log('');
|
|
151
|
+
const tail = all.slice(-SUMMARY_SAMPLE_LINES);
|
|
152
|
+
if (tail.length) {
|
|
153
|
+
console.log('last lines');
|
|
154
|
+
for (const line of tail) console.log(` ${line.trim()}`);
|
|
155
|
+
console.log('');
|
|
156
|
+
}
|
|
157
|
+
console.log(`full output: atris ${label} --raw`);
|
|
158
|
+
}
|
|
159
|
+
|
|
118
160
|
function runPassthrough(config, command, args) {
|
|
119
161
|
const binary = ensureBinary(config);
|
|
120
162
|
if (!binary.ok) return 1;
|
|
121
163
|
|
|
122
|
-
const
|
|
164
|
+
const raw = args.includes('--raw');
|
|
165
|
+
const rest = args.slice(command.match.length).filter((arg) => arg !== '--raw');
|
|
123
166
|
const cliArgs = [...command.forward, ...rest];
|
|
167
|
+
const summarize = command.summarize && !raw;
|
|
124
168
|
const result = spawnSync(config.binary, cliArgs, {
|
|
125
|
-
stdio: 'inherit',
|
|
169
|
+
...(summarize ? { encoding: 'utf8' } : { stdio: 'inherit' }),
|
|
126
170
|
env: process.env,
|
|
127
171
|
});
|
|
128
172
|
if (missingBinary(result)) {
|
|
@@ -133,6 +177,7 @@ function runPassthrough(config, command, args) {
|
|
|
133
177
|
console.error(`${config.name} command failed: ${result.error.message}`);
|
|
134
178
|
return 1;
|
|
135
179
|
}
|
|
180
|
+
if (summarize) printOutputSummary(config, cliArgs, result);
|
|
136
181
|
return result.status ?? 0;
|
|
137
182
|
}
|
|
138
183
|
|
package/lib/orb-context.js
CHANGED
|
@@ -13,7 +13,13 @@ const BACKLOG_STATUSES = new Set(['open', 'backlog', 'inbox', 'planned', 'plan',
|
|
|
13
13
|
function cleanTitle(value, max = 64) {
|
|
14
14
|
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
15
15
|
if (!text) return '';
|
|
16
|
-
|
|
16
|
+
if (text.length <= max) return text;
|
|
17
|
+
const slice = text.slice(0, max - 1);
|
|
18
|
+
const lastSpace = slice.lastIndexOf(' ');
|
|
19
|
+
// Cut on a word boundary so a title never ends on half a word ("...the owne…").
|
|
20
|
+
// Fall back to the hard slice only when a single word already fills the limit.
|
|
21
|
+
const body = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
|
|
22
|
+
return `${body.replace(/[\s,;:.!?-]+$/, '')}…`;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
function asRecord(value) {
|
|
@@ -475,6 +481,7 @@ async function appendOrbChoice(root, label, when = new Date()) {
|
|
|
475
481
|
}
|
|
476
482
|
|
|
477
483
|
module.exports = {
|
|
484
|
+
cleanTitle,
|
|
478
485
|
collectOrbContext,
|
|
479
486
|
appendOrbChoice,
|
|
480
487
|
};
|