pi-doc-injector 0.1.2 → 0.1.4

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
@@ -63,7 +63,7 @@ Create `.pi/doc-injector.json` in your project root to customize behavior:
63
63
  ```json
64
64
  {
65
65
  "docsPath": "./docs",
66
- "matchThreshold": 2,
66
+ "matchThreshold": 1,
67
67
  "contextThreshold": 80,
68
68
  "recursive": true
69
69
  }
@@ -72,7 +72,7 @@ Create `.pi/doc-injector.json` in your project root to customize behavior:
72
72
  | Option | Default | Description |
73
73
  | ------------------ | ---------- | -------------------------------------------------------- |
74
74
  | `docsPath` | `"./docs"` | Path to docs folder (relative to project root) |
75
- | `matchThreshold` | `2` | Minimum keyword matches required to inject a doc |
75
+ | `matchThreshold` | `1` | Minimum keyword matches required to inject a doc |
76
76
  | `contextThreshold` | `80` | Skip injection when context usage exceeds this % (0–100) |
77
77
  | `recursive` | `true` | Scan docs subdirectories recursively |
78
78
 
@@ -103,14 +103,28 @@ The extension uses a per-session injection model:
103
103
  - Use `/doc-inject reset` to manually reset all flags and allow docs to be injected again.
104
104
  - Use `/doc-inject list` to see which docs have been injected (✅) and which are pending (⬜).
105
105
 
106
+ ### System Prompt Lifecycle
107
+
108
+ Pi **reconstructs the system prompt from source files each turn** (verified against pi v0.70.6).
109
+
110
+ When `before_agent_start` fires, the `systemPrompt` passed to the extension is a freshly rebuilt prompt from `AGENTS.md`, `SYSTEM.md`, skills, and tool snippets. It is **not** accumulated from previous turns.
111
+
112
+ This means:
113
+
114
+ - Injections apply to the **current turn only** and do not persist in subsequent turns.
115
+ - There is no risk of duplicate injection sections stacking up over time.
116
+ - The `injected` flag alone is sufficient to prevent re-injection — no additional deduplication or marker-based stripping is needed.
117
+
118
+ For the full source-level verification, see the JSDoc block in `index.ts`.
119
+
106
120
  ## Development
107
121
 
108
122
  ```bash
109
123
  # Run tests
110
- bun test
124
+ npm test
111
125
 
112
126
  # Run tests in watch mode
113
- bun test --watch
127
+ npm run test:watch
114
128
  ```
115
129
 
116
130
  ## License
package/docs/bun.md CHANGED
@@ -1,32 +1,32 @@
1
1
  ---
2
2
  title: "JavaScript Runtime & Package Manager"
3
- keywords: [bun, package manager, runtime, install, test, build]
3
+ keywords: [npm, node, package manager, runtime, install, test, build]
4
4
  ---
5
5
 
6
6
  # JavaScript Runtime & Package Manager
7
7
 
8
- This project uses **Bun** as its JavaScript runtime and package manager.
8
+ This project uses **Node.js** as its JavaScript runtime and **npm** as its package manager.
9
9
 
10
- ## Why Bun?
10
+ ## Why npm?
11
11
 
12
- - Fast native TypeScript support (no separate tsc step needed)
13
- - All-in-one runtime, bundler, and test runner
14
- - Drop-in replacement for Node.js and npm/yarn/pnpm
15
- - Significantly faster installs and test executions
12
+ - The default package manager that ships with Node.js
13
+ - Widely supported across all CI/CD platforms
14
+ - Deterministic installs via `package-lock.json`
15
+ - No additional runtime or tooling required
16
16
 
17
17
  ## Common Commands
18
18
 
19
19
  ```bash
20
20
  # Install dependencies
21
- bun install
21
+ npm install
22
22
 
23
23
  # Run tests
24
- bun test
24
+ npm test
25
25
 
