review-workspace 1.6.0 → 1.7.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/README.md CHANGED
@@ -32,6 +32,20 @@ npx review-workspace serve .bundles/<name> --port 4317
32
32
 
33
33
  Opens the UI at `http://127.0.0.1:4317` (prints a write token too — needed only to raise/answer Questions, not to view). One process, no separate dev server.
34
34
 
35
+ ## Addressing review feedback in the target repo
36
+
37
+ When a bundle has open change-request Comments, publishing it exports a self-describing hand-off file (`.review-feedback/<mr-number>-round<N>.md`) into the *target* repo's own working tree. A second skill, `skills/address-review-feedback/`, reads that file there, implements or explicitly skips each change-request against the target repo's own conventions, runs the target repo's own verification, writes a structured response file next to the hand-off file, and commits and pushes — then asks whether to spawn a background `review-workspace` agent for the next round.
38
+
39
+ It installs the same way as the Generator skill above — no separate step:
40
+
41
+ ```sh
42
+ npx skills add sabasayer/review-workspace --global --agent claude-code -y
43
+ ```
44
+
45
+ (`npx skills add`/`update sabasayer/review-workspace` installs every directory under `skills/`, so this second skill comes along with the Generator.)
46
+
47
+ **Use it**: in the target repo, ask Claude Code to address the review feedback, e.g. "Address the review feedback using the address-review-feedback skill" — with or without pointing at a specific `.review-feedback/*.md` path; it finds the latest unaddressed one on its own otherwise.
48
+
35
49
  ## Development
36
50
 
37
51
  ```sh
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { basename, dirname, join, relative } from 'node:path';
2
+ import { basename, dirname, join, relative, resolve } from 'node:path';
3
3
  /**
4
4
  * A stable cross-round identity for one MR: repo slug + number, e.g.
5
5
  * "founda/application/xds-viewer-ui!367".
@@ -33,8 +33,13 @@ function parseBundleDirName(dirName) {
33
33
  }
34
34
  /** The sibling bundle path a new round for `bundlePath` would scaffold into. */
35
35
  export function nextRoundBundlePath(bundlePath) {
36
- const { baseName, round } = parseBundleDirName(basename(bundlePath));
37
- return join(dirname(bundlePath), `${baseName}-r${round + 1}`);
36
+ // Resolve first: basename/dirname on "." or ".." (a bundle path relative to
37
+ // cwd, e.g. when invoked as `round .`) otherwise collapse to "." for both,
38
+ // producing a literal "-r{N}" sibling nested inside the bundle itself
39
+ // instead of a real sibling directory next to it.
40
+ const resolved = resolve(bundlePath);
41
+ const { baseName, round } = parseBundleDirName(basename(resolved));
42
+ return join(dirname(resolved), `${baseName}-r${round + 1}`);
38
43
  }
39
44
  /**
40
45
  * Detects, purely from what's on disk plus a supplied live head, whether a
@@ -56,7 +61,8 @@ export function scaffoldNextRound(bundlePath, liveHead, options) {
56
61
  if (document.comparison.head === liveHead) {
57
62
  throw new Error(`Live head ${liveHead} matches the bundle's recorded head; no new round is needed`);
58
63
  }
59
- const { round: currentRound } = parseBundleDirName(basename(bundlePath));
64
+ const resolvedBundlePath = resolve(bundlePath);
65
+ const { round: currentRound } = parseBundleDirName(basename(resolvedBundlePath));
60
66
  const newBundlePath = nextRoundBundlePath(bundlePath);
61
67
  if (existsSync(newBundlePath)) {
62
68
  throw new Error(`Round bundle already exists at ${newBundlePath}`);
@@ -64,7 +70,7 @@ export function scaffoldNextRound(bundlePath, liveHead, options) {
64
70
  const chain = {
65
71
  mrKey: mrKeyFor(document.comparison),
66
72
  round: currentRound + 1,
67
- previousBundle: relative(newBundlePath, bundlePath),
73
+ previousBundle: relative(newBundlePath, resolvedBundlePath),
68
74
  previousHead: document.comparison.head,
69
75
  };
70
76
  mkdirSync(newBundlePath, { recursive: true });
@@ -0,0 +1,27 @@
1
+ const METADATA_BLOCK = /<!--\s*review-workspace-handoff\s*([\s\S]*?)-->/;
2
+ function field(block, key) {
3
+ const match = block.match(new RegExp(`^${key}:[ \\t]*(.*)$`, 'm'));
4
+ return match?.[1]?.trim();
5
+ }
6
+ /**
7
+ * Parses the `<!-- review-workspace-handoff ... -->` metadata block a hand-off
8
+ * Markdown file carries (written by `buildHandoffMarkdown`) back into its
9
+ * structured fields. Returns `undefined` for a missing block or a block
10
+ * missing any required field, rather than a partially-filled result.
11
+ */
12
+ export function parseHandoffMetadata(markdown) {
13
+ const blockMatch = markdown.match(METADATA_BLOCK);
14
+ if (!blockMatch)
15
+ return undefined;
16
+ const block = blockMatch[1];
17
+ const bundlePath = field(block, 'bundlePath');
18
+ const repository = field(block, 'repository');
19
+ const mrNumber = field(block, 'mrNumber');
20
+ const roundText = field(block, 'round');
21
+ if (!bundlePath || !repository || !mrNumber || !roundText)
22
+ return undefined;
23
+ const round = Number(roundText);
24
+ if (!Number.isInteger(round))
25
+ return undefined;
26
+ return { bundlePath, repository, mrNumber, round };
27
+ }
@@ -0,0 +1,13 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ /**
4
+ * Writes an implementer's per-comment response as `<mr-number>-round<N>.response.json`
5
+ * next to the hand-off file it corresponds to (same `.review-feedback/` directory).
6
+ * Pure serialization — no git or LLM behavior lives here.
7
+ */
8
+ export function writeHandoffResponse(options) {
9
+ const { handoffFilePath, mrNumber, round, responses } = options;
10
+ const filePath = join(dirname(handoffFilePath), `${mrNumber}-round${round}.response.json`);
11
+ writeFileSync(filePath, JSON.stringify(responses, null, 2) + '\n');
12
+ return filePath;
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "review-workspace",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "description": "A local, offline decision surface for reviewing a code Comparison — evidence-linked map of intent, behavior, risk, and observable effects.",
5
5
  "license": "MIT",
6
6
  "author": "Salih Sayer",