codetrap 0.1.6 → 0.1.8
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 +159 -51
- package/docs/installation.md +113 -29
- package/package.json +4 -3
- package/plugins/codetrap-agent/.codex-plugin/plugin.json +1 -2
- 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.md +31 -5
- package/scripts/search-policy-sweep.ts +131 -0
- package/src/commands/workflow.ts +186 -68
- package/src/db/connection.ts +6 -6
- 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 +32 -7
- package/src/lib/command-requests.ts +134 -1
- package/src/lib/config.ts +57 -7
- package/src/lib/constants.ts +1 -1
- package/src/lib/doctor.ts +96 -6
- package/src/lib/embed-output.ts +26 -0
- 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 +17 -11
- package/src/lib/scope-migration.ts +2 -1
- package/src/lib/scope.ts +4 -6
- package/src/lib/search-eval.ts +136 -23
- 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 +111 -51
- package/src/lib/session-review.ts +327 -0
- package/src/lib/session-store.ts +177 -55
- package/src/lib/store.ts +79 -11
- 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 +1543 -0
- package/src/web/client-shell.ts +414 -0
- package/src/web/client-text.ts +447 -0
- package/src/web/project-registry.ts +3 -5
- package/src/web/server.ts +184 -111
- package/src/web/static.ts +581 -484
- 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
package/src/web/server.ts
CHANGED
|
@@ -1,10 +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";
|
|
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";
|
|
8
15
|
import { WEB_INDEX_HTML } from "./static";
|
|
9
16
|
import {
|
|
10
17
|
addWebProject,
|
|
@@ -29,33 +36,6 @@ type WebContext = {
|
|
|
29
36
|
currentProjectRoot: string | null;
|
|
30
37
|
};
|
|
31
38
|
|
|
32
|
-
type WebCandidateReview =
|
|
33
|
-
| { status: "pending"; label: string }
|
|
34
|
-
| {
|
|
35
|
-
status: "accepted";
|
|
36
|
-
label: string;
|
|
37
|
-
trap_id: number;
|
|
38
|
-
scope: string;
|
|
39
|
-
trap_present: true;
|
|
40
|
-
trap_status: string;
|
|
41
|
-
trap_title: string;
|
|
42
|
-
}
|
|
43
|
-
| {
|
|
44
|
-
status: "accepted_missing";
|
|
45
|
-
label: string;
|
|
46
|
-
trap_id?: number;
|
|
47
|
-
scope?: string;
|
|
48
|
-
trap_present: false;
|
|
49
|
-
}
|
|
50
|
-
| {
|
|
51
|
-
status: "rejected";
|
|
52
|
-
label: string;
|
|
53
|
-
rejected_at?: string;
|
|
54
|
-
rejection_reason?: string;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
type WebCandidate = CandidateTrap & { review: WebCandidateReview };
|
|
58
|
-
|
|
59
39
|
export async function startWebServerFromArgs(args: string[], cwd = process.cwd()): Promise<void> {
|
|
60
40
|
const options = webServerOptionsFromArgs(args, cwd);
|
|
61
41
|
const token = options.token ?? randomBytes(18).toString("base64url");
|
|
@@ -89,6 +69,9 @@ export function createWebHandler(context: WebContext): (request: Request) => Pro
|
|
|
89
69
|
if (url.pathname === "/" || url.pathname === "/index.html") {
|
|
90
70
|
return htmlResponse(WEB_INDEX_HTML);
|
|
91
71
|
}
|
|
72
|
+
if (url.pathname === "/favicon.ico") {
|
|
73
|
+
return new Response(null, { status: 204 });
|
|
74
|
+
}
|
|
92
75
|
return jsonResponse({ error: "Not found" }, 404);
|
|
93
76
|
} catch (error) {
|
|
94
77
|
const status = error instanceof WebHttpError || error instanceof WebPayloadError ? error.status : 500;
|
|
@@ -141,20 +124,42 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
141
124
|
|
|
142
125
|
if (request.method === "GET" && url.pathname === "/api/sessions") {
|
|
143
126
|
const projectRoot = projectRootFromQuery(url, context);
|
|
144
|
-
const
|
|
145
|
-
|
|
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
|
+
});
|
|
146
134
|
}
|
|
147
135
|
|
|
148
136
|
if (request.method === "GET" && url.pathname === "/api/candidates") {
|
|
149
137
|
const projectRoot = projectRootFromQuery(url, context);
|
|
150
138
|
const sessionId = requiredQuery(url, "session");
|
|
151
|
-
const ops = sessionOperations(projectRoot);
|
|
139
|
+
const ops = sessionOperations(projectRoot, context.home);
|
|
152
140
|
const session = ops.sessions.showSession(sessionId).session;
|
|
153
141
|
const document = ops.sessions.candidateDocument(sessionId);
|
|
154
142
|
return jsonResponse({
|
|
155
143
|
project_root: projectRoot,
|
|
156
144
|
session,
|
|
157
|
-
candidates:
|
|
145
|
+
candidates: reviewedSessionCandidates(document.candidates, ops.traps),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (request.method === "GET" && url.pathname === "/api/traps") {
|
|
150
|
+
const projectRoot = projectRootFromQuery(url, context);
|
|
151
|
+
const groups = trapOperations(projectRoot, context.home).listTraps({
|
|
152
|
+
category: optionalQuery(url, "category"),
|
|
153
|
+
scope: optionalQuery(url, "scope"),
|
|
154
|
+
status: optionalQuery(url, "status"),
|
|
155
|
+
module: optionalQuery(url, "module"),
|
|
156
|
+
owner: optionalQuery(url, "owner"),
|
|
157
|
+
limit: optionalNumberQuery(url, "limit"),
|
|
158
|
+
offset: optionalNumberQuery(url, "offset"),
|
|
159
|
+
});
|
|
160
|
+
return jsonResponse({
|
|
161
|
+
project_root: projectRoot,
|
|
162
|
+
traps: toListJson(groups),
|
|
158
163
|
});
|
|
159
164
|
}
|
|
160
165
|
|
|
@@ -162,15 +167,65 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
162
167
|
const projectRoot = projectRootFromQuery(url, context);
|
|
163
168
|
const id = numberQuery(url, "id");
|
|
164
169
|
const scope = url.searchParams.get("scope") ?? undefined;
|
|
165
|
-
const details = trapOperations(projectRoot).getTrapDetails(id, scope);
|
|
170
|
+
const details = trapOperations(projectRoot, context.home).getTrapDetails(id, scope);
|
|
166
171
|
if (!details) throw new WebHttpError(404, `Trap #${id} not found.`);
|
|
167
|
-
return jsonResponse(details);
|
|
172
|
+
return jsonResponse(toTrapDetailsJson(details));
|
|
173
|
+
}
|
|
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
|
+
}
|
|
168
223
|
}
|
|
169
224
|
|
|
170
225
|
if (request.method === "POST" && url.pathname === "/api/candidate/save") {
|
|
171
226
|
const body = await readJsonBody(request);
|
|
172
227
|
const projectRoot = projectRootFromBody(body, context);
|
|
173
|
-
const result = sessionOperations(projectRoot).sessions.saveCandidate({
|
|
228
|
+
const result = sessionOperations(projectRoot, context.home).sessions.saveCandidate({
|
|
174
229
|
candidateId: stringBodyField(body, "candidateId"),
|
|
175
230
|
sessionId: optionalStringBodyField(body, "sessionId"),
|
|
176
231
|
edit: recordBodyField(body, "trap"),
|
|
@@ -181,14 +236,15 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
181
236
|
if (request.method === "POST" && url.pathname === "/api/candidate/accept") {
|
|
182
237
|
const body = await readJsonBody(request);
|
|
183
238
|
const projectRoot = projectRootFromBody(body, context);
|
|
184
|
-
const result = await sessionOperations(projectRoot).sessions.acceptCandidate({
|
|
239
|
+
const result = await sessionOperations(projectRoot, context.home).sessions.acceptCandidate({
|
|
185
240
|
candidateId: stringBodyField(body, "candidateId"),
|
|
186
241
|
sessionId: optionalStringBodyField(body, "sessionId"),
|
|
242
|
+
edit: optionalRecordBodyField(body, "trap"),
|
|
187
243
|
acceptAnyway: booleanBodyField(body, "acceptAnyway"),
|
|
188
244
|
supersedesId: optionalNumberBodyField(body, "supersedesId"),
|
|
189
245
|
});
|
|
190
246
|
if (!result.success) {
|
|
191
|
-
throw new WebPayloadError(409,
|
|
247
|
+
throw new WebPayloadError(409, sessionConflictPayload(result));
|
|
192
248
|
}
|
|
193
249
|
return jsonResponse({
|
|
194
250
|
success: true,
|
|
@@ -204,7 +260,7 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
204
260
|
if (request.method === "POST" && url.pathname === "/api/candidate/reject") {
|
|
205
261
|
const body = await readJsonBody(request);
|
|
206
262
|
const projectRoot = projectRootFromBody(body, context);
|
|
207
|
-
const result = sessionOperations(projectRoot).sessions.rejectCandidate({
|
|
263
|
+
const result = sessionOperations(projectRoot, context.home).sessions.rejectCandidate({
|
|
208
264
|
candidateId: stringBodyField(body, "candidateId"),
|
|
209
265
|
sessionId: optionalStringBodyField(body, "sessionId"),
|
|
210
266
|
reason: optionalStringBodyField(body, "reason"),
|
|
@@ -212,77 +268,48 @@ async function routeApi(request: Request, url: URL, context: WebContext): Promis
|
|
|
212
268
|
return jsonResponse({ success: true, session: result.session, candidate: result.candidate });
|
|
213
269
|
}
|
|
214
270
|
|
|
271
|
+
if (request.method === "POST" && url.pathname === "/api/session/delete") {
|
|
272
|
+
const body = await readJsonBody(request);
|
|
273
|
+
const projectRoot = projectRootFromBody(body, context);
|
|
274
|
+
const result = sessionOperations(projectRoot, context.home).sessions.deleteSession(
|
|
275
|
+
stringBodyField(body, "sessionId")
|
|
276
|
+
);
|
|
277
|
+
return jsonResponse({ success: true, ...result });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (request.method === "POST" && url.pathname === "/api/session/cleanup") {
|
|
281
|
+
const body = await readJsonBody(request);
|
|
282
|
+
const projectRoot = projectRootFromBody(body, context);
|
|
283
|
+
const ops = sessionOperations(projectRoot, context.home);
|
|
284
|
+
const result = ops.sessions.cleanupDeletedTrapCandidates(
|
|
285
|
+
optionalStringBodyField(body, "sessionId")
|
|
286
|
+
);
|
|
287
|
+
return jsonResponse({
|
|
288
|
+
success: true,
|
|
289
|
+
session: result.session,
|
|
290
|
+
removed_count: result.removed_count,
|
|
291
|
+
removed_candidate_ids: result.removed_candidate_ids,
|
|
292
|
+
candidates: reviewedSessionCandidates(result.candidates, ops.traps),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
215
296
|
throw new WebHttpError(404, "Not found");
|
|
216
297
|
}
|
|
217
298
|
|
|
218
|
-
function sessionOperations(projectRoot: string): { traps: TrapOperations; sessions: SessionOperations } {
|
|
219
|
-
const traps = trapOperations(projectRoot);
|
|
299
|
+
function sessionOperations(projectRoot: string, home?: string): { traps: TrapOperations; sessions: SessionOperations } {
|
|
300
|
+
const traps = trapOperations(projectRoot, home);
|
|
220
301
|
return {
|
|
221
302
|
traps,
|
|
222
303
|
sessions: new SessionOperations(new SessionStore(projectRoot), traps),
|
|
223
304
|
};
|
|
224
305
|
}
|
|
225
306
|
|
|
226
|
-
function trapOperations(projectRoot: string): TrapOperations {
|
|
227
|
-
return new TrapOperations(
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function webCandidates(candidates: CandidateTrap[], traps: TrapOperations): WebCandidate[] {
|
|
231
|
-
return candidates.map((candidate) => ({
|
|
232
|
-
...candidate,
|
|
233
|
-
review: candidateReview(candidate, traps),
|
|
234
|
-
}));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function candidateReview(candidate: CandidateTrap, traps: TrapOperations): WebCandidateReview {
|
|
238
|
-
if (candidate.status === "proposed") {
|
|
239
|
-
return { status: "pending", label: "pending review" };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (candidate.status === "rejected") {
|
|
243
|
-
return {
|
|
244
|
-
status: "rejected",
|
|
245
|
-
label: "rejected",
|
|
246
|
-
rejected_at: candidate.rejected_at,
|
|
247
|
-
rejection_reason: candidate.rejection_reason,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const trapId = candidate.accepted_trap_id;
|
|
252
|
-
const scope = candidate.accepted_scope ?? acceptedScopeFallback(candidate);
|
|
253
|
-
if (trapId === undefined) {
|
|
254
|
-
return {
|
|
255
|
-
status: "accepted_missing",
|
|
256
|
-
label: "accepted -> trap link missing",
|
|
257
|
-
scope,
|
|
258
|
-
trap_present: false,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const details = traps.getTrapDetails(trapId, scope);
|
|
263
|
-
if (!details) {
|
|
264
|
-
return {
|
|
265
|
-
status: "accepted_missing",
|
|
266
|
-
label: `accepted -> trap #${trapId} deleted`,
|
|
267
|
-
trap_id: trapId,
|
|
268
|
-
scope,
|
|
269
|
-
trap_present: false,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
status: "accepted",
|
|
275
|
-
label: `accepted -> trap #${trapId}`,
|
|
276
|
-
trap_id: trapId,
|
|
277
|
-
scope: details.scope,
|
|
278
|
-
trap_present: true,
|
|
279
|
-
trap_status: details.trap.status,
|
|
280
|
-
trap_title: details.trap.title,
|
|
281
|
-
};
|
|
307
|
+
function trapOperations(projectRoot: string, home?: string): TrapOperations {
|
|
308
|
+
return new TrapOperations(trapStore(projectRoot, home));
|
|
282
309
|
}
|
|
283
310
|
|
|
284
|
-
function
|
|
285
|
-
return
|
|
311
|
+
function trapStore(projectRoot: string, home?: string): TrapStore {
|
|
312
|
+
return new TrapStore(projectRoot, undefined, home);
|
|
286
313
|
}
|
|
287
314
|
|
|
288
315
|
function registerInitialProject(options: WebServerOptions): string | null {
|
|
@@ -353,6 +380,19 @@ function numberQuery(url: URL, key: string): number {
|
|
|
353
380
|
return value;
|
|
354
381
|
}
|
|
355
382
|
|
|
383
|
+
function optionalQuery(url: URL, key: string): string | undefined {
|
|
384
|
+
const value = url.searchParams.get(key)?.trim();
|
|
385
|
+
return value || undefined;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function optionalNumberQuery(url: URL, key: string): number | undefined {
|
|
389
|
+
const value = optionalQuery(url, key);
|
|
390
|
+
if (!value) return undefined;
|
|
391
|
+
const parsed = Number.parseInt(value, 10);
|
|
392
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw new WebHttpError(400, `${key} must be a positive integer.`);
|
|
393
|
+
return parsed;
|
|
394
|
+
}
|
|
395
|
+
|
|
356
396
|
function stringBodyField(body: Record<string, unknown>, key: string): string {
|
|
357
397
|
const value = optionalStringBodyField(body, key);
|
|
358
398
|
if (!value) throw new WebHttpError(400, `${key} is required.`);
|
|
@@ -382,6 +422,46 @@ function optionalNumberBodyField(body: Record<string, unknown>, key: string): nu
|
|
|
382
422
|
return value;
|
|
383
423
|
}
|
|
384
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
|
+
|
|
385
465
|
function booleanBodyField(body: Record<string, unknown>, key: string): boolean {
|
|
386
466
|
const value = body[key];
|
|
387
467
|
return typeof value === "boolean" ? value : false;
|
|
@@ -393,14 +473,11 @@ function recordBodyField(body: Record<string, unknown>, key: string): Record<str
|
|
|
393
473
|
return value;
|
|
394
474
|
}
|
|
395
475
|
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
candidate_id: result.candidate_id,
|
|
402
|
-
possible_conflicts: result.possible_conflicts,
|
|
403
|
-
};
|
|
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;
|
|
404
481
|
}
|
|
405
482
|
|
|
406
483
|
function parsePort(value: string): number {
|
|
@@ -435,7 +512,3 @@ class WebPayloadError extends Error {
|
|
|
435
512
|
super(String(payload.error ?? "Request failed"));
|
|
436
513
|
}
|
|
437
514
|
}
|
|
438
|
-
|
|
439
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
440
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
441
|
-
}
|