atris 3.37.1 → 3.40.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/README.md +2 -0
- package/atris/PERSONA.md +11 -11
- package/atris.md +17 -2
- package/ax +160 -34
- package/bin/atris.js +78 -6
- package/commands/activate.js +16 -3
- package/commands/agent-spawn.js +13 -18
- package/commands/autoland.js +76 -47
- package/commands/autopilot-front.js +0 -4
- package/commands/autopilot.js +0 -12
- package/commands/bench.js +10 -12
- package/commands/brain.js +0 -7
- package/commands/brief.js +0 -2
- package/commands/business-sync.js +0 -1
- package/commands/business.js +452 -12
- package/commands/chat-scan.js +5 -7
- package/commands/clean.js +31 -2
- package/commands/close.js +46 -11
- package/commands/cloud.js +0 -1
- package/commands/codex-goal.js +8 -12
- package/commands/computer.js +6 -5
- package/commands/console.js +64 -11
- package/commands/decide.js +166 -0
- package/commands/deck.js +30 -12
- package/commands/dream.js +0 -7
- package/commands/drill.js +14 -25
- package/commands/engine.js +206 -26
- package/commands/experiments.js +0 -4
- package/commands/fleet-report.js +12 -4
- package/commands/game.js +261 -0
- package/commands/gm.js +17 -7
- package/commands/goal.js +0 -6
- package/commands/harvest.js +1 -5
- package/commands/improve.js +29 -8
- package/commands/init.js +41 -37
- package/commands/integrations.js +0 -7
- package/commands/land.js +77 -20
- package/commands/launchpad.js +1 -16
- package/commands/lesson.js +17 -7
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/loop-front.js +0 -2
- package/commands/loops.js +6 -5
- package/commands/meet.js +0 -2
- package/commands/member.js +622 -573
- package/commands/mission.js +790 -314
- package/commands/next.js +0 -1
- package/commands/now.js +0 -12
- package/commands/one-lap.js +8 -8
- package/commands/orb.js +0 -4
- package/commands/pack-craft.js +0 -1
- package/commands/pack.js +3599 -424
- package/commands/play.js +6 -6
- package/commands/probe.js +12 -6
- package/commands/pulse.js +88 -64
- package/commands/push.js +0 -4
- package/commands/radar.js +14 -11
- package/commands/recover.js +340 -0
- package/commands/release.js +10 -9
- package/commands/report.js +0 -1
- package/commands/router.js +320 -0
- package/commands/run-front.js +0 -2
- package/commands/search.js +0 -3
- package/commands/site-deploy.js +870 -0
- package/commands/site.js +11 -2
- package/commands/skill.js +7 -2
- package/commands/slop.js +237 -7
- package/commands/status.js +6 -0
- package/commands/stream.js +4 -20
- package/commands/study.js +0 -3
- package/commands/sync-checkout.js +51 -0
- package/commands/sync.js +11 -40
- package/commands/task.js +999 -595
- package/commands/taste.js +101 -0
- package/commands/teach.js +86 -0
- package/commands/team.js +83 -3
- package/commands/unknowns.js +0 -10
- package/commands/update.js +0 -1
- package/commands/vercel.js +4 -2
- package/commands/visualize.js +0 -5
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -28
- package/commands/wiki.js +42 -15
- package/commands/wish.js +0 -1
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +88 -28
- package/commands/xp.js +28 -30
- package/commands/youtube.js +0 -2
- package/lib/accept-verify-gate.js +126 -0
- package/lib/activity-stream.js +0 -2
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +261 -3
- package/lib/autoland.js +66 -3
- package/lib/ax-auto-lane.js +68 -7
- package/lib/bench/context.js +0 -2
- package/lib/bench/report.js +0 -1
- package/lib/bench/runner.js +18 -11
- package/lib/brief-ledger.js +0 -6
- package/lib/business-simulate.js +382 -0
- package/lib/chat-log-scan.js +0 -5
- package/lib/checkout-sync.js +52 -0
- package/lib/claude-boot-block.js +48 -0
- package/lib/cloud-mission.js +0 -6
- package/lib/codex-flight.js +0 -1
- package/lib/conductor-artifacts.js +25 -0
- package/lib/context-gatherer.js +7 -2
- package/lib/deck-compose.js +0 -1
- package/lib/deck-history.js +0 -1
- package/lib/deck-layout.js +0 -5
- package/lib/deck-review.js +0 -7
- package/lib/deck-schema.js +0 -1
- package/lib/default-verifier.js +0 -2
- package/lib/engine-registry.js +157 -28
- package/lib/experiments/daily.js +0 -6
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +275 -42
- package/lib/functional-owner.js +0 -3
- package/lib/git-spawn.js +15 -0
- package/lib/inspect-fields.js +0 -1
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/learnings.js +0 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +11 -19
- package/lib/manifest.js +0 -2
- package/lib/member-alive.js +0 -1
- package/lib/member-switches.js +0 -5
- package/lib/mission-artifact.js +0 -1
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +296 -0
- package/lib/mission-room.js +0 -2
- package/lib/mission-runtime-loop.js +0 -1
- package/lib/next-moves.js +0 -18
- package/lib/official-cli-integration.js +47 -6
- package/lib/orb-context.js +14 -4
- package/lib/orb-scorecard.js +0 -1
- package/lib/outbound-send-gate.js +0 -6
- package/lib/pack-capabilities.js +685 -0
- package/lib/permission-grants.js +0 -6
- package/lib/policy-lessons.js +0 -2
- package/lib/pulse.js +68 -15
- package/lib/review-integrity.js +0 -2
- package/lib/router-brain.js +51 -3
- package/lib/runner-command.js +0 -10
- package/lib/runtime-bootstrap.js +0 -1
- package/lib/scorecard.js +0 -1
- package/lib/self-drive.js +45 -14
- package/lib/state-detection.js +0 -6
- package/lib/task-db.js +137 -7
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/team-presence.js +0 -1
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +129 -2
- package/lib/wiki.js +152 -9
- package/lib/wish-audit.js +21 -7
- package/lib/wish-delegate.js +27 -21
- package/lib/wish-stats.js +0 -5
- package/lib/wish-store.js +0 -1
- package/lib/workspace-safety.js +0 -2
- package/lib/zip.js +95 -8
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/commands/clean.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const escapeRegExp = require('../lib/escape-regexp');
|
|
5
|
+
const { wikiMetabolismNudge } = require('../lib/wiki');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* atris clean - Workspace housekeeping with auto-heal
|
|
@@ -60,6 +61,14 @@ function cleanAtris(options = {}) {
|
|
|
60
61
|
const stalePages = findStalePages(cwd, atrisDir);
|
|
61
62
|
results.stalePages = stalePages;
|
|
62
63
|
|
|
64
|
+
// 6. Dead code report (report-only: deletion is a human/reviewed call)
|
|
65
|
+
try {
|
|
66
|
+
const { findDeadCode } = require('./slop');
|
|
67
|
+
const dead = findDeadCode(cwd);
|
|
68
|
+
results.deadFiles = dead.dead.map((f) => path.relative(cwd, f));
|
|
69
|
+
results.testOnlyFiles = dead.testOnly.map((f) => path.relative(cwd, f));
|
|
70
|
+
} catch { results.deadFiles = []; results.testOnlyFiles = []; }
|
|
71
|
+
|
|
63
72
|
if (options.json) {
|
|
64
73
|
console.log(JSON.stringify(cleanResultPayload(results, options, cwd), null, 2));
|
|
65
74
|
return results;
|
|
@@ -75,6 +84,12 @@ function cleanAtris(options = {}) {
|
|
|
75
84
|
console.log('Results:');
|
|
76
85
|
console.log('');
|
|
77
86
|
|
|
87
|
+
const wikiNudge = wikiMetabolismNudge(cwd);
|
|
88
|
+
if (wikiNudge) {
|
|
89
|
+
console.log(wikiNudge);
|
|
90
|
+
console.log('');
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
// Stale tasks
|
|
79
94
|
if (staleTasks.length > 0) {
|
|
80
95
|
console.log(`⚠ ${staleTasks.length} stale ${staleTasks.length === 1 ? 'task' : 'tasks'} (claimed >3 days, not completed):`);
|
|
@@ -140,6 +155,16 @@ function cleanAtris(options = {}) {
|
|
|
140
155
|
console.log('✓ No stale wiki pages');
|
|
141
156
|
}
|
|
142
157
|
|
|
158
|
+
// Dead code
|
|
159
|
+
if ((results.deadFiles || []).length > 0) {
|
|
160
|
+
console.log(`⚠ ${results.deadFiles.length} dead ${results.deadFiles.length === 1 ? 'file' : 'files'} (unreachable and unreferenced — atris slop dead for detail):`);
|
|
161
|
+
results.deadFiles.slice(0, 5).forEach((f) => console.log(` • ${f}`));
|
|
162
|
+
if (results.deadFiles.length > 5) console.log(` ... and ${results.deadFiles.length - 5} more`);
|
|
163
|
+
console.log('');
|
|
164
|
+
} else {
|
|
165
|
+
console.log('✓ No dead code');
|
|
166
|
+
}
|
|
167
|
+
|
|
143
168
|
const hasIssues = staleTasks.length > 0 || unhealable.length > 0 || stalePages.length > 0;
|
|
144
169
|
if (hasIssues) {
|
|
145
170
|
console.log('Manual action needed:');
|
|
@@ -165,6 +190,7 @@ function cleanResultPayload(results, options = {}, cwd = process.cwd()) {
|
|
|
165
190
|
if (results.staleTasks.length > 0) manualAction.push('Delete stale tasks or finish them');
|
|
166
191
|
if (results.unhealableRefs.length > 0) manualAction.push('Manually fix unhealable MAP.md refs');
|
|
167
192
|
if (results.stalePages.length > 0) manualAction.push('Re-compile stale pages');
|
|
193
|
+
if ((results.deadFiles || []).length > 0) manualAction.push('Delete dead files (atris slop dead)');
|
|
168
194
|
return {
|
|
169
195
|
ok: manualAction.length === 0,
|
|
170
196
|
action: 'clean',
|
|
@@ -196,6 +222,11 @@ function cleanResultPayload(results, options = {}, cwd = process.cwd()) {
|
|
|
196
222
|
stale_source: page.staleSource,
|
|
197
223
|
})),
|
|
198
224
|
},
|
|
225
|
+
dead_code: {
|
|
226
|
+
count: (results.deadFiles || []).length,
|
|
227
|
+
items: results.deadFiles || [],
|
|
228
|
+
test_only: results.testOnlyFiles || [],
|
|
229
|
+
},
|
|
199
230
|
},
|
|
200
231
|
manual_action: manualAction,
|
|
201
232
|
};
|
|
@@ -659,8 +690,6 @@ module.exports = {
|
|
|
659
690
|
cleanResultPayload,
|
|
660
691
|
findStaleTasks,
|
|
661
692
|
healBrokenMapRefs,
|
|
662
|
-
archiveOldJournals,
|
|
663
|
-
cleanEmptySections,
|
|
664
693
|
findStalePages,
|
|
665
694
|
checkPageStaleness
|
|
666
695
|
};
|
package/commands/close.js
CHANGED
|
@@ -517,14 +517,27 @@ function pulseLivenessState(cwd, now) {
|
|
|
517
517
|
// pulse liveness check only sees silence; this sees a run of negative reward.
|
|
518
518
|
// Sums reward over the last N finished ticks; unhealthy when the trend is net
|
|
519
519
|
// negative, healthy once it climbs back to zero or better.
|
|
520
|
+
//
|
|
521
|
+
// Recency guard: this sensor only speaks to a *live* run of ticks. A
|
|
522
|
+
// decommissioned or intermittent loop leaves stale receipts frozen; without a
|
|
523
|
+
// guard the last N finished ticks stay net-negative forever and the flag can
|
|
524
|
+
// never clear (no fresh positive receipt arrives). It also must not treat a
|
|
525
|
+
// scattered set — e.g. one tick today plus five from days ago — as a "run":
|
|
526
|
+
// summing rewards across a multi-day gap is meaningless. So we only consider
|
|
527
|
+
// finished ticks from the last few days and require a full window of them; a
|
|
528
|
+
// silent or sparse loop is the pulse-liveness sensor's concern, and here it
|
|
529
|
+
// reports healthy so any open reward flag auto-closes.
|
|
520
530
|
const REWARD_REGRESSION_WINDOW = 6;
|
|
531
|
+
const REWARD_REGRESSION_RECENCY_MS = 2 * DAY_MS;
|
|
521
532
|
|
|
522
533
|
function rewardRegressionState(cwd, now) {
|
|
523
534
|
const receipts = pulse.readJsonl(pulse.pulseReceiptsPath(cwd));
|
|
524
535
|
const finished = receipts
|
|
525
536
|
.filter((r) => r && r.phase === 'finished' && parseDate(r.ts || r.at) !== null)
|
|
537
|
+
.filter((r) => now.getTime() - parseDate(r.ts || r.at) <= REWARD_REGRESSION_RECENCY_MS)
|
|
526
538
|
.sort((a, b) => parseDate(a.ts || a.at) - parseDate(b.ts || b.at));
|
|
527
|
-
|
|
539
|
+
// Not enough recent signal to call a degradation run: leave it to pulse liveness.
|
|
540
|
+
if (finished.length < REWARD_REGRESSION_WINDOW) return { healthy: true, unhealthy: false, sparse: true };
|
|
528
541
|
const window = finished.slice(-REWARD_REGRESSION_WINDOW);
|
|
529
542
|
const sum = window.reduce((acc, r) => acc + (Number(r.reward) || 0), 0);
|
|
530
543
|
return { healthy: sum >= 0, unhealthy: sum < 0, sum };
|
|
@@ -782,13 +795,14 @@ function appendOpenedFromSource({ what, owner = 'operator', lane = 'code', ttlDa
|
|
|
782
795
|
return event;
|
|
783
796
|
}
|
|
784
797
|
|
|
785
|
-
function appendClosedFromSource(flag, cwd, now) {
|
|
798
|
+
function appendClosedFromSource(flag, cwd, now, note) {
|
|
786
799
|
const event = {
|
|
787
800
|
kind: 'closed',
|
|
788
801
|
at: now.toISOString(),
|
|
789
802
|
id: flag.id,
|
|
790
803
|
proof: RESOLVED_IN_SOURCE_PROOF,
|
|
791
804
|
};
|
|
805
|
+
if (note) event.note = note;
|
|
792
806
|
appendEvent(event, cwd);
|
|
793
807
|
return event;
|
|
794
808
|
}
|
|
@@ -809,12 +823,20 @@ function scanState(cwd = process.cwd(), options = {}) {
|
|
|
809
823
|
const lessonFlags = lessonFlagsState(cwd, now);
|
|
810
824
|
const openBySource = new Map();
|
|
811
825
|
const dissolvedSources = new Set();
|
|
826
|
+
const recentlyClosedSources = new Set();
|
|
812
827
|
const autoClosed = [];
|
|
813
828
|
|
|
814
829
|
// Dissolve is a durable verdict: a source the operator declared dead on
|
|
815
|
-
// purpose must never re-open on a later scan.
|
|
830
|
+
// purpose must never re-open on a later scan. Recent closes get a 30-day
|
|
831
|
+
// quiet period so a resolved source cannot immediately breed a new flag.
|
|
816
832
|
for (const flag of foldEvents(readEvents(cwd), { now })) {
|
|
817
833
|
if (flag.status === 'dissolved' && flag.source) dissolvedSources.add(flag.source);
|
|
834
|
+
const closedAt = parseDate(flag.closed_at);
|
|
835
|
+
const closedAge = closedAt === null ? null : now.getTime() - closedAt;
|
|
836
|
+
if (flag.status === 'closed' && flag.source && closedAge !== null
|
|
837
|
+
&& closedAge >= 0 && closedAge <= 30 * DAY_MS) {
|
|
838
|
+
recentlyClosedSources.add(flag.source);
|
|
839
|
+
}
|
|
818
840
|
}
|
|
819
841
|
|
|
820
842
|
const failedTasks = Array.from(tasksById.values())
|
|
@@ -827,17 +849,22 @@ function scanState(cwd = process.cwd(), options = {}) {
|
|
|
827
849
|
if (flag.source) openBySource.set(flag.source, flag);
|
|
828
850
|
}
|
|
829
851
|
|
|
830
|
-
const closeFlag = (flag) => {
|
|
831
|
-
const event = appendClosedFromSource(flag, cwd, now);
|
|
852
|
+
const closeFlag = (flag, note) => {
|
|
853
|
+
const event = appendClosedFromSource(flag, cwd, now, note);
|
|
832
854
|
autoClosed.push({ flag, event });
|
|
833
855
|
openBySource.delete(flag.source);
|
|
856
|
+
recentlyClosedSources.add(flag.source);
|
|
834
857
|
};
|
|
835
858
|
|
|
836
859
|
for (const flag of Array.from(openBySource.values())) {
|
|
837
860
|
if (flag.source.startsWith('task:')) {
|
|
838
861
|
const task = tasksById.get(flag.source.slice('task:'.length));
|
|
839
862
|
const status = normalizeSpaces(task && task.status).toLowerCase();
|
|
840
|
-
if (task
|
|
863
|
+
if (!task) {
|
|
864
|
+
closeFlag(flag, 'task no longer in projection (done/archived)');
|
|
865
|
+
} else if (!taskWaitingOnHumanAccept(task) || taskResolved(task) || status === 'failed') {
|
|
866
|
+
closeFlag(flag);
|
|
867
|
+
}
|
|
841
868
|
continue;
|
|
842
869
|
}
|
|
843
870
|
if (flag.source.startsWith(FAILED_TASK_BATCH_PREFIX)) {
|
|
@@ -881,7 +908,8 @@ function scanState(cwd = process.cwd(), options = {}) {
|
|
|
881
908
|
|
|
882
909
|
const opened = [];
|
|
883
910
|
const openCandidate = (candidate) => {
|
|
884
|
-
if (!candidate.source || openBySource.has(candidate.source)
|
|
911
|
+
if (!candidate.source || openBySource.has(candidate.source)
|
|
912
|
+
|| dissolvedSources.has(candidate.source) || recentlyClosedSources.has(candidate.source)) return null;
|
|
885
913
|
const event = appendOpenedFromSource(candidate, cwd, now);
|
|
886
914
|
opened.push(event);
|
|
887
915
|
openBySource.set(candidate.source, { ...event, status: 'open' });
|
|
@@ -889,10 +917,11 @@ function scanState(cwd = process.cwd(), options = {}) {
|
|
|
889
917
|
};
|
|
890
918
|
|
|
891
919
|
for (const task of tasks) {
|
|
892
|
-
const
|
|
893
|
-
const
|
|
920
|
+
const currentTask = tasksById.get(normalizeSpaces(task && task.id));
|
|
921
|
+
const source = taskSource(currentTask);
|
|
922
|
+
const ref = taskRef(currentTask);
|
|
894
923
|
if (!source || !ref) continue;
|
|
895
|
-
if (taskWaitingOnHumanAccept(
|
|
924
|
+
if (taskWaitingOnHumanAccept(currentTask) && isOlderThan(currentTask.updated_at, 5, now)) {
|
|
896
925
|
openCandidate({
|
|
897
926
|
what: `task ${ref} has waited in review too long, accept it or send it back`,
|
|
898
927
|
lane: 'code',
|
|
@@ -1054,6 +1083,13 @@ function run(args = [], context = {}) {
|
|
|
1054
1083
|
printHelp();
|
|
1055
1084
|
return 0;
|
|
1056
1085
|
}
|
|
1086
|
+
// `close <sub> --help` is a help request, not a run — sweep and scan mutate
|
|
1087
|
+
// the ledger (escalations, auto-closes), so never execute a subcommand just
|
|
1088
|
+
// to show usage.
|
|
1089
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
1090
|
+
printHelp();
|
|
1091
|
+
return 0;
|
|
1092
|
+
}
|
|
1057
1093
|
if (subcommand === 'add') return commandAdd(rest, context);
|
|
1058
1094
|
if (subcommand === 'list') return commandList(rest, context);
|
|
1059
1095
|
if (subcommand === 'done') return commandDone(rest, context);
|
|
@@ -1078,6 +1114,5 @@ module.exports = {
|
|
|
1078
1114
|
openFlags,
|
|
1079
1115
|
ledgerPath,
|
|
1080
1116
|
readEvents,
|
|
1081
|
-
listLine,
|
|
1082
1117
|
sweepLine,
|
|
1083
1118
|
};
|
package/commands/cloud.js
CHANGED
package/commands/codex-goal.js
CHANGED
|
@@ -2,15 +2,13 @@ const fs = require('fs');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { spawnSync } = require('child_process');
|
|
5
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
5
6
|
|
|
6
7
|
const SCHEMA = 'atris.codex_goal.v1';
|
|
7
8
|
const CONFIRM_RESET_FLAG = '--confirm-complete-goal-reset';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function readFlag(args, name, fallback = '') {
|
|
10
|
+
// Preserve the existing rule that the next token is a value, even if it is a flag.
|
|
11
|
+
function readFollowingFlag(args, name, fallback = '') {
|
|
14
12
|
const index = args.indexOf(name);
|
|
15
13
|
if (index === -1 || index + 1 >= args.length) return fallback;
|
|
16
14
|
return args[index + 1];
|
|
@@ -32,7 +30,7 @@ function sqlString(value) {
|
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
function resolveStatePath(args = []) {
|
|
35
|
-
const explicit =
|
|
33
|
+
const explicit = readFollowingFlag(args, '--state', process.env.CODEX_STATE_DB || '');
|
|
36
34
|
if (explicit) return path.resolve(expandHome(explicit));
|
|
37
35
|
// Codex moved native goals into ~/.codex/goals_1.sqlite; older builds kept them in state_5.sqlite.
|
|
38
36
|
// Prefer the live goals DB so the bridge sees real goal activity, fall back to the legacy state DB.
|
|
@@ -43,7 +41,7 @@ function resolveStatePath(args = []) {
|
|
|
43
41
|
|
|
44
42
|
// Thread metadata (cwd/title) stayed in state_5.sqlite even after goals moved to goals_1.sqlite.
|
|
45
43
|
function resolveThreadsPath(args = []) {
|
|
46
|
-
const explicit =
|
|
44
|
+
const explicit = readFollowingFlag(args, '--threads-db', process.env.CODEX_THREADS_DB || '');
|
|
47
45
|
if (explicit) return path.resolve(expandHome(explicit));
|
|
48
46
|
const legacyDb = path.join(os.homedir(), '.codex', 'state_5.sqlite');
|
|
49
47
|
return fs.existsSync(legacyDb) ? path.resolve(legacyDb) : '';
|
|
@@ -79,7 +77,7 @@ function runGoalQuery(args, buildSql) {
|
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
function defaultRunsDir(args = []) {
|
|
82
|
-
return path.resolve(
|
|
80
|
+
return path.resolve(readFollowingFlag(args, '--out-dir', path.join(process.cwd(), '.atris', 'runs')));
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
function ensurePrivateDir(dir) {
|
|
@@ -196,7 +194,7 @@ function readRecentGoals(args, limit = 10) {
|
|
|
196
194
|
}
|
|
197
195
|
|
|
198
196
|
function resolveThreadGoal(args) {
|
|
199
|
-
const explicitThread =
|
|
197
|
+
const explicitThread = readFollowingFlag(args, '--thread', '');
|
|
200
198
|
if (explicitThread) return readGoalByThread(args, explicitThread);
|
|
201
199
|
if (hasFlag(args, '--latest')) return readLatestGoalForCwd(args, process.cwd());
|
|
202
200
|
const envThread = process.env.CODEX_THREAD_ID || '';
|
|
@@ -248,7 +246,7 @@ function statusCommand(args) {
|
|
|
248
246
|
return;
|
|
249
247
|
}
|
|
250
248
|
|
|
251
|
-
const limit = Math.max(1, Math.min(50, Number(
|
|
249
|
+
const limit = Math.max(1, Math.min(50, Number(readFollowingFlag(args, '--limit', '10')) || 10));
|
|
252
250
|
const goals = readRecentGoals(args, limit);
|
|
253
251
|
const payload = { ok: true, schema: SCHEMA, action: 'status', state_path: dbPath, goals };
|
|
254
252
|
printJsonOrText(payload, [
|
|
@@ -379,7 +377,5 @@ function codexGoalCommand(args = []) {
|
|
|
379
377
|
|
|
380
378
|
module.exports = {
|
|
381
379
|
codexGoalCommand,
|
|
382
|
-
CONFIRM_RESET_FLAG,
|
|
383
|
-
resolveStatePath,
|
|
384
380
|
sqlString,
|
|
385
381
|
};
|
package/commands/computer.js
CHANGED
|
@@ -1405,7 +1405,7 @@ function computerLocalLegacy(extraArgs = []) {
|
|
|
1405
1405
|
}
|
|
1406
1406
|
}
|
|
1407
1407
|
|
|
1408
|
-
function computerLocal(extraArgs = []) {
|
|
1408
|
+
function computerLocal(extraArgs = [], options = {}) {
|
|
1409
1409
|
printLocalWordmark();
|
|
1410
1410
|
printModeBanner('LOCAL', process.cwd(), [
|
|
1411
1411
|
'Claude Code + Atris workspace context.',
|
|
@@ -1417,7 +1417,7 @@ function computerLocal(extraArgs = []) {
|
|
|
1417
1417
|
const originalArgv = process.argv;
|
|
1418
1418
|
process.argv = [originalArgv[0], originalArgv[1], originalArgv[2], 'claude', ...extraArgs];
|
|
1419
1419
|
try {
|
|
1420
|
-
consoleCommand();
|
|
1420
|
+
consoleCommand(options);
|
|
1421
1421
|
} finally {
|
|
1422
1422
|
process.argv = originalArgv;
|
|
1423
1423
|
}
|
|
@@ -4390,11 +4390,12 @@ async function runComputer(argv = process.argv.slice(3), deps = {}) {
|
|
|
4390
4390
|
module.exports = {
|
|
4391
4391
|
runComputer,
|
|
4392
4392
|
computerSetup,
|
|
4393
|
-
|
|
4393
|
+
// `atris pack run` starts a packet as a free local computer, so it needs the
|
|
4394
|
+
// local starter and the business-binding check as seams, not copies.
|
|
4395
|
+
computerLocal,
|
|
4396
|
+
readBusinessBinding,
|
|
4394
4397
|
formatEngineSeats,
|
|
4395
4398
|
buildComputerCard,
|
|
4396
|
-
renderComputerCard,
|
|
4397
|
-
renderComputerCardMarkdown,
|
|
4398
4399
|
extractAttachedWorkspaceMismatch,
|
|
4399
4400
|
contextForAttachedWorkspaceMismatch,
|
|
4400
4401
|
printRecruitingLocalSyncOutcome,
|
package/commands/console.js
CHANGED
|
@@ -7,10 +7,40 @@ const { resolveClaudeRunnerBin } = require('../lib/runner-command');
|
|
|
7
7
|
|
|
8
8
|
// ── Context Gathering ──────────────────────────────────────────────
|
|
9
9
|
|
|
10
|
+
// Atris content lives in two shapes on disk, and the console has to read both.
|
|
11
|
+
//
|
|
12
|
+
// workspace: <dir>/atris/{skills,team,TODO.md,...}
|
|
13
|
+
// The normal repo shape. A project keeps its Atris brain in a subfolder so
|
|
14
|
+
// it does not collide with the project's own files. This is the common case.
|
|
15
|
+
//
|
|
16
|
+
// packet: <dir>/{pack.json,skills,team,docs,...}
|
|
17
|
+
// A packet built in root mode (pack.js exports with prefix '' when the
|
|
18
|
+
// source dir has pack.json at its root — this is how the shipped packs are
|
|
19
|
+
// built and what the web /packs/<slug>/download route serves). `pack
|
|
20
|
+
// install` preserves that flat layout, so the packet folder IS the atris
|
|
21
|
+
// root; there is no nested atris/ to find.
|
|
22
|
+
//
|
|
23
|
+
// Nested wins when both exist: never change what an existing workspace loads.
|
|
24
|
+
// Returns the directory that actually holds the atris content, or null.
|
|
25
|
+
function resolveAtrisRoot(workspaceDir) {
|
|
26
|
+
const nested = path.join(workspaceDir, 'atris');
|
|
27
|
+
try {
|
|
28
|
+
if (fs.statSync(nested).isDirectory()) return nested;
|
|
29
|
+
} catch {}
|
|
30
|
+
const manifest = path.join(workspaceDir, 'pack.json');
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.statSync(manifest).isFile()) return null;
|
|
33
|
+
JSON.parse(fs.readFileSync(manifest, 'utf8'));
|
|
34
|
+
return workspaceDir;
|
|
35
|
+
} catch {}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
10
39
|
function gatherAtrisContext(workspaceDir) {
|
|
11
|
-
const atrisDir =
|
|
40
|
+
const atrisDir = resolveAtrisRoot(workspaceDir);
|
|
12
41
|
const ctx = {
|
|
13
|
-
hasAtris:
|
|
42
|
+
hasAtris: Boolean(atrisDir),
|
|
43
|
+
atrisRoot: atrisDir,
|
|
14
44
|
skills: [],
|
|
15
45
|
teamMembers: [],
|
|
16
46
|
backlogCount: 0,
|
|
@@ -119,7 +149,8 @@ function buildSystemPrompt(ctx) {
|
|
|
119
149
|
if (ctx.teamMembers.length > 0) {
|
|
120
150
|
lines.push('## Team Members');
|
|
121
151
|
lines.push(`Available: ${ctx.teamMembers.join(', ')}`);
|
|
122
|
-
|
|
152
|
+
const teamRoot = ctx.atrisRoot ? path.join(ctx.atrisRoot, 'team') : 'atris/team';
|
|
153
|
+
lines.push(`Each has a MEMBER.md in ${teamRoot}/<name>/ defining their role.`);
|
|
123
154
|
lines.push('');
|
|
124
155
|
}
|
|
125
156
|
|
|
@@ -135,7 +166,8 @@ function buildSystemPrompt(ctx) {
|
|
|
135
166
|
|
|
136
167
|
if (ctx.backlogCount > 0) {
|
|
137
168
|
lines.push(`## Current Work`);
|
|
138
|
-
|
|
169
|
+
const todoPath = ctx.atrisRoot ? path.join(ctx.atrisRoot, 'TODO.md') : 'atris/TODO.md';
|
|
170
|
+
lines.push(`${ctx.backlogCount} task${ctx.backlogCount > 1 ? 's' : ''} in backlog. Check ${todoPath} for details.`);
|
|
139
171
|
lines.push('');
|
|
140
172
|
}
|
|
141
173
|
|
|
@@ -280,20 +312,37 @@ function checkAuth(backend) {
|
|
|
280
312
|
|
|
281
313
|
// ── Launch ──────────────────────────────────────────────────────────
|
|
282
314
|
|
|
283
|
-
function launchClaude(systemPrompt, extraArgs) {
|
|
315
|
+
function launchClaude(systemPrompt, extraArgs, options = {}) {
|
|
316
|
+
const skipPermissions = options.skipPermissions !== false;
|
|
284
317
|
const runnerBin = resolveClaudeRunnerBin();
|
|
318
|
+
const promptStdin = typeof options.promptStdin === 'string'
|
|
319
|
+
? options.promptStdin
|
|
320
|
+
: null;
|
|
285
321
|
const args = [
|
|
286
|
-
'--dangerously-skip-permissions',
|
|
322
|
+
...(skipPermissions ? ['--dangerously-skip-permissions'] : []),
|
|
323
|
+
...(promptStdin !== null ? ['--print'] : []),
|
|
287
324
|
'--append-system-prompt', systemPrompt,
|
|
288
325
|
...extraArgs,
|
|
289
326
|
];
|
|
290
327
|
|
|
291
328
|
const child = spawnSync(runnerBin, args, {
|
|
292
329
|
cwd: process.cwd(),
|
|
293
|
-
stdio: 'inherit',
|
|
294
|
-
|
|
330
|
+
stdio: promptStdin === null ? 'inherit' : ['pipe', 'inherit', 'inherit'],
|
|
331
|
+
...(promptStdin === null ? {} : { input: promptStdin }),
|
|
332
|
+
env: { ...process.env, ...(options.runnerEnv || {}), CLAUDECODE: undefined },
|
|
295
333
|
});
|
|
296
334
|
|
|
335
|
+
try {
|
|
336
|
+
if (typeof options.onRunnerExit === 'function') {
|
|
337
|
+
options.onRunnerExit({ status: child.status ?? 1, signal: child.signal || null });
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error(`warning: could not finalize runner receipt: ${error.message}`);
|
|
341
|
+
}
|
|
342
|
+
for (const cleanupPath of options.cleanupPaths || []) {
|
|
343
|
+
try { fs.rmSync(cleanupPath, { recursive: true, force: true }); } catch {}
|
|
344
|
+
}
|
|
345
|
+
|
|
297
346
|
if (child.error) {
|
|
298
347
|
console.error(`✗ Failed to start ${runnerBin}: ${child.error.message}`);
|
|
299
348
|
process.exit(1);
|
|
@@ -342,7 +391,11 @@ function launchCodex(systemPrompt, extraArgs) {
|
|
|
342
391
|
|
|
343
392
|
// ── Main ────────────────────────────────────────────────────────────
|
|
344
393
|
|
|
345
|
-
|
|
394
|
+
// skipPermissions defaults to true: `atris console` runs on a workspace you
|
|
395
|
+
// wrote. Callers that launch an agent inside content someone else authored
|
|
396
|
+
// (`atris pack run`) pass false so permission prompts stay on.
|
|
397
|
+
function consoleCommand(options = {}) {
|
|
398
|
+
const skipPermissions = options.skipPermissions !== false;
|
|
346
399
|
const args = process.argv.slice(3);
|
|
347
400
|
let requested = args[0];
|
|
348
401
|
|
|
@@ -385,7 +438,7 @@ function consoleCommand() {
|
|
|
385
438
|
|
|
386
439
|
// Launch
|
|
387
440
|
if (backend === 'claude') {
|
|
388
|
-
launchClaude(systemPrompt, extraArgs);
|
|
441
|
+
launchClaude(systemPrompt, extraArgs, { ...options, skipPermissions });
|
|
389
442
|
} else {
|
|
390
443
|
launchCodex(systemPrompt, extraArgs);
|
|
391
444
|
}
|
|
@@ -399,4 +452,4 @@ function consoleCommand() {
|
|
|
399
452
|
}
|
|
400
453
|
}
|
|
401
454
|
|
|
402
|
-
module.exports = { consoleCommand, gatherAtrisContext, buildSystemPrompt };
|
|
455
|
+
module.exports = { consoleCommand, gatherAtrisContext, buildSystemPrompt, resolveAtrisRoot, launchClaude };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
answerMissionHumanAsk,
|
|
5
|
+
listMissions,
|
|
6
|
+
listWorktreeRollupMissions,
|
|
7
|
+
pingMission,
|
|
8
|
+
} = require('./mission');
|
|
9
|
+
const { openHumanAsks, normalizeHumanAsks } = require('../lib/mission-human-asks');
|
|
10
|
+
const { redirectToWorkspaceRoot } = require('../lib/mission-root');
|
|
11
|
+
const { shortId } = require('../lib/short-name');
|
|
12
|
+
|
|
13
|
+
const TERMINAL_STATUSES = new Set(['stopped', 'complete']);
|
|
14
|
+
|
|
15
|
+
function missionTouchedAt(mission) {
|
|
16
|
+
return String(mission.updated_at || mission.created_at || '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function liveMissions(root = process.cwd()) {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
return [...listMissions(root), ...listWorktreeRollupMissions(root)]
|
|
22
|
+
.filter((mission) => {
|
|
23
|
+
if (!mission || !mission.id || seen.has(mission.id) || TERMINAL_STATUSES.has(mission.status)) return false;
|
|
24
|
+
seen.add(mission.id);
|
|
25
|
+
return true;
|
|
26
|
+
})
|
|
27
|
+
.sort((left, right) => (
|
|
28
|
+
missionTouchedAt(right).localeCompare(missionTouchedAt(left))
|
|
29
|
+
|| String(left.id).localeCompare(String(right.id))
|
|
30
|
+
));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectOpenDecisions(root = process.cwd()) {
|
|
34
|
+
const decisions = [];
|
|
35
|
+
for (const mission of liveMissions(root)) {
|
|
36
|
+
const normalized = normalizeHumanAsks(mission.human_asks);
|
|
37
|
+
normalized.forEach((ask, askIndex) => {
|
|
38
|
+
if (!ask.text.trim() || ask.answered_at) return;
|
|
39
|
+
decisions.push({
|
|
40
|
+
number: decisions.length + 1,
|
|
41
|
+
owner: String(mission.owner || 'unowned'),
|
|
42
|
+
mission_id: mission.id,
|
|
43
|
+
mission_short_id: shortId(mission.id),
|
|
44
|
+
mission_status: mission.status,
|
|
45
|
+
mission_updated_at: missionTouchedAt(mission),
|
|
46
|
+
ask_index: askIndex,
|
|
47
|
+
text: ask.text,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return decisions;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function printHelp() {
|
|
55
|
+
console.log('Usage:');
|
|
56
|
+
console.log(' atris decide');
|
|
57
|
+
console.log(' atris decide <n> y|n|yes|no [--note "<text>"]');
|
|
58
|
+
console.log(' atris decide --json');
|
|
59
|
+
console.log(' atris decide <n> y --json');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function fail(message, asJson, code = 2) {
|
|
63
|
+
if (asJson) {
|
|
64
|
+
console.log(JSON.stringify({ ok: false, action: 'decide_error', error: message }));
|
|
65
|
+
} else {
|
|
66
|
+
console.error(message);
|
|
67
|
+
}
|
|
68
|
+
process.exitCode = code;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseArgs(args) {
|
|
72
|
+
const asJson = args.includes('--json');
|
|
73
|
+
const rest = args.filter((arg) => arg !== '--json');
|
|
74
|
+
let note = '';
|
|
75
|
+
const noteIndex = rest.findIndex((arg) => arg === '--note' || String(arg).startsWith('--note='));
|
|
76
|
+
if (noteIndex !== -1) {
|
|
77
|
+
const noteArg = String(rest[noteIndex]);
|
|
78
|
+
if (noteArg === '--note') {
|
|
79
|
+
if (rest[noteIndex + 1] == null) return { asJson, error: '--note requires text' };
|
|
80
|
+
note = String(rest[noteIndex + 1]).trim();
|
|
81
|
+
rest.splice(noteIndex, 2);
|
|
82
|
+
} else {
|
|
83
|
+
note = noteArg.slice('--note='.length).trim();
|
|
84
|
+
rest.splice(noteIndex, 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { asJson, note, rest };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function decideCommand(args = []) {
|
|
91
|
+
redirectToWorkspaceRoot();
|
|
92
|
+
const parsed = parseArgs(args);
|
|
93
|
+
if (parsed.error) return fail(parsed.error, parsed.asJson);
|
|
94
|
+
const { asJson, note, rest } = parsed;
|
|
95
|
+
if (rest.includes('--help') || rest.includes('-h') || rest[0] === 'help') {
|
|
96
|
+
printHelp();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const decisions = collectOpenDecisions();
|
|
101
|
+
if (!rest.length) {
|
|
102
|
+
if (asJson) {
|
|
103
|
+
console.log(JSON.stringify({
|
|
104
|
+
ok: true,
|
|
105
|
+
action: 'decide_list',
|
|
106
|
+
count: decisions.length,
|
|
107
|
+
decisions,
|
|
108
|
+
}, null, 2));
|
|
109
|
+
} else if (!decisions.length) {
|
|
110
|
+
console.log('nothing is waiting for a decision.');
|
|
111
|
+
} else {
|
|
112
|
+
for (const decision of decisions) {
|
|
113
|
+
console.log(`[${decision.number}] ${decision.owner} · ${decision.mission_short_id} · ${decision.text}`);
|
|
114
|
+
}
|
|
115
|
+
console.log('atris decide <n> y|n');
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (rest.length !== 2) {
|
|
121
|
+
return fail('usage: atris decide <n> y|n|yes|no [--note "<text>"]', asJson);
|
|
122
|
+
}
|
|
123
|
+
const number = Number(rest[0]);
|
|
124
|
+
if (!Number.isInteger(number) || number < 1) {
|
|
125
|
+
return fail('decision number must be a positive integer', asJson);
|
|
126
|
+
}
|
|
127
|
+
const answerToken = String(rest[1]).toLowerCase();
|
|
128
|
+
const answer = answerToken === 'y' || answerToken === 'yes'
|
|
129
|
+
? 'yes'
|
|
130
|
+
: (answerToken === 'n' || answerToken === 'no' ? 'no' : null);
|
|
131
|
+
if (!answer) return fail('answer must be y, n, yes, or no', asJson);
|
|
132
|
+
const decision = decisions[number - 1];
|
|
133
|
+
if (!decision) return fail(`decision ${number} is not open`, asJson, 1);
|
|
134
|
+
|
|
135
|
+
const message = `Decision on "${decision.text}": ${answer.toUpperCase()}${note ? ` — ${note}` : ''}`;
|
|
136
|
+
pingMission([decision.mission_id, message, '--from', 'decide'], { silent: true });
|
|
137
|
+
const mission = answerMissionHumanAsk(decision.mission_id, decision.ask_index, answer, note);
|
|
138
|
+
const remainingOpenAsks = openHumanAsks(mission.human_asks).length;
|
|
139
|
+
const payload = {
|
|
140
|
+
ok: true,
|
|
141
|
+
action: 'decision_answered',
|
|
142
|
+
decision: {
|
|
143
|
+
...decision,
|
|
144
|
+
answer,
|
|
145
|
+
note,
|
|
146
|
+
message,
|
|
147
|
+
},
|
|
148
|
+
mission: {
|
|
149
|
+
id: mission.id,
|
|
150
|
+
short_id: decision.mission_short_id,
|
|
151
|
+
owner: mission.owner,
|
|
152
|
+
status: mission.status,
|
|
153
|
+
remaining_open_asks: remainingOpenAsks,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
if (asJson) {
|
|
157
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
158
|
+
} else {
|
|
159
|
+
console.log(`sent to ${decision.mission_short_id}: ${message}`);
|
|
160
|
+
console.log(`mission ${decision.mission_short_id} will read it on its next tick.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
decideCommand,
|
|
166
|
+
};
|