Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 4 additions & 18 deletions apps/hook/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,7 @@ if (args[0] === "sessions") {
server.stop();

// Output feedback (captured by slash command)
if (result.exit) {
console.log("Review session closed without feedback.");
} else if (result.approved) {
if (result.approved) {
console.log("Code review completed — no changes requested.");
} else {
console.log(result.feedback);
Expand Down Expand Up @@ -555,11 +553,7 @@ if (args[0] === "sessions") {
server.stop();

// Output feedback (captured by slash command)
if (result.exit) {
console.log("Annotation session closed without feedback.");
} else {
console.log(result.feedback || "No feedback provided.");
}
console.log(result.feedback || "No feedback provided.");
process.exit(0);

} else if (args[0] === "annotate-last" || args[0] === "last") {
Expand Down Expand Up @@ -673,11 +667,7 @@ if (args[0] === "sessions") {

server.stop();

if (result.exit) {
console.log("Annotation session closed without feedback.");
} else {
console.log(result.feedback || "No feedback provided.");
}
console.log(result.feedback || "No feedback provided.");
process.exit(0);

} else if (args[0] === "archive") {
Expand Down Expand Up @@ -862,11 +852,7 @@ if (args[0] === "sessions") {
await Bun.sleep(1500);
server.stop();

if (result.exit) {
console.log("Annotation session closed without feedback.");
} else {
console.log(result.feedback || "No feedback provided.");
}
console.log(result.feedback || "No feedback provided.");
process.exit(0);

} else if (args[0] === "improve-context") {
Expand Down
12 changes: 0 additions & 12 deletions apps/opencode-plugin/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ export async function handleReviewCommand(
await Bun.sleep(1500);
server.stop();

if (result.exit) {
return;
}

if (result.feedback) {
// @ts-ignore - Event properties contain sessionID
const sessionId = event.properties?.sessionID;
Expand Down Expand Up @@ -185,10 +181,6 @@ export async function handleAnnotateCommand(
await Bun.sleep(1500);
server.stop();

if (result.exit) {
return;
}

if (result.feedback) {
// @ts-ignore - Event properties contain sessionID
const sessionId = event.properties?.sessionID;
Expand Down Expand Up @@ -274,10 +266,6 @@ export async function handleAnnotateLastCommand(
await Bun.sleep(1500);
server.stop();

if (result.exit) {
return null;
}

return result.feedback || null;
}

Expand Down
12 changes: 3 additions & 9 deletions apps/pi-extension/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,7 @@ export default function plannotator(pi: ExtensionAPI): void {
const prUrl = args?.trim() || undefined;
const isPRReview = prUrl?.startsWith("http://") || prUrl?.startsWith("https://");
const result = await openCodeReview(ctx, { prUrl });
if (result.exit) {
ctx.ui.notify("Code review session closed.", "info");
} else if (result.feedback) {
if (result.feedback) {
if (result.approved) {
pi.sendUserMessage(
`# Code Review\n\nCode review completed — no changes requested.`,
Expand Down Expand Up @@ -426,9 +424,7 @@ export default function plannotator(pi: ExtensionAPI): void {

try {
const result = await openMarkdownAnnotation(ctx, absolutePath, markdown, mode ?? "annotate", folderPath);
if (result.exit) {
ctx.ui.notify("Annotation session closed.", "info");
} else if (result.feedback) {
if (result.feedback) {
const header = isFolder
? `# Markdown Annotations\n\nFolder: ${absolutePath}\n\n`
: `# Markdown Annotations\n\nFile: ${absolutePath}\n\n`;
Expand Down Expand Up @@ -468,9 +464,7 @@ export default function plannotator(pi: ExtensionAPI): void {

try {
const result = await openLastMessageAnnotation(ctx, lastText);
if (result.exit) {
ctx.ui.notify("Annotation session closed.", "info");
} else if (result.feedback) {
if (result.feedback) {
pi.sendUserMessage(
`# Message Annotations\n\n${result.feedback}\n\nPlease address the annotation feedback above.`,
);
Expand Down
6 changes: 3 additions & 3 deletions apps/pi-extension/plannotator-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export async function openPlanReviewBrowser(
export async function openCodeReview(
ctx: ExtensionContext,
options: { cwd?: string; defaultBranch?: string; diffType?: DiffType; prUrl?: string } = {},
): Promise<{ approved: boolean; feedback?: string; annotations?: unknown[]; agentSwitch?: string; exit?: boolean }> {
): Promise<{ approved: boolean; feedback?: string; annotations?: unknown[]; agentSwitch?: string }> {
if (!ctx.hasUI || !reviewHtmlContent) {
throw new Error("Plannotator code review browser is unavailable in this session.");
}
Expand Down Expand Up @@ -367,7 +367,7 @@ export async function openMarkdownAnnotation(
markdown: string,
mode: AnnotateMode,
folderPath?: string,
): Promise<{ feedback: string; exit?: boolean }> {
): Promise<{ feedback: string }> {
if (!ctx.hasUI || !planHtmlContent) {
throw new Error("Plannotator annotation browser is unavailable in this session.");
}
Expand Down Expand Up @@ -402,7 +402,7 @@ export async function openMarkdownAnnotation(
export async function openLastMessageAnnotation(
ctx: ExtensionContext,
lastText: string,
): Promise<{ feedback: string; exit?: boolean }> {
): Promise<{ feedback: string }> {
return openMarkdownAnnotation(ctx, "last-message", lastText, "annotate-last");
}

Expand Down
37 changes: 0 additions & 37 deletions apps/pi-extension/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,43 +214,6 @@ describe("pi review server", () => {
}
});

test("exit endpoint resolves decision with exit flag", async () => {
const homeDir = makeTempDir("plannotator-pi-home-");
const repoDir = initRepo();
process.env.HOME = homeDir;
process.chdir(repoDir);
process.env.PLANNOTATOR_PORT = String(await reservePort());

const gitContext = await getGitContext();
const diff = await runGitDiff("uncommitted", gitContext.defaultBranch);

const server = await startReviewServer({
rawPatch: diff.patch,
gitRef: diff.label,
error: diff.error,
diffType: "uncommitted",
gitContext,
origin: "pi",
htmlContent: "<!doctype html><html><body>review</body></html>",
});

try {
const exitResponse = await fetch(`${server.url}/api/exit`, { method: "POST" });
expect(exitResponse.status).toBe(200);
expect(await exitResponse.json()).toEqual({ ok: true });

await expect(server.waitForDecision()).resolves.toEqual({
exit: true,
approved: false,
feedback: "",
annotations: [],
agentSwitch: undefined,
});
} finally {
server.stop();
}
});

test("git-add endpoint stages and unstages files in review mode", async () => {
const homeDir = makeTempDir("plannotator-pi-home-");
const repoDir = initRepo();
Expand Down
138 changes: 137 additions & 1 deletion apps/pi-extension/server/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,60 @@ import {
type ObsidianConfig,
type BearConfig,
type OctarineConfig,
type RoamConfig,
type RoamSuggestionPage,
type RoamSuggestionsResult,
type IntegrationResult,
ROAM_API_VERSION,
extractTitle,
generateFrontmatter,
generateFilename,
generateOctarineFrontmatter,
generatePageTitle,
formatRoamDailyNotePage,
majorMinorMatches,
normalizeRoamDailyNoteParent,
normalizeRoamSuggestionsToPages,
frontmatterToAttributeBlocks,
stripFrontmatter,
stripRoamMetadataTags,
stripH1,
buildHashtags,
buildBearContent,
detectObsidianVaults,
DEFAULT_ROAM_PARENT_BLOCK,
} from "../generated/integrations-common.js";
import { sanitizeTag } from "../generated/project.js";
import { callRoamLocalApi } from "./roam-client.js";

export type { ObsidianConfig, BearConfig, OctarineConfig, IntegrationResult };
export type {
ObsidianConfig,
BearConfig,
OctarineConfig,
RoamConfig,
RoamSuggestionPage,
RoamSuggestionsResult,
IntegrationResult,
};
export {
ROAM_API_VERSION,
extractTitle,
generateFrontmatter,
generateFilename,
generateOctarineFrontmatter,
generatePageTitle,
formatRoamDailyNotePage,
majorMinorMatches,
normalizeRoamDailyNoteParent,
normalizeRoamSuggestionsToPages,
frontmatterToAttributeBlocks,
stripFrontmatter,
stripRoamMetadataTags,
stripH1,
buildHashtags,
buildBearContent,
detectObsidianVaults,
DEFAULT_ROAM_PARENT_BLOCK,
};

/** Detect project name from git or cwd (sync). Used by extractTags for note integrations. */
Expand Down Expand Up @@ -148,6 +180,110 @@ export async function saveToObsidian(
}
}

export async function saveToRoam(
config: RoamConfig,
): Promise<IntegrationResult> {
try {
const { frontmatter, body } = stripFrontmatter(config.plan);
const title = generatePageTitle(
body,
config.titleFormat,
config.titleSeparator,
);
const contentMarkdown = [
frontmatterToAttributeBlocks(frontmatter),
stripH1(body).trimStart(),
]
.filter((section) => section.trim().length > 0)
.join("\n\n");

if (config.saveLocation === "daily-note") {
const createPlanBlockResult = await callRoamLocalApi<{ uids?: string[] }>(
config,
"data.block.fromMarkdown",
[
{
location: {
order: "last",
"page-title": {
"daily-note-page": formatRoamDailyNotePage(new Date()),
},
"nest-under-str": normalizeRoamDailyNoteParent(
config.dailyNoteParent,
),
},
"markdown-string": title,
},
],
);

const planBlockUid = createPlanBlockResult.uids?.[0];
if (!planBlockUid) {
return {
success: false,
error: "Roam did not return a block UID for the saved plan",
};
}

if (contentMarkdown.trim().length > 0) {
await callRoamLocalApi<{ uids?: string[] }>(
config,
"data.block.fromMarkdown",
[
{
location: {
order: "last",
"parent-uid": planBlockUid,
},
"markdown-string": contentMarkdown,
},
],
);
}

return {
success: true,
path: `roam:${config.graphType}:${config.graphName}/${planBlockUid}`,
};
}

const markdown = [
frontmatterToAttributeBlocks(frontmatter),
DEFAULT_ROAM_PARENT_BLOCK,
stripH1(body).trimStart(),
]
.filter((section) => section.trim().length > 0)
.join("\n\n");

const result = await callRoamLocalApi<{
uid?: string;
title?: string;
page?: { uid?: string; title?: string };
}>(
config,
"data.page.fromMarkdown",
[
{
page: { title },
"markdown-string": markdown,
},
],
);

const pathId =
result.page?.uid ?? result.uid ?? result.page?.title ?? result.title ?? title;
return {
success: true,
path: `roam:${config.graphType}:${config.graphName}/${pathId}`,
};
} catch (err) {
return {
success: false,
error: err instanceof Error ? err.message : "Unknown error",
};
}
}

export async function saveToBear(
config: BearConfig,
): Promise<IntegrationResult> {
Expand Down
Loading