prreviewbuddy 0.81.0 → 0.82.1
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/CHANGELOG.md +33 -0
- package/dist/{delete_review-CFVFygqb.js → delete_review-BED4mPxB.js} +333 -23
- package/dist/main.js +1 -1
- package/dist/server.js +554 -36
- package/package.json +1 -1
- package/static/chunk-CQNkZ9TG.js +1 -0
- package/static/{chunk-DnOyRP8m.js → chunk-CWRKWlhO.js} +1 -1
- package/static/{chunk-C3E91izb.js → chunk-CxFmUaBj.js} +1 -1
- package/static/help.js +1 -1
- package/static/notifications.js +1 -1
- package/static/reviews.js +1 -1
- package/static/runs.js +1 -1
- package/static/settings.js +1 -1
- package/static/shell.css +128 -0
- package/static/workspace.js +79 -79
- package/static/chunk-C8d9FvLk.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.82.1
|
|
4
|
+
|
|
5
|
+
**Back no longer draws a focus ring you never had.** Going Back or Forward puts focus where you
|
|
6
|
+
left it, and the browser drew its keyboard ring there even when you had clicked with the mouse.
|
|
7
|
+
Now the ring comes back only if it was showing when you left, so a mouse reader returns to a
|
|
8
|
+
quiet page and a keyboard reader still sees where they are. This holds on the reviews list, a
|
|
9
|
+
review, Runs and Activity. Places saved by earlier versions come back without the ring. This needs
|
|
10
|
+
Chrome or Edge 145, Firefox 104 or Safari 18.4; older versions still put focus back, with the ring
|
|
11
|
+
as before.
|
|
12
|
+
|
|
13
|
+
## 0.82.0
|
|
14
|
+
|
|
15
|
+
**Collections.** Group reviews that belong together, such as the pull requests of one refactor or
|
|
16
|
+
one release, into collections on the Reviews page. Choose New collection, or tick reviews with Select
|
|
17
|
+
and choose Manage collections, which can also make a collection by name. A card's menu has Manage
|
|
18
|
+
collections too. A review can be in several collections, and adding a selection to one leaves every
|
|
19
|
+
other collection it is in alone. Where some of a selection is already in a collection, its box is
|
|
20
|
+
part-filled, and ticking it adds the rest.
|
|
21
|
+
|
|
22
|
+
Collections sit between Pinned and Active, each folding like a section and remembered across
|
|
23
|
+
reloads. Each lists its reviews that still need attention, with a count in its heading. A review
|
|
24
|
+
marked Done moves under Show completed, in every collection it is in, and comes back when it is
|
|
25
|
+
moved to Active. Reviews in a collection are listed there instead of under Active, Waiting or Done.
|
|
26
|
+
A pinned review stays under Pinned, and its collections count it. Under the Queue filter the list is
|
|
27
|
+
the queue as before, with no collections drawn. A new Collections filter shows chosen collections,
|
|
28
|
+
or Uncollected for reviews in none, and Clear filters resets it. Collections can be renamed and
|
|
29
|
+
deleted from their own menu; deleting one never deletes its reviews.
|
|
30
|
+
|
|
31
|
+
Reviews in a collection are kept for ninety days, like pinned and Done reviews, counted from when you
|
|
32
|
+
last opened or marked them. Adding one to a collection doesn't restart that count. Collections are
|
|
33
|
+
stored on your machine, in `~/.prreviewbuddy/collections.json`, and have nothing to do with GitHub
|
|
34
|
+
labels or projects.
|
|
35
|
+
|
|
3
36
|
## 0.81.0
|
|
4
37
|
|
|
5
38
|
**Gemini CLI is turned off.** PR Review Buddy no longer runs reviews, updates, Buddy AI answers,
|
|
@@ -5459,7 +5459,7 @@ var QUEUE_LOCK = "queue";
|
|
|
5459
5459
|
* `version` is not branched on today. It exists so that a later shape change is a decision rather
|
|
5460
5460
|
* than a guess about what an old file meant.
|
|
5461
5461
|
*/
|
|
5462
|
-
var VERSION$
|
|
5462
|
+
var VERSION$2 = 1;
|
|
5463
5463
|
/**
|
|
5464
5464
|
* What is queued, in order.
|
|
5465
5465
|
*
|
|
@@ -5473,7 +5473,7 @@ function readQueue() {
|
|
|
5473
5473
|
const parsed = JSON.parse(readFileSync(QUEUE_PATH, "utf8"));
|
|
5474
5474
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
|
|
5475
5475
|
const ids = parsed.ids;
|
|
5476
|
-
return Array.isArray(ids) ? normalise(ids) : [];
|
|
5476
|
+
return Array.isArray(ids) ? normalise$1(ids) : [];
|
|
5477
5477
|
} catch {
|
|
5478
5478
|
return [];
|
|
5479
5479
|
}
|
|
@@ -5519,10 +5519,10 @@ function updateQueue(change) {
|
|
|
5519
5519
|
* processes never share one, although under the lock they never write at once anyway.
|
|
5520
5520
|
*/
|
|
5521
5521
|
function writeQueueUnlocked(ids) {
|
|
5522
|
-
const next = normalise(ids);
|
|
5522
|
+
const next = normalise$1(ids);
|
|
5523
5523
|
mkdirSync(STORE_ROOT, { recursive: true });
|
|
5524
5524
|
const stored = {
|
|
5525
|
-
version: VERSION$
|
|
5525
|
+
version: VERSION$2,
|
|
5526
5526
|
ids: next
|
|
5527
5527
|
};
|
|
5528
5528
|
const temporary = `${QUEUE_PATH}.${process.pid}.tmp`;
|
|
@@ -5587,7 +5587,7 @@ function queueStamp() {
|
|
|
5587
5587
|
}
|
|
5588
5588
|
}
|
|
5589
5589
|
/** Strings only, first position wins, and never more than the cap. */
|
|
5590
|
-
function normalise(ids) {
|
|
5590
|
+
function normalise$1(ids) {
|
|
5591
5591
|
const seen = [];
|
|
5592
5592
|
for (const id of ids) {
|
|
5593
5593
|
if (typeof id !== "string" || id === "" || seen.includes(id)) continue;
|
|
@@ -5597,6 +5597,268 @@ function normalise(ids) {
|
|
|
5597
5597
|
return seen;
|
|
5598
5598
|
}
|
|
5599
5599
|
//#endregion
|
|
5600
|
+
//#region ../../packages/review-harness/src/workspace/collections.ts
|
|
5601
|
+
/**
|
|
5602
|
+
* Collections: the reviewer's own groups of reviews, such as everything in one refactor.
|
|
5603
|
+
*
|
|
5604
|
+
* A collection says what belongs together and nothing else. Whether a member still needs attention
|
|
5605
|
+
* is its lifecycle, which this file never reads or writes: assigning, removing, renaming and
|
|
5606
|
+
* deleting all leave every review's state exactly where it was.
|
|
5607
|
+
*
|
|
5608
|
+
* **Members are lineage keys, not review ids.** A card is a branch, and analysing the branch again
|
|
5609
|
+
* makes a new record with a new id. The queue stores ids and has to forward them (`forwardQueue`);
|
|
5610
|
+
* a lineage key is already what the card stands for, so membership survives updates, re-analysis
|
|
5611
|
+
* and restarts with nothing to keep in step.
|
|
5612
|
+
*
|
|
5613
|
+
* **Its own file**, for the reasons `queue.ts` gives: a record is rewritten whole by whoever loaded
|
|
5614
|
+
* it last and carries its diff inline, and `ui.json` is best-effort. And because a write here never
|
|
5615
|
+
* touches a record, it never moves the record's mtime, which is the clock retention runs on.
|
|
5616
|
+
* Organising a review is not opening or marking it.
|
|
5617
|
+
*
|
|
5618
|
+
* `STORE_ROOT` from `store_root.ts` rather than `store.ts`, for the import cycle `queue.ts` explains:
|
|
5619
|
+
* the prune in `store.ts` reads this file.
|
|
5620
|
+
*/
|
|
5621
|
+
var COLLECTIONS_PATH = join(STORE_ROOT, "collections.json");
|
|
5622
|
+
/** The lock's name. Reviews are locked under UUIDs in the same directory, so a word cannot collide. */
|
|
5623
|
+
var COLLECTIONS_LOCK = "collections";
|
|
5624
|
+
var VERSION$1 = 1;
|
|
5625
|
+
var ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
5626
|
+
/** Whether a value is shaped like a collection's id, which is what a fold key and a route accept. */
|
|
5627
|
+
function isCollectionId(value) {
|
|
5628
|
+
return typeof value === "string" && ID_PATTERN.test(value);
|
|
5629
|
+
}
|
|
5630
|
+
/**
|
|
5631
|
+
* Every collection, in the order they were created.
|
|
5632
|
+
*
|
|
5633
|
+
* Best-effort like `readQueue`: missing, unreadable and corrupt all mean "no collections". An entry
|
|
5634
|
+
* this code did not write is dropped rather than guessed at, and so is a second entry claiming an id
|
|
5635
|
+
* or a name already taken, so the rules `createCollection` keeps hold for a hand-edited file too.
|
|
5636
|
+
*/
|
|
5637
|
+
function readCollections() {
|
|
5638
|
+
try {
|
|
5639
|
+
const parsed = JSON.parse(readFileSync(COLLECTIONS_PATH, "utf8"));
|
|
5640
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
|
|
5641
|
+
const collections = parsed.collections;
|
|
5642
|
+
return Array.isArray(collections) ? normalise(collections) : [];
|
|
5643
|
+
} catch {
|
|
5644
|
+
return [];
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
function normalise(raw) {
|
|
5648
|
+
const kept = [];
|
|
5649
|
+
for (const entry of raw) {
|
|
5650
|
+
if (kept.length >= 100) break;
|
|
5651
|
+
if (!entry || typeof entry !== "object") continue;
|
|
5652
|
+
const { id, name, colour, createdAt, members } = entry;
|
|
5653
|
+
const clean = typeof name === "string" ? cleanName(name) : "";
|
|
5654
|
+
if (!isCollectionId(id) || clean === "" || clean.length > 60) continue;
|
|
5655
|
+
if (typeof createdAt !== "number" || !Number.isFinite(createdAt)) continue;
|
|
5656
|
+
if (kept.some((other) => other.id === id || sameName(other.name, clean))) continue;
|
|
5657
|
+
const keys = Array.isArray(members) ? members.filter((key) => typeof key === "string" && key !== "") : [];
|
|
5658
|
+
kept.push({
|
|
5659
|
+
id,
|
|
5660
|
+
name: clean,
|
|
5661
|
+
...typeof colour === "string" && colour !== "" ? { colour } : {},
|
|
5662
|
+
createdAt,
|
|
5663
|
+
members: [...new Set(keys)].slice(0, 500)
|
|
5664
|
+
});
|
|
5665
|
+
}
|
|
5666
|
+
return kept;
|
|
5667
|
+
}
|
|
5668
|
+
/** Spaces trimmed and runs of them collapsed, so " Release 0.82 " is "Release 0.82". */
|
|
5669
|
+
function cleanName(name) {
|
|
5670
|
+
return name.trim().replace(/\s+/g, " ");
|
|
5671
|
+
}
|
|
5672
|
+
/** Two names are one collection's name regardless of case: "refactor" is not a second Refactor. */
|
|
5673
|
+
function sameName(a, b) {
|
|
5674
|
+
return a.localeCompare(b, void 0, { sensitivity: "base" }) === 0;
|
|
5675
|
+
}
|
|
5676
|
+
function nameProblem(name, collections, except) {
|
|
5677
|
+
const clean = cleanName(name);
|
|
5678
|
+
if (clean === "") return "empty";
|
|
5679
|
+
if (clean.length > 60) return "too-long";
|
|
5680
|
+
if (collections.some((collection) => collection.id !== except && sameName(collection.name, clean))) return "duplicate";
|
|
5681
|
+
}
|
|
5682
|
+
/** What a refused change says, in the words the page shows. */
|
|
5683
|
+
function nameProblemText(problem) {
|
|
5684
|
+
if (problem === "empty") return "Give the collection a name.";
|
|
5685
|
+
if (problem === "too-long") return `Keep the name under 61 characters.`;
|
|
5686
|
+
return "A collection with that name already exists.";
|
|
5687
|
+
}
|
|
5688
|
+
/**
|
|
5689
|
+
* Change the collections as they are on disk now, under the lock: the only way they are written.
|
|
5690
|
+
* Same contract as `updateQueue`: `change` returns what to store or null for nothing, and null from
|
|
5691
|
+
* here means the write failed.
|
|
5692
|
+
*/
|
|
5693
|
+
function updateCollections(change) {
|
|
5694
|
+
try {
|
|
5695
|
+
return withWriteLock(COLLECTIONS_LOCK, () => {
|
|
5696
|
+
const current = readCollections();
|
|
5697
|
+
const next = change(current);
|
|
5698
|
+
return next === null ? current : writeUnlocked(next);
|
|
5699
|
+
});
|
|
5700
|
+
} catch {
|
|
5701
|
+
return null;
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
/** To a temporary file and renamed into place, for the reason `writeQueueUnlocked` gives. */
|
|
5705
|
+
function writeUnlocked(collections) {
|
|
5706
|
+
const next = normalise(collections);
|
|
5707
|
+
mkdirSync(STORE_ROOT, { recursive: true });
|
|
5708
|
+
const stored = {
|
|
5709
|
+
version: VERSION$1,
|
|
5710
|
+
collections: next
|
|
5711
|
+
};
|
|
5712
|
+
const temporary = `${COLLECTIONS_PATH}.${process.pid}.tmp`;
|
|
5713
|
+
try {
|
|
5714
|
+
writeFileSync(temporary, `${JSON.stringify(stored, null, 2)}\n`);
|
|
5715
|
+
renameSync(temporary, COLLECTIONS_PATH);
|
|
5716
|
+
} catch (error) {
|
|
5717
|
+
try {
|
|
5718
|
+
unlinkSync(temporary);
|
|
5719
|
+
} catch {}
|
|
5720
|
+
throw error;
|
|
5721
|
+
}
|
|
5722
|
+
return next;
|
|
5723
|
+
}
|
|
5724
|
+
function run$1(change) {
|
|
5725
|
+
const outcome = {};
|
|
5726
|
+
const stored = updateCollections((current) => {
|
|
5727
|
+
const result = change(current);
|
|
5728
|
+
if ("refused" in result) {
|
|
5729
|
+
outcome.refused = result.refused;
|
|
5730
|
+
return null;
|
|
5731
|
+
}
|
|
5732
|
+
outcome.id = result.id;
|
|
5733
|
+
return result.next;
|
|
5734
|
+
});
|
|
5735
|
+
if (outcome.refused !== void 0) return {
|
|
5736
|
+
ok: false,
|
|
5737
|
+
reason: outcome.refused
|
|
5738
|
+
};
|
|
5739
|
+
if (stored === null) return {
|
|
5740
|
+
ok: false,
|
|
5741
|
+
reason: "unsaved"
|
|
5742
|
+
};
|
|
5743
|
+
return {
|
|
5744
|
+
ok: true,
|
|
5745
|
+
collections: stored,
|
|
5746
|
+
...outcome.id === void 0 ? {} : { id: outcome.id }
|
|
5747
|
+
};
|
|
5748
|
+
}
|
|
5749
|
+
function createCollection(name, now = Date.now()) {
|
|
5750
|
+
return run$1((current) => {
|
|
5751
|
+
const problem = nameProblem(name, current);
|
|
5752
|
+
if (problem) return { refused: problem };
|
|
5753
|
+
if (current.length >= 100) return { refused: "full" };
|
|
5754
|
+
const id = randomUUID();
|
|
5755
|
+
return {
|
|
5756
|
+
next: [...current, {
|
|
5757
|
+
id,
|
|
5758
|
+
name: cleanName(name),
|
|
5759
|
+
createdAt: now,
|
|
5760
|
+
members: []
|
|
5761
|
+
}],
|
|
5762
|
+
id
|
|
5763
|
+
};
|
|
5764
|
+
});
|
|
5765
|
+
}
|
|
5766
|
+
/**
|
|
5767
|
+
* A new collection holding these lineages from the start: one transaction, so it is made with all of
|
|
5768
|
+
* them or not at all. "Create Refactor and add these three" never leaves an empty Refactor behind a
|
|
5769
|
+
* failure, and a retry never meets a duplicate name of its own making.
|
|
5770
|
+
*/
|
|
5771
|
+
function createWithMembers(name, keys, now = Date.now()) {
|
|
5772
|
+
const members = [...new Set(keys.filter((key) => key !== ""))];
|
|
5773
|
+
return run$1((current) => {
|
|
5774
|
+
const problem = nameProblem(name, current);
|
|
5775
|
+
if (problem) return { refused: problem };
|
|
5776
|
+
if (current.length >= 100 || members.length > 500) return { refused: "full" };
|
|
5777
|
+
const id = randomUUID();
|
|
5778
|
+
return {
|
|
5779
|
+
next: [...current, {
|
|
5780
|
+
id,
|
|
5781
|
+
name: cleanName(name),
|
|
5782
|
+
createdAt: now,
|
|
5783
|
+
members
|
|
5784
|
+
}],
|
|
5785
|
+
id
|
|
5786
|
+
};
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
/** A new name on the same collection: its id, its members and every fold kept against the id stay. */
|
|
5790
|
+
function renameCollection(id, name) {
|
|
5791
|
+
return run$1((current) => {
|
|
5792
|
+
if (!current.some((collection) => collection.id === id)) return { refused: "missing" };
|
|
5793
|
+
const problem = nameProblem(name, current, id);
|
|
5794
|
+
if (problem) return { refused: problem };
|
|
5795
|
+
return {
|
|
5796
|
+
next: current.map((collection) => collection.id === id ? {
|
|
5797
|
+
...collection,
|
|
5798
|
+
name: cleanName(name)
|
|
5799
|
+
} : collection),
|
|
5800
|
+
id
|
|
5801
|
+
};
|
|
5802
|
+
});
|
|
5803
|
+
}
|
|
5804
|
+
/** The collection and its memberships go. No review is touched: they were never inside it. */
|
|
5805
|
+
function deleteCollection(id) {
|
|
5806
|
+
return run$1((current) => {
|
|
5807
|
+
if (!current.some((collection) => collection.id === id)) return { refused: "missing" };
|
|
5808
|
+
return { next: current.filter((collection) => collection.id !== id) };
|
|
5809
|
+
});
|
|
5810
|
+
}
|
|
5811
|
+
/**
|
|
5812
|
+
* Add the given lineages to one collection, or take them out of it.
|
|
5813
|
+
*
|
|
5814
|
+
* **Only the named collection is touched.** Assigning a selection to Refactor leaves every other
|
|
5815
|
+
* membership those reviews have exactly as it was, which is what makes a bulk assignment additive.
|
|
5816
|
+
* Adding a member already there and removing one that is not are both nothing, not errors: a mixed
|
|
5817
|
+
* selection is the ordinary case.
|
|
5818
|
+
*/
|
|
5819
|
+
function setMembership(keys, id, action) {
|
|
5820
|
+
return run$1((current) => {
|
|
5821
|
+
const target = current.find((collection) => collection.id === id);
|
|
5822
|
+
if (!target) return { refused: "missing" };
|
|
5823
|
+
const members = action === "add" ? [...target.members, ...keys.filter((key, at) => key !== "" && !target.members.includes(key) && keys.indexOf(key) === at)] : target.members.filter((key) => !keys.includes(key));
|
|
5824
|
+
if (members.length === target.members.length && members.every((key, at) => key === target.members[at])) return {
|
|
5825
|
+
next: null,
|
|
5826
|
+
id
|
|
5827
|
+
};
|
|
5828
|
+
if (members.length > 500) return { refused: "full" };
|
|
5829
|
+
return {
|
|
5830
|
+
next: current.map((collection) => collection.id === id ? {
|
|
5831
|
+
...collection,
|
|
5832
|
+
members
|
|
5833
|
+
} : collection),
|
|
5834
|
+
id
|
|
5835
|
+
};
|
|
5836
|
+
});
|
|
5837
|
+
}
|
|
5838
|
+
/**
|
|
5839
|
+
* Take lineages out of every collection, because their reviews are gone. Stores nothing when none
|
|
5840
|
+
* of them was a member, so a deletion of an uncollected review never writes this file.
|
|
5841
|
+
*/
|
|
5842
|
+
function dropMembers(keys) {
|
|
5843
|
+
const gone = new Set(keys);
|
|
5844
|
+
return updateCollections((current) => current.some((collection) => collection.members.some((key) => gone.has(key))) ? current.map((collection) => ({
|
|
5845
|
+
...collection,
|
|
5846
|
+
members: collection.members.filter((key) => !gone.has(key))
|
|
5847
|
+
})) : null);
|
|
5848
|
+
}
|
|
5849
|
+
/** Every lineage in at least one collection. What retention asks: collected is kept. */
|
|
5850
|
+
function collectedKeys(collections = readCollections()) {
|
|
5851
|
+
return new Set(collections.flatMap((collection) => collection.members));
|
|
5852
|
+
}
|
|
5853
|
+
/** A token for the index's revision, as `queueStamp` is: it changes only when a person changes it. */
|
|
5854
|
+
function collectionsStamp() {
|
|
5855
|
+
try {
|
|
5856
|
+
return String(statSync(COLLECTIONS_PATH).mtimeMs);
|
|
5857
|
+
} catch {
|
|
5858
|
+
return "";
|
|
5859
|
+
}
|
|
5860
|
+
}
|
|
5861
|
+
//#endregion
|
|
5600
5862
|
//#region ../../packages/review-harness/src/workspace/lifecycle.ts
|
|
5601
5863
|
/**
|
|
5602
5864
|
* What happens next with a change, as the reviewer says it: still in play, parked until something
|
|
@@ -6134,18 +6396,23 @@ function followedRefName(branch, followedRef) {
|
|
|
6134
6396
|
* Queue exist to defeat recency: a review saved three weeks ago has to be reachable from the list
|
|
6135
6397
|
* or the pill that claims to show it is simply broken.
|
|
6136
6398
|
*
|
|
6137
|
-
* **
|
|
6399
|
+
* **Three windows, and the page shows their sum.** Drawing them from one limit would mean saving
|
|
6138
6400
|
* eleven reviews pushed eleven recent ones off the page -- the working set eating the list it was
|
|
6139
|
-
* added to. So `limit` bounds recency, `keptLimit` bounds what was kept,
|
|
6140
|
-
*
|
|
6141
|
-
* said here.
|
|
6401
|
+
* added to. So `limit` bounds recency, `keptLimit` bounds what was kept, `collected.limit` bounds
|
|
6402
|
+
* what is in a collection, and the most cards this can return is the three added together. No
|
|
6403
|
+
* constant says that on its own, which is why it is said here.
|
|
6142
6404
|
*/
|
|
6143
|
-
function indexReviewGroups(limit, keptLimit, keptIds, rank, groups = groupByLineage(recentWorkspaces(Number.MAX_SAFE_INTEGER))) {
|
|
6144
|
-
return selectIndexGroups(groups, limit, keptLimit, keptIds, rank);
|
|
6405
|
+
function indexReviewGroups(limit, keptLimit, keptIds, rank, groups = groupByLineage(recentWorkspaces(Number.MAX_SAFE_INTEGER)), collected = NO_COLLECTED) {
|
|
6406
|
+
return selectIndexGroups(groups, limit, keptLimit, keptIds, rank, collected);
|
|
6145
6407
|
}
|
|
6408
|
+
var NO_COLLECTED = {
|
|
6409
|
+
keys: /* @__PURE__ */ new Set(),
|
|
6410
|
+
limit: 0
|
|
6411
|
+
};
|
|
6146
6412
|
/**
|
|
6147
6413
|
* What the reviewer explicitly kept, which is what stops the recency window hiding it: the queue,
|
|
6148
|
-
* Saved, and anything marked Waiting or Done.
|
|
6414
|
+
* Saved, and anything marked Waiting or Done. Collections have a window of their own
|
|
6415
|
+
* (`CollectedWindow`).
|
|
6149
6416
|
*
|
|
6150
6417
|
* Done is the reviewer's archive, and an archive the recency window can empty is not one. Waiting
|
|
6151
6418
|
* is work they expect back, and a push to it must have a card to come back on.
|
|
@@ -6171,12 +6438,19 @@ function indexKeptIds(reviews, queue) {
|
|
|
6171
6438
|
* window rather than cut before its activity is ever looked at. Absent, it is when the newest
|
|
6172
6439
|
* review was made, which is what every other caller has always meant by recent.
|
|
6173
6440
|
*/
|
|
6174
|
-
function selectIndexGroups(groups, limit, keptLimit, keptIds, rank = (group) => group.latest.createdAt) {
|
|
6441
|
+
function selectIndexGroups(groups, limit, keptLimit, keptIds, rank = (group) => group.latest.createdAt, collected = NO_COLLECTED) {
|
|
6442
|
+
const inCollection = [];
|
|
6175
6443
|
const kept = [];
|
|
6176
6444
|
const rest = [];
|
|
6177
|
-
for (const group of groups) (
|
|
6445
|
+
for (const group of groups) if (collected.keys.has(group.key)) inCollection.push(group);
|
|
6446
|
+
else if (group.history.some((review) => keptIds.has(review.id))) kept.push(group);
|
|
6447
|
+
else rest.push(group);
|
|
6178
6448
|
const byRank = (a, b) => rank(b) - rank(a);
|
|
6179
|
-
return [
|
|
6449
|
+
return [
|
|
6450
|
+
...rest.sort(byRank).slice(0, limit),
|
|
6451
|
+
...kept.sort(byRank).slice(0, keptLimit),
|
|
6452
|
+
...inCollection.sort(byRank).slice(0, collected.limit)
|
|
6453
|
+
].sort(byRank);
|
|
6180
6454
|
}
|
|
6181
6455
|
/**
|
|
6182
6456
|
* The grouping itself, with the disk left out.
|
|
@@ -6528,11 +6802,12 @@ function touchWorkspace(workspace) {
|
|
|
6528
6802
|
function pruneOldWorkspaces() {
|
|
6529
6803
|
try {
|
|
6530
6804
|
const queued = new Set(readQueue());
|
|
6805
|
+
const collected = collectedKeys();
|
|
6531
6806
|
for (const entry of readdirSync(WORKSPACES_DIR)) {
|
|
6532
6807
|
const path = join(WORKSPACES_DIR, entry);
|
|
6533
6808
|
const age = Date.now() - statSync(path).mtimeMs;
|
|
6534
6809
|
if (age <= 2592e6) continue;
|
|
6535
|
-
if (age <= 7776e6 && isKept(entry, queued)) continue;
|
|
6810
|
+
if (age <= 7776e6 && isKept(entry, queued, collected)) continue;
|
|
6536
6811
|
unlinkSync(path);
|
|
6537
6812
|
}
|
|
6538
6813
|
} catch {}
|
|
@@ -6544,12 +6819,22 @@ function pruneOldWorkspaces() {
|
|
|
6544
6819
|
* is the safe direction: the alternative keeps unreadable files for three months on the strength of
|
|
6545
6820
|
* a question nobody could answer.
|
|
6546
6821
|
*/
|
|
6547
|
-
function isKept(entry, queued) {
|
|
6822
|
+
function isKept(entry, queued, collected) {
|
|
6548
6823
|
const id = workspaceIdFrom(entry);
|
|
6549
6824
|
if (id === null) return false;
|
|
6550
6825
|
if (queued.has(id)) return true;
|
|
6551
6826
|
const workspace = loadWorkspace(id);
|
|
6552
|
-
|
|
6827
|
+
if (!workspace) return false;
|
|
6828
|
+
if (workspace.pinned === true || parseLifecycle(workspace.lifecycle)?.state === "done") return true;
|
|
6829
|
+
return collected.size > 0 && isCollected(workspace, collected);
|
|
6830
|
+
}
|
|
6831
|
+
/** A record that cannot be summarised cannot claim a lineage, so it falls to the ordinary window. */
|
|
6832
|
+
function isCollected(workspace, collected) {
|
|
6833
|
+
try {
|
|
6834
|
+
return collected.has(lineageKey(summarise(workspace)));
|
|
6835
|
+
} catch {
|
|
6836
|
+
return false;
|
|
6837
|
+
}
|
|
6553
6838
|
}
|
|
6554
6839
|
/** The review an entry belongs to, whether it is the record or the usage sidecar beside it. */
|
|
6555
6840
|
function workspaceIdFrom(entry) {
|
|
@@ -19773,6 +20058,7 @@ function liveJobsFor(reviewId, now = Date.now()) {
|
|
|
19773
20058
|
* a fact: no job for this review can be created or claimed without the lock this call is holding.
|
|
19774
20059
|
*/
|
|
19775
20060
|
async function deleteReview(id) {
|
|
20061
|
+
const key = lineageKeyOf(id);
|
|
19776
20062
|
const outcome = await withReviewLock(id, async () => {
|
|
19777
20063
|
const live = liveJobsFor(id);
|
|
19778
20064
|
if (live.length > 0) return {
|
|
@@ -19781,10 +20067,31 @@ async function deleteReview(id) {
|
|
|
19781
20067
|
};
|
|
19782
20068
|
return deleteLocked(id);
|
|
19783
20069
|
});
|
|
19784
|
-
|
|
20070
|
+
if (isBusy(outcome)) return {
|
|
19785
20071
|
...emptyReport(),
|
|
19786
20072
|
busy: true
|
|
19787
|
-
}
|
|
20073
|
+
};
|
|
20074
|
+
forgetIfEmpty(key, outcome);
|
|
20075
|
+
return outcome;
|
|
20076
|
+
}
|
|
20077
|
+
/** The lineage a review belongs to, read before it is deleted, or absent when there is no review. */
|
|
20078
|
+
function lineageKeyOf(id) {
|
|
20079
|
+
const workspace = loadWorkspace(id);
|
|
20080
|
+
if (!workspace) return void 0;
|
|
20081
|
+
try {
|
|
20082
|
+
return lineageKey(summarise(workspace));
|
|
20083
|
+
} catch {
|
|
20084
|
+
return;
|
|
20085
|
+
}
|
|
20086
|
+
}
|
|
20087
|
+
/**
|
|
20088
|
+
* A lineage with no review left leaves every collection, so a branch reviewed again later starts
|
|
20089
|
+
* uncollected rather than reappearing in a group from before it was deleted. A lineage that still
|
|
20090
|
+
* has reviews keeps its memberships: deleting one analysis from a card is not deleting the card.
|
|
20091
|
+
*/
|
|
20092
|
+
function forgetIfEmpty(key, report) {
|
|
20093
|
+
if (key === void 0 || report.deleted.length === 0) return;
|
|
20094
|
+
if (!recentWorkspaces(Number.MAX_SAFE_INTEGER).some((review) => lineageKey(review) === key)) dropMembers([key]);
|
|
19788
20095
|
}
|
|
19789
20096
|
/**
|
|
19790
20097
|
* The removal itself, with the lock already held and the check already made.
|
|
@@ -19841,6 +20148,7 @@ async function deleteLocked(id) {
|
|
|
19841
20148
|
* quietly delete a review made after it was rendered.
|
|
19842
20149
|
*/
|
|
19843
20150
|
async function deleteLineage(id) {
|
|
20151
|
+
const key = lineageKeyOf(id);
|
|
19844
20152
|
const ids = lineageIds(id);
|
|
19845
20153
|
const outcome = await withReviewLocks(ids, async () => {
|
|
19846
20154
|
const live = ids.flatMap((each) => liveJobsFor(each));
|
|
@@ -19858,10 +20166,12 @@ async function deleteLineage(id) {
|
|
|
19858
20166
|
}
|
|
19859
20167
|
return report;
|
|
19860
20168
|
});
|
|
19861
|
-
|
|
20169
|
+
if (isBusy(outcome)) return {
|
|
19862
20170
|
...emptyReport(),
|
|
19863
20171
|
busy: true
|
|
19864
|
-
}
|
|
20172
|
+
};
|
|
20173
|
+
forgetIfEmpty(key, outcome);
|
|
20174
|
+
return outcome;
|
|
19865
20175
|
}
|
|
19866
20176
|
/**
|
|
19867
20177
|
* The ids on one card, so a caller can check every one of them for running work before it commits
|
|
@@ -19879,4 +20189,4 @@ function lineageIds(id) {
|
|
|
19879
20189
|
return [.../* @__PURE__ */ new Set([id, ...lineage.map((review) => review.id)])];
|
|
19880
20190
|
}
|
|
19881
20191
|
//#endregion
|
|
19882
|
-
export { MIN_WORKSPACE_PORT as $,
|
|
20192
|
+
export { MIN_WORKSPACE_PORT as $, resolveModel as $i, RUN_RECORD_RETENTION_MS as $n, deleteCollection as $r, markRead as $t, batchIdentity as A, isGradeFor as Ai, escapeHtml as An, positionInLineage as Ar, loadArchitecture as At, bootstrapUrl as B, WITHHELD_REASON as Bi, readMarker as Bn, LIFECYCLES as Br, ancestorsOf as Bt, RUN_KINDS as C, ANALYSIS_ONLY as Ci, UNESTABLISHED_MOTIVATION as Cn, kissDecisionsOf as Cr, mapRuntimes as Ct, reportRun as D, findingAssessment as Di, renderMarkdown as Dn, loadWorkspace as Dr, formatDuration as Dt, endRun as E, currentGradeIn as Ei, GITHUB_MARKDOWN as En, lineagePosition as Er, describeRuntimes as Et, activateQueued as F, followUpFindingId as Fi, withReviewLock as Fn, reviewedRepositories as Fr, storedScopes as Ft, serverEntryExists as G, isCommitReview as Gi, findExistingReview as Gn, lifecycleItemLabel as Gr, subjectOf as Gt, ensureServer as H, resolveTarget as Hi, requestJobStop as Hn, activityLabel as Hr, writeJsonAtomically as Ht, compactQueue as I, asString$1 as Ii, MANAGED_ROOT as In, summarise as Ir, trackedFiles as It, stopServer as J, shortNameOf as Ji, followedBranch as Jn, parseLifecycle as Jr, createPrState as Jt, spawnServer as K, resolveBase as Ki, checkCodeFreshness as Kn, lifecyclePill as Kr, categoryOf as Kt, queueForReview as L, extractJsonText as Li, createWorktree as Ln, touchWorkspace as Lr, ownersOf as Lt, parsePullRequestLink as M, reassessIn as Mi, modelHelpLines as Mn, previousKissResult as Mr, readArchitecture as Mt, unsupportedReason as N, sourcePhrase as Ni, ReviewBeingDeletedError as Nn, recentWorkspaces as Nr, repositoryKey as Nt, stopRun as O, findingIn as Oi, CODE_BLOCK_CLASS as On, matchingWorkspaceIds as Or, ARCHITECTURE_DIR as Ot, runBatch as P, wasReassessed as Pi, isBusy as Pn, reviewedCommit as Pr, storeArchitecture as Pt, MAX_WORKSPACE_PORT as Q, resolveAgent as Qi, fillFileUrlTemplate as Qn, createWithMembers as Qr, markAllRead as Qt, settleQueue as R, objectWithAnyOf as Ri, holdWorktree as Rn, workspaceRevision as Rr, QUOTE_LIMITS as Rt, track as S, withExplanation as Si, chatConversationSection as Sn, kissDecisionRefusal as Sr, waitingHistory as St, completeRun as T, assessmentLookup as Ti, hasAuthorContent as Tn, lineageKey as Tr, RUNTIME_WORDS as Tt, readServerRecord as U, originRemote as Ui, describeMovement as Un, isLifecycle as Ur, announceJob as Ut, clearServerRecordOwnedBy as V, kissOutcome as Vi, removeWorktree as Vn, activityAttributes as Vr, isArchitectureId as Vt, reviewsUrl as W, DetachedHeadError as Wi, describeSubject as Wn, lifecycleChip as Wr, branchOf as Wt, writeServerRecord as X, git as Xi, fetchHead as Xn, collectionsStamp as Xr, eventName as Xt, workspaceUrl as Y, shorthandOf as Yi, reviewedShaOf as Yn, sectionOf as Yr, createPrUpdate as Yt, DEFAULT_WORKSPACE_PORT as Z, refExists as Zi, forgeResolver as Zn, createCollection as Zr, listNotifications as Zt, runningJobs as _, processDiscussion as _i, unforeseenFailure as _n, decideKissRecommendation as _r, telemetryUploadConsent as _t, wasBlocked as a, STORE_ROOT as aa, queueAdd as ai, removeNotification as an, loadJob as ar, observeReview as at, startKissJob as b, explanationOf as bi, agentEnvOf as bn, indexKeptIds as br, PACKAGE_NAME as bt, RefreshUnavailableError as c, agentFor as ca, queueStamp as ci, unreadAround as cn, PHASES as cr, reanalyseReview as ct, mergeUpdateInto as d, isAgentId as da, isIssueOutstanding as di, existingPaths as dn, phasesFor as dr, append as dt, agentIdOf as ea, nameProblemText as ei, markReadForReview as en, allJobs as er, parseWorkspacePort as et, parkedUpdateRefusal as f, parkedReason as fa, isQuestionOutstanding as fi, ASK_REREAD_CEILING_MS as fn, progressSteps as fr, readEvents as ft, liveUpdateFor as g, reviewerDispositions as gi, storedConversation as gn, CLAIM_LEASE_MS as gr, recordTelemetryUploadConsent as gt, UpdateAlreadyRunningError as h, AgentUnavailableError as ha, resolvedByConversation as hi, saveRefresh as hn, isClaimed as hr, deliver as ht, liveJobsFor as i, CONFIG_PATH as ia, updateCollections as ii, readStateOf as in, isTerminal as ir, failureOf as it, extractPullRequestLinks as j, promptGrade as ji, renderInlineText as jn, presentedKissRunOf as jr, partFiles as jt, relativeTime as k, gradeScale as ki, CODE_COPY_CLASS as kn, mutateWorkspace as kr, checkArchitecture as kt, cancelUpdate as l, agentIdentityOf as la, readQueue as li, unreadCount as ln, isLiveJob as lr, runJob as lt, askCheckout as m, AgentCancelledError as ma, questionsOutstanding as mi, prepareConversationWithin as mn, clearClaim as mr, stamp as mt, deleteReview as n, readAgentPreference as na, renameCollection as ni, pruneNotifications as nn, endedAt as nr, statedWorkspacePort as nt, discardJob as o, AGENT_IDS as oa, queueAddMany as oi, retireNotification as on, pruneJobResidue as or, prepareReview as ot, updateReview as p, runnableAgents as pa, issuesOutstanding as pi, adoptLegacyBaseline as pn, purposeOf as pr, record as pt, stopChild as q, AmbiguousRefError as qi, checkReview as qn, offersLifecycle as qr, createArchitectureNotification as qt, lineageIds as r, writeAgentPreference as ra, setMembership as ri, readNotification as rn, fail as rr, writePortPreference as rt, FETCH_TIMEOUT_MS as s, agentById as sa, queueRemove as si, toneOf as sn, saveJob as sr, settleReview as st, deleteLineage as t, clearAgentPreference as ta, readCollections as ti, moveIds as tn, attemptIdOf as tr, resolveWorkspacePort as tt, cancellableHere as u, detectAgents as ua, doneVerb as ui, withUsageRecorded as un, isTerminalJobPhase as ur, startJob as ut, kissAgentId as v, recommendationsOf as vi, refreshPrContext as vn, followedRefName as vr, telemetryUploadEnabled as vt, beginRun as w, analysisRefOf as wi, authorPreview as wn, latestKissRun as wr, recordRuntimeBestEffort as wt, isDriving as x, isExplanationAction as xi, EXPLAIN_SIMPLY_PROMPT as xn, indexReviewGroups as xr, feedbackUrl as xt, recordKissRun as y, explanationActionOf as yi, isUnchanged as yn, groupByLineage as yr, BUILD_VERSION as yt, readIndexToken as z, describeAuthorship as zi, isWorktreeHeld as zn, workspaceStamps as zr, REPOSITORY_SCOPE as zt };
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as MIN_WORKSPACE_PORT, A as batchIdentity,
|
|
2
|
+
import { $ as MIN_WORKSPACE_PORT, $i as resolveModel, A as batchIdentity, Ar as positionInLineage, B as bootstrapUrl, Bi as WITHHELD_REASON, Bn as readMarker, Dr as loadWorkspace, Fr as reviewedRepositories, Gn as findExistingReview, H as ensureServer, Hi as resolveTarget, In as MANAGED_ROOT, Ir as summarise, J as stopServer, L as queueForReview, Mn as modelHelpLines, Nr as recentWorkspaces, Or as matchingWorkspaceIds, P as runBatch, Q as MAX_WORKSPACE_PORT, Qi as resolveAgent, Un as describeMovement, Vi as kissOutcome, Vn as removeWorktree, W as reviewsUrl, Wn as describeSubject, Xi as git, Y as workspaceUrl, Z as DEFAULT_WORKSPACE_PORT, Zn as forgeResolver, _ as runningJobs, _t as telemetryUploadConsent, a as wasBlocked, aa as STORE_ROOT, ar as loadJob, bt as PACKAGE_NAME, cr as PHASES, ct as reanalyseReview, d as mergeUpdateInto, dt as append, er as allJobs, et as parseWorkspacePort, f as parkedUpdateRefusal, fa as parkedReason, fr as progressSteps, g as liveUpdateFor, gt as recordTelemetryUploadConsent, h as UpdateAlreadyRunningError, ht as deliver, i as liveJobsFor, ia as CONFIG_PATH, k as relativeTime, kr as mutateWorkspace, mr as clearClaim, mt as stamp, n as deleteReview$1, na as readAgentPreference, nt as statedWorkspacePort, oa as AGENT_IDS, ot as prepareReview, p as updateReview, pa as runnableAgents, pr as purposeOf, pt as record$1, ra as writeAgentPreference, rt as writePortPreference, sa as agentById, st as settleReview, ua as detectAgents, ut as startJob, vr as followedRefName, vt as telemetryUploadEnabled, y as recordKissRun, yt as BUILD_VERSION, z as readIndexToken } from "./delete_review-BED4mPxB.js";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|