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.
Files changed (61) hide show
  1. package/README.md +132 -98
  2. package/docs/installation.md +61 -63
  3. package/package.json +4 -3
  4. package/plugins/codetrap-agent/.codex-plugin/plugin.json +2 -3
  5. package/plugins/codetrap-agent/hooks/post-flight-capture.example.md +19 -17
  6. package/plugins/codetrap-agent/hooks.json +2 -2
  7. package/{skills → plugins/codetrap-agent/skills}/codetrap-add/SKILL.md +10 -4
  8. package/plugins/codetrap-agent/skills/codetrap-capture/SKILL.md +14 -3
  9. package/plugins/codetrap-agent/skills/codetrap-capture-external/SKILL.md +52 -9
  10. package/plugins/codetrap-agent/skills/codetrap-check/SKILL.md +74 -6
  11. package/{skills → plugins/codetrap-agent/skills}/codetrap-search/SKILL.md +6 -5
  12. package/plugins/codetrap-agent/templates/AGENTS.codetrap-maintainer.md +15 -0
  13. package/plugins/codetrap-agent/templates/AGENTS.codetrap.md +16 -5
  14. package/scripts/release-preflight.ts +15 -0
  15. package/scripts/search-policy-sweep.ts +131 -0
  16. package/src/commands/workflow.ts +172 -68
  17. package/src/db/embedding-queries.ts +230 -48
  18. package/src/db/queries.ts +0 -25
  19. package/src/db/repository.ts +32 -21
  20. package/src/db/schema.ts +80 -0
  21. package/src/index.ts +34 -4
  22. package/src/lib/codex-setup.ts +247 -0
  23. package/src/lib/command-requests.ts +112 -1
  24. package/src/lib/config.ts +57 -7
  25. package/src/lib/constants.ts +1 -1
  26. package/src/lib/doctor.ts +42 -12
  27. package/src/lib/embedder.ts +118 -3
  28. package/src/lib/embedding-health.ts +3 -1
  29. package/src/lib/embedding-job.ts +3 -0
  30. package/src/lib/embedding-management.ts +65 -0
  31. package/src/lib/embedding-runtime.ts +177 -0
  32. package/src/lib/output-json.ts +0 -2
  33. package/src/lib/scope-context.ts +12 -6
  34. package/src/lib/scope-migration.ts +2 -1
  35. package/src/lib/scope.ts +0 -2
  36. package/src/lib/search-eval.ts +38 -18
  37. package/src/lib/search-policy-sweep.ts +563 -0
  38. package/src/lib/search-policy.ts +0 -4
  39. package/src/lib/search-service.ts +14 -15
  40. package/src/lib/session-candidate-document.ts +175 -0
  41. package/src/lib/session-candidate-scope.ts +6 -0
  42. package/src/lib/session-capture.ts +298 -32
  43. package/src/lib/session-codec.ts +1 -8
  44. package/src/lib/session-operations.ts +83 -60
  45. package/src/lib/session-review.ts +327 -0
  46. package/src/lib/session-store.ts +87 -73
  47. package/src/lib/store.ts +74 -10
  48. package/src/lib/string-list.ts +3 -0
  49. package/src/lib/text-lines.ts +7 -0
  50. package/src/lib/trap-search-document.ts +2 -1
  51. package/src/lib/value-types.ts +3 -0
  52. package/src/web/client-review.ts +171 -0
  53. package/src/web/client-script.ts +426 -51
  54. package/src/web/client-shell.ts +414 -0
  55. package/src/web/client-text.ts +112 -0
  56. package/src/web/project-registry.ts +3 -5
  57. package/src/web/server.ts +117 -103
  58. package/src/web/static.ts +364 -19
  59. package/skills/codetrap-capture-external/SKILL.md +0 -62
  60. package/skills/codetrap-check/SKILL.md +0 -69
  61. package/src/lib/embedding-index.ts +0 -53
@@ -2,7 +2,6 @@ import { readFileSync } from "node:fs";
2
2
  import { TrapStore } from "../lib/store";
