@dreki-gg/pi-code-reviewer 0.7.0 → 0.8.0

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
@@ -30,10 +30,28 @@ This creates:
30
30
  /review --lens quality,ux # Multiple lenses
31
31
  /review --base main # Diff against a branch
32
32
  /review --staged # Only staged changes
33
+ /review --repo ../project-pr35 # Review a worktree / sibling repo
33
34
  /review-lenses # List available lenses
34
35
  ```
35
36
 
36
- The `code_review` tool is also available for programmatic use by the agent.
37
+ ### Reviewing worktrees & other repos (`--repo`)
38
+
39
+ By default `/review` runs git against the Pi session directory. Pass `--repo <dir>`
40
+ (alias `--cwd <dir>`) to target a different directory — a git worktree (sibling or
41
+ nested) or another repo — without leaving the session:
42
+
43
+ ```
44
+ /review --repo /path/to/worktree --base HEAD~1 --lens code-quality,architecture
45
+ ```
46
+
47
+ - The path is resolved relative to the session directory and validated as a git
48
+ work tree (relative paths like `../project-pr35` work).
49
+ - `.code-review.json`, lenses, and recorded rejections all resolve relative to the
50
+ override directory.
51
+ - The session directory itself is left unchanged — only git/config/lens resolution
52
+ is redirected.
53
+
54
+ The `code_review` tool exposes the same override via its optional `cwd` parameter.
37
55
 
38
56
  ## How the review runs (Bugbot-style pipeline)
39
57
 
@@ -7,6 +7,7 @@ import { Type } from 'typebox';
7
7
  import { loadConfig, getLensDir } from '../config';
8
8
  import { collectDiff, getChangedFiles } from '../diff';
9
9
  import { discoverLenses, getLensContent } from '../lenses';
10
+ import { resolveRepoCwd } from '../resolve-cwd';
10
11
  import { resolveModelPlan } from '../model-plan';
11
12
  import { runPipeline } from '../passes';
12
13
  import {
@@ -72,10 +73,29 @@ export function registerReviewTool(pi: ExtensionAPI) {
72
73
  description: 'Review only staged changes instead of all working directory changes.',
73
74
  }),
74
75
  ),
76
+ cwd: Type.Optional(
77
+ Type.String({
78
+ description:
79
+ 'Override directory for git/config/lens resolution (e.g. a worktree or sibling repo). Resolved relative to the session directory and validated as a git work tree. The session directory is left unchanged.',
80
+ }),
81
+ ),
75
82
  }),
76
83
 
77
84
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
78
- const cwd = ctx.cwd;
85
+ let cwd: string;
86
+ try {
87
+ cwd = await resolveRepoCwd(pi, ctx.cwd, params.cwd);
88
+ } catch (cause) {
89
+ return {
90
+ content: [
91
+ {
92
+ type: 'text',
93
+ text: `cwd "${params.cwd}" is not a git work tree (${cause instanceof Error ? cause.message : String(cause)}).`,
94
+ },
95
+ ],
96
+ details: {},
97
+ };
98
+ }
79
99
  const config = await loadConfig(cwd);
80
100
  const lensDir = getLensDir(cwd, config);
81
101
  const available = await discoverLenses(lensDir);
@@ -14,13 +14,29 @@ import {
14
14
  runTools,
15
15
  } from '../reviewer';
16
16
  import { parseReviewArgs } from '../parse-args';
17
+ import { resolveRepoCwd } from '../resolve-cwd';
17
18
 
18
19
  export function registerReviewCommand(pi: ExtensionAPI) {
19
20
  pi.registerCommand('review', {
20
21
  description:
21
- 'Run a multi-lens code review on working directory changes. Usage: /review [--lens name,...] [--base ref] [--staged]',
22
+ 'Run a multi-lens code review on working directory changes. Usage: /review [--lens name,...] [--base ref] [--staged] [--repo dir]',
22
23
  handler: async (args, ctx) => {
23
- const cwd = ctx.cwd;
24
+ const parsed = parseReviewArgs(args ?? '');
25
+
26
+ // Override directory for git/config/lens resolution (worktrees, sibling
27
+ // repos). Resolved relative to the session CWD and validated; the session
28
+ // CWD itself is left unchanged.
29
+ let cwd: string;
30
+ try {
31
+ cwd = await resolveRepoCwd(pi, ctx.cwd, parsed.repo);
32
+ } catch (cause) {
33
+ ctx.ui.notify(
34
+ `--repo ${parsed.repo} is not a git work tree (${cause instanceof Error ? cause.message : String(cause)})`,
35
+ 'error',
36
+ );
37
+ return;
38
+ }
39
+
24
40
  const config = await loadConfig(cwd);
25
41
  const lensDir = getLensDir(cwd, config);
26
42
  const available = await discoverLenses(lensDir);
@@ -33,7 +49,6 @@ export function registerReviewCommand(pi: ExtensionAPI) {
33
49
  return;
34
50
  }
35
51
 
36
- const parsed = parseReviewArgs(args ?? '');
37
52
  const lensNames = resolveLensNames(parsed.lenses, config.defaultLenses, available, (msg) =>
38
53
  ctx.ui.notify(msg, 'warning'),
39
54
  );
@@ -3,10 +3,13 @@ export function parseReviewArgs(args: string): {
3
3
  lenses: string[];
4
4
  base?: string;
5
5
  staged: boolean;
6
+ /** Override directory for git operations (--repo, alias --cwd). */
7
+ repo?: string;
6
8
  } {
7
9
  const lenses: string[] = [];
8
10
  let base: string | undefined;
9
11
  let staged = false;
12
+ let repo: string | undefined;
10
13
 
11
14
  const parts = args.split(/\s+/).filter(Boolean);
12
15
 
@@ -18,8 +21,10 @@ export function parseReviewArgs(args: string): {
18
21
  base = parts[++i];
19
22
  } else if (part === '--staged') {
20
23
  staged = true;
24
+ } else if ((part === '--repo' || part === '--cwd') && i + 1 < parts.length) {
25
+ repo = parts[++i];
21
26
  }
22
27
  }
23
28
 
24
- return { lenses, base, staged };
29
+ return { lenses, base, staged, repo };
25
30
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Resolve the directory git operations should target.
3
+ *
4
+ * `/review` and the `code_review` tool default to the Pi session CWD, but a
5
+ * `--repo` / `--cwd` override (or tool `cwd` param) lets a worktree or sibling
6
+ * repo be reviewed from the same session. The override is resolved relative to
7
+ * the session CWD (so relative worktree paths like `../project-pr35` work) and
8
+ * validated with a single `git rev-parse --is-inside-work-tree` — that one call
9
+ * rejects both a missing directory and a non-git path. The session CWD itself is
10
+ * never mutated; this only redirects git/config/lens/rejections resolution.
11
+ */
12
+
13
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
14
+ import { Effect } from 'effect';
15
+ import { resolve } from 'node:path';
16
+
17
+ import { Executor, makeExecutorService } from './effects/exec';
18
+ import type { ExecError } from './errors';
19
+
20
+ const GIT_TIMEOUT_MS = 30_000;
21
+
22
+ export function resolveRepoCwdEffect(
23
+ sessionCwd: string,
24
+ override?: string,
25
+ ): Effect.Effect<string, ExecError, Executor> {
26
+ return Effect.gen(function* () {
27
+ if (!override) return sessionCwd;
28
+ const resolved = resolve(sessionCwd, override);
29
+ const executor = yield* Executor;
30
+ // Throws ExecError when the path is missing or not a git work tree; the
31
+ // caller maps that to a user-facing notice rather than silently falling
32
+ // back to the session CWD.
33
+ yield* executor.exec('git', ['rev-parse', '--is-inside-work-tree'], {
34
+ cwd: resolved,
35
+ timeout: GIT_TIMEOUT_MS,
36
+ });
37
+ return resolved;
38
+ });
39
+ }
40
+
41
+ /** Promise wrapper: resolve + validate the repo cwd with a live Executor from `pi`. */
42
+ export function resolveRepoCwd(
43
+ pi: Pick<ExtensionAPI, 'exec'>,
44
+ sessionCwd: string,
45
+ override?: string,
46
+ ): Promise<string> {
47
+ return Effect.runPromise(
48
+ resolveRepoCwdEffect(sessionCwd, override).pipe(
49
+ Effect.provideService(Executor, makeExecutorService(pi)),
50
+ ),
51
+ );
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreki-gg/pi-code-reviewer",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Multi-lens code review extension for pi — configurable review criteria per project",
5
5
  "keywords": [
6
6
  "pi-package"