26
- # Run tests in watch mode (recommended for development)
27
- bun test --watch
26
+ # Run tests in watch mode
27
+ npm run test:watch
28
28
  ```
29
29
 
30
30
  ## Version
31
31
 
32
- This project requires Bun >= 1.3.0. The lockfile (`bun.lock`) ensures reproducible installs.
32
+ This project requires Node.js >= 18.0.0. The lockfile (`package-lock.json`) ensures reproducible installs.
package/docs/publish.md CHANGED
@@ -1,10 +1,29 @@
1
1
  ---
2
2
  title: "Publishing Workflow"
3
- keywords: [publish, release, npm, version, tag, semantic versioning]
3
+ keywords: [publish, release, npm, version, tag, node, oidc, trusted publisher]
4
4
  ---
5
5
 
6
6
  # Publishing Workflow
7
7
 
8
+ ## Trusted Publisher (OIDC)
9
+
10
+ This package uses **npm trusted publishing** — no tokens needed. The GitHub Actions workflow authenticates via OIDC, which is configured at:
11
+
12
+ ```
13
+ https://www.npmjs.com/package/pi-doc-injector → Settings → Trusted Publisher
14
+ ```
15
+
16
+ The trusted publisher entry authorizes `lmn451/pi-docs` with workflow `publish.yml`.
17
+
18
+ ## How It Works
19
+
20
+ 1. Push a `v*` tag → triggers the publish workflow
21
+ 2. GitHub Actions generates a short-lived OIDC token (`id-token: write`)
22
+ 3. npm verifies the OIDC claims match the trusted publisher config
23
+ 4. Package is published with provenance attestation
24
+
25
+ No `NPM_TOKEN` secret, no token rotation, nothing to leak.
26
+
8
27
  ## Versioning
9
28
 
10
29
  We follow [Semantic Versioning](https://semver.org/):
@@ -12,69 +31,57 @@ We follow [Semantic Versioning](https://semver.org/):
12
31
  - **MINOR** — backwards-compatible functionality additions
13
32
  - **PATCH** — backwards-compatible bug fixes
14
33
 
15
- ## Publishing a New Version
16
-
17
- ### 1. Bump the version
34
+ ## Release Process
18
35
 
19
36
  ```bash
20
- npm version patch # 0.1.1 → 0.1.2
21
- npm version minor # 0.1.1 → 0.2.0
22
- npm version major # 0.1.1 1.0.0
37
+ # 1. Edit version in package.json (e.g., 0.1.1 → 0.1.2)
38
+
39
+ # 2. Commit, tag, and push
40
+ git add package.json
41
+ git commit -m "chore: bump version to X.Y.Z"
42
+ git tag vX.Y.Z
43
+ git push origin master
44
+ git push origin vX.Y.Z
23
45
  ```
24
46
 
25
- This updates `package.json` and creates a local tag.
26
-
27
- ### 2. Push the tag to trigger the workflow
28
-
29
- ```bash
30
- git push origin v0.1.1
31
- ```
32
-
33
- Or push all tags:
47
+ Or use `npm version`:
34
48
 
35
49
  ```bash
36
- git push origin --tags
50
+ npm version patch # bumps version, commits, tags
51
+ git push origin master --follow-tags
37
52
  ```
38
53
 
39
- **Important:** The publish workflow triggers on tags pushed to remote, not just locally created tags. The tag must match the pattern `v*` (e.g., `v0.1.1`, `v0.2.0`).
54
+ ## CI/CD
40
55
 
41
- ### 3. GitHub Actions Publish workflow
56
+ The `Publish` workflow triggers on `v*` tags and:
57
+ - Runs `npm ci`
58
+ - Verifies tag matches `package.json` version
59
+ - Runs `npm test`
60
+ - Publishes to npm with provenance via OIDC
42
61
 
43
- Once the tag is pushed, the `Publish` workflow automatically:
44
- - Runs tests
45
- - Publishes to npm registry
46
-
47
- Monitor the workflow at: `https://github.com/lmn451/pi-docs/actions`
62
+ Monitor: https://github.com/lmn451/pi-docs/actions
48
63
 
49
64
  ## Verify the Publish
50
65
 
51
- Check if the package was published:
52
-
53
66
  ```bash
54
67
  npm view pi-doc-injector
55
68
  ```
56
69
 
57
- ## Manual Publish
70
+ ## Manual Publish (first time only)
58
71
 
