@plannotator/pi-extension 0.19.23 → 0.19.24

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.
@@ -7,22 +7,24 @@
7
7
  */
8
8
 
9
9
  import { join } from "node:path";
10
- import { homedir, tmpdir } from "node:os";
10
+ import { tmpdir } from "node:os";
11
11
  import { appendFile, mkdir, unlink, writeFile, readFile } from "node:fs/promises";
12
12
  import { existsSync } from "node:fs";
13
13
  import { toRelativePath } from "./path-utils.js";
14
+ import { getPlannotatorDataDir } from "./data-dir";
14
15
 
15
16
  // ---------------------------------------------------------------------------
16
17
  // Debug log — only active when PLANNOTATOR_DEBUG is set
17
18
  // ---------------------------------------------------------------------------
18
19
 
20
+ const DATA_DIR = getPlannotatorDataDir();
19
21
  const DEBUG_ENABLED = !!process.env.PLANNOTATOR_DEBUG;
20
- const DEBUG_LOG_PATH = join(homedir(), ".plannotator", "codex-review-debug.log");
22
+ const DEBUG_LOG_PATH = join(DATA_DIR, "codex-review-debug.log");
21
23
 
22
24
  async function debugLog(label: string, data?: unknown): Promise<void> {
23
25
  if (!DEBUG_ENABLED) return;
24
26
  try {
25
- await mkdir(join(homedir(), ".plannotator"), { recursive: true });
27
+ await mkdir(DATA_DIR, { recursive: true });
26
28
  const timestamp = new Date().toISOString();
27
29
  const line = data !== undefined
28
30
  ? `[${timestamp}] ${label}: ${typeof data === "string" ? data : JSON.stringify(data, null, 2)}\n`
@@ -79,7 +81,7 @@ const CODEX_REVIEW_SCHEMA = JSON.stringify({
79
81
  additionalProperties: false,
80
82
  });
81
83
 
82
- const SCHEMA_DIR = join(homedir(), ".plannotator");
84
+ const SCHEMA_DIR = DATA_DIR;
83
85
  const SCHEMA_FILE = join(SCHEMA_DIR, "codex-review-schema.json");
84
86
  let schemaMaterialized = false;
85
87
 
@@ -6,8 +6,8 @@
6
6
  * Runtime-agnostic: uses only node:fs, node:os, node:child_process.
7
7
  */
8
8
 
9
- import { homedir } from "os";
10
9
  import { join } from "path";
10
+ import { getPlannotatorDataDir } from "./data-dir";
11
11
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
12
12
  import { execSync } from "child_process";
13
13
 
@@ -40,6 +40,7 @@ export type PromptSectionOverrides = Record<string, string | undefined>;
40
40
 
41
41
  export type PromptRuntime =
42
42
  | "claude-code"
43
+ | "amp"
43
44
  | "droid"
44
45
  | "opencode"
45
46
  | "copilot-cli"
@@ -128,7 +129,7 @@ export interface PlannotatorConfig {
128
129
  pfmReminder?: boolean;
129
130
  }
130
131
 
131
- const CONFIG_DIR = join(homedir(), ".plannotator");
132
+ const CONFIG_DIR = getPlannotatorDataDir();
132
133
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
133
134
 
134
135
  /**
@@ -0,0 +1,43 @@
1
+ // @generated — DO NOT EDIT. Source: packages/shared/data-dir.ts
2
+ /**
3
+ * Plannotator Data Directory
4
+ *
5
+ * Returns the base directory for all Plannotator data files.
6
+ *
7
+ * Priority:
8
+ * 1. PLANNOTATOR_DATA_DIR environment variable (with ~ expansion)
9
+ * 2. Default: ~/.plannotator
10
+ *
11
+ * This mirrors PASTE_DATA_DIR for the paste service and allows users
12
+ * to relocate all data (plans, history, drafts, config, hooks, sessions,
13
+ * debug logs, IPC registry, etc.) via a single variable — useful for
14
+ * XDG-style home directory cleanliness on Unix systems.
15
+ */
16
+
17
+ import { homedir } from "os";
18
+ import { join, resolve } from "path";
19
+
20
+ /**
21
+ * Resolve the Plannotator data directory.
22
+ *
23
+ * If PLANNOTATOR_DATA_DIR is set and non-empty, the value is used
24
+ * as the base directory. Leading ~ is expanded to the user's home
25
+ * directory.
26
+ *
27
+ * Falls back to ~/.plannotator when the env var is absent or empty.
28
+ */
29
+ export function getPlannotatorDataDir(): string {
30
+ const envDir = process.env.PLANNOTATOR_DATA_DIR?.trim();
31
+ if (!envDir) {
32
+ return join(homedir(), ".plannotator");
33
+ }
34
+
35
+ // Expand ~ to home directory
36
+ const home = homedir();
37
+ if (envDir === "~") return home;
38
+ if (envDir.startsWith("~/") || envDir.startsWith("~\\")) {
39
+ return join(home, envDir.slice(2));
40
+ }
41
+
42
+ return resolve(envDir);
43
+ }
@@ -9,16 +9,16 @@
9
9
  * Runtime-agnostic: uses only node:fs, node:path, node:os, node:crypto.
10
10
  */
11
11
 
12
- import { homedir } from "os";
13
12
  import { join } from "path";
14
13
  import { mkdirSync, writeFileSync, readFileSync, unlinkSync, existsSync } from "fs";
15
14
  import { createHash } from "crypto";
15
+ import { getPlannotatorDataDir } from "./data-dir";
16
16
 
17
17
  /**
18
18
  * Get the drafts directory, creating it if needed.
19
19
  */
20
20
  export function getDraftDir(): string {
21
- const dir = join(homedir(), ".plannotator", "drafts");
21
+ const dir = join(getPlannotatorDataDir(), "drafts");
22
22
  mkdirSync(dir, { recursive: true });
23
23
  return dir;
24
24
  }
@@ -18,15 +18,17 @@
18
18
  * - Same trust model as ~/.plannotator/config.json
19
19
  */
20
20
 
21
- import { homedir } from "os";
22
21
  import { join } from "path";
23
22
  import { readFileSync, statSync } from "fs";
23
+ import { getPlannotatorDataDir } from "./data-dir";
24
24
 
25
- /** Base directory for hook-injectable files (new path) */
26
- const HOOKS_BASE_DIR = join(homedir(), ".plannotator", "hooks");
25
+ const DATA_DIR = getPlannotatorDataDir();
27
26
 
28
- /** Legacy base directory (pre-migration path) */
29
- const LEGACY_BASE_DIR = join(homedir(), ".plannotator");
27
+ /** Hooks subdirectory (preferred location) */
28
+ const HOOKS_BASE_DIR = join(DATA_DIR, "hooks");
29
+
30
+ /** Fallback: hooks placed directly in the data dir (pre-hooks-subdir layout) */
31
+ const LEGACY_BASE_DIR = DATA_DIR;
30
32
 
31
33
  /** Maximum file size to read (50 KB) */
32
34
  const MAX_FILE_SIZE = 50 * 1024;
@@ -6,11 +6,11 @@
6
6
  * Self-hosted instances are supported via the --hostname flag.
7
7
  */
8
8
 
9
- import { homedir } from "os";
10
9
  import { join } from "path";
11
10
  import { mkdirSync, writeFileSync } from "fs";
12
11
  import type { PRRuntime, PRMetadata, PRContext, PRReviewFileComment, CommandResult } from "./pr-types";
13
12
  import { encodeApiFilePath } from "./pr-types";
13
+ import { getPlannotatorDataDir } from "./data-dir";
14
14
 
15
15
  // GitLab-specific MRRef shape (used internally)
16
16
  interface GlMRRef {
@@ -563,7 +563,7 @@ export async function submitGlMRReview(
563
563
  .filter((c): c is PRReviewFileComment => c !== null);
564
564
  let savedTo: string | null = null;
565
565
  try {
566
- const dir = join(homedir(), ".plannotator", "failed-comments");
566
+ const dir = join(getPlannotatorDataDir(), "failed-comments");
567
567
  mkdirSync(dir, { recursive: true });
568
568
  const slug = `${ref.host}-${ref.projectPath.replace(/\//g, "_")}-mr${ref.iid}-${Date.now()}`;
569
569
  savedTo = join(dir, `${slug}.json`);
@@ -17,6 +17,7 @@ export function resolveTemplate(
17
17
 
18
18
  export const PLAN_TOOL_NAMES: Record<PromptRuntime, string> = {
19
19
  "claude-code": "ExitPlanMode",
20
+ amp: "ExitPlanMode",
20
21
  droid: "ExitPlanMode",
21
22
  opencode: "submit_plan",
22
23
  "copilot-cli": "exit_plan_mode",
@@ -8,11 +8,13 @@
8
8
  * Runtime-agnostic: uses only node:fs, node:path, node:os.
9
9
  */
10
10
 
11
- import { homedir } from "os";
12
11
  import { join, resolve, sep } from "path";
13
12
  import { mkdirSync, writeFileSync, readFileSync, readdirSync, statSync, existsSync } from "fs";
14
13
  import { sanitizeTag } from "./project";
15
14
  import { resolveUserPath } from "./resolve-file";
15
+ import { getPlannotatorDataDir } from "./data-dir";
16
+
17
+ const DATA_DIR = getPlannotatorDataDir();
16
18
 
17
19
  /**
18
20
  * Get the plan storage directory, creating it if needed.
@@ -25,7 +27,7 @@ export function getPlanDir(customPath?: string | null): string {
25
27
  if (customPath?.trim()) {
26
28
  planDir = resolveUserPath(customPath);
27
29
  } else {
28
- planDir = join(homedir(), ".plannotator", "plans");
30
+ planDir = join(DATA_DIR, "plans");
29
31
  }
30
32
 
31
33
  mkdirSync(planDir, { recursive: true });
@@ -200,7 +202,7 @@ export function readArchivedPlan(filename: string, customPath?: string | null):
200
202
  * Not affected by the customPath setting (that only affects decision saves).
201
203
  */
202
204
  export function getHistoryDir(project: string, slug: string): string {
203
- const historyDir = join(homedir(), ".plannotator", "history", project, slug);
205
+ const historyDir = join(DATA_DIR, "history", project, slug);
204
206
  mkdirSync(historyDir, { recursive: true });
205
207
  return historyDir;
206
208
  }
@@ -267,7 +269,7 @@ export function getPlanVersion(
267
269
  slug: string,
268
270
  version: number
269
271
  ): string | null {
270
- const historyDir = join(homedir(), ".plannotator", "history", project, slug);
272
+ const historyDir = join(DATA_DIR, "history", project, slug);
271
273
  const fileName = `${String(version).padStart(3, "0")}.md`;
272
274
  const filePath = join(historyDir, fileName);
273
275
 
@@ -287,7 +289,7 @@ export function getPlanVersionPath(
287
289
  slug: string,
288
290
  version: number
289
291
  ): string | null {
290
- const historyDir = join(homedir(), ".plannotator", "history", project, slug);
292
+ const historyDir = join(DATA_DIR, "history", project, slug);
291
293
  const fileName = `${String(version).padStart(3, "0")}.md`;
292
294
  const filePath = join(historyDir, fileName);
293
295
  return existsSync(filePath) ? filePath : null;
@@ -298,7 +300,7 @@ export function getPlanVersionPath(
298
300
  * Returns 0 if the directory doesn't exist.
299
301
  */
300
302
  export function getVersionCount(project: string, slug: string): number {
301
- const historyDir = join(homedir(), ".plannotator", "history", project, slug);
303
+ const historyDir = join(DATA_DIR, "history", project, slug);
302
304
  try {
303
305
  const entries = readdirSync(historyDir);
304
306
  return entries.filter((e) => /^\d+\.md$/.test(e)).length;
@@ -315,7 +317,7 @@ export function listVersions(
315
317
  project: string,
316
318
  slug: string
317
319
  ): Array<{ version: number; timestamp: string }> {
318
- const historyDir = join(homedir(), ".plannotator", "history", project, slug);
320
+ const historyDir = join(DATA_DIR, "history", project, slug);
319
321
  try {
320
322
  const entries = readdirSync(historyDir);
321
323
  const versions: Array<{ version: number; timestamp: string }> = [];
@@ -345,7 +347,7 @@ export function listVersions(
345
347
  export function listProjectPlans(
346
348
  project: string
347
349
  ): Array<{ slug: string; versions: number; lastModified: string }> {
348
- const projectDir = join(homedir(), ".plannotator", "history", project);
350
+ const projectDir = join(DATA_DIR, "history", project);
349
351
  try {
350
352
  const entries = readdirSync(projectDir, { withFileTypes: true });
351
353
  const plans: Array<{ slug: string; versions: number; lastModified: string }> = [];
@@ -1,7 +1,8 @@
1
1
  // @generated — DO NOT EDIT. Source: packages/server/tour/tour-review.ts
2
2
  import { join } from "node:path";
3
- import { homedir, tmpdir } from "node:os";
3
+ import { tmpdir } from "node:os";
4
4
  import { mkdir, writeFile, readFile, unlink } from "node:fs/promises";
5
+ import { getPlannotatorDataDir } from "./data-dir";
5
6
  import type { DiffType } from "./review-core.js";
6
7
  import type { PRMetadata } from "./pr-provider.js";
7
8
  import { getLocalDiffInstruction } from "./agent-review-message.js";
@@ -384,7 +385,7 @@ export function buildTourClaudeCommand(prompt: string, model: string = "sonnet",
384
385
  };
385
386
  }
386
387
 
387
- const TOUR_SCHEMA_DIR = join(homedir(), ".plannotator");
388
+ const TOUR_SCHEMA_DIR = getPlannotatorDataDir();
388
389
  const TOUR_SCHEMA_FILE = join(TOUR_SCHEMA_DIR, "tour-schema.json");
389
390
  let tourSchemaMaterialized = false;
390
391
 
package/index.ts CHANGED
@@ -1165,9 +1165,9 @@ Execute each step in order. After completing a step, include [DONE:n] in your re
1165
1165
  pi.on("agent_end", async (_event, ctx) => {
1166
1166
  if (phase === "executing" && justApprovedPlan) {
1167
1167
  justApprovedPlan = false;
1168
- setTimeout(() => {
1169
- pi.sendUserMessage("Continue with the approved plan.");
1170
- }, 0);
1168
+ pi.sendUserMessage("Continue with the approved plan.", {
1169
+ deliverAs: "followUp",
1170
+ });
1171
1171
  return;
1172
1172
  }
1173
1173
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plannotator/pi-extension",
3
- "version": "0.19.23",
3
+ "version": "0.19.24",
4
4
  "type": "module",
5
5
  "description": "Plannotator Pi extension - interactive plan review with annotations, annotate agent messages, and review code/PRs",
6
6
  "author": "backnotprop",