pi-local-agents-only 0.1.4 → 0.1.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/CHANGELOG.md +10 -0
- package/README.md +2 -0
- package/extensions/local-agents-only.js +81 -12
- package/package.json +2 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.5 - 2026-04-07
|
|
4
|
+
|
|
5
|
+
- strip already-loaded global `AGENTS.md` / `CLAUDE.md` context reliably instead of rereading live files from disk
|
|
6
|
+
- remove the now-empty `# Project Context` section when only global context was loaded
|
|
7
|
+
- handle prompts whose custom prompt text also mentions the `# Project Context` heading
|
|
8
|
+
- make `/local-agents-only off` report when the repo is still enabled via the global allowlist or `PI_LOCAL_AGENTS_ONLY`
|
|
9
|
+
- document the repo-marker-only behavior of `/local-agents-only off`
|
|
10
|
+
- add integration tests against pi's real system prompt builder and command UX regressions
|
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ Disable for the current repo:
|
|
|
30
30
|
/local-agents-only off
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
`/local-agents-only off` clears the repo marker only. If the repo is still enabled via `/local-agents-only global-on` or `PI_LOCAL_AGENTS_ONLY=1`, it remains enabled until you also run `/local-agents-only global-off` or unset the env var.
|
|
34
|
+
|
|
33
35
|
Enable or disable via the global allowlist:
|
|
34
36
|
|
|
35
37
|
```bash
|
|
@@ -16,6 +16,10 @@ const MARKER = join(".pi", COMMAND);
|
|
|
16
16
|
const GLOBAL_CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
17
17
|
const ENV_TRUE = ["1", "true", "yes", "on"];
|
|
18
18
|
const ENV_FALSE = ["0", "false", "no", "off"];
|
|
19
|
+
const PROJECT_CONTEXT_HEADER = "\n\n# Project Context\n\nProject-specific instructions and guidelines:\n\n";
|
|
20
|
+
const SKILLS_HEADER = "\n\nThe following skills provide specialized instructions for specific tasks.";
|
|
21
|
+
const DATE_HEADER = "\nCurrent date:";
|
|
22
|
+
const CONTEXT_BLOCK_HEADER = /^## ([^\n]+(?:AGENTS|CLAUDE)\.md)\n\n/gm;
|
|
19
23
|
|
|
20
24
|
const getAgentDir = () => {
|
|
21
25
|
const env = process.env.PI_CODING_AGENT_DIR;
|
|
@@ -89,10 +93,59 @@ const getEnvToggle = (value = process.env.PI_LOCAL_AGENTS_ONLY) => {
|
|
|
89
93
|
return false;
|
|
90
94
|
}
|
|
91
95
|
};
|
|
92
|
-
const getGlobalContextPaths = (agentDir = getAgentDir()) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
const getGlobalContextPaths = (agentDir = getAgentDir()) => GLOBAL_CONTEXT_FILES.map((name) => join(agentDir, name));
|
|
97
|
+
const getExistingGlobalContextPaths = (agentDir = getAgentDir()) =>
|
|
98
|
+
getGlobalContextPaths(agentDir).filter((path) => existsSync(path));
|
|
99
|
+
const getContextSectionEnd = (prompt, offset) => {
|
|
100
|
+
const candidates = [prompt.indexOf(SKILLS_HEADER, offset), prompt.indexOf(DATE_HEADER, offset)].filter(
|
|
101
|
+
(index) => index !== -1,
|
|
102
|
+
);
|
|
103
|
+
return candidates.length > 0 ? Math.min(...candidates) : prompt.length;
|
|
104
|
+
};
|
|
105
|
+
const getContextBlocks = (contextSection) => {
|
|
106
|
+
const matches = [...contextSection.matchAll(CONTEXT_BLOCK_HEADER)];
|
|
107
|
+
return matches.map((match, index) => ({
|
|
108
|
+
path: match[1],
|
|
109
|
+
start: match.index,
|
|
110
|
+
end: index + 1 < matches.length ? matches[index + 1].index : contextSection.length,
|
|
111
|
+
}));
|
|
112
|
+
};
|
|
113
|
+
const stripGlobalContext = (prompt, globalPaths = getGlobalContextPaths()) => {
|
|
114
|
+
const sectionStart = prompt.lastIndexOf(PROJECT_CONTEXT_HEADER);
|
|
115
|
+
if (sectionStart === -1) {
|
|
116
|
+
return { prompt, removedPaths: [] };
|
|
117
|
+
}
|
|
118
|
+
const contextStart = sectionStart + PROJECT_CONTEXT_HEADER.length;
|
|
119
|
+
const sectionEnd = getContextSectionEnd(prompt, contextStart);
|
|
120
|
+
const contextSection = prompt.slice(contextStart, sectionEnd);
|
|
121
|
+
const blocks = getContextBlocks(contextSection);
|
|
122
|
+
if (blocks.length === 0) {
|
|
123
|
+
return { prompt, removedPaths: [] };
|
|
124
|
+
}
|
|
125
|
+
const globalPathKeys = new Set(globalPaths.map(normalizePath));
|
|
126
|
+
const keptBlocks = [];
|
|
127
|
+
const removedPaths = [];
|
|
128
|
+
for (const block of blocks) {
|
|
129
|
+
const blockText = contextSection.slice(block.start, block.end);
|
|
130
|
+
if (globalPathKeys.has(normalizePath(block.path))) {
|
|
131
|
+
removedPaths.push(block.path);
|
|
132
|
+
} else {
|
|
133
|
+
keptBlocks.push(blockText);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (removedPaths.length === 0) {
|
|
137
|
+
return { prompt, removedPaths: [] };
|
|
138
|
+
}
|
|
139
|
+
const prefix = prompt.slice(0, sectionStart);
|
|
140
|
+
const suffix = prompt.slice(sectionEnd);
|
|
141
|
+
if (keptBlocks.length === 0) {
|
|
142
|
+
return { prompt: `${prefix}${suffix}`, removedPaths: uniqueSorted(removedPaths) };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
prompt: `${prefix}${PROJECT_CONTEXT_HEADER}${keptBlocks.join("")}${suffix}`,
|
|
146
|
+
removedPaths: uniqueSorted(removedPaths),
|
|
147
|
+
};
|
|
148
|
+
};
|
|
96
149
|
const getGitTopLevel = (start) => {
|
|
97
150
|
const topLevel = runGit(start, ["rev-parse", "--show-toplevel"]);
|
|
98
151
|
return topLevel ? normalizePath(topLevel) : undefined;
|
|
@@ -143,8 +196,7 @@ const clearMarkers = (state) => {
|
|
|
143
196
|
rmSync(getMarkerPath(root), { force: true });
|
|
144
197
|
}
|
|
145
198
|
};
|
|
146
|
-
const buildLocalOnlyNotice = (
|
|
147
|
-
const paths = getGlobalContextPaths(agentDir);
|
|
199
|
+
const buildLocalOnlyNotice = (paths = getExistingGlobalContextPaths(getAgentDir())) => {
|
|
148
200
|
if (paths.length === 0) {
|
|
149
201
|
return "";
|
|
150
202
|
}
|
|
@@ -152,13 +204,15 @@ const buildLocalOnlyNotice = (agentDir = getAgentDir()) => {
|
|
|
152
204
|
"# Local Context Mode",
|
|
153
205
|
"This repo is in local-agents-only mode.",
|
|
154
206
|
"Ignore instructions from these global context files even if they appear in older session messages, summaries, or retries:",
|
|
155
|
-
...paths.map((path) => `- ${path}`),
|
|
207
|
+
...uniqueSorted(paths).map((path) => `- ${path}`),
|
|
156
208
|
"Follow only repo-local AGENTS.md or CLAUDE.md guidance for this project.",
|
|
157
209
|
].join("\n");
|
|
158
210
|
};
|
|
159
211
|
const applyLocalOnlyPrompt = (prompt, agentDir = getAgentDir()) => {
|
|
160
|
-
const stripped =
|
|
161
|
-
const notice = buildLocalOnlyNotice(
|
|
212
|
+
const { prompt: stripped, removedPaths } = stripGlobalContext(prompt, getGlobalContextPaths(agentDir));
|
|
213
|
+
const notice = buildLocalOnlyNotice(
|
|
214
|
+
removedPaths.length > 0 ? removedPaths : getExistingGlobalContextPaths(agentDir),
|
|
215
|
+
);
|
|
162
216
|
return notice ? `${stripped}\n\n${notice}` : stripped;
|
|
163
217
|
};
|
|
164
218
|
const setStatus = (ctx) => {
|
|
@@ -168,6 +222,21 @@ const setStatus = (ctx) => {
|
|
|
168
222
|
const mode = getMode(ctx.cwd);
|
|
169
223
|
ctx.ui.setStatus(COMMAND, mode.enabled ? `AGENTS: local-only (${mode.source})` : undefined);
|
|
170
224
|
};
|
|
225
|
+
const getProjectTarget = (state) =>
|
|
226
|
+
state.worktreeRoots.length > 1 ? `${state.projectRoot} and linked worktrees` : state.projectRoot;
|
|
227
|
+
const getOffNotification = (state) => {
|
|
228
|
+
const mode = getMode(state);
|
|
229
|
+
if (!mode.enabled) {
|
|
230
|
+
return `Disabled for ${getProjectTarget(state)}`;
|
|
231
|
+
}
|
|
232
|
+
if (mode.source === "global-config") {
|
|
233
|
+
return `Repo marker cleared for ${getProjectTarget(state)}, but local-agents-only is still enabled via global allowlist. Use /local-agents-only global-off to fully disable it.`;
|
|
234
|
+
}
|
|
235
|
+
if (mode.source === "env") {
|
|
236
|
+
return `Repo marker cleared for ${getProjectTarget(state)}, but local-agents-only is still enabled via PI_LOCAL_AGENTS_ONLY.`;
|
|
237
|
+
}
|
|
238
|
+
return `Repo marker cleared for ${getProjectTarget(state)}, but local-agents-only is still enabled via ${mode.source}.`;
|
|
239
|
+
};
|
|
171
240
|
|
|
172
241
|
export function findProjectRoot(start = process.cwd()) {
|
|
173
242
|
return getProjectState(start).projectRoot;
|
|
@@ -192,8 +261,8 @@ export function getMode(start = process.cwd(), envValue = process.env.PI_LOCAL_A
|
|
|
192
261
|
return { enabled: false, source: "default" };
|
|
193
262
|
}
|
|
194
263
|
|
|
195
|
-
export function stripGlobalBlocks(prompt,
|
|
196
|
-
return
|
|
264
|
+
export function stripGlobalBlocks(prompt, globalPaths = getGlobalContextPaths()) {
|
|
265
|
+
return stripGlobalContext(prompt, globalPaths).prompt;
|
|
197
266
|
}
|
|
198
267
|
|
|
199
268
|
export default function localAgentsOnly(pi) {
|
|
@@ -210,7 +279,7 @@ export default function localAgentsOnly(pi) {
|
|
|
210
279
|
case "off":
|
|
211
280
|
clearMarkers(state);
|
|
212
281
|
setStatus(ctx);
|
|
213
|
-
ctx.ui.notify(
|
|
282
|
+
ctx.ui.notify(getOffNotification(state), "info");
|
|
214
283
|
return;
|
|
215
284
|
case "global-on": {
|
|
216
285
|
const config = readConfig();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-local-agents-only",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Pi extension that strips global AGENTS.md and CLAUDE.md from the effective prompt for selected projects.",
|
|
5
5
|
"author": "Mitch Fultz (https://github.com/fitchmultz)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "https://github.com/fitchmultz/pi-local-agents-only/issues"
|
|
15
15
|
},
|
|
16
16
|
"homepage": "https://github.com/fitchmultz/pi-local-agents-only#readme",
|
|
17
|
-
"files": ["extensions", "README.md", "LICENSE"],
|
|
17
|
+
"files": ["extensions", "README.md", "CHANGELOG.md", "LICENSE"],
|
|
18
18
|
"pi": {
|
|
19
19
|
"extensions": ["./extensions"]
|
|
20
20
|
},
|