codiedev 0.10.0-beta.4 → 0.10.0-beta.5

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 (146) hide show
  1. package/README.md +7 -1
  2. package/assets/tui-brand-plugin.tsx +14 -2
  3. package/dist/backup/cache.js +1 -5
  4. package/dist/backup/match.js +0 -6
  5. package/dist/backup/run.js +3 -35
  6. package/dist/captureExclusions.js +0 -12
  7. package/dist/captureLog.js +0 -28
  8. package/dist/captureReadiness.js +0 -58
  9. package/dist/captureSignals.js +66 -3
  10. package/dist/cli.js +41 -18
  11. package/dist/codexHookTrust.js +61 -5
  12. package/dist/codexLimitScan.js +2 -109
  13. package/dist/codie.js +27 -40
  14. package/dist/codieBrandInstall.js +0 -18
  15. package/dist/codieLauncher.js +2 -70
  16. package/dist/codieRuntime.js +19 -23
  17. package/dist/codieRuntimeInstall.js +108 -23
  18. package/dist/codieStartup.js +0 -3
  19. package/dist/codieUsage.js +38 -41
  20. package/dist/commands/ask.js +0 -3
  21. package/dist/commands/capture.js +0 -2
  22. package/dist/commands/createTicket.js +0 -10
  23. package/dist/commands/doctor.js +41 -82
  24. package/dist/commands/inbox.js +0 -2
  25. package/dist/commands/library.js +0 -3
  26. package/dist/commands/pack.js +573 -0
  27. package/dist/commands/ping.js +0 -2
  28. package/dist/commands/pull.js +6 -10
  29. package/dist/commands/push.js +2 -12
  30. package/dist/commands/react.js +0 -2
  31. package/dist/commands/reverseTicket.js +0 -33
  32. package/dist/commands/search.js +0 -4
  33. package/dist/commands/send.js +0 -5
  34. package/dist/commands/share.js +0 -4
  35. package/dist/commands/shared.js +0 -35
  36. package/dist/commands/traces.js +280 -0
  37. package/dist/configDir.js +0 -10
  38. package/dist/connect.js +21 -77
  39. package/dist/connectFlow.js +0 -9
  40. package/dist/copilotHooks.js +395 -0
  41. package/dist/copilotInstallation.js +64 -0
  42. package/dist/cursorHooks.js +40 -17
  43. package/dist/cursorLifecycle.js +109 -0
  44. package/dist/detection.js +0 -30
  45. package/dist/harnessDirs.js +73 -0
  46. package/dist/hook.js +193 -316
  47. package/dist/inboxFormat.js +0 -24
  48. package/dist/launchContext.js +150 -0
  49. package/dist/launcher.js +140 -0
  50. package/dist/limitEvent.js +41 -35
  51. package/dist/limitEventReporter.js +1 -1
  52. package/dist/mcp.js +3 -77
  53. package/dist/nativeSteering.js +24 -0
  54. package/dist/packs/codie.js +182 -0
  55. package/dist/packs/harnesses.js +374 -0
  56. package/dist/packs/hookGate.js +256 -0
  57. package/dist/packs/install.js +285 -0
  58. package/dist/packs/manifest.js +126 -0
  59. package/dist/packs/paths.js +81 -0
  60. package/dist/packs/shim.js +214 -0
  61. package/dist/packs/store.js +155 -0
  62. package/dist/packs/sync.js +170 -0
  63. package/dist/prUrl.js +0 -5
  64. package/dist/repoResolver.js +0 -19
  65. package/dist/secret.js +0 -3
  66. package/dist/security.js +0 -20
  67. package/dist/securityDeepScan.js +1 -14
  68. package/dist/securityDoctor.js +0 -2
  69. package/dist/securityFeature.js +0 -1
  70. package/dist/securityInstall.js +6 -13
  71. package/dist/securityInventory.js +23 -13
  72. package/dist/securityPilot.js +0 -2
  73. package/dist/securityPolicy.js +0 -7
  74. package/dist/selfUpdate.js +331 -0
  75. package/dist/skillMarker.js +23 -0
  76. package/dist/subagents.js +135 -0
  77. package/dist/teamSkillContext.js +10 -0
  78. package/dist/transcriptModels.js +6 -8
  79. package/dist/upgradeNudge.js +0 -30
  80. package/dist/utils.js +286 -733
  81. package/dist/version.js +0 -10
  82. package/dist/versionCheck.js +0 -33
  83. package/package.json +7 -6
  84. package/dist/backup/cache.d.ts +0 -14
  85. package/dist/backup/match.d.ts +0 -2
  86. package/dist/backup/run.d.ts +0 -25
  87. package/dist/captureExclusions.d.ts +0 -30
  88. package/dist/captureLog.d.ts +0 -20
  89. package/dist/captureReadiness.d.ts +0 -65
  90. package/dist/captureSignals.d.ts +0 -19
  91. package/dist/cli.d.ts +0 -16
  92. package/dist/codexHookTrust.d.ts +0 -34
  93. package/dist/codexLimitScan.d.ts +0 -64
  94. package/dist/codie.d.ts +0 -2
  95. package/dist/codieBrandInstall.d.ts +0 -11
  96. package/dist/codieLauncher.d.ts +0 -86
  97. package/dist/codieRuntime.d.ts +0 -29
  98. package/dist/codieRuntimeInstall.d.ts +0 -28
  99. package/dist/codieStartup.d.ts +0 -26
  100. package/dist/codieUsage.d.ts +0 -57
  101. package/dist/commands/ask.d.ts +0 -1
  102. package/dist/commands/capture.d.ts +0 -14
  103. package/dist/commands/createTicket.d.ts +0 -27
  104. package/dist/commands/delete.d.ts +0 -1
  105. package/dist/commands/doctor.d.ts +0 -1
  106. package/dist/commands/inbox.d.ts +0 -3
  107. package/dist/commands/library.d.ts +0 -2
  108. package/dist/commands/note.d.ts +0 -1
  109. package/dist/commands/ping.d.ts +0 -1
  110. package/dist/commands/promote.d.ts +0 -1
  111. package/dist/commands/pull.d.ts +0 -2
  112. package/dist/commands/push.d.ts +0 -33
  113. package/dist/commands/react.d.ts +0 -2
  114. package/dist/commands/reverseTicket.d.ts +0 -2
  115. package/dist/commands/search.d.ts +0 -2
  116. package/dist/commands/security.d.ts +0 -1
  117. package/dist/commands/send.d.ts +0 -2
  118. package/dist/commands/share.d.ts +0 -2
  119. package/dist/commands/shared.d.ts +0 -64
  120. package/dist/configDir.d.ts +0 -1
  121. package/dist/connect.d.ts +0 -1
  122. package/dist/connectFlow.d.ts +0 -13
  123. package/dist/cursorHooks.d.ts +0 -23
  124. package/dist/detection.d.ts +0 -30
  125. package/dist/hook.d.ts +0 -6
  126. package/dist/inboxFormat.d.ts +0 -38
  127. package/dist/limitEvent.d.ts +0 -16
  128. package/dist/limitEventReporter.d.ts +0 -21
  129. package/dist/mcp.d.ts +0 -12
  130. package/dist/prUrl.d.ts +0 -9
  131. package/dist/repoResolver.d.ts +0 -17
  132. package/dist/secret.d.ts +0 -1
  133. package/dist/security.d.ts +0 -102
  134. package/dist/securityDeepScan.d.ts +0 -41
  135. package/dist/securityDoctor.d.ts +0 -32
  136. package/dist/securityFeature.d.ts +0 -5
  137. package/dist/securityInstall.d.ts +0 -63
  138. package/dist/securityInventory.d.ts +0 -54
  139. package/dist/securityPilot.d.ts +0 -19
  140. package/dist/securityPolicy.d.ts +0 -33
  141. package/dist/securitySession.d.ts +0 -6
  142. package/dist/transcriptModels.d.ts +0 -18
  143. package/dist/upgradeNudge.d.ts +0 -25
  144. package/dist/utils.d.ts +0 -346
  145. package/dist/version.d.ts +0 -1
  146. package/dist/versionCheck.d.ts +0 -68