59
- If needed, you can publish manually:
72
+ OIDC only works after the package exists on npm. For the initial publish:
60
73
 
61
74
  ```bash
62
- npm publish
75
+ npm login
76
+ npm publish --access public
63
77
  ```
64
78
 
65
- ## Setup
66
-
67
- Ensure your npm token is configured as a GitHub secret:
68
- - Go to repository Settings → Secrets and variables → Actions
69
- - Add a new secret named `NPM_TOKEN` with your npm access token
79
+ After that, configure the trusted publisher and all future releases go through CI.
70
80
 
71
81
  ## Troubleshooting
72
82
 
73
- **Workflow didn't run?**
74
- - Verify the tag exists remotely: `git ls-remote origin refs/tags/v0.1.1`
75
- - Check that the tag matches the pattern `v*`
76
- - Check GitHub Actions runs at: `https://github.com/lmn451/pi-docs/actions`
77
-
78
- **npm publish failed?**
79
- - Ensure `NPM_TOKEN` secret is set
80
- - Verify the version hasn't already been published
83
+ | Issue | Solution |
84
+ |-------|----------|
85
+ | Workflow didn't run | Verify tag exists: `git ls-remote origin refs/tags/vX.Y.Z` |
86
+ | 404 on publish | Verify trusted publisher config on npmjs.com matches exactly |
87
+ | Version already published | Bump to a new version |
package/index.ts CHANGED
@@ -22,6 +22,29 @@
22
22
  * session, once a doc is injected, it won't be re-injected unless the user
23
23
  * manually runs `/doc-inject reset`.
24
24
  *
25
+ * ## System Prompt Lifecycle (verified against pi v0.70.6)
26
+ *
27
+ * Pi **reconstructs the system prompt from source files each turn**. Here is
28
+ * the exact flow, verified via source-code review of dist/core/agent-session.js
29
+ * and dist/core/extensions/runner.js (v0.70.6):
30
+ *
31
+ * 1. Before each agent turn, pi calls `this._rebuildSystemPrompt(toolNames)`.
32
+ * This builds the prompt from `AGENTS.md`, `SYSTEM.md`, skills, enabled
33
+ * tool snippets — never from a previously modified (injected) prompt.
34
+ * 2. The rebuilt prompt is stored in `this._baseSystemPrompt`.
35
+ * 3. `emitBeforeAgentStart(..., this._baseSystemPrompt, ...)` passes this
36
+ * *fresh* base prompt to every extension handler.
37
+ * 4. Extension handlers can return a modified `systemPrompt` for the current
38
+ * turn. Pi uses the modified prompt **only for this turn**.
39
+ * 5. When no extension modifies the prompt, pi explicitly resets to
40
+ * `this._baseSystemPrompt` (comment in source: "Ensure we're using the
41
+ * base prompt (in case previous turn had modifications)").
42
+ *
43
+ * **Therefore**: Previous injections from `before_agent_start` do NOT persist
44
+ * across turns. Duplicate sections cannot accumulate in the system prompt.
45
+ * The `injected` flag alone is sufficient to prevent re-injection — no
46
+ * marker-based stripping or deduplication is needed.
47
+ *
25
48
  * ## Race Condition Note
26
49
  *
27
50
  * If `resources_discover` (rebuild) fires while `before_agent_start` is running,
@@ -143,7 +166,7 @@ export default async function docInjectorExtension(pi: ExtensionAPI) {
143
166
  });
144
167
 
145
168
  // ---- Event: before_agent_start (inject into system prompt) ----
