@astrofoundry/pi-astro 0.5.1 → 0.6.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.
@@ -0,0 +1,85 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { constants } from "fs";
3
+ import { access as fsAccess, readFile as fsReadFile, unlink as fsUnlink, writeFile as fsWriteFile } from "fs/promises";
4
+
5
+ import type { Workspace } from "./types.ts";
6
+
7
+ export function createRealWorkspace(pi: ExtensionAPI): Workspace {
8
+ const readCache = new Map<string, string>();
9
+ return {
10
+ readText: async (absolutePath: string) => {
11
+ if (readCache.has(absolutePath)) return readCache.get(absolutePath)!;
12
+ const content = await fsReadFile(absolutePath, "utf-8");
13
+ readCache.set(absolutePath, content);
14
+ return content;
15
+ },
16
+ writeText: async (absolutePath: string, content: string) => {
17
+ // Skip the write (and the file-modified event) when content is
18
+ // identical to what we last read. Prevents thrashing downstream
19
+ // consumers (watchers, context-guard) after no-op dedups.
20
+ const existing = readCache.get(absolutePath);
21
+ if (existing === content) return;
22
+ readCache.delete(absolutePath);
23
+ await fsWriteFile(absolutePath, content, "utf-8");
24
+ pi.events.emit("context-guard:file-modified", { path: absolutePath });
25
+ },
26
+ deleteFile: async (absolutePath: string) => {
27
+ readCache.delete(absolutePath);
28
+ await fsUnlink(absolutePath);
29
+ pi.events.emit("context-guard:file-modified", { path: absolutePath });
30
+ },
31
+ exists: async (absolutePath: string) => {
32
+ try {
33
+ await fsAccess(absolutePath, constants.F_OK);
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ },
39
+ checkWriteAccess: (absolutePath: string) => fsAccess(absolutePath, constants.R_OK | constants.W_OK),
40
+ };
41
+ }
42
+
43
+ export function createVirtualWorkspace(cwd: string): Workspace {
44
+ const state = new Map<string, string | null>();
45
+
46
+ async function ensureLoaded(absolutePath: string): Promise<void> {
47
+ if (state.has(absolutePath)) return;
48
+ try {
49
+ const content = await fsReadFile(absolutePath, "utf-8");
50
+ state.set(absolutePath, content);
51
+ } catch {
52
+ state.set(absolutePath, null);
53
+ }
54
+ }
55
+
56
+ return {
57
+ readText: async (absolutePath) => {
58
+ await ensureLoaded(absolutePath);
59
+ const content = state.get(absolutePath);
60
+ if (content === null || content === undefined) {
61
+ throw new Error(`File not found: ${absolutePath.replace(`${cwd}/`, "")}`);
62
+ }
63
+ return content;
64
+ },
65
+ writeText: async (absolutePath, content) => {
66
+ state.set(absolutePath, content);
67
+ },
68
+ deleteFile: async (absolutePath) => {
69
+ await ensureLoaded(absolutePath);
70
+ if (state.get(absolutePath) === null) {
71
+ throw new Error(`File not found: ${absolutePath.replace(`${cwd}/`, "")}`);
72
+ }
73
+ state.set(absolutePath, null);
74
+ },
75
+ exists: async (absolutePath) => {
76
+ await ensureLoaded(absolutePath);
77
+ return state.get(absolutePath) !== null;
78
+ },
79
+ checkWriteAccess: async (absolutePath: string) => {
80
+ // Check real-fs write permission during the virtual preflight so
81
+ // that read-only files fail fast *before* any real file is touched.
82
+ await fsAccess(absolutePath, constants.W_OK);
83
+ },
84
+ };
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrofoundry/pi-astro",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Personal pi customizations (extensions, skills, prompts, themes) for the pi coding agent.",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -52,7 +52,9 @@
52
52
  "@mariozechner/pi-ai": "^0.70.0",
53
53
  "@mariozechner/pi-coding-agent": "^0.70.0",
54
54
  "@mariozechner/pi-tui": "^0.70.0",
55
+ "@types/diff": "^8.0.0",
55
56
  "@types/node": "^25.6.0",
57
+ "@vitest/coverage-v8": "^4.1.5",
56
58
  "eslint": "^10.2.1",
57
59
  "globals": "^17.5.0",
58
60
  "typebox": "^1.1.33",
@@ -60,11 +62,15 @@
60
62
  "typescript-eslint": "^8.59.0",
61
63
  "vitest": "^4.1.5"
62
64
  },
65
+ "dependencies": {
66
+ "@google/genai": "^1.50.1",
67
+ "diff": "^9.0.0"
68
+ },
63
69
  "scripts": {
64
- "test": "vitest run --passWithNoTests",
70
+ "test": "vitest run --coverage",
65
71
  "lint": "eslint .",
66
72
  "typecheck": "tsc --noEmit",
67
- "check": "tsc --noEmit && eslint . && vitest run --passWithNoTests",
73
+ "check": "tsc --noEmit && eslint . && vitest run --coverage",
68
74
  "release:patch": "v=$(pnpm version patch --no-git-tag-version) && git add -A && git commit -m \"Release $v\" && git tag $v && git push && git push origin $v",
69
75
  "release:minor": "v=$(pnpm version minor --no-git-tag-version) && git add -A && git commit -m \"Release $v\" && git tag $v && git push && git push origin $v",
70
76
  "release:major": "v=$(pnpm version major --no-git-tag-version) && git add -A && git commit -m \"Release $v\" && git tag $v && git push && git push origin $v"