atris 3.70.0 → 3.71.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/README.md +18 -0
- package/atris/skills/README.md +1 -0
- package/atris/skills/campaigns/SKILL.md +81 -0
- package/atris/skills/meta/SKILL.md +52 -8
- package/ax +226 -42
- package/bin/atris.js +26 -6
- package/commands/autoland.js +176 -7
- package/commands/autopilot.js +71 -11
- package/commands/campaign.js +759 -0
- package/commands/compile.js +19 -8
- package/commands/doctor.js +58 -0
- package/commands/dream.js +18 -3
- package/commands/engine.js +147 -5
- package/commands/improve.js +119 -9
- package/commands/init.js +18 -1
- package/commands/interview.js +15 -3
- package/commands/land.js +22 -6
- package/commands/link.js +1 -0
- package/commands/member.js +359 -94
- package/commands/mission.js +249 -36
- package/commands/report.js +42 -8
- package/commands/run.js +9 -2
- package/commands/scout.js +9 -1
- package/commands/task.js +71 -17
- package/commands/team.js +212 -4
- package/commands/unknowns.js +15 -1
- package/commands/verify.js +46 -0
- package/commands/worktree.js +108 -15
- package/commands/write.js +12 -1
- package/commands/youtube.js +17 -2
- package/lib/accept-verify-gate.js +1 -0
- package/lib/auto-accept-certified.js +1 -0
- package/lib/autoland.js +13 -4
- package/lib/campaign-cadence.js +131 -0
- package/lib/campaign-core.js +330 -0
- package/lib/campaign-holdout.js +65 -0
- package/lib/campaign-loop.js +242 -0
- package/lib/campaign-missions.js +237 -0
- package/lib/campaign-new.js +251 -0
- package/lib/campaign-protect.js +75 -0
- package/lib/campaign-reading.js +147 -0
- package/lib/campaign-schema.js +196 -0
- package/lib/campaign-store.js +380 -0
- package/lib/campaign-sync.js +302 -0
- package/lib/campaign-trace.js +300 -0
- package/lib/conductor-artifacts.js +15 -1
- package/lib/daily-log.js +11 -1
- package/lib/dispatch-scout.js +18 -0
- package/lib/engine-ask.js +33 -3
- package/lib/engine-registry.js +2 -0
- package/lib/engine-resource.js +518 -0
- package/lib/fleet.js +125 -16
- package/lib/init-commit.js +80 -0
- package/lib/intents.js +8 -0
- package/lib/known-commands.js +1 -1
- package/lib/land-green.js +58 -7
- package/lib/landing-proof.js +352 -0
- package/lib/member-autoland.js +395 -18
- package/lib/member-board.js +7 -1
- package/lib/member-map-fold.js +126 -0
- package/lib/member-map-inbox.js +80 -0
- package/lib/member-progress.js +48 -1
- package/lib/member-report.js +127 -8
- package/lib/member-self-next-job.js +97 -0
- package/lib/member-task-pick.js +41 -14
- package/lib/member-up.js +249 -16
- package/lib/roster-prep.js +25 -2
- package/lib/runner-command.js +5 -0
- package/lib/task-db.js +47 -11
- package/lib/task-explanation.js +7 -0
- package/lib/task-list-keeper.js +8 -1
- package/lib/task-receipt.js +6 -1
- package/mcp/atris-mcp/index.mjs +212 -10
- package/package.json +2 -1
- package/scripts/det/engine-reading.js +37 -0
- package/scripts/det/ytnotes +29 -5
package/commands/report.js
CHANGED
|
@@ -88,22 +88,38 @@ function withWorkspaceCwd(root, fn) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
// A done task is only a landing when its stored git proof says so. Done
|
|
92
|
+
// without that proof is reported separately, never counted as landed.
|
|
93
|
+
function buildDoneTasks(root, cutoff) {
|
|
92
94
|
const tasks = readProjection(root) || [];
|
|
93
95
|
return tasks
|
|
94
96
|
.filter(task => task && task.status === 'done' && Number(timestampMs(task.done_at)) >= cutoff)
|
|
95
97
|
.sort((a, b) => Number(timestampMs(b.done_at) || 0) - Number(timestampMs(a.done_at) || 0))
|
|
96
98
|
.map((task) => {
|
|
97
99
|
const id = inlineText(task.display_id || task.task_ref || task.id, 'task');
|
|
100
|
+
const landing = task.metadata && typeof task.metadata.landing === 'object' ? task.metadata.landing : null;
|
|
98
101
|
return {
|
|
99
102
|
id,
|
|
100
103
|
title: inlineText(task.title, 'untitled task'),
|
|
101
104
|
done_at: task.done_at || null,
|
|
102
105
|
source: `task ${id}`,
|
|
106
|
+
landed: landing?.status === 'landed',
|
|
107
|
+
landing_status: landing?.status || 'unchecked',
|
|
108
|
+
landing_reason: landing?.reason || (landing ? '' : 'no landing proof recorded'),
|
|
103
109
|
};
|
|
104
110
|
});
|
|
105
111
|
}
|
|
106
112
|
|
|
113
|
+
function buildLandings(root, cutoff) {
|
|
114
|
+
return buildDoneTasks(root, cutoff).filter(task => task.landed);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function landingStatusText(task) {
|
|
118
|
+
const status = String(task?.landing_status || 'unchecked').replace(/_/g, ' ');
|
|
119
|
+
const reason = String(task?.landing_reason || '').replace(/_/g, ' ');
|
|
120
|
+
return reason ? `${status}, ${reason}` : status;
|
|
121
|
+
}
|
|
122
|
+
|
|
107
123
|
function buildJournalCompletions(root, days, now) {
|
|
108
124
|
return withWorkspaceCwd(root, () => {
|
|
109
125
|
const completions = [];
|
|
@@ -198,13 +214,16 @@ function buildWeekReportData(root = process.cwd(), { days = DEFAULT_DAYS, now =
|
|
|
198
214
|
const windowDays = asWindowDays(days);
|
|
199
215
|
const nowMs = timestampMs(now) || Date.now();
|
|
200
216
|
const cutoff = nowMs - windowDays * DAY_MS;
|
|
201
|
-
const
|
|
217
|
+
const done = buildDoneTasks(root, cutoff);
|
|
218
|
+
const landings = done.filter(task => task.landed);
|
|
219
|
+
const unproven = done.filter(task => !task.landed);
|
|
202
220
|
const completions = buildJournalCompletions(root, windowDays, nowMs);
|
|
203
221
|
const xp = buildXp(root, cutoff);
|
|
204
222
|
return {
|
|
205
|
-
empty:
|
|
223
|
+
empty: done.length === 0 && completions.length === 0 && xp.total <= 0,
|
|
206
224
|
days: windowDays,
|
|
207
225
|
landings,
|
|
226
|
+
unproven,
|
|
208
227
|
completions,
|
|
209
228
|
xp,
|
|
210
229
|
};
|
|
@@ -212,11 +231,13 @@ function buildWeekReportData(root = process.cwd(), { days = DEFAULT_DAYS, now =
|
|
|
212
231
|
|
|
213
232
|
function renderWeekReport(data) {
|
|
214
233
|
const landings = Array.isArray(data?.landings) ? data.landings : [];
|
|
234
|
+
const unproven = Array.isArray(data?.unproven) ? data.unproven : [];
|
|
215
235
|
const completions = Array.isArray(data?.completions) ? data.completions : [];
|
|
216
236
|
const xp = data?.xp && typeof data.xp === 'object' ? data.xp : { total: 0, receipts: [] };
|
|
217
237
|
const receipts = Array.isArray(xp.receipts) ? xp.receipts : [];
|
|
238
|
+
const unprovenHeader = unproven.length ? `, ${unproven.length} done but not proven landed` : '';
|
|
218
239
|
const lines = [
|
|
219
|
-
`week in review: ${landings.length} landed, ${completions.length} completions, ${formatXp(xp.total)} xp`,
|
|
240
|
+
`week in review: ${landings.length} landed${unprovenHeader}, ${completions.length} completions, ${formatXp(xp.total)} xp`,
|
|
220
241
|
];
|
|
221
242
|
|
|
222
243
|
if (data?.empty) {
|
|
@@ -227,12 +248,20 @@ function renderWeekReport(data) {
|
|
|
227
248
|
|
|
228
249
|
if (landings.length) {
|
|
229
250
|
lines.push('');
|
|
230
|
-
lines.push(`landings: ${landings.length} tasks,
|
|
251
|
+
lines.push(`landings: ${landings.length} tasks, proven by a git landing check`);
|
|
231
252
|
for (const landing of landings) {
|
|
232
253
|
lines.push(`- landed: ${inlineText(landing.title)} | size: 1 task | know: ${inlineText(landing.source || landing.id, 'task')}`);
|
|
233
254
|
}
|
|
234
255
|
}
|
|
235
256
|
|
|
257
|
+
if (unproven.length) {
|
|
258
|
+
lines.push('');
|
|
259
|
+
lines.push(`done but not proven landed: ${unproven.length} tasks, git could not confirm the work reached the target`);
|
|
260
|
+
for (const task of unproven) {
|
|
261
|
+
lines.push(`- done: ${inlineText(task.title)} | landing: ${inlineText(landingStatusText(task))} | know: ${inlineText(task.source || task.id, 'task')}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
236
265
|
if (completions.length) {
|
|
237
266
|
lines.push('');
|
|
238
267
|
lines.push(`journal completions: ${completions.length} items, known from daily logs`);
|
|
@@ -258,6 +287,7 @@ function panelRows(rows, emptyTitle, emptySub) {
|
|
|
258
287
|
|
|
259
288
|
function buildWeekReportHtmlSpec(data) {
|
|
260
289
|
const landings = Array.isArray(data?.landings) ? data.landings : [];
|
|
290
|
+
const unproven = Array.isArray(data?.unproven) ? data.unproven : [];
|
|
261
291
|
const completions = Array.isArray(data?.completions) ? data.completions : [];
|
|
262
292
|
const xp = data?.xp && typeof data.xp === 'object' ? data.xp : { total: 0, receipts: [] };
|
|
263
293
|
const receipts = Array.isArray(xp.receipts) ? xp.receipts : [];
|
|
@@ -277,18 +307,22 @@ function buildWeekReportHtmlSpec(data) {
|
|
|
277
307
|
{
|
|
278
308
|
type: 'panel',
|
|
279
309
|
heading: 'landings',
|
|
280
|
-
sub: 'done tasks
|
|
310
|
+
sub: 'done tasks whose git landing check passed; the rest are marked not proven',
|
|
281
311
|
panel: {
|
|
282
312
|
header: {
|
|
283
313
|
title: 'task landings',
|
|
284
|
-
meta: `${landings.length} ${
|
|
314
|
+
meta: `${landings.length} landed${unproven.length ? `, ${unproven.length} not proven` : ''}`,
|
|
285
315
|
},
|
|
286
316
|
rows: panelRows(
|
|
287
317
|
landings.map((landing) => ({
|
|
288
318
|
title: `${inlineText(landing.id, 'task')}: ${lowerText(landing.title, 'untitled task')}`,
|
|
289
319
|
sub: lowerText(landing.source || landing.id, 'task'),
|
|
290
320
|
sev: 0,
|
|
291
|
-
}))
|
|
321
|
+
})).concat(unproven.map((task) => ({
|
|
322
|
+
title: `${inlineText(task.id, 'task')}: ${lowerText(task.title, 'untitled task')}`,
|
|
323
|
+
sub: `not proven landed: ${lowerText(landingStatusText(task), 'unchecked')}`,
|
|
324
|
+
sev: 2,
|
|
325
|
+
}))),
|
|
292
326
|
'no task landings found',
|
|
293
327
|
'task projection had no done items in this window'
|
|
294
328
|
),
|
package/commands/run.js
CHANGED
|
@@ -223,12 +223,16 @@ function executePhase(phase, context, options = {}) {
|
|
|
223
223
|
const prompt = buildRunPrompt(phase, context, priorCycleReview);
|
|
224
224
|
const tmpFile = path.join(process.cwd(), '.run-prompt.tmp');
|
|
225
225
|
fs.writeFileSync(tmpFile, prompt);
|
|
226
|
+
let endResource = () => null;
|
|
226
227
|
|
|
227
228
|
try {
|
|
228
229
|
const cmd = buildRunnerCommand({ promptFile: tmpFile, allowedTools: 'Bash,Read,Write,Edit,Glob,Grep' });
|
|
229
230
|
// Strip CLAUDECODE env var to allow spawning from within a Claude Code session
|
|
230
231
|
const env = { ...process.env };
|
|
231
232
|
delete env.CLAUDECODE;
|
|
233
|
+
// Machine memory and load at both ends of the launch, so a phase killed
|
|
234
|
+
// with SIGKILL says whether the machine was short on memory.
|
|
235
|
+
endResource = require('../lib/engine-resource').startEngineRun(process.cwd(), { engine: 'runner', task: `run ${phase}` });
|
|
232
236
|
const output = execPhaseCommandSync(cmd, {
|
|
233
237
|
cwd: process.cwd(),
|
|
234
238
|
encoding: 'utf8',
|
|
@@ -241,14 +245,17 @@ function executePhase(phase, context, options = {}) {
|
|
|
241
245
|
});
|
|
242
246
|
|
|
243
247
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
248
|
+
endResource();
|
|
244
249
|
return output || '';
|
|
245
250
|
} catch (err) {
|
|
246
251
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
252
|
+
const resourceEnd = endResource(err);
|
|
247
253
|
if (isPhaseTimeoutError(err)) {
|
|
248
254
|
throw new Error(`${phase} timed out after ${timeout / 1000}s`);
|
|
249
255
|
}
|
|
250
256
|
if (isPhaseKillError(err)) {
|
|
251
|
-
|
|
257
|
+
const cause = resourceEnd && resourceEnd.cause ? `, ${resourceEnd.cause}` : '';
|
|
258
|
+
throw new Error(`${phase} killed by ${err.signal || 'a signal'} before the ${timeout / 1000}s wall${cause}`);
|
|
252
259
|
}
|
|
253
260
|
// execSync throws on non-zero exit but may still have output
|
|
254
261
|
if (err.stdout) return err.stdout;
|
|
@@ -1068,4 +1075,4 @@ function diffRunLogs(args = []) {
|
|
|
1068
1075
|
}
|
|
1069
1076
|
}
|
|
1070
1077
|
|
|
1071
|
-
module.exports = { runAtris, hasWork, hasInboxOrBacklogWork, roadmapOpenCount, getRunLogDir, getRunLogPath, writePhaseToRunLog, appendMasterLoopReceipt, listRunLogs, pruneRunLogs, searchRunLogs, statsRunLogs, exportRunLogs, diffRunLogs, buildRunPrompt };
|
|
1078
|
+
module.exports = { runAtris, executePhase, hasWork, hasInboxOrBacklogWork, roadmapOpenCount, getRunLogDir, getRunLogPath, writePhaseToRunLog, appendMasterLoopReceipt, listRunLogs, pruneRunLogs, searchRunLogs, statsRunLogs, exportRunLogs, diffRunLogs, buildRunPrompt };
|
package/commands/scout.js
CHANGED
|
@@ -65,12 +65,16 @@ function runScout(question, { dir, model, engine, timeoutMs }) {
|
|
|
65
65
|
return new Promise((resolve) => {
|
|
66
66
|
const eng = ENGINES[engine];
|
|
67
67
|
const [cmd, cmdArgs] = eng.argv(scoutPrompt(question, dir), model);
|
|
68
|
+
// Machine memory and load at both ends of the run, so a SIGKILL says
|
|
69
|
+
// whether it was the time limit, low memory, or an unknown cause.
|
|
70
|
+
const endResource = require('../lib/engine-resource').startEngineRun(process.cwd(), { engine, task: 'scout' });
|
|
68
71
|
const child = spawn(cmd, cmdArgs, { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
69
72
|
|
|
70
73
|
let out = '';
|
|
71
74
|
let err = '';
|
|
72
75
|
const timer = setTimeout(() => {
|
|
73
76
|
child.kill('SIGKILL');
|
|
77
|
+
endResource({ code: 'ETIMEDOUT', signal: 'SIGKILL' });
|
|
74
78
|
resolve({ question, ok: false, text: `timed out after ${timeoutMs / 1000}s` });
|
|
75
79
|
}, timeoutMs);
|
|
76
80
|
|
|
@@ -78,11 +82,15 @@ function runScout(question, { dir, model, engine, timeoutMs }) {
|
|
|
78
82
|
child.stderr.on('data', (d) => { err += d; });
|
|
79
83
|
child.on('error', (e) => {
|
|
80
84
|
clearTimeout(timer);
|
|
85
|
+
endResource(e);
|
|
81
86
|
resolve({ question, ok: false, text: `failed to spawn ${cmd}: ${e.message}` });
|
|
82
87
|
});
|
|
83
|
-
child.on('close', (code) => {
|
|
88
|
+
child.on('close', (code, signal) => {
|
|
84
89
|
clearTimeout(timer);
|
|
90
|
+
// The child does not throw, so a failure is passed in the thrown shape.
|
|
91
|
+
const end = endResource(code === 0 ? null : { signal, stderr: err, message: `${cmd} exited ${code}` });
|
|
85
92
|
if (code === 0 && out.trim()) resolve({ question, ok: true, text: eng.parse(out) });
|
|
93
|
+
else if (signal) resolve({ question, ok: false, text: `stopped by ${signal}${end && end.cause ? `; ${end.cause}` : ''}` });
|
|
86
94
|
else resolve({ question, ok: false, text: (err.trim() || out.trim() || `exit ${code}`) });
|
|
87
95
|
});
|
|
88
96
|
});
|
package/commands/task.js
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
} = require('../lib/task-proof');
|
|
23
23
|
const {
|
|
24
24
|
candidatePolicyGate,
|
|
25
|
+
declaredCandidatePathValues,
|
|
25
26
|
evaluateAutoAccept,
|
|
26
27
|
isAgentCertified,
|
|
27
28
|
isAutoCertifyVerifyCommandAllowed,
|
|
@@ -32,8 +33,10 @@ const {
|
|
|
32
33
|
DENIED_TAGS,
|
|
33
34
|
} = require('../lib/auto-accept-certified');
|
|
34
35
|
const { evaluateAcceptVerify } = require('../lib/accept-verify-gate');
|
|
36
|
+
const { proveTaskLanding } = require('../lib/landing-proof');
|
|
35
37
|
const {
|
|
36
38
|
taskExplanation,
|
|
39
|
+
explicitDoneLooksLike,
|
|
37
40
|
explanationFieldsFromInput,
|
|
38
41
|
explanationLines,
|
|
39
42
|
taskApprovalControls,
|
|
@@ -253,7 +256,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
253
256
|
atris task archive <id> --reason "..." [--from-failed]
|
|
254
257
|
Sweep off-roadmap/duplicate work as archived (not failed);
|
|
255
258
|
--from-failed opts in to relabel a fail-closed row (never done)
|
|
256
|
-
atris task reopen <id> [--reason "..."] Send a done, failed, or archived task back to open
|
|
259
|
+
atris task reopen <id> [--reason "..."] Send a done, waiting, failed, or archived task back to open
|
|
257
260
|
atris task clear-done [--before <days>] [--dry-run] [--json] Archive completed rows, oldest first
|
|
258
261
|
atris task prune-test-junk [--yes] [--vacuum] [--json]
|
|
259
262
|
Count rows test runs left from temp folders; --yes backs up the db, then removes them
|
|
@@ -1637,6 +1640,11 @@ function buildTaskStreams(tasks, goals) {
|
|
|
1637
1640
|
// work, one clear way to ask for a change. Every enabled/blocked answer comes
|
|
1638
1641
|
// from the existing review handoff, so this relabels what the certification
|
|
1639
1642
|
// rules already allow and can never widen it.
|
|
1643
|
+
function joinPlainList(items) {
|
|
1644
|
+
if (items.length <= 2) return items.join(' and ');
|
|
1645
|
+
return `${items.slice(0, -1).join(', ')}, and ${items[items.length - 1]}`;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1640
1648
|
function taskApprovalFor(task, { reviewer = 'codex-review', hasExistingReviewFollowUp = null } = {}) {
|
|
1641
1649
|
const metadata = task && task.metadata || {};
|
|
1642
1650
|
const approvalStatus = (task && task.review && task.review.approval_status) || metadata.approval_status || null;
|
|
@@ -1659,12 +1667,16 @@ function taskApprovalFor(task, { reviewer = 'codex-review', hasExistingReviewFol
|
|
|
1659
1667
|
? reviewHandoffForTask(task, { suppressExistingFollowUp: true, hasExistingReviewFollowUp })
|
|
1660
1668
|
: null;
|
|
1661
1669
|
const planned = current === 'plan';
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1670
|
+
// Name only the plan parts that are really missing: a job given a check
|
|
1671
|
+
// should not be told it still needs one, and a job that says what done
|
|
1672
|
+
// looks like already has its finish line.
|
|
1673
|
+
const planMissing = [
|
|
1674
|
+
!(metadata.task_goal || metadata.goal_objective || task.objective) && 'a goal',
|
|
1675
|
+
!(metadata.exit_condition || explicitDoneLooksLike(metadata)) && 'a finish line',
|
|
1676
|
+
!(metadata.verify || metadata.proof_needed) && 'a check',
|
|
1677
|
+
!metadata.first_move && 'a first move',
|
|
1678
|
+
].filter(Boolean);
|
|
1679
|
+
const planReady = planned && planMissing.length === 0;
|
|
1668
1680
|
const acceptEnabled = inReview ? handoffAllowsHumanAccept(handoff) : planReady;
|
|
1669
1681
|
const planOwner = task && (task.claimed_by || taskAssignee(task)) || DEFAULT_OWNER;
|
|
1670
1682
|
const acceptCommand = inReview
|
|
@@ -1683,7 +1695,7 @@ function taskApprovalFor(task, { reviewer = 'codex-review', hasExistingReviewFol
|
|
|
1683
1695
|
? 'The proof points at unfinished or unsafe work. Ask for a change before approval.'
|
|
1684
1696
|
: 'The proof still needs its required checks before a person can approve it.'
|
|
1685
1697
|
: planned
|
|
1686
|
-
?
|
|
1698
|
+
? `The plan still needs ${joinPlainList(planMissing)} before approval.`
|
|
1687
1699
|
: current === 'backlog'
|
|
1688
1700
|
? 'This task still needs a complete plan before approval.'
|
|
1689
1701
|
: current === 'do'
|
|
@@ -5455,11 +5467,12 @@ function autoAcceptCertifiedSmallReviews(taskDb, db, projection) {
|
|
|
5455
5467
|
results.push({ ...evaluation, action: 'accept_failed', task_id: task.id, reason: accepted.reason });
|
|
5456
5468
|
continue;
|
|
5457
5469
|
}
|
|
5458
|
-
stampAutoAcceptMetadata(taskDb, db, task
|
|
5470
|
+
const landing = stampAutoAcceptMetadata(taskDb, db, task, REVIEW_AUTO_ACCEPT_ACTOR, REVIEW_AUTO_ACCEPT_POLICY);
|
|
5459
5471
|
refreshCareerXpAfterReview(accepted.reviewed);
|
|
5460
5472
|
results.push({
|
|
5461
5473
|
...evaluation,
|
|
5462
5474
|
action: 'accepted',
|
|
5475
|
+
landing,
|
|
5463
5476
|
task_id: task.id,
|
|
5464
5477
|
reward: accepted.reviewed.episode.reward.value,
|
|
5465
5478
|
});
|
|
@@ -7920,9 +7933,13 @@ function taskDetail(taskDb, db, taskId) {
|
|
|
7920
7933
|
};
|
|
7921
7934
|
}
|
|
7922
7935
|
|
|
7936
|
+
// Printed lines get pasted into a shell. Plain words keep readable double
|
|
7937
|
+
// quotes; text a shell would act on ($, backticks, \, !) is single-quoted so
|
|
7938
|
+
// pasting it runs no command and keeps the words as written.
|
|
7923
7939
|
function taskCommandQuote(value) {
|
|
7924
|
-
const text = String(value || '').replace(/\s+/g, ' ').
|
|
7925
|
-
return `
|
|
7940
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim() || '...';
|
|
7941
|
+
if (/[$`\\!]/.test(text)) return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
7942
|
+
return `"${text.replace(/"/g, '\\"')}"`;
|
|
7926
7943
|
}
|
|
7927
7944
|
|
|
7928
7945
|
function cleanPublicText(value, max = 500) {
|
|
@@ -9771,7 +9788,7 @@ function cmdReopen(args) {
|
|
|
9771
9788
|
const db = taskDb.open();
|
|
9772
9789
|
const taskId = requireTaskId(taskDb, db, id, 'atris task reopen');
|
|
9773
9790
|
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
9774
|
-
const result = taskDb.reopenTask(db, { id: taskId, actor, reason });
|
|
9791
|
+
const result = taskDb.reopenTask(db, { id: taskId, actor, reason, fromReview: true });
|
|
9775
9792
|
if (!result.reopened) {
|
|
9776
9793
|
const detail = `reopen failed: ${taskId} ${result.reason}`;
|
|
9777
9794
|
if (wantsJson(args)) {
|
|
@@ -10212,7 +10229,9 @@ function cmdReady(args) {
|
|
|
10212
10229
|
? `double-check complete; autoland will accept this ${whenLands}.`
|
|
10213
10230
|
: needsExternalVerifier
|
|
10214
10231
|
? 'proof is ready; this verifier needs a second agent review because autoland cannot rerun it.'
|
|
10215
|
-
|
|
10232
|
+
// Autoland accepts; landing is a git fact it proves separately. Do not
|
|
10233
|
+
// promise "lands it" for work that may not be committed or merged.
|
|
10234
|
+
: `proof is ready; autoland runs the second check and accepts it ${whenLands}; it lands once committed and merged.`)
|
|
10216
10235
|
: (agentCertified
|
|
10217
10236
|
? 'double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
|
|
10218
10237
|
: 'proof is ready; one more agent check before human approval. XP waits for the human.'),
|
|
@@ -10464,19 +10483,50 @@ async function cmdAccept(args) {
|
|
|
10464
10483
|
if (publicSync && !publicSync.ok) process.exitCode = 1;
|
|
10465
10484
|
}
|
|
10466
10485
|
|
|
10467
|
-
|
|
10486
|
+
// Every auto-accept passes through here, so this is where the record says
|
|
10487
|
+
// whether the work actually landed. Accepted is a review verdict; landed is a
|
|
10488
|
+
// fact about git, proven by lib/landing-proof.js or not claimed at all.
|
|
10489
|
+
// The check behind a landing is the one that actually ran: the verifier's
|
|
10490
|
+
// passing re-run first, then the check the proof names, then the builder's
|
|
10491
|
+
// recorded verify.
|
|
10492
|
+
function landingProofVerify(task) {
|
|
10493
|
+
const metadata = task.metadata || {};
|
|
10494
|
+
const cache = metadata.verify_cache;
|
|
10495
|
+
if (cache && typeof cache === 'object' && cache.ok === true && String(cache.command || '').trim()) {
|
|
10496
|
+
return String(cache.command).trim();
|
|
10497
|
+
}
|
|
10498
|
+
return certifyVerifyCandidate(task) || String(metadata.verify || '').trim();
|
|
10499
|
+
}
|
|
10500
|
+
|
|
10501
|
+
function stampAutoAcceptMetadata(taskDb, db, task, actor, policy) {
|
|
10502
|
+
const taskId = task.id;
|
|
10468
10503
|
const row = taskDb.getTask(db, taskId);
|
|
10469
|
-
if (!row) return;
|
|
10504
|
+
if (!row) return null;
|
|
10505
|
+
// The caller's task object was read before the re-check ran, so it lacks
|
|
10506
|
+
// the verify_cache and certify stamps written since. Prove from the row as
|
|
10507
|
+
// it stands now, keeping the caller's events and review for claimed paths.
|
|
10508
|
+
const current = { ...task, ...row, metadata: row.metadata && typeof row.metadata === 'object' ? row.metadata : {} };
|
|
10509
|
+
let landing;
|
|
10510
|
+
try {
|
|
10511
|
+
landing = proveTaskLanding(current, {
|
|
10512
|
+
files: declaredCandidatePathValues(current),
|
|
10513
|
+
verify: landingProofVerify(current),
|
|
10514
|
+
});
|
|
10515
|
+
} catch (err) {
|
|
10516
|
+
landing = { status: 'unverified', reason: 'landing_proof_failed', detail: String((err && err.message) || err).slice(0, 200) };
|
|
10517
|
+
}
|
|
10470
10518
|
const metadata = row.metadata && typeof row.metadata === 'object' ? { ...row.metadata } : {};
|
|
10471
10519
|
metadata.auto_accepted_at = new Date().toISOString();
|
|
10472
10520
|
metadata.auto_accepted_by = actor;
|
|
10473
10521
|
metadata.auto_accept_policy = policy;
|
|
10522
|
+
metadata.landing = { ...landing, checked_at: metadata.auto_accepted_at };
|
|
10474
10523
|
db.prepare(`
|
|
10475
10524
|
UPDATE tasks
|
|
10476
10525
|
SET metadata = ?,
|
|
10477
10526
|
updated_at = ?
|
|
10478
10527
|
WHERE id = ?
|
|
10479
10528
|
`).run(JSON.stringify(metadata), Date.now(), taskId);
|
|
10529
|
+
return landing;
|
|
10480
10530
|
}
|
|
10481
10531
|
|
|
10482
10532
|
function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '', autoAccepted = false }) {
|
|
@@ -10985,11 +11035,12 @@ function cmdAutoAcceptCertified(args) {
|
|
|
10985
11035
|
results.push({ ...evaluation, action: 'accept_failed', reason: accepted.reason });
|
|
10986
11036
|
continue;
|
|
10987
11037
|
}
|
|
10988
|
-
stampAutoAcceptMetadata(taskDb, db, task
|
|
11038
|
+
const landing = stampAutoAcceptMetadata(taskDb, db, task, actor, evaluation.policy);
|
|
10989
11039
|
refreshCareerXpAfterReview(accepted.reviewed);
|
|
10990
11040
|
results.push({
|
|
10991
11041
|
...evaluation,
|
|
10992
11042
|
action: 'accepted',
|
|
11043
|
+
landing,
|
|
10993
11044
|
reward: accepted.reviewed.episode.reward.value,
|
|
10994
11045
|
task_id: task.id,
|
|
10995
11046
|
});
|
|
@@ -11246,11 +11297,12 @@ function cmdSweep(args) {
|
|
|
11246
11297
|
results.push({ ...evaluation, action: 'accept_failed', task_id: task.id, reason: accepted.reason });
|
|
11247
11298
|
continue;
|
|
11248
11299
|
}
|
|
11249
|
-
stampAutoAcceptMetadata(taskDb, db, task
|
|
11300
|
+
const landing = stampAutoAcceptMetadata(taskDb, db, task, actor, evaluation.policy);
|
|
11250
11301
|
refreshCareerXpAfterReview(accepted.reviewed);
|
|
11251
11302
|
results.push({
|
|
11252
11303
|
...evaluation,
|
|
11253
11304
|
action: 'accepted',
|
|
11305
|
+
landing,
|
|
11254
11306
|
task_id: task.id,
|
|
11255
11307
|
reward: accepted.reviewed.episode.reward.value,
|
|
11256
11308
|
});
|
|
@@ -13658,4 +13710,6 @@ module.exports = {
|
|
|
13658
13710
|
taskDescriptionForCloud,
|
|
13659
13711
|
taskReviewCleanEvidenceCommand,
|
|
13660
13712
|
certifyVerifyCandidate,
|
|
13713
|
+
landingProofVerify,
|
|
13714
|
+
stampAutoAcceptMetadata,
|
|
13661
13715
|
};
|