@mrclrchtr/supi-claude-md 1.1.2 → 1.2.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/README.md +18 -49
- package/node_modules/@mrclrchtr/supi-core/package.json +9 -1
- package/package.json +10 -2
- package/src/claude-md.ts +14 -34
- package/src/config.ts +0 -8
- package/src/settings-registration.ts +0 -49
- package/src/state.ts +10 -34
- package/src/subdirectory.ts +5 -48
package/README.md
CHANGED
|
@@ -1,73 +1,42 @@
|
|
|
1
1
|
# @mrclrchtr/supi-claude-md
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Subdirectory context for PI — your project's conventions follow the agent wherever it goes.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pi install npm:@mrclrchtr/supi-claude-md
|
|
9
|
-
```
|
|
5
|
+
Pi loads your root `CLAUDE.md` by default. Claude-MD extends that downward: when the agent reaches into `src/auth/`, it picks up `src/auth/CLAUDE.md` too. Conventions are where the code is, not just at the project root.
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
Then it helps you keep those files in shape — audit quality, flag stale sections, and capture session learnings with your approval.
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Pi loads root and ancestor instruction files natively into the system prompt on every turn. This package only handles subdirectories below `cwd`. To pick up root instruction file edits mid-session, use pi's `/reload` command or restart the session.
|
|
16
|
-
|
|
17
|
-
If your install surface includes the shared SuPi settings command (for example via `@mrclrchtr/supi`), this package contributes a Claude-MD section there:
|
|
18
|
-
|
|
19
|
-
```text
|
|
20
|
-
/supi-settings
|
|
21
|
-
```
|
|
9
|
+
## What you get
|
|
22
10
|
|
|
23
|
-
|
|
11
|
+
### Context that travels
|
|
24
12
|
|
|
25
|
-
|
|
26
|
-
- `Subdirectory Re-read Interval`: text input; enter a number of turns or `0` to disable subdirectory re-reads
|
|
27
|
-
- `Context Threshold`: common percentage values from `0` to `100`
|
|
28
|
-
- `Context File Names`: comma-separated text input; empty input restores the default filenames
|
|
13
|
+
Reads, writes, edits, LSP operations — any time the agent touches a file, it picks up the nearest `CLAUDE.md` or `AGENTS.md` in that directory. Each subdirectory's context is injected once (on first discovery) and available for the rest of the session.
|
|
29
14
|
|
|
30
|
-
|
|
15
|
+
### CLAUDE.md maintenance
|
|
31
16
|
|
|
32
|
-
|
|
33
|
-
- `claude-md-revision`: capture session learnings into CLAUDE.md with user approval
|
|
17
|
+
Two bundled skills:
|
|
34
18
|
|
|
35
|
-
|
|
19
|
+
- **claude-md-improver** — audit every CLAUDE.md in your repo. Flags redundancy, stale sections, and content already covered by SuPi's auto-injected context. Suggests targeted updates.
|
|
20
|
+
- **claude-md-revision** — capture what you learned this session into CLAUDE.md. Ask the agent to remember a pattern, convention, or gotcha — it proposes the edit, you approve.
|
|
36
21
|
|
|
37
|
-
|
|
22
|
+
## Install
|
|
38
23
|
|
|
39
|
-
|
|
24
|
+
```bash
|
|
25
|
+
pi install npm:@mrclrchtr/supi-claude-md
|
|
26
|
+
```
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
- project: `.pi/supi/config.json`
|
|
28
|
+
## Settings
|
|
43
29
|
|
|
44
|
-
|
|
30
|
+
Configure via `/supi-settings` or directly in config:
|
|
45
31
|
|
|
46
32
|
```json
|
|
47
33
|
{
|
|
48
34
|
"claude-md": {
|
|
49
|
-
"rereadInterval": 3,
|
|
50
|
-
"contextThreshold": 80,
|
|
51
35
|
"subdirs": true,
|
|
52
36
|
"fileNames": ["CLAUDE.md", "AGENTS.md"]
|
|
53
37
|
}
|
|
54
38
|
}
|
|
55
39
|
```
|
|
56
40
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- `rereadInterval`: turns between re-reading previously injected subdirectory context; `0` disables subdirectory re-reads (first-time discovery is unaffected)
|
|
60
|
-
- `contextThreshold`: skip subdirectory re-injection when context usage is at or above this percent; `100` disables context gating; first-time discovery is always allowed
|
|
61
|
-
- `subdirs`: enable or disable subdirectory discovery
|
|
62
|
-
- `fileNames`: ordered list of context filenames to search for
|
|
63
|
-
|
|
64
|
-
## Requirements
|
|
65
|
-
|
|
66
|
-
- `@earendil-works/pi-coding-agent`
|
|
67
|
-
- `@earendil-works/pi-tui`
|
|
68
|
-
- `@mrclrchtr/supi-core`
|
|
69
|
-
|
|
70
|
-
## Source
|
|
71
|
-
|
|
72
|
-
- Entrypoint: `src/claude-md.ts`
|
|
73
|
-
- Skills: `skills/claude-md-improver/`, `skills/claude-md-revision/`
|
|
41
|
+
- `subdirs` — toggle subdirectory discovery on/off
|
|
42
|
+
- `fileNames` — which filenames to look for (comma-separated)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,5 +22,13 @@
|
|
|
22
22
|
"@earendil-works/pi-coding-agent": "*",
|
|
23
23
|
"@earendil-works/pi-tui": "*"
|
|
24
24
|
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"@earendil-works/pi-coding-agent": {
|
|
27
|
+
"optional": true
|
|
28
|
+
},
|
|
29
|
+
"@earendil-works/pi-tui": {
|
|
30
|
+
"optional": true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
25
33
|
"main": "src/index.ts"
|
|
26
34
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-claude-md",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "SuPi claude-md extension — automatic subdirectory context injection for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"!__tests__"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@mrclrchtr/supi-core": "1.
|
|
24
|
+
"@mrclrchtr/supi-core": "1.2.0"
|
|
25
25
|
},
|
|
26
26
|
"bundledDependencies": [
|
|
27
27
|
"@mrclrchtr/supi-core"
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
"@earendil-works/pi-coding-agent": "*",
|
|
31
31
|
"@earendil-works/pi-tui": "*"
|
|
32
32
|
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@earendil-works/pi-coding-agent": {
|
|
35
|
+
"optional": true
|
|
36
|
+
},
|
|
37
|
+
"@earendil-works/pi-tui": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
33
41
|
"pi": {
|
|
34
42
|
"extensions": [
|
|
35
43
|
"./src/claude-md.ts"
|
package/src/claude-md.ts
CHANGED
|
@@ -13,17 +13,17 @@ import type {
|
|
|
13
13
|
ExtensionContext,
|
|
14
14
|
SessionCompactEvent,
|
|
15
15
|
SessionStartEvent,
|
|
16
|
-
TurnEndEvent,
|
|
17
16
|
} from "@earendil-works/pi-coding-agent";
|
|
18
17
|
import { loadClaudeMdConfig } from "./config.ts";
|
|
18
|
+
import type { DiscoveredContextFile } from "./discovery.ts";
|
|
19
19
|
import {
|
|
20
20
|
extractPathFromToolEvent,
|
|
21
21
|
filterAlreadyLoaded,
|
|
22
22
|
findSubdirContextFiles,
|
|
23
23
|
} from "./discovery.ts";
|
|
24
24
|
import { registerClaudeMdSettings } from "./settings-registration.ts";
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
25
|
+
import type { ClaudeMdState } from "./state.ts";
|
|
26
|
+
import { createInitialState, reconstructState } from "./state.ts";
|
|
27
27
|
import { formatSubdirContext, shouldInjectSubdir } from "./subdirectory.ts";
|
|
28
28
|
|
|
29
29
|
const baseDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -39,10 +39,8 @@ export default function claudeMdExtension(pi: ExtensionAPI) {
|
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
41
|
const branch = ctx.sessionManager.getBranch();
|
|
42
|
-
|
|
43
42
|
if (branch.length > 0) {
|
|
44
43
|
const reconstructed = reconstructState(branch);
|
|
45
|
-
state.completedTurns = reconstructed.completedTurns;
|
|
46
44
|
state.injectedDirs = reconstructed.injectedDirs;
|
|
47
45
|
}
|
|
48
46
|
} catch {
|
|
@@ -50,15 +48,6 @@ export default function claudeMdExtension(pi: ExtensionAPI) {
|
|
|
50
48
|
}
|
|
51
49
|
});
|
|
52
50
|
|
|
53
|
-
// ── Turn tracking ──────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
pi.on("turn_end", async (event: TurnEndEvent, _ctx: ExtensionContext) => {
|
|
56
|
-
const msg = event.message as { stopReason?: string };
|
|
57
|
-
if (msg?.stopReason === "stop") {
|
|
58
|
-
state.completedTurns++;
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
51
|
// ── Compaction ─────────────────────────────────────────────
|
|
63
52
|
|
|
64
53
|
pi.on("session_compact", async (_event: SessionCompactEvent, _ctx: ExtensionContext) => {
|
|
@@ -96,17 +85,11 @@ export default function claudeMdExtension(pi: ExtensionAPI) {
|
|
|
96
85
|
);
|
|
97
86
|
if (found.length === 0) return;
|
|
98
87
|
|
|
99
|
-
const dirsToInject =
|
|
100
|
-
injectedDirs: state.injectedDirs,
|
|
101
|
-
currentTurn: state.completedTurns,
|
|
102
|
-
rereadInterval: config.rereadInterval,
|
|
103
|
-
contextThreshold: config.contextThreshold,
|
|
104
|
-
contextUsage: _ctx.getContextUsage() as ContextUsage | undefined,
|
|
105
|
-
});
|
|
88
|
+
const dirsToInject = collectFreshDirs(found, state.injectedDirs);
|
|
106
89
|
if (dirsToInject.size === 0) return;
|
|
107
90
|
|
|
108
91
|
const filesToInject = Array.from(dirsToInject.values()).flat();
|
|
109
|
-
const contextText = formatSubdirContext(filesToInject
|
|
92
|
+
const contextText = formatSubdirContext(filesToInject);
|
|
110
93
|
if (!contextText) return;
|
|
111
94
|
|
|
112
95
|
updateInjectedDirTracking(state, dirsToInject);
|
|
@@ -135,13 +118,13 @@ function captureNativePaths(
|
|
|
135
118
|
}
|
|
136
119
|
}
|
|
137
120
|
|
|
138
|
-
function
|
|
139
|
-
found:
|
|
140
|
-
|
|
141
|
-
): Map<string,
|
|
142
|
-
const dirsToInject = new Map<string,
|
|
121
|
+
function collectFreshDirs(
|
|
122
|
+
found: DiscoveredContextFile[],
|
|
123
|
+
injectedDirs: Set<string>,
|
|
124
|
+
): Map<string, DiscoveredContextFile[]> {
|
|
125
|
+
const dirsToInject = new Map<string, DiscoveredContextFile[]>();
|
|
143
126
|
for (const file of found) {
|
|
144
|
-
if (shouldInjectSubdir(file.dir,
|
|
127
|
+
if (shouldInjectSubdir(file.dir, injectedDirs)) {
|
|
145
128
|
const existing = dirsToInject.get(file.dir) ?? [];
|
|
146
129
|
existing.push(file);
|
|
147
130
|
dirsToInject.set(file.dir, existing);
|
|
@@ -152,12 +135,9 @@ function collectStaleDirs(
|
|
|
152
135
|
|
|
153
136
|
function updateInjectedDirTracking(
|
|
154
137
|
state: ClaudeMdState,
|
|
155
|
-
dirsToInject: Map<string,
|
|
138
|
+
dirsToInject: Map<string, DiscoveredContextFile[]>,
|
|
156
139
|
): void {
|
|
157
|
-
for (const
|
|
158
|
-
|
|
159
|
-
if (firstFile) {
|
|
160
|
-
state.injectedDirs.set(dir, { turn: state.completedTurns, file: firstFile.relativePath });
|
|
161
|
-
}
|
|
140
|
+
for (const dir of dirsToInject.keys()) {
|
|
141
|
+
state.injectedDirs.add(dir);
|
|
162
142
|
}
|
|
163
143
|
}
|
package/src/config.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Config shape (in supi shared config, "claude-md" section):
|
|
4
4
|
// {
|
|
5
|
-
// "rereadInterval": 3, // turns between subdirectory re-reads (0 = off)
|
|
6
|
-
// "contextThreshold": 80, // skip injection when context % >= threshold
|
|
7
5
|
// "subdirs": true, // enable subdirectory context discovery
|
|
8
6
|
// "fileNames": ["CLAUDE.md", "AGENTS.md"] // context file names to look for
|
|
9
7
|
// }
|
|
@@ -11,10 +9,6 @@
|
|
|
11
9
|
import { loadSupiConfig } from "@mrclrchtr/supi-core";
|
|
12
10
|
|
|
13
11
|
export interface ClaudeMdConfig {
|
|
14
|
-
/** Turns between re-reading previously injected subdirectory context. 0 = disabled. Default: 3 */
|
|
15
|
-
rereadInterval: number;
|
|
16
|
-
/** Skip injection when context window usage % >= threshold. 0 = always skip, 100 = never skip. Default: 80 */
|
|
17
|
-
contextThreshold: number;
|
|
18
12
|
/** Enable subdirectory context discovery. Default: true */
|
|
19
13
|
subdirs: boolean;
|
|
20
14
|
/** Context file names to look for (first match per directory). Default: ["CLAUDE.md", "AGENTS.md"] */
|
|
@@ -22,8 +16,6 @@ export interface ClaudeMdConfig {
|
|
|
22
16
|
}
|
|
23
17
|
|
|
24
18
|
export const CLAUDE_MD_DEFAULTS: ClaudeMdConfig = {
|
|
25
|
-
rereadInterval: 3,
|
|
26
|
-
contextThreshold: 80,
|
|
27
19
|
subdirs: true,
|
|
28
20
|
fileNames: ["CLAUDE.md", "AGENTS.md"],
|
|
29
21
|
};
|
|
@@ -8,30 +8,6 @@ import {
|
|
|
8
8
|
} from "@mrclrchtr/supi-core";
|
|
9
9
|
import { CLAUDE_MD_DEFAULTS, type ClaudeMdConfig } from "./config.ts";
|
|
10
10
|
|
|
11
|
-
const THRESHOLD_VALUES = [
|
|
12
|
-
"0",
|
|
13
|
-
"5",
|
|
14
|
-
"10",
|
|
15
|
-
"15",
|
|
16
|
-
"20",
|
|
17
|
-
"25",
|
|
18
|
-
"30",
|
|
19
|
-
"35",
|
|
20
|
-
"40",
|
|
21
|
-
"45",
|
|
22
|
-
"50",
|
|
23
|
-
"55",
|
|
24
|
-
"60",
|
|
25
|
-
"65",
|
|
26
|
-
"70",
|
|
27
|
-
"75",
|
|
28
|
-
"80",
|
|
29
|
-
"85",
|
|
30
|
-
"90",
|
|
31
|
-
"95",
|
|
32
|
-
"100",
|
|
33
|
-
];
|
|
34
|
-
|
|
35
11
|
// ── Settings registration ────────────────────────────────────
|
|
36
12
|
|
|
37
13
|
export function registerClaudeMdSettings(): void {
|
|
@@ -58,16 +34,6 @@ function handleSettingChange(
|
|
|
58
34
|
helpers.set("subdirs", value === "on");
|
|
59
35
|
break;
|
|
60
36
|
}
|
|
61
|
-
case "rereadInterval": {
|
|
62
|
-
const num = Number.parseInt(value, 10);
|
|
63
|
-
helpers.set("rereadInterval", Number.isNaN(num) ? 0 : num);
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
case "contextThreshold": {
|
|
67
|
-
const num = Number.parseInt(value, 10);
|
|
68
|
-
helpers.set("contextThreshold", Number.isNaN(num) ? 80 : num);
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
37
|
case "fileNames": {
|
|
72
38
|
const names = value
|
|
73
39
|
.split(",")
|
|
@@ -92,21 +58,6 @@ function buildClaudeMdSettingItems(settings: ClaudeMdConfig): SettingItem[] {
|
|
|
92
58
|
currentValue: settings.subdirs ? "on" : "off",
|
|
93
59
|
values: ["on", "off"],
|
|
94
60
|
},
|
|
95
|
-
{
|
|
96
|
-
id: "rereadInterval",
|
|
97
|
-
label: "Subdirectory Re-read Interval",
|
|
98
|
-
description: "Turns between re-reading previously injected subdirectory context (0 = off)",
|
|
99
|
-
currentValue: String(settings.rereadInterval),
|
|
100
|
-
submenu: (currentValue, done) =>
|
|
101
|
-
createInputSubmenu(currentValue, "Interval (0 = off):", done),
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
id: "contextThreshold",
|
|
105
|
-
label: "Context Threshold",
|
|
106
|
-
description: "Skip injection when context window usage % ≥ threshold (100 = never skip)",
|
|
107
|
-
currentValue: String(settings.contextThreshold),
|
|
108
|
-
values: THRESHOLD_VALUES,
|
|
109
|
-
},
|
|
110
61
|
{
|
|
111
62
|
id: "fileNames",
|
|
112
63
|
label: "Context File Names",
|
package/src/state.ts
CHANGED
|
@@ -5,18 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
|
|
8
|
-
export interface InjectedDir {
|
|
9
|
-
/** Turn number when this directory's context was last injected */
|
|
10
|
-
turn: number;
|
|
11
|
-
/** Relative path of the context file that was injected */
|
|
12
|
-
file: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
8
|
export interface ClaudeMdState {
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
/** Map of directory path → injection info */
|
|
19
|
-
injectedDirs: Map<string, InjectedDir>;
|
|
9
|
+
/** Set of directory paths whose context has already been injected */
|
|
10
|
+
injectedDirs: Set<string>;
|
|
20
11
|
/** Set of paths already loaded by pi natively (dedup) */
|
|
21
12
|
nativeContextPaths: Set<string>;
|
|
22
13
|
/** Whether this is the first before_agent_start (for native path capture) */
|
|
@@ -25,41 +16,27 @@ export interface ClaudeMdState {
|
|
|
25
16
|
|
|
26
17
|
export function createInitialState(): ClaudeMdState {
|
|
27
18
|
return {
|
|
28
|
-
|
|
29
|
-
injectedDirs: new Map(),
|
|
19
|
+
injectedDirs: new Set(),
|
|
30
20
|
nativeContextPaths: new Set(),
|
|
31
21
|
firstAgentStart: true,
|
|
32
22
|
};
|
|
33
23
|
}
|
|
34
24
|
|
|
35
|
-
const CONTEXT_TAG_REGEX =
|
|
36
|
-
/<extension-context\s+source="supi-claude-md"\s+file="([^"]+)"\s+turn="(\d+)">/g;
|
|
25
|
+
const CONTEXT_TAG_REGEX = /<extension-context\s+source="supi-claude-md"\s+file="([^"]+)"[^>]*>/g;
|
|
37
26
|
|
|
38
27
|
export function reconstructState(branch: SessionEntry[]): {
|
|
39
|
-
|
|
40
|
-
injectedDirs: Map<string, InjectedDir>;
|
|
28
|
+
injectedDirs: Set<string>;
|
|
41
29
|
} {
|
|
42
|
-
|
|
43
|
-
const injectedDirs = new Map<string, InjectedDir>();
|
|
30
|
+
const injectedDirs = new Set<string>();
|
|
44
31
|
|
|
45
32
|
for (const entry of branch) {
|
|
46
|
-
if (isCompletedAssistantTurn(entry)) completedTurns++;
|
|
47
|
-
|
|
48
33
|
const toolResultContent = getToolResultContent(entry);
|
|
49
34
|
if (toolResultContent) {
|
|
50
35
|
extractInjectedDirs(toolResultContent, injectedDirs);
|
|
51
36
|
}
|
|
52
37
|
}
|
|
53
38
|
|
|
54
|
-
return {
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isCompletedAssistantTurn(entry: SessionEntry): boolean {
|
|
58
|
-
return (
|
|
59
|
-
entry.type === "message" &&
|
|
60
|
-
entry.message.role === "assistant" &&
|
|
61
|
-
entry.message.stopReason === "stop"
|
|
62
|
-
);
|
|
39
|
+
return { injectedDirs };
|
|
63
40
|
}
|
|
64
41
|
|
|
65
42
|
function getToolResultContent(entry: SessionEntry): unknown {
|
|
@@ -69,7 +46,7 @@ function getToolResultContent(entry: SessionEntry): unknown {
|
|
|
69
46
|
return entry.message.content;
|
|
70
47
|
}
|
|
71
48
|
|
|
72
|
-
function extractInjectedDirs(content: unknown, injectedDirs:
|
|
49
|
+
function extractInjectedDirs(content: unknown, injectedDirs: Set<string>): void {
|
|
73
50
|
const parts = content as Array<{ type?: string; text?: string }> | undefined;
|
|
74
51
|
if (!parts) return;
|
|
75
52
|
|
|
@@ -80,15 +57,14 @@ function extractInjectedDirs(content: unknown, injectedDirs: Map<string, Injecte
|
|
|
80
57
|
}
|
|
81
58
|
}
|
|
82
59
|
|
|
83
|
-
function parseContextTags(text: string, injectedDirs:
|
|
60
|
+
function parseContextTags(text: string, injectedDirs: Set<string>): void {
|
|
84
61
|
const matches = text.matchAll(CONTEXT_TAG_REGEX);
|
|
85
62
|
for (const match of matches) {
|
|
86
63
|
const file = match[1];
|
|
87
|
-
const turn = Number.parseInt(match[2] ?? "0", 10);
|
|
88
64
|
if (file) {
|
|
89
65
|
const lastSlash = Math.max(file.lastIndexOf("/"), file.lastIndexOf("\\"));
|
|
90
66
|
const dir = lastSlash >= 0 ? file.substring(0, lastSlash) : ".";
|
|
91
|
-
injectedDirs.
|
|
67
|
+
injectedDirs.add(dir);
|
|
92
68
|
}
|
|
93
69
|
}
|
|
94
70
|
}
|
package/src/subdirectory.ts
CHANGED
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
// Subdirectory context injection logic.
|
|
2
2
|
//
|
|
3
3
|
// Handles formatting discovered context files into <extension-context> blocks
|
|
4
|
-
// and determining whether injection should occur
|
|
4
|
+
// and determining whether injection should occur.
|
|
5
5
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import { wrapExtensionContext } from "@mrclrchtr/supi-core";
|
|
8
8
|
import type { DiscoveredContextFile } from "./discovery.ts";
|
|
9
|
-
import type { InjectedDir } from "./state.ts";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Context usage info from pi's ctx.getContextUsage().
|
|
13
|
-
*/
|
|
14
|
-
export interface ContextUsage {
|
|
15
|
-
tokens: number | null;
|
|
16
|
-
contextWindow: number;
|
|
17
|
-
percent: number | null;
|
|
18
|
-
}
|
|
19
9
|
|
|
20
10
|
/**
|
|
21
11
|
* Format discovered context files into <extension-context> blocks.
|
|
22
12
|
* Each file is read and wrapped individually.
|
|
23
13
|
*/
|
|
24
|
-
export function formatSubdirContext(files: DiscoveredContextFile[]
|
|
14
|
+
export function formatSubdirContext(files: DiscoveredContextFile[]): string {
|
|
25
15
|
const parts: string[] = [];
|
|
26
16
|
|
|
27
17
|
for (const file of files) {
|
|
@@ -31,7 +21,6 @@ export function formatSubdirContext(files: DiscoveredContextFile[], turn: number
|
|
|
31
21
|
parts.push(
|
|
32
22
|
wrapExtensionContext("supi-claude-md", content, {
|
|
33
23
|
file: file.relativePath,
|
|
34
|
-
turn,
|
|
35
24
|
}),
|
|
36
25
|
);
|
|
37
26
|
}
|
|
@@ -43,42 +32,10 @@ export function formatSubdirContext(files: DiscoveredContextFile[], turn: number
|
|
|
43
32
|
return parts.join("\n\n");
|
|
44
33
|
}
|
|
45
34
|
|
|
46
|
-
export interface InjectionCheckOptions {
|
|
47
|
-
injectedDirs: Map<string, InjectedDir>;
|
|
48
|
-
currentTurn: number;
|
|
49
|
-
rereadInterval: number;
|
|
50
|
-
contextThreshold: number;
|
|
51
|
-
contextUsage?: ContextUsage;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
35
|
/**
|
|
55
36
|
* Determine if subdirectory context should be injected.
|
|
56
|
-
* Returns true if
|
|
57
|
-
* - The directory has not been injected yet (always, even under context pressure)
|
|
58
|
-
* - The directory was injected but is stale (turn delta >= rereadInterval)
|
|
59
|
-
* AND context usage is below the threshold
|
|
60
|
-
* - rereadInterval is 0 (disabled — always false for re-injections)
|
|
37
|
+
* Returns true only if the directory has not been injected yet.
|
|
61
38
|
*/
|
|
62
|
-
export function shouldInjectSubdir(dir: string,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Never-injected directory: always inject (even when rereadInterval is 0)
|
|
66
|
-
// First-time discovery is always allowed regardless of context pressure
|
|
67
|
-
const injected = injectedDirs.get(dir);
|
|
68
|
-
if (!injected) return true;
|
|
69
|
-
|
|
70
|
-
// Already-injected directory: skip if reread is disabled
|
|
71
|
-
if (rereadInterval === 0) return false;
|
|
72
|
-
|
|
73
|
-
// Re-injection: skip when context usage is at or above threshold
|
|
74
|
-
if (
|
|
75
|
-
contextThreshold < 100 &&
|
|
76
|
-
contextUsage &&
|
|
77
|
-
contextUsage.percent != null &&
|
|
78
|
-
contextUsage.percent >= contextThreshold
|
|
79
|
-
) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return currentTurn - injected.turn >= rereadInterval;
|
|
39
|
+
export function shouldInjectSubdir(dir: string, injectedDirs: Set<string>): boolean {
|
|
40
|
+
return !injectedDirs.has(dir);
|
|
84
41
|
}
|