pi-openspec-context 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-01-15
9
+
10
+ ### Added
11
+
12
+ - **Initial Release**: Lightweight Pi extension for automatic OpenSpec context injection
13
+ - **Core Feature**: Single-responsibility automatic context extraction and system prompt injection at `before_agent_start` hook
14
+ - **Smart Detection**: Two-tier detection strategy
15
+ - Traverses upward to find `openspec/root` directories
16
+ - Queries `openspec store list` to match registered stores
17
+ - **Flexible Context Extraction**:
18
+ - Supports both OpenSpec roots (`openspec context`)
19
+ - Supports OpenSpec stores (`openspec context --store <id>`)
20
+ - **In-Memory Caching**: Per-workspace context caching to minimize CLI invocations
21
+ - Root targets cached by absolute path
22
+ - Store targets cached by ID
23
+ - **Resilient Execution**: Hardened with timeouts and error handling
24
+ - 10-second default timeout (configurable via `OPENSPEC_CONTEXT_TIMEOUT_MS`)
25
+ - Graceful fallback on missing `openspec` CLI
26
+ - Graceful fallback on timeouts and CLI errors
27
+ - Graceful fallback on empty output
28
+ - **Output Sanitization**: ANSI color code stripping for clean context injection
29
+ - **Type Safety**: Fully typed TypeScript with strict mode
30
+ - Discriminated unions for target types
31
+ - Strict null checks enabled
32
+ - No implicit `any` types
33
+ - **Complete Test Coverage**: 85+ unit, integration, and E2E tests
34
+ - Unit tests for core modules (cache, detector, runner)
35
+ - Extension lifecycle tests with mock Pi API
36
+ - In-process harness tests with TestSession
37
+ - Docker-based E2E tests simulating real Pi environment
38
+ - Resilience tests for timeout, error, and missing CLI scenarios
39
+ - **Documentation**:
40
+ - Comprehensive README with architecture, installation, and troubleshooting
41
+ - API reference for extension and cache usage
42
+ - Development guide with build/test/lint commands
43
+ - SOLID principles documentation
44
+ - **Package Metadata**:
45
+ - MIT License with attribution to tobias-weiss-ai-xr/pi-openspec
46
+ - Keywords: pi-package, pi, openspec, extension, context, spec-driven-development
47
+ - Pi package configuration for automatic discovery
48
+
49
+ ### Technical Details
50
+
51
+ - **Target Platform**: Node.js with ES2022 module support
52
+ - **Module System**: TypeScript NodeNext ESM with .js import extensions
53
+ - **Parser**: TypeScript with strict mode enabled
54
+ - **Linting**: ESLint with TypeScript support
55
+ - **Testing**: Vitest with Docker-based E2E capabilities
56
+ - **Architecture**: Single-responsibility modules (types, runner, detector, cache, index)
57
+
58
+ ### Breaking Changes
59
+
60
+ None (initial release)
61
+
62
+ ### Migration Guide
63
+
64
+ None (initial release)
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Raphael Bahat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Based on work from tobias-weiss-ai-xr/pi-openspec
package/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # pi-openspec-context
2
+
3
+ A lightweight Pi extension that automatically injects OpenSpec context into system prompts at the start of each agent session.
4
+
5
+ ## Overview
6
+
7
+ `pi-openspec-context` enhances the Pi coding agent by seamlessly integrating OpenSpec specification data into the system prompt. This enables the AI agent to understand project structure, APIs, data models, and other specification-driven context without explicit user prompts.
8
+
9
+ ### Key Features
10
+
11
+ - **Automatic Context Injection**: Detects OpenSpec projects and injects context into `before_agent_start` hook
12
+ - **Smart Caching**: Caches extracted context per workspace to minimize CLI calls
13
+ - **Flexible Detection**: Works with both OpenSpec roots and registered OpenSpec stores
14
+ - **Resilient Fallback**: Gracefully handles missing `openspec` CLI, timeouts, and errors
15
+ - **ANSI Sanitization**: Strips color codes from context output for clean integration
16
+ - **Type-Safe**: Fully typed TypeScript implementation with strict mode enabled
17
+
18
+ ## Installation
19
+
20
+ ### As a Pi Package
21
+
22
+ ```bash
23
+ pi install pi-openspec-context
24
+ ```
25
+
26
+ ### Global Installation (for local development)
27
+
28
+ ```bash
29
+ npm install -g pi-openspec-context
30
+ ```
31
+
32
+ Then add to your Pi configuration:
33
+
34
+ ```json
35
+ {
36
+ "extensions": [
37
+ "pi-openspec-context"
38
+ ]
39
+ }
40
+ ```
41
+
42
+ ### Project-Local Installation
43
+
44
+ ```bash
45
+ npm install --save-dev pi-openspec-context
46
+ ```
47
+
48
+ ## Prerequisites
49
+
50
+ ### Required
51
+ - **openspec CLI**: Must be installed and accessible in your PATH
52
+ - Installation: `npm install -g @tobias-weiss-ai-xr/openspec` or `pip install openspec`
53
+ - Verify: `openspec --version`
54
+
55
+ ### Optional
56
+ - Pi coding agent v0.84.0 or later (typically included with Pi installation)
57
+
58
+ ## Architecture & Behavior
59
+
60
+ ### Detection Strategy
61
+
62
+ The extension uses a two-tier detection approach:
63
+
64
+ 1. **OpenSpec Root Detection**: Traverses upward from the current working directory looking for `openspec/config.yaml`
65
+ 2. **Store Detection**: If no root is found, queries `openspec store list --json` to match the current directory against registered stores
66
+
67
+ ### Context Extraction
68
+
69
+ Once a target is detected:
70
+ - **Root Target**: Executes `openspec context` at the root path
71
+ - **Store Target**: Executes `openspec context --store <store-id>` from the current directory
72
+
73
+ ### Caching
74
+
75
+ Extracted context is cached in-memory per target:
76
+ - Root targets are keyed by absolute path
77
+ - Store targets are keyed by store ID
78
+ - Cache persists for the lifetime of the Pi session
79
+
80
+ ### Injection
81
+
82
+ The cleaned context is injected into the system prompt using a marker:
83
+
84
+ ```
85
+ [OpenSpec context]
86
+ <extracted context data>
87
+ ```
88
+
89
+ ## Configuration
90
+
91
+ The extension works with zero configuration. Simply install and enable it.
92
+
93
+ ### Environment Variables
94
+
95
+ Optional environment variable to override the default timeout:
96
+ - `OPENSPEC_CONTEXT_TIMEOUT_MS`: Timeout for `openspec` CLI execution (default: 10000ms)
97
+
98
+ ## Troubleshooting
99
+
100
+ ### "openspec: command not found"
101
+
102
+ **Problem**: The extension runs but no context is injected.
103
+
104
+ **Solution**:
105
+ 1. Verify OpenSpec is installed: `which openspec`
106
+ 2. Install if missing: `npm install -g @tobias-weiss-ai-xr/openspec`
107
+ 3. Check PATH is configured: `echo $PATH`
108
+ 4. Restart Pi after installing OpenSpec
109
+
110
+ ### Context Not Appearing in Prompts
111
+
112
+ **Problem**: Extension is loaded but context not injected.
113
+
114
+ **Checklist**:
115
+ 1. Confirm you're in an OpenSpec root or registered store: `openspec store list`
116
+ 2. Verify OpenSpec can extract context: `openspec context`
117
+ 3. Check extension is loaded: Look for "pi-openspec-context" in Pi logs
118
+ 4. Inspect cache behavior: Add debug logging to extension
119
+
120
+ ### Timeout Errors
121
+
122
+ **Problem**: Extension times out and falls back silently.
123
+
124
+ **Solution**:
125
+ 1. Test OpenSpec manually: `time openspec context`
126
+ 2. Increase timeout if needed: Set `OPENSPEC_CONTEXT_TIMEOUT_MS=30000`
127
+ 3. Check for hung `openspec` processes: `ps aux | grep openspec`
128
+
129
+ ### Empty or Whitespace-Only Context
130
+
131
+ **Problem**: OpenSpec runs but returns empty output.
132
+
133
+ **Solution**:
134
+ 1. Verify OpenSpec project is valid: `openspec validate`
135
+ 2. Check for malformed YAML in `openspec/` directory
136
+ 3. Try manual context extraction: `openspec context`
137
+
138
+ ## API Reference
139
+
140
+ ### Extension Export
141
+
142
+ The package exports a default extension factory:
143
+
144
+ ```typescript
145
+ import extension from 'pi-openspec-context';
146
+
147
+ // Used automatically by Pi's extension loader
148
+ extension(pi);
149
+ ```
150
+
151
+ ### Custom Cache Usage
152
+
153
+ For advanced use cases, you can create an extension with a custom cache:
154
+
155
+ ```typescript
156
+ import { createExtension, contextCache } from 'pi-openspec-context';
157
+
158
+ // Use default shared cache
159
+ const ext = createExtension();
160
+ ext(pi);
161
+
162
+ // Or provide custom cache
163
+ import { OpenSpecContextCache } from 'pi-openspec-context/cache';
164
+ const customCache = new OpenSpecContextCache();
165
+ const extWithCustomCache = createExtension(customCache);
166
+ extWithCustomCache(pi);
167
+ ```
168
+
169
+ ### Cache Methods
170
+
171
+ - `get(target): string | null` - Retrieve cached context
172
+ - `set(target, context): void` - Store context in cache
173
+ - `has(target): boolean` - Check if target is cached
174
+ - `delete(target): boolean` - Remove specific target from cache
175
+ - `clear(): void` - Clear all cached contexts
176
+ - `size(): number` - Get cache entry count
177
+
178
+ ## Development
179
+
180
+ ### Building
181
+
182
+ ```bash
183
+ npm run build
184
+ ```
185
+
186
+ ### Testing
187
+
188
+ ```bash
189
+ npm run test # Run all tests
190
+ npm run test:watch # Watch mode
191
+ npm run test:e2e # Docker E2E tests only
192
+ ```
193
+
194
+ ### Type Checking
195
+
196
+ ```bash
197
+ npm run typecheck
198
+ ```
199
+
200
+ ### Linting
201
+
202
+ ```bash
203
+ npm run lint
204
+ ```
205
+
206
+ ## Architecture Notes
207
+
208
+ ### Modules
209
+
210
+ - **`src/types.ts`**: Core type definitions (discriminated unions, interfaces)
211
+ - **`src/runner.ts`**: OpenSpec CLI execution and ANSI sanitization
212
+ - **`src/detector.ts`**: Root and store detection logic
213
+ - **`src/cache.ts`**: In-memory caching with path normalization
214
+ - **`src/index.ts`**: Pi extension lifecycle hook implementation
215
+
216
+ ### SOLID Principles
217
+
218
+ - **Single Responsibility**: Each module has one clear purpose
219
+ - **Open/Closed**: Extension logic is frozen but cacheable via factory function
220
+ - **Liskov Substitution**: Cache implements a standard interface
221
+ - **Interface Segregation**: Types are minimal and focused
222
+ - **Dependency Inversion**: Depends on `PiExecContext` interface, not concrete implementations
223
+
224
+ ### Error Handling
225
+
226
+ All errors are caught and logged silently. The extension falls back gracefully:
227
+ - Missing `openspec` CLI → returns `{}`
228
+ - CLI errors → returns `{}`
229
+ - Timeout → returns `{}`
230
+ - Empty output → returns `{}`
231
+
232
+ This ensures the extension never breaks the agent workflow.
233
+
234
+ ## License
235
+
236
+ MIT License
237
+
238
+ ## Attribution
239
+
240
+ Based on original work by [tobias-weiss-ai-xr/pi-openspec](https://github.com/tobias-weiss-ai-xr/pi-openspec)
241
+
242
+ Created by [Raphael Bahat](https://earendil.works)
@@ -0,0 +1,11 @@
1
+ import type { OpenSpecTarget } from "./types.js";
2
+ export declare function targetToKey(target: OpenSpecTarget): string;
3
+ export declare class OpenSpecContextCache {
4
+ private map;
5
+ get(target: OpenSpecTarget): string | null;
6
+ set(target: OpenSpecTarget, context: string): void;
7
+ has(target: OpenSpecTarget): boolean;
8
+ delete(target: OpenSpecTarget): boolean;
9
+ clear(): void;
10
+ size(): number;
11
+ }
package/dist/cache.js ADDED
@@ -0,0 +1,38 @@
1
+ import * as path from "path";
2
+ export function targetToKey(target) {
3
+ switch (target.type) {
4
+ case "root":
5
+ return "root:" + path.resolve(target.path);
6
+ case "store":
7
+ return "store:" + target.id;
8
+ default:
9
+ // Exhaustiveness check: if we reach here, there's an unhandled case
10
+ const exhaustiveCheck = target;
11
+ throw new Error(`Unknown target type: ${exhaustiveCheck}`);
12
+ }
13
+ }
14
+ export class OpenSpecContextCache {
15
+ map = new Map();
16
+ get(target) {
17
+ const key = targetToKey(target);
18
+ return this.map.get(key) ?? null;
19
+ }
20
+ set(target, context) {
21
+ const key = targetToKey(target);
22
+ this.map.set(key, context);
23
+ }
24
+ has(target) {
25
+ const key = targetToKey(target);
26
+ return this.map.has(key);
27
+ }
28
+ delete(target) {
29
+ const key = targetToKey(target);
30
+ return this.map.delete(key);
31
+ }
32
+ clear() {
33
+ this.map.clear();
34
+ }
35
+ size() {
36
+ return this.map.size;
37
+ }
38
+ }
@@ -0,0 +1,15 @@
1
+ import * as fs from "fs";
2
+ import type { OpenSpecTarget, PiExecContext } from "./types.js";
3
+ /**
4
+ * Finds the OpenSpec root directory by traversing upwards from cwd
5
+ * looking for openspec/config.yaml
6
+ */
7
+ export declare function findOpenSpecRoot(cwd: string, existsFn?: typeof fs.existsSync): string | null;
8
+ /**
9
+ * Finds a store ID that matches the given cwd by querying openspec store list
10
+ */
11
+ export declare function findStoreId(pi: PiExecContext, cwd: string): Promise<string | null>;
12
+ /**
13
+ * Detects the OpenSpec target (root or store) for the given directory
14
+ */
15
+ export declare function detectOpenSpecTarget(pi: PiExecContext, cwd: string): Promise<OpenSpecTarget | null>;
@@ -0,0 +1,83 @@
1
+ import * as path from "path";
2
+ import * as fs from "fs";
3
+ import { runOpenSpec } from "./runner.js";
4
+ /**
5
+ * Finds the OpenSpec root directory by traversing upwards from cwd
6
+ * looking for openspec/config.yaml
7
+ */
8
+ export function findOpenSpecRoot(cwd, existsFn = fs.existsSync) {
9
+ let current = path.resolve(cwd);
10
+ while (true) {
11
+ const configPath = path.join(current, "openspec", "config.yaml");
12
+ if (existsFn(configPath)) {
13
+ return current;
14
+ }
15
+ const parent = path.dirname(current);
16
+ if (parent === current) {
17
+ // Reached root directory (/ on Unix, C:\ on Windows)
18
+ break;
19
+ }
20
+ current = parent;
21
+ }
22
+ return null;
23
+ }
24
+ /**
25
+ * Finds a store ID that matches the given cwd by querying openspec store list
26
+ */
27
+ export async function findStoreId(pi, cwd) {
28
+ // Execute openspec store list --json command
29
+ const output = await runOpenSpec(pi, ["store", "list", "--json"], cwd);
30
+ if (output === null) {
31
+ return null;
32
+ }
33
+ // Parse JSON output
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(output);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ // Verify stores is an array
42
+ if (!Array.isArray(parsed.stores)) {
43
+ return null;
44
+ }
45
+ // Resolve cwd to absolute path
46
+ const resolvedCwd = path.resolve(cwd);
47
+ // Match against each store's root/path/dir
48
+ for (const store of parsed.stores) {
49
+ const storeRoot = store.root || store.path || store.dir;
50
+ if (!storeRoot)
51
+ continue;
52
+ const resolvedRoot = path.resolve(storeRoot);
53
+ // Check for exact match or subdirectory match
54
+ if (resolvedCwd === resolvedRoot ||
55
+ resolvedCwd.startsWith(resolvedRoot + path.sep)) {
56
+ return store.id || null;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Detects the OpenSpec target (root or store) for the given directory
63
+ */
64
+ export async function detectOpenSpecTarget(pi, cwd) {
65
+ // First try to find an OpenSpec root
66
+ const root = findOpenSpecRoot(cwd);
67
+ if (root) {
68
+ return {
69
+ type: "root",
70
+ path: root,
71
+ };
72
+ }
73
+ // Then try to find a store ID
74
+ const storeId = await findStoreId(pi, cwd);
75
+ if (storeId) {
76
+ return {
77
+ type: "store",
78
+ id: storeId,
79
+ cwd,
80
+ };
81
+ }
82
+ return null;
83
+ }
@@ -0,0 +1,12 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { OpenSpecContextCache } from "./cache.js";
3
+ export declare const contextCache: OpenSpecContextCache;
4
+ /**
5
+ * Creates an extension factory with optional custom cache
6
+ * @param cache Optional custom cache instance (defaults to shared contextCache)
7
+ */
8
+ export declare function createExtension(cache?: OpenSpecContextCache): (pi: ExtensionAPI) => void;
9
+ /**
10
+ * Default extension function that registers the before_agent_start listener
11
+ */
12
+ export default function (pi: ExtensionAPI): void;
package/dist/index.js ADDED
@@ -0,0 +1,69 @@
1
+ import { detectOpenSpecTarget } from "./detector.js";
2
+ import { runOpenSpec, sanitizeContext } from "./runner.js";
3
+ import { OpenSpecContextCache } from "./cache.js";
4
+ // Global cache instance for the extension
5
+ export const contextCache = new OpenSpecContextCache();
6
+ /**
7
+ * Creates a before_agent_start handler with the given cache and PI instance
8
+ * @param pi The ExtensionAPI instance
9
+ * @param cache The cache instance to use
10
+ */
11
+ function createBeforeAgentStartHandler(pi, cache) {
12
+ return async (event, ctx) => {
13
+ // Get working directory with fallback chain
14
+ const cwd = event.systemPromptOptions?.cwd ||
15
+ ctx?.cwd ||
16
+ process.cwd();
17
+ // Detect OpenSpec target (root or store)
18
+ const target = await detectOpenSpecTarget(pi, cwd);
19
+ if (!target) {
20
+ return {};
21
+ }
22
+ // Check cache first
23
+ let contextText = cache.get(target);
24
+ // If not cached, run openspec to retrieve context
25
+ if (!contextText) {
26
+ if (target.type === "root") {
27
+ contextText = await runOpenSpec(pi, ["context"], target.path);
28
+ }
29
+ else if (target.type === "store") {
30
+ contextText = await runOpenSpec(pi, ["context", "--store", target.id], target.cwd);
31
+ }
32
+ // Cache the result if we got one
33
+ if (contextText) {
34
+ cache.set(target, contextText);
35
+ }
36
+ }
37
+ // Return empty if no context available
38
+ if (!contextText) {
39
+ return {};
40
+ }
41
+ // Sanitize the context (strip ANSI codes, trim whitespace)
42
+ const cleaned = sanitizeContext(contextText);
43
+ // Return empty if sanitized content is empty
44
+ if (!cleaned) {
45
+ return {};
46
+ }
47
+ // Inject context into system prompt
48
+ return {
49
+ systemPrompt: event.systemPrompt + "\n\n[OpenSpec context]\n" + cleaned,
50
+ };
51
+ };
52
+ }
53
+ /**
54
+ * Creates an extension factory with optional custom cache
55
+ * @param cache Optional custom cache instance (defaults to shared contextCache)
56
+ */
57
+ export function createExtension(cache) {
58
+ const cacheInstance = cache || contextCache;
59
+ return (pi) => {
60
+ pi.on("before_agent_start", createBeforeAgentStartHandler(pi, cacheInstance));
61
+ };
62
+ }
63
+ /**
64
+ * Default extension function that registers the before_agent_start listener
65
+ */
66
+ export default function (pi) {
67
+ const handler = createBeforeAgentStartHandler(pi, contextCache);
68
+ pi.on("before_agent_start", handler);
69
+ }
@@ -0,0 +1,14 @@
1
+ import type { PiExecContext } from "./types.js";
2
+ export declare const DEFAULT_TIMEOUT_MS = 10000;
3
+ /**
4
+ * Strips ANSI color/control codes and trims whitespace
5
+ */
6
+ export declare function stripAnsi(text: string): string;
7
+ /**
8
+ * Sanitizes context output for injection (alias for stripAnsi)
9
+ */
10
+ export declare function sanitizeContext(text: string): string;
11
+ /**
12
+ * Executes openspec CLI command and returns output or null on failure
13
+ */
14
+ export declare function runOpenSpec(pi: PiExecContext, args: string[], cwd: string, timeoutMs?: number): Promise<string | null>;
package/dist/runner.js ADDED
@@ -0,0 +1,39 @@
1
+ export const DEFAULT_TIMEOUT_MS = 10000;
2
+ /**
3
+ * Strips ANSI color/control codes and trims whitespace
4
+ */
5
+ export function stripAnsi(text) {
6
+ return text.replace(/\u001b\[[0-9;]*m/g, "").trim();
7
+ }
8
+ /**
9
+ * Sanitizes context output for injection (alias for stripAnsi)
10
+ */
11
+ export function sanitizeContext(text) {
12
+ return stripAnsi(text);
13
+ }
14
+ /**
15
+ * Executes openspec CLI command and returns output or null on failure
16
+ */
17
+ export async function runOpenSpec(pi, args, cwd, timeoutMs = DEFAULT_TIMEOUT_MS) {
18
+ try {
19
+ const result = await pi.exec("openspec", args, {
20
+ timeout: timeoutMs,
21
+ cwd,
22
+ });
23
+ // Return null if exit code is not 0
24
+ if (result.code !== 0) {
25
+ return null;
26
+ }
27
+ // Strip ANSI codes and trim
28
+ const cleaned = stripAnsi(result.stdout);
29
+ // Return null if output is empty
30
+ if (!cleaned) {
31
+ return null;
32
+ }
33
+ return cleaned;
34
+ }
35
+ catch {
36
+ // Catch all exceptions (ENOENT, timeout, etc.) and return null
37
+ return null;
38
+ }
39
+ }
@@ -0,0 +1,28 @@
1
+ export type OpenSpecTarget = {
2
+ type: "root";
3
+ path: string;
4
+ } | {
5
+ type: "store";
6
+ id: string;
7
+ cwd: string;
8
+ };
9
+ export interface OpenSpecStoreEntry {
10
+ id?: string;
11
+ root?: string;
12
+ path?: string;
13
+ dir?: string;
14
+ }
15
+ export interface OpenSpecStoreListOutput {
16
+ stores?: OpenSpecStoreEntry[];
17
+ }
18
+ export interface OpenSpecExecutionResult {
19
+ stdout: string;
20
+ stderr: string;
21
+ code: number;
22
+ }
23
+ export interface PiExecContext {
24
+ exec(cmd: string, args: string[], opts?: {
25
+ timeout?: number;
26
+ cwd?: string;
27
+ }): Promise<OpenSpecExecutionResult>;
28
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "type": "module",
3
+ "name": "pi-openspec-context",
4
+ "version": "0.1.0",
5
+ "description": "Lightweight Pi extension for automatic OpenSpec context injection into system prompts",
6
+ "license": "MIT",
7
+ "author": "Raphael Bahat <raphael@earendil.works>",
8
+ "keywords": [
9
+ "pi-package",
10
+ "pi",
11
+ "openspec",
12
+ "extension",
13
+ "context",
14
+ "spec-driven-development"
15
+ ],
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "CHANGELOG.md",
22
+ "LICENSE"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "typecheck": "tsc -p tsconfig.build.json --noEmit",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "test:e2e": "vitest run docker/test-e2e.test.ts",
30
+ "lint": "eslint ."
31
+ },
32
+ "pi": {
33
+ "extensions": [
34
+ "./dist/index.js"
35
+ ]
36
+ },
37
+ "peerDependencies": {
38
+ "@earendil-works/pi-coding-agent": "*"
39
+ },
40
+ "devDependencies": {
41
+ "@earendil-works/pi-coding-agent": "^0.84.0",
42
+ "@types/node": "^20.0.0",
43
+ "@typescript-eslint/eslint-plugin": "^8.67.0",
44
+ "@typescript-eslint/parser": "^8.67.0",
45
+ "eslint": "^9.0.0",
46
+ "typescript": "^5.4.0",
47
+ "typescript-eslint": "^8.67.0",
48
+ "vitest": "^1.6.0"
49
+ },
50
+ "dependencies": {
51
+ "@gaodes/pi-test-harness": "file:../pi-test-harness"
52
+ }
53
+ }