codetrap 0.1.2 → 0.1.3
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/.agents/plugins/marketplace.json +20 -0
- package/README.md +107 -32
- package/docs/installation.md +18 -10
- package/package.json +4 -1
- package/plugins/codetrap-agent/.codex-plugin/plugin.json +34 -0
- package/plugins/codetrap-agent/hooks/post-flight-capture.example.md +25 -0
- package/plugins/codetrap-agent/hooks/pre-edit.example.sh +10 -0
- package/plugins/codetrap-agent/hooks.json +11 -0
- package/plugins/codetrap-agent/skills/codetrap-capture/SKILL.md +19 -0
- package/plugins/codetrap-agent/skills/codetrap-check/SKILL.md +14 -0
- package/plugins/codetrap-agent/templates/AGENTS.codetrap.md +25 -0
- package/scripts/release-preflight.ts +55 -0
- package/skills/codetrap-add/SKILL.md +4 -1
- package/skills/codetrap-check/SKILL.md +24 -4
- package/skills/codetrap-search/SKILL.md +32 -12
- package/src/commands/command-result.ts +29 -0
- package/src/commands/router.ts +6 -400
- package/src/commands/workflow.ts +466 -0
- package/src/db/embedding-queries.ts +33 -0
- package/src/db/queries.ts +119 -20
- package/src/db/repository.ts +39 -2
- package/src/db/schema.ts +35 -0
- package/src/domain/trap.ts +31 -2
- package/src/index.ts +13 -1
- package/src/lib/config.ts +102 -0
- package/src/lib/constants.ts +1 -1
- package/src/lib/doctor.ts +76 -0
- package/src/lib/embedding-health.ts +49 -0
- package/src/lib/format.ts +5 -1
- package/src/lib/output-json.ts +116 -0
- package/src/lib/scope-context.ts +116 -0
- package/src/lib/scope-migration.ts +360 -0
- package/src/lib/search-normalizer.ts +6 -0
- package/src/lib/search-policy.ts +276 -0
- package/src/lib/search-result-card.ts +1 -0
- package/src/lib/search-service.ts +36 -98
- package/src/lib/store.ts +96 -107
- package/src/lib/trap-archive.ts +9 -42
- package/src/lib/trap-codec.ts +113 -0
- package/src/lib/trap-json-fields.ts +12 -0
- package/src/lib/trap-mutation-result.ts +36 -0
- package/src/lib/trap-operations.ts +27 -6
- package/src/lib/trap-scope-match.ts +112 -0
- package/src/lib/trap-search-document.ts +8 -1
- package/src/lib/trap-transfer.ts +88 -0
- package/src/mcp/server.ts +75 -57
- package/src/mcp/tools.ts +32 -5
|
@@ -3,7 +3,7 @@ name: codetrap-search
|
|
|
3
3
|
description: Search the codetrap pitfall database for known mistakes and project lessons. Use when starting work in a new area or when the user asks whether similar issues were seen before.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Search the codetrap database for recorded pitfalls matching the user's query.
|
|
6
|
+
Search the codetrap database for recorded pitfalls matching the user's query. Default to the CLI `codetrap search --json`; use MCP only when it is already available and scoped to the right project.
|
|
7
7
|
|
|
8
8
|
## When to use
|
|
9
9
|
|
|
@@ -13,31 +13,51 @@ Search the codetrap database for recorded pitfalls matching the user's query. Us
|
|
|
13
13
|
|
|
14
14
|
## How to search
|
|
15
15
|
|
|
16
|
-
### Via
|
|
16
|
+
### Via CLI (preferred)
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Run from the current project cwd:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
codetrap search "<keywords>" --mode hybrid --json
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
For scoped work, pass known file/module/owner context:
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
```bash
|
|
27
|
+
codetrap search "<keywords>" --path src/db/repository.ts --module db --owner platform --json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or pipe the query through stdin:
|
|
26
31
|
|
|
27
32
|
```bash
|
|
28
|
-
|
|
33
|
+
echo "<keywords>" | codetrap search --mode hybrid --json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
CLI JSON returns compact action cards. Each card includes `avoid`, `do_instead`, and `next_action.command`; run that command to inspect full details.
|
|
37
|
+
|
|
38
|
+
### Via MCP (optional)
|
|
39
|
+
|
|
40
|
+
Call the `search_traps` MCP tool when available:
|
|
41
|
+
```
|
|
42
|
+
search_traps(query="<keywords>", scope=<optional>, category=<optional>, path=<optional>, module=<optional>, owner=<optional>, cwd=<optional>)
|
|
29
43
|
```
|
|
30
44
|
|
|
45
|
+
`search_traps` returns compact action cards. Each card includes `avoid`, `do_instead`, and `next_action.details_args` with both `id` and `scope`. Preserve that scope when calling `get_trap`.
|
|
46
|
+
|
|
47
|
+
## Result review rule
|
|
48
|
+
|
|
49
|
+
Review the top 3 action cards before deciding that no trap applies. Do not rely only on the first result; a relevant trap can rank second or third. If fewer than 3 cards are returned, review all returned cards.
|
|
50
|
+
|
|
31
51
|
## How to present results
|
|
32
52
|
|
|
33
|
-
1. Show the most relevant traps first (project scope traps before global)
|
|
34
|
-
2. Summarize each card's title, severity, `avoid`, and `do_instead`
|
|
35
|
-
3. If
|
|
53
|
+
1. Show the most relevant reviewed traps first (project scope traps before global)
|
|
54
|
+
2. Summarize each reviewed card's title, severity, `avoid`, and `do_instead`
|
|
55
|
+
3. If any reviewed card is highly relevant, or has `critical`/`error` severity and is plausibly related, and you are about to edit code, run the CLI `next_action.command`; with MCP, call `get_trap` with the card's `id` and `scope` before proceeding
|
|
36
56
|
4. If no results, tell the user (this is a new area with no recorded pitfalls yet)
|
|
37
57
|
|
|
38
58
|
## Example
|
|
39
59
|
|
|
40
60
|
User: "I need to add a new API endpoint"
|
|
41
|
-
→ Search: `
|
|
61
|
+
→ Search: `codetrap search "API endpoint" --mode hybrid --json`
|
|
42
62
|
→ Results show: "Don't use axios, use fetchWrapper" (project, error)
|
|
43
63
|
→ Tell user: "I see a project convention: always use fetchWrapper instead of axios. I'll follow that."
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type CommandResult = {
|
|
2
|
+
exitCode: number;
|
|
3
|
+
stdout?: string;
|
|
4
|
+
stderr?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function textResult(stdout = "", exitCode = 0): CommandResult {
|
|
8
|
+
return { exitCode, stdout };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function jsonResult(value: unknown, exitCode = 0): CommandResult {
|
|
12
|
+
return textResult(JSON.stringify(value, null, 2), exitCode);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function errorResult(message: string, exitCode = 1): CommandResult {
|
|
16
|
+
return { exitCode, stderr: message };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function renderCommandResult(result: CommandResult): void {
|
|
20
|
+
if (result.stdout !== undefined && result.stdout !== "") {
|
|
21
|
+
console.log(result.stdout);
|
|
22
|
+
}
|
|
23
|
+
if (result.stderr !== undefined && result.stderr !== "") {
|
|
24
|
+
console.error(result.stderr);
|
|
25
|
+
}
|
|
26
|
+
if (result.exitCode !== 0) {
|
|
27
|
+
process.exit(result.exitCode);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/commands/router.ts
CHANGED
|
@@ -1,407 +1,13 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { TrapStore } from "../lib/store";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import { SEARCH_MODES, type SearchMode } from "../lib/constants";
|
|
6
|
-
import { TrapOperations } from "../lib/trap-operations";
|
|
2
|
+
import { renderCommandResult, type CommandResult } from "./command-result";
|
|
3
|
+
import { executeCommand as executeWorkflow, parseArgs } from "./workflow";
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
opts: Record<string, string>;
|
|
10
|
-
positionals: string[];
|
|
11
|
-
};
|
|
5
|
+
export { parseArgs };
|
|
12
6
|
|
|
13
7
|
export async function run(strip: string[], store: TrapStore): Promise<void> {
|
|
14
|
-
|
|
15
|
-
const args = strip.slice(1);
|
|
16
|
-
const operations = new TrapOperations(store);
|
|
17
|
-
|
|
18
|
-
switch (sub) {
|
|
19
|
-
case "add":
|
|
20
|
-
return cmdAdd(args, operations);
|
|
21
|
-
case "search":
|
|
22
|
-
return cmdSearch(args, operations);
|
|
23
|
-
case "list":
|
|
24
|
-
return cmdList(args, operations);
|
|
25
|
-
case "show":
|
|
26
|
-
return cmdShow(args, operations);
|
|
27
|
-
case "edit":
|
|
28
|
-
return cmdEdit(args, operations);
|
|
29
|
-
case "delete":
|
|
30
|
-
case "rm":
|
|
31
|
-
return cmdDelete(args, operations);
|
|
32
|
-
case "add_trap_evidence":
|
|
33
|
-
case "add-evidence":
|
|
34
|
-
return cmdAddTrapEvidence(args, operations);
|
|
35
|
-
case "archive_trap":
|
|
36
|
-
case "archive":
|
|
37
|
-
return cmdArchiveTrap(args, operations);
|
|
38
|
-
case "supersede_trap":
|
|
39
|
-
case "supersede":
|
|
40
|
-
return cmdSupersedeTrap(args, operations);
|
|
41
|
-
case "init":
|
|
42
|
-
return cmdInit(args, store);
|
|
43
|
-
case "export":
|
|
44
|
-
return cmdExport(args, operations);
|
|
45
|
-
case "import":
|
|
46
|
-
return cmdImport(args, operations);
|
|
47
|
-
case "stats":
|
|
48
|
-
return cmdStats(args, operations);
|
|
49
|
-
case "embed":
|
|
50
|
-
return cmdEmbed(args, store);
|
|
51
|
-
default:
|
|
52
|
-
console.log(`Unknown command: ${sub}`);
|
|
53
|
-
console.log("Commands: init, add, search, list, show, edit, delete, add_trap_evidence, archive_trap, supersede_trap, export, import, stats, embed");
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function parseArgs(args: string[]): ParsedArgs {
|
|
59
|
-
const opts: Record<string, string> = {};
|
|
60
|
-
const positionals: string[] = [];
|
|
61
|
-
for (let i = 0; i < args.length; i++) {
|
|
62
|
-
if (args[i].startsWith("--")) {
|
|
63
|
-
const key = args[i].slice(2);
|
|
64
|
-
const val = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
|
|
65
|
-
opts[key] = val;
|
|
66
|
-
} else {
|
|
67
|
-
positionals.push(args[i]);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return { opts, positionals };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ---- commands ----
|
|
74
|
-
|
|
75
|
-
function cmdInit(_args: string[], store: TrapStore): void {
|
|
76
|
-
if (store.hasProject()) {
|
|
77
|
-
console.log(`Already in a project: ${store.getProjectRoot()}`);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
// init is handled by index.ts before creating the store
|
|
81
|
-
console.log("Project initialized.");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function cmdAdd(args: string[], operations: TrapOperations): void {
|
|
85
|
-
const { opts, positionals } = parseArgs(args);
|
|
86
|
-
// --json mode for AI/script usage
|
|
87
|
-
if (opts.json !== undefined) {
|
|
88
|
-
if (!opts.json || opts.json === "true") {
|
|
89
|
-
console.error("Error: --json requires a JSON string argument");
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
try {
|
|
93
|
-
const input = JSON.parse(opts.json);
|
|
94
|
-
const result = operations.addTrap(input);
|
|
95
|
-
console.log(`Trap #${result.id} added to ${result.scope} scope.`);
|
|
96
|
-
} catch (e: any) {
|
|
97
|
-
console.error(`Error: ${e.message}`);
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Quick mode: codetrap add "title"
|
|
104
|
-
if (positionals.length > 0) {
|
|
105
|
-
console.log(`Use --json mode for structured input.`);
|
|
106
|
-
console.log(`Quick add: codetrap add --json '{"title":"${positionals.join(" ")}","category":"other","scope":"global","context":"...","mistake":"...","fix":"..."}'`);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Interactive mode
|
|
111
|
-
console.log("Interactive mode not yet implemented. Use --json for now.");
|
|
112
|
-
console.log('Example: codetrap add --json \'{"title":"...","category":"convention","scope":"project","context":"...","mistake":"...","fix":"..."}\'');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function cmdSearch(args: string[], operations: TrapOperations): Promise<void> {
|
|
116
|
-
const { opts, positionals } = parseArgs(args);
|
|
117
|
-
if (positionals.length === 0) {
|
|
118
|
-
console.error("Usage: codetrap search <query> [--category X] [--limit N] [--mode fts|semantic|hybrid] [--status active|superseded|archived|all]");
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
let cards: Awaited<ReturnType<TrapOperations["searchTrapCards"]>>;
|
|
122
|
-
try {
|
|
123
|
-
const mode = opts.mode ? parseSearchMode(opts.mode) : undefined;
|
|
124
|
-
cards = await operations.searchTrapCards({
|
|
125
|
-
query: positionals.join(" "),
|
|
126
|
-
category: opts.category,
|
|
127
|
-
scope: opts.scope,
|
|
128
|
-
limit: opts.limit ? parseInt(opts.limit) : 20,
|
|
129
|
-
mode,
|
|
130
|
-
status: opts.status,
|
|
131
|
-
});
|
|
132
|
-
} catch (e: any) {
|
|
133
|
-
console.error(`Error: ${e.message}`);
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
let count = 0;
|
|
138
|
-
for (const card of cards) {
|
|
139
|
-
console.log(formatTrapActionCard(card));
|
|
140
|
-
console.log("");
|
|
141
|
-
count++;
|
|
142
|
-
}
|
|
143
|
-
if (count === 0) console.log("No traps found.");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function cmdList(args: string[], operations: TrapOperations): void {
|
|
147
|
-
const { opts } = parseArgs(args);
|
|
148
|
-
|
|
149
|
-
let groups: { traps: Trap[]; scope: string }[];
|
|
150
|
-
try {
|
|
151
|
-
groups = operations.listTraps({
|
|
152
|
-
category: opts.category,
|
|
153
|
-
scope: opts.scope,
|
|
154
|
-
status: opts.status,
|
|
155
|
-
limit: opts.limit ? parseInt(opts.limit) : 50,
|
|
156
|
-
});
|
|
157
|
-
} catch (e: any) {
|
|
158
|
-
console.error(`Error: ${e.message}`);
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let count = 0;
|
|
163
|
-
for (const group of groups) {
|
|
164
|
-
for (const t of group.traps) {
|
|
165
|
-
console.log(formatTrapShort(t, group.scope));
|
|
166
|
-
count++;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (count === 0) console.log("No traps found.");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function cmdShow(args: string[], operations: TrapOperations): void {
|
|
173
|
-
const { opts, positionals } = parseArgs(args);
|
|
174
|
-
if (positionals.length === 0) {
|
|
175
|
-
console.error("Usage: codetrap show <id> [--scope project|global]");
|
|
176
|
-
process.exit(1);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const id = parseInt(positionals[0]);
|
|
180
|
-
if (isNaN(id)) {
|
|
181
|
-
console.error("Error: id must be a number");
|
|
182
|
-
process.exit(1);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const result = operations.getTrapDetails(id, opts.scope);
|
|
186
|
-
if (!result) {
|
|
187
|
-
console.error(`Trap #${id} not found.`);
|
|
188
|
-
process.exit(1);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
operations.hitTrap(id, result.scope);
|
|
192
|
-
console.log(formatTrapDetails(result));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function cmdEdit(args: string[], operations: TrapOperations): void {
|
|
196
|
-
const { opts, positionals } = parseArgs(args);
|
|
197
|
-
if (positionals.length === 0) {
|
|
198
|
-
console.error("Usage: codetrap edit <id> --json '{\"title\":\"new title\"}' [--scope project|global]");
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const id = parseInt(positionals[0]);
|
|
203
|
-
if (isNaN(id)) {
|
|
204
|
-
console.error("Error: id must be a number");
|
|
205
|
-
process.exit(1);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (!opts.json) {
|
|
209
|
-
console.error("Error: edit requires --json for now.");
|
|
210
|
-
console.error("Example: codetrap edit 1 --json '{\"title\":\"new title\"}' [--scope project|global]");
|
|
211
|
-
process.exit(1);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
const parsed = JSON.parse(opts.json);
|
|
216
|
-
const result = operations.updateTrap(id, parsed, opts.scope);
|
|
217
|
-
if (result.success) {
|
|
218
|
-
console.log(`Trap #${id} updated in ${result.scope} scope.`);
|
|
219
|
-
} else {
|
|
220
|
-
console.error(`Trap #${id} not found or no fields changed.`);
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
} catch (e: any) {
|
|
224
|
-
console.error(`Error: ${e.message}`);
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function cmdDelete(args: string[], operations: TrapOperations): void {
|
|
230
|
-
const { opts, positionals } = parseArgs(args);
|
|
231
|
-
if (positionals.length === 0) {
|
|
232
|
-
console.error("Usage: codetrap delete <id> [--scope project|global]");
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const id = parseInt(positionals[0]);
|
|
237
|
-
if (isNaN(id)) {
|
|
238
|
-
console.error("Error: id must be a number");
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const result = operations.deleteTrap(id, opts.scope);
|
|
243
|
-
if (result.success) {
|
|
244
|
-
console.log(`Trap #${id} deleted from ${result.scope} scope.`);
|
|
245
|
-
} else {
|
|
246
|
-
console.error(`Trap #${id} not found.`);
|
|
247
|
-
process.exit(1);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function cmdAddTrapEvidence(args: string[], operations: TrapOperations): void {
|
|
252
|
-
const { opts, positionals } = parseArgs(args);
|
|
253
|
-
if (positionals.length === 0) {
|
|
254
|
-
console.error("Usage: codetrap add_trap_evidence <id> --source_type manual|conversation|commit|issue|test_failure [--scope project|global] [--source_ref X] [--related_files a,b] [--note X]");
|
|
255
|
-
process.exit(1);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const id = parseInt(positionals[0]);
|
|
259
|
-
if (isNaN(id)) {
|
|
260
|
-
console.error("Error: id must be a number");
|
|
261
|
-
process.exit(1);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
const input = opts.json ? JSON.parse(opts.json) : {
|
|
266
|
-
source_type: opts.source_type ?? opts["source-type"],
|
|
267
|
-
source_ref: opts.source_ref ?? opts["source-ref"],
|
|
268
|
-
observed_at: opts.observed_at ?? opts["observed-at"],
|
|
269
|
-
related_files: parseCsv(opts.related_files ?? opts["related-files"]),
|
|
270
|
-
note: opts.note,
|
|
271
|
-
};
|
|
272
|
-
const result = operations.addTrapEvidence(id, input, opts.scope);
|
|
273
|
-
if (!result.success) {
|
|
274
|
-
console.error(`Trap #${id} not found.`);
|
|
275
|
-
process.exit(1);
|
|
276
|
-
}
|
|
277
|
-
console.log(`Evidence #${result.evidence_id} added to trap #${id} in ${result.scope} scope.`);
|
|
278
|
-
} catch (e: any) {
|
|
279
|
-
console.error(`Error: ${e.message}`);
|
|
280
|
-
process.exit(1);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function cmdArchiveTrap(args: string[], operations: TrapOperations): void {
|
|
285
|
-
const { opts, positionals } = parseArgs(args);
|
|
286
|
-
if (positionals.length === 0) {
|
|
287
|
-
console.error("Usage: codetrap archive_trap <id> [--scope project|global]");
|
|
288
|
-
process.exit(1);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const id = parseInt(positionals[0]);
|
|
292
|
-
if (isNaN(id)) {
|
|
293
|
-
console.error("Error: id must be a number");
|
|
294
|
-
process.exit(1);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const result = operations.archiveTrap(id, opts.scope);
|
|
298
|
-
if (result.success) {
|
|
299
|
-
console.log(`Trap #${id} archived in ${result.scope} scope.`);
|
|
300
|
-
} else {
|
|
301
|
-
console.error(`Trap #${id} not found.`);
|
|
302
|
-
process.exit(1);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function cmdSupersedeTrap(args: string[], operations: TrapOperations): void {
|
|
307
|
-
const { opts, positionals } = parseArgs(args);
|
|
308
|
-
if (positionals.length < 2) {
|
|
309
|
-
console.error("Usage: codetrap supersede_trap <old_id> <new_id> [--scope project|global] [--state_key key]");
|
|
310
|
-
process.exit(1);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const id = parseInt(positionals[0]);
|
|
314
|
-
const supersededById = parseInt(positionals[1]);
|
|
315
|
-
if (isNaN(id) || isNaN(supersededById)) {
|
|
316
|
-
console.error("Error: ids must be numbers");
|
|
317
|
-
process.exit(1);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const result = operations.supersedeTrap(id, supersededById, opts.scope, opts.state_key ?? opts["state-key"]);
|
|
321
|
-
if (result.success) {
|
|
322
|
-
console.log(`Trap #${id} superseded by #${supersededById} in ${result.scope} scope.`);
|
|
323
|
-
} else {
|
|
324
|
-
console.error(`Trap #${id} or #${supersededById} not found in the same scope.`);
|
|
325
|
-
process.exit(1);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function cmdExport(args: string[], operations: TrapOperations): void {
|
|
330
|
-
const { opts } = parseArgs(args);
|
|
331
|
-
const traps = operations.exportTraps(opts.scope);
|
|
332
|
-
console.log(JSON.stringify(traps, null, 2));
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function cmdImport(args: string[], operations: TrapOperations): void {
|
|
336
|
-
const { positionals } = parseArgs(args);
|
|
337
|
-
if (positionals.length === 0) {
|
|
338
|
-
console.error("Usage: codetrap import <file.json>");
|
|
339
|
-
process.exit(1);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const data = readFileSync(positionals[0], "utf-8");
|
|
343
|
-
const traps = JSON.parse(data);
|
|
344
|
-
if (!Array.isArray(traps)) {
|
|
345
|
-
console.error("Error: JSON file must contain an array of traps");
|
|
346
|
-
process.exit(1);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const count = operations.importTraps(traps);
|
|
350
|
-
console.log(`Imported ${count} traps.`);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function cmdStats(_args: string[], operations: TrapOperations): void {
|
|
354
|
-
const stats = operations.getStats();
|
|
355
|
-
|
|
356
|
-
if (stats.project) {
|
|
357
|
-
console.log("── Project ──");
|
|
358
|
-
printStats(stats.project);
|
|
359
|
-
}
|
|
360
|
-
console.log("── Global ──");
|
|
361
|
-
printStats(stats.global);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
async function cmdEmbed(args: string[], store: TrapStore): Promise<void> {
|
|
365
|
-
const { opts } = parseArgs(args);
|
|
366
|
-
try {
|
|
367
|
-
const result = await store.ensureEmbeddings({
|
|
368
|
-
scope: opts.scope,
|
|
369
|
-
category: opts.category,
|
|
370
|
-
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
371
|
-
force: opts.force === "true",
|
|
372
|
-
batchSize: opts["batch-size"] ? parseInt(opts["batch-size"]) : undefined,
|
|
373
|
-
});
|
|
374
|
-
for (const scoped of result.scopes) {
|
|
375
|
-
console.log(`[${scoped.scope}] embeddings generated: ${scoped.generated}, skipped: ${scoped.skipped}, batches: ${scoped.batches}`);
|
|
376
|
-
}
|
|
377
|
-
console.log(`Total generated: ${result.generated}, skipped: ${result.skipped}, batches: ${result.batches}`);
|
|
378
|
-
} catch (e: any) {
|
|
379
|
-
console.error(`Error: ${e.message}`);
|
|
380
|
-
process.exit(1);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function printStats(s: { total: number; byCategory: Record<string, number>; bySeverity: Record<string, number> }): void {
|
|
385
|
-
console.log(` Total: ${s.total}`);
|
|
386
|
-
console.log(" By category:");
|
|
387
|
-
for (const [cat, count] of Object.entries(s.byCategory)) {
|
|
388
|
-
console.log(` ${cat}: ${count}`);
|
|
389
|
-
}
|
|
390
|
-
console.log(" By severity:");
|
|
391
|
-
for (const [sev, count] of Object.entries(s.bySeverity)) {
|
|
392
|
-
console.log(` ${sev}: ${count}`);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function parseSearchMode(mode: string): SearchMode {
|
|
397
|
-
if ((SEARCH_MODES as readonly string[]).includes(mode)) return mode as SearchMode;
|
|
398
|
-
throw new Error(`Invalid search mode: ${mode}. Expected one of: ${SEARCH_MODES.join(", ")}`);
|
|
8
|
+
renderCommandResult(await executeWorkflow(strip, store));
|
|
399
9
|
}
|
|
400
10
|
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
return value
|
|
404
|
-
.split(",")
|
|
405
|
-
.map((item) => item.trim())
|
|
406
|
-
.filter(Boolean);
|
|
11
|
+
export async function executeCommand(strip: string[], store: TrapStore): Promise<CommandResult> {
|
|
12
|
+
return executeWorkflow(strip, store);
|
|
407
13
|
}
|