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/workflow.js
CHANGED
|
@@ -19,6 +19,7 @@ const { startFirstTalk } = require('../lib/context-gatherer');
|
|
|
19
19
|
const { isNonInteractive } = require('../lib/noninteractive');
|
|
20
20
|
const { loadContext } = require('../lib/state-detection');
|
|
21
21
|
const { buildToolResultBody } = require('../lib/tool-result-encode');
|
|
22
|
+
const { commitReviewLearning } = require('./learn');
|
|
22
23
|
|
|
23
24
|
function wrapWorkflowText(text, width = 76) {
|
|
24
25
|
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
|
@@ -612,7 +613,7 @@ async function planAtris(userInput = null) {
|
|
|
612
613
|
|
|
613
614
|
if (showFactory) {
|
|
614
615
|
console.log('┌─────────────────────────────────────────────────────────────┐');
|
|
615
|
-
console.log('│ Atris Plan
|
|
616
|
+
console.log('│ Atris Plan - Navigator Agent Activated │');
|
|
616
617
|
console.log('└─────────────────────────────────────────────────────────────┘');
|
|
617
618
|
console.log('');
|
|
618
619
|
|
|
@@ -641,7 +642,7 @@ async function planAtris(userInput = null) {
|
|
|
641
642
|
console.log(`- Navigator spec: ${navigatorPath || 'atris/team/navigator/MEMBER.md (missing)'}`);
|
|
642
643
|
console.log(`- Persona: ${personaFileRef || 'atris/PERSONA.md (missing)'}`);
|
|
643
644
|
const mapDisplay = mapFileRef
|
|
644
|
-
? `${mapFileRef}${mapIsPlaceholder ? ' (placeholder
|
|
645
|
+
? `${mapFileRef}${mapIsPlaceholder ? ' (placeholder, generate first)' : ''}`
|
|
645
646
|
: 'atris/MAP.md (missing)';
|
|
646
647
|
console.log(`- MAP: ${mapDisplay}`);
|
|
647
648
|
console.log(`- TODO: ${taskSourcePath || 'atris/TODO.md (missing)'}`);
|
|
@@ -710,11 +711,12 @@ async function planAtris(userInput = null) {
|
|
|
710
711
|
console.log('');
|
|
711
712
|
}
|
|
712
713
|
console.log('Workflow:');
|
|
713
|
-
console.log('1) ASCII visualize
|
|
714
|
+
console.log('1) ASCII visualize; use existing approval for this scope, otherwise wait for approval');
|
|
714
715
|
console.log('2) Run the Confidence Gate before writing tasks');
|
|
715
716
|
printConfidenceGate(' ');
|
|
716
|
-
console.log('3)
|
|
717
|
-
console.log('
|
|
717
|
+
console.log('3) Create each task in the live task plane: `atris task add "<title>" --tag <tag>`, then `atris task plan <id> --goal ... --exit ... --proof-needed ...`');
|
|
718
|
+
console.log(' Keep a delegated owner: `atris task delegate "<title>" --to <member>`; plan keeps that owner unless you pass --owner');
|
|
719
|
+
console.log(' atris/TODO.md is a generated view: never hand-edit it, regenerate with `atris task render --out atris/TODO.md`');
|
|
718
720
|
console.log('4) Log to atris/team/navigator/logs/YYYY-MM-DD.md');
|
|
719
721
|
console.log(' (Task, Delivered, User reaction, Pattern)');
|
|
720
722
|
if (atris2Mode) {
|
|
@@ -783,7 +785,7 @@ async function planAtris(userInput = null) {
|
|
|
783
785
|
userPrompt += `Your job (execute these steps):\n\n`;
|
|
784
786
|
userPrompt += `STEP 1: Generate ASCII visualizations for user approval\n`;
|
|
785
787
|
userPrompt += ` Create diagrams showing architecture, flows, schemas, UI/UX.\n`;
|
|
786
|
-
userPrompt += ` SHOW these diagrams
|
|
788
|
+
userPrompt += ` SHOW these diagrams; use existing approval for this scope, otherwise wait for approval before proceeding.\n\n`;
|
|
787
789
|
userPrompt += `STEP 2: Run the Confidence Gate before writing tasks\n`;
|
|
788
790
|
userPrompt += confidenceGatePrompt('plan') + `\n\n`;
|
|
789
791
|
userPrompt += `STEP 3: Break approved ideas into concrete tasks\n`;
|
|
@@ -799,7 +801,7 @@ async function planAtris(userInput = null) {
|
|
|
799
801
|
userPrompt += `STEP 5: Log to your journal\n`;
|
|
800
802
|
userPrompt += ` - Write to atris/team/navigator/logs/YYYY-MM-DD.md\n`;
|
|
801
803
|
userPrompt += ` - Include: Task, Delivered, User reaction, Pattern\n`;
|
|
802
|
-
userPrompt += ` - Your journal is how you learn
|
|
804
|
+
userPrompt += ` - Your journal is how you learn, so record what worked\n\n`;
|
|
803
805
|
userPrompt += `Start planning now. Read MAP.md for file references.`;
|
|
804
806
|
|
|
805
807
|
console.log('');
|
|
@@ -866,6 +868,73 @@ async function planAtris(userInput = null) {
|
|
|
866
868
|
// Prompt mode continues with existing output (already logged above)
|
|
867
869
|
}
|
|
868
870
|
|
|
871
|
+
// Both executor surfaces require the exact live record before any work.
|
|
872
|
+
// The rendered list omits raw instructions and cannot select the mission.
|
|
873
|
+
function executorDispatchForTask(task) {
|
|
874
|
+
const metadata = task && task.metadata || {};
|
|
875
|
+
return {
|
|
876
|
+
taskId: task && task.id || '',
|
|
877
|
+
owner: metadata.assigned_to || task && task.claimed_by || metadata.stage_owner || '',
|
|
878
|
+
goalId: metadata.goal_id || '',
|
|
879
|
+
missionId: metadata.mission_id || '',
|
|
880
|
+
objective: metadata.task_goal || metadata.goal_objective || metadata.stage_goal || '',
|
|
881
|
+
workspaceRoot: task && task.workspace_root || '',
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function validateExecutorDispatch(expected = {}, task) {
|
|
886
|
+
if (!expected.taskId || !expected.owner || (!expected.goalId && !expected.missionId && !expected.objective)) {
|
|
887
|
+
return { ok: false, reason: 'missing task, owner, or mission in dispatch' };
|
|
888
|
+
}
|
|
889
|
+
const actual = executorDispatchForTask(task);
|
|
890
|
+
if (!task || ['taskId', 'owner', 'goalId', 'missionId', 'objective', 'workspaceRoot']
|
|
891
|
+
.some(key => (expected[key] || '') !== (actual[key] || ''))) {
|
|
892
|
+
return { ok: false, reason: 'task, owner, or mission changed since dispatch' };
|
|
893
|
+
}
|
|
894
|
+
if (!['open', 'claimed'].includes(task.status)
|
|
895
|
+
|| (task.status === 'claimed' && task.claimed_by !== expected.owner)) {
|
|
896
|
+
return { ok: false, reason: 'task is inactive or claimed by another owner' };
|
|
897
|
+
}
|
|
898
|
+
return { ok: true };
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function executorTaskHandoff({ taskId = '', owner = '', goalId = '', missionId = '', objective = '' } = {}) {
|
|
902
|
+
const ref = taskId || '<task-id>';
|
|
903
|
+
const expected = JSON.stringify({ taskId: taskId || null, owner: owner || null, goalId: goalId || null, missionId: missionId || null, objective: objective || null });
|
|
904
|
+
return `Expected dispatch: ${expected}. Before claiming or editing, run \`atris task show ${ref} --json\` for the exact current dispatched task. If the expected task ID, owner, or mission is missing, stop; no edits are allowed. Do not select another displayed row. Check the ID, current mission, active status (open or claimed by the same owner), and functional owner against these expected values. Refuse a stale or mismatched task. Read the raw metadata, requirements, events, and verify command for exact paths, flags, and engine/model instructions; the explanation and rendered TODO are summaries only. Then claim that same task with \`atris task claim ${ref} --as <task-owner>\`, using its recorded functional owner and keeping the engine separate.`;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// The executor prompt sent to a backend agent in --execute mode. Kept as a
|
|
908
|
+
// pure function so the emitted instructions can be tested without a network:
|
|
909
|
+
// the agent claims and finishes work through the live task plane, and treats
|
|
910
|
+
// atris/TODO.md as a generated view it never hand-edits.
|
|
911
|
+
function executorAgentPrompt({ filteredTasks = '', taskSource = 'atris/TODO.md', context = 'UNKNOWN', taskId = '', owner = '', goalId = '', missionId = '', objective = '' } = {}) {
|
|
912
|
+
let userPrompt = `⚠️ CRITICAL: Execute tasks NOW. Use file tools to edit code, terminal to run commands.\n\n`;
|
|
913
|
+
userPrompt += `You are the Executor. Get it done, precisely, following instructions perfectly.\n\n`;
|
|
914
|
+
|
|
915
|
+
if (filteredTasks) {
|
|
916
|
+
userPrompt += `## REFERENCE TASK SUMMARIES (generated view from ${taskSource}; live truth is \`atris task list\`):\n${filteredTasks}\n\n`;
|
|
917
|
+
} else {
|
|
918
|
+
userPrompt += `## TASKS TO EXECUTE:\n(No tasks found - run \`atris task list\` for the live task plane)\n\n`;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
userPrompt += `Your process (EXECUTE these steps):\n`;
|
|
922
|
+
userPrompt += `1. ${executorTaskHandoff({ taskId, owner, goalId, missionId, objective })}\n`;
|
|
923
|
+
userPrompt += `2. For this task: Show ASCII visualization first (especially complex changes)\n`;
|
|
924
|
+
userPrompt += `3. Run the Confidence Gate before editing\n`;
|
|
925
|
+
userPrompt += confidenceGatePrompt('do') + `\n`;
|
|
926
|
+
userPrompt += `4. Execute task: Use file edit tools, terminal commands, etc.\n`;
|
|
927
|
+
userPrompt += `5. Before completion, rerun the gate against proof and residual risk\n`;
|
|
928
|
+
userPrompt += `6. Send it to review: \`atris task ready <id> --proof "<commands run>"\`; a human accepts. Never hand-edit TODO.md; \`atris task render --out atris/TODO.md\` regenerates it\n`;
|
|
929
|
+
userPrompt += `7. Log to atris/team/executor/logs/YYYY-MM-DD.md\n`;
|
|
930
|
+
userPrompt += ` (Task, Delivered, Errors hit, Learned)\n`;
|
|
931
|
+
userPrompt += `8. Use MAP.md to navigate codebase\n\n`;
|
|
932
|
+
userPrompt += `DO NOT just describe what you would do - actually edit files and execute commands!\n`;
|
|
933
|
+
userPrompt += `Context: ${context}\n`;
|
|
934
|
+
userPrompt += `Start only the dispatched task after the raw-record check.`;
|
|
935
|
+
return userPrompt;
|
|
936
|
+
}
|
|
937
|
+
|
|
869
938
|
async function doAtris() {
|
|
870
939
|
const { loadConfig } = require('../utils/config');
|
|
871
940
|
const { loadCredentials, ensureValidCredentials } = require('../utils/auth');
|
|
@@ -1044,7 +1113,7 @@ async function doAtris() {
|
|
|
1044
1113
|
console.log(`- Executor spec: ${executorPath || 'atris/team/executor/MEMBER.md (missing)'}`);
|
|
1045
1114
|
console.log(`- Persona: ${personaFileRef || 'atris/PERSONA.md (missing)'}`);
|
|
1046
1115
|
const mapDisplay = mapPath
|
|
1047
|
-
? `${mapPath}${mapIsPlaceholder ? ' (placeholder
|
|
1116
|
+
? `${mapPath}${mapIsPlaceholder ? ' (placeholder, generate first)' : ''}`
|
|
1048
1117
|
: 'atris/MAP.md (missing)';
|
|
1049
1118
|
console.log(`- MAP: ${mapDisplay}`);
|
|
1050
1119
|
console.log(`- TODO: ${taskSourcePath || 'atris/TODO.md (missing)'}`);
|
|
@@ -1097,17 +1166,17 @@ async function doAtris() {
|
|
|
1097
1166
|
console.log('');
|
|
1098
1167
|
}
|
|
1099
1168
|
console.log('Workflow:');
|
|
1100
|
-
console.log(
|
|
1101
|
-
console.log('
|
|
1169
|
+
console.log(`1) ${executorTaskHandoff(executorDispatchForTask(firstMinute && firstMinute.task))}`);
|
|
1170
|
+
console.log(' The live task plane is truth; never hand-edit TODO.md to claim or move work');
|
|
1102
1171
|
console.log('2) Run the Confidence Gate against the task before editing');
|
|
1103
1172
|
printConfidenceGate(' ');
|
|
1104
1173
|
console.log('3) Execute step-by-step. Run tests as you go.');
|
|
1105
1174
|
console.log('4) Before completion, rerun the gate against proof and residual risk');
|
|
1106
|
-
console.log('5) When done,
|
|
1175
|
+
console.log('5) When done, send it to review: `atris task ready <id> --proof "<commands run>"`; a human accepts, then `atris task render --out atris/TODO.md` refreshes the view');
|
|
1107
1176
|
console.log('6) Log to atris/team/executor/logs/YYYY-MM-DD.md');
|
|
1108
1177
|
console.log(' (Task, Delivered, Errors hit, Learned)');
|
|
1109
1178
|
console.log('');
|
|
1110
|
-
console.log('⛔ Do NOT plan
|
|
1179
|
+
console.log('⛔ Do NOT plan, just execute what\'s written.');
|
|
1111
1180
|
console.log('');
|
|
1112
1181
|
|
|
1113
1182
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
@@ -1146,6 +1215,10 @@ async function doAtris() {
|
|
|
1146
1215
|
|
|
1147
1216
|
// Check execution mode
|
|
1148
1217
|
if (executionMode === 'agent') {
|
|
1218
|
+
// Bind edit permission to the selected live task, not its rendered summary.
|
|
1219
|
+
const dispatch = executorDispatchForTask(firstMinute && firstMinute.task);
|
|
1220
|
+
const dispatchCheck = validateExecutorDispatch(dispatch, firstMinute && firstMinute.task);
|
|
1221
|
+
if (!dispatchCheck.ok) throw new Error(`executor dispatch refused: ${dispatchCheck.reason}`);
|
|
1149
1222
|
// Agent mode: execute via backend API
|
|
1150
1223
|
if (!config.agent_id) {
|
|
1151
1224
|
throw new Error('No agent selected. Run "atris agent" first.');
|
|
@@ -1175,29 +1248,11 @@ async function doAtris() {
|
|
|
1175
1248
|
}
|
|
1176
1249
|
|
|
1177
1250
|
// Build user prompt with context
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
if (
|
|
1182
|
-
|
|
1183
|
-
} else {
|
|
1184
|
-
userPrompt += `## TASKS TO EXECUTE:\n(No tasks found - check TODO.md)\n\n`;
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
userPrompt += `Your process (EXECUTE these steps):\n`;
|
|
1188
|
-
userPrompt += `1. Read tasks from TODO.md (shown above)\n`;
|
|
1189
|
-
userPrompt += `2. For each task: Show ASCII visualization first (especially complex changes)\n`;
|
|
1190
|
-
userPrompt += `3. Run the Confidence Gate before editing\n`;
|
|
1191
|
-
userPrompt += confidenceGatePrompt('do') + `\n`;
|
|
1192
|
-
userPrompt += `4. Execute task: Use file edit tools, terminal commands, etc.\n`;
|
|
1193
|
-
userPrompt += `5. Before completion, rerun the gate against proof and residual risk\n`;
|
|
1194
|
-
userPrompt += `6. Move task to ## Completed in TODO.md\n`;
|
|
1195
|
-
userPrompt += `7. Log to atris/team/executor/logs/YYYY-MM-DD.md\n`;
|
|
1196
|
-
userPrompt += ` (Task, Delivered, Errors hit, Learned)\n`;
|
|
1197
|
-
userPrompt += `8. Use MAP.md to navigate codebase\n\n`;
|
|
1198
|
-
userPrompt += `DO NOT just describe what you would do - actually edit files and execute commands!\n`;
|
|
1199
|
-
userPrompt += `Context: ${context}\n`;
|
|
1200
|
-
userPrompt += `Start executing tasks now.`;
|
|
1251
|
+
const taskDb = require('../lib/task-db');
|
|
1252
|
+
const liveTask = taskDb.getTask(taskDb.open(), dispatch.taskId);
|
|
1253
|
+
const currentDispatchCheck = validateExecutorDispatch(dispatch, liveTask);
|
|
1254
|
+
if (!currentDispatchCheck.ok) throw new Error(`executor dispatch refused: ${currentDispatchCheck.reason}`);
|
|
1255
|
+
const userPrompt = executorAgentPrompt({ filteredTasks, taskSource, context, ...dispatch });
|
|
1201
1256
|
|
|
1202
1257
|
console.log('');
|
|
1203
1258
|
console.log('🤖 AGENT MODE: Executing via backend API...');
|
|
@@ -1417,13 +1472,13 @@ async function reviewAtris() {
|
|
|
1417
1472
|
}
|
|
1418
1473
|
|
|
1419
1474
|
const mapDisplay = mapPath
|
|
1420
|
-
? `${mapPath}${mapIsPlaceholder ? ' (placeholder
|
|
1475
|
+
? `${mapPath}${mapIsPlaceholder ? ' (placeholder, generate first)' : ''}`
|
|
1421
1476
|
: 'atris/MAP.md (missing)';
|
|
1422
1477
|
|
|
1423
1478
|
if (showFull) {
|
|
1424
1479
|
console.log('');
|
|
1425
1480
|
console.log('┌─────────────────────────────────────────────────────────────┐');
|
|
1426
|
-
console.log('│ Atris Review
|
|
1481
|
+
console.log('│ Atris Review - Validator Agent Activated │');
|
|
1427
1482
|
console.log('└─────────────────────────────────────────────────────────────┘');
|
|
1428
1483
|
console.log('');
|
|
1429
1484
|
|
|
@@ -1594,7 +1649,7 @@ async function reviewAtris() {
|
|
|
1594
1649
|
userPrompt += ` • Verify everything works\n`;
|
|
1595
1650
|
userPrompt += ` • Find all plausible loopholes; patch them or name residual risk\n`;
|
|
1596
1651
|
userPrompt += ` • Test thoroughly (unless user says no)\n`;
|
|
1597
|
-
userPrompt += ` • Confirm active task state is clean
|
|
1652
|
+
userPrompt += ` • Confirm active task state is clean, with no unresolved Backlog/In Progress/Blocked rows for reviewed work.\n`;
|
|
1598
1653
|
userPrompt += ` If durable task state changed, regenerate the readable view with \`atris task render --out atris/TODO.md\`.\n`;
|
|
1599
1654
|
userPrompt += ` Do not hand-delete rendered completed history; if a task fails, move or mark it blocked with a note.\n`;
|
|
1600
1655
|
userPrompt += ` • Log to atris/team/validator/logs/YYYY-MM-DD.md\n`;
|
|
@@ -1718,7 +1773,7 @@ async function reviewAtris() {
|
|
|
1718
1773
|
if (fs.existsSync(logFile)) {
|
|
1719
1774
|
let journalContent = fs.readFileSync(logFile, 'utf8');
|
|
1720
1775
|
const timestamp = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
1721
|
-
const learning = `- ${timestamp}
|
|
1776
|
+
const learning = `- ${timestamp} \u2014 ${answer.trim()}`;
|
|
1722
1777
|
|
|
1723
1778
|
// Find or create ## Notes section
|
|
1724
1779
|
if (journalContent.includes('## Notes')) {
|
|
@@ -1732,17 +1787,12 @@ async function reviewAtris() {
|
|
|
1732
1787
|
console.log(`✓ Logged to journal: ${learning}`);
|
|
1733
1788
|
}
|
|
1734
1789
|
|
|
1735
|
-
// Also log to structured learnings (if learnings module exists)
|
|
1736
1790
|
try {
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
const type = /^(don't|never|avoid|watch out|careful)/i.test(insight) ? 'pitfall' : 'pattern';
|
|
1741
|
-
const key = insight.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).slice(0, 4).join('-');
|
|
1742
|
-
addLearning({ type, key, insight, confidence: 7, source: 'review', files: [] });
|
|
1743
|
-
console.log(`✓ Saved to learnings: [7/10] ${type}/${key}`);
|
|
1791
|
+
commitReviewLearning(answer.trim(), {
|
|
1792
|
+
cwd: process.cwd(),
|
|
1793
|
+
});
|
|
1744
1794
|
} catch {
|
|
1745
|
-
// learnings module not available
|
|
1795
|
+
// learnings module not available, so skip silently
|
|
1746
1796
|
}
|
|
1747
1797
|
}
|
|
1748
1798
|
|
|
@@ -1844,6 +1894,9 @@ module.exports = {
|
|
|
1844
1894
|
doAtris,
|
|
1845
1895
|
reviewAtris,
|
|
1846
1896
|
renderReviewMinute,
|
|
1897
|
+
executorAgentPrompt,
|
|
1898
|
+
executorDispatchForTask,
|
|
1899
|
+
validateExecutorDispatch,
|
|
1847
1900
|
executeAgentSDKFast,
|
|
1848
1901
|
makeCloudExecutor,
|
|
1849
1902
|
postToolResult
|
package/commands/worktree.js
CHANGED
|
@@ -939,7 +939,7 @@ function guard(args) {
|
|
|
939
939
|
return 2;
|
|
940
940
|
}
|
|
941
941
|
// Conductor plumbing (worktree metadata, the fleet prompt written for this very
|
|
942
|
-
// engine, runtime scratch, the brief ledger) must not count as dirt
|
|
942
|
+
// engine, runtime scratch, the brief ledger) must not count as dirt, otherwise the
|
|
943
943
|
// harness blocks the engine with the harness's own files.
|
|
944
944
|
const counts = statusCounts(root, { ignoreUntracked: isConductorArtifact });
|
|
945
945
|
if (counts && (counts.staged || counts.unstaged || counts.untracked) && !hasFlag(args, '--allow-dirty')) {
|
package/commands/write.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// atris write
|
|
1
|
+
// atris write: guided writing sessions (plan-do-review for prose).
|
|
2
2
|
//
|
|
3
3
|
// The contract: the human types every word of the draft. Atris structures the
|
|
4
4
|
// session (outline beats with states), tracks progress against the plan, and
|
|
5
5
|
// reviews against the taste gate (writing policy + slop detector). It NEVER
|
|
6
|
-
// writes or rewrites prose
|
|
6
|
+
// writes or rewrites prose. Review output is suggestions, not edits.
|
|
7
7
|
//
|
|
8
8
|
// Session = a folder of plain markdown, so the same files open in the web and
|
|
9
9
|
// desktop editors with no extra format:
|
|
10
10
|
// atris/writing/<slug>/plan.md outline beats: [ ] empty, [~] drafted, [x] passed
|
|
11
|
-
// atris/writing/<slug>/draft.md the piece
|
|
11
|
+
// atris/writing/<slug>/draft.md the piece, human-only territory
|
|
12
12
|
//
|
|
13
13
|
// Usage:
|
|
14
14
|
// atris write start "<topic>" [--dump "raw ideas"] [--beats "a | b | c"]
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// atris write pass <n> [slug] mark beat n passed (human calls it, not the AI)
|
|
18
18
|
// atris write list list sessions
|
|
19
19
|
//
|
|
20
|
-
// Zero external deps (Node built-ins only)
|
|
20
|
+
// Zero external deps (Node built-ins only). Repo contract.
|
|
21
21
|
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const path = require('path');
|
|
@@ -212,7 +212,7 @@ function review(argv, root = process.cwd()) {
|
|
|
212
212
|
console.log(` ⚠ ${empty.length} beat${empty.length === 1 ? '' : 's'} still empty: ${empty.map((b) => b.title).join(', ')}`);
|
|
213
213
|
}
|
|
214
214
|
if (findings.length) {
|
|
215
|
-
console.log(`\n slop tells (fix them yourself
|
|
215
|
+
console.log(`\n slop tells (fix them yourself, atris never edits your draft):`);
|
|
216
216
|
for (const f of findings) console.log(` ✗ draft.md:${f.line} ${f.rule.padEnd(16)} ${f.why}`);
|
|
217
217
|
} else {
|
|
218
218
|
console.log(` ✓ slop scan clean`);
|
|
@@ -235,7 +235,7 @@ function pass(argv, root = process.cwd()) {
|
|
|
235
235
|
const beat = plan.beats.find((b) => b.n === n);
|
|
236
236
|
if (!beat) { console.error(` no beat ${n} in ${slug}`); return 2; }
|
|
237
237
|
const counts = draftWordCounts(slug, plan.beats, root);
|
|
238
|
-
if ((counts.get(n) || 0) === 0) { console.error(` beat ${n} "${beat.title}" has no words yet
|
|
238
|
+
if ((counts.get(n) || 0) === 0) { console.error(` beat ${n} "${beat.title}" has no words yet, write it first`); return 2; }
|
|
239
239
|
beat.state = 'x';
|
|
240
240
|
writePlanStates(slug, plan.beats, root);
|
|
241
241
|
console.log(` ● passed: ${n}. ${beat.title}`);
|
|
@@ -370,17 +370,17 @@ function coach(argv, root = process.cwd()) {
|
|
|
370
370
|
|
|
371
371
|
function help() {
|
|
372
372
|
console.log(`
|
|
373
|
-
atris write
|
|
373
|
+
atris write: guided writing sessions (you write every word; atris structures + reviews)
|
|
374
374
|
|
|
375
375
|
atris write the coach: knows where you are, gets you going
|
|
376
376
|
atris write start "<topic>" [--dump "..."] [--beats "a | b | c"]
|
|
377
377
|
atris write coach [slug] the coach: cheers your best line, asks the next question (--offline for no-LLM)
|
|
378
378
|
atris write status [slug] progress against the outline (beats landed)
|
|
379
379
|
atris write review [slug] taste gate: slop scan + writing-policy passes (read-only)
|
|
380
|
-
atris write pass <n> [slug] mark beat n passed
|
|
380
|
+
atris write pass <n> [slug] mark beat n passed, the human calls it
|
|
381
381
|
atris write list all sessions
|
|
382
382
|
|
|
383
|
-
sessions are plain markdown in atris/writing/<slug
|
|
383
|
+
sessions are plain markdown in atris/writing/<slug>/, the same files open
|
|
384
384
|
in the web and desktop editors. atris never writes or edits your draft.
|
|
385
385
|
`);
|
|
386
386
|
return 0;
|
package/commands/x-search.js
CHANGED
|
@@ -11,6 +11,8 @@ const {
|
|
|
11
11
|
extractTeachMechanisms,
|
|
12
12
|
isThinTeachLesson,
|
|
13
13
|
TEACH_THIN_REFUSE,
|
|
14
|
+
printLearnerCheckGate,
|
|
15
|
+
proveSavedLearnerBaseline,
|
|
14
16
|
} = require('./youtube');
|
|
15
17
|
|
|
16
18
|
const DEFAULT_TIMEOUT_MS = 120000;
|
|
@@ -27,10 +29,10 @@ function showXSearchHelp(output = console.log, commandName = 'atris x-search') {
|
|
|
27
29
|
output('');
|
|
28
30
|
output(`Search X/Twitter via Atris (${COST_HINT}).`);
|
|
29
31
|
output('Requires login. Same auth path as atris youtube process.');
|
|
30
|
-
output('Prints to stdout. Rich ephemeral prints one apply next-step (no files).');
|
|
32
|
+
output('Prints to stdout. Rich ephemeral prints one apply next-step, then hands off to atris youtube search (no files).');
|
|
31
33
|
output('--save files a brief only when the result is rich.');
|
|
32
34
|
output('unsave deletes the filed brief, apply stub, and matching experiment pack (no paid calls).');
|
|
33
|
-
output('Empty or failed search
|
|
35
|
+
output('Empty or failed search prints credits refunded only when the server marks a refund.');
|
|
34
36
|
output('');
|
|
35
37
|
output('Options:');
|
|
36
38
|
output(' --limit <n> Max results hint (search only)');
|
|
@@ -221,7 +223,9 @@ function parseUnsaveArgs(argv = []) {
|
|
|
221
223
|
const options = {
|
|
222
224
|
mode: 'unsave',
|
|
223
225
|
help: false,
|
|
226
|
+
json: false,
|
|
224
227
|
source: null,
|
|
228
|
+
sources: [],
|
|
225
229
|
};
|
|
226
230
|
|
|
227
231
|
if (args.length === 0 || ['help', '--help', '-h'].includes(args[0])) {
|
|
@@ -236,19 +240,20 @@ function parseUnsaveArgs(argv = []) {
|
|
|
236
240
|
const arg = args[i];
|
|
237
241
|
if (arg === '--help' || arg === '-h' || arg === 'help') {
|
|
238
242
|
options.help = true;
|
|
243
|
+
} else if (arg === '--json') {
|
|
244
|
+
options.json = true;
|
|
239
245
|
} else if (arg === '--unsave') {
|
|
240
246
|
continue;
|
|
241
247
|
} else if (arg.startsWith('-')) {
|
|
242
248
|
throw new Error(`Unknown option: ${arg}`);
|
|
243
|
-
} else if (!options.source) {
|
|
244
|
-
options.source = arg;
|
|
245
249
|
} else {
|
|
246
|
-
|
|
250
|
+
options.sources.push(arg);
|
|
251
|
+
if (!options.source) options.source = arg;
|
|
247
252
|
}
|
|
248
253
|
}
|
|
249
254
|
|
|
250
255
|
if (options.help) return options;
|
|
251
|
-
if (!options.
|
|
256
|
+
if (!options.sources.length) throw new Error('usage: atris x-search unsave <query-or-source>');
|
|
252
257
|
return options;
|
|
253
258
|
}
|
|
254
259
|
|
|
@@ -322,9 +327,8 @@ function xSearchCredits(data) {
|
|
|
322
327
|
return { used, remaining, refunded };
|
|
323
328
|
}
|
|
324
329
|
|
|
325
|
-
function
|
|
330
|
+
function creditsRefundedExplicitly(credits) {
|
|
326
331
|
if (!credits) return false;
|
|
327
|
-
if (credits.used === 0) return true;
|
|
328
332
|
if (credits.refunded === true) return true;
|
|
329
333
|
return typeof credits.refunded === 'number' && credits.refunded > 0;
|
|
330
334
|
}
|
|
@@ -334,7 +338,7 @@ function formatCreditsLines(credits) {
|
|
|
334
338
|
if (credits.used !== undefined || credits.remaining !== undefined) {
|
|
335
339
|
lines.push(`Credits: ${credits.used !== undefined ? credits.used : '?'} used, ${credits.remaining !== undefined ? credits.remaining : '?'} remaining`);
|
|
336
340
|
}
|
|
337
|
-
if (
|
|
341
|
+
if (creditsRefundedExplicitly(credits)) {
|
|
338
342
|
lines.push('credits refunded');
|
|
339
343
|
}
|
|
340
344
|
return lines;
|
|
@@ -349,7 +353,7 @@ function xSearchFailureError(result) {
|
|
|
349
353
|
? ' xAI is unavailable; retry in a few seconds.'
|
|
350
354
|
: '';
|
|
351
355
|
const credits = xSearchCredits(result.data);
|
|
352
|
-
const refundHint = result.status === 502 &&
|
|
356
|
+
const refundHint = result.status === 502 && creditsRefundedExplicitly(credits)
|
|
353
357
|
? ' credits refunded.'
|
|
354
358
|
: '';
|
|
355
359
|
const lines = [`X search failed (${result.status}): ${resultErrorText(result)}.${hint}${refundHint}`];
|
|
@@ -387,6 +391,12 @@ async function runXSearch(options, deps = {}) {
|
|
|
387
391
|
forceMint: true,
|
|
388
392
|
});
|
|
389
393
|
if (remint?.ok && remint.token) {
|
|
394
|
+
if (!options.json) {
|
|
395
|
+
const print = typeof deps.output === 'function' ? deps.output : () => {};
|
|
396
|
+
for (const line of formatCreditsLines(xSearchCredits(result.data))) {
|
|
397
|
+
print(line);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
390
400
|
auth = remint;
|
|
391
401
|
result = await call(auth.token);
|
|
392
402
|
}
|
|
@@ -403,6 +413,21 @@ function xSearchApplySource(options) {
|
|
|
403
413
|
return options?.query;
|
|
404
414
|
}
|
|
405
415
|
|
|
416
|
+
function quoteYoutubeSearchQuery(source) {
|
|
417
|
+
return `"${String(source || '').replace(/"/g, '')}"`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function printYoutubeSearchNext(source, output) {
|
|
421
|
+
if (!source) return;
|
|
422
|
+
output(`next: atris youtube search ${quoteYoutubeSearchQuery(source)}`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const EMPTY_YOUTUBE_SEARCH_NEXT = 'next: atris youtube search " "';
|
|
426
|
+
|
|
427
|
+
function printEmptyYoutubeSearchNext(output) {
|
|
428
|
+
output(EMPTY_YOUTUBE_SEARCH_NEXT);
|
|
429
|
+
}
|
|
430
|
+
|
|
406
431
|
function xSearchApplyRel(source) {
|
|
407
432
|
return applyGate.applySidecarRel('x-search', applyGate.applySlug(source));
|
|
408
433
|
}
|
|
@@ -509,10 +534,34 @@ function unsaveXSearch(target, deps = {}) {
|
|
|
509
534
|
return 0;
|
|
510
535
|
}
|
|
511
536
|
|
|
537
|
+
function unsaveTargets(options) {
|
|
538
|
+
if (Array.isArray(options?.sources) && options.sources.length) return options.sources;
|
|
539
|
+
const single = options?.source || options?.query;
|
|
540
|
+
return single ? [single] : [];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function runXSearchUnsave(options, deps = {}) {
|
|
544
|
+
const output = deps.output || ((line = '') => console.log(line));
|
|
545
|
+
const targets = unsaveTargets(options);
|
|
546
|
+
if (!targets.length) {
|
|
547
|
+
output('usage: atris x-search unsave <query-or-source>');
|
|
548
|
+
return 2;
|
|
549
|
+
}
|
|
550
|
+
let code = 0;
|
|
551
|
+
for (const target of targets) {
|
|
552
|
+
const status = unsaveXSearch(target, deps);
|
|
553
|
+
if (status !== 0) code = status;
|
|
554
|
+
}
|
|
555
|
+
if (code === 0 && options.json !== true && deps.json !== true) {
|
|
556
|
+
printEmptyYoutubeSearchNext(output);
|
|
557
|
+
}
|
|
558
|
+
return code;
|
|
559
|
+
}
|
|
560
|
+
|
|
512
561
|
function saveRichXSearch({ cwd, source, text, now } = {}) {
|
|
513
562
|
const lesson = xSearchLessonFromText(text);
|
|
514
563
|
if (isThinTeachLesson(lesson)) {
|
|
515
|
-
return { thin: true, brief: null, packRel: null };
|
|
564
|
+
return { thin: true, brief: null, packRel: null, lesson };
|
|
516
565
|
}
|
|
517
566
|
const brief = fileXSearchBrief({ cwd, source, text, now });
|
|
518
567
|
const packRel = fileTeachExperiment({
|
|
@@ -521,19 +570,20 @@ function saveRichXSearch({ cwd, source, text, now } = {}) {
|
|
|
521
570
|
slug: source ? xSearchExperimentSlug(source) : null,
|
|
522
571
|
applyRel: source ? xSearchApplyRel(source) : null,
|
|
523
572
|
});
|
|
524
|
-
return { thin: false, brief, packRel };
|
|
573
|
+
return { thin: false, brief, packRel, lesson };
|
|
525
574
|
}
|
|
526
575
|
|
|
527
576
|
function ensureXSearchApply({ cwd, source, packRel, now, output } = {}) {
|
|
528
577
|
const pack = packRel || (source ? xSearchExperimentRel(source) : null);
|
|
578
|
+
const slug = pack ? path.basename(pack) : null;
|
|
529
579
|
return applyGate.ensureApply({
|
|
530
580
|
cwd,
|
|
531
581
|
source,
|
|
532
582
|
rel: source ? xSearchApplyRel(source) : null,
|
|
533
583
|
now,
|
|
534
584
|
output,
|
|
535
|
-
incompleteMessage:
|
|
536
|
-
? `next:
|
|
585
|
+
incompleteMessage: slug
|
|
586
|
+
? `next: atris experiments keep ${slug}`
|
|
537
587
|
: APPLY_NEXT_MESSAGE,
|
|
538
588
|
required: false,
|
|
539
589
|
change: pack ? `apply ${pack}` : undefined,
|
|
@@ -598,7 +648,7 @@ async function xSearchCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
598
648
|
}
|
|
599
649
|
|
|
600
650
|
if (options.mode === 'unsave' || options.unsave) {
|
|
601
|
-
return
|
|
651
|
+
return runXSearchUnsave(options, deps);
|
|
602
652
|
}
|
|
603
653
|
|
|
604
654
|
let status = 0;
|
|
@@ -611,11 +661,19 @@ async function xSearchCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
611
661
|
output(hasResults ? formatXSearchResult(data) : formatEmptyXSearchResult(data));
|
|
612
662
|
}
|
|
613
663
|
if (!hasResults) {
|
|
664
|
+
if (
|
|
665
|
+
!options.json
|
|
666
|
+
&& !options.save
|
|
667
|
+
&& !creditsRefundedExplicitly(xSearchCredits(data))
|
|
668
|
+
) {
|
|
669
|
+
printEmptyYoutubeSearchNext(output);
|
|
670
|
+
}
|
|
614
671
|
status = 2;
|
|
615
672
|
} else if (options.save) {
|
|
616
673
|
const source = xSearchApplySource(options);
|
|
674
|
+
const cwd = deps.cwd || process.cwd();
|
|
617
675
|
const saved = saveRichXSearch({
|
|
618
|
-
cwd
|
|
676
|
+
cwd,
|
|
619
677
|
source,
|
|
620
678
|
text: xSearchContent(data),
|
|
621
679
|
now: deps.applyNow || deps.now,
|
|
@@ -625,21 +683,34 @@ async function xSearchCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
625
683
|
status = 2;
|
|
626
684
|
} else {
|
|
627
685
|
const ensureApply = deps.ensureApply || ensureXSearchApply;
|
|
628
|
-
|
|
629
|
-
cwd
|
|
686
|
+
const applyCode = ensureApply({
|
|
687
|
+
cwd,
|
|
630
688
|
source,
|
|
631
689
|
packRel: saved.packRel,
|
|
632
690
|
now: deps.applyNow || deps.now,
|
|
633
691
|
output,
|
|
634
692
|
});
|
|
693
|
+
if (deps.ensureApply) {
|
|
694
|
+
status = applyCode;
|
|
695
|
+
} else {
|
|
696
|
+
const baseline = proveSavedLearnerBaseline({
|
|
697
|
+
cwd,
|
|
698
|
+
applyRel: source ? xSearchApplyRel(source) : null,
|
|
699
|
+
lesson: saved.lesson,
|
|
700
|
+
output,
|
|
701
|
+
json: options.json === true,
|
|
702
|
+
});
|
|
703
|
+
status = baseline !== 0 ? baseline : applyCode;
|
|
704
|
+
}
|
|
635
705
|
}
|
|
636
706
|
} else {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
) {
|
|
707
|
+
const lesson = xSearchLessonFromText(xSearchContent(data));
|
|
708
|
+
const json = options.json === true;
|
|
709
|
+
if (!json && !isThinTeachLesson(lesson)) {
|
|
641
710
|
applyGate.hintEphemeralApply(output, 'x-search');
|
|
642
711
|
}
|
|
712
|
+
printLearnerCheckGate(output, lesson, { includeCheck: true, json });
|
|
713
|
+
if (!json) printYoutubeSearchNext(xSearchApplySource(options), output);
|
|
643
714
|
status = 0;
|
|
644
715
|
}
|
|
645
716
|
} catch (err) {
|
package/commands/xp.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const crypto = require('crypto');
|
|
7
7
|
const { spawnSync } = require('child_process');
|
|
8
8
|
const { hasFlag: hasExactFlag } = require('../lib/arg-parser');
|
|
9
|
+
const { treeHashFor } = require('../lib/tree-hash');
|
|
9
10
|
|
|
10
11
|
const DEFAULT_GRAPH_DAYS = 365;
|
|
11
12
|
const MAX_SYNC_GRAPH_DAYS = 370;
|
|
@@ -22,7 +23,7 @@ const CODEX_STATE_FILE = path.join(os.homedir(), '.codex', 'state_5.sqlite');
|
|
|
22
23
|
|
|
23
24
|
// Default single-workspace XP root. Local XP reads task_episodes.jsonl (written
|
|
24
25
|
// by task-db at the resolved workspace root) and writes its projection beside
|
|
25
|
-
// it, so the `--workspace` default must resolve the same shared root
|
|
26
|
+
// it, so the `--workspace` default must resolve the same shared root, otherwise
|
|
26
27
|
// `xp --local` from a subdir (e.g. backend/) read the wrong/empty episodes and
|
|
27
28
|
// split the projection into a nested .atris. Explicit --workspace still wins;
|
|
28
29
|
// the multi-root roster path (discoverCareerXpWorkspaces) is untouched. Falls
|
|
@@ -908,7 +909,7 @@ function readTaskEpisodeTail(episodePath, cursorPath, options = {}) {
|
|
|
908
909
|
}
|
|
909
910
|
}
|
|
910
911
|
|
|
911
|
-
function receiptFromTaskEpisode(episode) {
|
|
912
|
+
function receiptFromTaskEpisode(episode, treeHash) {
|
|
912
913
|
const episodeId = episode?.episode_id;
|
|
913
914
|
const label = episode?.rl?.label;
|
|
914
915
|
const rejected = label === 'rework_requested' || label === 'rejected';
|
|
@@ -924,6 +925,7 @@ function receiptFromTaskEpisode(episode) {
|
|
|
924
925
|
|
|
925
926
|
return {
|
|
926
927
|
schema: 'atris.career_xp_receipt.v1',
|
|
928
|
+
tree_hash: treeHash,
|
|
927
929
|
receipt_id: `task_review:${episodeId}${rejected ? ':rejected' : ''}`,
|
|
928
930
|
source: 'atris-cli',
|
|
929
931
|
source_type: 'task_review',
|
|
@@ -1071,10 +1073,11 @@ function collectLocalXpProjectionState(args = [], { write = true } = {}) {
|
|
|
1071
1073
|
&& fileSize(episodePath) > 0
|
|
1072
1074
|
&& fileSize(receiptsPath) === 0;
|
|
1073
1075
|
const tail = readTaskEpisodeTail(episodePath, cursorPath, { forceReset: replayFromStart });
|
|
1076
|
+
const treeHash = write && tail.episodes.length ? treeHashFor(workspace) : null;
|
|
1074
1077
|
const seen = new Set(existingChain.receipts.map(receipt => receipt.receipt_id).filter(Boolean));
|
|
1075
1078
|
let previousHash = existingChain.integrity.head_hash || null;
|
|
1076
1079
|
const newReceipts = tail.episodes
|
|
1077
|
-
.map(receiptFromTaskEpisode)
|
|
1080
|
+
.map((episode) => receiptFromTaskEpisode(episode, treeHash))
|
|
1078
1081
|
.filter(Boolean)
|
|
1079
1082
|
.filter((receipt) => {
|
|
1080
1083
|
if (seen.has(receipt.receipt_id)) return false;
|