atris 3.57.4 → 3.58.6
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 +12 -1
- package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
- package/atris/skills/create-member/SKILL.md +16 -13
- package/atris/skills/design/SKILL.md +27 -4
- package/atris/skills/engines/SKILL.md +55 -12
- package/atris/skills/x-search/SKILL.md +8 -6
- package/atris/skills/youtube/SKILL.md +66 -21
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris/team/executor/MEMBER.md +3 -3
- package/atris/team/launcher/MEMBER.md +4 -4
- package/atris/team/navigator/MEMBER.md +3 -3
- package/atris/team/researcher/MEMBER.md +2 -2
- package/atris/team/validator/MEMBER.md +1 -1
- package/atris.md +14 -0
- package/ax +153 -23
- package/bin/atris.js +78 -30
- package/commands/aeo.js +4 -4
- package/commands/agents.js +1 -1
- package/commands/align.js +12 -12
- package/commands/analytics.js +3 -3
- package/commands/app.js +5 -5
- package/commands/ask.js +175 -0
- package/commands/auth.js +59 -31
- package/commands/autoland.js +29 -5
- package/commands/autopilot-front.js +6 -6
- package/commands/autopilot.js +90 -90
- package/commands/avail.js +5 -5
- package/commands/bench.js +63 -1
- package/commands/browse.js +2 -2
- package/commands/business.js +12 -12
- package/commands/card.js +2 -2
- package/commands/clean.js +13 -13
- package/commands/close.js +3 -3
- package/commands/compile.js +13 -13
- package/commands/computer.js +94 -27
- package/commands/console.js +5 -5
- package/commands/decide.js +1 -1
- package/commands/deck.js +9 -9
- package/commands/drive.js +4 -4
- package/commands/engine.js +173 -19
- package/commands/errors.js +3 -3
- package/commands/experiments.js +18 -1
- package/commands/feed.js +2 -2
- package/commands/feedback.js +4 -4
- package/commands/fleet.js +12 -12
- package/commands/game.js +1 -1
- package/commands/guide.js +49 -0
- package/commands/improve.js +19 -17
- package/commands/init.js +67 -19
- package/commands/interview.js +7 -7
- package/commands/land.js +40 -15
- package/commands/learn.js +216 -50
- package/commands/lesson.js +2 -2
- package/commands/log.js +1 -1
- package/commands/loops.js +4 -4
- package/commands/member.js +75 -50
- package/commands/mission.js +90 -82
- package/commands/one-lap.js +1 -1
- package/commands/pack.js +74 -8
- package/commands/plugin.js +6 -6
- package/commands/probe.js +9 -9
- package/commands/pull.js +35 -35
- package/commands/pulse.js +1 -1
- package/commands/push.js +21 -21
- package/commands/rainmaker.js +1 -1
- package/commands/recap.js +7 -7
- package/commands/reel.js +2 -2
- package/commands/release.js +3 -3
- package/commands/report.js +1 -1
- package/commands/review.js +5 -5
- package/commands/revisions.js +1 -1
- package/commands/run.js +1 -1
- package/commands/scout.js +3 -3
- package/commands/security-review.js +7 -7
- package/commands/serve.js +9 -9
- package/commands/setup.js +1 -1
- package/commands/sign.js +4 -4
- package/commands/signup.js +4 -4
- package/commands/site.js +8 -3
- package/commands/skill.js +6 -6
- package/commands/slop.js +18 -18
- package/commands/social.js +3 -3
- package/commands/soul.js +14 -14
- package/commands/spaceship.js +1 -1
- package/commands/status.js +2 -2
- package/commands/strings.js +13 -13
- package/commands/study.js +4 -4
- package/commands/sync.js +8 -8
- package/commands/task.js +50 -25
- package/commands/teach.js +2 -2
- package/commands/team.js +1 -1
- package/commands/theme.js +2 -2
- package/commands/tree.js +34 -0
- package/commands/truth.js +7 -7
- package/commands/verify.js +12 -12
- package/commands/visualize.js +2 -2
- package/commands/workflow.js +101 -48
- package/commands/worktree.js +1 -1
- package/commands/write.js +9 -9
- package/commands/x-search.js +93 -22
- package/commands/xp.js +6 -3
- package/commands/youtube.js +1019 -123
- package/lib/accept-verify-gate.js +3 -3
- package/lib/activity-stream.js +1 -1
- package/lib/auto-accept-certified.js +28 -12
- package/lib/autoland.js +6 -6
- package/lib/bench/engines.js +59 -8
- package/lib/bench/report.js +1 -1
- package/lib/bench/runner.js +204 -5
- package/lib/bench/tree-render.js +76 -0
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/claude-boot-block.js +2 -0
- package/lib/deck-compose.js +1 -1
- package/lib/deck-history.js +2 -2
- package/lib/deck-layout.js +1 -1
- package/lib/deck-review.js +5 -5
- package/lib/deck-schema.js +1 -1
- package/lib/default-verifier.js +1 -1
- package/lib/developer-api.js +3 -0
- package/lib/engine-ask.js +45 -7
- package/lib/engine-registry.js +15 -2
- package/lib/file-ops.js +1 -1
- package/lib/fleet.js +71 -35
- package/lib/html-render.js +1 -1
- package/lib/intents.js +172 -0
- package/lib/journal.js +1 -1
- package/lib/known-commands.js +2 -2
- package/lib/land-green.js +364 -0
- package/lib/lesson-contradiction.js +2 -2
- package/lib/lesson-preflight.js +1 -1
- package/lib/manifest.js +5 -5
- package/lib/member-alive.js +9 -5
- package/lib/memory-view.js +3 -3
- package/lib/mission-protected-lane.js +1 -1
- package/lib/mission-root.js +2 -2
- package/lib/orb-context.js +10 -10
- package/lib/pack-capabilities.js +861 -3
- package/lib/permission-grants.js +2 -2
- package/lib/policy-lessons.js +6 -6
- package/lib/pulse.js +8 -8
- package/lib/receipt-evidence.js +1 -1
- package/lib/reel.js +1 -1
- package/lib/review-integrity.js +1 -1
- package/lib/runner-command.js +35 -5
- package/lib/scorecard.js +4 -4
- package/lib/security-scan.js +5 -5
- package/lib/self-drive.js +1 -1
- package/lib/site-publish.js +372 -0
- package/lib/site.js +1 -1
- package/lib/slides-deck.js +2 -2
- package/lib/state-detection.js +2 -2
- package/lib/sync-telemetry.js +2 -2
- package/lib/task-db.js +17 -10
- package/lib/task-explanation.js +1 -1
- package/lib/task-proof.js +2 -2
- package/lib/task-receipt.js +3 -3
- package/lib/theme.js +12 -12
- package/lib/todo-fallback.js +3 -3
- package/lib/todo-sections.js +1 -1
- package/lib/tree-hash.js +79 -0
- package/lib/usage.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/workspace-safety.js +4 -4
- package/lib/workspace-scaffold.js +3 -3
- package/package.json +1 -1
- package/scripts/det/concierge-bakeoff.sh +26 -0
- package/scripts/det/ytnotes +82 -5
- package/scripts/det/ytquote-repair.js +0 -1
- package/scripts/det/ytrail-race.js +0 -5
- package/scripts/member-operate.mjs +52 -14
- package/scripts/outbound-artifact-gate.js +0 -3
- package/utils/auth.js +47 -9
- package/utils/config.js +0 -1
- package/scripts/det/checklist-score.js +0 -191
package/commands/mission.js
CHANGED
|
@@ -90,6 +90,7 @@ const {
|
|
|
90
90
|
const { resolveWorkspaceRoot, redirectToWorkspaceRoot } = require('../lib/mission-root');
|
|
91
91
|
const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
|
|
92
92
|
const { readJson, writeJson } = require('../lib/json-file');
|
|
93
|
+
const { treeHashFor } = require('../lib/tree-hash');
|
|
93
94
|
const {
|
|
94
95
|
normalizeHumanAsks,
|
|
95
96
|
openHumanAsks,
|
|
@@ -453,7 +454,7 @@ function exitMissionError(message, code = 1, asJson = false) {
|
|
|
453
454
|
}
|
|
454
455
|
|
|
455
456
|
// Write-time warnings judge only what a machine can truly judge: identifiers,
|
|
456
|
-
// flags, and task ids. Whether a why is present is a judgment call
|
|
457
|
+
// flags, and task ids. Whether a why is present is a judgment call, the tick
|
|
457
458
|
// prompt demands it, the review pass judges it. A warning that cries wolf on
|
|
458
459
|
// plain sentences teaches agents to ignore it (golden-path papercut). The
|
|
459
460
|
// strict operatorReady bar stays at the digest surface, where under-showing
|
|
@@ -1203,7 +1204,7 @@ function listWorktreeRollupMissions(root = process.cwd()) {
|
|
|
1203
1204
|
// run/tick/show. This is the same local+rollup, deduped-by-id, list-sorted
|
|
1204
1205
|
// set `mission status`/`list` renders, so id/slug/suffix lookups search
|
|
1205
1206
|
// exactly what list showed. n stays exactly as each mission's home workspace
|
|
1206
|
-
// assigned it (loadMissionMap owns that)
|
|
1207
|
+
// assigned it (loadMissionMap owns that), this index does NOT renumber,
|
|
1207
1208
|
// because n must stay a stable, durable handle across saves (see
|
|
1208
1209
|
// mission-number-stability.test.js); numeric-n resolution stays scoped to
|
|
1209
1210
|
// the local root, where n is guaranteed unique.
|
|
@@ -1266,7 +1267,7 @@ function missionNumberDisplayIndex(missions, canonicalMissions = missions) {
|
|
|
1266
1267
|
const MISSION_REF_SUFFIX_MIN_LENGTH = 6;
|
|
1267
1268
|
|
|
1268
1269
|
// A ref that "looks like" an id/suffix/number is a single token with no
|
|
1269
|
-
// whitespace matching a hex/id/numeric shape. Those must resolve or error
|
|
1270
|
+
// whitespace matching a hex/id/numeric shape. Those must resolve or error,
|
|
1270
1271
|
// never silently fall through to starting a new mission (a mistyped id/n
|
|
1271
1272
|
// creating a junk mission is the worst failure mode here).
|
|
1272
1273
|
function missionRefLooksLikeHandle(ref) {
|
|
@@ -1402,10 +1403,10 @@ function exitMissingMission(ref, code = 1, asJson = false, root = process.cwd())
|
|
|
1402
1403
|
} else if (candidates.length) {
|
|
1403
1404
|
console.error('Nearest candidates:');
|
|
1404
1405
|
for (const candidate of candidates) {
|
|
1405
|
-
console.error(` #${candidate.n} ${candidate.id} (${candidate.status})
|
|
1406
|
+
console.error(` #${candidate.n} ${candidate.id} (${candidate.status}), ${candidate.objective}`);
|
|
1406
1407
|
}
|
|
1407
1408
|
} else {
|
|
1408
|
-
console.error('Mission ids are workspace-local
|
|
1409
|
+
console.error('Mission ids are workspace-local, run this from the workspace that created the mission.');
|
|
1409
1410
|
}
|
|
1410
1411
|
}
|
|
1411
1412
|
process.exit(code);
|
|
@@ -1435,7 +1436,7 @@ function resolveMission(ref, root = process.cwd()) {
|
|
|
1435
1436
|
return chosen;
|
|
1436
1437
|
}
|
|
1437
1438
|
if (matches.length) return matches[0];
|
|
1438
|
-
// No mission has this n. An all-digit ref can still be a hash suffix
|
|
1439
|
+
// No mission has this n. An all-digit ref can still be a hash suffix,
|
|
1439
1440
|
// missionId()'s 8-hex-char suffix is purely numeric ~2.4% of the time
|
|
1440
1441
|
// ((10/16)^8), and returning null here made those missions unresolvable
|
|
1441
1442
|
// by suffix. Fall through to slug/suffix resolution instead.
|
|
@@ -1500,7 +1501,7 @@ function saveMission(mission, root = process.cwd(), eventType = 'mission_updated
|
|
|
1500
1501
|
});
|
|
1501
1502
|
appendJsonLine(paths.missionsJsonl, next);
|
|
1502
1503
|
// Self-maintenance: once history outweighs live missions 4:1 the ledger is
|
|
1503
|
-
// rewritten to one row per mission (latest state, first display number
|
|
1504
|
+
// rewritten to one row per mission (latest state, first display number,
|
|
1504
1505
|
// exactly what loadMissionMap reconstructs). Every status/tick path parses
|
|
1505
1506
|
// this file several times, so unbounded snapshot history taxes everything.
|
|
1506
1507
|
try { compactMissionLedger(paths.missionsJsonl); } catch {}
|
|
@@ -1780,7 +1781,7 @@ function missionLandingStepSummary(summary) {
|
|
|
1780
1781
|
const withoutProofTail = withoutInternalId.replace(/;\s+(?=(?:PR\s+\d+|(?:node|npm|git)\b)).*$/i, '').trim();
|
|
1781
1782
|
const plainVerified = missionPlainVerifiedSummary(withoutProofTail);
|
|
1782
1783
|
if (plainVerified) return plainVerified;
|
|
1783
|
-
const beforeInlineProof = withoutProofTail.split(/\s+(
|
|
1784
|
+
const beforeInlineProof = withoutProofTail.split(/\s+(?:\u2014|-)\s+verified by\b/i)[0] || withoutProofTail;
|
|
1784
1785
|
const beforeChecks = (beforeInlineProof.split(/\s+(?:checks?|verified|proof):\s+/i)[0] || beforeInlineProof)
|
|
1785
1786
|
.replace(/,\s*(?:verifier|proof|checks?)\s+(?:still|pending)[.!]?$/i, '')
|
|
1786
1787
|
.trim();
|
|
@@ -2095,26 +2096,38 @@ function normalizeMissionReceiptResult(mission, result, receiptPath = '') {
|
|
|
2095
2096
|
|
|
2096
2097
|
function missionRunStartNextLine(mission, nextCommand, warnings = []) {
|
|
2097
2098
|
const missingVerifier = warnings.some((warning) => warning && warning.code === 'missing_verifier');
|
|
2098
|
-
if (missingVerifier) return 'Add a
|
|
2099
|
+
if (missingVerifier) return 'Add a completion check, then start the first piece of work.';
|
|
2099
2100
|
if (isCodexGoalMission(mission) && !codexNativeGoalAck(mission)) {
|
|
2100
|
-
return 'Start the
|
|
2101
|
+
return 'Start the first piece of work in this chat.';
|
|
2101
2102
|
}
|
|
2102
|
-
if (/attach-task/.test(nextCommand || '')) return '
|
|
2103
|
-
return '
|
|
2103
|
+
if (/attach-task/.test(nextCommand || '')) return 'Start the first piece of work.';
|
|
2104
|
+
return 'Keep working on the next useful result.';
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function missionRunCheckPlanText(command) {
|
|
2108
|
+
const check = String(command || '').trim();
|
|
2109
|
+
if (!check) return 'No completion check is ready yet.';
|
|
2110
|
+
if (/^git\s+diff\s+--check\b/i.test(check)) {
|
|
2111
|
+
return 'Atris will check the changed files for formatting problems before the result is ready.';
|
|
2112
|
+
}
|
|
2113
|
+
if (/\bnode\s+--test\b/i.test(check)) {
|
|
2114
|
+
return 'Atris will run the relevant behavior checks before the result is ready.';
|
|
2115
|
+
}
|
|
2116
|
+
if (/^test\s+-s\s+\S+/i.test(check)) {
|
|
2117
|
+
return 'Atris will confirm the saved result exists and is not empty.';
|
|
2118
|
+
}
|
|
2119
|
+
return 'A completion check is ready and will run before the result is ready.';
|
|
2104
2120
|
}
|
|
2105
2121
|
|
|
2106
2122
|
function missionRunTakeoffLines(mission, { warnings = [], nextCommand = '' } = {}) {
|
|
2107
|
-
const checked = mission.verifier
|
|
2108
|
-
? `Verifier configured: ${mission.verifier}.`
|
|
2109
|
-
: 'No verifier was recorded for this mission.';
|
|
2110
2123
|
return [
|
|
2111
|
-
'
|
|
2124
|
+
'Mission started.',
|
|
2112
2125
|
` Goal: ${mission.objective}`,
|
|
2113
|
-
` Done when: ${mission.stop_condition || 'the
|
|
2126
|
+
` Done when: ${mission.stop_condition || 'the result is checked and ready to review'}.`,
|
|
2114
2127
|
...(missionBudgetLine(mission) ? [` Budget: ${missionBudgetLine(mission)}`] : []),
|
|
2115
2128
|
...missionGoalChainLines(mission),
|
|
2116
|
-
'
|
|
2117
|
-
`
|
|
2129
|
+
' Saved: Atris will remember this mission and its progress.',
|
|
2130
|
+
` How it will be checked: ${missionRunCheckPlanText(mission.verifier)}`,
|
|
2118
2131
|
` Next: ${missionRunStartNextLine(mission, nextCommand, warnings)}`,
|
|
2119
2132
|
];
|
|
2120
2133
|
}
|
|
@@ -2176,16 +2189,9 @@ function selectMissionRunUsefulTarget(rawObjective, root = process.cwd()) {
|
|
|
2176
2189
|
}
|
|
2177
2190
|
}
|
|
2178
2191
|
|
|
2179
|
-
function
|
|
2180
|
-
const clean = String(text || '').replace(/\s+/g, ' ').trim();
|
|
2181
|
-
if (clean.length <= max) return clean;
|
|
2182
|
-
return `${clean.slice(0, max - 3).replace(/\s+\S*$/, '').trimEnd()}...`;
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
function missionRunPreflightObjective(rawObjective, room, owner) {
|
|
2186
|
-
const name = room?.name || 'Mission Room';
|
|
2192
|
+
function missionRunPreflightObjective(rawObjective, room) {
|
|
2187
2193
|
const task = room?.task_plan_preview?.task || room?.truth_snapshot || rawObjective;
|
|
2188
|
-
return
|
|
2194
|
+
return String(task || rawObjective || '').replace(/\s+/g, ' ').trim();
|
|
2189
2195
|
}
|
|
2190
2196
|
|
|
2191
2197
|
function missionRunTrustedObjective(rawObjective, room, target) {
|
|
@@ -2257,7 +2263,7 @@ function buildMissionRunRoomPreflight(rawObjective, args = [], options = {}) {
|
|
|
2257
2263
|
const written = writeMissionRoomReceipt(room, { root });
|
|
2258
2264
|
const shapedObjective = trustedRun
|
|
2259
2265
|
? missionRunTrustedObjective(rawObjective, written.room, selectedTarget)
|
|
2260
|
-
: missionRunPreflightObjective(rawObjective, written.room
|
|
2266
|
+
: missionRunPreflightObjective(rawObjective, written.room);
|
|
2261
2267
|
const shaping = guardMissionRunShaping(rawObjective, shapedObjective, selectedTarget);
|
|
2262
2268
|
const acceptedTarget = shaping.selectedTarget;
|
|
2263
2269
|
const taskSpineRequired = !acceptedTarget && (explicitPreflight || signalPreflight);
|
|
@@ -4022,7 +4028,7 @@ function inheritedWorktreeBase(cwd) {
|
|
|
4022
4028
|
// Dedup gate: the same objective + owner already active anywhere in the
|
|
4023
4029
|
// workspace family (this store or any worktree's) is reused, never cloned.
|
|
4024
4030
|
// Born 2026-07-02: an hourly alive loop spawned six identical auto-improver
|
|
4025
|
-
// missions in six fresh worktrees in one day
|
|
4031
|
+
// missions in six fresh worktrees in one day, pure token burn. --duplicate
|
|
4026
4032
|
// is the explicit escape hatch.
|
|
4027
4033
|
const TWIN_ACTIVE_STATUSES = new Set(['planning', 'ready', 'running']);
|
|
4028
4034
|
|
|
@@ -4069,7 +4075,7 @@ function startMission(args, options = {}) {
|
|
|
4069
4075
|
printJsonOrText(
|
|
4070
4076
|
{ ok: true, action: 'mission_recovered', recovered: true, mission: existing, note: 'objective looked like a mission id; recovered the existing mission instead of creating a new one' },
|
|
4071
4077
|
[
|
|
4072
|
-
`That's a mission id, not an objective
|
|
4078
|
+
`That's a mission id, not an objective, recovered ${existing.id} (${existing.status}).`,
|
|
4073
4079
|
`Resume: atris mission run ${existing.id}`,
|
|
4074
4080
|
],
|
|
4075
4081
|
asJson,
|
|
@@ -4119,7 +4125,7 @@ function startMission(args, options = {}) {
|
|
|
4119
4125
|
{ ok: true, action: 'mission_reused', reused: true, mission: twin, note: 'an active mission with this objective and owner already exists; resumed instead of cloning (pass --duplicate to force a second one)' },
|
|
4120
4126
|
[
|
|
4121
4127
|
`Already active: ${twin.id} (${twin.status})`,
|
|
4122
|
-
`Same objective, same owner
|
|
4128
|
+
`Same objective, same owner, reusing it instead of starting a clone.`,
|
|
4123
4129
|
`Resume: atris mission run ${twin.id}`,
|
|
4124
4130
|
`Really want a second one: re-run with --duplicate`,
|
|
4125
4131
|
],
|
|
@@ -4140,7 +4146,7 @@ function startMission(args, options = {}) {
|
|
|
4140
4146
|
}
|
|
4141
4147
|
// --worktree: bind the mission to its own isolated checkout. We chdir before
|
|
4142
4148
|
// any state writes so the mission record, baseline sidecar, receipts, and
|
|
4143
|
-
// member files all land inside the worktree
|
|
4149
|
+
// member files all land inside the worktree, ticks run there, and the main
|
|
4144
4150
|
// checkout's dirt never reaches the mission baseline.
|
|
4145
4151
|
if (hasFlag(args, '--worktree')) {
|
|
4146
4152
|
let created;
|
|
@@ -4268,14 +4274,15 @@ async function startMissionFromRunObjective(objective, args) {
|
|
|
4268
4274
|
landRun ? DEFAULT_LONG_RUN_VERIFIER : inferRunObjectiveVerifier(missionObjective)
|
|
4269
4275
|
|| inferRunObjectiveVerifier(rawObjective)
|
|
4270
4276
|
|| (missionRunPreflight?.trusted_run ? DEFAULT_LONG_RUN_VERIFIER : '')
|
|
4271
|
-
|| (inferredLoop.wantsLongRun ? DEFAULT_LONG_RUN_VERIFIER : '')
|
|
4277
|
+
|| (inferredLoop.wantsLongRun ? DEFAULT_LONG_RUN_VERIFIER : '')
|
|
4278
|
+
|| (hasFlag(args, '--no-verify') ? '' : resolveDefaultVerifier(process.cwd(), { allowBroadSuite: false })),
|
|
4272
4279
|
);
|
|
4273
4280
|
const stopCondition = readFlag(
|
|
4274
4281
|
args,
|
|
4275
4282
|
'--stop',
|
|
4276
4283
|
budgetStopCondition(budgetContract) || (inferredLoop.wantsLongRun
|
|
4277
4284
|
? `run for ${inferredLoop.requestedHours || 'the requested overnight window'} hour${inferredLoop.requestedHours === 1 ? '' : 's'}, or stop when proof is ready`
|
|
4278
|
-
: (verifier ? '
|
|
4285
|
+
: (verifier ? 'the first result is checked and ready to review' : 'the first result is saved and ready to review')),
|
|
4279
4286
|
);
|
|
4280
4287
|
const startArgs = [
|
|
4281
4288
|
missionObjective,
|
|
@@ -5083,7 +5090,7 @@ function missionRunsReceiptIndex(root = process.cwd()) {
|
|
|
5083
5090
|
}
|
|
5084
5091
|
|
|
5085
5092
|
// Roll every ran tick for a mission into a verification-debt tally. `unchecked`
|
|
5086
|
-
// counts ran ticks that recorded no verifier result at all
|
|
5093
|
+
// counts ran ticks that recorded no verifier result at all, the "tick recorded
|
|
5087
5094
|
// but nothing was checked" red the report and now.md rollup surface and count
|
|
5088
5095
|
// against the mission.
|
|
5089
5096
|
function missionVerificationDebt(mission, root = process.cwd()) {
|
|
@@ -5111,7 +5118,7 @@ function missionVerificationDebt(mission, root = process.cwd()) {
|
|
|
5111
5118
|
// when every ran tick was checked, so clean missions stay quiet.
|
|
5112
5119
|
function missionVerificationDebtLine(debt) {
|
|
5113
5120
|
if (!debt || !debt.unchecked) return '';
|
|
5114
|
-
return `unverified ticks: ${debt.unchecked} of ${debt.ran} ran no check
|
|
5121
|
+
return `unverified ticks: ${debt.unchecked} of ${debt.ran} ran no check ⚠, treat those increments as unproven`;
|
|
5115
5122
|
}
|
|
5116
5123
|
|
|
5117
5124
|
function missionReportTimeline(mission, root = process.cwd(), limit = 6) {
|
|
@@ -6026,7 +6033,7 @@ function doctorMission(args) {
|
|
|
6026
6033
|
if (findings.length) process.exitCode = 1;
|
|
6027
6034
|
}
|
|
6028
6035
|
|
|
6029
|
-
// `atris mission watch [id]
|
|
6036
|
+
// `atris mission watch [id]`, read-only live heartbeat. Prints a line per tick as it
|
|
6030
6037
|
// lands so a human (or any terminal) can see the loop is alive without rerunning status.
|
|
6031
6038
|
function watchMission(args) {
|
|
6032
6039
|
if (hasFlag(args, '--help') || hasFlag(args, '-h') || String(args[0] || '').trim() === 'help') {
|
|
@@ -6052,7 +6059,7 @@ function watchMission(args) {
|
|
|
6052
6059
|
const fingerprint = (mission) => [mission.status, mission.last_tick_at, mission.last_tick_index, mission.receipt_path].join('|');
|
|
6053
6060
|
const tickNote = (mission) => {
|
|
6054
6061
|
const heartbeat = missionHeartbeatLines(mission).map((line) => line.trim()).join(', ');
|
|
6055
|
-
return `${heartbeat || `state: ${mission.status}`}${mission.receipt_path ?
|
|
6062
|
+
return `${heartbeat || `state: ${mission.status}`}${mission.receipt_path ? `, proof: ${mission.receipt_path}` : ''}`;
|
|
6056
6063
|
};
|
|
6057
6064
|
const seen = new Map();
|
|
6058
6065
|
let lastIdleAt = Date.now();
|
|
@@ -6060,7 +6067,7 @@ function watchMission(args) {
|
|
|
6060
6067
|
const poll = () => {
|
|
6061
6068
|
const targets = loadTargets();
|
|
6062
6069
|
if (!targets.length && !seen.size) {
|
|
6063
|
-
emitOnce('no active missions yet
|
|
6070
|
+
emitOnce('no active missions yet, waiting');
|
|
6064
6071
|
}
|
|
6065
6072
|
let changed = false;
|
|
6066
6073
|
for (const mission of targets) {
|
|
@@ -6101,6 +6108,7 @@ function writeReceipt(mission, result, root = process.cwd()) {
|
|
|
6101
6108
|
const finalResult = normalizeMissionReceiptResult(mission, result, relativeReceiptPath);
|
|
6102
6109
|
const receipt = {
|
|
6103
6110
|
schema: 'atris.mission_receipt.v1',
|
|
6111
|
+
tree_hash: treeHashFor(root),
|
|
6104
6112
|
mission_id: mission.id,
|
|
6105
6113
|
objective: mission.objective,
|
|
6106
6114
|
owner: mission.owner,
|
|
@@ -6349,7 +6357,7 @@ function loadMissionWorktreeBaseline(missionId, root = process.cwd()) {
|
|
|
6349
6357
|
// Closed missions no longer tick, so the sidecar is dead weight; prune it and
|
|
6350
6358
|
// fold a compact audit summary into the mission record (full path lists stay
|
|
6351
6359
|
// out of missions.jsonl, which re-appends the whole record on every save).
|
|
6352
|
-
// Paused missions keep their sidecar
|
|
6360
|
+
// Paused missions keep their sidecar, resume ticks still subtract it.
|
|
6353
6361
|
function pruneMissionWorktreeBaseline(mission, root = process.cwd()) {
|
|
6354
6362
|
const baseline = loadMissionWorktreeBaseline(mission.id, root);
|
|
6355
6363
|
try { fs.rmSync(missionBaselinePath(mission.id, root), { force: true }); } catch {}
|
|
@@ -6380,7 +6388,7 @@ function worktreeReceipt(before, after, { verifier = '', baseline = null } = {})
|
|
|
6380
6388
|
// tick-start snapshot for legacy missions). Only paths dirtied beyond that
|
|
6381
6389
|
// baseline count toward the unverified signal. Loop exhaust the mission
|
|
6382
6390
|
// writes about itself (state plane, receipts, rendered status) is not work
|
|
6383
|
-
// product, so it never counts
|
|
6391
|
+
// product, so it never counts, otherwise every multi-tick mission in a repo
|
|
6384
6392
|
// that doesn't gitignore those dirs would flag its own bookkeeping.
|
|
6385
6393
|
const baselinePaths = baseline
|
|
6386
6394
|
? new Set(baseline.paths)
|
|
@@ -6409,7 +6417,7 @@ function worktreeReceipt(before, after, { verifier = '', baseline = null } = {})
|
|
|
6409
6417
|
}
|
|
6410
6418
|
|
|
6411
6419
|
// ---------------------------------------------------------------------------
|
|
6412
|
-
// `atris mission run <id
|
|
6420
|
+
// `atris mission run <id>`, bounded local headless loop. v0.1.
|
|
6413
6421
|
// Spawns `claude -p --resume <session>` per tick. Honors cadence, active-hours,
|
|
6414
6422
|
// rate-limit info, and a flock per mission. `max-ticks` bounds total attempts;
|
|
6415
6423
|
// `ran_ticks` separately reports ticks that actually made progress.
|
|
@@ -7811,19 +7819,19 @@ function consecutiveVerifierFails(ticks) {
|
|
|
7811
7819
|
}
|
|
7812
7820
|
|
|
7813
7821
|
// BCK-1324: a "holding tick" is a tick that reports status=ran/reason=tick-ok
|
|
7814
|
-
// (so it never trips the error-streak breakers) but left no structural trace
|
|
7822
|
+
// (so it never trips the error-streak breakers) but left no structural trace,
|
|
7815
7823
|
// no new or cleared dirty files beyond whatever was already dirty at tick
|
|
7816
7824
|
// start, and no fresh verifier pass. Claude's own summary text ("holding
|
|
7817
7825
|
// tick, no drift") is not the signal: agents self-label busywork as progress
|
|
7818
7826
|
// constantly. The worktree diff and verifier result are ground truth.
|
|
7819
7827
|
function tickMadeProgress(tick) {
|
|
7820
|
-
if (!tick || tick.status !== 'ran') return true; // errors/skips aren't "idle"
|
|
7828
|
+
if (!tick || tick.status !== 'ran') return true; // errors/skips aren't "idle", other breakers own those
|
|
7821
7829
|
const wt = tick.worktree;
|
|
7822
7830
|
if (wt && wt.available) {
|
|
7823
7831
|
if ((wt.new_dirty_count || 0) > 0) return true;
|
|
7824
7832
|
if ((wt.cleared_dirty_count || 0) > 0) return true;
|
|
7825
7833
|
} else {
|
|
7826
|
-
// No worktree signal available (e.g. git unavailable)
|
|
7834
|
+
// No worktree signal available (e.g. git unavailable), don't punish a
|
|
7827
7835
|
// tick we have no evidence against.
|
|
7828
7836
|
return true;
|
|
7829
7837
|
}
|
|
@@ -7833,7 +7841,7 @@ function tickMadeProgress(tick) {
|
|
|
7833
7841
|
|
|
7834
7842
|
// Count the trailing run of ticks (most recent first) that made no progress
|
|
7835
7843
|
// per tickMadeProgress. A single progressing tick anywhere in the run resets
|
|
7836
|
-
// this to 0
|
|
7844
|
+
// this to 0, only the tail streak matters.
|
|
7837
7845
|
function consecutiveNoProgressTicks(ticks) {
|
|
7838
7846
|
let n = 0;
|
|
7839
7847
|
for (let i = ticks.length - 1; i >= 0; i--) {
|
|
@@ -7854,7 +7862,7 @@ function missionJudgmentPlainText(value) {
|
|
|
7854
7862
|
.replace(/\b[0-9A-HJKMNP-TV-Z]{26}\b/gi, ' ')
|
|
7855
7863
|
.replace(/\b[0-9a-f]{12,40}\b/gi, ' ')
|
|
7856
7864
|
.replace(/\b[A-Z][A-Z0-9]{1,9}-\d+\b/g, ' ')
|
|
7857
|
-
.replace(/[
|
|
7865
|
+
.replace(/[\u2014–]/g, ' ')
|
|
7858
7866
|
.replace(/[*_~#>]+/g, ' ')
|
|
7859
7867
|
.replace(/\s+/g, ' ')
|
|
7860
7868
|
.replace(/^[\s:;,.-]+|[\s:;,.-]+$/g, '')
|
|
@@ -8202,7 +8210,7 @@ function acquireMissionLock(missionId, root = process.cwd(), options = {}) {
|
|
|
8202
8210
|
const holderKnown = Number.isInteger(holderPid) && holderPid > 0;
|
|
8203
8211
|
// A lock is created empty (openSync 'wx') and its pid record is written a
|
|
8204
8212
|
// moment later. A racing waiter that reads the file inside that window sees
|
|
8205
|
-
// no pid
|
|
8213
|
+
// no pid, do NOT treat that as a dead owner, or two processes both "own"
|
|
8206
8214
|
// the lock and clobber each other's writes. Only steal when the holder pid
|
|
8207
8215
|
// is known-dead, or when a pidless lock has sat abandoned past the tiny
|
|
8208
8216
|
// creation window (guards against a crash between openSync and writeSync).
|
|
@@ -8434,7 +8442,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
|
|
|
8434
8442
|
`**Lane:** ${frozen.lane}`,
|
|
8435
8443
|
`**Cadence:** ${mission.cadence}`,
|
|
8436
8444
|
`**Stop condition:** ${mission.stop_condition || 'human marks complete'}`,
|
|
8437
|
-
`**Verifier (frozen):** ${frozen.verifier || '(none
|
|
8445
|
+
`**Verifier (frozen):** ${frozen.verifier || '(none, receipt only)'}`,
|
|
8438
8446
|
`**Last status:** ${mission.status}`,
|
|
8439
8447
|
`**Last tick:** ${mission.last_tick_at || 'never'}`,
|
|
8440
8448
|
...missionBudgetPromptLines(mission),
|
|
@@ -8442,7 +8450,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
|
|
|
8442
8450
|
``,
|
|
8443
8451
|
`## Your task`,
|
|
8444
8452
|
`Do ONE increment of work toward the stop condition. ONE. No more.`,
|
|
8445
|
-
`- You are the member "${mission.owner}". Read atris/team/${mission.owner}/MEMBER.md (and SOUL.md if present) before acting
|
|
8453
|
+
`- You are the member "${mission.owner}". Read atris/team/${mission.owner}/MEMBER.md (and SOUL.md if present) before acting, work in that identity, inside its scope and stop rules. After your work, append what you did and what you learned to atris/team/${mission.owner}/logs/<today's date>.md.`,
|
|
8446
8454
|
`- FIRST: inspect current mission/task state before acting. Read the relevant files, run \`atris mission status ${mission.id}\`, \`git status\`, or \`atris task list\` as needed so you know what's already done.`,
|
|
8447
8455
|
`- Pick the smallest concrete action that moves the mission forward.`,
|
|
8448
8456
|
`- Before acting, state your single next move in one sentence.`,
|
|
@@ -8473,14 +8481,14 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
|
|
|
8473
8481
|
`- Do NOT start new missions, modify other missions, or expand scope.`,
|
|
8474
8482
|
`- Do NOT run destructive commands without strong evidence they're correct.`,
|
|
8475
8483
|
``,
|
|
8476
|
-
`When done, output a short receipt. The summary's first line must name what changed and how it was verified. Then include: (1) the exact files edited / commands run / artifacts produced
|
|
8484
|
+
`When done, output a short receipt. The summary's first line must name what changed and how it was verified. Then include: (1) the exact files edited / commands run / artifacts produced, name them, (1b) one verify command a reviewer can rerun to check the work, (2) the metric of progress, (3) what the next tick should pick up. End the receipt with one line naming the layer this tick touched: \`layer: identity|beliefs|capabilities|behaviors|environment\` (final line, the harness parses it).`,
|
|
8477
8485
|
];
|
|
8478
8486
|
if (mission.task_ids?.length) {
|
|
8479
8487
|
lines.push('', `## Task ids`, mission.task_ids.map((t) => `- ${t}`).join('\n'));
|
|
8480
8488
|
}
|
|
8481
8489
|
const humanAsks = openHumanAsks(mission.human_asks);
|
|
8482
8490
|
if (humanAsks.length) {
|
|
8483
|
-
lines.push('', `## Human asks (don't act on these
|
|
8491
|
+
lines.push('', `## Human asks (don't act on these, surface them)`, humanAsks.map((ask) => `- ${ask.text}`).join('\n'));
|
|
8484
8492
|
}
|
|
8485
8493
|
return lines.join('\n');
|
|
8486
8494
|
}
|
|
@@ -8515,16 +8523,16 @@ function missionPauseNextAction(pauseReason, missionId, deadModel = null, lastEr
|
|
|
8515
8523
|
return 'stopped: the worker kept reporting the same thing 3 times in a row';
|
|
8516
8524
|
}
|
|
8517
8525
|
if (pauseReason === 'model-unavailable' && deadModel) {
|
|
8518
|
-
return `model "${deadModel}" is unavailable
|
|
8526
|
+
return `model "${deadModel}" is unavailable, set a live model (mission.model, ATRIS_RUNNER_MODEL, or legacy ATRIS_CLAUDE_MODEL), then: atris mission run ${missionId}`;
|
|
8519
8527
|
}
|
|
8520
8528
|
if (typeof pauseReason === 'string' && pauseReason.startsWith('repeated-error:')) {
|
|
8521
8529
|
const reason = pauseReason.slice('repeated-error:'.length);
|
|
8522
|
-
return `tick kept failing with "${reason}"
|
|
8530
|
+
return `tick kept failing with "${reason}", inspect the last receipt, fix the cause, then: atris mission run ${missionId}`;
|
|
8523
8531
|
}
|
|
8524
8532
|
// Single-tick cron runs pause via max-ticks-reached on the very first errored tick.
|
|
8525
8533
|
// A bare "resume" there just re-errors; point the operator at the cause instead.
|
|
8526
8534
|
if (pauseReason === 'max-ticks-reached' && lastErrorReason) {
|
|
8527
|
-
return `hit the tick budget while erroring ("${lastErrorReason}")
|
|
8535
|
+
return `hit the tick budget while erroring ("${lastErrorReason}"), inspect the last receipt before resuming: atris mission run ${missionId}`;
|
|
8528
8536
|
}
|
|
8529
8537
|
return `resume with: atris mission run ${missionId}`;
|
|
8530
8538
|
}
|
|
@@ -9081,7 +9089,7 @@ async function parseAndValidateMissionRunPhase(args) {
|
|
|
9081
9089
|
}
|
|
9082
9090
|
// --fleet: staff every idle capable engine on the board's claimable
|
|
9083
9091
|
// safe-lane tasks, build in parallel worktrees, land serially. Humble flag,
|
|
9084
|
-
// full loop
|
|
9092
|
+
// full loop, see lib/fleet.js. --dry-run previews the staffing only.
|
|
9085
9093
|
if (hasFlag(args, '--fleet')) {
|
|
9086
9094
|
const { runFleetFlight } = require('../lib/fleet');
|
|
9087
9095
|
const slots = Math.max(1, Number(readFlag(args, '--slots', '')) || 3);
|
|
@@ -9202,7 +9210,7 @@ async function resolveMissionRunPhase(context) {
|
|
|
9202
9210
|
// BCK-1319: a bare single token that looks like an id/suffix/number (no
|
|
9203
9211
|
// whitespace, hex/id/numeric shape) is a mistyped or stale mission handle,
|
|
9204
9212
|
// not a new objective. Silently starting a fresh mission from it is the
|
|
9205
|
-
// worst failure mode
|
|
9213
|
+
// worst failure mode, it buries the mission the operator meant to run.
|
|
9206
9214
|
// Only a genuine multi-word (or non-handle-shaped) ref keeps the
|
|
9207
9215
|
// start-a-new-mission shortcut.
|
|
9208
9216
|
if (!mission && ref && missionRefLooksLikeHandle(ref)) {
|
|
@@ -9383,7 +9391,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9383
9391
|
const effectiveMaxTicks = (cadenceSeconds === 0 && !hasExplicitTickBudget) ? 1 : maxTicks;
|
|
9384
9392
|
|
|
9385
9393
|
// Session setup: only Claude-backed workers need a persisted session id.
|
|
9386
|
-
// atris2 turns are stateless per tick
|
|
9394
|
+
// atris2 turns are stateless per tick, continuity lives on disk (logs, receipts, now.md).
|
|
9387
9395
|
if (!skipWorker && !atris2Runner && !drillRunner && !sessionId && !pendingSessionId) {
|
|
9388
9396
|
pendingSessionId = crypto.randomUUID();
|
|
9389
9397
|
mission = saveMission({ ...mission, pending_session_id: pendingSessionId }, cwd, 'mission_session_pending', { session_id: pendingSessionId }).mission;
|
|
@@ -9464,7 +9472,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9464
9472
|
// the auto runner has already resolved this tick to a *different* ready engine
|
|
9465
9473
|
// (the walled one is now credit_out and excluded from resolution), the run-wide
|
|
9466
9474
|
// cooldown no longer applies. Clear it so the fresh engine runs now instead of
|
|
9467
|
-
// the loop sleeping through
|
|
9475
|
+
// the loop sleeping through, or pausing on `rate-limit-exceeded-wall` for, a
|
|
9468
9476
|
// wall this engine never hit.
|
|
9469
9477
|
if (lastRateLimit && rateLimitedEngineId && tickEngineId && tickEngineId !== rateLimitedEngineId) {
|
|
9470
9478
|
result.engine_swapped_from = rateLimitedEngineId;
|
|
@@ -9663,8 +9671,8 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9663
9671
|
// held, so the next resume fails with "already in use". A session
|
|
9664
9672
|
// that was cleaned up between ticks fails the resume the other way,
|
|
9665
9673
|
// with "No conversation found with session ID". Both mean the stored
|
|
9666
|
-
// id is dead. Session continuity is disposable
|
|
9667
|
-
// on disk (receipts, logs, now.md)
|
|
9674
|
+
// id is dead. Session continuity is disposable, mission state lives
|
|
9675
|
+
// on disk (receipts, logs, now.md), so rotate to a fresh id instead
|
|
9668
9676
|
// of grinding the repeated-error breaker on a stale session.
|
|
9669
9677
|
const staleSession = /session id .* is already in use/i.test(claudeResult.stderr || '')
|
|
9670
9678
|
|| /no conversation found with session id/i.test(claudeResult.stderr || '');
|
|
@@ -9692,7 +9700,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9692
9700
|
break;
|
|
9693
9701
|
}
|
|
9694
9702
|
} else if (sessionId && claudeResult.sessionIds.length > 0 && !claudeResult.sessionIds.includes(sessionId)) {
|
|
9695
|
-
// session_id mismatch on a resumed session
|
|
9703
|
+
// session_id mismatch on a resumed session, abort run
|
|
9696
9704
|
pauseReason = 'session-id-mismatch';
|
|
9697
9705
|
break;
|
|
9698
9706
|
}
|
|
@@ -9741,7 +9749,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9741
9749
|
|| worktreeReceipt(tickWorktreeBefore, gitWorktreeSnapshot(cwd), { verifier: frozen.verifier, baseline: runWorktreeBaseline });
|
|
9742
9750
|
|
|
9743
9751
|
// Layer classification needs the receipt text AND the worktree receipt, so it
|
|
9744
|
-
// runs here
|
|
9752
|
+
// runs here, after both exist, covering the claude and atris2 branches alike.
|
|
9745
9753
|
if (!(cachedStep && cachedStep.tick && cachedStep.tick.layer)) {
|
|
9746
9754
|
const tickReceiptText = result.atris2?.receipt_text || result.claude?.receipt_text || result.drill?.receipt_text || '';
|
|
9747
9755
|
const layerInfo = extractLayerFromReceiptText(tickReceiptText, tickWorktree?.new_since_baseline_sample);
|
|
@@ -9770,7 +9778,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9770
9778
|
const fullBudgetMode = budgetRemainingSeconds > 0;
|
|
9771
9779
|
// Worker may call `mission complete` mid-tick. Re-read disk so we do not
|
|
9772
9780
|
// demote terminal status back to ready (always-on + verifier-pass used to
|
|
9773
|
-
// reopen closed rooms
|
|
9781
|
+
// reopen closed rooms, mission 32 complete@19:23:33 → ready@19:23:48).
|
|
9774
9782
|
const latestOnDisk = loadMissionMap(cwd).get(mission.id) || mission;
|
|
9775
9783
|
const alreadyTerminal = TERMINAL_STATUSES.has(String(latestOnDisk.status || ''));
|
|
9776
9784
|
let newStatus = (verifierResult?.passed && mission.always_on) ? 'ready' :
|
|
@@ -9863,7 +9871,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9863
9871
|
ranTicks++;
|
|
9864
9872
|
backoffAttempt = 0;
|
|
9865
9873
|
} else if (result.status === 'errored' && result.reason !== 'claude-session-busy') {
|
|
9866
|
-
// A rotated session is already healed
|
|
9874
|
+
// A rotated session is already healed, the next tick starts on a
|
|
9867
9875
|
// fresh id, so backing off just burns wall clock. If rotation itself
|
|
9868
9876
|
// keeps failing, the repeated-error breaker below still stops the run.
|
|
9869
9877
|
backoffAttempt++;
|
|
@@ -9887,7 +9895,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9887
9895
|
if (consecutiveIdenticalSummaryTicks(ticks) >= 3) { pauseReason = 'stuck-repeating'; break; }
|
|
9888
9896
|
// A retired/inaccessible model is deterministic: the id is fixed for the run, so
|
|
9889
9897
|
// every remaining tick (and every future cron firing) fails identically. Backoff
|
|
9890
|
-
// only slows the bleeding. Stop on first detection and surface the dead id
|
|
9898
|
+
// only slows the bleeding. Stop on first detection and surface the dead id,
|
|
9891
9899
|
// CLI-245 named this failure; this stops the loop from grinding on it forever.
|
|
9892
9900
|
if (result.status === 'errored' && result.reason === 'model-unavailable') { pauseReason = 'model-unavailable'; break; }
|
|
9893
9901
|
// Any OTHER error that recurs identically (claude-timeout, atris2-error, claude-error)
|
|
@@ -10054,12 +10062,12 @@ function completeMissionRunPhase(context) {
|
|
|
10054
10062
|
}
|
|
10055
10063
|
if (context.handled) return;
|
|
10056
10064
|
|
|
10057
|
-
// BCK-1324: no-progress is a clean, honest stop
|
|
10065
|
+
// BCK-1324: no-progress is a clean, honest stop, the run did what it
|
|
10058
10066
|
// could and correctly recognized there was nothing left to do. It is NOT
|
|
10059
10067
|
// a failure/blocker: pausing it (resumable, retried by cron/self-drive)
|
|
10060
10068
|
// or dispatching handleMissionBlocker (files a fleet task, dispatches an
|
|
10061
10069
|
// engine) would recreate the exact busywork loop this guard exists to
|
|
10062
|
-
// stop. Give it its own branch, shaped like stopMission()'s stop path
|
|
10070
|
+
// stop. Give it its own branch, shaped like stopMission()'s stop path,
|
|
10063
10071
|
// status=stopped, a receipt, no escalation, no blocker.
|
|
10064
10072
|
if (pauseReason === 'no-progress') {
|
|
10065
10073
|
const stoppedAt = stampIso();
|
|
@@ -10077,7 +10085,7 @@ function completeMissionRunPhase(context) {
|
|
|
10077
10085
|
stop_reason: noProgressReason,
|
|
10078
10086
|
receipt_path: noProgressReceipt || mission.receipt_path || null,
|
|
10079
10087
|
worktree_baseline: baselineSummary || mission.worktree_baseline || null,
|
|
10080
|
-
next_action: 'mission stopped: no progress
|
|
10088
|
+
next_action: 'mission stopped: no progress, inspect the last few receipts, then start a fresh mission or resume with new context',
|
|
10081
10089
|
}, cwd, 'mission_run_stopped_no_progress', {
|
|
10082
10090
|
reason: noProgressReason,
|
|
10083
10091
|
idle_ticks: idleCount,
|
|
@@ -10106,7 +10114,7 @@ function completeMissionRunPhase(context) {
|
|
|
10106
10114
|
}).mission;
|
|
10107
10115
|
}
|
|
10108
10116
|
|
|
10109
|
-
// 'no-progress' is a clean stop the run diagnosed itself
|
|
10117
|
+
// 'no-progress' is a clean stop the run diagnosed itself, never route it
|
|
10110
10118
|
// through handleMissionBlocker (that would file a blocker task and
|
|
10111
10119
|
// dispatch an engine to "fix" a mission that correctly stopped itself).
|
|
10112
10120
|
const blockerReason = ['no-progress', 'budget-exhausted'].includes(pauseReason)
|
|
@@ -10226,7 +10234,7 @@ function tickMission(args) {
|
|
|
10226
10234
|
}
|
|
10227
10235
|
|
|
10228
10236
|
try {
|
|
10229
|
-
// Re-read inside the lock
|
|
10237
|
+
// Re-read inside the lock, the initial resolveMission ran before we held it.
|
|
10230
10238
|
mission = resolveMission(mission.id) || mission;
|
|
10231
10239
|
|
|
10232
10240
|
if (['complete', 'stopped'].includes(mission.status)) {
|
|
@@ -10241,7 +10249,7 @@ function tickMission(args) {
|
|
|
10241
10249
|
// budget window from now BEFORE the budget check below reads it. Without this,
|
|
10242
10250
|
// a spend-full-budget mission whose original window already elapsed, resumed
|
|
10243
10251
|
// via `mission tick` rather than `mission run`, would compute zero remaining
|
|
10244
|
-
// budget on this very tick and land immediately
|
|
10252
|
+
// budget on this very tick and land immediately, the footgun `mission run`
|
|
10245
10253
|
// already closes. Stamping resumed_at here makes both resume paths agree.
|
|
10246
10254
|
mission = saveMission({
|
|
10247
10255
|
...mission,
|
|
@@ -10486,7 +10494,7 @@ function receiptShowsPass(receipt) {
|
|
|
10486
10494
|
|| result?.tick?.verifier_passed === true;
|
|
10487
10495
|
}
|
|
10488
10496
|
|
|
10489
|
-
// Terminal gate: a verifier mission may only complete on real evidence
|
|
10497
|
+
// Terminal gate: a verifier mission may only complete on real evidence, a
|
|
10490
10498
|
// passing receipt belonging to this mission, or durable state showing the
|
|
10491
10499
|
// verifier passed. Mirrors the task plane's proof-only accept guard so the
|
|
10492
10500
|
// final transition consumes the receipts instead of trusting free text.
|
|
@@ -10658,7 +10666,7 @@ function stopMission(args) {
|
|
|
10658
10666
|
}
|
|
10659
10667
|
|
|
10660
10668
|
// A mission parked in paused/planning/ready and untouched for a week is
|
|
10661
|
-
// abandoned in practice
|
|
10669
|
+
// abandoned in practice, nobody resumes it, and each one is a line the
|
|
10662
10670
|
// operator re-reads forever. The daily autoland tick expires them to
|
|
10663
10671
|
// stopped with a revive hint; running missions and anything touched
|
|
10664
10672
|
// recently are never aged out, and a tick on an expired id revives it.
|
|
@@ -10700,7 +10708,7 @@ function expireStaleMissions(root = process.cwd(), { idleDays = MISSION_IDLE_EXP
|
|
|
10700
10708
|
for (const mission of listMissions(root)) {
|
|
10701
10709
|
const status = String(mission.status || '').toLowerCase();
|
|
10702
10710
|
if (!statuses.has(status)) continue;
|
|
10703
|
-
// updated_at and paused_at are machine-polluted
|
|
10711
|
+
// updated_at and paused_at are machine-polluted, status renders and
|
|
10704
10712
|
// goal controllers re-save parked missions daily, so a mission nobody
|
|
10705
10713
|
// has run since May reads as "touched today". Real activity is the
|
|
10706
10714
|
// last tick (or creation, for missions that never ran).
|
|
@@ -10737,7 +10745,7 @@ function expireStaleMissions(root = process.cwd(), { idleDays = MISSION_IDLE_EXP
|
|
|
10737
10745
|
}
|
|
10738
10746
|
|
|
10739
10747
|
// Zombie-mission reap: a mission left paused past a short leash (48h default)
|
|
10740
|
-
// is dead in practice long before the 7-day general idle expiry ever fires
|
|
10748
|
+
// is dead in practice long before the 7-day general idle expiry ever fires,
|
|
10741
10749
|
// nobody is coming back to it inside a session, and it just sits on
|
|
10742
10750
|
// `mission list` as noise. Narrower than expireStaleMissions on purpose: only
|
|
10743
10751
|
// `paused`, never planning/ready, so the weekly cadence for those is unchanged.
|
|
@@ -11190,7 +11198,7 @@ function classifyPathsByLayer(paths) {
|
|
|
11190
11198
|
return winnerLayer ? { layer: winnerLayer, source: 'fallback' } : { layer: null, source: 'unknown' };
|
|
11191
11199
|
}
|
|
11192
11200
|
|
|
11193
|
-
// `atris mission layers
|
|
11201
|
+
// `atris mission layers`, per-layer growth curve across tick receipts. The member
|
|
11194
11202
|
// proof standard says: if every tick is one layer and none touch the others, the
|
|
11195
11203
|
// loop is doing work but not getting smarter. This makes that check one command.
|
|
11196
11204
|
function layersMission(args) {
|
|
@@ -11251,7 +11259,7 @@ function layersMission(args) {
|
|
|
11251
11259
|
...LAYERS.map((l) => ` ${l.padEnd(12)} ${String(byLayer[l]).padStart(3)}${byLayer[l] ? ' ' + '█'.repeat(Math.min(byLayer[l], 40)) : ''}`),
|
|
11252
11260
|
...(untagged ? [` untagged ${String(untagged).padStart(3)} (pre-layer receipts or missing tag)`] : []),
|
|
11253
11261
|
` provenance: explicit ${bySource.explicit}, explicit-inline ${bySource['explicit-inline']}, fallback ${bySource.fallback}`,
|
|
11254
|
-
...(skewed ? [` rebalance: ${Math.round((byLayer[dominant] / tagged) * 100)}% of tagged ticks are "${dominant}"
|
|
11262
|
+
...(skewed ? [` rebalance: ${Math.round((byLayer[dominant] / tagged) * 100)}% of tagged ticks are "${dominant}", the proof standard wants the other layers moving too`] : []),
|
|
11255
11263
|
];
|
|
11256
11264
|
printJsonOrText({ ok: true, since: sinceRaw || null, total, tagged, untagged, by_layer: byLayer, by_source: bySource, dominant: tagged ? dominant : null, skewed }, lines, asJson);
|
|
11257
11265
|
}
|
|
@@ -11340,7 +11348,7 @@ function findMissionAcrossWorktrees(ref, root = process.cwd()) {
|
|
|
11340
11348
|
return null;
|
|
11341
11349
|
}
|
|
11342
11350
|
|
|
11343
|
-
// atris mission ping <id> "<message>"
|
|
11351
|
+
// atris mission ping <id> "<message>", leave a note the mission's next tick
|
|
11344
11352
|
// reads (and consumes) as operator direction. This is how you talk to an
|
|
11345
11353
|
// always-on member mid-run without stopping it.
|
|
11346
11354
|
function inspectMission(args) {
|
|
@@ -11419,7 +11427,7 @@ function pingMission(args, opts = {}) {
|
|
|
11419
11427
|
if (asJson) {
|
|
11420
11428
|
console.log(JSON.stringify({ ok: true, action: 'mission_ping', mission_id: saved.id, pending_pings: pending, ping }));
|
|
11421
11429
|
} else {
|
|
11422
|
-
console.log(`pinged ${saved.id}
|
|
11430
|
+
console.log(`pinged ${saved.id}, the next tick reads it (${pending} unread).`);
|
|
11423
11431
|
}
|
|
11424
11432
|
}
|
|
11425
11433
|
return saved;
|
|
@@ -11569,7 +11577,7 @@ function missionCommand(args) {
|
|
|
11569
11577
|
if (first && !first.startsWith('-')) {
|
|
11570
11578
|
// Shortcut forms: a mission id, or an objective (quoted or spread
|
|
11571
11579
|
// across argv: `atris mission fix the issue`). Two shapes are
|
|
11572
|
-
// mistyped verbs, not objectives
|
|
11580
|
+
// mistyped verbs, not objectives, a later arg naming a mission id
|
|
11573
11581
|
// (`mission say <id> ...`), and a single bare word with no objective
|
|
11574
11582
|
// after it. Creating a mission from those silently is worse than erroring.
|
|
11575
11583
|
const positionals = args.filter((value) => !String(value).startsWith('-'));
|
package/commands/one-lap.js
CHANGED
|
@@ -664,7 +664,7 @@ async function runOneLap(ask, options = {}) {
|
|
|
664
664
|
ownCli: runCli,
|
|
665
665
|
log: (line) => {
|
|
666
666
|
const clean = String(line || '')
|
|
667
|
-
.replace(/[
|
|
667
|
+
.replace(/[\u2014\u2013]/g, '-')
|
|
668
668
|
.replace(/[✓✔]/g, 'passed')
|
|
669
669
|
.replace(/[✗✖]/g, 'failed')
|
|
670
670
|
.replace(/[⏸·→]/g, '-')
|