codetrap 0.1.7 → 0.1.9
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 +132 -98
- package/docs/installation.md +61 -63
- package/package.json +4 -3
- package/plugins/codetrap-agent/.codex-plugin/plugin.json +2 -3
- package/plugins/codetrap-agent/hooks/post-flight-capture.example.md +19 -17
- package/plugins/codetrap-agent/hooks.json +2 -2
- package/{skills → plugins/codetrap-agent/skills}/codetrap-add/SKILL.md +10 -4
- package/plugins/codetrap-agent/skills/codetrap-capture/SKILL.md +14 -3
- package/plugins/codetrap-agent/skills/codetrap-capture-external/SKILL.md +52 -9
- package/plugins/codetrap-agent/skills/codetrap-check/SKILL.md +74 -6
- package/{skills → plugins/codetrap-agent/skills}/codetrap-search/SKILL.md +6 -5
- package/plugins/codetrap-agent/templates/AGENTS.codetrap-maintainer.md +15 -0
- package/plugins/codetrap-agent/templates/AGENTS.codetrap.md +16 -5
- package/scripts/release-preflight.ts +15 -0
- package/scripts/search-policy-sweep.ts +131 -0
- package/src/commands/workflow.ts +172 -68
- package/src/db/embedding-queries.ts +230 -48
- package/src/db/queries.ts +0 -25
- package/src/db/repository.ts +32 -21
- package/src/db/schema.ts +80 -0
- package/src/index.ts +34 -4
- package/src/lib/codex-setup.ts +247 -0
- package/src/lib/command-requests.ts +112 -1
- package/src/lib/config.ts +57 -7
- package/src/lib/constants.ts +1 -1
- package/src/lib/doctor.ts +42 -12
- package/src/lib/embedder.ts +118 -3
- package/src/lib/embedding-health.ts +3 -1
- package/src/lib/embedding-job.ts +3 -0
- package/src/lib/embedding-management.ts +65 -0
- package/src/lib/embedding-runtime.ts +177 -0
- package/src/lib/output-json.ts +0 -2
- package/src/lib/scope-context.ts +12 -6
- package/src/lib/scope-migration.ts +2 -1
- package/src/lib/scope.ts +0 -2
- package/src/lib/search-eval.ts +38 -18
- package/src/lib/search-policy-sweep.ts +563 -0
- package/src/lib/search-policy.ts +0 -4
- package/src/lib/search-service.ts +14 -15
- package/src/lib/session-candidate-document.ts +175 -0
- package/src/lib/session-candidate-scope.ts +6 -0
- package/src/lib/session-capture.ts +298 -32
- package/src/lib/session-codec.ts +1 -8
- package/src/lib/session-operations.ts +83 -60
- package/src/lib/session-review.ts +327 -0
- package/src/lib/session-store.ts +87 -73
- package/src/lib/store.ts +74 -10
- package/src/lib/string-list.ts +3 -0
- package/src/lib/text-lines.ts +7 -0
- package/src/lib/trap-search-document.ts +2 -1
- package/src/lib/value-types.ts +3 -0
- package/src/web/client-review.ts +171 -0
- package/src/web/client-script.ts +426 -51
- package/src/web/client-shell.ts +414 -0
- package/src/web/client-text.ts +112 -0
- package/src/web/project-registry.ts +3 -5
- package/src/web/server.ts +117 -103
- package/src/web/static.ts +364 -19
- package/skills/codetrap-capture-external/SKILL.md +0 -62
- package/skills/codetrap-check/SKILL.md +0 -69
- package/src/lib/embedding-index.ts +0 -53
|
@@ -2,7 +2,9 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
4
|
import { CODETRAP_DIR } from "../lib/constants";
|
|
5
|
-
import { findProjectRoot
|
|
5
|
+
import { findProjectRoot } from "../lib/scope";
|
|
6
|
+
import { resolveScopePath } from "../lib/scope-path";
|
|
7
|
+
import { isRecord } from "../lib/value-types";
|
|
6
8
|
|
|
7
9
|
export const WEB_PROJECTS_FILE = "web-projects.json";
|
|
8
10
|
export const WEB_PROJECTS_VERSION = 1;
|
|
@@ -100,7 +102,3 @@ function uniqueProjects(projects: WebProject[]): WebProject[] {
|
|
|
100
102
|
}
|
|
101
103
|
return out;
|
|
102
104
|
}
|
|
103
|
-
|
|
104
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
105
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
106
|
-
}
|
package/src/web/server.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { DEFAULT_OLLAMA_DIMENSIONS, DEFAULT_OLLAMA_ENDPOINT, DEFAULT_OLLAMA_MODEL, EmbeddingProviderUnavailableError } from "../lib/embedder";
|
|
2
3
|
import { CATEGORIES, SCOPES, SEVERITIES } from "../lib/constants";
|
|
3
|
-
import type
|
|
4
|
+
import { loadCodetrapConfig, type EmbeddingProviderSetting, type EmbeddingSettings } from "../lib/config";
|
|
4
5
|
import { TrapStore } from "../lib/store";
|
|
5
6
|
import { TrapOperations } from "../lib/trap-operations";
|
|
6
|
-
import { SessionOperations
|
|
7
|
+
import { SessionOperations } from "../lib/session-operations";
|
|
7
8
|
import { SessionStore } from "../lib/session-store";
|
|
8
9
|
import { toListJson, toTrapDetailsJson } from "../lib/output-json";
|
|
10
|
+
import { isRecord } from "../lib/value-types";
|
|
11
|
+
import {
|
|
12
|
+
reviewedSessionCandidates,
|
|
13
|
+
sessionConflictPayload,
|
|
14
|
+
} from "../lib/session-review";
|
|
9
15
|
import { WEB_INDEX_HTML } from "./static";
|
|
10
16
|
import {
|
|
11
17
|
addWebProject,
|
|
@@ -30,33 +36,6 @@ type WebContext = {
|
|
|
30
36
|
currentProjectRoot: string | null;
|
|
31
37
|
};
|
|
32
38
|
|
|
33
|
-
type WebCandidateReview =
|
|
34
|
-
| { status: "pending"; label: string }
|
|
35
|
-
| {
|
|
36
|
-
status: "accepted";
|
|
37
|
-
label: string;
|
|
38
|
-
trap_id: number;
|
|
39
|
-
scope: string;
|
|
40
|
-
trap_present: true;
|
|
41
|
-
trap_status: string;
|
|
42
|
-
trap_title: string;
|
|
43
|
-
}
|
|
44
|
-
| {
|
|
45
|
-
status: "accepted_missing";
|
|
46
|
-
label: string;
|
|
47
|
-
trap_id?: number;
|
|
48
|
-
scope?: string;
|
|
49
|
-
trap_present: false;
|
|
50
|
-
}
|
|
51
|
-
| {
|
|
52
|
-
status: "rejected";
|
|
53
|
-
label: string;
|
|
54
|
-
rejected_at?: string;
|
|
55
|
-
rejection_reason?: string;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type WebCandidate = CandidateTrap & { review: WebCandidateReview };
|
|
59
|
-
|
|
60
39
|
export async function startWebServerFromArgs(args: string[], cwd = process.cwd()): Promise<void> {
|
|
61
40
|
const options = webServerOptionsFromArgs(args, cwd);
|
|
62
41
|
const token = options.token ?? randomBytes(18).toString("base64url");
|
|
@@ -145,8 +124,13 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
145
124
|
|
|
146
125
|
if (request.method === "GET" && url.pathname === "/api/sessions") {
|
|
147
126
|
const projectRoot = projectRootFromQuery(url, context);
|
|
148
|
-
const
|
|
149
|
-
|
|
127
|
+
const ops = sessionOperations(projectRoot, context.home);
|
|
128
|
+
const sessions = ops.sessions.listSessions({ status: "all", limit: 100 });
|
|
129
|
+
return jsonResponse({
|
|
130
|
+
project_root: projectRoot,
|
|
131
|
+
candidate_review: ops.sessions.candidateReviewSummary(),
|
|
132
|
+
sessions,
|
|
133
|
+
});
|
|
150
134
|
}
|
|
151
135
|
|
|
152
136
|
if (request.method === "GET" && url.pathname === "/api/candidates") {
|
|
@@ -158,7 +142,7 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
158
142
|
return jsonResponse({
|
|
159
143
|
project_root: projectRoot,
|
|
160
144
|
session,
|
|
161
|
-
candidates:
|
|
145
|
+
candidates: reviewedSessionCandidates(document.candidates, ops.traps),
|
|
162
146
|
});
|
|
163
147
|
}
|
|
164
148
|
|
|
@@ -188,6 +172,56 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
188
172
|
return jsonResponse(toTrapDetailsJson(details));
|
|
189
173
|
}
|
|
190
174
|
|
|
175
|
+
if (request.method === "GET" && url.pathname === "/api/embeddings") {
|
|
176
|
+
const projectRoot = projectRootFromQuery(url, context);
|
|
177
|
+
const status = await trapStore(projectRoot, context.home).embeddingStatus();
|
|
178
|
+
return jsonResponse({
|
|
179
|
+
project_root: projectRoot,
|
|
180
|
+
settings: loadCodetrapConfig(context.home).embeddings ?? null,
|
|
181
|
+
...status,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (request.method === "POST" && url.pathname === "/api/embeddings/use") {
|
|
186
|
+
const body = await readJsonBody(request);
|
|
187
|
+
const projectRoot = projectRootFromBody(body, context);
|
|
188
|
+
const embeddings = embeddingSettingsFromBody(body);
|
|
189
|
+
const written = trapStore(projectRoot, context.home).configureEmbeddings(embeddings);
|
|
190
|
+
const refreshed = await trapStore(projectRoot, context.home).embeddingStatus();
|
|
191
|
+
return jsonResponse({
|
|
192
|
+
success: true,
|
|
193
|
+
project_root: projectRoot,
|
|
194
|
+
path: written.path,
|
|
195
|
+
config: written.config,
|
|
196
|
+
embeddings: written.config.embeddings ?? embeddings,
|
|
197
|
+
settings: written.config.embeddings ?? embeddings,
|
|
198
|
+
next_actions: embeddingReindexActions(projectRoot),
|
|
199
|
+
status: refreshed,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (request.method === "POST" && url.pathname === "/api/embeddings/reindex") {
|
|
204
|
+
const body = await readJsonBody(request);
|
|
205
|
+
const projectRoot = projectRootFromBody(body, context);
|
|
206
|
+
const scope = scopeBodyField(body, "scope");
|
|
207
|
+
const store = trapStore(projectRoot, context.home);
|
|
208
|
+
try {
|
|
209
|
+
const result = await store.ensureEmbeddings({ scope });
|
|
210
|
+
return jsonResponse({
|
|
211
|
+
success: true,
|
|
212
|
+
project_root: projectRoot,
|
|
213
|
+
scope,
|
|
214
|
+
result,
|
|
215
|
+
status: await store.embeddingStatus(),
|
|
216
|
+
});
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (error instanceof EmbeddingProviderUnavailableError) {
|
|
219
|
+
throw new WebHttpError(400, error.message);
|
|
220
|
+
}
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
191
225
|
if (request.method === "POST" && url.pathname === "/api/candidate/save") {
|
|
192
226
|
const body = await readJsonBody(request);
|
|
193
227
|
const projectRoot = projectRootFromBody(body, context);
|
|
@@ -205,11 +239,12 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
205
239
|
const result = await sessionOperations(projectRoot, context.home).sessions.acceptCandidate({
|
|
206
240
|
candidateId: stringBodyField(body, "candidateId"),
|
|
207
241
|
sessionId: optionalStringBodyField(body, "sessionId"),
|
|
242
|
+
edit: optionalRecordBodyField(body, "trap"),
|
|
208
243
|
acceptAnyway: booleanBodyField(body, "acceptAnyway"),
|
|
209
244
|
supersedesId: optionalNumberBodyField(body, "supersedesId"),
|
|
210
245
|
});
|
|
211
246
|
if (!result.success) {
|
|
212
|
-
throw new WebPayloadError(409,
|
|
247
|
+
throw new WebPayloadError(409, sessionConflictPayload(result));
|
|
213
248
|
}
|
|
214
249
|
return jsonResponse({
|
|
215
250
|
success: true,
|
|
@@ -254,7 +289,7 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
254
289
|
session: result.session,
|
|
255
290
|
removed_count: result.removed_count,
|
|
256
291
|
removed_candidate_ids: result.removed_candidate_ids,
|
|
257
|
-
candidates:
|
|
292
|
+
candidates: reviewedSessionCandidates(result.candidates, ops.traps),
|
|
258
293
|
});
|
|
259
294
|
}
|
|
260
295
|
|
|
@@ -270,65 +305,11 @@ function sessionOperations(projectRoot: string, home?: string): { traps: TrapOpe
|
|
|
270
305
|
}
|
|
271
306
|
|
|
272
307
|
function trapOperations(projectRoot: string, home?: string): TrapOperations {
|
|
273
|
-
return new TrapOperations(
|
|
308
|
+
return new TrapOperations(trapStore(projectRoot, home));
|
|
274
309
|
}
|
|
275
310
|
|
|
276
|
-
function
|
|
277
|
-
return
|
|
278
|
-
...candidate,
|
|
279
|
-
review: candidateReview(candidate, traps),
|
|
280
|
-
}));
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function candidateReview(candidate: CandidateTrap, traps: TrapOperations): WebCandidateReview {
|
|
284
|
-
if (candidate.status === "proposed") {
|
|
285
|
-
return { status: "pending", label: "pending review" };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (candidate.status === "rejected") {
|
|
289
|
-
return {
|
|
290
|
-
status: "rejected",
|
|
291
|
-
label: "rejected",
|
|
292
|
-
rejected_at: candidate.rejected_at,
|
|
293
|
-
rejection_reason: candidate.rejection_reason,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const trapId = candidate.accepted_trap_id;
|
|
298
|
-
const scope = candidate.accepted_scope ?? acceptedScopeFallback(candidate);
|
|
299
|
-
if (trapId === undefined) {
|
|
300
|
-
return {
|
|
301
|
-
status: "accepted_missing",
|
|
302
|
-
label: "accepted -> trap link missing",
|
|
303
|
-
scope,
|
|
304
|
-
trap_present: false,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const details = traps.getTrapDetails(trapId, scope);
|
|
309
|
-
if (!details) {
|
|
310
|
-
return {
|
|
311
|
-
status: "accepted_missing",
|
|
312
|
-
label: `accepted -> trap #${trapId} deleted`,
|
|
313
|
-
trap_id: trapId,
|
|
314
|
-
scope,
|
|
315
|
-
trap_present: false,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return {
|
|
320
|
-
status: "accepted",
|
|
321
|
-
label: `accepted -> trap #${trapId}`,
|
|
322
|
-
trap_id: trapId,
|
|
323
|
-
scope: details.scope,
|
|
324
|
-
trap_present: true,
|
|
325
|
-
trap_status: details.trap.status,
|
|
326
|
-
trap_title: details.trap.title,
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function acceptedScopeFallback(candidate: CandidateTrap): string {
|
|
331
|
-
return candidate.trap.scope === "global" ? "global" : "project";
|
|
311
|
+
function trapStore(projectRoot: string, home?: string): TrapStore {
|
|
312
|
+
return new TrapStore(projectRoot, undefined, home);
|
|
332
313
|
}
|
|
333
314
|
|
|
334
315
|
function registerInitialProject(options: WebServerOptions): string | null {
|
|
@@ -441,6 +422,46 @@ function optionalNumberBodyField(body: Record<string, unknown>, key: string): nu
|
|
|
441
422
|
return value;
|
|
442
423
|
}
|
|
443
424
|
|
|
425
|
+
function scopeBodyField(body: Record<string, unknown>, key: string): "project" | "global" {
|
|
426
|
+
const value = stringBodyField(body, key);
|
|
427
|
+
if (value === "project" || value === "global") return value;
|
|
428
|
+
throw new WebHttpError(400, `${key} must be project or global.`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function embeddingSettingsFromBody(body: Record<string, unknown>): EmbeddingSettings {
|
|
432
|
+
const provider = embeddingProviderBodyField(body, "provider");
|
|
433
|
+
if (provider === "jina") {
|
|
434
|
+
return { provider };
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
provider,
|
|
438
|
+
endpoint: optionalStringBodyField(body, "endpoint") ?? DEFAULT_OLLAMA_ENDPOINT,
|
|
439
|
+
model: optionalStringBodyField(body, "model") ?? DEFAULT_OLLAMA_MODEL,
|
|
440
|
+
dimensions: optionalNumberBodyField(body, "dimensions") ?? DEFAULT_OLLAMA_DIMENSIONS,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function embeddingProviderBodyField(body: Record<string, unknown>, key: string): EmbeddingProviderSetting {
|
|
445
|
+
const value = stringBodyField(body, key);
|
|
446
|
+
if (value === "ollama" || value === "jina") return value;
|
|
447
|
+
throw new WebHttpError(400, `${key} must be ollama or jina.`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function embeddingReindexActions(_projectRoot: string): { scope: "project" | "global"; command: string; reason: string }[] {
|
|
451
|
+
return [
|
|
452
|
+
{
|
|
453
|
+
scope: "project",
|
|
454
|
+
command: "codetrap embeddings reindex --scope project",
|
|
455
|
+
reason: "Generate project embeddings for the selected profile.",
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
scope: "global",
|
|
459
|
+
command: "codetrap embeddings reindex --scope global",
|
|
460
|
+
reason: "Generate global embeddings for the selected profile.",
|
|
461
|
+
},
|
|
462
|
+
];
|
|
463
|
+
}
|
|
464
|
+
|
|
444
465
|
function booleanBodyField(body: Record<string, unknown>, key: string): boolean {
|
|
445
466
|
const value = body[key];
|
|
446
467
|
return typeof value === "boolean" ? value : false;
|
|
@@ -452,14 +473,11 @@ function recordBodyField(body: Record<string, unknown>, key: string): Record<str
|
|
|
452
473
|
return value;
|
|
453
474
|
}
|
|
454
475
|
|
|
455
|
-
function
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
candidate_id: result.candidate_id,
|
|
461
|
-
possible_conflicts: result.possible_conflicts,
|
|
462
|
-
};
|
|
476
|
+
function optionalRecordBodyField(body: Record<string, unknown>, key: string): Record<string, unknown> | undefined {
|
|
477
|
+
const value = body[key];
|
|
478
|
+
if (value === undefined || value === null) return undefined;
|
|
479
|
+
if (!isRecord(value)) throw new WebHttpError(400, `${key} must be an object.`);
|
|
480
|
+
return value;
|
|
463
481
|
}
|
|
464
482
|
|
|
465
483
|
function parsePort(value: string): number {
|
|
@@ -494,7 +512,3 @@ class WebPayloadError extends Error {
|
|
|
494
512
|
super(String(payload.error ?? "Request failed"));
|
|
495
513
|
}
|
|
496
514
|
}
|
|
497
|
-
|
|
498
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
499
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
500
|
-
}
|