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/autopilot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Atris Autopilot
|
|
2
|
+
* Atris Autopilot, Suggest, justify, execute. One task at a time.
|
|
3
3
|
*
|
|
4
4
|
* Scans the workspace for signals (stale pages, broken refs, abandoned tasks,
|
|
5
5
|
* inbox items, backlog) and suggests the most important thing to do next.
|
|
@@ -117,7 +117,7 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
117
117
|
const atrisDir = path.join(cwd, 'atris');
|
|
118
118
|
const suggestions = [];
|
|
119
119
|
|
|
120
|
-
// --- Endgame tasks (highest priority
|
|
120
|
+
// --- Endgame tasks (highest priority, pursue the current horizon to completion) ---
|
|
121
121
|
const todoPath = path.join(atrisDir, 'TODO.md');
|
|
122
122
|
const todo = parseTodo(todoPath);
|
|
123
123
|
|
|
@@ -172,7 +172,7 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
172
172
|
if (skipped.has(key)) continue;
|
|
173
173
|
suggestions.push({
|
|
174
174
|
task: `Finish or remove stale task: ${st.title}`,
|
|
175
|
-
why: `Claimed ${st.daysSinceClaim} days ago and never completed. Either finish it or delete it
|
|
175
|
+
why: `Claimed ${st.daysSinceClaim} days ago and never completed. Either finish it or delete it, stale tasks add noise.`,
|
|
176
176
|
kind: 'cleanup',
|
|
177
177
|
priority: 3,
|
|
178
178
|
skipKey: key
|
|
@@ -187,7 +187,7 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
187
187
|
const sample = unhealable.slice(0, 3).map(r => `${r.file}:${r.line}`).join(', ');
|
|
188
188
|
suggestions.push({
|
|
189
189
|
task: `Fix ${unhealable.length} broken reference${unhealable.length > 1 ? 's' : ''} in MAP.md`,
|
|
190
|
-
why: `These file:line references point to code that moved or was deleted: ${sample}. MAP.md is the navigation
|
|
190
|
+
why: `These file:line references point to code that moved or was deleted: ${sample}. MAP.md is the navigation, it needs to be accurate.`,
|
|
191
191
|
kind: 'docs',
|
|
192
192
|
priority: 4
|
|
193
193
|
});
|
|
@@ -277,7 +277,7 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
277
277
|
const key = `feature:${dir.name}`;
|
|
278
278
|
if (!skipped.has(key)) {
|
|
279
279
|
suggestions.push({
|
|
280
|
-
task: `Complete feature spec for "${dir.name}"
|
|
280
|
+
task: `Complete feature spec for "${dir.name}", missing ${missing.join(' and ')}`,
|
|
281
281
|
why: `idea.md exists but the feature is incomplete. Navigator needs to create ${missing.join(' and ')} so executor can build it.`,
|
|
282
282
|
kind: 'feature',
|
|
283
283
|
priority: 6.5
|
|
@@ -297,7 +297,7 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
297
297
|
const daysSinceMapUpdate = (Date.now() - mapStat.mtime.getTime()) / (1000 * 60 * 60 * 24);
|
|
298
298
|
if (daysSinceMapUpdate > 7) {
|
|
299
299
|
suggestions.push({
|
|
300
|
-
task: 'Review and refresh MAP.md
|
|
300
|
+
task: 'Review and refresh MAP.md, it hasn\'t been updated in over a week',
|
|
301
301
|
why: `Last modified ${Math.floor(daysSinceMapUpdate)} days ago. Code may have drifted from the map. A quick review keeps navigation accurate.`,
|
|
302
302
|
kind: 'review',
|
|
303
303
|
priority: 7
|
|
@@ -385,8 +385,8 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
385
385
|
const { logFile } = getLogPath();
|
|
386
386
|
const now = new Date();
|
|
387
387
|
const hhmm = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
|
388
|
-
const lines = staleSkipped.map(s => `- ${s.task} (${s.status})${s.reasoning ?
|
|
389
|
-
const note = `\n### Staleness skip
|
|
388
|
+
const lines = staleSkipped.map(s => `- ${s.task} (${s.status})${s.reasoning ? `, ${s.reasoning}` : ''}`);
|
|
389
|
+
const note = `\n### Staleness skip, ${hhmm}\n${lines.join('\n')}\n`;
|
|
390
390
|
if (fs.existsSync(logFile)) {
|
|
391
391
|
const content = fs.readFileSync(logFile, 'utf8');
|
|
392
392
|
const notesIdx = content.indexOf('## Notes');
|
|
@@ -422,7 +422,7 @@ function askApproval() {
|
|
|
422
422
|
|
|
423
423
|
/**
|
|
424
424
|
* Ask the human whether an unverified task is still relevant.
|
|
425
|
-
* Interactive mode only
|
|
425
|
+
* Interactive mode only, in auto mode, caller skips silently.
|
|
426
426
|
* Returns { fresh: boolean }.
|
|
427
427
|
*/
|
|
428
428
|
function askHuman(taskTitle) {
|
|
@@ -438,7 +438,7 @@ function askHuman(taskTitle) {
|
|
|
438
438
|
|
|
439
439
|
/**
|
|
440
440
|
* Type-check a child_process error as a real wall-clock timeout. Node's
|
|
441
|
-
* execSync attaches `code: 'ETIMEDOUT'` (plus `signal`) on timeout
|
|
441
|
+
* execSync attaches `code: 'ETIMEDOUT'` (plus `signal`) on timeout, it does
|
|
442
442
|
* NOT set `killed`, so a `killed`-only guard is dead code on the exact error
|
|
443
443
|
* it was written for (lesson: etimedout-error-shape, 2026-06-10). A bare
|
|
444
444
|
* `signal` without ETIMEDOUT is NOT a timeout: it's an OOM SIGKILL or an
|
|
@@ -449,7 +449,7 @@ function isPhaseTimeoutError(err) {
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
/**
|
|
452
|
-
* Any abnormal child death
|
|
452
|
+
* Any abnormal child death, timeout or signal kill. The group sweep in
|
|
453
453
|
* execPhaseCommandSync uses this wider net (orphans need sweeping either
|
|
454
454
|
* way); the thrown message uses the narrow predicate to name the cause.
|
|
455
455
|
*/
|
|
@@ -459,12 +459,12 @@ function isPhaseKillError(err) {
|
|
|
459
459
|
|
|
460
460
|
/**
|
|
461
461
|
* execSync with the phase-timeout orphan fix. Node's sync-exec timeout signals
|
|
462
|
-
* only the direct child pid
|
|
462
|
+
* only the direct child pid, the `/bin/sh -c` wrapper, so the `claude` it
|
|
463
463
|
* spawned kept committing 160–296s past the 600s wall (lesson:
|
|
464
464
|
* etimedout-error-shape, 2026-06-10). `detached: true` makes the wrapper a
|
|
465
465
|
* process-group leader; on timeout we sweep the whole group via
|
|
466
466
|
* `process.kill(-pid, 'SIGKILL')`. ESRCH on the sweep means the group already
|
|
467
|
-
* died
|
|
467
|
+
* died, fine. The original error is rethrown untouched so every call site
|
|
468
468
|
* keeps its existing catch contract (err.stdout passthrough included).
|
|
469
469
|
*/
|
|
470
470
|
function execPhaseCommandSync(cmd, opts = {}) {
|
|
@@ -511,10 +511,10 @@ function executePhaseDetailed(phase, context, options = {}) {
|
|
|
511
511
|
} catch (err) {
|
|
512
512
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
513
513
|
if (isPhaseTimeoutError(err)) {
|
|
514
|
-
throw new Error(`${phase} phase timed out after ${timeout / 1000}s (configured runner hit the wall; any work it committed survives
|
|
514
|
+
throw new Error(`${phase} phase timed out after ${timeout / 1000}s (configured runner hit the wall; any work it committed survives, reconcile from pre-tick HEADs)`);
|
|
515
515
|
}
|
|
516
516
|
if (isPhaseKillError(err)) {
|
|
517
|
-
throw new Error(`${phase} phase killed by ${err.signal || 'a signal'} before the ${timeout / 1000}s wall
|
|
517
|
+
throw new Error(`${phase} phase killed by ${err.signal || 'a signal'} before the ${timeout / 1000}s wall, not a timeout; check memory pressure or an external supervisor`);
|
|
518
518
|
}
|
|
519
519
|
if (err.stdout) {
|
|
520
520
|
return { prompt, output: err.stdout };
|
|
@@ -638,10 +638,10 @@ function getContextFiles(phase, options = {}) {
|
|
|
638
638
|
// uncommitted work. Sibling-repo edits ride per-tick worktrees (the same
|
|
639
639
|
// ../repo siblings snapshotRepoHeads tracks); destructive git on the shared
|
|
640
640
|
// checkout is forbidden (COORDINATION.md Rule 4). Interpolated into the
|
|
641
|
-
// default and self-heal do prompts
|
|
641
|
+
// default and self-heal do prompts, never the benchmark prompt (it never
|
|
642
642
|
// commits).
|
|
643
|
-
const SHARED_CHECKOUT_GIT_CONTRACT = `- Shared-checkout git safety (COORDINATION.md Rule 4): edits to any repo OTHER than this tick's cwd (../atrisos-backend-style sibling repos) go through a per-tick worktree
|
|
644
|
-
- On a shared checkout, \`git reset\`, \`git checkout --\`, \`git clean\`, and stashing other agents' work are FORBIDDEN
|
|
643
|
+
const SHARED_CHECKOUT_GIT_CONTRACT = `- Shared-checkout git safety (COORDINATION.md Rule 4): edits to any repo OTHER than this tick's cwd (../atrisos-backend-style sibling repos) go through a per-tick worktree, start with \`atris worktree start --member <member> --task "<task>"\`, land with \`atris worktree ship --message "<msg>" --verify "<cmd>"\`. Never edit a sibling repo's shared checkout directly.
|
|
644
|
+
- On a shared checkout, \`git reset\`, \`git checkout --\`, \`git clean\`, and stashing other agents' work are FORBIDDEN, concurrent ticks' uncommitted work lives there.`;
|
|
645
645
|
|
|
646
646
|
/**
|
|
647
647
|
* Target files of experiment-queue entries that have not run yet. The gated
|
|
@@ -796,7 +796,7 @@ When done, reply: done.`;
|
|
|
796
796
|
Task: ${task}
|
|
797
797
|
|
|
798
798
|
Read today's journal completions and the git log from the past few days.
|
|
799
|
-
Extract patterns worth remembering
|
|
799
|
+
Extract patterns worth remembering, things that surprised you, approaches that worked,
|
|
800
800
|
mistakes that were caught. Append to atris/lessons.md. One line per lesson. Be specific.
|
|
801
801
|
|
|
802
802
|
When done, reply: done.`;
|
|
@@ -809,7 +809,7 @@ When done, reply: done.`;
|
|
|
809
809
|
Self-heal task: ${task}
|
|
810
810
|
|
|
811
811
|
This is an unresolved \`fail\` lesson from atris/lessons.md. grep confirms the bug pattern
|
|
812
|
-
is still present in-repo
|
|
812
|
+
is still present in-repo, the fix has NOT been shipped yet.
|
|
813
813
|
|
|
814
814
|
Plan the smallest fix:
|
|
815
815
|
1. Parse the lesson for file:line references and the described bug pattern.
|
|
@@ -818,7 +818,7 @@ Plan the smallest fix:
|
|
|
818
818
|
- **Exit:** the specific behavior that proves the fix
|
|
819
819
|
- **Verify:** a command that fails now and will pass after the fix${lessonSlug ? ` (include "${lessonSlug}" in the task title so the lesson auto-resolves)` : ''}
|
|
820
820
|
- **Rollback:** how to revert if the fix misses
|
|
821
|
-
4. Do NOT fix it in this phase
|
|
821
|
+
4. Do NOT fix it in this phase, planner only. The executor will do the work.
|
|
822
822
|
|
|
823
823
|
When done, reply: done.`;
|
|
824
824
|
}
|
|
@@ -827,7 +827,7 @@ When done, reply: done.`;
|
|
|
827
827
|
|
|
828
828
|
Task: ${task}
|
|
829
829
|
|
|
830
|
-
Understand the scope
|
|
830
|
+
Understand the scope, what files need to change? Break into sub-tasks if needed.
|
|
831
831
|
Add tasks to atris/TODO.md under ## Backlog.
|
|
832
832
|
|
|
833
833
|
When done, reply: done.`;
|
|
@@ -867,7 +867,7 @@ Rules:
|
|
|
867
867
|
- You CAN read and write code. You CANNOT plan or create new tasks.
|
|
868
868
|
- Execute ONE step at a time. Verify each step before moving on.
|
|
869
869
|
- Check MAP.md for file locations before grepping.
|
|
870
|
-
- Stay in scope. Only fix the bug described in the lesson
|
|
870
|
+
- Stay in scope. Only fix the bug described in the lesson, no side quests.
|
|
871
871
|
${SHARED_CHECKOUT_GIT_CONTRACT}
|
|
872
872
|
|
|
873
873
|
Read these files first:
|
|
@@ -887,7 +887,7 @@ When done, reply: done.`;
|
|
|
887
887
|
|
|
888
888
|
const pendingTargets = pendingExperimentTargets();
|
|
889
889
|
const experimentGuard = pendingTargets.length
|
|
890
|
-
? `\n- Do NOT edit these files this tick
|
|
890
|
+
? `\n- Do NOT edit these files this tick, the gated experiment loop owns pending patches to them: ${pendingTargets.join(', ')}. If the task requires changing them, stop and flag instead.`
|
|
891
891
|
: '';
|
|
892
892
|
return `You are the executor. Read your MEMBER.md spec first if available.
|
|
893
893
|
|
|
@@ -936,7 +936,7 @@ Task: ${task}
|
|
|
936
936
|
2. Name any tests or checks you ran and whether they passed.
|
|
937
937
|
3. Call out bugs, edge cases, or drift.
|
|
938
938
|
4. Reply \`done\` if this run passes the review bar.
|
|
939
|
-
5. Reply \`failed
|
|
939
|
+
5. Reply \`failed, [reason]\` if it does not.`;
|
|
940
940
|
}
|
|
941
941
|
|
|
942
942
|
return `You are the validator. Read your MEMBER.md spec first if available.
|
|
@@ -946,8 +946,8 @@ Rules:
|
|
|
946
946
|
- Ultrathink: spec match, scope check, edge cases, integration.
|
|
947
947
|
- Run tests if they exist. Check MAP.md is still accurate.
|
|
948
948
|
- If you halted, were surprised, or learned a non-obvious lesson, append ONE line to atris/lessons.md in this exact format:
|
|
949
|
-
- **[YYYY-MM-DD] short-slug
|
|
950
|
-
Skip if the tick taught nothing non-obvious. Lessons compound
|
|
949
|
+
- **[YYYY-MM-DD] short-slug**, pass|fail, One sentence on what surprised you or what to remember.
|
|
950
|
+
Skip if the tick taught nothing non-obvious. Lessons compound, future /endgame runs read this file before picking horizons.
|
|
951
951
|
|
|
952
952
|
Read these files first:
|
|
953
953
|
${readFiles}
|
|
@@ -965,7 +965,7 @@ Task: ${task}
|
|
|
965
965
|
6. If something is wrong, fix it before signing off.
|
|
966
966
|
|
|
967
967
|
When done, reply: done.
|
|
968
|
-
If broken beyond quick fix, reply: failed
|
|
968
|
+
If broken beyond quick fix, reply: failed, [reason].`;
|
|
969
969
|
}
|
|
970
970
|
|
|
971
971
|
return '';
|
|
@@ -975,7 +975,7 @@ If broken beyond quick fix, reply: failed — [reason].`;
|
|
|
975
975
|
* Build a clean kebab-case lesson slug from free text. Strips non-alphanumerics
|
|
976
976
|
* (em-dashes were leaking into slugs verbatim) and truncates at a word boundary
|
|
977
977
|
* instead of mid-word (e.g. the old `.slice(0, 40)` produced
|
|
978
|
-
* `verify-fail-per-member-model-selection
|
|
978
|
+
* `verify-fail-per-member-model-selection-the-member-`).
|
|
979
979
|
*/
|
|
980
980
|
function lessonSlug(text, maxLen = 40) {
|
|
981
981
|
const base = String(text || 'unknown')
|
|
@@ -986,7 +986,7 @@ function lessonSlug(text, maxLen = 40) {
|
|
|
986
986
|
if (base.length <= maxLen) return base;
|
|
987
987
|
const cut = base.slice(0, maxLen);
|
|
988
988
|
const lastDash = cut.lastIndexOf('-');
|
|
989
|
-
// base[maxLen] continues a word
|
|
989
|
+
// base[maxLen] continues a word, back up to the last full word.
|
|
990
990
|
const atBoundary = base[maxLen] === '-';
|
|
991
991
|
const trimmed = atBoundary ? cut : (lastDash > 0 ? cut.slice(0, lastDash) : cut);
|
|
992
992
|
return trimmed.replace(/-+$/g, '') || 'unknown';
|
|
@@ -994,22 +994,22 @@ function lessonSlug(text, maxLen = 40) {
|
|
|
994
994
|
|
|
995
995
|
/**
|
|
996
996
|
* Write a lesson to atris/lessons.md
|
|
997
|
-
* Appends a line in format: - **[YYYY-MM-DD] slug**
|
|
997
|
+
* Appends a line in format: - **[YYYY-MM-DD] slug** - pass/fail - explanation
|
|
998
998
|
*/
|
|
999
999
|
function writeLesson(cwd, slug, status, explanation) {
|
|
1000
1000
|
const lessonsPath = path.join(cwd, 'atris', 'lessons.md');
|
|
1001
1001
|
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
1002
|
-
const lessonLine = `- **[${today}] ${slug}**
|
|
1002
|
+
const lessonLine = `- **[${today}] ${slug}** - ${status} - ${explanation}`;
|
|
1003
1003
|
|
|
1004
1004
|
if (!fs.existsSync(lessonsPath)) {
|
|
1005
|
-
fs.writeFileSync(lessonsPath, `# lessons.md
|
|
1005
|
+
fs.writeFileSync(lessonsPath, `# lessons.md, What We Learned\n\n> Append-only. One line per lesson.\n\n---\n\n${lessonLine}\n`);
|
|
1006
1006
|
return;
|
|
1007
1007
|
}
|
|
1008
1008
|
|
|
1009
1009
|
let content = fs.readFileSync(lessonsPath, 'utf8');
|
|
1010
1010
|
// Same-day dedup: if an identical line already exists, skip the write. A
|
|
1011
1011
|
// cron firing every 13min produced 5 identical no-verify-field lessons in
|
|
1012
|
-
// one day (2026-05-08) before the picker-side fix landed
|
|
1012
|
+
// one day (2026-05-08) before the picker-side fix landed, pure noise. The
|
|
1013
1013
|
// append-only contract still holds across days because today's date is in
|
|
1014
1014
|
// the line.
|
|
1015
1015
|
if (content.includes(lessonLine)) return;
|
|
@@ -1094,7 +1094,7 @@ function regressionCheck(cwd) {
|
|
|
1094
1094
|
* Get the verify command for a task from TODO.md
|
|
1095
1095
|
* Reads TODO.md, finds the task by title across active/completed sections,
|
|
1096
1096
|
* and extracts the verify field.
|
|
1097
|
-
* Returns { cmd, explicit }
|
|
1097
|
+
* Returns { cmd, explicit }, explicit is true only if the task has an explicit Verify field.
|
|
1098
1098
|
*/
|
|
1099
1099
|
function getVerifyCommand(cwd, taskTitle) {
|
|
1100
1100
|
const todoPath = path.join(cwd, 'atris', 'TODO.md');
|
|
@@ -1137,7 +1137,7 @@ function shouldAdoptPlannedVerify(kind) {
|
|
|
1137
1137
|
// Task-plane status vocabulary lint. `atris task list/queue/current --status <s>`
|
|
1138
1138
|
// only matches raw stored statuses (commands/task.js); `ready` is a TRANSITION
|
|
1139
1139
|
// (`atris task ready` moves a task to review), so `--status ready` always
|
|
1140
|
-
// returns "(no tasks)"
|
|
1140
|
+
// returns "(no tasks)", a verify built on it is an unreachable gate; the
|
|
1141
1141
|
// matching listable form is --status review (lessons.md
|
|
1142
1142
|
// verify-status-vocabulary, 3rd occurrence 2026-06-10).
|
|
1143
1143
|
const LISTABLE_TASK_STATUSES = ['open', 'claimed', 'review', 'done', 'failed'];
|
|
@@ -1160,7 +1160,7 @@ function lintVerifyTaskStatusVocabulary(text) {
|
|
|
1160
1160
|
: `use one of --status ${vocabulary}`;
|
|
1161
1161
|
return {
|
|
1162
1162
|
ok: false,
|
|
1163
|
-
reason: `Verify uses unlistable task status "--status ${status}"
|
|
1163
|
+
reason: `Verify uses unlistable task status "--status ${status}", the listable vocabulary is ${vocabulary}; ${suggestion}`,
|
|
1164
1164
|
};
|
|
1165
1165
|
}
|
|
1166
1166
|
return null;
|
|
@@ -1197,7 +1197,7 @@ function haltInvalidVerify(cwd, context, verifyCmd, reason, startedAt, phaseResu
|
|
|
1197
1197
|
/**
|
|
1198
1198
|
* Infer a default verify command from the repo shape. Order matters:
|
|
1199
1199
|
* package.json with a non-stub test script → `npm test`; then pytest/python;
|
|
1200
|
-
* then rust/go; otherwise null (no default
|
|
1200
|
+
* then rust/go; otherwise null (no default, skip verify).
|
|
1201
1201
|
*/
|
|
1202
1202
|
function detectDefaultVerify(cwd) {
|
|
1203
1203
|
const pkg = path.join(cwd, 'package.json');
|
|
@@ -1246,14 +1246,14 @@ function verifyJudgeIntegrity() {
|
|
|
1246
1246
|
}
|
|
1247
1247
|
|
|
1248
1248
|
/**
|
|
1249
|
-
* Build the validator's plan-review prompt. Fresh context
|
|
1249
|
+
* Build the validator's plan-review prompt. Fresh context, the validator
|
|
1250
1250
|
* reads the plan output and the contract fields as if it has never seen them.
|
|
1251
1251
|
*/
|
|
1252
1252
|
function buildPlanReviewPrompt(context, planOutput) {
|
|
1253
1253
|
const files = Array.isArray(context.files) && context.files.length
|
|
1254
1254
|
? context.files.join(', ')
|
|
1255
1255
|
: 'none declared in context';
|
|
1256
|
-
return `You are the validator in plan-review mode. You have NOT seen the planning context
|
|
1256
|
+
return `You are the validator in plan-review mode. You have NOT seen the planning context, read everything fresh.
|
|
1257
1257
|
|
|
1258
1258
|
Task: "${context.task}"
|
|
1259
1259
|
Kind: ${context.kind || 'unknown'}
|
|
@@ -1265,9 +1265,9 @@ ${planOutput || '(no plan output captured)'}
|
|
|
1265
1265
|
---
|
|
1266
1266
|
|
|
1267
1267
|
Read from disk:
|
|
1268
|
-
- atris/atris.md (the workspace protocol
|
|
1268
|
+
- atris/atris.md (the workspace protocol, operating rules and task shape)
|
|
1269
1269
|
- atris/TODO.md (find this task; inspect Files, Exit, Verify, After, Rollback)
|
|
1270
|
-
- atris/lessons.md (recent failures
|
|
1270
|
+
- atris/lessons.md (recent failures, last 20 lines)
|
|
1271
1271
|
|
|
1272
1272
|
Decide if the plan is safe to execute. Check:
|
|
1273
1273
|
1. Verify points at a falsifiable raw shell command or rubric (not \`true\`, \`echo ok\`, Markdown backticks, or English like "returns 1" / "shows today's date").
|
|
@@ -1305,7 +1305,7 @@ function parseVerdict(output) {
|
|
|
1305
1305
|
const text = String(output || '');
|
|
1306
1306
|
const rawLines = text.split('\n');
|
|
1307
1307
|
const lines = rawLines.map((l) => l.trim()).filter(Boolean);
|
|
1308
|
-
// Scan from the end backwards
|
|
1308
|
+
// Scan from the end backwards, the verdict is supposed to be LAST.
|
|
1309
1309
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1310
1310
|
const line = lines[i];
|
|
1311
1311
|
if (/^SIGNOFF\s*:/i.test(line)) {
|
|
@@ -1398,7 +1398,7 @@ function defaultCodexExecutor(prompt, { cwd, timeout = 180000 } = {}) {
|
|
|
1398
1398
|
maxBuffer: 10 * 1024 * 1024,
|
|
1399
1399
|
detached: true,
|
|
1400
1400
|
});
|
|
1401
|
-
// No sh wrapper here, but codex spawns its own children
|
|
1401
|
+
// No sh wrapper here, but codex spawns its own children, sweep the group
|
|
1402
1402
|
// on timeout so they cannot outlive the wall (same orphan class as the
|
|
1403
1403
|
// claude sites; ESRCH means the tree is already dead).
|
|
1404
1404
|
if (proc.pid && ((proc.error && proc.error.code === 'ETIMEDOUT') || proc.signal)) {
|
|
@@ -1508,7 +1508,7 @@ function runPlanReview({ cwd, context, planOutput, options = {} }) {
|
|
|
1508
1508
|
|
|
1509
1509
|
/**
|
|
1510
1510
|
* Append a plan-review rejection to today's journal under ## Notes.
|
|
1511
|
-
* Intentionally does NOT write to lessons.md
|
|
1511
|
+
* Intentionally does NOT write to lessons.md, rejections only become lessons
|
|
1512
1512
|
* if a human spots a reusable failure pattern.
|
|
1513
1513
|
*/
|
|
1514
1514
|
function appendPlanRejection(cwd, context, review) {
|
|
@@ -1532,7 +1532,7 @@ function appendPlanRejection(cwd, context, review) {
|
|
|
1532
1532
|
(review.proposed.rollback ? `- Rollback: ${review.proposed.rollback}\n` : '')
|
|
1533
1533
|
: '';
|
|
1534
1534
|
const block =
|
|
1535
|
-
`\n### Plan rejected
|
|
1535
|
+
`\n### Plan rejected, ${now}\n\n` +
|
|
1536
1536
|
`**Task:** ${context.task}\n` +
|
|
1537
1537
|
`**Signers:** ${signers}\n` +
|
|
1538
1538
|
`**Reason:** ${review.reason}\n` +
|
|
@@ -1606,7 +1606,7 @@ function gitHeadAt(dir) {
|
|
|
1606
1606
|
|
|
1607
1607
|
/**
|
|
1608
1608
|
* Snapshot HEAD of the workspace repo plus any sibling repos named in the
|
|
1609
|
-
* task text
|
|
1609
|
+
* task text, both explicit `../atris-cli`-style refs (the journal convention)
|
|
1610
1610
|
* and bare sibling-directory names like `atris-cli` that resolve to a git
|
|
1611
1611
|
* repo next to cwd. Returns [{ label, dir, head }].
|
|
1612
1612
|
*/
|
|
@@ -1645,7 +1645,7 @@ function diffAdvancedRepoHeads(snapshot) {
|
|
|
1645
1645
|
|
|
1646
1646
|
/**
|
|
1647
1647
|
* The T31-typed do-phase timeout message thrown by executePhaseDetailed.
|
|
1648
|
-
* Plan/review timeouts stay human halts
|
|
1648
|
+
* Plan/review timeouts stay human halts, only the do phase commits work
|
|
1649
1649
|
* worth reconciling.
|
|
1650
1650
|
*/
|
|
1651
1651
|
function isDoPhaseTimeoutMessage(message) {
|
|
@@ -1728,7 +1728,7 @@ function appendTimeoutReconciliation(cwd, { task, advanced }) {
|
|
|
1728
1728
|
.map((r) => `- ${r.label}: ${String(r.before).slice(0, 7)} → ${String(r.after).slice(0, 7)}`)
|
|
1729
1729
|
.join('\n');
|
|
1730
1730
|
const block =
|
|
1731
|
-
`\n### Timeout reconciliation
|
|
1731
|
+
`\n### Timeout reconciliation, ${now}, work-landed-receipt-died\n\n` +
|
|
1732
1732
|
`**Task:** ${task}\n` +
|
|
1733
1733
|
`**What happened:** the do-phase wall killed the reporter, but commits landed:\n` +
|
|
1734
1734
|
`${repoLines}\n` +
|
|
@@ -1739,9 +1739,9 @@ function appendTimeoutReconciliation(cwd, { task, advanced }) {
|
|
|
1739
1739
|
function appendCheckAndAdvance(cwd, task, receiptLine) {
|
|
1740
1740
|
const now = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
1741
1741
|
const block =
|
|
1742
|
-
`\n### Check-and-advance
|
|
1742
|
+
`\n### Check-and-advance, ${now}, advanced-already-done\n\n` +
|
|
1743
1743
|
`**Task:** ${task}\n` +
|
|
1744
|
-
`**What happened:** verify passed before work started AND today's journal already carries a completion receipt
|
|
1744
|
+
`**What happened:** verify passed before work started AND today's journal already carries a completion receipt, the work shipped on a prior tick whose reporter died before bookkeeping. Bullet marked, picker advanced.\n` +
|
|
1745
1745
|
`**Receipt:** ${receiptLine}\n`;
|
|
1746
1746
|
return appendUnderNotes(cwd, block);
|
|
1747
1747
|
}
|
|
@@ -1781,7 +1781,7 @@ function reconcileTimedOutTick(cwd, snapshot, taskTitle) {
|
|
|
1781
1781
|
function runTaskOnce(context, options = {}) {
|
|
1782
1782
|
const { verbose = false, cwd = process.cwd() } = options;
|
|
1783
1783
|
|
|
1784
|
-
// Judge integrity check
|
|
1784
|
+
// Judge integrity check, halt if computeTickReward was tampered with
|
|
1785
1785
|
const integrity = verifyJudgeIntegrity();
|
|
1786
1786
|
if (!integrity.ok) {
|
|
1787
1787
|
writeLesson(cwd, 'judge-corruption', 'fail',
|
|
@@ -1812,7 +1812,7 @@ function runTaskOnce(context, options = {}) {
|
|
|
1812
1812
|
// Reactive signals (inbox, staleness, imagined) use npm test as default.
|
|
1813
1813
|
if (!verifyResult.explicit && context.kind === 'endgame') {
|
|
1814
1814
|
writeLesson(cwd, 'no-verify-field', 'fail',
|
|
1815
|
-
`Task "${context.task}" has no explicit **Verify:** field in TODO.md. Tick halted
|
|
1815
|
+
`Task "${context.task}" has no explicit **Verify:** field in TODO.md. Tick halted, every endgame task must declare how to verify it.`);
|
|
1816
1816
|
return {
|
|
1817
1817
|
outcome: 'halted',
|
|
1818
1818
|
reason: 'no-verify-field',
|
|
@@ -1825,7 +1825,7 @@ function runTaskOnce(context, options = {}) {
|
|
|
1825
1825
|
|
|
1826
1826
|
// Falsifiability gate (endgame + explicit Verify only).
|
|
1827
1827
|
// Run Verify BEFORE the work. If it passes, the rubric is trivial or the
|
|
1828
|
-
// task is already done
|
|
1828
|
+
// task is already done, either way, halt. This is the keystone that makes
|
|
1829
1829
|
// Verify load-bearing. The cmd is captured here and reused post-execute so
|
|
1830
1830
|
// an agent cannot swap the rubric mid-tick.
|
|
1831
1831
|
//
|
|
@@ -1868,7 +1868,7 @@ function runTaskOnce(context, options = {}) {
|
|
|
1868
1868
|
verifyPass: false,
|
|
1869
1869
|
};
|
|
1870
1870
|
} catch {
|
|
1871
|
-
// Pre-verify failed
|
|
1871
|
+
// Pre-verify failed, good, the rubric is falsifiable. Proceed.
|
|
1872
1872
|
}
|
|
1873
1873
|
}
|
|
1874
1874
|
|
|
@@ -1883,7 +1883,7 @@ function runTaskOnce(context, options = {}) {
|
|
|
1883
1883
|
};
|
|
1884
1884
|
}
|
|
1885
1885
|
|
|
1886
|
-
// Phase: plan-review
|
|
1886
|
+
// Phase: plan-review, validator reads the plan fresh and signs off or rejects.
|
|
1887
1887
|
// Can be skipped via options.skipPlanReview (tests only). Codex is optional,
|
|
1888
1888
|
// opt-in via env var / tags. On REJECT, the tick halts and the rejection is
|
|
1889
1889
|
// journaled; lessons.md is NOT touched (only promoted lessons go there).
|
|
@@ -2074,7 +2074,7 @@ function computeTickReward(execution, tickOutcome, verifyCmd) {
|
|
|
2074
2074
|
* - idle: when true, block must contain literal "0 tasks in 0s"
|
|
2075
2075
|
* so getIdleTickCount still works.
|
|
2076
2076
|
* - reward: optional tick reward score (from computeTickReward)
|
|
2077
|
-
* Safe to call inside a try/catch
|
|
2077
|
+
* Safe to call inside a try/catch, a write failure must never crash a tick.
|
|
2078
2078
|
*/
|
|
2079
2079
|
function appendTickSummary(cwd, { time, outcome, horizon, nextStep, idle, reward } = {}) {
|
|
2080
2080
|
const now = new Date();
|
|
@@ -2257,7 +2257,7 @@ function printPlainBlock(text) {
|
|
|
2257
2257
|
function getTickStatus(cwd) {
|
|
2258
2258
|
const atrisDir = path.join(cwd, 'atris');
|
|
2259
2259
|
|
|
2260
|
-
let identity = '(no identity set
|
|
2260
|
+
let identity = '(no identity set, see atris/PERSONA.md)';
|
|
2261
2261
|
const personaPath = path.join(atrisDir, 'PERSONA.md');
|
|
2262
2262
|
if (fs.existsSync(personaPath)) {
|
|
2263
2263
|
const lines = fs.readFileSync(personaPath, 'utf8').split('\n');
|
|
@@ -2271,7 +2271,7 @@ function getTickStatus(cwd) {
|
|
|
2271
2271
|
}
|
|
2272
2272
|
|
|
2273
2273
|
const endgame = readEndgameState(cwd);
|
|
2274
|
-
const slug = endgame.slug === 'unset' ? '(no endgame active
|
|
2274
|
+
const slug = endgame.slug === 'unset' ? '(no endgame active, feed inbox or /endgame)' : endgame.slug;
|
|
2275
2275
|
const horizon = endgame.horizon;
|
|
2276
2276
|
const remaining = endgame.remaining;
|
|
2277
2277
|
const completedEndgame = endgame.completed;
|
|
@@ -2318,7 +2318,7 @@ function renderHumanSuggestion(suggestion, step, maxIterations) {
|
|
|
2318
2318
|
/**
|
|
2319
2319
|
* Print the visual ASCII tick status block. Shows identity (forward / flow),
|
|
2320
2320
|
* current endgame slug + horizon (backward / endgame), and progress through
|
|
2321
|
-
* endgame steps. Two halves of the same engine
|
|
2321
|
+
* endgame steps. Two halves of the same engine, flow and endgame, meeting
|
|
2322
2322
|
* at the next tick. Called at the start of each autopilot run.
|
|
2323
2323
|
*/
|
|
2324
2324
|
function printTickStatus(cwd, options = {}) {
|
|
@@ -2362,7 +2362,7 @@ function printTickStatus(cwd, options = {}) {
|
|
|
2362
2362
|
* Idle marker is the literal substring `0 tasks in 0s` (case-insensitive). Scans
|
|
2363
2363
|
* the Notes section bottom-up; the first non-marker, non-blank line breaks the
|
|
2364
2364
|
* streak. Returns 0 when the journal is missing or has no `## Notes` section.
|
|
2365
|
-
* Pure read-only
|
|
2365
|
+
* Pure read-only, no side effects, no callers yet.
|
|
2366
2366
|
*/
|
|
2367
2367
|
function getIdleTickCount(cwd) {
|
|
2368
2368
|
const now = new Date();
|
|
@@ -2397,7 +2397,7 @@ function getIdleTickCount(cwd) {
|
|
|
2397
2397
|
* - recentCommits: string[] from `git log --oneline -20` (empty on failure)
|
|
2398
2398
|
* - wikiHealth: string of `atris/wiki/STATUS.md` contents, or null if missing
|
|
2399
2399
|
* - recentLessons: string[] of last 10 non-empty lines from `atris/lessons.md`
|
|
2400
|
-
* Pure read-only
|
|
2400
|
+
* Pure read-only, try/catch each source, safe defaults on failure. No callers yet.
|
|
2401
2401
|
*/
|
|
2402
2402
|
function getRecentSignals(cwd) {
|
|
2403
2403
|
let recentCommits = [];
|
|
@@ -2557,7 +2557,7 @@ function scoreEndgameCandidates(cwd, candidates) {
|
|
|
2557
2557
|
}
|
|
2558
2558
|
|
|
2559
2559
|
/**
|
|
2560
|
-
* Proactive "surprise me" scanner
|
|
2560
|
+
* Proactive "surprise me" scanner, surfaces things the user didn't ask about.
|
|
2561
2561
|
* Returns an array of suggestion objects in the same shape as the reactive
|
|
2562
2562
|
* signals in suggestNextTask. Three orthogonal checks, none requiring
|
|
2563
2563
|
* cross-session state:
|
|
@@ -2583,7 +2583,7 @@ function scanAnomalies(cwd) {
|
|
|
2583
2583
|
const sample = untracked.slice(0, 3).map(t => `${t.file}:${t.line}`).join(', ');
|
|
2584
2584
|
const firstText = first.text.slice(0, 60);
|
|
2585
2585
|
results.push({
|
|
2586
|
-
task: `Track the ${untracked.length} orphan TODO${untracked.length > 1 ? 's' : ''} in source
|
|
2586
|
+
task: `Track the ${untracked.length} orphan TODO${untracked.length > 1 ? 's' : ''} in source, first: "${firstText}"`,
|
|
2587
2587
|
why: `Code has ${untracked.length} \`// TODO\`/\`// FIXME\` comment${untracked.length > 1 ? 's' : ''} never written to TODO.md. First: "${firstText}" (${sample}). Either convert to real tasks or delete if obsolete.`,
|
|
2588
2588
|
kind: 'orphan-todo',
|
|
2589
2589
|
priority: 6,
|
|
@@ -2619,7 +2619,7 @@ function scanAnomalies(cwd) {
|
|
|
2619
2619
|
const hotspot = findHotspot(cwd);
|
|
2620
2620
|
if (hotspot) {
|
|
2621
2621
|
results.push({
|
|
2622
|
-
task: `Pause and review ${hotspot.file}
|
|
2622
|
+
task: `Pause and review ${hotspot.file}, ${hotspot.commits} commits in the last 24h`,
|
|
2623
2623
|
why: `That file has churned more than any other file today. Could be genuine progress or a sign the change isn't sticking. Worth reading the diff before continuing.`,
|
|
2624
2624
|
kind: 'hotspot',
|
|
2625
2625
|
priority: 6.5,
|
|
@@ -2635,7 +2635,7 @@ function scanAnomalies(cwd) {
|
|
|
2635
2635
|
* Grep source code for TODO/FIXME comments. Skips test/, node_modules/,
|
|
2636
2636
|
* atris/, and .md files. Returns [{file, line, text}].
|
|
2637
2637
|
*
|
|
2638
|
-
* Uses a loose grep then filters to real comment prefixes in JS
|
|
2638
|
+
* Uses a loose grep then filters to real comment prefixes in JS, git grep's
|
|
2639
2639
|
* -E flag doesn't support `\s` on macOS, so we keep the pattern simple and
|
|
2640
2640
|
* refine post-hoc.
|
|
2641
2641
|
*/
|
|
@@ -2798,7 +2798,7 @@ function parseLessons(cwd) {
|
|
|
2798
2798
|
for (const rawLine of content.split('\n')) {
|
|
2799
2799
|
const line = rawLine;
|
|
2800
2800
|
if (!line.trim().startsWith('- **[')) continue;
|
|
2801
|
-
const m = line.match(/\*\*\[(\d{4}-\d{2}-\d{2})\]\s+([\w-]+)\*\*\s*[
|
|
2801
|
+
const m = line.match(/\*\*\[(\d{4}-\d{2}-\d{2})\]\s+([\w-]+)\*\*\s*[-\u2014]\s*(pass|fail)?\s*[-\u2014]?\s*(.*)$/);
|
|
2802
2802
|
if (!m) continue;
|
|
2803
2803
|
const [, date, id, verdict, rest] = m;
|
|
2804
2804
|
const resolvedTag = /\[resolved\]/.test(rest);
|
|
@@ -2911,7 +2911,7 @@ function legacyLessonFileRefs(lessonLine) {
|
|
|
2911
2911
|
}
|
|
2912
2912
|
|
|
2913
2913
|
/**
|
|
2914
|
-
* The pre-v3.8 resolver
|
|
2914
|
+
* The pre-v3.8 resolver, kept as an internal fallback for prose-only lessons
|
|
2915
2915
|
* that don't have detector metadata yet. Never auto-promotes a prose lesson to
|
|
2916
2916
|
* resolved in the typed system (callers can still use the `resolvedTag` field
|
|
2917
2917
|
* from parseLessons for hand-tagged entries).
|
|
@@ -2959,13 +2959,13 @@ function isLessonResolvedLegacy(lessonLine, cwd) {
|
|
|
2959
2959
|
*
|
|
2960
2960
|
* Self-healing seed: instead of imagining new horizons via LLM, use what the
|
|
2961
2961
|
* system already wrote down about itself. A `fail` lesson with `isLessonResolved
|
|
2962
|
-
* === false` means grep confirms the bug pattern is still present
|
|
2962
|
+
* === false` means grep confirms the bug pattern is still present, actionable.
|
|
2963
2963
|
*/
|
|
2964
2964
|
/**
|
|
2965
2965
|
* Returns true if a recent (within `windowDays`) `verify-not-falsifiable`
|
|
2966
2966
|
* lesson references this exact task title. The falsifiability gate halts the
|
|
2967
2967
|
* tick when the Verify clause already passes before work starts (task is
|
|
2968
|
-
* already shipped or rubric is trivial), but nothing in TODO.md changes
|
|
2968
|
+
* already shipped or rubric is trivial), but nothing in TODO.md changes,
|
|
2969
2969
|
* so the next tick re-picks the same task and burns another 90s+ halting in
|
|
2970
2970
|
* the same place. Reading the lesson log breaks the loop without requiring
|
|
2971
2971
|
* a TODO.md hand-edit (which is the structurally-broken file we route around
|
|
@@ -3009,7 +3009,7 @@ function pickUnresolvedFailLesson(cwd) {
|
|
|
3009
3009
|
if (lesson.id === 'no-verify-field') continue;
|
|
3010
3010
|
if (lesson.id === 'verify-failed' && lesson.legacy) continue;
|
|
3011
3011
|
if (lesson.resolvedTag) continue;
|
|
3012
|
-
// Typed lesson with explicit status wins
|
|
3012
|
+
// Typed lesson with explicit status wins, respect the sidecar.
|
|
3013
3013
|
// `resolved` = done. `observed` = process rule, not a fixable code state.
|
|
3014
3014
|
// `attempted` with attempts >= MAX_ATTEMPTS = needs human re-scoping, skip.
|
|
3015
3015
|
// Only `open` and `attempted` (under the cap) flow to self-heal execution.
|
|
@@ -3034,13 +3034,13 @@ function pickUnresolvedFailLesson(cwd) {
|
|
|
3034
3034
|
|
|
3035
3035
|
if (candidates.length === 0) return null;
|
|
3036
3036
|
|
|
3037
|
-
// Oldest first
|
|
3037
|
+
// Oldest first, longest-standing fails get priority
|
|
3038
3038
|
candidates.sort((a, b) => a.date.localeCompare(b.date));
|
|
3039
3039
|
return candidates[0];
|
|
3040
3040
|
}
|
|
3041
3041
|
|
|
3042
3042
|
function getLessonVerdict(lessonLine) {
|
|
3043
|
-
const match = lessonLine.match(/\*\*\[\d{4}-\d{2}-\d{2}\]\s+[\w-]+\*\*\s*[
|
|
3043
|
+
const match = lessonLine.match(/\*\*\[\d{4}-\d{2}-\d{2}\]\s+[\w-]+\*\*\s*[-\u2014]\s*(pass|fail)\b/i);
|
|
3044
3044
|
return match ? match[1].toLowerCase() : null;
|
|
3045
3045
|
}
|
|
3046
3046
|
|
|
@@ -3087,7 +3087,7 @@ Based on these signals, propose exactly 3 candidate next horizons for the loop t
|
|
|
3087
3087
|
- Distinct from the other two candidates.
|
|
3088
3088
|
- Not a restatement of a \`pass\` lesson; pass lessons are shipped constraints, not open work.
|
|
3089
3089
|
|
|
3090
|
-
Output STRICT JSON ONLY
|
|
3090
|
+
Output STRICT JSON ONLY, no prose, no markdown code fences, no commentary. The output must be a single JSON array with exactly 3 objects, each shaped:
|
|
3091
3091
|
|
|
3092
3092
|
[
|
|
3093
3093
|
{ "title": "one-line horizon title", "confidence": 0.0-1.0, "rationale": "one sentence why this is worth pursuing now" },
|
|
@@ -3118,7 +3118,7 @@ Reply with the JSON array and nothing else.`;
|
|
|
3118
3118
|
throw new Error(`horizon-proposal phase timed out after ${PHASE_TIMEOUT / 1000}s`);
|
|
3119
3119
|
}
|
|
3120
3120
|
if (isPhaseKillError(err)) {
|
|
3121
|
-
throw new Error(`horizon-proposal phase killed by ${err.signal || 'a signal'} before the ${PHASE_TIMEOUT / 1000}s wall
|
|
3121
|
+
throw new Error(`horizon-proposal phase killed by ${err.signal || 'a signal'} before the ${PHASE_TIMEOUT / 1000}s wall, not a timeout`);
|
|
3122
3122
|
}
|
|
3123
3123
|
throw err;
|
|
3124
3124
|
} finally {
|
|
@@ -3136,7 +3136,7 @@ Reply with the JSON array and nothing else.`;
|
|
|
3136
3136
|
try {
|
|
3137
3137
|
parsed = JSON.parse(jsonText);
|
|
3138
3138
|
} catch (err) {
|
|
3139
|
-
throw new Error(`proposeCandidateHorizons: JSON parse failed
|
|
3139
|
+
throw new Error(`proposeCandidateHorizons: JSON parse failed, ${err.message}`);
|
|
3140
3140
|
}
|
|
3141
3141
|
|
|
3142
3142
|
if (!Array.isArray(parsed)) {
|
|
@@ -3180,7 +3180,7 @@ Reply with the JSON array and nothing else.`;
|
|
|
3180
3180
|
droppedByLesson = true;
|
|
3181
3181
|
break;
|
|
3182
3182
|
}
|
|
3183
|
-
// Candidate matches this lesson
|
|
3183
|
+
// Candidate matches this lesson, check if the lesson is resolved
|
|
3184
3184
|
if (isLessonResolved(lessonLine, cwd)) {
|
|
3185
3185
|
// Tag lesson [resolved] in lessons.md
|
|
3186
3186
|
try {
|
|
@@ -3336,7 +3336,7 @@ async function autopilotAtris(description, options = {}) {
|
|
|
3336
3336
|
|
|
3337
3337
|
if (dryRun) {
|
|
3338
3338
|
if (verbose) {
|
|
3339
|
-
console.log(' (dry run
|
|
3339
|
+
console.log(' (dry run, would execute this)');
|
|
3340
3340
|
console.log('');
|
|
3341
3341
|
} else {
|
|
3342
3342
|
printPlainBlock([
|
|
@@ -3434,7 +3434,7 @@ async function autopilotAtris(description, options = {}) {
|
|
|
3434
3434
|
lastExecution = execution;
|
|
3435
3435
|
lastVerifyCmd = execution.verifyCmd;
|
|
3436
3436
|
|
|
3437
|
-
// Early halt
|
|
3437
|
+
// Early halt, judge corruption or no verify field
|
|
3438
3438
|
if (execution.outcome === 'halted') {
|
|
3439
3439
|
tickOutcome = 'halted';
|
|
3440
3440
|
tickOutcomeText = `I halted before running "${lastTaskTitle}": ${execution.reason}.`;
|
|
@@ -3452,18 +3452,18 @@ async function autopilotAtris(description, options = {}) {
|
|
|
3452
3452
|
break;
|
|
3453
3453
|
}
|
|
3454
3454
|
|
|
3455
|
-
// T33b: the falsifiability gate found a completion receipt
|
|
3455
|
+
// T33b: the falsifiability gate found a completion receipt, the work
|
|
3456
3456
|
// already shipped, the bullet is checked, move straight to the next pick.
|
|
3457
3457
|
if (execution.outcome === 'advanced-already-done') {
|
|
3458
3458
|
completed++;
|
|
3459
3459
|
tickOutcome = 'built';
|
|
3460
|
-
tickOutcomeText = `"${lastTaskTitle}" was already done
|
|
3460
|
+
tickOutcomeText = `"${lastTaskTitle}" was already done, verify passed pre-work and today's journal carries its completion receipt, so I checked the bullet and advanced.`;
|
|
3461
3461
|
tickNextStep = 'pick the next endgame task';
|
|
3462
3462
|
if (verbose) {
|
|
3463
3463
|
console.log(' already done (journal receipt found). bullet checked, advancing.');
|
|
3464
3464
|
} else {
|
|
3465
3465
|
printPlainBlock([
|
|
3466
|
-
'That task was already done
|
|
3466
|
+
'That task was already done, verify passed before work and a completion receipt exists in today\'s journal.',
|
|
3467
3467
|
'I checked the bullet and advanced.',
|
|
3468
3468
|
'',
|
|
3469
3469
|
'Next I will look for the next task.'
|
|
@@ -3581,7 +3581,7 @@ async function autopilotAtris(description, options = {}) {
|
|
|
3581
3581
|
|
|
3582
3582
|
} catch (err) {
|
|
3583
3583
|
// T33a: a do-phase timeout with commits landed is a dead reporter, not
|
|
3584
|
-
// dead work
|
|
3584
|
+
// dead work, write the reconciliation receipt, mark the bullet, and
|
|
3585
3585
|
// record work-landed-receipt-died instead of halting for a human.
|
|
3586
3586
|
let reconciliation = null;
|
|
3587
3587
|
if (isDoPhaseTimeoutMessage(err.message)) {
|
|
@@ -3595,10 +3595,10 @@ async function autopilotAtris(description, options = {}) {
|
|
|
3595
3595
|
.map((r) => `${r.label} ${String(r.before).slice(0, 7)} → ${String(r.after).slice(0, 7)}`)
|
|
3596
3596
|
.join(', ');
|
|
3597
3597
|
tickOutcome = 'work-landed-receipt-died';
|
|
3598
|
-
tickOutcomeText = `"${lastTaskTitle}" hit the do-phase wall but commits landed (${landed}). I wrote the reconciliation receipt and marked the bullet
|
|
3598
|
+
tickOutcomeText = `"${lastTaskTitle}" hit the do-phase wall but commits landed (${landed}). I wrote the reconciliation receipt and marked the bullet, work-landed-receipt-died, no human halt.`;
|
|
3599
3599
|
tickNextStep = 'pick the next task';
|
|
3600
3600
|
if (verbose) {
|
|
3601
|
-
console.log(` do phase timed out, but work landed (${landed}). reconciled
|
|
3601
|
+
console.log(` do phase timed out, but work landed (${landed}). reconciled, no human halt.`);
|
|
3602
3602
|
} else {
|
|
3603
3603
|
printPlainBlock([
|
|
3604
3604
|
'The do phase timed out, but commits landed before the wall.',
|
|
@@ -3630,7 +3630,7 @@ async function autopilotAtris(description, options = {}) {
|
|
|
3630
3630
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
3631
3631
|
|
|
3632
3632
|
// Heartbeat: plain-language tick summary into today's journal `## Notes`.
|
|
3633
|
-
// Guarded
|
|
3633
|
+
// Guarded, a journal write failure must never crash the tick.
|
|
3634
3634
|
try {
|
|
3635
3635
|
const horizonSlug = readHorizonSlug(cwd);
|
|
3636
3636
|
const time = new Date().toLocaleTimeString('en-US', {
|
|
@@ -3738,7 +3738,7 @@ function isStillTrue(fact, cwd) {
|
|
|
3738
3738
|
});
|
|
3739
3739
|
grepHits++;
|
|
3740
3740
|
} catch {
|
|
3741
|
-
// grep returns non-zero when no match
|
|
3741
|
+
// grep returns non-zero when no match, that's fine
|
|
3742
3742
|
}
|
|
3743
3743
|
}
|
|
3744
3744
|
|
|
@@ -3762,13 +3762,13 @@ function isStillTrue(fact, cwd) {
|
|
|
3762
3762
|
// Strong mechanical evidence: grep found terms AND recent git activity
|
|
3763
3763
|
if (gitHits > 0) return 'actionable';
|
|
3764
3764
|
|
|
3765
|
-
// Grep found terms but no recent git activity
|
|
3765
|
+
// Grep found terms but no recent git activity, can't fully verify
|
|
3766
3766
|
return 'unverified';
|
|
3767
3767
|
}
|
|
3768
3768
|
|
|
3769
3769
|
/**
|
|
3770
3770
|
* Ask a local model whether a task/fact is still relevant.
|
|
3771
|
-
* Called when isStillTrue returns 'unverified'
|
|
3771
|
+
* Called when isStillTrue returns 'unverified', the mechanical check
|
|
3772
3772
|
* couldn't confirm or deny, so we ask the configured runner to inspect the codebase.
|
|
3773
3773
|
*
|
|
3774
3774
|
* @param {{ title: string, age: number, source?: string }} fact
|
|
@@ -3812,7 +3812,7 @@ Search the codebase to verify. Reply: YES <reason> or NO <reason>`;
|
|
|
3812
3812
|
return { fresh, reasoning };
|
|
3813
3813
|
} catch (err) {
|
|
3814
3814
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
3815
|
-
// On timeout or crash, treat as unverifiable
|
|
3815
|
+
// On timeout or crash, treat as unverifiable, conservative default
|
|
3816
3816
|
return { fresh: false, reasoning: `Model check failed: ${(err.message || '').slice(0, 100)}` };
|
|
3817
3817
|
}
|
|
3818
3818
|
}
|