146
- pi.on("before_agent_start", async (_event, _ctx) => {
169
+ pi.on("before_agent_start", async (event, ctx) => {
147
170
  if (!enabled || !registry || pendingMatches.size === 0) return;
148
171
 
149
172
  const matchedEntries: DocEntry[] = [];
@@ -160,7 +183,7 @@ export default async function docInjectorExtension(pi: ExtensionAPI) {
160
183
  // Skip injection if context usage exceeds the configured threshold
161
184
  // (default: 80%). This prevents doc injection from pushing the context
162
185
  // past the model's limit.
163
- const usage = _ctx.getContextUsage();
186
+ const usage = ctx.getContextUsage();
164
187
  if (usage && usage.tokens && usage.tokens > 0 && usage.percent && usage.percent > config.contextThreshold) {
165
188
  console.warn(`[doc-injector] Skipping injection: context usage > ${config.contextThreshold}%`);
166
189
  pendingMatches.clear();
@@ -174,7 +197,7 @@ export default async function docInjectorExtension(pi: ExtensionAPI) {
174
197
  pendingMatches.clear();
175
198
 
176
199
  return {
177
- systemPrompt: (_event.systemPrompt || "") + "\n\n" + append,
200
+ systemPrompt: (event.systemPrompt || "") + "\n\n" + append,
178
201
  };
179
202
  });
180
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-doc-injector",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Auto-inject relevant project documentation into Pi's LLM context based on keyword matching",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -13,8 +13,8 @@
13
13
  "README.md"
14
14
  ],
15
15
  "scripts": {
16
- "test": "bun test",
17
- "test:watch": "bun test --watch"
16
+ "test": "vitest run",
17
+ "test:watch": "vitest"
18
18
  },
19
19
  "keywords": [
20
20
  "pi-package",
@@ -37,10 +37,12 @@
37
37
  "@mariozechner/pi-coding-agent": "*"
38
38
  },
39
39
  "devDependencies": {
40
- "@types/bun": "^1.3.13"
40
+ "@types/node": "^22.0.0",
41
+ "typescript": "^5.7.0",
42
+ "vitest": "^3.0.0"
41
43
  },
42
44
  "publishConfig": {
43
45
  "access": "public"
44
46
  },
45
- "packageManager": "bun@1.3.11"
47
+ "packageManager": "npm@11.5.2"
46
48
  }
package/registry.ts CHANGED
@@ -111,7 +111,10 @@ export class DocRegistry {
111
111
  injected: preserved.get(filePath) ?? false,
112
112
  });
113
113
  } catch (err) {
114
- console.warn(`[doc-injector] Error reading ${relativePath}:`, err);
114
+ // Only warn for unexpected errors, not ENOENT (file deleted/moved after scan)
115
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
116
+ console.warn(`[doc-injector] Error reading ${relativePath}:`, err);
117
+ }
115
118
  }
116
119
  }
117
120
 
@@ -141,16 +144,28 @@ export class DocRegistry {
141
144
  for (const dirent of dirents) {
142
145
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
143
146
 
144
- // Build relative path from the directory tree
145
- const parentPath = (dirent as Dirent & { path?: string }).path ?? "";
146
- const relPath = parentPath
147
- ? relative(dir, join(parentPath, dirent.name))
148
- : dirent.name;
147
+ const fileName = basename(dirent.name);
148
+
149
+ // Cross-runtime: when dirent.name is just the filename, resolve the
150
+ // relative path from the parent directory. Use parentPath (Node 18+)
151
+ // with fallback to .path (Bun) for older runtimes.
152
+ let relPath: string;
153
+ if (dirent.name === fileName) {
154
+ const parentPath = (dirent as Dirent & { parentPath?: string; path?: string }).parentPath
155
+ ?? (dirent as Dirent & { path?: string }).path
156
+ ?? "";
157
+ relPath = parentPath
158
+ ? relative(dir, join(parentPath, dirent.name))
159
+ : dirent.name;
160
+ } else {
161
+ // Node-style: dirent.name already contains the relative path from dir
162
+ relPath = dirent.name;
163
+ }
149
164
 
150
165
  results.push({
151
166
  filePath: join(dir, relPath),
152
167
  relativePath: relPath,
153
- fileName: dirent.name,
168
+ fileName,
154
169
  });
155
170
  }
156
171
 
package/types.ts CHANGED
@@ -38,7 +38,7 @@ export interface DocInjectorConfig {
38
38
  /** Default configuration values. */
39
39
  export const DEFAULT_CONFIG: DocInjectorConfig = {
40
40
  docsPath: "./docs",
41
- matchThreshold: 2,
41
+ matchThreshold: 1,
42
42
  contextThreshold: 80,
43
43
  recursive: true,
44
44
  };