Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cf85127
chore(porch): 1037 init pir
amrmelsayed Aug 5, 2026
76bb964
[PIR #1037] Plan draft
amrmelsayed Aug 5, 2026
ad16423
chore(porch): 1037 plan-approval gate-requested
amrmelsayed Aug 5, 2026
36bdc2a
chore(porch): 1037 plan-approval gate-approved
amrmelsayed Aug 6, 2026
a2ab7bd
chore(porch): 1037 implement phase-transition
amrmelsayed Aug 6, 2026
35b7938
[PIR #1037] Pure review-queue module: schema, packaging, bracketed pa…
amrmelsayed Aug 6, 2026
9e15670
[PIR #1037] ReviewQueueStore: per-builder fs persistence, watcher syn…
amrmelsayed Aug 6, 2026
ca3ee86
[PIR #1037] Mode-aware diff codelenses: diffCodelensMode setting, tit…
amrmelsayed Aug 6, 2026
9aafea0
[PIR #1037] Builder-review comment controller: inline threads, reconc…
amrmelsayed Aug 6, 2026
f038316
[PIR #1037] Submit Review: batched bracketed-paste flush to builder P…
amrmelsayed Aug 6, 2026
6111a7c
[PIR #1037] docs: thread — implement phase complete
amrmelsayed Aug 6, 2026
e690d54
chore(porch): 1037 dev-approval gate-requested
amrmelsayed Aug 6, 2026
67cb80d
[PIR #1037] Focus comment input on codelens click via built-in addCom…
amrmelsayed Aug 10, 2026
f58047f
[PIR #1037] Comment input: pass range args to addComment, refresh com…
amrmelsayed Aug 10, 2026
24ba461
[PIR #1037] Mount queued threads on their full range so the widget si…
amrmelsayed Aug 10, 2026
1f8e321
[PIR #1037] Extend thread ranges to last-line content end so the rang…
amrmelsayed Aug 10, 2026
a094352
[PIR #1037] Flip diffCodelensMode default to forward (preserve #789 f…
amrmelsayed Aug 10, 2026
db1ab3c
chore(porch): 1037 dev-approval gate-approved
amrmelsayed Aug 10, 2026
049ee19
chore(porch): 1037 review phase-transition
amrmelsayed Aug 10, 2026
68fde13
[PIR #1037] Review + retrospective
amrmelsayed Aug 10, 2026
edbdc1d
chore(porch): 1037 record PR #1382
amrmelsayed Aug 10, 2026
d7de7e6
chore(porch): 1037 review build-complete
amrmelsayed Aug 10, 2026
b11dd87
[PIR #1037] Address consultation notes: stale docblocks, document ter…
amrmelsayed Aug 10, 2026
945e3b4
[PIR #1037] Fix consultation findings: mode-independent commenting ra…
amrmelsayed Aug 10, 2026
967a6f9
chore(porch): 1037 pr gate-requested
amrmelsayed Aug 10, 2026
e4725db
chore(porch): 1037 pr gate-approved
amrmelsayed Aug 10, 2026
f17544e
chore(porch): 1037 protocol complete
amrmelsayed Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 130 additions & 2 deletions apps/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,52 @@
{
"command": "codev.cancelEditReviewComment",
"title": "Codev: Cancel Edit"
},
{
"command": "codev.diffCodelensUseForward",
"title": "Codev: Switch Diff Codelens to Forward Mode",
"icon": "$(terminal)"
},
{
"command": "codev.diffCodelensUseComment",
"title": "Codev: Switch Diff Codelens to Comment Mode",
"icon": "$(comment)"
},
{
"command": "codev.commentSelectionForBuilder",
"title": "Codev: Comment for Builder"
},
{
"command": "codev.submitReview",
"title": "Codev: Submit Review"
},
{
"command": "codev.discardReviewComments",
"title": "Codev: Discard Review Comments"
},
{
"command": "codev.submitBuilderComment",
"title": "Codev: Queue Comment for Builder",
"enablement": "!commentIsEmpty"
},
{
"command": "codev.deleteBuilderComment",
"title": "Codev: Delete Pending Comment",
"icon": "$(trash)"
},
{
"command": "codev.startEditBuilderComment",
"title": "Codev: Edit Pending Comment",
"icon": "$(edit)"
},
{
"command": "codev.saveEditBuilderComment",
"title": "Codev: Save Pending Comment",
"enablement": "!commentIsEmpty"
},
{
"command": "codev.cancelEditBuilderComment",
"title": "Codev: Cancel Pending Comment Edit"
}
],
"menus": {
Expand All @@ -384,6 +430,16 @@
"command": "codev.openMarkdownPreview",
"when": "resourceLangId == markdown && resourcePath =~ /\\/codev\\/(plans|specs|reviews)\\//",
"group": "navigation"
},
{
"command": "codev.diffCodelensUseForward",
"when": "codev.activeEditorIsBuilderFile && codev.diffCodelensMode == 'comment'",
"group": "navigation"
},
{
"command": "codev.diffCodelensUseComment",
"when": "codev.activeEditorIsBuilderFile && codev.diffCodelensMode == 'forward'",
"group": "navigation"
}
],
"commandPalette": [
Expand All @@ -407,6 +463,38 @@
"command": "codev.forwardSelectionToBuilder",
"when": "false"
},
{
"command": "codev.commentSelectionForBuilder",
"when": "false"
},
{
"command": "codev.diffCodelensUseForward",
"when": "false"
},
{
"command": "codev.diffCodelensUseComment",
"when": "false"
},
{
"command": "codev.submitBuilderComment",
"when": "false"
},
{
"command": "codev.deleteBuilderComment",
"when": "false"
},
{
"command": "codev.startEditBuilderComment",
"when": "false"
},
{
"command": "codev.saveEditBuilderComment",
"when": "false"
},
{
"command": "codev.cancelEditBuilderComment",
"when": "false"
},
{
"command": "codev.openBuilderById",
"when": "false"
Expand Down Expand Up @@ -506,9 +594,14 @@
],
"editor/context": [
{
"command": "codev.forwardSelectionToBuilder",
"when": "codev.activeEditorIsBuilderFile && editorHasSelection",
"command": "codev.commentSelectionForBuilder",
"when": "codev.activeEditorIsBuilderFile",
"group": "codev@1"
},
{
"command": "codev.forwardSelectionToBuilder",
"when": "codev.activeEditorIsBuilderFile",
"group": "codev@2"
}
],
"view/item/context": [
Expand Down Expand Up @@ -740,20 +833,35 @@
"command": "codev.submitReviewComment",
"group": "inline",
"when": "commentController == codev-review && commentThreadIsEmpty"
},
{
"command": "codev.submitBuilderComment",
"group": "inline",
"when": "commentController == codev-builder-review && commentThreadIsEmpty"
}
],
"comments/commentThread/title": [
{
"command": "codev.deleteReviewComment",
"group": "inline@1",
"when": "commentController == codev-review && commentThread == inline-review"
},
{
"command": "codev.deleteBuilderComment",
"group": "inline@1",
"when": "commentController == codev-builder-review && commentThread == pending-builder-comment"
}
],
"comments/comment/title": [
{
"command": "codev.startEditReviewComment",
"group": "group@1",
"when": "commentController == codev-review && comment == inline-review"
},
{
"command": "codev.startEditBuilderComment",
"group": "group@1",
"when": "commentController == codev-builder-review && comment == pending-builder-comment"
}
],
"comments/comment/context": [
Expand All @@ -766,6 +874,16 @@
"command": "codev.cancelEditReviewComment",
"group": "inline@1",
"when": "commentController == codev-review"
},
{
"command": "codev.saveEditBuilderComment",
"group": "inline@2",
"when": "commentController == codev-builder-review"
},
{
"command": "codev.cancelEditBuilderComment",
"group": "inline@1",
"when": "commentController == codev-builder-review"
}
]
},
Expand Down Expand Up @@ -915,6 +1033,16 @@
"default": "editor",
"description": "Where to open Codev terminals (editor area or bottom panel)"
},
"codev.diffCodelensMode": {
"type": "string",
"enum": ["comment", "forward"],
"enumDescriptions": [
"Codelenses compose a review comment in an inline thread; comments queue per builder and reach the PTY only via Submit Review.",
"Codelenses type the file/line reference straight into the builder terminal (the original fire-and-forget flow)."
],
"default": "forward",
"description": "Which single action the codelenses in a builder diff offer. The right-click context menu always offers both actions regardless of mode. Toggle via the diff editor's title-bar button; the choice persists per workspace."
},
"codev.maxTerminals": {
"type": "number",
"default": 25,
Expand Down
131 changes: 131 additions & 0 deletions apps/vscode/src/__tests__/builder-review-ranges.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* Regression (#1037, raised by the PR consultation): the builder-review
* commenting ranges must NOT depend on `codev.diffCodelensMode`. The
* `workbench.action.addComment` command — which backs the comment codelens,
* the gutter "+", AND the always-visible context-menu action — validates
* against these ranges, so a comment-mode-only provider silently breaks
* `Codev: Comment for Builder` from the context menu whenever the editor is
* in forward mode, which is the DEFAULT. Ranges must be provided for
* registered builder-diff files in every mode, with file comments enabled.
*/

import { describe, it, expect, beforeEach, vi } from 'vitest';

const h = vi.hoisted(() => {
class EventEmitter<T> {
private handlers: Array<(e: T) => void> = [];
event = (fn: (e: T) => void): { dispose(): void } => {
this.handlers.push(fn);
return { dispose() {} };
};
fire(value: T): void {
for (const fn of this.handlers) { fn(value); }
}
dispose(): void {}
}
const state = {
mode: 'forward' as string,
controller: undefined as unknown,
};
return { EventEmitter, state };
});

vi.mock('vscode', () => ({
EventEmitter: h.EventEmitter,
Range: class {
constructor(
public startLine: number,
public startChar: number,
public endLine: number,
public endChar: number,
) {}
},
Selection: class {},
Uri: { file: (fsPath: string) => ({ fsPath, toString: () => `file://${fsPath}` }) },
Disposable: class {
constructor(private fn: () => void) {}
dispose(): void { this.fn(); }
},
MarkdownString: class {
constructor(public value: string) {}
},
CommentMode: { Preview: 1, Editing: 0 },
CommentThreadCollapsibleState: { Collapsed: 0, Expanded: 1 },
comments: {
createCommentController: (id: string, label: string) => {
const controller = {
id,
label,
options: undefined as unknown,
commentingRangeProvider: undefined as unknown,
createCommentThread: vi.fn(),
dispose: vi.fn(),
};
h.state.controller = controller;
return controller;
},
},
commands: {
registerCommand: () => ({ dispose() {} }),
executeCommand: () => Promise.resolve(undefined),
},
window: {
activeTextEditor: undefined,
visibleTextEditors: [],
onDidChangeActiveTextEditor: () => ({ dispose() {} }),
showWarningMessage: vi.fn(),
},
workspace: {
textDocuments: [],
getConfiguration: () => ({ get: () => h.state.mode }),
onDidChangeConfiguration: () => ({ dispose() {} }),
},
languages: { registerCodeLensProvider: () => ({ dispose() {} }) },
}));

const { activateBuilderReviewComments } = await import('../comments/builder-review.js');
const { setDiffInjectSession } = await import('../diff-inject-codelens.js');

const ENTRY = { fsPath: '/wt/pkg/src/a.ts', builderId: 'pir-9', relPath: 'pkg/src/a.ts', hunks: [] };
const DOC = { uri: { fsPath: ENTRY.fsPath }, lineCount: 40 };

const storeStub = {
onDidChangeQueue: () => ({ dispose() {} }),
getWorktreePath: () => '/wt',
registerWorktree: () => {},
load: async () => [],
getComments: () => [],
} as never;

const overviewStub = { getData: () => null } as never;

interface Controller {
commentingRangeProvider: {
provideCommentingRanges(doc: unknown): { enableFileComments: boolean; ranges: unknown[] } | unknown[];
};
}

beforeEach(() => {
setDiffInjectSession([]);
activateBuilderReviewComments({ subscriptions: [] } as never, storeStub, overviewStub);
setDiffInjectSession([ENTRY]);
});

describe('builder-review commenting ranges are mode-independent', () => {
it.each(['forward', 'comment', 'garbage'])(
'provides ranges + file comments for a registered file in %s mode',
mode => {
h.state.mode = mode;
const provider = (h.state.controller as Controller).commentingRangeProvider;
const result = provider.provideCommentingRanges(DOC) as { enableFileComments: boolean; ranges: unknown[] };
expect(result.enableFileComments).toBe(true);
expect(result.ranges.length).toBeGreaterThan(0);
},
);

it('provides nothing for an unregistered file', () => {
const provider = (h.state.controller as Controller).commentingRangeProvider;
const result = provider.provideCommentingRanges({ uri: { fsPath: '/elsewhere.ts' }, lineCount: 5 });
expect(result).toEqual([]);
});
});
Loading
Loading