@@ -1,6 +1,4 @@
1
1
  "use strict";
2
- // `codiedev react <postId> <emoji>` — toggle a reaction on a feed post.
3
- // Calling twice with the same emoji removes it.
4
2
  Object.defineProperty(exports, "__esModule", { value: true });
5
3
  exports.runReact = runReact;
6
4
  const shared_1 = require("./shared");
@@ -5,15 +5,6 @@ exports.runReverseTicket = runReverseTicket;
5
5
  const child_process_1 = require("child_process");
6
6
  const shared_1 = require("./shared");
7
7
  const prUrl_1 = require("../prUrl");
8
- /**
9
- * Run a binary with an explicit argv, never a shell.
10
- *
11
- * The `gh`/`glab` API paths are built from an owner/repo parsed out of a URL the
12
- * caller pastes in. `parsePrOrMrUrl` only bounds those segments to "no slash",
13
- * so they can still carry `$(…)`, backticks or `;` — which a `shell: true`
14
- * invocation would execute. Passing argv directly means the shell never sees
15
- * them at all.
16
- */
17
8
  function runArgv(bin, argv, opts = {}) {
18
9
  const res = (0, child_process_1.spawnSync)(bin, argv, {
19
10
  stdio: ["pipe", "pipe", "pipe"],
@@ -28,10 +19,6 @@ function runArgv(bin, argv, opts = {}) {
28
19
  }
29
20
  return res.stdout ?? "";
30
21
  }
31
- // Git ref names legally contain `$()`, `;`, `|` and backticks, all of which
32
- // break out of the `shell: true` git calls in branch mode below. A ref supplied
33
- // via `--base` is caller-controlled, so bound it to the safe subset before it
34
- // reaches a command line. Mirrors `validate_ref` in modal/runner.py.
35
22
  const SAFE_REF_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$/;
36
23
  function isSafeRef(ref) {
37
24
  if (!ref || ref.startsWith("-"))
@@ -56,7 +43,6 @@ async function fetchPrOrMrBundle(parsed, rawUrl, noTruncate) {
56
43
  diff = noTruncate ? rawDiff : rawDiff.slice(0, 12_000);
57
44
  }
58
45
  catch {
59
- // diff is nice-to-have
60
46
  }
61
47
  }
62
48
  catch (err) {
@@ -80,9 +66,6 @@ async function fetchPrOrMrBundle(parsed, rawUrl, noTruncate) {
80
66
  htmlUrl: prJson.html_url ?? rawUrl,
81
67
  };
82
68
  }
83
- // GitLab path. `glab api projects/<encoded-path>/...` returns the same
84
- // MR shape as the REST API. We pull the diff from `merge_requests/<n>/changes`
85
- // and reassemble a unified diff out of each file's `diff` blob.
86
69
  const projectPath = `${parsed.owner}/${parsed.repo}`;
87
70
  let mrJson;
88
71
  let changesJson;
@@ -142,9 +125,6 @@ function ghRaw(path) {
142
125
  path,
143
126
  ]);
144
127
  }
