atris 3.69.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 +40 -1
- 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 +433 -99
- package/commands/mission.js +332 -41
- package/commands/report.js +42 -8
- package/commands/run.js +9 -2
- package/commands/scout.js +9 -1
- package/commands/task.js +132 -16
- package/commands/team.js +212 -4
- package/commands/unknowns.js +15 -1
- package/commands/verify.js +46 -0
- package/commands/worktree.js +148 -18
- 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 +35 -4
- package/lib/engine-registry.js +2 -0
- package/lib/engine-resource.js +518 -0
- package/lib/engine-validate.js +189 -9
- 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 +479 -10
- package/lib/member-board.js +86 -10
- 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 +433 -0
- package/lib/member-scaffold.js +1 -2
- package/lib/member-self-next-job.js +97 -0
- package/lib/member-switches.js +7 -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 +63 -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,6 +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)
|
|
259
|
+
atris task reopen <id> [--reason "..."] Send a done, waiting, failed, or archived task back to open
|
|
256
260
|
atris task clear-done [--before <days>] [--dry-run] [--json] Archive completed rows, oldest first
|
|
257
261
|
atris task prune-test-junk [--yes] [--vacuum] [--json]
|
|
258
262
|
Count rows test runs left from temp folders; --yes backs up the db, then removes them
|
|
@@ -1636,6 +1640,11 @@ function buildTaskStreams(tasks, goals) {
|
|
|
1636
1640
|
// work, one clear way to ask for a change. Every enabled/blocked answer comes
|
|
1637
1641
|
// from the existing review handoff, so this relabels what the certification
|
|
1638
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
|
+
|
|
1639
1648
|
function taskApprovalFor(task, { reviewer = 'codex-review', hasExistingReviewFollowUp = null } = {}) {
|
|
1640
1649
|
const metadata = task && task.metadata || {};
|
|
1641
1650
|
const approvalStatus = (task && task.review && task.review.approval_status) || metadata.approval_status || null;
|
|
@@ -1658,12 +1667,16 @@ function taskApprovalFor(task, { reviewer = 'codex-review', hasExistingReviewFol
|
|
|
1658
1667
|
? reviewHandoffForTask(task, { suppressExistingFollowUp: true, hasExistingReviewFollowUp })
|
|
1659
1668
|
: null;
|
|
1660
1669
|
const planned = current === 'plan';
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
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;
|
|
1667
1680
|
const acceptEnabled = inReview ? handoffAllowsHumanAccept(handoff) : planReady;
|
|
1668
1681
|
const planOwner = task && (task.claimed_by || taskAssignee(task)) || DEFAULT_OWNER;
|
|
1669
1682
|
const acceptCommand = inReview
|
|
@@ -1682,7 +1695,7 @@ function taskApprovalFor(task, { reviewer = 'codex-review', hasExistingReviewFol
|
|
|
1682
1695
|
? 'The proof points at unfinished or unsafe work. Ask for a change before approval.'
|
|
1683
1696
|
: 'The proof still needs its required checks before a person can approve it.'
|
|
1684
1697
|
: planned
|
|
1685
|
-
?
|
|
1698
|
+
? `The plan still needs ${joinPlainList(planMissing)} before approval.`
|
|
1686
1699
|
: current === 'backlog'
|
|
1687
1700
|
? 'This task still needs a complete plan before approval.'
|
|
1688
1701
|
: current === 'do'
|
|
@@ -5454,11 +5467,12 @@ function autoAcceptCertifiedSmallReviews(taskDb, db, projection) {
|
|
|
5454
5467
|
results.push({ ...evaluation, action: 'accept_failed', task_id: task.id, reason: accepted.reason });
|
|
5455
5468
|
continue;
|
|
5456
5469
|
}
|
|
5457
|
-
stampAutoAcceptMetadata(taskDb, db, task
|
|
5470
|
+
const landing = stampAutoAcceptMetadata(taskDb, db, task, REVIEW_AUTO_ACCEPT_ACTOR, REVIEW_AUTO_ACCEPT_POLICY);
|
|
5458
5471
|
refreshCareerXpAfterReview(accepted.reviewed);
|
|
5459
5472
|
results.push({
|
|
5460
5473
|
...evaluation,
|
|
5461
5474
|
action: 'accepted',
|
|
5475
|
+
landing,
|
|
5462
5476
|
task_id: task.id,
|
|
5463
5477
|
reward: accepted.reviewed.episode.reward.value,
|
|
5464
5478
|
});
|
|
@@ -6243,6 +6257,27 @@ function cmdAdd(args) {
|
|
|
6243
6257
|
const taskDb = getTaskDb();
|
|
6244
6258
|
const db = taskDb.open();
|
|
6245
6259
|
const ws = taskDb.workspaceRoot();
|
|
6260
|
+
// Filing a job that is already open or claimed hands back that job instead
|
|
6261
|
+
// of making a copy. Work already handed in for review can be filed again.
|
|
6262
|
+
const existing = taskDb.findUnfinishedTaskByTitle(db, { workspaceRoot: ws, title });
|
|
6263
|
+
if (existing) {
|
|
6264
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
6265
|
+
const task = compactTaskFromProjection(projection, existing.id);
|
|
6266
|
+
if (wantsJson(args)) {
|
|
6267
|
+
printJson({
|
|
6268
|
+
ok: true,
|
|
6269
|
+
action: 'reused',
|
|
6270
|
+
task_id: existing.id,
|
|
6271
|
+
inserted: false,
|
|
6272
|
+
operator_title_warning: null,
|
|
6273
|
+
projection_path: outPath,
|
|
6274
|
+
task,
|
|
6275
|
+
});
|
|
6276
|
+
return;
|
|
6277
|
+
}
|
|
6278
|
+
console.log(`${taskRef(task)}\t${existing.title}\t(already on the list, reused)`);
|
|
6279
|
+
return;
|
|
6280
|
+
}
|
|
6246
6281
|
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
|
|
6247
6282
|
// Generation throttle, the named root cause is generation > human-review rate. An AGENT cannot keep
|
|
6248
6283
|
// minting tasks while a wall of certified-but-unaccepted work waits; that treadmill is what accept-group
|
|
@@ -7898,9 +7933,13 @@ function taskDetail(taskDb, db, taskId) {
|
|
|
7898
7933
|
};
|
|
7899
7934
|
}
|
|
7900
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.
|
|
7901
7939
|
function taskCommandQuote(value) {
|
|
7902
|
-
const text = String(value || '').replace(/\s+/g, ' ').
|
|
7903
|
-
return `
|
|
7940
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim() || '...';
|
|
7941
|
+
if (/[$`\\!]/.test(text)) return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
7942
|
+
return `"${text.replace(/"/g, '\\"')}"`;
|
|
7904
7943
|
}
|
|
7905
7944
|
|
|
7906
7945
|
function cleanPublicText(value, max = 500) {
|
|
@@ -9738,6 +9777,45 @@ function cmdArchive(args) {
|
|
|
9738
9777
|
}
|
|
9739
9778
|
}
|
|
9740
9779
|
|
|
9780
|
+
// Send a done, failed, or archived task back to open so it can be claimed
|
|
9781
|
+
// again. Review sign-offs are cleared; the old status and reason are kept.
|
|
9782
|
+
function cmdReopen(args) {
|
|
9783
|
+
const pos = positional(args);
|
|
9784
|
+
const id = pos[0];
|
|
9785
|
+
if (!id) failTask('atris task reopen', 'missing_id', 'task id required');
|
|
9786
|
+
const reason = textFlag(args, ['--reason']) || 'reopened';
|
|
9787
|
+
const taskDb = getTaskDb();
|
|
9788
|
+
const db = taskDb.open();
|
|
9789
|
+
const taskId = requireTaskId(taskDb, db, id, 'atris task reopen');
|
|
9790
|
+
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
9791
|
+
const result = taskDb.reopenTask(db, { id: taskId, actor, reason, fromReview: true });
|
|
9792
|
+
if (!result.reopened) {
|
|
9793
|
+
const detail = `reopen failed: ${taskId} ${result.reason}`;
|
|
9794
|
+
if (wantsJson(args)) {
|
|
9795
|
+
printJson({ ok: false, command: 'atris task reopen', reason: result.reason, task_id: taskId, detail });
|
|
9796
|
+
process.exit(1);
|
|
9797
|
+
}
|
|
9798
|
+
console.error(detail);
|
|
9799
|
+
process.exit(1);
|
|
9800
|
+
}
|
|
9801
|
+
const reopenedFrom = result.row && result.row.metadata && result.row.metadata.reopened_from || null;
|
|
9802
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
9803
|
+
const task = compactTaskFromProjection(projection, taskId);
|
|
9804
|
+
if (wantsJson(args)) {
|
|
9805
|
+
printJson({
|
|
9806
|
+
ok: true,
|
|
9807
|
+
action: 'reopened',
|
|
9808
|
+
task_id: taskId,
|
|
9809
|
+
reopened_from: reopenedFrom,
|
|
9810
|
+
reason,
|
|
9811
|
+
projection_path: outPath,
|
|
9812
|
+
task,
|
|
9813
|
+
});
|
|
9814
|
+
return;
|
|
9815
|
+
}
|
|
9816
|
+
console.log(`reopened ${taskRef(task)}${reopenedFrom ? ` (was ${reopenedFrom})` : ''}: ${reason}`);
|
|
9817
|
+
}
|
|
9818
|
+
|
|
9741
9819
|
function taskCompletionTime(row) {
|
|
9742
9820
|
const doneAt = Number(row && row.done_at);
|
|
9743
9821
|
if (Number.isFinite(doneAt) && doneAt > 0) return doneAt;
|
|
@@ -10151,7 +10229,9 @@ function cmdReady(args) {
|
|
|
10151
10229
|
? `double-check complete; autoland will accept this ${whenLands}.`
|
|
10152
10230
|
: needsExternalVerifier
|
|
10153
10231
|
? 'proof is ready; this verifier needs a second agent review because autoland cannot rerun it.'
|
|
10154
|
-
|
|
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.`)
|
|
10155
10235
|
: (agentCertified
|
|
10156
10236
|
? 'double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
|
|
10157
10237
|
: 'proof is ready; one more agent check before human approval. XP waits for the human.'),
|
|
@@ -10403,19 +10483,50 @@ async function cmdAccept(args) {
|
|
|
10403
10483
|
if (publicSync && !publicSync.ok) process.exitCode = 1;
|
|
10404
10484
|
}
|
|
10405
10485
|
|
|
10406
|
-
|
|
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;
|
|
10407
10503
|
const row = taskDb.getTask(db, taskId);
|
|
10408
|
-
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
|
+
}
|
|
10409
10518
|
const metadata = row.metadata && typeof row.metadata === 'object' ? { ...row.metadata } : {};
|
|
10410
10519
|
metadata.auto_accepted_at = new Date().toISOString();
|
|
10411
10520
|
metadata.auto_accepted_by = actor;
|
|
10412
10521
|
metadata.auto_accept_policy = policy;
|
|
10522
|
+
metadata.landing = { ...landing, checked_at: metadata.auto_accepted_at };
|
|
10413
10523
|
db.prepare(`
|
|
10414
10524
|
UPDATE tasks
|
|
10415
10525
|
SET metadata = ?,
|
|
10416
10526
|
updated_at = ?
|
|
10417
10527
|
WHERE id = ?
|
|
10418
10528
|
`).run(JSON.stringify(metadata), Date.now(), taskId);
|
|
10529
|
+
return landing;
|
|
10419
10530
|
}
|
|
10420
10531
|
|
|
10421
10532
|
function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '', autoAccepted = false }) {
|
|
@@ -10924,11 +11035,12 @@ function cmdAutoAcceptCertified(args) {
|
|
|
10924
11035
|
results.push({ ...evaluation, action: 'accept_failed', reason: accepted.reason });
|
|
10925
11036
|
continue;
|
|
10926
11037
|
}
|
|
10927
|
-
stampAutoAcceptMetadata(taskDb, db, task
|
|
11038
|
+
const landing = stampAutoAcceptMetadata(taskDb, db, task, actor, evaluation.policy);
|
|
10928
11039
|
refreshCareerXpAfterReview(accepted.reviewed);
|
|
10929
11040
|
results.push({
|
|
10930
11041
|
...evaluation,
|
|
10931
11042
|
action: 'accepted',
|
|
11043
|
+
landing,
|
|
10932
11044
|
reward: accepted.reviewed.episode.reward.value,
|
|
10933
11045
|
task_id: task.id,
|
|
10934
11046
|
});
|
|
@@ -11185,11 +11297,12 @@ function cmdSweep(args) {
|
|
|
11185
11297
|
results.push({ ...evaluation, action: 'accept_failed', task_id: task.id, reason: accepted.reason });
|
|
11186
11298
|
continue;
|
|
11187
11299
|
}
|
|
11188
|
-
stampAutoAcceptMetadata(taskDb, db, task
|
|
11300
|
+
const landing = stampAutoAcceptMetadata(taskDb, db, task, actor, evaluation.policy);
|
|
11189
11301
|
refreshCareerXpAfterReview(accepted.reviewed);
|
|
11190
11302
|
results.push({
|
|
11191
11303
|
...evaluation,
|
|
11192
11304
|
action: 'accepted',
|
|
11305
|
+
landing,
|
|
11193
11306
|
task_id: task.id,
|
|
11194
11307
|
reward: accepted.reviewed.episode.reward.value,
|
|
11195
11308
|
});
|
|
@@ -13475,6 +13588,7 @@ async function runTaskCommand(args) {
|
|
|
13475
13588
|
case 'finish': return cmdFinish(rest);
|
|
13476
13589
|
case 'fail': return cmdDone([...rest, '--failed']);
|
|
13477
13590
|
case 'archive': return cmdArchive(rest);
|
|
13591
|
+
case 'reopen': return cmdReopen(rest);
|
|
13478
13592
|
case 'clear-done': return cmdClearDone(rest);
|
|
13479
13593
|
case 'prune-test-junk': return cmdPruneTestJunk(rest);
|
|
13480
13594
|
case 'keep': return cmdKeep(rest);
|
|
@@ -13521,7 +13635,7 @@ const MUTATING_TASK_COMMANDS = new Set([
|
|
|
13521
13635
|
'continue-work', 'continue', 'chat', 'note', 'say', 'retitle', 'tag', 'tags', 'step',
|
|
13522
13636
|
'ready', 'result', 'accept', 'landing', 'land-review', 'auto-accept-certified',
|
|
13523
13637
|
'auto-accept', 'sweep', 'audit', 'certify-verified', 'accept-group', 'revise',
|
|
13524
|
-
'done', 'finish', 'fail', 'archive', 'clear-done', 'reap-mission-blockers', 'reap-blockers', 'relabel-archived', 'review', 'import',
|
|
13638
|
+
'done', 'finish', 'fail', 'archive', 'reopen', 'clear-done', 'reap-mission-blockers', 'reap-blockers', 'relabel-archived', 'review', 'import',
|
|
13525
13639
|
'setup', 'review-lane-act', 'review-act', 'act-review', 'review-lane-loop',
|
|
13526
13640
|
'review-loop', 'loop-review', 'review-lane-run', 'review-run', 'run-review',
|
|
13527
13641
|
]);
|
|
@@ -13596,4 +13710,6 @@ module.exports = {
|
|
|
13596
13710
|
taskDescriptionForCloud,
|
|
13597
13711
|
taskReviewCleanEvidenceCommand,
|
|
13598
13712
|
certifyVerifyCandidate,
|
|
13713
|
+
landingProofVerify,
|
|
13714
|
+
stampAutoAcceptMetadata,
|
|
13599
13715
|
};
|