@wrongstack/core 0.1.9 → 0.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/dist/agent-bridge-DmBiCipY.d.ts +33 -0
- package/dist/compactor-DSl2FK7a.d.ts +17 -0
- package/dist/config-DXrqb41m.d.ts +193 -0
- package/dist/{provider-txgB0Oq9.d.ts → context-u0bryklF.d.ts} +540 -472
- package/dist/coordination/index.d.ts +892 -0
- package/dist/coordination/index.js +2869 -0
- package/dist/coordination/index.js.map +1 -0
- package/dist/defaults/index.d.ts +34 -2309
- package/dist/defaults/index.js +5610 -4608
- package/dist/defaults/index.js.map +1 -1
- package/dist/events-B6Q03pTu.d.ts +290 -0
- package/dist/execution/index.d.ts +260 -0
- package/dist/execution/index.js +1625 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index.d.ts +81 -11
- package/dist/index.js +7727 -6174
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +10 -0
- package/dist/infrastructure/index.js +575 -0
- package/dist/infrastructure/index.js.map +1 -0
- package/dist/input-reader-E-ffP2ee.d.ts +12 -0
- package/dist/kernel/index.d.ts +15 -4
- package/dist/kernel/index.js.map +1 -1
- package/dist/logger-BH6AE0W9.d.ts +24 -0
- package/dist/logger-BMQgxvdy.d.ts +12 -0
- package/dist/mcp-servers-BA1Ofmfj.d.ts +100 -0
- package/dist/memory-CEXuo7sz.d.ts +16 -0
- package/dist/mode-CV077NjV.d.ts +27 -0
- package/dist/models/index.d.ts +60 -0
- package/dist/models/index.js +621 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models-registry-DqzwpBQy.d.ts +46 -0
- package/dist/models-registry-Y2xbog0E.d.ts +95 -0
- package/dist/multi-agent-BDfkxL5C.d.ts +351 -0
- package/dist/observability/index.d.ts +353 -0
- package/dist/observability/index.js +691 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability-BhnVLBLS.d.ts +67 -0
- package/dist/path-resolver-CPRj4bFY.d.ts +10 -0
- package/dist/path-resolver-Crkt8wTQ.d.ts +54 -0
- package/dist/plugin-CoYYZKdn.d.ts +447 -0
- package/dist/renderer-0A2ZEtca.d.ts +158 -0
- package/dist/sdd/index.d.ts +206 -0
- package/dist/sdd/index.js +864 -0
- package/dist/sdd/index.js.map +1 -0
- package/dist/secret-scrubber-3TLUkiCV.d.ts +31 -0
- package/dist/secret-scrubber-CwYliRWd.d.ts +54 -0
- package/dist/secret-vault-DoISxaKO.d.ts +19 -0
- package/dist/security/index.d.ts +46 -0
- package/dist/security/index.js +536 -0
- package/dist/security/index.js.map +1 -0
- package/dist/selector-BRqzvugb.d.ts +51 -0
- package/dist/session-reader-C3x96CDR.d.ts +150 -0
- package/dist/skill-Bx8jxznf.d.ts +72 -0
- package/dist/storage/index.d.ts +540 -0
- package/dist/storage/index.js +1802 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/{system-prompt-vAB0F54-.d.ts → system-prompt-CG9jU5-5.d.ts} +9 -1
- package/dist/task-graph-BITvWt4t.d.ts +160 -0
- package/dist/tool-executor-CYdZdtno.d.ts +97 -0
- package/dist/types/index.d.ts +26 -4
- package/dist/types/index.js +1787 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +49 -2
- package/dist/utils/index.js +100 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +34 -2
- package/dist/mode-Pjt5vMS6.d.ts +0 -815
- package/dist/session-reader-9sOTgmeC.d.ts +0 -1087
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export { D as DefaultModelsRegistry, a as DefaultModelsRegistryOptions, c as classifyFamily } from '../models-registry-DqzwpBQy.js';
|
|
2
|
+
import { c as ModeStore, a as ModeConfig, M as Mode } from '../mode-CV077NjV.js';
|
|
3
|
+
import { g as Provider, M as Message } from '../context-u0bryklF.js';
|
|
4
|
+
import { M as MessageSelector, S as SelectorResult } from '../selector-BRqzvugb.js';
|
|
5
|
+
import '../models-registry-Y2xbog0E.js';
|
|
6
|
+
|
|
7
|
+
declare class DefaultModeStore implements ModeStore {
|
|
8
|
+
private activeModeId;
|
|
9
|
+
private modes;
|
|
10
|
+
private configDir;
|
|
11
|
+
constructor(config: ModeConfig);
|
|
12
|
+
getActiveMode(): Promise<Mode | null>;
|
|
13
|
+
setActiveMode(modeId: string | null): Promise<void>;
|
|
14
|
+
listModes(): Promise<Mode[]>;
|
|
15
|
+
getMode(modeId: string): Promise<Mode | null>;
|
|
16
|
+
addMode(mode: Mode): Promise<void>;
|
|
17
|
+
removeMode(modeId: string): Promise<void>;
|
|
18
|
+
private loadActiveMode;
|
|
19
|
+
private saveActiveMode;
|
|
20
|
+
}
|
|
21
|
+
interface ModeLoaderOptions {
|
|
22
|
+
projectModesDir?: string;
|
|
23
|
+
userModesDir?: string;
|
|
24
|
+
}
|
|
25
|
+
declare function loadProjectModes(modesDir: string): Promise<Mode[]>;
|
|
26
|
+
declare function loadUserModes(modesDir: string): Promise<Mode[]>;
|
|
27
|
+
|
|
28
|
+
interface LLMSelectorOptions {
|
|
29
|
+
/** Provider used for the selector LLM call. Required. */
|
|
30
|
+
provider: Provider;
|
|
31
|
+
/** Model for the selector. Defaults to the provider's default model. */
|
|
32
|
+
model?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum tokens to keep in context (target budget).
|
|
35
|
+
* Selector will aim to keep total content below this.
|
|
36
|
+
*/
|
|
37
|
+
maxContextTokens?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Prompt instructing the selector how to behave.
|
|
40
|
+
* Should guide the LLM on importance tiers and output format.
|
|
41
|
+
*/
|
|
42
|
+
systemPrompt?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* LLM-powered message selector. Calls a sub-LLM to analyze the
|
|
46
|
+
* message history and produce a keep/collapse plan — more surgical
|
|
47
|
+
* than fixed-window rules.
|
|
48
|
+
*/
|
|
49
|
+
declare class LLMSelector implements MessageSelector {
|
|
50
|
+
private readonly provider;
|
|
51
|
+
private readonly model;
|
|
52
|
+
private readonly maxContextTokens;
|
|
53
|
+
private readonly systemPrompt;
|
|
54
|
+
constructor(opts: LLMSelectorOptions);
|
|
55
|
+
select(messages: Message[], maxToKeep: number): Promise<SelectorResult>;
|
|
56
|
+
private fallbackSelect;
|
|
57
|
+
private parseSelectorOutput;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { DefaultModeStore, LLMSelector, type LLMSelectorOptions, type ModeLoaderOptions, loadProjectModes, loadUserModes };
|
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path3 from 'path';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
|
|
5
|
+
// src/models/models-registry.ts
|
|
6
|
+
async function atomicWrite(targetPath, content, opts = {}) {
|
|
7
|
+
const dir = path3.dirname(targetPath);
|
|
8
|
+
await fs.mkdir(dir, { recursive: true });
|
|
9
|
+
const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
10
|
+
try {
|
|
11
|
+
if (typeof content === "string") {
|
|
12
|
+
await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
13
|
+
} else {
|
|
14
|
+
await fs.writeFile(tmp, content, { flag: "wx" });
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const fh = await fs.open(tmp, "r+");
|
|
18
|
+
try {
|
|
19
|
+
await fh.sync();
|
|
20
|
+
} finally {
|
|
21
|
+
await fh.close();
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
let mode;
|
|
26
|
+
try {
|
|
27
|
+
const stat3 = await fs.stat(targetPath);
|
|
28
|
+
mode = stat3.mode & 511;
|
|
29
|
+
} catch {
|
|
30
|
+
mode = opts.mode;
|
|
31
|
+
}
|
|
32
|
+
if (mode !== void 0) {
|
|
33
|
+
await fs.chmod(tmp, mode);
|
|
34
|
+
}
|
|
35
|
+
await fs.rename(tmp, targetPath);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
try {
|
|
38
|
+
await fs.unlink(tmp);
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/models/models-registry.ts
|
|
46
|
+
var DEFAULT_URL = "https://models.dev/api.json";
|
|
47
|
+
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
48
|
+
var FAMILY_BY_NPM = {
|
|
49
|
+
"@ai-sdk/anthropic": "anthropic",
|
|
50
|
+
"@ai-sdk/google-vertex/anthropic": "anthropic",
|
|
51
|
+
"@ai-sdk/openai": "openai",
|
|
52
|
+
"@ai-sdk/openai-compatible": "openai-compatible",
|
|
53
|
+
"@ai-sdk/groq": "openai-compatible",
|
|
54
|
+
"@ai-sdk/xai": "openai-compatible",
|
|
55
|
+
"@ai-sdk/cerebras": "openai-compatible",
|
|
56
|
+
"@ai-sdk/togetherai": "openai-compatible",
|
|
57
|
+
"@ai-sdk/perplexity": "openai-compatible",
|
|
58
|
+
"@ai-sdk/deepinfra": "openai-compatible",
|
|
59
|
+
"@openrouter/ai-sdk-provider": "openai-compatible",
|
|
60
|
+
"ai-gateway-provider": "openai-compatible",
|
|
61
|
+
"@ai-sdk/vercel": "openai-compatible",
|
|
62
|
+
"@ai-sdk/gateway": "openai-compatible",
|
|
63
|
+
"@aihubmix/ai-sdk-provider": "openai-compatible",
|
|
64
|
+
"venice-ai-sdk-provider": "openai-compatible",
|
|
65
|
+
"@ai-sdk/google": "google"
|
|
66
|
+
};
|
|
67
|
+
function classifyFamily(npm) {
|
|
68
|
+
if (!npm) return "unsupported";
|
|
69
|
+
return FAMILY_BY_NPM[npm] ?? "unsupported";
|
|
70
|
+
}
|
|
71
|
+
var DefaultModelsRegistry = class {
|
|
72
|
+
payload;
|
|
73
|
+
fetchedAt;
|
|
74
|
+
cacheFile;
|
|
75
|
+
url;
|
|
76
|
+
ttlMs;
|
|
77
|
+
fetchImpl;
|
|
78
|
+
seed;
|
|
79
|
+
maxStaleAgeMs;
|
|
80
|
+
constructor(opts) {
|
|
81
|
+
this.cacheFile = opts.cacheFile;
|
|
82
|
+
this.url = opts.url ?? DEFAULT_URL;
|
|
83
|
+
this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
|
|
84
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
85
|
+
this.seed = opts.seed;
|
|
86
|
+
const maxStaleSeconds = opts.maxStaleAgeSeconds ?? 7 * 24 * 3600;
|
|
87
|
+
this.maxStaleAgeMs = maxStaleSeconds * 1e3;
|
|
88
|
+
}
|
|
89
|
+
async load(opts = {}) {
|
|
90
|
+
if (this.payload && !opts.force) return this.payload;
|
|
91
|
+
if (this.seed) {
|
|
92
|
+
this.payload = this.seed;
|
|
93
|
+
this.fetchedAt = /* @__PURE__ */ new Date();
|
|
94
|
+
return this.payload;
|
|
95
|
+
}
|
|
96
|
+
if (!opts.force) {
|
|
97
|
+
const cached = await this.readCache();
|
|
98
|
+
if (cached && this.isFresh(cached.fetchedAt)) {
|
|
99
|
+
this.payload = cached.payload;
|
|
100
|
+
this.fetchedAt = new Date(cached.fetchedAt);
|
|
101
|
+
return cached.payload;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return await this.refresh();
|
|
106
|
+
} catch (err) {
|
|
107
|
+
const cached = await this.readCache();
|
|
108
|
+
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
|
|
109
|
+
this.payload = cached.payload;
|
|
110
|
+
this.fetchedAt = new Date(cached.fetchedAt);
|
|
111
|
+
return cached.payload;
|
|
112
|
+
}
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async refresh() {
|
|
117
|
+
const res = await this.fetchImpl(this.url, {
|
|
118
|
+
method: "GET",
|
|
119
|
+
headers: { accept: "application/json" }
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
throw new Error(`ModelsRegistry: HTTP ${res.status} fetching ${this.url}`);
|
|
123
|
+
}
|
|
124
|
+
const json = await res.json();
|
|
125
|
+
this.payload = json;
|
|
126
|
+
this.fetchedAt = /* @__PURE__ */ new Date();
|
|
127
|
+
const envelope = {
|
|
128
|
+
fetchedAt: this.fetchedAt.toISOString(),
|
|
129
|
+
url: this.url,
|
|
130
|
+
payload: json
|
|
131
|
+
};
|
|
132
|
+
await atomicWrite(this.cacheFile, JSON.stringify(envelope));
|
|
133
|
+
return json;
|
|
134
|
+
}
|
|
135
|
+
async listProviders() {
|
|
136
|
+
const payload = await this.load();
|
|
137
|
+
return Object.values(payload).map((p) => this.resolveProvider(p));
|
|
138
|
+
}
|
|
139
|
+
async getProvider(id) {
|
|
140
|
+
const payload = await this.load();
|
|
141
|
+
const p = payload[id];
|
|
142
|
+
return p ? this.resolveProvider(p) : void 0;
|
|
143
|
+
}
|
|
144
|
+
async getModel(providerId, modelId) {
|
|
145
|
+
const provider = await this.getProvider(providerId);
|
|
146
|
+
if (!provider) return void 0;
|
|
147
|
+
const model = provider.models.find((m) => m.id === modelId);
|
|
148
|
+
if (!model) return void 0;
|
|
149
|
+
return {
|
|
150
|
+
providerId,
|
|
151
|
+
modelId,
|
|
152
|
+
capabilities: {
|
|
153
|
+
tools: model.tool_call ?? false,
|
|
154
|
+
vision: Boolean(model.modalities?.input?.includes("image")),
|
|
155
|
+
reasoning: model.reasoning ?? false,
|
|
156
|
+
maxContext: model.limit?.context ?? 0,
|
|
157
|
+
maxOutput: model.limit?.output,
|
|
158
|
+
knowledge: model.knowledge
|
|
159
|
+
},
|
|
160
|
+
cost: model.cost
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async suggestModel(providerId) {
|
|
164
|
+
const provider = await this.getProvider(providerId);
|
|
165
|
+
if (!provider || provider.models.length === 0) return void 0;
|
|
166
|
+
const ranked = [...provider.models].sort((a, b) => {
|
|
167
|
+
const at = a.release_date ?? a.last_updated ?? "";
|
|
168
|
+
const bt = b.release_date ?? b.last_updated ?? "";
|
|
169
|
+
return bt.localeCompare(at);
|
|
170
|
+
});
|
|
171
|
+
return ranked[0]?.id;
|
|
172
|
+
}
|
|
173
|
+
async ageSeconds() {
|
|
174
|
+
if (!this.fetchedAt) {
|
|
175
|
+
const cached = await this.readCache();
|
|
176
|
+
if (!cached) return Number.POSITIVE_INFINITY;
|
|
177
|
+
return (Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3;
|
|
178
|
+
}
|
|
179
|
+
return (Date.now() - this.fetchedAt.getTime()) / 1e3;
|
|
180
|
+
}
|
|
181
|
+
resolveProvider(p) {
|
|
182
|
+
return {
|
|
183
|
+
id: p.id,
|
|
184
|
+
name: p.name,
|
|
185
|
+
family: classifyFamily(p.npm),
|
|
186
|
+
apiBase: p.api,
|
|
187
|
+
envVars: p.env ?? [],
|
|
188
|
+
doc: p.doc,
|
|
189
|
+
models: Object.values(p.models ?? {}),
|
|
190
|
+
npm: p.npm
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
isFresh(fetchedAtIso) {
|
|
194
|
+
return Date.now() - new Date(fetchedAtIso).getTime() < this.ttlMs;
|
|
195
|
+
}
|
|
196
|
+
isWithinMaxStaleAge(fetchedAtIso) {
|
|
197
|
+
return Date.now() - new Date(fetchedAtIso).getTime() < this.maxStaleAgeMs;
|
|
198
|
+
}
|
|
199
|
+
async readCache() {
|
|
200
|
+
try {
|
|
201
|
+
const raw = await fs.readFile(this.cacheFile, "utf8");
|
|
202
|
+
return JSON.parse(raw);
|
|
203
|
+
} catch {
|
|
204
|
+
return void 0;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
208
|
+
cacheLocation() {
|
|
209
|
+
return path3.resolve(this.cacheFile);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/types/mode.ts
|
|
214
|
+
var DEFAULT_MODES = [
|
|
215
|
+
{
|
|
216
|
+
id: "default",
|
|
217
|
+
name: "Default",
|
|
218
|
+
description: "General-purpose coding assistant",
|
|
219
|
+
prompt: "",
|
|
220
|
+
tags: ["general"]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "code-reviewer",
|
|
224
|
+
name: "Code Reviewer",
|
|
225
|
+
description: "Focus on code quality, best practices, and potential bugs",
|
|
226
|
+
prompt: `## Code Reviewer Mode
|
|
227
|
+
|
|
228
|
+
When reviewing code:
|
|
229
|
+
- Look for potential bugs, race conditions, and edge cases
|
|
230
|
+
- Check for security vulnerabilities (SQL injection, XSS, CSRF, etc.)
|
|
231
|
+
- Evaluate error handling completeness
|
|
232
|
+
- Assess code readability and maintainability
|
|
233
|
+
- Check for performance anti-patterns
|
|
234
|
+
- Verify test coverage for critical paths
|
|
235
|
+
- Ensure naming conventions are followed`,
|
|
236
|
+
tags: ["review", "quality", "security"],
|
|
237
|
+
toolPreferences: ["read", "grep", "git", "diff", "test"]
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "code-auditor",
|
|
241
|
+
name: "Code Auditor",
|
|
242
|
+
description: "Security-focused code analysis",
|
|
243
|
+
prompt: `## Code Auditor Mode
|
|
244
|
+
|
|
245
|
+
When auditing code for security:
|
|
246
|
+
- Identify injection vulnerabilities (SQL, Command, XSS, LDAP)
|
|
247
|
+
- Check authentication and authorization patterns
|
|
248
|
+
- Look for sensitive data exposure (secrets, PII in logs)
|
|
249
|
+
- Verify cryptographic implementations
|
|
250
|
+
- Check for insecure dependencies or configurations
|
|
251
|
+
- Assess input validation and output encoding
|
|
252
|
+
- Look for timing attacks and information leakage`,
|
|
253
|
+
tags: ["security", "audit", "compliance"],
|
|
254
|
+
toolPreferences: ["grep", "read", "audit", "bash"]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: "architect",
|
|
258
|
+
name: "Software Architect",
|
|
259
|
+
description: "Design patterns, scalability, and system design",
|
|
260
|
+
prompt: `## Architect Mode
|
|
261
|
+
|
|
262
|
+
When designing or reviewing architecture:
|
|
263
|
+
- Evaluate scalability and future growth
|
|
264
|
+
- Check for appropriate design patterns
|
|
265
|
+
- Assess coupling and cohesion
|
|
266
|
+
- Look forSOLID principle violations
|
|
267
|
+
- Evaluate data modeling decisions
|
|
268
|
+
- Check for eventual consistency issues
|
|
269
|
+
- Assess API design and contract stability
|
|
270
|
+
- Consider operational aspects (monitoring, logging, deployment)`,
|
|
271
|
+
tags: ["architecture", "design", "scalability"],
|
|
272
|
+
toolPreferences: ["read", "glob", "tree", "diff"]
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: "debugger",
|
|
276
|
+
name: "Debugger",
|
|
277
|
+
description: "Root cause analysis and error investigation",
|
|
278
|
+
prompt: `## Debugger Mode
|
|
279
|
+
|
|
280
|
+
When investigating bugs:
|
|
281
|
+
- Reproduce the issue with minimal steps
|
|
282
|
+
- Check error messages and stack traces thoroughly
|
|
283
|
+
- Look for related logs and historical context
|
|
284
|
+
- Verify assumptions about data flow
|
|
285
|
+
- Check for race conditions in async code
|
|
286
|
+
- Validate environment and configuration
|
|
287
|
+
- Use binary search to isolate the root cause
|
|
288
|
+
- Verify fixes with tests before considering done`,
|
|
289
|
+
tags: ["debug", "investigation", "error-resolution"],
|
|
290
|
+
toolPreferences: ["read", "grep", "bash", "logs", "test"]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "tester",
|
|
294
|
+
name: "QA Engineer",
|
|
295
|
+
description: "Test coverage, edge cases, and quality assurance",
|
|
296
|
+
prompt: `## Tester Mode
|
|
297
|
+
|
|
298
|
+
When testing or writing tests:
|
|
299
|
+
- Cover happy path and error paths equally
|
|
300
|
+
- Think about edge cases and boundary conditions
|
|
301
|
+
- Check for missing null/undefined handling tests
|
|
302
|
+
- Verify error messages are tested
|
|
303
|
+
- Look for race condition tests in async code
|
|
304
|
+
- Assess mutation testing opportunities
|
|
305
|
+
- Check for integration test gaps
|
|
306
|
+
- Verify test isolation and cleanup`,
|
|
307
|
+
tags: ["testing", "qa", "quality"],
|
|
308
|
+
toolPreferences: ["read", "grep", "test", "bash"]
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "devops",
|
|
312
|
+
name: "DevOps Engineer",
|
|
313
|
+
description: "Infrastructure, deployment, and operations",
|
|
314
|
+
prompt: `## DevOps Mode
|
|
315
|
+
|
|
316
|
+
When working on infrastructure:
|
|
317
|
+
- Check for containerization and deployment readiness
|
|
318
|
+
- Verify CI/CD pipeline configurations
|
|
319
|
+
- Assess monitoring and alerting setup
|
|
320
|
+
- Look for health check endpoints
|
|
321
|
+
- Check for graceful shutdown handling
|
|
322
|
+
- Verify backup and disaster recovery plans
|
|
323
|
+
- Assess secrets management
|
|
324
|
+
- Check for resource limits and quotas`,
|
|
325
|
+
tags: ["devops", "infrastructure", "operations"],
|
|
326
|
+
toolPreferences: ["read", "bash", "grep", "logs", "git"]
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: "refactorer",
|
|
330
|
+
name: "Refactorer",
|
|
331
|
+
description: "Code improvement and modernization",
|
|
332
|
+
prompt: `## Refactorer Mode
|
|
333
|
+
|
|
334
|
+
When refactoring code:
|
|
335
|
+
- Maintain existing behavior \u2014 tests must pass before and after
|
|
336
|
+
- Make one change at a time, verify after each
|
|
337
|
+
- Prefer small, focused commits
|
|
338
|
+
- Preserve API contracts unless explicitly changing
|
|
339
|
+
- Remove dead code and comments
|
|
340
|
+
- Improve naming as you go
|
|
341
|
+
- Don't mix formatting changes with logic changes
|
|
342
|
+
- Keep performance in mind \u2014 don't regress`,
|
|
343
|
+
tags: ["refactor", "modernization", "improvement"],
|
|
344
|
+
toolPreferences: ["read", "edit", "test", "git", "grep"]
|
|
345
|
+
}
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
// src/models/mode-store.ts
|
|
349
|
+
var DefaultModeStore = class {
|
|
350
|
+
activeModeId = null;
|
|
351
|
+
modes;
|
|
352
|
+
configDir;
|
|
353
|
+
constructor(config) {
|
|
354
|
+
this.configDir = config.directory;
|
|
355
|
+
this.modes = [...DEFAULT_MODES];
|
|
356
|
+
}
|
|
357
|
+
async getActiveMode() {
|
|
358
|
+
if (!this.activeModeId) {
|
|
359
|
+
await this.loadActiveMode();
|
|
360
|
+
}
|
|
361
|
+
if (!this.activeModeId) return null;
|
|
362
|
+
return this.modes.find((m) => m.id === this.activeModeId) ?? null;
|
|
363
|
+
}
|
|
364
|
+
async setActiveMode(modeId) {
|
|
365
|
+
this.activeModeId = modeId;
|
|
366
|
+
await this.saveActiveMode();
|
|
367
|
+
}
|
|
368
|
+
async listModes() {
|
|
369
|
+
return [...this.modes];
|
|
370
|
+
}
|
|
371
|
+
async getMode(modeId) {
|
|
372
|
+
return this.modes.find((m) => m.id === modeId) ?? null;
|
|
373
|
+
}
|
|
374
|
+
async addMode(mode) {
|
|
375
|
+
const idx = this.modes.findIndex((m) => m.id === mode.id);
|
|
376
|
+
if (idx >= 0) {
|
|
377
|
+
this.modes[idx] = mode;
|
|
378
|
+
} else {
|
|
379
|
+
this.modes.push(mode);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
async removeMode(modeId) {
|
|
383
|
+
const builtIn = DEFAULT_MODES.find((m) => m.id === modeId);
|
|
384
|
+
if (builtIn) {
|
|
385
|
+
throw new Error(`Cannot remove built-in mode "${modeId}"`);
|
|
386
|
+
}
|
|
387
|
+
this.modes = this.modes.filter((m) => m.id !== modeId);
|
|
388
|
+
if (this.activeModeId === modeId) {
|
|
389
|
+
this.activeModeId = null;
|
|
390
|
+
await this.saveActiveMode();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async loadActiveMode() {
|
|
394
|
+
try {
|
|
395
|
+
const configPath = path3.join(this.configDir, "mode.json");
|
|
396
|
+
const content = await fs.readFile(configPath, "utf8");
|
|
397
|
+
const data = JSON.parse(content);
|
|
398
|
+
this.activeModeId = data.activeMode ?? null;
|
|
399
|
+
} catch {
|
|
400
|
+
this.activeModeId = "default";
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async saveActiveMode() {
|
|
404
|
+
try {
|
|
405
|
+
await fs.mkdir(this.configDir, { recursive: true });
|
|
406
|
+
const configPath = path3.join(this.configDir, "mode.json");
|
|
407
|
+
await fs.writeFile(
|
|
408
|
+
configPath,
|
|
409
|
+
JSON.stringify({ activeMode: this.activeModeId }, null, 2),
|
|
410
|
+
"utf8"
|
|
411
|
+
);
|
|
412
|
+
} catch {
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
async function loadProjectModes(modesDir) {
|
|
417
|
+
const modes = [];
|
|
418
|
+
try {
|
|
419
|
+
const entries = await fs.readdir(modesDir);
|
|
420
|
+
for (const entry of entries) {
|
|
421
|
+
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
422
|
+
const filePath = path3.join(modesDir, entry);
|
|
423
|
+
const stat3 = await fs.stat(filePath);
|
|
424
|
+
if (!stat3.isFile()) continue;
|
|
425
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
426
|
+
const id = path3.basename(entry, path3.extname(entry));
|
|
427
|
+
modes.push({
|
|
428
|
+
id,
|
|
429
|
+
name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
430
|
+
description: content.split("\n")[0] ?? id,
|
|
431
|
+
prompt: content,
|
|
432
|
+
tags: ["project"]
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
return modes;
|
|
438
|
+
}
|
|
439
|
+
async function loadUserModes(modesDir) {
|
|
440
|
+
const modes = [];
|
|
441
|
+
try {
|
|
442
|
+
const manifestPath = path3.join(modesDir, "modes.json");
|
|
443
|
+
const content = await fs.readFile(manifestPath, "utf8");
|
|
444
|
+
const manifest = JSON.parse(content);
|
|
445
|
+
for (const mode of manifest.modes) {
|
|
446
|
+
modes.push(mode);
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
return modes;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/types/blocks.ts
|
|
454
|
+
function isTextBlock(b) {
|
|
455
|
+
return b.type === "text";
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/models/llm-selector.ts
|
|
459
|
+
var DEFAULT_SYSTEM_PROMPT = `You are a context pruning assistant. Given a conversation history and a token budget, decide which message ranges are worth keeping verbatim and which should be collapsed into summaries.
|
|
460
|
+
|
|
461
|
+
Output a JSON object with this structure:
|
|
462
|
+
{
|
|
463
|
+
"kept": [{"from": 0, "to": 5, "importance": "critical"}],
|
|
464
|
+
"collapsed": [{"from": 6, "to": 20, "summary": "optional summary"}],
|
|
465
|
+
"reasoning": "brief explanation of decisions"
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
Importance tiers:
|
|
469
|
+
- "critical": decisions, file edits, tool results that affect state, final answers
|
|
470
|
+
- "high": substantive tool use, complex reasoning, non-obvious observations
|
|
471
|
+
- "medium": routine exchanges, confirmations, straightforward Q&A
|
|
472
|
+
|
|
473
|
+
Rules:
|
|
474
|
+
- Always keep the most recent K pairs (preserve recency)
|
|
475
|
+
- Never collapse the final 2 user/assistant pairs (working memory)
|
|
476
|
+
- Preserve tool results that modified files or had external effects
|
|
477
|
+
- Collapse old, low-information exchanges (greetings, acknowledgements, etc.)
|
|
478
|
+
- If unsure, keep rather than collapse (errors are more costly than waste)
|
|
479
|
+
|
|
480
|
+
Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
|
|
481
|
+
function estimateTokens(messages) {
|
|
482
|
+
let total = 0;
|
|
483
|
+
for (const m of messages) {
|
|
484
|
+
if (typeof m.content === "string") {
|
|
485
|
+
total += Math.ceil(m.content.length / 4);
|
|
486
|
+
} else if (Array.isArray(m.content)) {
|
|
487
|
+
for (const b of m.content) {
|
|
488
|
+
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
489
|
+
else total += Math.ceil(JSON.stringify(b).length / 4);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return total;
|
|
494
|
+
}
|
|
495
|
+
function formatMessages(messages, maxChars = 8e3) {
|
|
496
|
+
const lines = [];
|
|
497
|
+
let used = 0;
|
|
498
|
+
for (let i = 0; i < messages.length; i++) {
|
|
499
|
+
const m = messages[i];
|
|
500
|
+
const role = m.role.padEnd(10, " ");
|
|
501
|
+
let text;
|
|
502
|
+
if (typeof m.content === "string") {
|
|
503
|
+
text = m.content.slice(0, 500);
|
|
504
|
+
} else {
|
|
505
|
+
const content = m.content;
|
|
506
|
+
text = content.filter(isTextBlock).map((b) => b.text).join(" ");
|
|
507
|
+
const toolUses = content.filter((b) => b.type === "tool_use");
|
|
508
|
+
if (toolUses.length > 0) {
|
|
509
|
+
text += ` [tools: ${toolUses.map((b) => b.name).join(", ")}]`;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const line = `[${i}][${role}]: ${text}`;
|
|
513
|
+
if (used + line.length > maxChars) break;
|
|
514
|
+
lines.push(line);
|
|
515
|
+
used += line.length;
|
|
516
|
+
}
|
|
517
|
+
return lines.join("\n");
|
|
518
|
+
}
|
|
519
|
+
var LLMSelector = class {
|
|
520
|
+
provider;
|
|
521
|
+
model;
|
|
522
|
+
maxContextTokens;
|
|
523
|
+
systemPrompt;
|
|
524
|
+
constructor(opts) {
|
|
525
|
+
this.provider = opts.provider;
|
|
526
|
+
this.model = opts.model ?? "unknown";
|
|
527
|
+
this.maxContextTokens = opts.maxContextTokens ?? 4e4;
|
|
528
|
+
this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
529
|
+
}
|
|
530
|
+
async select(messages, maxToKeep) {
|
|
531
|
+
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
532
|
+
const historyText = formatMessages(messages);
|
|
533
|
+
const totalTokens = estimateTokens(messages);
|
|
534
|
+
const systemText = `${this.systemPrompt}
|
|
535
|
+
|
|
536
|
+
Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
|
|
537
|
+
`;
|
|
538
|
+
const budgetInstruction = totalTokens > effectiveBudget ? `
|
|
539
|
+
|
|
540
|
+
IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiveBudget}). You MUST collapse enough to fit. Prefer collapsing older/lower-importance ranges.` : "";
|
|
541
|
+
const req = {
|
|
542
|
+
model: this.model,
|
|
543
|
+
system: [{ type: "text", text: systemText + budgetInstruction }],
|
|
544
|
+
messages: [{ role: "user", content: historyText }],
|
|
545
|
+
maxTokens: 1024
|
|
546
|
+
};
|
|
547
|
+
let raw;
|
|
548
|
+
try {
|
|
549
|
+
const ac = new AbortController();
|
|
550
|
+
const res = await this.provider.complete(req, { signal: ac.signal });
|
|
551
|
+
const textBlocks = res.content.filter(isTextBlock);
|
|
552
|
+
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
553
|
+
} catch (err) {
|
|
554
|
+
return this.fallbackSelect(messages, effectiveBudget);
|
|
555
|
+
}
|
|
556
|
+
return this.parseSelectorOutput(raw, messages.length);
|
|
557
|
+
}
|
|
558
|
+
fallbackSelect(messages, budget) {
|
|
559
|
+
const toKeep = [];
|
|
560
|
+
const toCollapse = [];
|
|
561
|
+
let tokenCount = 0;
|
|
562
|
+
let startIdx = 0;
|
|
563
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
564
|
+
const m = messages[i];
|
|
565
|
+
const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
|
|
566
|
+
(acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
|
|
567
|
+
0
|
|
568
|
+
);
|
|
569
|
+
if (tokenCount + cost <= budget) {
|
|
570
|
+
tokenCount += cost;
|
|
571
|
+
} else {
|
|
572
|
+
startIdx = i + 1;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (startIdx > 0) {
|
|
577
|
+
toCollapse.push({ from: 0, to: startIdx - 1 });
|
|
578
|
+
}
|
|
579
|
+
toKeep.push({ from: startIdx, to: messages.length - 1, importance: "high" });
|
|
580
|
+
return {
|
|
581
|
+
kept: toKeep,
|
|
582
|
+
collapsed: toCollapse,
|
|
583
|
+
reasoning: `Fallback: kept last ${messages.length - startIdx} messages within ${budget} token budget`
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
parseSelectorOutput(raw, messageCount) {
|
|
587
|
+
const jsonStart = raw.indexOf("{");
|
|
588
|
+
const jsonEnd = raw.lastIndexOf("}");
|
|
589
|
+
if (jsonStart === -1 || jsonEnd === -1) {
|
|
590
|
+
return this.fallbackSelect(
|
|
591
|
+
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
592
|
+
this.maxContextTokens
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
let parsed;
|
|
596
|
+
try {
|
|
597
|
+
parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
|
|
598
|
+
} catch {
|
|
599
|
+
return this.fallbackSelect(
|
|
600
|
+
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
601
|
+
this.maxContextTokens
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
const obj = parsed;
|
|
605
|
+
const kept = obj.kept ?? [];
|
|
606
|
+
const collapsed = obj.collapsed ?? [];
|
|
607
|
+
return {
|
|
608
|
+
kept: kept.map((k) => ({
|
|
609
|
+
from: k.from,
|
|
610
|
+
to: k.to,
|
|
611
|
+
importance: k.importance ?? "medium"
|
|
612
|
+
})),
|
|
613
|
+
collapsed: collapsed.map((c) => ({ from: c.from, to: c.to, summary: c.summary })),
|
|
614
|
+
reasoning: typeof obj.reasoning === "string" ? obj.reasoning : ""
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
export { DefaultModeStore, DefaultModelsRegistry, LLMSelector, classifyFamily, loadProjectModes, loadUserModes };
|
|
620
|
+
//# sourceMappingURL=index.js.map
|
|
621
|
+
//# sourceMappingURL=index.js.map
|