@josephyoung/pi-file-reference 0.1.0 → 0.1.2

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,29 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - package.json
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: read
16
+ id-token: write
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 24
22
+ - run: |
23
+ PACKAGE_VERSION=$(node -p "require('./package.json').version")
24
+ PUBLISHED_VERSION=$(npm view @josephyoung/pi-file-reference version 2>/dev/null || echo "0.0.0")
25
+ if [ "$PACKAGE_VERSION" != "$PUBLISHED_VERSION" ]; then
26
+ npm publish --provenance --access public
27
+ else
28
+ echo "Version $PACKAGE_VERSION already published, skipping"
29
+ fi
package/AGENTS.md ADDED
@@ -0,0 +1,47 @@
1
+ # pi-file-reference
2
+
3
+ Pi extension that resolves `@filepath` references in AGENTS.md and injects file content into system prompt.
4
+
5
+ ## Structure
6
+
7
+ - `extensions/index.ts` — extension entry point
8
+ - `session_start`: scan AGENTS.md for @refs, read files, cache in memory
9
+ - `before_agent_start`: inject cached file content into system prompt
10
+ - `package.json` — pi package metadata
11
+
12
+ ## How it works
13
+
14
+ When a user writes `@./docs/style-guide.md` in their AGENTS.md:
15
+
16
+ 1. The extension finds all `@` references in AGENTS.md (cwd or ~/.pi/agent/)
17
+ 2. Resolves paths (relative, absolute, ~/ expansion)
18
+ 3. If the path is a file: reads it
19
+ If the path is a directory: reads all immediate files (depth 1, sorted alphabetically)
20
+ 4. Injects them into the system prompt under `# Context References`
21
+
22
+ ## Conventions
23
+
24
+ - All code and comments in English
25
+ - AGENTS.md drives the project-level AI context
26
+ - Match Pi's internal style for context file injection (`## @path\n\n{content}`)
27
+
28
+ ## Publishing
29
+
30
+ After functional changes are committed and pushed, update the GitHub repo description if the feature set changed:
31
+
32
+ ```bash
33
+ gh repo edit --description "Pi extension: ..."
34
+ ```
35
+
36
+ Then bump version and push:
37
+
38
+ ```bash
39
+ npm version patch # bump version, creates commit + tag
40
+ git push origin main --tags # push to main triggers CI (package.json change)
41
+ ```
42
+
43
+ - GitHub Actions workflow: `.github/workflows/publish.yml`
44
+ - Trigger: push to main when `package.json` changes, or `workflow_dispatch`
45
+ - CI checks if version > npm latest before publishing (idempotent)
46
+ - Uses npm Trusted Publishing (OIDC) — no tokens or OTP needed
47
+ - OIDC runs on `refs/heads/main` — ensure npm Trusted Publisher is configured for branch `main`
package/README.md CHANGED
@@ -21,6 +21,16 @@ A [pi](https://github.com/earendil-works/pi-coding-agent) extension that resolve
21
21
 
22
22
  The `@` must be at the start of a line or preceded by whitespace.
23
23
 
24
+ ### Directory references
25
+
26
+ When `@path` resolves to a directory, all immediate files (depth 1) are injected:
27
+
28
+ ```
29
+ @./docs
30
+ ```
31
+
32
+ Injects `@docs/style-guide.md`, `@docs/patterns.md`, etc. (sorted alphabetically). Subdirectories are skipped. Trailing `/` is stripped (`@./docs/` works the same).
33
+
24
34
  ## Installation
25
35
 
26
36
  ```bash
@@ -1,10 +1,9 @@
1
- import type { ExtensionAPI, CustomEntry } from "@earendil-works/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import * as os from "node:os";
5
5
 
6
6
  const AGENTS_FILE = "AGENTS.md";
7
- const STORE_KEY = "agents-at-context";
8
7
 
9
8
  /**
10
9
  * Parse @filepath references from a line.
@@ -123,52 +122,65 @@ function loadRefs(cwd: string): RefContent[] {
123
122
  return true;
124
123
  });
125
124
 
126
- // Read each referenced file
125
+ // Read each referenced file or directory
127
126
  const results: RefContent[] = [];
128
127
  for (const ref of uniqueRefs) {
129
- const resolvedPath = resolveRef(ref, baseDir);
130
- if (fs.existsSync(resolvedPath)) {
131
- results.push({ ref, content: fs.readFileSync(resolvedPath, "utf-8") });
128
+ // Strip trailing slashes for consistent handling
129
+ const cleanRef = ref.endsWith("/") ? ref.slice(0, -1) : ref;
130
+ const resolvedPath = resolveRef(cleanRef, baseDir);
131
+
132
+ if (!fs.existsSync(resolvedPath)) {
133
+ console.warn(`[pi-file-reference] @${cleanRef} -> ${resolvedPath} not found, skipping`);
134
+ continue;
135
+ }
136
+
137
+ const stat = fs.statSync(resolvedPath);
138
+ if (stat.isDirectory()) {
139
+ // Read all files at depth 1, skip subdirectories
140
+ const entries = fs.readdirSync(resolvedPath, { withFileTypes: true });
141
+ const files = entries
142
+ .filter((e) => e.isFile())
143
+ .map((e) => e.name)
144
+ .sort(); // deterministic order
145
+
146
+ if (files.length === 0) {
147
+ console.warn(`[pi-file-reference] @${cleanRef} is an empty directory, skipping`);
148
+ continue;
149
+ }
150
+
151
+ for (const fileName of files) {
152
+ const filePath = path.join(resolvedPath, fileName);
153
+ results.push({
154
+ ref: `${cleanRef}/${fileName}`,
155
+ content: fs.readFileSync(filePath, "utf-8"),
156
+ });
157
+ }
132
158
  } else {
133
- console.warn(`[agents-at-reader] @${ref} -> ${resolvedPath} not found, skipping`);
159
+ results.push({ ref: cleanRef, content: fs.readFileSync(resolvedPath, "utf-8") });
134
160
  }
135
161
  }
136
162
 
137
163
  return results;
138
164
  }
139
165
 
166
+ let cachedRefs: RefContent[] = [];
167
+
140
168
  export default function (pi: ExtensionAPI) {
141
169
  pi.on("session_start", (_event, ctx) => {
142
- // Check if custom entry already exists (e.g., after reload)
143
- const entries = ctx.sessionManager.getEntries();
144
- const existing = entries.find(
145
- (e): e is CustomEntry<{ refs: RefContent[] }> =>
146
- e.type === "custom" && e.customType === STORE_KEY,
147
- );
148
- if (existing) return;
149
-
150
- const refs = loadRefs(ctx.cwd);
151
- if (refs.length === 0) return;
152
-
153
- pi.appendEntry(STORE_KEY, { refs });
170
+ cachedRefs = loadRefs(ctx.cwd);
154
171
  });
155
172
 
156
- pi.on("before_agent_start", (event, ctx) => {
157
- const entries = ctx.sessionManager.getEntries();
158
- const entry = entries.find(
159
- (e): e is CustomEntry<{ refs: RefContent[] }> =>
160
- e.type === "custom" && e.customType === STORE_KEY,
161
- );
162
- if (!entry?.data?.refs?.length) return;
173
+ pi.on("before_agent_start", (event, _ctx) => {
174
+ if (!cachedRefs.length) return;
163
175
 
164
- const injected = entry.data.refs
165
- .map((r) => `// @${r.ref}\n${r.content}`)
176
+ const injected = cachedRefs
177
+ .map((r) => `## @${r.ref}\n\n${r.content}`)
166
178
  .join("\n\n");
167
179
 
168
180
  return {
169
181
  systemPrompt:
170
182
  event.systemPrompt +
171
- `\n\n---\n// @AGENTS.md 引用文件 — 注入以下文件内容供参考\n${injected}\n---`,
183
+ `\n\n# Context References\n\n${injected}`,
172
184
  };
173
185
  });
174
186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyoung/pi-file-reference",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "description": "Pi extension that resolves @filepath references in AGENTS.md and injects file content into system prompt",
6
6
  "type": "module",
package/tsconfig.json CHANGED
@@ -7,5 +7,5 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true
9
9
  },
10
- "include": ["agents-at-reader.ts"]
10
+ "include": ["extensions/**/*.ts"]
11
11
  }