atris 3.34.0 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +35 -0
- package/FOR_AGENTS.md +5 -3
- package/README.md +5 -3
- package/atris/GETTING_STARTED.md +1 -1
- package/atris/atris.md +3 -0
- package/atris/policies/day-loop-voice.md +102 -0
- package/atris/policies/outbound-artifact-gate.md +2 -0
- package/atris/skills/design/SKILL.md +56 -32
- package/atris/skills/endgame/SKILL.md +12 -6
- package/atris/skills/engines/SKILL.md +35 -8
- package/atris/skills/fable-method/SKILL.md +66 -0
- package/atris/skills/improve/SKILL.md +65 -45
- package/atris/skills/render-cli/SKILL.md +88 -0
- package/atris/skills/youtube/SKILL.md +10 -1
- package/atris.md +6 -2
- package/ax +617 -31
- package/bin/atris.js +719 -266
- package/commands/activate.js +194 -88
- package/commands/aeo.js +52 -0
- package/commands/agents.js +166 -0
- package/commands/autoland.js +718 -72
- package/commands/autopilot-front.js +20 -2
- package/commands/autopilot.js +118 -2
- package/commands/avail.js +407 -0
- package/commands/bench.js +188 -0
- package/commands/brain.js +3 -0
- package/commands/brief.js +651 -0
- package/commands/business-sync.js +192 -6
- package/commands/business.js +91 -8
- package/commands/clean.js +50 -24
- package/commands/close.js +1083 -0
- package/commands/cloud.js +245 -0
- package/commands/codex-goal.js +26 -2
- package/commands/compile.js +292 -1
- package/commands/computer.js +150 -3
- package/commands/dream.js +365 -0
- package/commands/drill.js +371 -0
- package/commands/drive.js +187 -0
- package/commands/engine.js +1061 -29
- package/commands/experiments.js +28 -0
- package/commands/feed.js +202 -0
- package/commands/feedback.js +34 -12
- package/commands/fleet-report.js +206 -0
- package/commands/github.js +38 -0
- package/commands/gm.js +285 -3
- package/commands/goal.js +247 -0
- package/commands/improve.js +642 -26
- package/commands/init.js +83 -43
- package/commands/integrations.js +39 -11
- package/commands/interview.js +209 -0
- package/commands/land.js +253 -52
- package/commands/lesson.js +112 -1
- package/commands/lifecycle.js +39 -3
- package/commands/linear.js +38 -0
- package/commands/log.js +84 -1
- package/commands/loops.js +220 -16
- package/commands/meet.js +220 -0
- package/commands/member.js +899 -66
- package/commands/mission.js +3512 -332
- package/commands/next.js +137 -0
- package/commands/now.js +240 -21
- package/commands/one-lap.js +776 -0
- package/commands/orb.js +314 -0
- package/commands/pack-craft.js +179 -0
- package/commands/pack.js +823 -0
- package/commands/play.js +3 -2
- package/commands/probe.js +30 -3
- package/commands/pulse.js +241 -46
- package/commands/push.js +260 -82
- package/commands/radar.js +259 -14
- package/commands/rainmaker.js +49 -0
- package/commands/report.js +415 -0
- package/commands/scout.js +147 -0
- package/commands/search.js +363 -0
- package/commands/serve.js +54 -0
- package/commands/skill.js +47 -3
- package/commands/slop.js +50 -2
- package/commands/soul.js +1 -1
- package/commands/status.js +50 -5
- package/commands/stream.js +861 -0
- package/commands/stripe.js +38 -0
- package/commands/study.js +693 -0
- package/commands/supabase.js +39 -0
- package/commands/sync.js +67 -54
- package/commands/task.js +2275 -182
- package/commands/team.js +73 -0
- package/commands/truth.js +29 -3
- package/commands/unknowns.js +627 -0
- package/commands/update.js +44 -0
- package/commands/vercel.js +38 -0
- package/commands/verify.js +96 -0
- package/commands/watch.js +303 -0
- package/commands/wish.js +500 -0
- package/commands/workflow.js +11 -5
- package/commands/worktree.js +299 -20
- package/commands/write.js +399 -0
- package/commands/xp.js +29 -11
- package/lib/auto-accept-certified.js +391 -43
- package/lib/autoland.js +353 -52
- package/lib/ax-auto-lane.js +79 -0
- package/lib/bench/context.js +147 -0
- package/lib/bench/engines.js +141 -0
- package/lib/bench/report.js +140 -0
- package/lib/bench/runner.js +512 -0
- package/lib/brief-ledger.js +350 -0
- package/lib/cloud-mission.js +259 -0
- package/lib/codex-flight.js +154 -0
- package/lib/default-runner.js +45 -0
- package/lib/default-verifier.js +70 -0
- package/lib/engine-registry.js +232 -0
- package/lib/experiments/daily.js +640 -0
- package/lib/fleet.js +2431 -38
- package/lib/improve-vitals-html.js +171 -0
- package/lib/known-commands.js +58 -0
- package/lib/loop-doctor.js +416 -0
- package/lib/member-switches.js +144 -0
- package/lib/memory-view.js +14 -5
- package/lib/mission-room.js +1 -0
- package/lib/mission-root.js +52 -0
- package/lib/mission-runtime-loop.js +7 -0
- package/lib/next-moves.js +327 -10
- package/lib/official-cli-integration.js +174 -0
- package/lib/one-lap-validator.js +60 -0
- package/lib/orb-context.js +477 -0
- package/lib/orb-scorecard.js +224 -0
- package/lib/outbound-send-gate.js +165 -0
- package/lib/permission-grants.js +293 -0
- package/lib/policy-lessons.js +52 -1
- package/lib/pulse.js +277 -3
- package/lib/receipt-block.js +168 -0
- package/lib/receipt-evidence.js +65 -4
- package/lib/review-integrity.js +147 -0
- package/lib/router-brain.js +352 -0
- package/lib/runner-command.js +33 -0
- package/lib/self-drive.js +258 -0
- package/lib/short-name.js +103 -0
- package/lib/spawn-env.js +18 -0
- package/lib/state-detection.js +56 -1
- package/lib/sync-status.js +59 -0
- package/lib/task-db.js +319 -27
- package/lib/task-proof.js +43 -1
- package/lib/task-receipt.js +93 -0
- package/lib/team-presence.js +260 -0
- package/lib/tool-result-encode.js +7 -0
- package/lib/trust-tiers.js +90 -0
- package/lib/usage.js +107 -0
- package/lib/voice-gate.js +163 -0
- package/lib/wish-audit.js +1368 -0
- package/lib/wish-delegate.js +1840 -0
- package/lib/wish-design.js +110 -0
- package/lib/wish-stats.js +183 -0
- package/lib/wish-store.js +354 -0
- package/lib/zip.js +221 -0
- package/package.json +3 -1
- package/templates/loops/atris/loops/LOOPS.md +55 -0
- package/templates/loops/atris/loops/TICK.md +24 -0
- package/templates/loops/atris/loops/feedback.md +22 -0
- package/templates/loops/atris/loops/quality.md +22 -0
- package/templates/loops/atris/wiki/systems/loops.md +41 -0
- package/utils/api.js +5 -1
- package/utils/auth.js +57 -21
- package/utils/update-check.js +27 -6
- package/atris/learnings.jsonl +0 -1
package/commands/push.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
+
const readline = require('readline');
|
|
4
5
|
const { loadCredentials } = require('../utils/auth');
|
|
5
6
|
const { apiRequestJson } = require('../utils/api');
|
|
6
7
|
const { loadBusinesses, saveBusinesses, businessMatchesSlug } = require('./business');
|
|
@@ -54,6 +55,102 @@ function pathInScope(filePath, onlyPrefixes) {
|
|
|
54
55
|
return onlyPrefixes.some(prefix => filePath.startsWith(prefix));
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
const PUSH_VALUE_FLAGS = new Set(['--from', '--only', '--timeout']);
|
|
59
|
+
|
|
60
|
+
function looksLikePushPath(token) {
|
|
61
|
+
const value = String(token || '');
|
|
62
|
+
if (!value || value.startsWith('-')) return false;
|
|
63
|
+
if (value.includes('/') || value.includes('\\')) return true;
|
|
64
|
+
return value.includes('.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizePushScopePath(raw) {
|
|
68
|
+
const wikiPrefix = normalizeWikiOnlyPrefix(raw);
|
|
69
|
+
if (wikiPrefix) return `/${wikiPrefix.replace(/^\//, '')}`;
|
|
70
|
+
let n = '/' + String(raw || '').replace(/^\//, '').replace(/\\/g, '/');
|
|
71
|
+
if (!n.endsWith('/') && !n.includes('.')) n += '/';
|
|
72
|
+
return n;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse push argv into optional business slug, positional file paths, and --changed.
|
|
77
|
+
* `atris push [business] [file...] [--changed] [--only <prefix>]`
|
|
78
|
+
*/
|
|
79
|
+
function parsePushArgv(argv = process.argv) {
|
|
80
|
+
const args = argv.slice(3);
|
|
81
|
+
const flags = new Set();
|
|
82
|
+
const values = {};
|
|
83
|
+
const positionals = [];
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < args.length; i++) {
|
|
86
|
+
const a = args[i];
|
|
87
|
+
if (!a.startsWith('-')) {
|
|
88
|
+
positionals.push(a);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const eq = a.indexOf('=');
|
|
92
|
+
if (eq !== -1) {
|
|
93
|
+
values[a.slice(0, eq)] = a.slice(eq + 1);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (PUSH_VALUE_FLAGS.has(a) && args[i + 1] && !args[i + 1].startsWith('-')) {
|
|
97
|
+
values[a] = args[++i];
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
flags.add(a);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let slug = null;
|
|
104
|
+
const pathArgs = [];
|
|
105
|
+
for (const token of positionals) {
|
|
106
|
+
if (!slug && !looksLikePushPath(token)) slug = token;
|
|
107
|
+
else pathArgs.push(token);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
slug,
|
|
112
|
+
pathArgs,
|
|
113
|
+
changed: flags.has('--changed'),
|
|
114
|
+
onlyRaw: values['--only'] || null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Local files whose hash differs from (or is absent in) the last-synced manifest. */
|
|
119
|
+
function resolveChangedScopePaths(localFiles = {}, baseFiles = {}) {
|
|
120
|
+
const paths = [];
|
|
121
|
+
for (const [filePath, info] of Object.entries(localFiles || {})) {
|
|
122
|
+
const base = baseFiles[filePath];
|
|
123
|
+
if (!base || base.hash !== info.hash) paths.push(filePath.startsWith('/') ? filePath : `/${filePath}`);
|
|
124
|
+
}
|
|
125
|
+
return paths.sort();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Merge --only, positional paths, and --changed into onlyPrefixes for buildPushChangePlan.
|
|
130
|
+
* Returns null when nothing scopes the push (full workspace plan).
|
|
131
|
+
* `--changed` with no diffs returns [] so the plan stays empty instead of widening.
|
|
132
|
+
*/
|
|
133
|
+
function mergePushScopes({ onlyRaw = null, pathArgs = [], changed = false, localFiles = {}, baseFiles = {} } = {}) {
|
|
134
|
+
const scopes = [];
|
|
135
|
+
if (onlyRaw) {
|
|
136
|
+
for (const part of String(onlyRaw).split(',')) {
|
|
137
|
+
const trimmed = part.trim();
|
|
138
|
+
if (trimmed) scopes.push(normalizePushScopePath(trimmed));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const raw of pathArgs || []) {
|
|
142
|
+
const trimmed = String(raw || '').trim();
|
|
143
|
+
if (trimmed) scopes.push(normalizePushScopePath(trimmed));
|
|
144
|
+
}
|
|
145
|
+
if (changed) {
|
|
146
|
+
for (const filePath of resolveChangedScopePaths(localFiles, baseFiles)) {
|
|
147
|
+
scopes.push(filePath.startsWith('/') ? filePath : `/${filePath}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (scopes.length === 0) return changed ? [] : null;
|
|
151
|
+
return [...new Set(scopes)];
|
|
152
|
+
}
|
|
153
|
+
|
|
57
154
|
function buildPushChangePlan({
|
|
58
155
|
localFiles = {},
|
|
59
156
|
baseFiles = {},
|
|
@@ -98,6 +195,34 @@ function shouldRetrySyncIndividually(result, filesToPush) {
|
|
|
98
195
|
return result.status !== 403 && result.status !== 409;
|
|
99
196
|
}
|
|
100
197
|
|
|
198
|
+
function buildPushUploadBatches(filesToPush = [], { maxBatchBytes = 256 * 1024 } = {}) {
|
|
199
|
+
const batches = [];
|
|
200
|
+
let current = [];
|
|
201
|
+
let currentBytes = 0;
|
|
202
|
+
const limit = Number.isFinite(maxBatchBytes) && maxBatchBytes > 0 ? maxBatchBytes : 256 * 1024;
|
|
203
|
+
|
|
204
|
+
const flush = () => {
|
|
205
|
+
if (current.length > 0) batches.push(current);
|
|
206
|
+
current = [];
|
|
207
|
+
currentBytes = 0;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
for (const file of filesToPush) {
|
|
211
|
+
const bytes = Buffer.byteLength(file && file.content ? String(file.content) : '', 'utf8');
|
|
212
|
+
if (bytes > limit) {
|
|
213
|
+
flush();
|
|
214
|
+
batches.push([file]);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (current.length > 0 && currentBytes + bytes > limit) flush();
|
|
218
|
+
current.push(file);
|
|
219
|
+
currentBytes += bytes;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
flush();
|
|
223
|
+
return batches;
|
|
224
|
+
}
|
|
225
|
+
|
|
101
226
|
function isMassDeletePlan({ deletedPaths = [], filesToPush = [], unchangedCount = 0 } = {}) {
|
|
102
227
|
const deleteCount = deletedPaths.length;
|
|
103
228
|
if (deleteCount === 0) return false;
|
|
@@ -217,6 +342,36 @@ function renderPushSafetyBlock(report, slug) {
|
|
|
217
342
|
return `${lines.join('\n')}\n`;
|
|
218
343
|
}
|
|
219
344
|
|
|
345
|
+
function renderDriftBlock(driftFiles) {
|
|
346
|
+
const lines = [];
|
|
347
|
+
const count = driftFiles.length;
|
|
348
|
+
lines.push('');
|
|
349
|
+
lines.push(` ${count} file${count === 1 ? '' : 's'} conflict. run atris sync --review to pick local/cloud/merge, or atris push --only <path> to ship just what changed.`);
|
|
350
|
+
lines.push('');
|
|
351
|
+
lines.push(' Files that differ on cloud:');
|
|
352
|
+
driftFiles.slice(0, 8).forEach((p) => lines.push(` ~ ${p.replace(/^\//, '')}`));
|
|
353
|
+
if (driftFiles.length > 8) lines.push(` ... +${driftFiles.length - 8} more`);
|
|
354
|
+
lines.push('');
|
|
355
|
+
lines.push(' To override (force-push, may clobber cloud edits): atris push --force');
|
|
356
|
+
lines.push('');
|
|
357
|
+
return `${lines.join('\n')}\n`;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function needsForceBroadWorkspaceConfirm({ force = false, allowBroadWorkspace = false, yes = false, dryRun = false } = {}) {
|
|
361
|
+
return force && allowBroadWorkspace && !yes && !dryRun;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function promptConfirm(message, { input = process.stdin, output = process.stdout } = {}) {
|
|
365
|
+
return new Promise((resolve) => {
|
|
366
|
+
const rl = readline.createInterface({ input, output });
|
|
367
|
+
rl.question(message, (answer) => {
|
|
368
|
+
rl.close();
|
|
369
|
+
const normalized = String(answer).trim().toLowerCase();
|
|
370
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
220
375
|
function parsePushTimeoutSec(argv = process.argv, defaultSec = 120) {
|
|
221
376
|
let raw = null;
|
|
222
377
|
const eqArg = argv.find(a => a.startsWith('--timeout='));
|
|
@@ -233,17 +388,20 @@ function parsePushTimeoutSec(argv = process.argv, defaultSec = 120) {
|
|
|
233
388
|
|
|
234
389
|
async function pushAtris() {
|
|
235
390
|
const elapsedMs = startTimer();
|
|
236
|
-
|
|
391
|
+
const parsed = parsePushArgv(process.argv);
|
|
392
|
+
let slug = parsed.slug;
|
|
237
393
|
let _coldWake = false;
|
|
238
394
|
|
|
239
395
|
if (process.argv.includes('--help') || process.argv.includes('-h') || slug === 'help') {
|
|
240
|
-
console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
|
|
396
|
+
console.log('Usage: atris push [business] [file...] [--changed] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
|
|
241
397
|
console.log('');
|
|
242
398
|
console.log(' Push requires a fresh pull. If cloud has changed since your last pull,');
|
|
243
399
|
console.log(' the push will be blocked until you run `atris pull`. Use --force to override.');
|
|
244
400
|
console.log('');
|
|
245
401
|
console.log(' atris push Push from current folder (auto-detect business)');
|
|
246
402
|
console.log(' atris push example-co Push example-co/ or atris/example-co/');
|
|
403
|
+
console.log(' atris push atris/now.md Push only the named file(s)');
|
|
404
|
+
console.log(' atris push --changed Push only files that differ from the last-synced manifest');
|
|
247
405
|
console.log(' atris push example-co --only team/nate Only push files in team/nate/');
|
|
248
406
|
console.log(' atris push --force Bypass freshness check (force-push, may overwrite cloud changes)');
|
|
249
407
|
console.log(' atris push --delete Allow small cloud deletes shown by --dry-run');
|
|
@@ -251,6 +409,7 @@ async function pushAtris() {
|
|
|
251
409
|
console.log(' --allow-broad-workspace Publish a large unscoped workspace plan after review');
|
|
252
410
|
console.log(' --allow-nested-workspace Explicitly allow nested <slug>/ paths');
|
|
253
411
|
console.log(' --allow-sync-artifacts Explicitly allow *.remote/*.local/*.base/*.cloud files');
|
|
412
|
+
console.log(' --yes Skip interactive confirmation (e.g. force broad workspace)');
|
|
254
413
|
process.exit(0);
|
|
255
414
|
}
|
|
256
415
|
|
|
@@ -267,13 +426,15 @@ async function pushAtris() {
|
|
|
267
426
|
}
|
|
268
427
|
|
|
269
428
|
if (!slug || slug === '--help') {
|
|
270
|
-
console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
|
|
429
|
+
console.log('Usage: atris push [business] [file...] [--changed] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
|
|
271
430
|
console.log('');
|
|
272
431
|
console.log(' Push requires a fresh pull. If cloud has changed since your last pull,');
|
|
273
432
|
console.log(' the push will be blocked until you run `atris pull`. Use --force to override.');
|
|
274
433
|
console.log('');
|
|
275
434
|
console.log(' atris push Push from current folder (auto-detect business)');
|
|
276
435
|
console.log(' atris push example-co Push example-co/ or atris/example-co/');
|
|
436
|
+
console.log(' atris push atris/now.md Push only the named file(s)');
|
|
437
|
+
console.log(' atris push --changed Push only files that differ from the last-synced manifest');
|
|
277
438
|
console.log(' atris push example-co --only team/nate Only push files in team/nate/');
|
|
278
439
|
console.log(' atris push --force Bypass freshness check (force-push, may overwrite cloud changes)');
|
|
279
440
|
console.log(' atris push --delete Allow small cloud deletes shown by --dry-run');
|
|
@@ -281,11 +442,13 @@ async function pushAtris() {
|
|
|
281
442
|
console.log(' --allow-broad-workspace Publish a large unscoped workspace plan after review');
|
|
282
443
|
console.log(' --allow-nested-workspace Explicitly allow nested <slug>/ paths');
|
|
283
444
|
console.log(' --allow-sync-artifacts Explicitly allow *.remote/*.local/*.base/*.cloud files');
|
|
445
|
+
console.log(' --yes Skip interactive confirmation (e.g. force broad workspace)');
|
|
284
446
|
process.exit(0);
|
|
285
447
|
}
|
|
286
448
|
|
|
287
449
|
const force = process.argv.includes('--force');
|
|
288
450
|
const dryRun = process.argv.includes('--dry-run');
|
|
451
|
+
const yes = process.argv.includes('--yes');
|
|
289
452
|
const allowDelete = process.argv.includes('--delete');
|
|
290
453
|
const allowMassDelete = process.argv.includes('--delete-all');
|
|
291
454
|
const allowBroadWorkspace = process.argv.includes('--allow-broad-workspace');
|
|
@@ -293,24 +456,7 @@ async function pushAtris() {
|
|
|
293
456
|
const allowSyncArtifacts = process.argv.includes('--allow-sync-artifacts');
|
|
294
457
|
const allowCrossRootManifest = process.argv.includes('--allow-cross-root-manifest');
|
|
295
458
|
const timeoutSec = parsePushTimeoutSec(process.argv);
|
|
296
|
-
|
|
297
|
-
// Parse --only
|
|
298
|
-
let onlyRaw = null;
|
|
299
|
-
const onlyEq = process.argv.find(a => a.startsWith('--only='));
|
|
300
|
-
if (onlyEq) onlyRaw = onlyEq.slice(7);
|
|
301
|
-
else {
|
|
302
|
-
const oi = process.argv.indexOf('--only');
|
|
303
|
-
if (oi !== -1 && process.argv[oi + 1] && !process.argv[oi + 1].startsWith('-')) onlyRaw = process.argv[oi + 1];
|
|
304
|
-
}
|
|
305
|
-
let onlyPrefixes = onlyRaw
|
|
306
|
-
? onlyRaw.split(',').map(p => {
|
|
307
|
-
const wikiPrefix = normalizeWikiOnlyPrefix(p);
|
|
308
|
-
if (wikiPrefix) return `/${wikiPrefix.replace(/^\//, '')}`;
|
|
309
|
-
let n = '/' + p.replace(/^\//, '');
|
|
310
|
-
if (!n.endsWith('/') && !n.includes('.')) n += '/';
|
|
311
|
-
return n;
|
|
312
|
-
})
|
|
313
|
-
: null;
|
|
459
|
+
let onlyPrefixes = null;
|
|
314
460
|
|
|
315
461
|
const creds = loadCredentials();
|
|
316
462
|
if (!creds || !creds.token) { console.error('Not logged in. Run: atris login'); process.exit(1); }
|
|
@@ -328,6 +474,20 @@ async function pushAtris() {
|
|
|
328
474
|
// Refuse to walk/upload dangerous paths ($HOME, /, /Users, system dirs).
|
|
329
475
|
assertSafeWorkspaceRoot(sourceDir, { slug, op: 'push from' });
|
|
330
476
|
|
|
477
|
+
// Force-pushing a broad workspace is the most destructive push combo.
|
|
478
|
+
// Require an interactive y/N confirm unless the caller passed --yes.
|
|
479
|
+
if (needsForceBroadWorkspaceConfirm({ force, allowBroadWorkspace, yes, dryRun })) {
|
|
480
|
+
if (!process.stdin.isTTY) {
|
|
481
|
+
console.log(' --force --allow-broad-workspace requires an interactive terminal or --yes');
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
const ok = await promptConfirm('This will force push a broad workspace. Continue? (y/N) ');
|
|
485
|
+
if (!ok) {
|
|
486
|
+
console.log(' aborted');
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
331
491
|
// Push the whole business workspace by default. Brain-only sync must be
|
|
332
492
|
// explicit with --only atris/ so root workspace files cannot be missed.
|
|
333
493
|
|
|
@@ -412,6 +572,17 @@ async function pushAtris() {
|
|
|
412
572
|
return;
|
|
413
573
|
}
|
|
414
574
|
|
|
575
|
+
// Resolve scoped push filters before freshness so drift checks honor
|
|
576
|
+
// positional paths / --changed the same way --only already did.
|
|
577
|
+
const baseFiles = filterSyncFiles((manifest && manifest.files) ? manifest.files : {});
|
|
578
|
+
onlyPrefixes = mergePushScopes({
|
|
579
|
+
onlyRaw: parsed.onlyRaw,
|
|
580
|
+
pathArgs: parsed.pathArgs,
|
|
581
|
+
changed: parsed.changed,
|
|
582
|
+
localFiles,
|
|
583
|
+
baseFiles,
|
|
584
|
+
});
|
|
585
|
+
|
|
415
586
|
console.log('');
|
|
416
587
|
console.log(`Pushing to ${businessName}...`);
|
|
417
588
|
|
|
@@ -466,16 +637,7 @@ async function pushAtris() {
|
|
|
466
637
|
}
|
|
467
638
|
}
|
|
468
639
|
if (driftFiles.length > 0) {
|
|
469
|
-
|
|
470
|
-
console.log('');
|
|
471
|
-
console.log(` ✗ Cloud has changed since your last pull. Refusing to push stale state.`);
|
|
472
|
-
console.log('');
|
|
473
|
-
console.log(' Files that differ on cloud:');
|
|
474
|
-
driftFiles.slice(0, 8).forEach((p) => console.log(` ~ ${p.replace(/^\//, '')}`));
|
|
475
|
-
if (driftFiles.length > 8) console.log(` ... +${driftFiles.length - 8} more`);
|
|
476
|
-
console.log('');
|
|
477
|
-
console.log(' Run `atris pull` first, then push your changes.');
|
|
478
|
-
console.log(' To override (force-push, may clobber cloud edits): atris push --force');
|
|
640
|
+
process.stdout.write(renderDriftBlock(driftFiles));
|
|
479
641
|
await emit('drift', { error_detail: `${driftFiles.length} file(s) drifted` });
|
|
480
642
|
process.exit(1);
|
|
481
643
|
}
|
|
@@ -498,7 +660,11 @@ async function pushAtris() {
|
|
|
498
660
|
|
|
499
661
|
// Compare local hashes to manifest — NO server call needed
|
|
500
662
|
// Files where local hash differs from manifest = changed locally
|
|
501
|
-
|
|
663
|
+
if (parsed.changed && Array.isArray(onlyPrefixes) && onlyPrefixes.length === 0 && (!parsed.pathArgs || parsed.pathArgs.length === 0) && !parsed.onlyRaw) {
|
|
664
|
+
console.log('\n Already up to date.\n');
|
|
665
|
+
await emit('success', { files_unchanged: Object.keys(localFiles).length });
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
502
668
|
const { filesToPush, deletedPaths, unchangedCount } = buildPushChangePlan({
|
|
503
669
|
localFiles,
|
|
504
670
|
baseFiles,
|
|
@@ -667,64 +833,68 @@ async function pushAtris() {
|
|
|
667
833
|
|
|
668
834
|
if (filesToPush.length > 0) {
|
|
669
835
|
// Push files to server (strip leading slash — server requires workspace-relative paths)
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (
|
|
687
|
-
|
|
688
|
-
|
|
836
|
+
const uploadBatches = buildPushUploadBatches(filesToPush);
|
|
837
|
+
for (const batch of uploadBatches) {
|
|
838
|
+
result = await syncFiles(batch);
|
|
839
|
+
|
|
840
|
+
if (!result.ok) {
|
|
841
|
+
if (result.status === 403) {
|
|
842
|
+
const detail = result.errorMessage || result.error || (result.data && result.data.detail) || '';
|
|
843
|
+
if (detail && /plan required|business, max, or enterprise/i.test(detail)) {
|
|
844
|
+
console.error(`\n Access denied: ${detail}`);
|
|
845
|
+
await emit('access_denied', { error_detail: detail });
|
|
846
|
+
process.exit(1);
|
|
847
|
+
}
|
|
848
|
+
// Permission denied — retry with only team/ and journal/ files
|
|
849
|
+
const allowed = filesToPush.filter(f => f.path.startsWith('/team/') || f.path.startsWith('/journal/'));
|
|
850
|
+
skipped = filesToPush.filter(f => !f.path.startsWith('/team/') && !f.path.startsWith('/journal/'));
|
|
851
|
+
|
|
852
|
+
if (allowed.length > 0) {
|
|
853
|
+
const retry = await syncFiles(allowed);
|
|
854
|
+
if (retry.ok) {
|
|
855
|
+
recordSyncResults(allowed, retry);
|
|
856
|
+
pushed = landedPaths.size;
|
|
857
|
+
break;
|
|
858
|
+
} else {
|
|
859
|
+
console.error(`\n Push failed: ${retry.errorMessage || retry.error || retry.status}`);
|
|
860
|
+
await emit('access_denied', { error_detail: `403 retry failed: ${retry.status}` });
|
|
861
|
+
process.exit(1);
|
|
862
|
+
}
|
|
689
863
|
} else {
|
|
690
|
-
console.error(
|
|
691
|
-
await emit('access_denied'
|
|
864
|
+
console.error('\n Access denied: you can only push to your team/ folder.');
|
|
865
|
+
await emit('access_denied');
|
|
692
866
|
process.exit(1);
|
|
693
867
|
}
|
|
694
|
-
} else {
|
|
695
|
-
console.error('\n
|
|
696
|
-
await emit('
|
|
868
|
+
} else if (result.status === 409) {
|
|
869
|
+
console.error('\n Computer is sleeping. Wake it first.');
|
|
870
|
+
await emit('cold_wake', { error_detail: 'computer sleeping (409)' });
|
|
697
871
|
process.exit(1);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
path: f.path,
|
|
714
|
-
status: single.status || 'error',
|
|
715
|
-
error: single.errorMessage || single.error || '',
|
|
716
|
-
});
|
|
872
|
+
} else if (shouldRetrySyncIndividually(result, batch)) {
|
|
873
|
+
batchFailureDetail = `${result.status || 'unknown'}: ${result.errorMessage || result.error || 'batch sync failed'}`;
|
|
874
|
+
console.log('');
|
|
875
|
+
console.log(` Batch push failed (${result.errorMessage || result.error || result.status}). Retrying one file at a time...`);
|
|
876
|
+
for (const f of batch) {
|
|
877
|
+
const single = await syncFiles([f]);
|
|
878
|
+
if (single.ok) {
|
|
879
|
+
recordSyncResults([f], single);
|
|
880
|
+
} else {
|
|
881
|
+
failedToLand.push({
|
|
882
|
+
path: f.path,
|
|
883
|
+
status: single.status || 'error',
|
|
884
|
+
error: single.errorMessage || single.error || '',
|
|
885
|
+
});
|
|
886
|
+
}
|
|
717
887
|
}
|
|
888
|
+
pushed = landedPaths.size;
|
|
889
|
+
} else {
|
|
890
|
+
console.error(`\n Push failed: ${result.errorMessage || result.error || result.status}`);
|
|
891
|
+
await emit('status_unknown', { error_detail: `sync status ${result.status}` });
|
|
892
|
+
process.exit(1);
|
|
718
893
|
}
|
|
719
|
-
pushed = landedPaths.size;
|
|
720
894
|
} else {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
process.exit(1);
|
|
895
|
+
recordSyncResults(batch, result);
|
|
896
|
+
pushed = landedPaths.size;
|
|
724
897
|
}
|
|
725
|
-
} else {
|
|
726
|
-
recordSyncResults(filesToPush, result);
|
|
727
|
-
pushed = landedPaths.size;
|
|
728
898
|
}
|
|
729
899
|
}
|
|
730
900
|
|
|
@@ -882,6 +1052,7 @@ async function pushAtris() {
|
|
|
882
1052
|
module.exports = {
|
|
883
1053
|
pushAtris,
|
|
884
1054
|
buildPushChangePlan,
|
|
1055
|
+
buildPushUploadBatches,
|
|
885
1056
|
shouldRetrySyncIndividually,
|
|
886
1057
|
resolvePushSourceDir,
|
|
887
1058
|
canonicalWorkspaceRoot,
|
|
@@ -890,8 +1061,15 @@ module.exports = {
|
|
|
890
1061
|
isBusinessWorkspaceRoot,
|
|
891
1062
|
isMassDeletePlan,
|
|
892
1063
|
parsePushTimeoutSec,
|
|
1064
|
+
parsePushArgv,
|
|
1065
|
+
normalizePushScopePath,
|
|
1066
|
+
resolveChangedScopePaths,
|
|
1067
|
+
mergePushScopes,
|
|
893
1068
|
analyzePushSafety,
|
|
894
1069
|
isNestedWorkspacePollutionPath,
|
|
895
1070
|
isSyncReviewArtifactPath,
|
|
896
1071
|
renderPushSafetyBlock,
|
|
1072
|
+
renderDriftBlock,
|
|
1073
|
+
needsForceBroadWorkspaceConfirm,
|
|
1074
|
+
promptConfirm,
|
|
897
1075
|
};
|