@coderook/cli 0.31.4 → 0.31.5

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.4",
5
+ "version": "0.31.5",
6
6
  "author": {
7
7
  "name": "ACCA Gaming Productions",
8
8
  "url": "https://coderook.com"
@@ -10,6 +10,8 @@ exports.projects = projects;
10
10
  exports.findPublicProject = findPublicProject;
11
11
  exports.findProject = findProject;
12
12
  exports.health = health;
13
+ exports.openProposal = openProposal;
14
+ exports.proposals = proposals;
13
15
  exports.mergeTracks = mergeTracks;
14
16
  exports.mergeTrack = mergeTrack;
15
17
  exports.resolveMergeConflict = resolveMergeConflict;
@@ -265,6 +267,17 @@ async function health() {
265
267
  });
266
268
  return (await response.json());
267
269
  }
270
+ /** Offer a line of work to another, usually main. */
271
+ async function openProposal(repositoryId, input) {
272
+ return call(`/v1/repositories/${repositoryId}/proposals`, {
273
+ method: "POST",
274
+ body: input,
275
+ });
276
+ }
277
+ async function proposals(repositoryId, all = false) {
278
+ const body = await call(`/v1/repositories/${repositoryId}/proposals${all ? "?state=all" : ""}`);
279
+ return body.proposals;
280
+ }
268
281
  async function mergeTracks(repositoryId) {
269
282
  const body = await call(`/v1/repositories/${repositoryId}/merge-tracks`);
270
283
  return body.mergeTracks;
@@ -1026,6 +1026,17 @@ The connection failed: ${text}`));
1026
1026
  observedHeadVersionId: result.versionId,
1027
1027
  local: result.local,
1028
1028
  manifest: result.manifest,
1029
+ /*
1030
+ The line this folder is on survives a save onto it.
1031
+
1032
+ The same fault `get` had and lost: this record is built fresh, and
1033
+ without naming `track` every save onto a line moved the folder back to
1034
+ main — so the second save on a line went to main, and `cbx propose`
1035
+ found the folder "on main" straight after `cbx track spike --new` and a
1036
+ save. A one-off `--track` does not move it either: that flag means
1037
+ "just this once", so the folder keeps the line it had.
1038
+ */
1039
+ ...(link?.track ? { track: link.track } : {}),
1029
1040
  });
1030
1041
  /*
1031
1042
  What this save hashed, kept for the next one.
@@ -1547,6 +1558,69 @@ async function commandDoctor() {
1547
1558
  * project looks untouched — so this is the way to find out that work of
1548
1559
  * yours is sitting somewhere, still complete, waiting.
1549
1560
  */
1561
+ /**
1562
+ * Offer this folder's line of work to another line, usually main.
1563
+ *
1564
+ * The folder's line is the one proposed, because it is the one being worked
1565
+ * on; proposing main is refused rather than guessed at, since the answer to
1566
+ * "propose what?" is then a line nobody named.
1567
+ */
1568
+ async function commandPropose(parsed) {
1569
+ const folder = folderFor(parsed);
1570
+ const link = await (0, config_js_1.readLink)(folder);
1571
+ if (!link) {
1572
+ console.error(red("This folder is not linked to a project on your account."));
1573
+ return 1;
1574
+ }
1575
+ const from = flagText(parsed, "from") ?? (await (0, track_commands_js_1.trackFor)(folder));
1576
+ const into = flagText(parsed, "into") ?? "main";
1577
+ if (from === into) {
1578
+ console.error(red(`This folder is on ${from}. Start a line with cbx track <name> --new, save onto it, then propose it.`));
1579
+ return 1;
1580
+ }
1581
+ const title = flagText(parsed, "message", "m", "title");
1582
+ if (!title) {
1583
+ console.error(red('Give it a title: cbx propose -m "What this changes"'));
1584
+ return 1;
1585
+ }
1586
+ const opened = await (0, api_js_1.openProposal)(link.repositoryId, {
1587
+ from,
1588
+ into,
1589
+ title,
1590
+ ...(flagText(parsed, "body") ? { body: flagText(parsed, "body") } : {}),
1591
+ });
1592
+ const reference = opened.mergeTrack.reference;
1593
+ console.log(`${accent(reference)} ${from} → ${into} ${title}`);
1594
+ if (opened.conflicts.settled) {
1595
+ console.log(dim(` ${opened.conflicts.settled} file(s) both sides changed were combined line by line.`));
1596
+ }
1597
+ console.log(opened.conflicts.unresolved
1598
+ ? ` ${opened.conflicts.unresolved} file(s) need a decision. Run cbx merge ${reference} to look.`
1599
+ : dim(` Nothing to decide. Run cbx merge ${reference} --apply when it has been reviewed.`));
1600
+ return 0;
1601
+ }
1602
+ async function commandProposals(parsed) {
1603
+ const folder = folderFor(parsed);
1604
+ const link = await (0, config_js_1.readLink)(folder);
1605
+ if (!link) {
1606
+ console.error(red("This folder is not linked to a project on your account."));
1607
+ return 1;
1608
+ }
1609
+ const listed = await (0, api_js_1.proposals)(link.repositoryId, hasFlag(parsed, "all"));
1610
+ if (!listed.length) {
1611
+ console.log(hasFlag(parsed, "all") ? "No proposals yet." : "No open proposals.");
1612
+ return 0;
1613
+ }
1614
+ for (const proposal of listed) {
1615
+ const counts = proposal.conflicts;
1616
+ const waiting = counts && counts.unresolved ? red(`${counts.unresolved} to decide`) : "";
1617
+ console.log(`${accent(proposal.reference)} ${proposal.proposal?.from ?? "?"} → ${proposal.proposal?.into ?? "?"} ` +
1618
+ `${proposal.proposal?.title ?? ""} ${waiting}` +
1619
+ (proposal.state === "open" ? "" : dim(` ${proposal.state}`)));
1620
+ }
1621
+ console.log(dim("\nRun cbx merge <reference> to look at one."));
1622
+ return 0;
1623
+ }
1550
1624
  async function commandMerges(parsed) {
1551
1625
  const folder = folderFor(parsed);
1552
1626
  const link = await (0, config_js_1.readLink)(folder);
@@ -1629,6 +1703,18 @@ async function commandMerge(parsed) {
1629
1703
  }
1630
1704
  }
1631
1705
  const now = await (0, api_js_1.mergeTrack)(summary.id);
1706
+ if (now.proposal) {
1707
+ console.log(`\n${accent(now.mergeTrack.reference)} ${now.proposal.from} → ${now.proposal.into}` +
1708
+ (now.changeRequest ? ` ${now.changeRequest.title}` : ""));
1709
+ const approvals = now.changeRequest?.approvals;
1710
+ if (approvals && approvals.required) {
1711
+ console.log(` ${approvals.satisfied ? accent("approved") : red("waiting for review")}` +
1712
+ dim(` · ${approvals.approvals} of ${approvals.required} approvals`));
1713
+ }
1714
+ for (const blocked of now.provisional.blockedByChecks ?? []) {
1715
+ console.log(` ${red("check")} ${blocked}`);
1716
+ }
1717
+ }
1632
1718
  console.log(`
1633
1719
  ${accent(now.mergeTrack.reference)} · ${now.provisional.fileCount} files · ` +
1634
1720
  (now.provisional.ready
@@ -1646,10 +1732,14 @@ ${accent(now.mergeTrack.reference)} · ${now.provisional.fileCount} files · ` +
1646
1732
  const applied = await (0, api_js_1.applyMerge)(summary.id);
1647
1733
  console.log(`
1648
1734
  Applied as ${accent(`v${applied.version.sequence}`)}.`);
1735
+ if (applied.closedIssues?.length) {
1736
+ console.log(`Closed ${applied.closedIssues.map((number) => `#${number}`).join(", ")}.`);
1737
+ }
1649
1738
  console.log(dim("Run cbx get to bring it down to this folder."));
1650
1739
  return 0;
1651
1740
  }
1652
- if (!decision) {
1741
+ const stillOpen = now.conflicts.some((conflict) => !conflict.resolvedAt);
1742
+ if (!decision && stillOpen) {
1653
1743
  console.log(dim(`
1654
1744
  --mine keeps yours, --theirs keeps what was already saved,` +
1655
1745
  ` --drop removes the file.
@@ -2197,7 +2287,7 @@ const SPECS = [
2197
2287
  detail: "A signed POST to an address of yours when something happens here.\n" +
2198
2288
  "The signing secret is generated by the service and shown once, when\n" +
2199
2289
  "the hook is added — no route hands it back afterwards.\n\n" +
2200
- "Events: version.published, merge.opened, merge.applied,\n" +
2290
+ "Events: version.published, merge.opened, merge.applied, merge.cancelled,\n" +
2201
2291
  "check.reported, change_request.opened, change_request.reviewed.\n" +
2202
2292
  "Naming none of them sends all of them.",
2203
2293
  options: [
@@ -2552,6 +2642,31 @@ const SPECS = [
2552
2642
  options: [{ flags: "--revoke <id>", description: "stop one working now" }],
2553
2643
  run: service_commands_js_1.commandTokens,
2554
2644
  },
2645
+ {
2646
+ name: "propose",
2647
+ group: "Working with a folder",
2648
+ summary: "offer this folder's line to main",
2649
+ usage: 'propose -m "title" [--into main] [--body text] [folder]',
2650
+ options: [
2651
+ { flags: "-m, --message <text>", description: "the proposal's title" },
2652
+ { flags: "--into <line>", description: "the line to propose into (default main)" },
2653
+ { flags: "--from <line>", description: "propose this line instead of the folder's" },
2654
+ { flags: "--body <text>", description: "a longer description" },
2655
+ ],
2656
+ detail: "Opens a proposal from the line this folder is on into main (or --into).\n" +
2657
+ "Files both sides changed are combined line by line where they can be;\n" +
2658
+ "the rest wait for a decision in cbx merge. \"Fixes #12\" in the title or\n" +
2659
+ "body closes issue 12 when the proposal lands.",
2660
+ run: commandPropose,
2661
+ },
2662
+ {
2663
+ name: "proposals",
2664
+ group: "Working with a folder",
2665
+ summary: "open proposals on this project",
2666
+ usage: "proposals [--all] [folder]",
2667
+ options: [{ flags: "--all", description: "include merged and cancelled ones" }],
2668
+ run: commandProposals,
2669
+ },
2555
2670
  {
2556
2671
  name: "merges",
2557
2672
  group: "When somebody saved first",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coderook/cli",
3
- "version": "0.31.4",
3
+ "version": "0.31.5",
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",