3
3
  import { formatTrapShort, formatTrapDetails, formatTrapActionCard } from "../lib/format";
4
4
  import type { Trap } from "../domain/trap";
5
- import type { SessionMetadata } from "../domain/session";
6
5
  import {
7
6
  formatScopeMigrationText,
8
7
  runScopeMigration,
@@ -11,16 +10,31 @@ import {
11
10
  import { TrapOperations } from "../lib/trap-operations";
12
11
  import { buildDoctorReport, formatDoctorText } from "../lib/doctor";
13
12
  import { formatEmbedText } from "../lib/embed-output";
13
+ import {
14
+ formatEmbeddingProfilesText,
15
+ formatEmbeddingStatusText,
16
+ formatEmbeddingsUseText,
17
+ type EmbeddingsUseResult,
18
+ } from "../lib/embedding-management";
14
19
  import { searchDefaultsFromConfig } from "../lib/config";
20
+ import { formatCodexSetupText, runCodexSetup } from "../lib/codex-setup";
15
21
  import { SessionStore } from "../lib/session-store";
16
22
  import { SessionOperations, type SessionConflictResult } from "../lib/session-operations";
17
23
  import {
18
24
  CANDIDATES_FILE,
19
25
  NOTES_FILE,
20
26
  RECAP_FILE,
21
- sessionRelativeDir,
22
27
  sessionRelativeFile,
23
28
  } from "../lib/session-codec";
29
+ import {
30
+ sessionAcceptPayload,
31
+ sessionCliConflictPayload,
32
+ sessionCleanupPayload,
33
+ sessionConflictPayload,
34
+ sessionConflictText,
35
+ sessionPayload,
36
+ sessionRejectPayload,
37
+ } from "../lib/session-review";
24
38
  import {
25
39
  toCliSearchJson,
26
40
  toListJson,
@@ -36,10 +50,12 @@ import {
36
50
  import { mutationJsonPayload } from "../lib/trap-mutation-result";
37
51
  import {
38
52
  embedRequestFromArgs,
53
+ embeddingsUseRequestFromArgs,
39
54
  evidenceRequestFromArgs,
40
55
  listRequestFromArgs,
41
56
  searchRequestFromArgs,
42
57
  sessionAcceptRequestFromArgs,
58
+ sessionCaptureRequestFromArgs,
43
59
  sessionCandidateRequestFromArgs,
44
60
  sessionCloseRequestFromArgs,
45
61
  sessionIdRequestFromArgs,
@@ -95,18 +111,22 @@ export async function executeCommand(strip: string[], store: TrapStore): Promise
95
111
  return cmdStats(args, operations);
96
112
  case "doctor":
97
113
  return cmdDoctor(args, store, operations);
114
+ case "setup":
115
+ return cmdSetup(args);
98
116
  case "repair-scope":
99
117
  return cmdScopeMigration("repair-scope", args, operations);
100
118
  case "migrate-project":
101
119
  return cmdScopeMigration("migrate-project", args, operations);
102
120
  case "embed":
103
121
  return cmdEmbed(args, store);
122
+ case "embeddings":
123
+ return cmdEmbeddings(args, store);
104
124
  case "session":
105
125
  return cmdSession(args, store, operations);
106
126
  default:
107
127
  return errorResult([
108
128
  `Unknown command: ${sub}`,
109
- "Commands: init, add, search, list, show, edit, delete, add_trap_evidence, archive_trap, supersede_trap, export, import, stats, doctor, repair-scope, migrate-project, embed, session",
129
+ "Commands: init, add, search, list, show, edit, delete, add_trap_evidence, archive_trap, supersede_trap, export, import, stats, doctor, setup, repair-scope, migrate-project, embed, embeddings, session",
110
130
  ].join("\n"));
111
131
  }
112
132
  }
@@ -341,14 +361,43 @@ function cmdStats(args: string[], operations: TrapOperations): CommandResult {
341
361
  : textResult(formatStatsText(stats));
342
362
  }
343
363
 
344
- function cmdDoctor(args: string[], store: TrapStore, operations: TrapOperations): CommandResult {
364
+ async function cmdDoctor(args: string[], store: TrapStore, operations: TrapOperations): Promise<CommandResult> {
345
365
  const { opts } = parseArgs(args);
346
- const report = buildDoctorReport(store, operations);
366
+ const projectRoot = store.getProjectRoot();
367
+ const candidateReview = projectRoot
368
+ ? new SessionOperations(new SessionStore(projectRoot), operations).candidateReviewSummary()
369
+ : null;
370
+ const report = await buildDoctorReport(store, operations, process.cwd(), candidateReview);
347
371
  return opts.json !== undefined
348
372
  ? jsonResult(report)
349
373
  : textResult(formatDoctorText(report));
350
374
  }
351
375
 
376
+ function cmdSetup(args: string[]): CommandResult {
377
+ const sub = args[0];
378
+ const rest = args.slice(1);
379
+ if (sub !== "codex") {
380
+ return errorResult("Usage: codetrap setup codex [--mcp] [--no-agents] [--agents-file AGENTS.md] [--codex-home <path>] [--dry-run] [--json]");
381
+ }
382
+ const { opts } = parseArgs(rest);
383
+ try {
384
+ const result = runCodexSetup({
385
+ cwd: process.cwd(),
386
+ codexHome: opts["codex-home"],
387
+ agentsFile: opts["agents-file"],
388
+ installMcp: opts.mcp !== undefined,
389
+ skipAgents: opts["no-agents"] !== undefined,
390
+ dryRun: opts["dry-run"] !== undefined,
391
+ });
392
+ if (opts.json !== undefined) return jsonResult(result, result.success ? 0 : 1);
393
+ return result.success
394
+ ? textResult(formatCodexSetupText(result))
395
+ : errorResult(formatCodexSetupText(result));
396
+ } catch (error) {
397
+ return errorFrom(error);
398
+ }
399
+ }
400
+
352
401
  function cmdScopeMigration(
353
402
  command: ScopeMigrationCommand,
354
403
  args: string[],
@@ -388,6 +437,59 @@ async function cmdEmbed(args: string[], store: TrapStore): Promise<CommandResult
388
437
  }
389
438
  }
390
439
 
440
+ async function cmdEmbeddings(args: string[], store: TrapStore): Promise<CommandResult> {
441
+ const sub = args[0] ?? "status";
442
+ const rest = args.length === 0 ? [] : args.slice(1);
443
+
444
+ try {
445
+ switch (sub) {
446
+ case "status": {
447
+ const { opts } = parseArgs(rest);
448
+ const status = await store.embeddingStatus({ scope: opts.scope });
449
+ return opts.json !== undefined
450
+ ? jsonResult(status)
451
+ : textResult(formatEmbeddingStatusText(status));
452
+ }
453
+ case "list":
454
+ case "profiles": {
455
+ const { opts } = parseArgs(rest);
456
+ const profiles = store.embeddingProfiles({ scope: opts.scope });
457
+ const payload = {
458
+ active_profile_id: store.embeddingRuntimeStatus().profile_id,
459
+ ...profiles,
460
+ };
461
+ return opts.json !== undefined
462
+ ? jsonResult(payload)
463
+ : textResult(formatEmbeddingProfilesText(profiles));
464
+ }
465
+ case "use": {
466
+ const { opts, positionals } = parseArgs(rest);
467
+ const request = embeddingsUseRequestFromArgs(positionals, opts);
468
+ const written = store.configureEmbeddings(request.embeddings);
469
+ const scope = store.hasProject() ? "project" : "global";
470
+ const result: EmbeddingsUseResult = {
471
+ ...written,
472
+ embeddings: written.config.embeddings ?? request.embeddings,
473
+ next_action: {
474
+ command: `codetrap embeddings reindex --scope ${scope}`,
475
+ reason: "Generate embeddings for the selected profile.",
476
+ },
477
+ };
478
+ return opts.json !== undefined
479
+ ? jsonResult(result)
480
+ : textResult(formatEmbeddingsUseText(result));
481
+ }
482
+ case "reindex":
483
+ case "embed":
484
+ return cmdEmbed(rest, store);
485
+ default:
486
+ return errorResult("Usage: codetrap embeddings <status|list|profiles|use|reindex> [--json]");
487
+ }
488
+ } catch (error) {
489
+ return errorFrom(error);
490
+ }
491
+ }
492
+
391
493
  async function cmdSession(args: string[], store: TrapStore, trapOperations: TrapOperations): Promise<CommandResult> {
392
494
  const sub = args[0];
393
495
  const rest = args.slice(1);
@@ -413,6 +515,8 @@ async function cmdSession(args: string[], store: TrapStore, trapOperations: Trap
413
515
  return cmdSessionNotes(rest, sessions);
414
516
  case "close":
415
517
  return cmdSessionClose(rest, sessions);
518
+ case "capture":
519
+ return cmdSessionCapture(rest, sessions);
416
520
  case "candidates":
417
521
  return cmdSessionCandidates(rest, sessions);
418
522
  case "candidate":
@@ -428,7 +532,7 @@ async function cmdSession(args: string[], store: TrapStore, trapOperations: Trap
428
532
  case "cleanup":
429
533
  return cmdSessionCleanup(rest, sessions);
430
534
  default:
431
- return errorResult("Usage: codetrap session <start|note|status|list|show|notes|close|candidates|candidate|accept|reject|delete|prune|cleanup>");
535
+ return errorResult("Usage: codetrap session <start|note|status|list|show|notes|close|capture|candidates|candidate|accept|reject|delete|prune|cleanup>");
432
536
  }
433
537
  } catch (error) {
434
538
  return errorFrom(error);
@@ -472,14 +576,31 @@ function cmdSessionStatus(args: string[], sessions: SessionOperations): CommandR
472
576
  return jsonResult({
473
577
  active_session_id: status.active_session_id,
474
578
  session: status.session ? sessionPayload(status.session) : null,
579
+ candidate_review: status.candidate_review,
475
580
  });
476
581
  }
477
- if (!status.session) return textResult("No active session.");
478
- return textResult([
582
+ if (!status.session) {
583
+ const lines = ["No active session."];
584
+ if (status.candidate_review.pending_count > 0) {
585
+ lines.push(
586
+ `Pending candidate review: ${status.candidate_review.pending_count} candidate(s) across ${status.candidate_review.pending_session_count} session(s).`
587
+ );
588
+ if (status.candidate_review.next_session_id) {
589
+ lines.push(`Review: codetrap session candidates ${status.candidate_review.next_session_id}`);
590
+ }
591
+ lines.push("Open web review: codetrap web");
592
+ }
593
+ return textResult(lines.join("\n"));
594
+ }
595
+ const lines = [
479
596
  `Active session ${status.session.id}`,
480
597
  `Goal: ${status.session.goal}`,
481
598
  `Notes: ${sessionRelativeFile(status.session.id, NOTES_FILE)}`,
482
- ].join("\n"));
599
+ ];
600
+ if (status.candidate_review.pending_count > 0) {
601
+ lines.push(`Pending candidate review: ${status.candidate_review.pending_count} candidate(s).`);
602
+ }
603
+ return textResult(lines.join("\n"));
483
604
  }
484
605
 
485
606
  function cmdSessionList(args: string[], sessions: SessionOperations): CommandResult {
@@ -487,7 +608,9 @@ function cmdSessionList(args: string[], sessions: SessionOperations): CommandRes
487
608
  const entries = sessions.listSessions(sessionListRequestFromArgs(opts));
488
609
  if (opts.json !== undefined) return jsonResult(entries);
489
610
  if (entries.length === 0) return textResult("No sessions found.");
490
- return textResult(entries.map((entry) => `${entry.id} [${entry.status}] ${entry.goal}`).join("\n"));
611
+ return textResult(entries.map((entry) =>
612
+ `${entry.id} [${entry.status}] ${entry.goal} (${entry.pending_count} pending, ${entry.reviewed_count} reviewed)`
613
+ ).join("\n"));
491
614
  }
492
615
 
493
616
  function cmdSessionShow(args: string[], sessions: SessionOperations): CommandResult {
@@ -525,7 +648,6 @@ function cmdSessionClose(args: string[], sessions: SessionOperations): CommandRe
525
648
  ...sessionPayload(result.session),
526
649
  recap_path: result.recap_path,
527
650
  candidate_count: result.candidate_count,
528
- traps_written: result.traps_written,
529
651
  };
530
652
  if (opts.json !== undefined) return jsonResult(payload);
531
653
  const lines = [
@@ -539,6 +661,39 @@ function cmdSessionClose(args: string[], sessions: SessionOperations): CommandRe
539
661
  return textResult(lines.join("\n"));
540
662
  }
541
663
 
664
+ function cmdSessionCapture(args: string[], sessions: SessionOperations): CommandResult {
665
+ const { opts } = parseArgs(args);
666
+ const result = sessions.captureCandidate(sessionCaptureRequestFromArgs(opts, {
667
+ isTTY: process.stdin.isTTY === true,
668
+ readStdin: () => readFileSync(0, "utf-8"),
669
+ readFile: (path) => readFileSync(path, "utf-8"),
670
+ }));
671
+ const nextAction = `codetrap session candidate ${result.candidate.id} --session ${result.session.id} --json`;
672
+ const payload = {
673
+ success: true,
674
+ session_id: result.session.id,
675
+ candidate_id: result.candidate.id,
676
+ status: result.candidate.status,
677
+ quality_score: result.candidate.quality_score,
678
+ candidate_count: result.candidate_count,
679
+ created_session: result.created_session,
680
+ closed_session: result.closed_session,
681
+ duplicate: result.duplicate,
682
+ candidate_traps_path: sessionRelativeFile(result.session.id, CANDIDATES_FILE),
683
+ recap_path: result.recap_path,
684
+ next_action: {
685
+ command: nextAction,
686
+ },
687
+ };
688
+ if (opts.json !== undefined) return jsonResult(payload);
689
+ return textResult([
690
+ `${result.duplicate ? "Reused" : "Captured"} candidate ${result.candidate.id} in session ${result.session.id}.`,
691
+ result.created_session ? "Created and closed a post-flight session." : "Session remains active.",
692
+ `Candidate inbox: ${payload.candidate_traps_path}`,
693
+ `Review: ${nextAction}`,
694
+ ].join("\n"));
695
+ }
696
+
542
697
  function cmdSessionCandidates(args: string[], sessions: SessionOperations): CommandResult {
543
698
  const { opts, positionals } = parseArgs(args);
544
699
  const request = sessionIdRequestFromArgs(positionals);
@@ -562,16 +717,7 @@ async function cmdSessionAccept(args: string[], sessions: SessionOperations): Pr
562
717
  const { opts, positionals } = parseArgs(args);
563
718
  const accepted = await sessions.acceptCandidate(sessionAcceptRequestFromArgs(positionals, opts));
564
719
  if (!accepted.success) return possibleConflictResult(accepted, opts.json !== undefined);
565
- const payload = {
566
- success: true,
567
- session_id: accepted.session.id,
568
- candidate_id: accepted.candidate.id,
569
- status: accepted.candidate.status,
570
- trap_id: accepted.trap_id,
571
- scope: accepted.scope,
572
- evidence_id: accepted.evidence_id,
573
- superseded_id: accepted.superseded_id,
574
- };
720
+ const payload = sessionAcceptPayload(accepted);
575
721
  if (opts.json !== undefined) return jsonResult(payload);
576
722
  const lines = [`Accepted ${accepted.candidate.id}; wrote trap #${accepted.trap_id} to ${accepted.scope} scope.`];
577
723
  if (accepted.superseded_id !== null) lines.push(`Superseded trap #${accepted.superseded_id}.`);
@@ -581,13 +727,7 @@ async function cmdSessionAccept(args: string[], sessions: SessionOperations): Pr
581
727
  function cmdSessionReject(args: string[], sessions: SessionOperations): CommandResult {
582
728
  const { opts, positionals } = parseArgs(args);
583
729
  const rejected = sessions.rejectCandidate(sessionRejectRequestFromArgs(positionals, opts));
584
- const payload = {
585
- success: true,
586
- session_id: rejected.session.id,
587
- candidate_id: rejected.candidate.id,
588
- status: rejected.candidate.status,
589
- reason: rejected.candidate.rejection_reason ?? null,
590
- };
730
+ const payload = sessionRejectPayload(rejected);
591
731
  if (opts.json !== undefined) return jsonResult(payload);
592
732
  return textResult(`Rejected ${rejected.candidate.id}.`);
593
733
  }
@@ -621,12 +761,7 @@ function cmdSessionCleanup(args: string[], sessions: SessionOperations): Command
621
761
  }
622
762
  const request = sessionIdRequestFromArgs(positionals);
623
763
  const result = sessions.cleanupDeletedTrapCandidates(request.sessionId);
624
- const payload = {
625
- success: true,
626
- session_id: result.session.id,
627
- removed_count: result.removed_count,
628
- removed_candidate_ids: result.removed_candidate_ids,
629
- };
764
+ const payload = sessionCleanupPayload(result);
630
765
  if (opts.json !== undefined) return jsonResult(payload);
631
766
  return textResult(`Removed ${result.removed_count} deleted-trap candidate(s) from session ${result.session.id}.`);
632
767
  }
@@ -679,42 +814,11 @@ function errorMessage(error: unknown): string {
679
814
  return error instanceof Error ? error.message : String(error);
680
815
  }
681
816
 
682
- function sessionPayload(session: SessionMetadata) {
683
- return {
684
- ...session,
685
- session_dir: sessionRelativeDir(session.id),
686
- notes_path: sessionRelativeFile(session.id, NOTES_FILE),
687
- recap_path: sessionRelativeFile(session.id, RECAP_FILE),
688
- candidate_traps_path: sessionRelativeFile(session.id, CANDIDATES_FILE),
689
- };
690
- }
691
-
692
817
  function possibleConflictResult(
693
818
  result: SessionConflictResult,
694
819
  asJson: boolean
695
820
  ): CommandResult {
696
- const payload = {
697
- success: false,
698
- error: "Possible active trap conflict found.",
699
- session_id: result.session_id,
700
- candidate_id: result.candidate_id,
701
- possible_conflicts: result.possible_conflicts,
702
- next_actions: [
703
- `codetrap session accept ${result.candidate_id} --session ${result.session_id} --accept-anyway`,
704
- `codetrap session accept ${result.candidate_id} --session ${result.session_id} --supersedes <trap-id>`,
705
- `codetrap session reject ${result.candidate_id} --session ${result.session_id} --reason <reason>`,
706
- ],
707
- };
708
- if (asJson) return jsonResult(payload, 1);
709
-
710
- return errorResult([
711
- "Possible active trap conflict found:",
712
- ...result.possible_conflicts.map((conflict) => [
713
- `#${conflict.trap_id} ${conflict.title}`,
714
- ` reason: ${conflict.reason}`,
715
- ` fix: ${conflict.fix}`,
716
- ].join("\n")),
717
- "",
718
- `Use --accept-anyway to save as a new trap, or --supersedes <trap-id> to preserve lifecycle history.`,
719
- ].join("\n"));
821
+ const payload = sessionConflictPayload(result);
822
+ if (asJson) return jsonResult(sessionCliConflictPayload(payload), 1);
823
+ return errorResult(sessionConflictText(payload));
720
824
  }