@coderook/cli 0.31.7 → 0.31.8

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.
@@ -2,7 +2,7 @@
2
2
  "name": "coderook",
3
3
  "displayName": "CodeRook",
4
4
  "description": "Save, browse and restore whole-snapshot versions of a project on CodeRook, from Claude Code.",
5
- "version": "0.31.7",
5
+ "version": "0.31.8",
6
6
  "author": {
7
7
  "name": "ACCA Gaming Productions",
8
8
  "url": "https://coderook.com"
@@ -19,6 +19,7 @@ const node_path_1 = __importDefault(require("node:path"));
19
19
  const faults_js_1 = require("./faults.js");
20
20
  const identify_js_1 = require("./identify.js");
21
21
  const worktree_js_1 = require("./worktree.js");
22
+ const rules_js_1 = require("./rules.js");
22
23
  const publish_name_js_1 = require("./publish_name.js");
23
24
  const compress_js_1 = require("./compress.js");
24
25
  const compression_policy_js_1 = require("../shared/compression_policy.js");
@@ -142,6 +143,8 @@ class Uploader {
142
143
  */
143
144
  plannedLayouts = new Map();
144
145
  plannedQuote = null;
146
+ /** Files sent only because git tracks them; named in the save so the service allows them. */
147
+ trackedByGit = [];
145
148
  /*
146
149
  Digests worked out while planning, so sending does not work them out again.
147
150
 
@@ -407,6 +410,13 @@ class Uploader {
407
410
  const rules = request.allowIgnored
408
411
  ? { shared: "", local: "" }
409
412
  : await (0, worktree_js_1.readRules)(request.localPath);
413
+ /*
414
+ What the survey keeps only because git tracks it, named for the
415
+ service: its own check of the rules would otherwise refuse them as a
416
+ rule this client forgot. See gitTracked.
417
+ */
418
+ this.trackedByGit = [];
419
+ const tracked = request.allowIgnored ? null : await (0, worktree_js_1.gitTracked)(request.localPath);
410
420
  /*
411
421
  Surveyed, not listed.
412
422
 
@@ -443,6 +453,12 @@ class Uploader {
443
453
  percent: 6,
444
454
  bytesPerSecond: 0,
445
455
  })));
456
+ if (tracked) {
457
+ const layers = await (0, worktree_js_1.collectLayers)(request.localPath, rules);
458
+ this.trackedByGit = everything
459
+ .map((file) => file.path)
460
+ .filter((file) => tracked.files.has(file) && (0, rules_js_1.excludes)(file, false, layers));
461
+ }
446
462
  (0, profile_js_1.counted)("files surveyed", everything.length);
447
463
  const unticked = new Set(request.excluded ?? []);
448
464
  const ticked = request.excluded
@@ -854,6 +870,7 @@ class Uploader {
854
870
  : { expectedHeadVersionId: request.expectedHeadVersionId }),
855
871
  ...(request.track ? { track: request.track } : {}),
856
872
  ...(request.allowIgnored ? { allowIgnored: true } : {}),
873
+ ...(this.trackedByGit.length ? { trackedByGit: this.trackedByGit } : {}),
857
874
  ...(request.allowSecrets ? { allowSecrets: true } : {}),
858
875
  ...(request.linesAdded === undefined ? {} : { linesAdded: request.linesAdded }),
859
876
  ...(request.linesRemoved === undefined
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.IGNORE_HEADING = exports.KEEP_HEADING = exports.LOCAL_EXCLUDE_FILE = exports.IGNORE_FILE = exports.STARTER_IGNORE = exports.CODEROOK_TAIL = exports.CODEROOK_TAIL_HEADING = exports.KEPT_ANYWAY = exports.KEPT_ANYWAY_HEADING = exports.ALSO_LEFT_OUT = exports.ALSO_LEFT_OUT_HEADING = exports.STARTER_MARKER = exports.EVALUATION_FILE_LIMIT = void 0;
6
+ exports.IGNORE_HEADING = exports.KEEP_HEADING = exports.TRACKED_BY_GIT = exports.LOCAL_EXCLUDE_FILE = exports.IGNORE_FILE = exports.STARTER_IGNORE = exports.CODEROOK_TAIL = exports.CODEROOK_TAIL_HEADING = exports.KEPT_ANYWAY = exports.KEPT_ANYWAY_HEADING = exports.ALSO_LEFT_OUT = exports.ALSO_LEFT_OUT_HEADING = exports.STARTER_MARKER = exports.EVALUATION_FILE_LIMIT = void 0;
7
7
  exports.ruleTarget = ruleTarget;
8
8
  exports.keptTargets = keptTargets;
9
9
  exports.withoutOpposites = withoutOpposites;
@@ -11,6 +11,7 @@ exports.starterAuthored = starterAuthored;
11
11
  exports.missingStarterRules = missingStarterRules;
12
12
  exports.missingEngineRules = missingEngineRules;
13
13
  exports.isRepository = isRepository;
14
+ exports.gitTracked = gitTracked;
14
15
  exports.migrateRetiredRules = migrateRetiredRules;
15
16
  exports.starterFor = starterFor;
16
17
  exports.moveRule = moveRule;
@@ -404,6 +405,39 @@ async function isRepository(root) {
404
405
  return false;
405
406
  }
406
407
  }
408
+ /** The label a file kept because git tracks it carries, where a rule would be. */
409
+ exports.TRACKED_BY_GIT = "(tracked by git)";
410
+ /**
411
+ * The files git tracks here, which an ignore rule does not take away.
412
+ *
413
+ * Git applies .gitignore only to files it does not track yet. Once a file is
414
+ * committed, a rule written later — or a broad one like `storage/` that also
415
+ * matches a source folder three levels down — leaves it exactly where it is.
416
+ * This applied the rules to everything, so a project whose .gitignore said
417
+ * `storage/` arrived without `internal/storage/*.go`: in git, whole; on
418
+ * CodeRook, missing the six files that made it build.
419
+ *
420
+ * Null when the folder is not a repository, or git cannot be asked — then the
421
+ * rules alone decide, as they always did.
422
+ */
423
+ async function gitTracked(root) {
424
+ if (!(await isRepository(root)))
425
+ return null;
426
+ const listed = await git(root, "ls-files", "-z");
427
+ if (listed === null)
428
+ return null;
429
+ const files = new Set(listed.split(String.fromCharCode(0)).filter(Boolean));
430
+ const folders = new Set();
431
+ for (const file of files) {
432
+ for (let cut = file.lastIndexOf("/"); cut > 0; cut = file.lastIndexOf("/", cut - 1)) {
433
+ const folder = file.slice(0, cut);
434
+ if (folders.has(folder))
435
+ break;
436
+ folders.add(folder);
437
+ }
438
+ }
439
+ return { files, folders };
440
+ }
407
441
  /**
408
442
  * Convert the rules an earlier build wrote into one .gitignore.
409
443
  *
@@ -519,6 +553,15 @@ async function moveRule(root, file, keep) {
519
553
  const directory = await (0, promises_1.stat)(node_path_1.default.join(root, bare))
520
554
  .then((entry) => entry.isDirectory())
521
555
  .catch(() => file.endsWith("/"));
556
+ /*
557
+ A file git tracks cannot be ignored by a rule — git keeps it, and so does
558
+ this. Writing the rule anyway would look like it worked and change
559
+ nothing, so it is refused with what would actually leave it out.
560
+ */
561
+ if (!keep && !directory && (await gitTracked(root))?.files.has(bare)) {
562
+ throw new Error(`git tracks ${bare}, so a rule cannot leave it out. Untick it for one ` +
563
+ `save, or stop git tracking it: git rm --cached "${bare}"`);
564
+ }
522
565
  const target = directory ? `${bare}/` : bare;
523
566
  const line = keep ? `!${target}` : target;
524
567
  /*
@@ -885,6 +928,8 @@ stats,
885
928
  /** Filled with how much of the folder the cap allowed through. */
886
929
  outcome) {
887
930
  const layers = await collectLayers(root, rules);
931
+ /* What git already tracks goes up whatever a rule says; see gitTracked. */
932
+ const tracked = await gitTracked(root);
888
933
  const files = [];
889
934
  const present = new Set();
890
935
  const pending = [root];
@@ -926,7 +971,9 @@ outcome) {
926
971
  // Skipping an excluded directory outright is what keeps this fast,
927
972
  // but a `!` rule exists to rescue what is inside one, so the subtree
928
973
  // is only skipped when no negation could reach into it.
929
- if ((0, rules_js_1.excludes)(relative, true, layers) && !(0, rules_js_1.negationReachesInto)(relative, layers)) {
974
+ if ((0, rules_js_1.excludes)(relative, true, layers) &&
975
+ !(0, rules_js_1.negationReachesInto)(relative, layers) &&
976
+ !tracked?.folders.has(relative)) {
930
977
  continue;
931
978
  }
932
979
  pending.push(full);
@@ -934,7 +981,7 @@ outcome) {
934
981
  }
935
982
  if (!entry.isFile())
936
983
  continue;
937
- if ((0, rules_js_1.excludes)(relative, false, layers))
984
+ if ((0, rules_js_1.excludes)(relative, false, layers) && !tracked?.files.has(relative))
938
985
  continue;
939
986
  wanted.push({ full, relative });
940
987
  }
@@ -1060,6 +1107,7 @@ async function surveyFiles(root, rules,
1060
1107
  */
1061
1108
  onFound) {
1062
1109
  const layers = await collectLayers(root, rules);
1110
+ const tracked = await gitTracked(root);
1063
1111
  const found = [];
1064
1112
  /*
1065
1113
  A level at a time, eight in flight.
@@ -1094,7 +1142,8 @@ onFound) {
1094
1142
  const relative = node_path_1.default.relative(root, full).split(node_path_1.default.sep).join("/");
1095
1143
  if (entry.isDirectory()) {
1096
1144
  if ((0, rules_js_1.excludes)(relative, true, layers) &&
1097
- !(0, rules_js_1.negationReachesInto)(relative, layers)) {
1145
+ !(0, rules_js_1.negationReachesInto)(relative, layers) &&
1146
+ !tracked?.folders.has(relative)) {
1098
1147
  continue;
1099
1148
  }
1100
1149
  next.push(full);
@@ -1102,7 +1151,7 @@ onFound) {
1102
1151
  }
1103
1152
  if (!entry.isFile())
1104
1153
  continue;
1105
- if ((0, rules_js_1.excludes)(relative, false, layers))
1154
+ if ((0, rules_js_1.excludes)(relative, false, layers) && !tracked?.files.has(relative))
1106
1155
  continue;
1107
1156
  here.push({ full, relative });
1108
1157
  }
@@ -1408,6 +1457,7 @@ function relativeTo(root, full) {
1408
1457
  */
1409
1458
  async function evaluateRules(root, rules, options = {}) {
1410
1459
  const layers = await collectLayers(root, rules);
1460
+ const tracked = await gitTracked(root);
1411
1461
  const impactBytes = new Map();
1412
1462
  const impactFiles = new Map();
1413
1463
  const excluded = [];
@@ -1449,6 +1499,18 @@ async function evaluateRules(root, rules, options = {}) {
1449
1499
  });
1450
1500
  continue;
1451
1501
  }
1502
+ if (rule && tracked?.files.has(relative)) {
1503
+ /*
1504
+ A rule matches it, and git tracks it, so it goes up regardless — and
1505
+ is listed with what kept it, beside the files a `!` rule keeps.
1506
+ */
1507
+ keptFiles += 1;
1508
+ keptBytes += size;
1509
+ uploadFiles += 1;
1510
+ uploadBytes += size;
1511
+ excluded.push({ path: relative, bytes: size, rule: exports.TRACKED_BY_GIT, forceKept: true });
1512
+ continue;
1513
+ }
1452
1514
  if (rule) {
1453
1515
  skippedFiles += 1;
1454
1516
  skippedBytes += size;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coderook/cli",
3
- "version": "0.31.7",
3
+ "version": "0.31.8",
4
4
  "description": "CodeRook from the command line, on any operating system",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "homepage": "https://coderook.com",