atris 3.58.7 → 3.59.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 +3 -2
- package/GETTING_STARTED.md +2 -2
- package/PERSONA.md +11 -11
- package/README.md +1 -1
- package/atris/GEMINI.md +7 -6
- package/atris/PERSONA.md +4 -4
- package/atris/features/README.md +77 -6
- package/atris/skills/README.md +44 -15
- package/atris/skills/aeo/SKILL.md +10 -10
- package/atris/skills/atris/SKILL.md +1 -1
- package/atris/skills/engines/SKILL.md +69 -50
- package/atris/skills/meta/SKILL.md +4 -4
- package/bin/atris.js +8 -6
- package/commands/aeo.js +9 -12
- package/commands/analytics.js +8 -11
- package/commands/autopilot.js +79 -8
- package/commands/avail.js +1 -1
- package/commands/brain.js +3 -1
- package/commands/business.js +13 -8
- package/commands/clean.js +17 -0
- package/commands/close.js +2 -1
- package/commands/computer.js +15 -10
- package/commands/context-sync.js +3 -3
- package/commands/doc-health.js +55 -2
- package/commands/engine.js +384 -4
- package/commands/experiments.js +13 -4
- package/commands/host.js +53 -0
- package/commands/improve.js +1 -1
- package/commands/integrations.js +2 -5
- package/commands/learn.js +1 -1
- package/commands/member.js +231 -53
- package/commands/mission.js +129 -19
- package/commands/one-lap.js +58 -3
- package/commands/pack.js +2 -2
- package/commands/probe.js +11 -5
- package/commands/pull.js +1 -1
- package/commands/push.js +1 -1
- package/commands/rainmaker.js +3 -16
- package/commands/run.js +4 -1
- package/commands/scout.js +1 -1
- package/commands/search.js +5 -3
- package/commands/spaceship.js +1 -1
- package/commands/stream.js +27 -2
- package/commands/study.js +15 -9
- package/commands/sync.js +1 -1
- package/commands/task.js +13 -2
- package/commands/unknowns.js +5 -0
- package/commands/verify.js +12 -4
- package/commands/visualize.js +20 -3
- package/commands/workflow.js +23 -7
- package/lib/codex-flight.js +28 -5
- package/lib/dispatch-scout.js +31 -22
- package/lib/engine-registry.js +1482 -13
- package/lib/feedback-triage.js +2 -0
- package/lib/fleet.js +111 -12
- package/lib/host.js +336 -0
- package/lib/improve-vitals-html.js +3 -2
- package/lib/known-commands.js +1 -1
- package/lib/lesson-contradiction.js +2 -2
- package/lib/map-refs.js +226 -0
- package/lib/member-engine.js +253 -0
- package/lib/mission-room.js +4 -4
- package/lib/noninteractive.js +15 -4
- package/lib/policy-lessons.js +1 -1
- package/lib/receipt-block.js +137 -28
- package/lib/roster-markdown.js +206 -0
- package/lib/roster-models.js +179 -0
- package/lib/runner-command.js +95 -15
- package/lib/scorecard.js +4 -4
- package/lib/self-drive.js +9 -1
- package/lib/sleep-sync.js +11 -0
- package/lib/state-detection.js +28 -9
- package/lib/task-db.js +3 -2
- package/lib/todo-fallback.js +1 -1
- package/lib/wish-audit.js +10 -6
- package/lib/wish-delegate.js +29 -5
- package/mcp/atris-mcp/index.mjs +77 -24
- package/package.json +2 -5
- package/scripts/det/det.js +2 -2
- package/scripts/det/pr-description.js +2 -2
- package/scripts/det/test.js +2 -2
- package/templates/members/host/MEMBER.md +23 -0
- package/templates/members/host/MISSION.md +11 -0
- package/templates/members/host/SOUL.md +7 -0
- package/templates/members/host/context/why.md +3 -0
- package/templates/members/host/goals.json +5 -0
- package/templates/members/host/goals.md +3 -0
- package/templates/members/host/skills/host/SKILL.md +13 -0
- package/utils/api.js +1 -1
- package/utils/auth.js +17 -7
- package/utils/backend-root.js +31 -0
- package/utils/config.js +5 -4
- package/utils/owner-identity.js +61 -0
- package/atris/policies/LESSONS.md +0 -13
- package/atris/skills/autopilot/atris-autopilot.md +0 -71
package/commands/sync.js
CHANGED
|
@@ -615,7 +615,7 @@ Detect: Project has \`atris/\` folder with MAP.md, TODO.md, PERSONA.md
|
|
|
615
615
|
Commands: plan → do → review
|
|
616
616
|
|
|
617
617
|
Key behaviors:
|
|
618
|
-
- Read PERSONA.md (
|
|
618
|
+
- Read PERSONA.md (MAP.md first, plain words, lead with the outcome)
|
|
619
619
|
- Check MAP.md for file:line refs
|
|
620
620
|
- Use \`atris task\` for claims, proof, ready, and accept
|
|
621
621
|
- Give every task a plain first layer: what changes, why it matters, and what
|
package/commands/task.js
CHANGED
|
@@ -634,6 +634,12 @@ function goldenPathMissionXpTask(task) {
|
|
|
634
634
|
&& /\b(?:golden[- ]path|zero[- ]knowledge|zero[- ]papercuts?|fresh[- ](?:laptop|environment|install|home)|self[- ]landed)\b/i.test(text);
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
+
// A receipt that cannot be read drops out of review evidence; name it under
|
|
638
|
+
// ATRIS_DEBUG so the gap is findable, matching lib/todo.js.
|
|
639
|
+
function debugUnreadableReceipt(rel, err) {
|
|
640
|
+
if (process.env.ATRIS_DEBUG) console.error(`[task] receipt unreadable, skipped: ${rel}: ${err && err.message}`);
|
|
641
|
+
}
|
|
642
|
+
|
|
637
643
|
function receiptTextForProof(proof, root = process.cwd()) {
|
|
638
644
|
const chunks = [];
|
|
639
645
|
const pattern = new RegExp(RECEIPT_PATH_PATTERN.source, 'g');
|
|
@@ -652,7 +658,9 @@ function receiptTextForProof(proof, root = process.cwd()) {
|
|
|
652
658
|
last_landing: parsed.last_landing || null,
|
|
653
659
|
summary: parsed.summary || null,
|
|
654
660
|
}).slice(0, 12000));
|
|
655
|
-
} catch {
|
|
661
|
+
} catch (err) {
|
|
662
|
+
debugUnreadableReceipt(rel, err);
|
|
663
|
+
}
|
|
656
664
|
}
|
|
657
665
|
return chunks.join(' ');
|
|
658
666
|
}
|
|
@@ -672,7 +680,10 @@ function missionReceiptResultForProof(task, proof, root = process.cwd()) {
|
|
|
672
680
|
try {
|
|
673
681
|
const parsed = JSON.parse(fs.readFileSync(path.resolve(root, rel), 'utf8'));
|
|
674
682
|
landing = parsed?.result?.landing || parsed?.landing || parsed?.last_landing || null;
|
|
675
|
-
} catch {
|
|
683
|
+
} catch (err) {
|
|
684
|
+
debugUnreadableReceipt(rel, err);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
676
687
|
const changed = String(landing?.changed || landing?.happened || '').replace(/\s+/g, ' ').trim();
|
|
677
688
|
if (!changed) continue;
|
|
678
689
|
if (/\brecorded tick \d+\.?$/i.test(changed) || /^recorded a proof heartbeat\b/i.test(changed)) continue;
|
package/commands/unknowns.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { mapNotesPath } = require('../lib/map-refs');
|
|
5
6
|
const { spawnSync } = require('child_process');
|
|
6
7
|
const taskDb = require('../lib/task-db');
|
|
7
8
|
const {
|
|
@@ -167,6 +168,8 @@ function formatTaskEvents(rows) {
|
|
|
167
168
|
|
|
168
169
|
function gatherTerritoryContext(root, db) {
|
|
169
170
|
const map = readFileBestEffort(path.join(root, 'atris', 'MAP.md'), { lines: 100 });
|
|
171
|
+
const notesPath = mapNotesPath(root);
|
|
172
|
+
const notes = notesPath ? readFileBestEffort(path.join(root, notesPath), { lines: 100 }) : '';
|
|
170
173
|
const lessonsMd = readFileBestEffort(path.join(root, 'atris', 'lessons.md'), { maxChars: READ_LIMIT });
|
|
171
174
|
const lessonsJson = readFileBestEffort(path.join(root, 'atris', 'lessons.json'), { maxChars: READ_LIMIT });
|
|
172
175
|
const journals = readRecentJournals(root);
|
|
@@ -176,6 +179,7 @@ function gatherTerritoryContext(root, db) {
|
|
|
176
179
|
return [
|
|
177
180
|
section('workspace_root', root),
|
|
178
181
|
section('atris/MAP.md first 100 lines', map),
|
|
182
|
+
...(notesPath ? [section(`${notesPath} first 100 lines`, notes)] : []),
|
|
179
183
|
section('atris/lessons.md', lessonsMd),
|
|
180
184
|
section('atris/lessons.json', lessonsJson),
|
|
181
185
|
section('last 3 daily journals', journals),
|
|
@@ -614,4 +618,5 @@ module.exports = {
|
|
|
614
618
|
unknownsCommand,
|
|
615
619
|
showHelp,
|
|
616
620
|
fallbackPayload,
|
|
621
|
+
gatherTerritoryContext,
|
|
617
622
|
};
|
package/commands/verify.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { spawnSync } = require('child_process');
|
|
4
4
|
const escapeRegExp = require('../lib/escape-regexp');
|
|
5
|
+
const { existingMapDocs } = require('../lib/map-refs');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* atris verify [task] - Validate work is actually done
|
|
@@ -397,13 +398,13 @@ function checkDocsVsChanges(cwd, atrisDir) {
|
|
|
397
398
|
return result;
|
|
398
399
|
}
|
|
399
400
|
|
|
400
|
-
// Check if significant files are in MAP.md
|
|
401
|
+
// Check if significant files are in MAP.md or the notes file it points to
|
|
401
402
|
const mapFile = path.join(atrisDir, 'MAP.md');
|
|
402
403
|
if (!fs.existsSync(mapFile)) {
|
|
403
404
|
return result;
|
|
404
405
|
}
|
|
405
406
|
|
|
406
|
-
const mapContent =
|
|
407
|
+
const mapContent = mapDocsText(atrisDir);
|
|
407
408
|
|
|
408
409
|
const significantExtensions = ['.js', '.ts', '.py', '.go', '.rs', '.rb', '.java'];
|
|
409
410
|
const significantChanges = changedFiles.filter(f =>
|
|
@@ -547,6 +548,12 @@ function verifyChange(cwd, change) {
|
|
|
547
548
|
};
|
|
548
549
|
}
|
|
549
550
|
|
|
551
|
+
// A file counts as documented when the boot map or atris/refs/MAP-NOTES.md names it.
|
|
552
|
+
function mapDocsText(atrisDir) {
|
|
553
|
+
const root = path.dirname(atrisDir);
|
|
554
|
+
return existingMapDocs(root).map((doc) => fs.readFileSync(path.join(root, doc), 'utf8')).join('\n');
|
|
555
|
+
}
|
|
556
|
+
|
|
550
557
|
/**
|
|
551
558
|
* Check if MAP.md documents the expected files
|
|
552
559
|
*/
|
|
@@ -556,7 +563,7 @@ function checkMapForFiles(atrisDir, changes) {
|
|
|
556
563
|
return { documented: false };
|
|
557
564
|
}
|
|
558
565
|
|
|
559
|
-
const content =
|
|
566
|
+
const content = mapDocsText(atrisDir);
|
|
560
567
|
const fileChanges = changes.filter(c => c.type === 'file');
|
|
561
568
|
|
|
562
569
|
if (fileChanges.length === 0) {
|
|
@@ -725,5 +732,6 @@ module.exports = {
|
|
|
725
732
|
escapeRegExp,
|
|
726
733
|
mapCoverage,
|
|
727
734
|
isPathCovered,
|
|
728
|
-
normalizeMapPath
|
|
735
|
+
normalizeMapPath,
|
|
736
|
+
checkMapForFiles,
|
|
729
737
|
};
|
package/commands/visualize.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { mapNotesPath } = require('../lib/map-refs');
|
|
3
4
|
const { spawnSync } = require('child_process');
|
|
4
5
|
const { getLogPath } = require('../lib/journal');
|
|
5
6
|
const { ensureValidCredentials } = require('../utils/auth');
|
|
@@ -153,14 +154,16 @@ function readBusinessMeta(cwd = process.cwd()) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
function promptWords(prompt) {
|
|
158
|
+
return new Set(String(prompt || '').toLowerCase().split(/[^a-z0-9]+/).filter(w => w.length >= 4));
|
|
159
|
+
}
|
|
160
|
+
|
|
156
161
|
function findRelevantContextFiles(cwd, prompt) {
|
|
157
162
|
const roots = [
|
|
158
163
|
path.join(cwd, 'atris', 'context'),
|
|
159
164
|
path.join(cwd, 'atris', 'wiki'),
|
|
160
165
|
];
|
|
161
|
-
const words =
|
|
162
|
-
prompt.toLowerCase().split(/[^a-z0-9]+/).filter(w => w.length >= 4)
|
|
163
|
-
);
|
|
166
|
+
const words = promptWords(prompt);
|
|
164
167
|
const files = [];
|
|
165
168
|
|
|
166
169
|
function walk(dir) {
|
|
@@ -192,6 +195,17 @@ function findRelevantContextFiles(cwd, prompt) {
|
|
|
192
195
|
.slice(0, 4);
|
|
193
196
|
}
|
|
194
197
|
|
|
198
|
+
// The deep map is large: keep only the lines that share a word with the prompt.
|
|
199
|
+
function mapNotesExcerpt(cwd, prompt, maxChars) {
|
|
200
|
+
const notesPath = mapNotesPath(cwd);
|
|
201
|
+
if (!notesPath) return '';
|
|
202
|
+
const words = promptWords(prompt);
|
|
203
|
+
if (!words.size) return '';
|
|
204
|
+
const lines = fs.readFileSync(path.join(cwd, notesPath), 'utf8').split(/\r?\n/)
|
|
205
|
+
.filter(line => [...words].some(word => line.toLowerCase().includes(word)));
|
|
206
|
+
return lines.join('\n').slice(0, maxChars);
|
|
207
|
+
}
|
|
208
|
+
|
|
195
209
|
function collectWorkspaceContext(prompt, cwd = process.cwd()) {
|
|
196
210
|
const business = readBusinessMeta(cwd);
|
|
197
211
|
const chunks = [];
|
|
@@ -201,6 +215,8 @@ function collectWorkspaceContext(prompt, cwd = process.cwd()) {
|
|
|
201
215
|
|
|
202
216
|
const mapSnippet = readTextIfExists(path.join(cwd, 'atris', 'MAP.md'), 1400);
|
|
203
217
|
if (mapSnippet) chunks.push(`MAP excerpt:\n${mapSnippet}`);
|
|
218
|
+
const notesSnippet = mapNotesExcerpt(cwd, prompt, 1400);
|
|
219
|
+
if (notesSnippet) chunks.push(`MAP notes excerpt:\n${notesSnippet}`);
|
|
204
220
|
|
|
205
221
|
const todoSnippet = readTextIfExists(path.join(cwd, 'atris', 'TODO.md'), 1000);
|
|
206
222
|
if (todoSnippet) chunks.push(`TODO excerpt:\n${todoSnippet}`);
|
|
@@ -389,4 +405,5 @@ async function visualizeAtris(args = process.argv.slice(3)) {
|
|
|
389
405
|
|
|
390
406
|
module.exports = {
|
|
391
407
|
visualizeAtris,
|
|
408
|
+
collectWorkspaceContext,
|
|
392
409
|
};
|
package/commands/workflow.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { mapNotesPath } = require('../lib/map-refs');
|
|
3
4
|
const { getLogPath } = require('../lib/journal');
|
|
4
5
|
const {
|
|
5
6
|
buildFirstMinute,
|
|
@@ -21,6 +22,10 @@ const { loadContext } = require('../lib/state-detection');
|
|
|
21
22
|
const { buildToolResultBody } = require('../lib/tool-result-encode');
|
|
22
23
|
const { commitReviewLearning } = require('./learn');
|
|
23
24
|
|
|
25
|
+
function reviewLearningLine(timestamp, answer) {
|
|
26
|
+
return `- ${timestamp} - ${String(answer).trim().replace(/\s*\u2014\s*/g, ', ')}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
function appendReviewLearningToJournal(journalContent, learning) {
|
|
25
30
|
const text = String(journalContent || '').replace(/\r\n/g, '\n');
|
|
26
31
|
const note = String(learning || '');
|
|
@@ -487,6 +492,19 @@ async function runAtris2Local(userInput, atris2Mode) {
|
|
|
487
492
|
}
|
|
488
493
|
}
|
|
489
494
|
|
|
495
|
+
// Where the agent finds code. When the short routing map has a notes file,
|
|
496
|
+
// point line lookups there so the agent does not hunt for refs the map lacks.
|
|
497
|
+
function mapPromptSection(targetDir, cwd = process.cwd()) {
|
|
498
|
+
const mapFile = path.join(targetDir, 'MAP.md');
|
|
499
|
+
if (!fs.existsSync(mapFile)) return '';
|
|
500
|
+
const mapRef = path.relative(cwd, mapFile);
|
|
501
|
+
const notes = mapNotesPath(path.dirname(targetDir));
|
|
502
|
+
if (!notes) return `## MAP.md\nRead this file for file:line references: ${mapRef}\n\n`;
|
|
503
|
+
const notesRef = path.relative(cwd, path.join(path.dirname(targetDir), notes));
|
|
504
|
+
return `## MAP.md\nRead this file to find where work lives: ${mapRef}\n\n`
|
|
505
|
+
+ `## MAP-NOTES.md\nRead this file for exact file:line references: ${notesRef}\n\n`;
|
|
506
|
+
}
|
|
507
|
+
|
|
490
508
|
async function planAtris(userInput = null) {
|
|
491
509
|
const { loadConfig } = require('../utils/config');
|
|
492
510
|
const { loadCredentials, ensureValidCredentials } = require('../utils/auth');
|
|
@@ -769,9 +787,7 @@ async function planAtris(userInput = null) {
|
|
|
769
787
|
systemPrompt += '## PERSONA.md\n' + fs.readFileSync(personaPath, 'utf8') + '\n\n';
|
|
770
788
|
}
|
|
771
789
|
|
|
772
|
-
|
|
773
|
-
systemPrompt += `## MAP.md\nRead this file for file:line references: ${mapFileRef}\n\n`;
|
|
774
|
-
}
|
|
790
|
+
systemPrompt += mapPromptSection(targetDir);
|
|
775
791
|
|
|
776
792
|
// Build user prompt with context
|
|
777
793
|
let userPrompt = `You are the Navigator. Take ideas from Inbox → break them down into perfect, manageable tasks.\n\n`;
|
|
@@ -1249,9 +1265,7 @@ async function doAtris() {
|
|
|
1249
1265
|
if (persona) {
|
|
1250
1266
|
systemPrompt += '## PERSONA.md\n' + persona + '\n\n';
|
|
1251
1267
|
}
|
|
1252
|
-
|
|
1253
|
-
systemPrompt += `## MAP.md\nRead this file for file:line references: ${mapPath}\n\n`;
|
|
1254
|
-
}
|
|
1268
|
+
systemPrompt += mapPromptSection(targetDir);
|
|
1255
1269
|
if (profile) {
|
|
1256
1270
|
systemPrompt += `## PROJECT CONTEXT\nType: ${context}\nProfile: ${JSON.stringify(profile, null, 2)}\n\n`;
|
|
1257
1271
|
}
|
|
@@ -1782,7 +1796,7 @@ async function reviewAtris() {
|
|
|
1782
1796
|
if (fs.existsSync(logFile)) {
|
|
1783
1797
|
let journalContent = fs.readFileSync(logFile, 'utf8');
|
|
1784
1798
|
const timestamp = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
1785
|
-
const learning =
|
|
1799
|
+
const learning = reviewLearningLine(timestamp, answer);
|
|
1786
1800
|
|
|
1787
1801
|
journalContent = appendReviewLearningToJournal(journalContent, learning);
|
|
1788
1802
|
|
|
@@ -1894,10 +1908,12 @@ async function executeAgentSDKFast(userInput) {
|
|
|
1894
1908
|
}
|
|
1895
1909
|
|
|
1896
1910
|
module.exports = {
|
|
1911
|
+
mapPromptSection,
|
|
1897
1912
|
planAtris,
|
|
1898
1913
|
doAtris,
|
|
1899
1914
|
reviewAtris,
|
|
1900
1915
|
appendReviewLearningToJournal,
|
|
1916
|
+
reviewLearningLine,
|
|
1901
1917
|
renderReviewMinute,
|
|
1902
1918
|
executorAgentPrompt,
|
|
1903
1919
|
executorDispatchForTask,
|
package/lib/codex-flight.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const { spawn, spawnSync } = require('child_process');
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
// Worktree flights need full file access to edit and ship the requested repo.
|
|
9
|
+
const FLIGHT_SANDBOX = 'danger-full-access';
|
|
8
10
|
const CONTRACT = "CRITICAL: complete the entire job in this single run; never end your reply on a plan. Do NOT run any atris boot/brain/state commands; the only atris commands allowed are 'atris worktree guard' and 'atris worktree ship'.";
|
|
9
11
|
const COAUTHOR = 'Co-authored-by: Atris <299057014+atris-builder[bot]@users.noreply.github.com>';
|
|
10
12
|
|
|
@@ -96,12 +98,30 @@ function parseTaskId(stdout) {
|
|
|
96
98
|
return asMatch ? asMatch[1] : '';
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
function
|
|
101
|
+
function resolveCodexCompanion() {
|
|
102
|
+
if (process.env.ATRIS_CODEX_COMPANION) {
|
|
103
|
+
const override = path.resolve(process.env.ATRIS_CODEX_COMPANION);
|
|
104
|
+
return fs.existsSync(override) && fs.statSync(override).isFile() ? override : null;
|
|
105
|
+
}
|
|
106
|
+
const cache = path.join(os.homedir(), '.claude', 'plugins', 'cache', 'openai-codex', 'codex');
|
|
107
|
+
if (!fs.existsSync(cache)) return null;
|
|
108
|
+
const versions = fs.readdirSync(cache, { withFileTypes: true })
|
|
109
|
+
.filter((entry) => entry.isDirectory())
|
|
110
|
+
.map((entry) => entry.name)
|
|
111
|
+
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
112
|
+
for (const version of versions) {
|
|
113
|
+
const script = path.join(cache, version, 'scripts', 'codex-companion.mjs');
|
|
114
|
+
if (fs.existsSync(script) && fs.statSync(script).isFile()) return script;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function spawnCodex(worktreePath, prompt, companion) {
|
|
100
120
|
return new Promise((resolve, reject) => {
|
|
101
|
-
const argv = [
|
|
121
|
+
const argv = [companion, 'task', '--background', '--write', prompt];
|
|
102
122
|
const child = spawn('node', argv, {
|
|
103
123
|
cwd: worktreePath,
|
|
104
|
-
env: { ...process.env, CODEX_COMPANION_SANDBOX:
|
|
124
|
+
env: { ...process.env, CODEX_COMPANION_SANDBOX: FLIGHT_SANDBOX },
|
|
105
125
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
106
126
|
});
|
|
107
127
|
let stdout = '';
|
|
@@ -143,12 +163,15 @@ async function dispatchCodexFlight({ repoPath, slug, brief, verifyCmd, dryRun =
|
|
|
143
163
|
return { dryRun: true, prompt };
|
|
144
164
|
}
|
|
145
165
|
|
|
166
|
+
const companion = resolveCodexCompanion();
|
|
167
|
+
if (!companion) throw new Error('codex companion not found. set ATRIS_CODEX_COMPANION to its script path.');
|
|
146
168
|
runWorktreeAdd(repo.repoPath, branch, worktreePath);
|
|
147
|
-
const task = await spawnCodex(worktreePath, prompt);
|
|
169
|
+
const task = await spawnCodex(worktreePath, prompt, companion);
|
|
148
170
|
return { taskId: task.taskId, worktreePath, branch };
|
|
149
171
|
}
|
|
150
172
|
|
|
151
173
|
module.exports = {
|
|
152
174
|
dispatchCodexFlight,
|
|
153
175
|
buildPrompt,
|
|
176
|
+
resolveCodexCompanion,
|
|
154
177
|
};
|
package/lib/dispatch-scout.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
|
+
const { MAP_REF_DOCS, existingMapDocs } = require('./map-refs');
|
|
6
7
|
const {
|
|
7
8
|
buildReadOnlyEngineInvocation,
|
|
8
9
|
runAskProcess,
|
|
@@ -91,9 +92,8 @@ function seedScoutContext({ task, worktreePath }) {
|
|
|
91
92
|
const trackedList = trackedFiles(worktreePath);
|
|
92
93
|
const tracked = new Set(trackedList);
|
|
93
94
|
const keywords = titleKeywords(task && task.title);
|
|
94
|
-
const mapPath =
|
|
95
|
-
|
|
96
|
-
try { mapText = fs.readFileSync(path.join(worktreePath, mapPath), 'utf8'); } catch {}
|
|
95
|
+
const mapPath = MAP_REF_DOCS[0];
|
|
96
|
+
const mapTexts = readMapTexts(worktreePath);
|
|
97
97
|
|
|
98
98
|
const scores = new Map();
|
|
99
99
|
const gotchaCandidates = [];
|
|
@@ -103,18 +103,21 @@ function seedScoutContext({ task, worktreePath }) {
|
|
|
103
103
|
};
|
|
104
104
|
if (tracked.has(mapPath)) addScore(mapPath, 1);
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
for (
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
// The boot map routes; the notes file holds the deep file:line refs. Read both.
|
|
107
|
+
for (const [doc, mapText] of mapTexts) {
|
|
108
|
+
const mapLines = mapText.split(/\r?\n/);
|
|
109
|
+
for (let index = 0; index < mapLines.length; index += 1) {
|
|
110
|
+
const line = mapLines[index];
|
|
111
|
+
const lower = line.toLowerCase();
|
|
112
|
+
const matches = keywords.filter((keyword) => lower.includes(keyword));
|
|
113
|
+
const refs = mapPathRefs(line, tracked);
|
|
114
|
+
for (const ref of refs) {
|
|
115
|
+
const pathMatches = keywords.filter((keyword) => ref.toLowerCase().includes(keyword)).length;
|
|
116
|
+
if (matches.length || pathMatches) addScore(ref, 80 + (matches.length * 5) + (pathMatches * 8));
|
|
117
|
+
}
|
|
118
|
+
if (matches.length && line.trim() && line.trim().length <= 300 && gotchaCandidates.length < 16) {
|
|
119
|
+
gotchaCandidates.push({ ...(doc === mapPath ? {} : { file: doc }), line: index + 1, text: line.trim() });
|
|
120
|
+
}
|
|
118
121
|
}
|
|
119
122
|
}
|
|
120
123
|
|
|
@@ -268,15 +271,22 @@ function verifyHit(hit, { worktreePath, tracked, allowed }) {
|
|
|
268
271
|
return { path: citedPath, line: nearest.line, excerpt, why };
|
|
269
272
|
}
|
|
270
273
|
|
|
271
|
-
|
|
274
|
+
// Map text by doc, boot map first. A missing doc is simply absent.
|
|
275
|
+
function readMapTexts(root) {
|
|
276
|
+
return new Map(existingMapDocs(root).map((doc) => [doc, fs.readFileSync(path.join(root, doc), 'utf8')]));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function verifyMapGotcha(gotcha, mapTexts) {
|
|
272
280
|
if (!gotcha || typeof gotcha !== 'object' || Array.isArray(gotcha)) return null;
|
|
273
281
|
const text = String(gotcha.text || '').trim();
|
|
274
282
|
const citedLine = Number(gotcha.line);
|
|
283
|
+
const file = gotcha.file === undefined ? MAP_REF_DOCS[0] : String(gotcha.file);
|
|
275
284
|
if (!text || text.length > 300 || !Number.isInteger(citedLine) || citedLine < 1) return null;
|
|
276
|
-
|
|
285
|
+
if (!mapTexts.has(file)) return null;
|
|
286
|
+
const lines = mapTexts.get(file).split(/\r?\n/);
|
|
277
287
|
const actualLine = lines.findIndex((line) => line.trim() === text) + 1;
|
|
278
288
|
if (!actualLine) return null;
|
|
279
|
-
return { line: actualLine, text };
|
|
289
|
+
return { ...(file === MAP_REF_DOCS[0] ? {} : { file }), line: actualLine, text };
|
|
280
290
|
}
|
|
281
291
|
|
|
282
292
|
function verifyScoutPack(rawPack, { task, worktreePath, allowedFiles, expectedCommit = '' }) {
|
|
@@ -301,11 +311,10 @@ function verifyScoutPack(rawPack, { task, worktreePath, allowedFiles, expectedCo
|
|
|
301
311
|
});
|
|
302
312
|
if (hits.length < 2) return null;
|
|
303
313
|
|
|
304
|
-
|
|
305
|
-
try { mapText = fs.readFileSync(path.join(worktreePath, 'atris', 'MAP.md'), 'utf8'); } catch {}
|
|
314
|
+
const mapTexts = readMapTexts(worktreePath);
|
|
306
315
|
const mapGotchas = (Array.isArray(rawPack.map_gotchas) ? rawPack.map_gotchas : [])
|
|
307
316
|
.slice(0, SCOUT_MAX_MAP_GOTCHAS)
|
|
308
|
-
.map((gotcha) => verifyMapGotcha(gotcha,
|
|
317
|
+
.map((gotcha) => verifyMapGotcha(gotcha, mapTexts))
|
|
309
318
|
.filter(Boolean);
|
|
310
319
|
const notChecked = (Array.isArray(rawPack.not_checked) ? rawPack.not_checked : [])
|
|
311
320
|
.map((entry) => String(entry || '').trim())
|
|
@@ -360,7 +369,7 @@ function renderVerifiedScoutBlock(pack, { worktreePath }) {
|
|
|
360
369
|
}
|
|
361
370
|
if (pack.map_gotchas.length) {
|
|
362
371
|
lines.push('MAP gotchas:');
|
|
363
|
-
lines.push(...pack.map_gotchas.map((gotcha) => `- MAP line ${gotcha.line}: ${gotcha.text}`));
|
|
372
|
+
lines.push(...pack.map_gotchas.map((gotcha) => `- ${gotcha.file || 'MAP'} line ${gotcha.line}: ${gotcha.text}`));
|
|
364
373
|
}
|
|
365
374
|
if (pack.not_checked.length) lines.push(`Not checked: ${pack.not_checked.join('; ')}`);
|
|
366
375
|
return lines.join('\n');
|