145
- // glab api takes a project path (URL-encoded) or numeric id and a sub-path.
146
- // For self-hosted GitLab, glab respects `GITLAB_HOST` env if the user has
147
- // configured glab against their internal instance.
148
128
  function glabApi(projectPath, subpath) {
149
129
  const encoded = encodeURIComponent(projectPath);
150
130
  return JSON.parse(runArgv("glab", ["api", `projects/${encoded}/${subpath}`]));
@@ -179,7 +159,6 @@ function parseArgs(args) {
179
159
  }
180
160
  async function runReverseTicket(args) {
181
161
  const { prUrl, base, forcedKey, noTruncate } = parseArgs(args);
182
- // No PR URL → branch mode (mid-session, no PR exists yet).
183
162
  if (!prUrl) {
184
163
  return runBranchMode({ explicitBase: base, forcedKey });
185
164
  }
@@ -282,7 +261,6 @@ function safeGit(fn) {
282
261
  }
283
262
  }
284
263
  async function runBranchMode(opts) {
285
- // Confirm we're in a git repo.
286
264
  try {
287
265
  git("rev-parse --is-inside-work-tree");
288
266
  }
@@ -290,13 +268,10 @@ async function runBranchMode(opts) {
290
268
  console.error("Not inside a git repository. Run this from a checked-out repo.");
291
269
  process.exit(1);
292
270
  }
293
- // `--base` lands unquoted on a `git` command line below, so reject anything
294
- // outside the safe-ref subset instead of letting the shell interpret it.
295
271
  if (opts.explicitBase !== undefined && !isSafeRef(opts.explicitBase)) {
296
272
  console.error(`Invalid --base ref: ${JSON.stringify(opts.explicitBase)}. Expected a plain branch or tag name.`);
297
273
  process.exit(1);
298
274
  }
299
- // Resolve base ref — explicit, then origin/main, then origin/master.
300
275
  const candidates = opts.explicitBase
301
276
  ? [opts.explicitBase]
302
277
  : ["origin/main", "origin/master"];
@@ -308,7 +283,6 @@ async function runBranchMode(opts) {
308
283
  break;
309
284
  }
310
285
  catch {
311
- // try next
312
286
  }
313
287
  }
314
288
  if (!baseRef) {
@@ -322,9 +296,6 @@ async function runBranchMode(opts) {
322
296
  }
323
297
  const baseSha = git(`merge-base HEAD ${baseRef}`);
324
298
  const headSha = git("rev-parse HEAD");
325
- // Filter generated/lockfile noise from the diff body. The --stat (collected
326
- // separately) still includes every file untouched, so excluded files still
327
- // surface in the ticket's "Other files touched" section.
328
299
  const isNoise = (p) => /(^|\/)(node_modules|dist|build|\.next|\.turbo|coverage|out)\//.test(p) ||
329
300
  /(^|\/)package-lock\.json$/.test(p) ||
330
301
  /(^|\/)yarn\.lock$/.test(p) ||
@@ -348,8 +319,6 @@ async function runBranchMode(opts) {
348
319
  }
349
320
  };
350
321
  const shQuote = (s) => `'${s.replace(/'/g, "'\\''")}'`;
351
- // Capture all work relative to the base ref: committed + uncommitted-tracked
352
- // + new untracked. Matches "ticket my current changes" intent.
353
322
  const trackedRaw = git(`diff --name-only ${baseSha}`);
354
323
  const trackedFiles = trackedRaw ? trackedRaw.split("\n").filter(Boolean) : [];
355
324
  const untrackedRaw = safeGit(() => git("ls-files --others --exclude-standard"));
@@ -375,7 +344,6 @@ async function runBranchMode(opts) {
375
344
  .slice(0, 8_000);
376
345
  }
377
346
  catch {
378
- // best effort
379
347
  }
380
348
  let diff = "";
381
349
  try {
@@ -391,7 +359,6 @@ async function runBranchMode(opts) {
391
359
  .slice(0, 40_000);
392
360
  }
393
361
  catch {
394
- // best effort
395
362
  }
396
363
  let repo;
397
364
  try {
@@ -1,8 +1,4 @@
1
1
  "use strict";
2
- // `codiedev search "<query>"` — semantic + keyword search across the org's
3
- // captured sessions, artifacts, and skills. Hits are filtered by companyId
4
- // server-side, so a single search surfaces results from every teammate's
5
- // prior sessions in the same workspace.
6
2
  Object.defineProperty(exports, "__esModule", { value: true });
7
3
  exports.runSearch = runSearch;
8
4
  const shared_1 = require("./shared");
@@ -1,8 +1,4 @@
1
1
  "use strict";
2
- // `codiedev send <filename> <recipient> [message]` — share an artifact AND
3
- // send a short message that lands in the teammate's inbox. The active-
4
- // handoff version of `codiedev share`. Use this when the user is actively
5
- // looping someone in.
6
2
  Object.defineProperty(exports, "__esModule", { value: true });
7
3
  exports.runSend = runSend;
8
4
  const shared_1 = require("./shared");
@@ -13,7 +9,6 @@ function parseArgs(args) {
13
9
  console.error('Usage: codiedev send <filename> <recipient> ["<message>"]');
14
10
  process.exit(1);
15
11
  }
16
- // Recombine remaining tokens — supports both quoted and unquoted messages.
17
12
  const message = rest.length > 0 ? (0, shared_1.stripQuotes)(rest.join(" ")) : undefined;
18
13
  return { filename, to, message };
19
14
  }
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
- // `codiedev share <filename> <recipient> [--role read|edit]` — silently
3
- // grant a teammate access to an artifact (no notification). Use
4
- // `codiedev send` instead when the user wants to actively loop someone in.
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.runShare = runShare;
7
4
  const shared_1 = require("./shared");
@@ -71,7 +68,6 @@ async function runShare(args, configOverride) {
71
68
  latencyMs: Date.now() - start,
72
69
  error: err.message,
73
70
  });
74
- // Ambiguous recipients return 409 with candidates in the body.
75
71
  const body = err.body;
76
72
  if (body && Array.isArray(body.candidates) && body.candidates.length > 0) {
77
73
  console.error(`Recipient "${parsed.to}" is ambiguous. Candidates:`);
@@ -45,13 +45,6 @@ const https = __importStar(require("https"));
45
45
  const http = __importStar(require("http"));
46
46
  const utils_1 = require("../utils");
47
47
  const version_1 = require("../version");
48
- /**
49
- * Layer per-command env overrides on top of the on-disk config. Useful for
50
- * ad-hoc testing — point a prod-connected CLI at a dev Convex deployment
51
- * without re-running `codiedev connect`. Both CODIEDEV_URL and CODIEDEV_TOKEN
52
- * must be set together (half-overrides would mismatch URL and auth realm).
53
- * Other fields (companyId, repos[]) come from the file unchanged.
54
- */
55
48
  function applyConfigEnvOverrides(config) {
56
49
  const url = process.env.CODIEDEV_URL;
57
50
  const token = process.env.CODIEDEV_TOKEN;
@@ -60,10 +53,6 @@ function applyConfigEnvOverrides(config) {
60
53
  }
61
54
  return config;
62
55
  }
63
- /**
64
- * Require a connected Codiedev config. Exits the process with a helpful
65
- * message if the user hasn't run `codiedev connect` yet.
66
- */
67
56
  function requireConfig() {
68
57
  const config = (0, utils_1.readConfig)();
69
58
  if (!config) {
@@ -72,10 +61,6 @@ function requireConfig() {
72
61
  }
73
62
  return applyConfigEnvOverrides(config);
74
63
  }
75
- /**
76
- * Perform an authenticated HTTP request against the Codiedev backend.
77
- * Returns the parsed JSON response body. Throws on non-2xx.
78
- */
79
64
  function apiRequest(method, path, options) {
80
65
  return new Promise((resolve, reject) => {
81
66
  const base = options.config.backendUrl;
@@ -135,12 +120,6 @@ function apiRequest(method, path, options) {
135
120
  req.end();
136
121
  });
137
122
  }
138
- /**
139
- * Fire-and-forget per-invocation telemetry from CLI subcommands. Mirrors
140
- * the MCP server's emitTelemetry but stamps `source: "cli"` so adoption
141
- * dashboards can distinguish between the two surfaces. Never blocks the
142
- * user's command, never throws — telemetry must not change behavior.
143
- */
144
123
  function recordAgentEvent(config, event) {
145
124
  apiRequest("POST", "/api/telemetry/mcp-event", {
146
125
  config,
@@ -153,7 +132,6 @@ function recordAgentEvent(config, event) {
153
132
  source: "cli",
154
133
  },
155
134
  }).catch(() => {
156
- // Swallow — telemetry must never affect user-facing flow.
157
135
  });
158
136
  }
159
137
  function recordInstallEvent(config, event) {
@@ -169,14 +147,8 @@ function recordInstallEvent(config, event) {
169
147
  cliVersion: version_1.CLI_VERSION,
170
148
  },
171
149
  }).catch(() => {
172
- // Swallow — telemetry must never affect connect flow.
173
150
  });
174
151
  }
175
- /**
176
- * Wraps a CLI subcommand body so success and failure both record telemetry
177
- * and the original error propagates after the event is queued. Use this in
178
- * each `runXxx` command instead of inline timing + try/catch boilerplate.
179
- */
180
152
  async function withTelemetry(config, tool, fn) {
181
153
  const start = Date.now();
182
154
  try {
@@ -194,9 +166,6 @@ async function withTelemetry(config, tool, fn) {
194
166
  throw err;
195
167
  }
196
168
  }
197
- /**
198
- * Relative-time formatter for inbox / listing output.
199
- */
200
169
  function timeAgo(ts) {
201
170
  const diff = Date.now() - ts;
202
171
  const mins = Math.floor(diff / 60000);
@@ -210,10 +179,6 @@ function timeAgo(ts) {
210
179
  const days = Math.floor(hours / 24);
211
180
  return `${days}d ago`;
212
181
  }
213
- /**
214
- * Strip surrounding quotes from a CLI argument if the user wrapped it.
215
- * `codiedev ping <name> "hey"` on some shells passes the quotes through.
216
- */
217
182
  function stripQuotes(s) {
218
183
  if ((s.startsWith('"') && s.endsWith('"')) ||
219
184
  (s.startsWith("'") && s.endsWith("'"))) {
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.TRACES_HELP = void 0;
37
+ exports.slugify = slugify;
38
+ exports.casesBrief = casesBrief;
39
+ exports.resultsTemplate = resultsTemplate;
40
+ exports.runTraces = runTraces;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const shared_1 = require("./shared");
44
+ exports.TRACES_HELP = `
45
+ codiedev traces — test cases saved from Codiedev → Traces
46
+
47
+ codiedev traces agents Agents you maintain, with saved case counts
48
+ codiedev traces cases <agent> [--suite name] [--case id[,id]] [--model id] [--out dir] [--json]
49
+ Write an agent's cases (one suite, or just
50
+ the cases named) into this repo, default
51
+ .codiedev/traces/<agent>[/<suite>]/, then
52
+ ask your coding agent to run them.
53
+ --model runs them on another model, e.g.
54
+ the replacement for a retiring one
55
+ codiedev traces report <results.json> [--harness name]
56
+ Send the filled-in results back, so
57
+ Traces shows the pass rate for that model
58
+
59
+ <agent> is the agent's name (its API key name) or id. Only an agent's
60
+ maintainers can pull its cases or report on them.
61
+ `.trim();
62
+ const FLAG_LABEL = {
63
+ budget_block: "refused for budget",
64
+ errors: "model call failed",
65
+ tool_loop: "repeated the same tool call",
66
+ truncated: "reply cut off at max_tokens",
67
+ context_overflow: "prompt over 70% of the context window",
68
+ };
69
+ const TRIES = 3;
70
+ const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
71
+ const recordedModel = (res) => res.cases.flatMap((c) => c.steps.map((s) => s.model))[0] ?? "the recorded model";
72
+ function slugify(name) {
73
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
74
+ }
75
+ function casesBrief(res, casesFile, opts = {}) {
76
+ const fix = res.cases.filter((c) => c.kind === "fix");
77
+ const keep = res.cases.filter((c) => c.kind === "keep");
78
+ const withBodies = res.cases.filter((c) => c.steps.some((s) => s.request)).length;
79
+ const target = opts.model;
80
+ const results = opts.resultsFile ? path.basename(opts.resultsFile) : "results.json";
81
+ const lines = [
82
+ `# Test cases for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${target ? ` on ${target}` : ""}`,
83
+ "",
84
+ res.agent.description ? `${res.agent.description}\n` : "",
85
+ `Saved from Codiedev → Traces. ${plural(res.cases.length, "case")} in \`${path.basename(casesFile)}\` (one JSON object per line):`,
86
+ `- **${fix.length} to fix**: production runs that failed. The fix should make them pass.`,
87
+ `- **${keep.length} to keep passing**: good runs. They should still pass after any change.`,
88
+ "",
89
+ ];
90
+ if (target) {
91
+ lines.push("## This is a model check", "", `Run every case on **${target}** instead of ${recordedModel(res)} (the model the cases were recorded on). Change only the model the agent asks the gateway for; keep its prompts and tools as they are, so the result says how ${target} does on the same work.`, "");
92
+ }
93
+ lines.push("## How to run them", "", "1. Find where this agent builds its model requests and calls the Codiedev gateway.", `2. For each case, replay \`steps[0].request\` (the first prompt the agent sent) through the agent's own code path, so tools and prompts are the current version, not the recorded one${target ? `, with the model set to ${target}` : ""}.`, "3. Judge each try against the case:", " - A case's `expected`, when present, is what a passing answer looks like (a reference reply or criteria). It wins over everything below.", " - `fix` cases: the run must not reproduce its `flags` (listed below), and must finish (no failed call, no `finish_reason: \"length\"`, no repeated identical tool call).", " - `keep` cases: the final reply must still do what the recorded final reply did. Compare meaning, not wording.", " - A case's `note`, when present, is the engineer's extra context.");
94
+ if (res.judge?.model || res.judge?.rubric) {
95
+ lines.push(` - Where passing needs judgement, judge with **${res.judge.model ?? "a strong model"}** through the gateway${res.judge.rubric ? `, using this rubric: "${res.judge.rubric}"` : ""}. Use the same judge for every model so results compare.`);
96
+ }
97
+ lines.push(`4. Run each case ${TRIES} times: agents are stochastic, so report a pass rate per case, not a single result.`, `5. Fill in \`${results}\`: for each case set \`passes\` (out of \`tries\`), \`output\` (the agent's final reply on one try, so it can be compared side by side with other models) and a one-line \`note\` on any failure. Set \`harness\` to the coding agent you are (claude-code, codex, cursor, copilot, …).`, `6. Send it back: \`codiedev traces report ${opts.resultsFile ? path.relative(process.cwd(), opts.resultsFile) || results : results}\`. Then show a table: case, kind, passes out of ${TRIES}, and the failure note.`, "", "Send real model calls through the gateway with the agent's usual key and `X-Codiedev-Run-Id: case-<id>-<try>`, so the replays show up in Traces too.", "");
98
+ if (withBodies < res.cases.length) {
99
+ lines.push(`Note: ${plural(res.cases.length - withBodies, "case")} ${res.cases.length - withBodies === 1 ? "has" : "have"} no recorded prompt (the agent wasn't keeping prompts then). For those, rebuild the input from the case's \`expected\` or \`note\`, or skip them and say so.`, "");
100
+ }
101
+ lines.push("## Cases", "");
102
+ for (const c of res.cases) {
103
+ const flags = c.flags.length ? c.flags.map((f) => FLAG_LABEL[f] ?? f).join(", ") : "no flags";
104
+ lines.push(`- \`${c.id}\` · ${c.name} · ${c.kind} · ${flags} · ${plural(c.steps.length, "model call")}${c.expected ? ` · expected: "${c.expected.replace(/\s+/g, " ").slice(0, 200)}"` : ""}${c.note ? ` · note: "${c.note}"` : ""}`);
105
+ }
106
+ return lines.filter((line, i, all) => !(line === "" && all[i - 1] === "")).join("\n") + "\n";
107
+ }
108
+ function resultsTemplate(res, model) {
109
+ return {
110
+ agent: res.agent.name,
111
+ suite: res.suite,
112
+ model: model ?? recordedModel(res),
113
+ judgeModel: res.judge?.model ?? null,
114
+ harness: null,
115
+ results: res.cases.map((c) => ({ caseId: c.id, name: c.name, passes: null, tries: TRIES, note: null, output: null })),
116
+ };
117
+ }
118
+ function flag(args, i, name) {
119
+ const a = args[i];
120
+ if (a === name && i + 1 < args.length)
121
+ return { value: args[i + 1], skip: 1 };
122
+ if (a.startsWith(`${name}=`))
123
+ return { value: a.slice(name.length + 1), skip: 0 };
124
+ return { skip: -1 };
125
+ }
126
+ function parseCasesArgs(args) {
127
+ const parsed = { json: false };
128
+ for (let i = 0; i < args.length; i++) {
129
+ const a = args[i];
130
+ let matched = false;
131
+ for (const [name, key] of [["--out", "out"], ["-o", "out"], ["--suite", "suite"], ["--model", "model"], ["--case", "caseIds"]]) {
132
+ const f = flag(args, i, name);
133
+ if (f.skip >= 0) {
134
+ parsed[key] = f.value;
135
+ i += f.skip;
136
+ matched = true;
137
+ break;
138
+ }
139
+ }
140
+ if (matched)
141
+ continue;
142
+ if (a === "--json")
143
+ parsed.json = true;
144
+ else if (!a.startsWith("-") && !parsed.agent)
145
+ parsed.agent = a;
146
+ }
147
+ return parsed;
148
+ }
149
+ async function runAgents(config) {
150
+ const start = Date.now();
151
+ let res;
152
+ try {
153
+ res = await (0, shared_1.apiRequest)("GET", "/api/cli/traces/agents", { config });
154
+ }
155
+ catch (err) {
156
+ const e = err;
157
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_agents", ok: false, latencyMs: Date.now() - start, error: e.message });
158
+ console.error(`Couldn't list agents: ${e.message}`);
159
+ process.exit(1);
160
+ }
161
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_agents", ok: true, latencyMs: Date.now() - start });
162
+ if (!res.agents.length) {
163
+ console.log("You don't maintain any agents yet. Connect one in Codiedev → Traces → How it works.");
164
+ return;
165
+ }
166
+ for (const agent of res.agents) {
167
+ const capture = agent.promptDays ? `prompts kept ${agent.promptDays} days` : "metadata only";
168
+ console.log(`${agent.name.padEnd(28)} ${plural(agent.cases, "case").padStart(9)} ${capture}`);
169
+ }
170
+ console.log("\nPull one: codiedev traces cases <agent>");
171
+ }
172
+ async function runCases(args, config) {
173
+ const { agent, suite, model, caseIds, out, json } = parseCasesArgs(args);
174
+ if (!agent) {
175
+ console.error("Usage: codiedev traces cases <agent> [--suite name] [--model id] [--out dir] [--json]");
176
+ process.exit(1);
177
+ }
178
+ const start = Date.now();
179
+ let res;
180
+ try {
181
+ res = await (0, shared_1.apiRequest)("GET", "/api/cli/traces/cases", { config, query: { agent, ...(suite ? { suite } : {}), ...(caseIds ? { case: caseIds } : {}) } });
182
+ }
183
+ catch (err) {
184
+ const e = err;
185
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_cases", ok: false, latencyMs: Date.now() - start, error: e.message });
186
+ if (e.status === 404)
187
+ console.error(`No agent called "${agent}". See your agents with: codiedev traces agents`);
188
+ else if (e.status === 403)
189
+ console.error(`Only ${agent}'s maintainers can pull its test cases.`);
190
+ else
191
+ console.error(`Couldn't pull cases: ${e.message}`);
192
+ process.exit(1);
193
+ }
194
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_cases", ok: true, latencyMs: Date.now() - start });
195
+ if (json) {
196
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
197
+ return;
198
+ }
199
+ if (!res.cases.length) {
200
+ console.log(suite
201
+ ? `${res.agent.name} has no test cases in "${suite}". See your suites in Codiedev → Traces → Test cases.`
202
+ : `${res.agent.name} has no saved test cases. Open a run in Codiedev → Traces and click "Save as test case".`);
203
+ return;
204
+ }
205
+ const dir = path.resolve(process.cwd(), out ?? path.join(".codiedev", "traces", slugify(res.agent.name), ...(res.suite ? [slugify(res.suite)] : []), ...(caseIds && res.cases.length === 1 ? [slugify(res.cases[0].name)] : []), ...(model ? [slugify(model)] : [])));
206
+ fs.mkdirSync(dir, { recursive: true });
207
+ const casesFile = path.join(dir, "cases.jsonl");
208
+ fs.writeFileSync(casesFile, res.cases.map((c) => JSON.stringify(c)).join("\n") + "\n", "utf8");
209
+ const resultsFile = path.join(dir, "results.json");
210
+ fs.writeFileSync(resultsFile, JSON.stringify(resultsTemplate(res, model), null, 2) + "\n", "utf8");
211
+ const briefFile = path.join(dir, "RUN_CASES.md");
212
+ fs.writeFileSync(briefFile, casesBrief(res, casesFile, { model, resultsFile }), "utf8");
213
+ const fix = res.cases.filter((c) => c.kind === "fix").length;
214
+ const rel = (file) => path.relative(process.cwd(), file) || file;
215
+ console.log(`✓ ${plural(res.cases.length, "case")} for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${model ? ` on ${model}` : ""} (${fix} to fix, ${res.cases.length - fix} to keep passing)`);
216
+ console.log(` ${rel(casesFile)}`);
217
+ console.log(` ${rel(briefFile)}`);
218
+ console.log(` ${rel(resultsFile)} (filled in by your coding agent, then: codiedev traces report ${rel(resultsFile)})`);
219
+ console.log(`\nNext: ask your coding agent to "run the test cases in ${rel(briefFile)}"`);
220
+ }
221
+ async function runReport(args, config) {
222
+ let file;
223
+ let harness;
224
+ for (let i = 0; i < args.length; i++) {
225
+ const f = flag(args, i, "--harness");
226
+ if (f.skip >= 0) {
227
+ harness = f.value;
228
+ i += f.skip;
229
+ continue;
230
+ }
231
+ if (!args[i].startsWith("-") && !file)
232
+ file = args[i];
233
+ }
234
+ if (!file) {
235
+ console.error("Usage: codiedev traces report <results.json> [--harness name]");
236
+ process.exit(1);
237
+ }
238
+ let body;
239
+ try {
240
+ body = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), file), "utf8"));
241
+ }
242
+ catch (err) {
243
+ console.error(`Couldn't read ${file}: ${err.message}`);
244
+ process.exit(1);
245
+ }
246
+ const missing = body.results.filter((r) => typeof r.passes !== "number");
247
+ if (missing.length) {
248
+ console.error(`${plural(missing.length, "case")} in ${file} ${missing.length === 1 ? "has" : "have"} no "passes" yet: ${missing.slice(0, 5).map((r) => r.name).join(", ")}. Run them first.`);
249
+ process.exit(1);
250
+ }
251
+ const start = Date.now();
252
+ try {
253
+ const res = await (0, shared_1.apiRequest)("POST", "/api/cli/traces/results", {
254
+ config,
255
+ body: { ...body, harness: harness ?? body.harness ?? undefined },
256
+ });
257
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_report", ok: true, latencyMs: Date.now() - start });
258
+ const passed = body.results.filter((r) => r.passes === r.tries).length;
259
+ console.log(`✓ Reported ${body.agent}${body.suite ? ` · ${body.suite}` : ""} on ${body.model}: ${Math.round(res.passRate * 100)}% of tries passed, ${passed}/${body.results.length} cases passed every try.`);
260
+ console.log(" See it next to other models in Codiedev → Traces → Test cases.");
261
+ }
262
+ catch (err) {
263
+ const e = err;
264
+ (0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_report", ok: false, latencyMs: Date.now() - start, error: e.message });
265
+ console.error(e.status === 403 ? `Only ${body.agent}'s maintainers can report its results.` : `Couldn't report: ${e.message}`);
266
+ process.exit(1);
267
+ }
268
+ }
269
+ async function runTraces(args, configOverride) {
270
+ const [sub, ...rest] = args;
271
+ if (sub === "agents")
272
+ return runAgents(configOverride ?? (0, shared_1.requireConfig)());
273
+ if (sub === "cases")
274
+ return runCases(rest, configOverride ?? (0, shared_1.requireConfig)());
275
+ if (sub === "report")
276
+ return runReport(rest, configOverride ?? (0, shared_1.requireConfig)());
277
+ console.log(exports.TRACES_HELP);
278
+ if (sub && sub !== "help")
279
+ process.exit(1);
280
+ }
package/dist/configDir.js CHANGED
@@ -34,16 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.codiedevStateDir = codiedevStateDir;
37
- // Single source of truth for where the CLI's config/state lives on disk.
38
- //
39
- // Defaults to ~/.codiedev. The desktop app sets CODIEDEV_CONFIG_DIR to
40
- // ~/.codiedev-desktop so a desktop session never touches the user's real
41
- // prod config — env-based (not a flag) so detached capture-finish/upload
42
- // children inherit the override automatically.
43
- //
44
- // Deliberately dependency-free (only node:path/os) so it can be imported
45
- // by both utils.ts and modules utils.ts itself imports (e.g. captureLog.ts)
46
- // without creating a circular import.
47
37
  const path = __importStar(require("path"));
48
38
  const os = __importStar(require("os"));
49
39
  const CONFIG_DIR = process.env.CODIEDEV_CONFIG_DIR?.trim()