ahead-pi 0.8.4 → 0.8.5

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
@@ -127,7 +127,11 @@ INDEPENDENT HUMAN REVIEWS AND ACCEPTS
127
127
 
128
128
  A draft branch or draft PR may exist earlier. The handoff gate is requesting human review or marking the PR ready, not ordinary draft pushes.
129
129
 
130
- `/ahead-review` opens the first editor-neutral review workbench. It fingerprints the current Git changeset, shows the changed paths and diff in Pi, can open a selected path in VS Code when detected or configured with `AHEAD_EDITOR=vscode`, requests snapshot-bound AI findings, and opens the required human disposition or independent-review record. Any engineering change produces a new fingerprint and requires review again. `.ahead/**` evidence is excluded from that fingerprint so recording the review does not invalidate it.
130
+ `/ahead-review` opens the first editor-neutral review workbench. It fingerprints the current Git changeset, shows the changed paths and diff in Pi, can open a selected path in VS Code or Zed when detected or configured with `AHEAD_EDITOR=vscode` or `AHEAD_EDITOR=zed`, requests snapshot-bound AI findings, and opens the required human disposition or independent-review record. Any engineering change produces a new fingerprint and requires review again. `.ahead/**` evidence is excluded from that fingerprint so recording the review does not invalidate it.
131
+
132
+ VS Code is detected from its terminal environment. For a deterministic Zed terminal, add `"AHEAD_EDITOR": "zed"` under Zed's `terminal.env` settings; Zed's CLI must be installed. `AHEAD_EDITOR=none` disables external opening.
133
+
134
+ Human-owned plans and other artifacts open in that detected editor as a temporary Markdown draft. Save your work, then close only that temporary tab; AHEAD copies the draft into `.ahead/runs/` after the tab closes and the form passes validation. The host editor stays open, and saving alone does not record the artifact.
131
135
 
132
136
  ## Human commands
