atris 3.15.12 → 3.15.13

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/commands/pull.js CHANGED
@@ -566,11 +566,35 @@ async function pullBusiness(slug) {
566
566
  // Compute local file hashes
567
567
  const localFiles = localFilesBeforePull;
568
568
 
569
+ function isPullPathInScope(filePath) {
570
+ if (!onlyPrefixes) return true;
571
+ const rel = filePath.replace(/^\//, '');
572
+ return onlyPrefixes.some((pref) => rel.startsWith(pref));
573
+ }
574
+
575
+ function filterFilesToPullScope(filesMap = {}) {
576
+ if (!onlyPrefixes) return filesMap;
577
+ return Object.fromEntries(
578
+ Object.entries(filesMap).filter(([p]) => isPullPathInScope(p))
579
+ );
580
+ }
581
+
582
+ function filterManifestToPullScope(existingManifest) {
583
+ if (!onlyPrefixes || !existingManifest || !existingManifest.files) return existingManifest;
584
+ return {
585
+ ...existingManifest,
586
+ files: filterFilesToPullScope(existingManifest.files),
587
+ };
588
+ }
589
+
569
590
  // If output dir is empty (fresh clone) or --force, treat as first sync — pull everything
570
- const effectiveManifest = (Object.keys(localFiles).length === 0 || force) ? null : manifest;
591
+ const scopedLocalFiles = filterFilesToPullScope(localFiles);
592
+ const scopedRemoteFiles = filterFilesToPullScope(remoteFiles);
593
+ const scopedManifest = filterManifestToPullScope(manifest);
594
+ const effectiveManifest = (Object.keys(scopedLocalFiles).length === 0 || force) ? null : scopedManifest;
571
595
 
572
596
  // Three-way compare
573
- const diff = threeWayCompare(localFiles, remoteFiles, effectiveManifest);
597
+ const diff = threeWayCompare(scopedLocalFiles, scopedRemoteFiles, effectiveManifest);
574
598
 
575
599
  if (dryRun) {
576
600
  console.log('');
package/commands/push.js CHANGED
@@ -95,6 +95,15 @@ function shouldRetrySyncIndividually(result, filesToPush) {
95
95
  return result.status !== 403 && result.status !== 409;
96
96
  }
97
97
 
98
+ function isMassDeletePlan({ deletedPaths = [], filesToPush = [], unchangedCount = 0 } = {}) {
99
+ const deleteCount = deletedPaths.length;
100
+ if (deleteCount === 0) return false;
101
+ const survivingCount = filesToPush.length + unchangedCount;
102
+ if (deleteCount >= 10 && survivingCount === 0) return true;
103
+ if (deleteCount >= 25) return true;
104
+ return deleteCount >= 10 && deleteCount > survivingCount;
105
+ }
106
+
98
107
  async function pushAtris() {
99
108
  const elapsedMs = startTimer();
100
109
  let slug = process.argv[3];
@@ -113,7 +122,7 @@ async function pushAtris() {
113
122
  }
114
123
 
115
124
  if (!slug || slug === '--help') {
116
- console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete]');
125
+ console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
117
126
  console.log('');
118
127
  console.log(' Push requires a fresh pull. If cloud has changed since your last pull,');
119
128
  console.log(' the push will be blocked until you run `atris pull`. Use --force to override.');
@@ -122,13 +131,15 @@ async function pushAtris() {
122
131
  console.log(' atris push pallet Push pallet/ or atris/pallet/');
123
132
  console.log(' atris push pallet --only team/nate Only push files in team/nate/');
124
133
  console.log(' atris push --force Bypass freshness check (force-push, may overwrite cloud changes)');
125
- console.log(' atris push --delete Allow cloud deletes shown by --dry-run');
134
+ console.log(' atris push --delete Allow small cloud deletes shown by --dry-run');
135
+ console.log(' atris push --delete-all Extra confirmation for mass-delete recovery');
126
136
  process.exit(0);
127
137
  }
128
138
 
129
139
  const force = process.argv.includes('--force');
130
140
  const dryRun = process.argv.includes('--dry-run');
131
141
  const allowDelete = process.argv.includes('--delete');
142
+ const allowMassDelete = process.argv.includes('--delete-all');
132
143
  const allowCrossRootManifest = process.argv.includes('--allow-cross-root-manifest');
133
144
 
134
145
  // Parse --only
@@ -390,6 +401,24 @@ async function pushAtris() {
390
401
  process.exit(1);
391
402
  }
392
403
 
404
+ if (deletedPaths.length > 0 && allowDelete && !allowMassDelete && isMassDeletePlan({ deletedPaths, filesToPush, unchangedCount })) {
405
+ console.log('');
406
+ console.log(` ✗ Refusing mass delete of ${deletedPaths.length} cloud files with --delete alone.`);
407
+ console.log('');
408
+ console.log(' This looks like a missing local folder or wrong source root.');
409
+ console.log(' No cloud files were deleted.');
410
+ console.log('');
411
+ console.log(' First inspect the plan:');
412
+ console.log(` atris push ${resolvedSlug || slug} --dry-run`);
413
+ console.log('');
414
+ console.log(' If this is truly an intentional full cleanup, rerun with both flags:');
415
+ console.log(` atris push ${resolvedSlug || slug} --delete --delete-all`);
416
+ console.log('');
417
+ console.log(' If this is surprising, pull cloud truth first:');
418
+ console.log(` atris pull ${resolvedSlug || slug} --keep-local --timeout 120`);
419
+ process.exit(1);
420
+ }
421
+
393
422
  let pushed = 0;
394
423
  let deleted = 0;
395
424
  let skipped = [];
@@ -682,4 +711,5 @@ module.exports = {
682
711
  canonicalWorkspaceRoot,
683
712
  basenameOfManifestPath,
684
713
  isBusinessWorkspaceRoot,
714
+ isMassDeletePlan,
685
715
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.15.12",
3
+ "version": "3.15.13",
4
4
  "description": "Atris — an operating system for intelligence. Integrates with any agent.",
5
5
  "main": "bin/atris.js",
6
6
  "bin": {