133
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ahead-pi",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "AHEAD workflow enforcement and context for Pi",
5
5
  "keywords": [
6
6
  "ahead",
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { execFile } from "node:child_process";
2
- import { readFile } from "node:fs/promises";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
3
5
  import { fileURLToPath } from "node:url";
4
6
  import type {
5
7
  ExtensionAPI,
@@ -32,6 +34,7 @@ import {
32
34
  import { showReferenceViewer } from "./reference-viewer.js";
33
35
  import {
34
36
  collectReviewSnapshot,
37
+ detectEditor,
35
38
  extractFindingIds,
36
39
  extractReviewFingerprint,
37
40
  openInConfiguredEditor,
@@ -1367,7 +1370,7 @@ async function openReviewWorkbench(pi: ExtensionAPI, ctx: ExtensionCommandContex
1367
1370
  const path = await ctx.ui.select("Open changed file", snapshot.changed_files);
1368
1371
  if (path && !openInConfiguredEditor(store.projectRoot, { path })) {
1369
1372
  ctx.ui.notify(
1370
- `No supported editor was detected. Open ${path} from the repository, or set AHEAD_EDITOR=vscode.`,
1373
+ `No supported editor was detected. Open ${path} from the repository, or set AHEAD_EDITOR=vscode or AHEAD_EDITOR=zed.`,
1371
1374
  "info",
1372
1375
  );
1373
1376
  }
@@ -1655,7 +1658,8 @@ async function recordHumanArtifact(
1655
1658
  // "failed" (timeout, unparseable output, transient error) stays silent:
1656
1659
  // the plain template is the fallback and needs no apology.
1657
1660
  }
1658
- let content = await ctx.ui.editor(
1661
+ let content = await editHumanArtifact(
1662
+ ctx,
1659
1663
  `AHEAD mode · ${artifact.title} · write in your own words`,
1660
1664
  template,
1661
1665
  );
@@ -1684,7 +1688,8 @@ async function recordHumanArtifact(
1684
1688
  ctx.ui.notify("Draft discarded. Nothing was saved.", "info");
1685
1689
  return;
1686
1690
  }
1687
- const revised = await ctx.ui.editor(
1691
+ const revised = await editHumanArtifact(
1692
+ ctx,
1688
1693
  `AHEAD mode · ${artifact.title} · finish the required fields`,
1689
1694
  content,
1690
1695
  );
@@ -1726,6 +1731,29 @@ async function recordHumanArtifact(
1726
1731
  );
1727
1732
  }
1728
1733
 
1734
+ async function editHumanArtifact(
1735
+ ctx: ExtensionCommandContext,
1736
+ title: string,
1737
+ content: string,
1738
+ ): Promise<string | undefined> {
1739
+ if (!detectEditor()) {
1740
+ return ctx.ui.editor(title, content);
1741
+ }
1742
+
1743
+ const temporaryRoot = await mkdtemp(join(tmpdir(), "ahead-artifact-"));
1744
+ const temporaryPath = join(temporaryRoot, "artifact.md");
1745
+ try {
1746
+ await writeFile(temporaryPath, content, "utf8");
1747
+ if (openInConfiguredEditor(temporaryRoot, { path: "artifact.md" }, { wait: true })) {
1748
+ return await readFile(temporaryPath, "utf8");
1749
+ }
1750
+ ctx.ui.notify("The configured editor could not be opened; using Pi's editor instead.", "info");
1751
+ return await ctx.ui.editor(title, content);
1752
+ } finally {
1753
+ await rm(temporaryRoot, { recursive: true, force: true });
1754
+ }
1755
+ }
1756
+
1729
1757
  async function humanArtifactTemplate(
1730
1758
  store: RunStore,
1731
1759
  state: RunState,
package/src/review.ts CHANGED
@@ -13,6 +13,8 @@ export interface SourceLocation {
13
13
  column?: number;
14
14
  }
15
15
 
16
+ export type EditorKind = "vscode" | "zed";
17
+
16
18
  export interface ReviewSnapshot {
17
19
  api_version: "ahead.review-snapshot/v0.1";
18
20
  base_ref: string;
@@ -242,16 +244,23 @@ export function validateReviewDisposition(
242
244
  return errors;
243
245
  }
244
246
 
245
- export function openInConfiguredEditor(root: string, location: SourceLocation): boolean {
246
- const command = configuredEditorCommand();
247
- if (!command) {
247
+ export function openInConfiguredEditor(
248
+ root: string,
249
+ location: SourceLocation,
250
+ options: { wait?: boolean } = {},
251
+ ): boolean {
252
+ const editor = detectEditor();
253
+ if (!editor) {
248
254
  return false;
249
255
  }
250
256
  const absolute = safeRepositoryPath(root, location.path);
251
257
  const line = location.line ?? 1;
252
258
  const column = location.column ?? 1;
253
259
  const target = `${absolute}:${line}:${column}`;
254
- const result = spawnSync(command, ["--goto", target], { stdio: "ignore" });
260
+ const command = editor === "zed" ? "zed" : "code";
261
+ const wait = options.wait ? ["--wait"] : [];
262
+ const args = editor === "zed" ? [...wait, target] : [...wait, "--goto", target];
263
+ const result = spawnSync(command, args, { stdio: "ignore" });
255
264
  return !result.error && result.status === 0;
256
265
  }
257
266
 
@@ -280,16 +289,22 @@ function resolveReviewBase(root: string): string {
280
289
  return "HEAD";
281
290
  }
282
291
 
283
- function configuredEditorCommand(): string | undefined {
292
+ export function detectEditor(): EditorKind | undefined {
284
293
  const explicit = process.env.AHEAD_EDITOR?.trim();
285
294
  if (explicit === "vscode" || explicit === "code") {
286
- return "code";
295
+ return "vscode";
296
+ }
297
+ if (explicit === "zed") {
298
+ return "zed";
287
299
  }
288
300
  if (explicit === "none") {
289
301
  return undefined;
290
302
  }
303
+ if (process.env.ZED === "1" || process.env.TERM_PROGRAM === "zed") {
304
+ return "zed";
305
+ }
291
306
  return process.env.VSCODE_IPC_HOOK_CLI || process.env.TERM_PROGRAM === "vscode"
292
- ? "code"
307
+ ? "vscode"
293
308
  : undefined;
294
309
  }
295
310