@moda-ai/cli 1.22.2 → 1.23.1

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.
@@ -6,32 +6,24 @@ import {
6
6
  MAX_RECORDED_SKIPS,
7
7
  MAX_SCANNED_FILE_BYTES,
8
8
  SKIPPED_DIRS,
9
+ __require,
9
10
  createCommandContext,
10
11
  embeddedTenantIdFromApiKey,
11
- ensureSecretFilePermissions,
12
12
  getActiveProfile,
13
- getModaPaths,
14
- isAuthSessionValid,
15
13
  isEvalLikePath,
16
- isLoopbackBaseUrl,
17
14
  isSecretishPath,
18
15
  isSkippedDir,
19
16
  isSkippedPathPrefix,
20
17
  isTextFile,
21
- loadAuthSession,
22
- loadProfileConfig,
23
18
  packSnapshot,
24
19
  redactDeep,
25
20
  resolveApiKey,
26
21
  resolveIngestUrl,
27
22
  resolveModaBaseUrl,
28
- resolveSecretReference,
29
23
  resolveTenantId,
30
- secretFilePathFromRef,
31
24
  shouldInventoryFile,
32
- stringOption,
33
- validateConfig
34
- } from "./cli-pwbvtes8.js";
25
+ stringOption
26
+ } from "./cli-mnqa4rp6.js";
35
27
 
36
28
  // src/api-client.ts
37
29
  var REQUEST_TIMEOUT_MS = 30000;
@@ -131,1947 +123,9 @@ function resetApiRequestCount() {
131
123
  apiRequestCount = 0;
132
124
  }
133
125
 
134
- // src/prompts.ts
135
- import { execFileSync } from "node:child_process";
136
- import { createHash } from "node:crypto";
137
- import {
138
- existsSync as existsSync2,
139
- lstatSync,
140
- mkdirSync,
141
- readFileSync,
142
- readdirSync,
143
- writeFileSync as writeFileSync2
144
- } from "node:fs";
145
- import { dirname, isAbsolute as isAbsolute3, join, relative, resolve as resolve3 } from "node:path";
146
-
147
- // src/prompts-ab.ts
148
- import { existsSync } from "node:fs";
149
- import { isAbsolute, resolve } from "node:path";
150
- var DEFAULT_SEEDS_PER_CASE = 3;
151
- var DEFAULT_POLL_INTERVAL_MS = 15000;
152
- var DEFAULT_WAIT_TIMEOUT_MS = 2 * 60 * 60 * 1000;
153
- var TERMINAL_RUN_STATUSES = new Set(["completed", "skipped", "error"]);
154
- async function runPromptAb(flags, profileOptions, context) {
155
- validateConfig();
156
- const tenantId = flags["tenant-id"] || resolveTenantId(profileOptions);
157
- if (!tenantId) {
158
- throw new Error("Missing tenant id. Run `moda init` or pass --tenant-id=<id>.");
159
- }
160
- const baselineSource = flags.baseline || flags["baseline-file"] || flags["baseline-key"];
161
- const candidateSource = flags.candidate || flags["candidate-file"] || flags["candidate-key"];
162
- if (!baselineSource || !candidateSource) {
163
- throw new Error("Usage: moda prompts ab --baseline=<file|key> --candidate=<file|key> [--auto-generate|--set-id=ID|--conversations=id1,id2]");
164
- }
165
- if (flags.sync === "true") {
166
- await runPromptSync({ ...flags, watch: "false" }, profileOptions);
167
- }
168
- const promptArms = {
169
- prod: resolvePromptArmSpec(baselineSource, "baseline"),
170
- proposed: resolvePromptArmSpec(candidateSource, "candidate")
171
- };
172
- const replaySetId = await ensureReplaySet(flags, tenantId, profileOptions);
173
- const seedsPerCase = parsePositiveInt(flags.seeds ?? flags["seeds-per-case"], DEFAULT_SEEDS_PER_CASE, 10);
174
- const assistantModel = (flags.model ?? flags["assistant-model"] ?? "").trim();
175
- const enqueue = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(replaySetId)}/run`, {
176
- method: "POST",
177
- body: JSON.stringify({
178
- promptArms,
179
- seedsPerCase,
180
- assistantModel: assistantModel || undefined,
181
- promotePrimary: flags["no-promote"] !== "true"
182
- })
183
- }, profileOptions, { timeoutMs: 120000, retries: 1 });
184
- const noWait = flags.wait === "false" || flags["no-wait"] === "true";
185
- if (noWait) {
186
- context.output.writeData({
187
- replaySetId,
188
- runId: enqueue.runId,
189
- status: enqueue.status,
190
- message: enqueue.message ?? "Replay comparison queued"
191
- });
192
- return;
193
- }
194
- const timeoutMs = parsePositiveInt(flags.timeout, DEFAULT_WAIT_TIMEOUT_MS, 24 * 60 * 60 * 1000);
195
- const pollIntervalMs = parsePositiveInt(flags["poll-interval"], DEFAULT_POLL_INTERVAL_MS, 120000);
196
- const deadline = Date.now() + timeoutMs;
197
- let latest = null;
198
- while (Date.now() < deadline) {
199
- latest = await fetchLatestRun(tenantId, replaySetId, enqueue.runId, profileOptions);
200
- const status = latest.run?.status ?? "";
201
- const runId = latest.run?.runId ?? "";
202
- if (TERMINAL_RUN_STATUSES.has(status) && runId === enqueue.runId) {
203
- break;
204
- }
205
- if (context.outputMode === "human") {
206
- process.stderr.write(`Waiting for replay run (${status || "pending"})...
207
- `);
208
- }
209
- await sleep(pollIntervalMs);
210
- }
211
- if (!latest?.run || latest.run.runId !== enqueue.runId || !TERMINAL_RUN_STATUSES.has(latest.run.status)) {
212
- throw new Error(`Timed out after ${timeoutMs}ms waiting for replay run ${enqueue.runId} to finish`);
213
- }
214
- const result = {
215
- replaySetId,
216
- runId: latest.run.runId,
217
- status: latest.run.status,
218
- verdict: formatVerdict(latest),
219
- run: { ...latest.run },
220
- cases: latest.cases.map((row) => ({ ...row }))
221
- };
222
- if (context.outputMode === "human") {
223
- printHumanVerdict(result);
224
- }
225
- context.output.writeData(result);
226
- }
227
- function resolvePromptArmSpec(source, label, opts = {}) {
228
- const trimmed = source.trim();
229
- if (!trimmed) {
230
- throw new Error(`Empty prompt source for ${label}`);
231
- }
232
- const fromDiscovery = () => {
233
- const discovered = discoverPrompts();
234
- const match = discovered.find((prompt) => prompt.key === trimmed) ?? discovered.find((prompt) => prompt.sourcePath === trimmed);
235
- return match ? { key: match.key, content: match.content } : null;
236
- };
237
- const absPath = isAbsolute(trimmed) ? trimmed : resolve(process.cwd(), trimmed);
238
- let key;
239
- let content;
240
- const preferred = opts.preferKey ? fromDiscovery() : null;
241
- if (preferred) {
242
- key = preferred.key;
243
- content = preferred.content;
244
- } else if (opts.preferKey) {
245
- throw new Error(`Could not resolve prompt key '${trimmed}' — no discovered prompt matched this key`);
246
- } else if (existsSync(absPath)) {
247
- const parsed = parsePromptFileForAb(absPath);
248
- key = parsed.key;
249
- content = parsed.content;
250
- } else {
251
- const match = fromDiscovery();
252
- if (!match) {
253
- throw new Error(`Could not resolve prompt '${trimmed}' — pass a .prompt.md path or a discovered prompt key`);
254
- }
255
- key = match.key;
256
- content = match.content;
257
- }
258
- const locked = readPromptLock()?.prompts[key];
259
- return {
260
- content,
261
- prompt_version_id: locked?.versionId ?? "",
262
- prompt_id: locked?.promptId ?? "",
263
- prompt_label: label
264
- };
265
- }
266
- async function ensureReplaySet(flags, tenantId, profileOptions) {
267
- const existingSetId = (flags["set-id"] || flags["replay-set-id"] || "").trim();
268
- if (existingSetId) {
269
- return existingSetId;
270
- }
271
- const conversations = parseCsv(flags.conversations ?? flags["conversation-ids"]);
272
- if (conversations.length) {
273
- return createSetFromConversations(conversations, flags, tenantId, profileOptions);
274
- }
275
- if (flags["auto-generate"] === "false" && !existingSetId) {
276
- throw new Error("Provide --set-id=, --conversations=, or allow --auto-generate (default)");
277
- }
278
- const caseCount = parsePositiveInt(flags.cases ?? flags["case-count"], 5, 25);
279
- const lookbackDays = parsePositiveInt(flags["lookback-days"], 30, 365);
280
- const name = (flags.name ?? flags["set-name"] ?? `Prompt A/B ${new Date().toISOString().slice(0, 10)}`).trim();
281
- const generated = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/auto-generate`, {
282
- method: "POST",
283
- body: JSON.stringify({ name, caseCount, lookbackDays })
284
- }, profileOptions, { timeoutMs: 300000, retries: 0 });
285
- if (!generated?.id) {
286
- throw new Error("Auto-generate did not return a replay set id");
287
- }
288
- return generated.id;
289
- }
290
- async function createSetFromConversations(conversationIds, flags, tenantId, profileOptions) {
291
- const name = (flags.name ?? flags["set-name"] ?? `Prompt A/B ${conversationIds.length} conv`).trim();
292
- const created = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets`, {
293
- method: "POST",
294
- body: JSON.stringify({ name, description: "Created by moda prompts ab" })
295
- }, profileOptions);
296
- let position = 0;
297
- for (const conversationId of conversationIds) {
298
- const scenario = await loadScenarioFromConversation(conversationId);
299
- await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(created.id)}/cases`, {
300
- method: "POST",
301
- body: JSON.stringify({
302
- title: conversationId,
303
- scenario,
304
- sourceConversationId: conversationId,
305
- successCriteria: [
306
- "The agent understands the user request",
307
- "The agent uses tools appropriately when needed",
308
- "The agent provides a helpful final answer"
309
- ],
310
- position: position++
311
- })
312
- }, profileOptions);
313
- }
314
- return created.id;
315
- }
316
- async function loadScenarioFromConversation(conversationId) {
317
- try {
318
- const data = await callDataAPI(`/conversations/${encodeURIComponent(conversationId)}/context?msg_index=0&window=0`);
319
- const firstUser = (data.messages ?? []).find((message) => message.role === "user" && String(message.content ?? "").trim());
320
- const opening = String(firstUser?.content ?? "").trim();
321
- if (opening) {
322
- return `The user says: "${opening}"
323
-
324
- Replay the conversation faithfully and help them accomplish their goal.`;
325
- }
326
- } catch {}
327
- return `Replay conversation ${conversationId}: help the user accomplish their original goal using the available tools.`;
328
- }
329
- async function fetchLatestRun(tenantId, replaySetId, runId, profileOptions) {
330
- return callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(replaySetId)}/runs/latest?runId=${encodeURIComponent(runId)}`, { method: "GET" }, profileOptions, { timeoutMs: 60000, retries: 2 });
331
- }
332
- function candidateOutcome(payload) {
333
- const run = payload.run;
334
- if (!run || run.status !== "completed")
335
- return "no-run";
336
- const delta = run.proposedPassCount - run.prodPassCount;
337
- if (delta > 0)
338
- return "candidate";
339
- if (delta < 0)
340
- return "baseline";
341
- return "tie";
342
- }
343
- function formatVerdict(payload) {
344
- const run = payload.run;
345
- if (!run)
346
- return "no run";
347
- if (run.status === "skipped")
348
- return `skipped: ${run.notes || "no reason"}`;
349
- if (run.status === "error")
350
- return `error: ${run.notes || "unknown error"}`;
351
- if (run.status !== "completed")
352
- return `status=${run.status}`;
353
- const delta = run.proposedPassCount - run.prodPassCount;
354
- const winner = delta > 0 ? "candidate wins" : delta < 0 ? "baseline wins" : "tie on case passes";
355
- const rates = `baseline ${run.prodPassCount}/${run.caseCount} (${pct(run.prodPassRate)}), candidate ${run.proposedPassCount}/${run.caseCount} (${pct(run.proposedPassRate)})`;
356
- const notes = run.notes?.trim();
357
- return `${winner}; ${rates}${notes ? `; ${notes}` : ""}`;
358
- }
359
- function printHumanVerdict(result) {
360
- process.stderr.write(`
361
- Prompt A/B replay verdict
362
- `);
363
- process.stderr.write(`-------------------------
364
- `);
365
- process.stderr.write(`Replay set: ${result.replaySetId}
366
- `);
367
- process.stderr.write(`Run id: ${result.runId}
368
- `);
369
- process.stderr.write(`Status: ${result.status}
370
- `);
371
- process.stderr.write(`Verdict: ${result.verdict}
372
- `);
373
- if (result.cases.length) {
374
- process.stderr.write(`
375
- Per-case:
376
- `);
377
- for (const row of result.cases) {
378
- const caseId = String(row.caseId ?? "");
379
- const prod = formatPass(row.prodPass);
380
- const proposed = formatPass(row.proposedPass);
381
- process.stderr.write(` ${caseId}: baseline=${prod} candidate=${proposed}
382
- `);
383
- }
384
- }
385
- process.stderr.write(`
386
- `);
387
- }
388
- function formatPass(value) {
389
- if (value === null || value === undefined)
390
- return "abstained";
391
- return value ? "pass" : "fail";
392
- }
393
- function pct(rate) {
394
- return `${Math.round(Math.max(0, Math.min(1, rate)) * 100)}%`;
395
- }
396
- async function enqueueAndPollComparison(args, profileOptions) {
397
- const enqueue = await callControlAPI(`/tenants/${encodeURIComponent(args.tenantId)}/replay-sets/${encodeURIComponent(args.replaySetId)}/run`, {
398
- method: "POST",
399
- body: JSON.stringify({
400
- promptArms: args.promptArms,
401
- seedsPerCase: args.seedsPerCase ?? DEFAULT_SEEDS_PER_CASE,
402
- assistantModel: args.assistantModel || undefined,
403
- promotePrimary: args.promotePrimary ?? false
404
- })
405
- }, profileOptions, { timeoutMs: 120000, retries: 1 });
406
- const timeoutMs = args.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
407
- const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
408
- const deadline = Date.now() + timeoutMs;
409
- let latest = null;
410
- while (Date.now() < deadline) {
411
- latest = await fetchLatestRun(args.tenantId, args.replaySetId, enqueue.runId, profileOptions);
412
- const status = latest.run?.status ?? "";
413
- const runId = latest.run?.runId ?? "";
414
- if (TERMINAL_RUN_STATUSES.has(status) && runId === enqueue.runId) {
415
- return latest;
416
- }
417
- args.onWaiting?.(status || "pending");
418
- await sleep(pollIntervalMs);
419
- }
420
- if (!latest?.run || latest.run.runId !== enqueue.runId || !TERMINAL_RUN_STATUSES.has(latest.run.status)) {
421
- throw new Error(`Timed out after ${timeoutMs}ms waiting for replay run ${enqueue.runId} to finish`);
422
- }
423
- return latest;
424
- }
425
- function parsePositiveInt(raw, fallback, max) {
426
- if (!raw)
427
- return fallback;
428
- const parsed = Number.parseInt(raw, 10);
429
- if (!Number.isFinite(parsed) || parsed < 1)
430
- return fallback;
431
- return Math.min(parsed, max);
432
- }
433
- function parseCsv(raw) {
434
- if (!raw?.trim())
435
- return [];
436
- return raw.split(",").map((part) => part.trim()).filter(Boolean);
437
- }
438
- function sleep(ms) {
439
- return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
440
- }
441
-
442
- // src/prompts-propose.ts
443
- import { writeFileSync } from "node:fs";
444
- import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
445
- var PROPOSE_TIMEOUT_MS = 300000;
446
- async function runPromptPropose(positionals, flags, profileOptions, context) {
447
- validateConfig();
448
- const promptKey = flags["prompt-key"] || positionals[1];
449
- const fromRunId = flags["from-run"] || flags["from-run-id"] || flags["run-id"];
450
- const replaySetId = flags["set-id"] || flags["replay-set-id"];
451
- if (!promptKey || !fromRunId || !replaySetId) {
452
- throw new Error("Usage: moda prompts propose --prompt-key=<key> --from-run=<run_id> --set-id=<replay_set_id> [--max-dossiers=8] [--model=<slug>] [--out=path.prompt.md] [--gate] [--promote-on-win]");
453
- }
454
- const maxDossiers = parsePositiveInt2(flags["max-dossiers"], 8, 16);
455
- const model = (flags.model ?? flags["revise-model"] ?? "").trim();
456
- const result = await callControlAPI(`/prompts/${encodeURIComponent(promptKey)}/propose`, {
457
- method: "POST",
458
- body: JSON.stringify({ fromRunId, replaySetId, maxDossiers, model: model || undefined })
459
- }, profileOptions, { timeoutMs: PROPOSE_TIMEOUT_MS, retries: 0 });
460
- let outPath;
461
- if (result.status === "proposed" && result.content && flags.out) {
462
- outPath = isAbsolute2(flags.out) ? flags.out : resolve2(process.cwd(), flags.out);
463
- writeFileSync(outPath, result.content, "utf8");
464
- }
465
- const wantGate = flags.gate === "true";
466
- const promoteOnWin = flags["promote-on-win"] === "true";
467
- let gate;
468
- if (wantGate) {
469
- if (result.status !== "proposed" || !result.content) {
470
- gate = {
471
- ran: false,
472
- scope: "full-set",
473
- scopeNote: `no candidate to gate (${result.status})`,
474
- verdict: "skipped",
475
- outcome: "no-run",
476
- runId: null,
477
- promoted: false
478
- };
479
- } else {
480
- gate = await runGate({
481
- result,
482
- promptKey,
483
- replaySetId,
484
- flags,
485
- promoteOnWin,
486
- profileOptions,
487
- context
488
- });
489
- }
490
- }
491
- if (context.outputMode === "human") {
492
- printHuman(result, outPath, gate);
493
- }
494
- context.output.writeData({
495
- ...result,
496
- ...outPath ? { outPath } : {},
497
- ...gate ? { gate } : {}
498
- });
499
- }
500
- async function runGate(args) {
501
- const { result, promptKey, replaySetId, flags, promoteOnWin, profileOptions, context } = args;
502
- const tenantId = flags["tenant-id"] || resolveTenantId(profileOptions);
503
- if (!tenantId) {
504
- throw new Error("Missing tenant id for --gate. Run `moda init` or pass --tenant-id=<id>.");
505
- }
506
- const prodArm = resolvePromptArmSpec(promptKey, "baseline", { preferKey: true });
507
- const proposedArm = {
508
- content: result.content ?? "",
509
- prompt_version_id: result.versionId ?? "",
510
- prompt_id: "",
511
- prompt_label: "candidate"
512
- };
513
- const seedsPerCase = parsePositiveInt2(flags.seeds ?? flags["seeds-per-case"], 3, 10);
514
- const assistantModel = (flags["assistant-model"] ?? "").trim();
515
- const timeoutMs = parsePositiveInt2(flags["gate-timeout"] ?? flags.timeout, 2 * 60 * 60 * 1000, 24 * 60 * 60 * 1000);
516
- const pollIntervalMs = parsePositiveInt2(flags["poll-interval"], 15000, 120000);
517
- const scopeNote = result.holdoutCaseIds.length ? `gated over the FULL replay set (${result.holdoutCaseIds.length} holdout case(s) could not be isolated — the run endpoint scores every case)` : "gated over the FULL replay set";
518
- if (context.outputMode === "human") {
519
- process.stderr.write(`
520
- Gating candidate for '${promptKey}' — ${scopeNote}...
521
- `);
522
- }
523
- const payload = await enqueueAndPollComparison({
524
- tenantId,
525
- replaySetId,
526
- promptArms: { prod: prodArm, proposed: proposedArm },
527
- seedsPerCase,
528
- assistantModel: assistantModel || undefined,
529
- promotePrimary: false,
530
- timeoutMs,
531
- pollIntervalMs,
532
- onWaiting: context.outputMode === "human" ? (status) => process.stderr.write(`Waiting for gate run (${status})...
533
- `) : undefined
534
- }, profileOptions);
535
- const outcome = candidateOutcome(payload);
536
- const gate = {
537
- ran: true,
538
- scope: "full-set",
539
- scopeNote,
540
- verdict: formatVerdict(payload),
541
- outcome,
542
- runId: payload.run?.runId ?? null,
543
- promoted: false
544
- };
545
- if (promoteOnWin) {
546
- if (outcome === "candidate" && result.versionId) {
547
- await callControlAPI(`/prompts/${encodeURIComponent(promptKey)}/labels`, { method: "POST", body: JSON.stringify({ label: "prod", versionId: result.versionId }) }, profileOptions);
548
- gate.promoted = true;
549
- } else {
550
- gate.promoteSkippedReason = outcome === "candidate" ? "no candidate versionId to promote" : `candidate did not strictly win (${outcome})`;
551
- }
552
- }
553
- return gate;
554
- }
555
- function printHuman(result, outPath, gate) {
556
- const w = (s) => process.stderr.write(s + `
557
- `);
558
- if (result.status !== "proposed") {
559
- w(`No candidate produced (${result.status}): ${result.note}`);
560
- return;
561
- }
562
- w(`Proposed candidate for '${result.promptKey}'`);
563
- w(` new version: ${result.versionId} (unlabeled — not promoted)`);
564
- w(` base version: ${result.baseVersionId ?? "(none)"}`);
565
- w(` from run: built from ${result.dossierCount} failure dossier(s)`);
566
- if (result.changelog)
567
- w(` changelog: ${result.changelog}`);
568
- if (result.risks.length)
569
- w(` risks: ${result.risks.map((r) => `- ${r}`).join(`
570
- `)}`);
571
- if (outPath)
572
- w(` written to: ${outPath}`);
573
- if (result.lowN) {
574
- w("");
575
- w(` ⚠ low sample: ${result.lowN}`);
576
- }
577
- w("");
578
- if (gate?.ran) {
579
- w("Gate (automatic A/B of candidate vs baseline):");
580
- w(` scope: ${gate.scopeNote}`);
581
- w(` verdict: ${gate.verdict}`);
582
- if (gate.runId)
583
- w(` run id: ${gate.runId}`);
584
- if (gate.promoted) {
585
- w(` promoted: candidate won — promoted version ${result.versionId} to label 'prod'`);
586
- } else if (gate.promoteSkippedReason) {
587
- w(` promote: skipped (${gate.promoteSkippedReason})`);
588
- }
589
- w("");
590
- if (!gate.promoted) {
591
- w(` moda prompts promote ${result.promptKey} --label=prod --version=${result.versionId}`);
592
- }
593
- return;
594
- }
595
- if (gate && !gate.ran) {
596
- w(`Gate not run: ${gate.scopeNote}`);
597
- w("");
598
- }
599
- w("Next — gate on holdout cases the revision never saw, then promote if it wins:");
600
- if (result.holdoutCaseIds.length) {
601
- w(` holdout cases (${result.holdoutCaseIds.length}): ${result.holdoutCaseIds.join(", ")}`);
602
- } else {
603
- w(" (too few failures to reserve a holdout — gate on a fresh --auto-generate set)");
604
- }
605
- if (outPath) {
606
- w(` moda prompts ab --baseline-key=${result.promptKey} --candidate=${outPath} --auto-generate`);
607
- } else {
608
- w(` # re-run with --out=<path.prompt.md> to materialize the candidate, then:`);
609
- w(` moda prompts ab --baseline-key=${result.promptKey} --candidate=<path.prompt.md> --auto-generate`);
610
- }
611
- w(` # or: moda prompts propose ... --gate --promote-on-win (auto-A/B, promote on a strict win)`);
612
- w(` moda prompts promote ${result.promptKey} --label=prod --version=${result.versionId}`);
613
- }
614
- function parsePositiveInt2(raw, fallback, max) {
615
- const n = Number.parseInt(raw ?? "", 10);
616
- if (!Number.isFinite(n) || n <= 0)
617
- return fallback;
618
- return Math.min(n, max);
619
- }
620
-
621
- // src/prompts.ts
622
- var MODA_DIR = ".moda";
623
- var MANIFEST_PATH = join(MODA_DIR, "prompts.yml");
624
- var LOCK_PATH = join(MODA_DIR, "prompts.lock.json");
625
- var DEFAULT_PROMPT_PATHS = [
626
- "prompts/**/*.prompt.md",
627
- "prompts/**/*.prompt.json",
628
- "prompts/**/*.prompt.yaml",
629
- "prompts/**/*.prompt.yml"
630
- ];
631
- async function runPromptsCommand(input, legacyFlags, legacyProfileOptions) {
632
- const context = Array.isArray(input) ? createCommandContext({
633
- command: "prompts",
634
- positional: input[0],
635
- positionals: input,
636
- flags: legacyFlags ?? {}
637
- }, {
638
- commandFlags: legacyFlags ?? {},
639
- outputMode: legacyFlags?.agent === "true" ? "agent" : legacyFlags?.json === "true" ? "json" : "human",
640
- env: legacyProfileOptions?.env
641
- }) : input;
642
- const positionals = context.positionals;
643
- const flags = context.flags;
644
- const profileOptions = legacyProfileOptions ?? { profile: context.profile, env: context.env };
645
- const subcommand = positionals[0] || "status";
646
- switch (subcommand) {
647
- case "init":
648
- await initPrompts({ silent: true });
649
- context.output.writeData({
650
- manifest: MANIFEST_PATH,
651
- lockfile: LOCK_PATH,
652
- promptPaths: readManifest().promptPaths
653
- });
654
- break;
655
- case "status":
656
- context.output.writeData(await getPromptStatus());
657
- break;
658
- case "diff":
659
- context.output.writeData(await getPromptStatus());
660
- break;
661
- case "sync":
662
- await syncPrompts(flags, profileOptions, context);
663
- break;
664
- case "promote":
665
- await promotePrompt(positionals, flags, profileOptions, context);
666
- break;
667
- case "ab":
668
- await runPromptAb(flags, profileOptions, context);
669
- break;
670
- case "propose":
671
- await runPromptPropose(positionals, flags, profileOptions, context);
672
- break;
673
- default:
674
- throw new Error(`Unknown prompts command '${subcommand}'. Use init, status, diff, sync, promote, ab, or propose.`);
675
- }
676
- }
677
- async function initPrompts(options = {}) {
678
- mkdirSync(MODA_DIR, { recursive: true });
679
- if (!existsSync2(MANIFEST_PATH)) {
680
- writeFileSync2(MANIFEST_PATH, [
681
- "version: 1",
682
- "prompt_paths:",
683
- ...DEFAULT_PROMPT_PATHS.map((path) => ` - "${path}"`),
684
- ""
685
- ].join(`
686
- `));
687
- }
688
- if (!options.silent) {
689
- printJson({
690
- manifest: MANIFEST_PATH,
691
- lockfile: LOCK_PATH,
692
- promptPaths: readManifest().promptPaths
693
- });
694
- }
695
- }
696
- async function syncPrompts(flags, profileOptions, context) {
697
- if (flags.watch === "true") {
698
- await watchPromptSync(flags, profileOptions);
699
- return;
700
- }
701
- await syncPromptsOnce(flags, profileOptions, context);
702
- }
703
- async function runPromptSync(flags, profileOptions) {
704
- await initPrompts({ silent: true });
705
- const prompts = discoverPrompts();
706
- const sourceCommit = getGitCommit();
707
- const manifestHash = hashFile(MANIFEST_PATH);
708
- const dryRun = flags["dry-run"] === "true";
709
- const response = await callControlAPI("/prompts/sync", {
710
- method: "POST",
711
- body: JSON.stringify({
712
- prompts: prompts.map((prompt) => ({
713
- ...prompt,
714
- sourceCommit,
715
- manifestHash
716
- })),
717
- sourceCommit,
718
- manifestHash,
719
- dryRun
720
- })
721
- }, profileOptions);
722
- if (!dryRun) {
723
- writeLock(prompts, response.synced ?? []);
724
- }
725
- return { synced: response.synced ?? [], dryRun };
726
- }
727
- async function syncPromptsOnce(flags, profileOptions, context) {
728
- const result = await runPromptSync(flags, profileOptions);
729
- const payload = {
730
- synced: result.synced,
731
- lockfile: result.dryRun ? undefined : LOCK_PATH
732
- };
733
- if (context)
734
- context.output.writeData(payload);
735
- else
736
- printJson(payload);
737
- }
738
- async function watchPromptSync(flags, profileOptions) {
739
- console.error("Watching prompt files. Press Ctrl+C to stop.");
740
- let lastSignature = "";
741
- const intervalMs = Math.max(1000, Number(flags.interval ?? 2500));
742
- while (true) {
743
- const status = await getPromptStatus();
744
- const signature = stableJson(status.prompts);
745
- if (signature !== lastSignature) {
746
- lastSignature = signature;
747
- if (status.changed > 0 || status.new > 0 || status.deleted > 0) {
748
- await syncPromptsOnce({ ...flags, watch: "false" }, profileOptions);
749
- }
750
- }
751
- await new Promise((resolve4) => setTimeout(resolve4, intervalMs));
752
- }
753
- }
754
- async function promotePrompt(positionals, flags, profileOptions, context) {
755
- const key = positionals[1];
756
- const label = flags.label;
757
- const versionId = flags.version || flags["version-id"];
758
- if (!key)
759
- throw new Error("Usage: moda prompts promote <prompt_key> --label=prod --version=<version_id>");
760
- if (!label)
761
- throw new Error("Missing required flag: --label=dev|staging|prod");
762
- if (!versionId)
763
- throw new Error("Missing required flag: --version=<version_id>");
764
- const response = await callControlAPI(`/prompts/${encodeURIComponent(key)}/labels`, {
765
- method: "POST",
766
- body: JSON.stringify({ label, versionId })
767
- }, profileOptions);
768
- context.output.writeData(response);
769
- }
770
- async function getPromptStatus() {
771
- await initPrompts({ silent: true });
772
- const prompts = discoverPrompts();
773
- const lock = readLock();
774
- const rows = prompts.map((prompt) => {
775
- const locked = lock?.prompts[prompt.key];
776
- const state = !locked ? "new" : locked.contentHash === prompt.contentHash ? "unchanged" : "changed";
777
- return {
778
- key: prompt.key,
779
- sourcePath: prompt.sourcePath,
780
- contentHash: prompt.contentHash,
781
- lockedHash: locked?.contentHash ?? null,
782
- versionId: locked?.versionId ?? null,
783
- state
784
- };
785
- });
786
- const discoveredKeys = new Set(prompts.map((prompt) => prompt.key));
787
- for (const [key, locked] of Object.entries(lock?.prompts ?? {})) {
788
- if (!discoveredKeys.has(key)) {
789
- rows.push({
790
- key,
791
- sourcePath: locked.sourcePath,
792
- contentHash: null,
793
- lockedHash: locked.contentHash,
794
- versionId: locked.versionId ?? null,
795
- state: "deleted"
796
- });
797
- }
798
- }
799
- return {
800
- manifest: MANIFEST_PATH,
801
- lockfile: LOCK_PATH,
802
- total: rows.length,
803
- changed: rows.filter((row) => row.state === "changed").length,
804
- new: rows.filter((row) => row.state === "new").length,
805
- deleted: rows.filter((row) => row.state === "deleted").length,
806
- prompts: rows
807
- };
808
- }
809
- function discoverPrompts() {
810
- const manifest = readManifest();
811
- const rules = manifest.promptPaths.map((pattern) => {
812
- const suffixMatch = pattern.match(/\.prompt\.(md|json|ya?ml)$/i);
813
- if (!suffixMatch)
814
- return null;
815
- const suffix = `.prompt.${suffixMatch[1]}`;
816
- const globIndex = pattern.search(/[*?[]/);
817
- const baseRaw = globIndex === -1 ? dirname(pattern) : pattern.slice(0, globIndex);
818
- const base = baseRaw.replace(/[\\/]+$/, "") || ".";
819
- const baseAbs = isAbsolute3(base) ? base : resolve3(process.cwd(), base);
820
- return { baseAbs, suffix };
821
- }).filter((rule) => Boolean(rule));
822
- const effectiveRules = rules.length ? rules : [
823
- { baseAbs: process.cwd(), suffix: ".prompt.md" },
824
- { baseAbs: process.cwd(), suffix: ".prompt.json" }
825
- ];
826
- const discovered = new Set;
827
- for (const { baseAbs, suffix } of effectiveRules) {
828
- if (!existsSync2(baseAbs))
829
- continue;
830
- for (const file of walk(baseAbs)) {
831
- if (file.endsWith(suffix))
832
- discovered.add(file);
833
- }
834
- }
835
- return [...discovered].sort().map(parsePromptFile);
836
- }
837
- function parsePromptFile(absPath) {
838
- const sourcePath = relative(process.cwd(), absPath);
839
- const raw = readFileSync(absPath, "utf8");
840
- if (absPath.endsWith(".json")) {
841
- const parsed = JSON.parse(raw);
842
- const key2 = parsed.key || keyFromPath(sourcePath);
843
- const content = parsed.content || JSON.stringify(parsed.messages ?? [], null, 2);
844
- return normalizePrompt({ ...parsed, key: key2, name: parsed.name || key2, sourcePath, content });
845
- }
846
- const { frontmatter, body } = parseFrontmatter(raw);
847
- const key = frontmatter.key || keyFromPath(sourcePath);
848
- return normalizePrompt({
849
- key,
850
- name: frontmatter.name || key,
851
- description: frontmatter.description,
852
- category: frontmatter.category,
853
- systemPrompt: frontmatter.system_prompt || frontmatter.systemPrompt,
854
- modelConfig: frontmatter.model ? { model: frontmatter.model } : undefined,
855
- sourcePath,
856
- content: body.trim()
857
- });
858
- }
859
- function normalizePrompt(prompt) {
860
- const normalized = stableJson({
861
- key: prompt.key,
862
- content: prompt.content,
863
- systemPrompt: prompt.systemPrompt ?? null,
864
- messages: prompt.messages ?? [],
865
- variables: prompt.variables ?? [],
866
- variablesSchema: prompt.variablesSchema ?? {},
867
- modelConfig: prompt.modelConfig ?? {},
868
- toolRefs: prompt.toolRefs ?? [],
869
- responseSchema: prompt.responseSchema ?? null
870
- });
871
- return {
872
- key: prompt.key,
873
- name: prompt.name || prompt.key,
874
- description: prompt.description,
875
- sourcePath: prompt.sourcePath,
876
- content: prompt.content,
877
- systemPrompt: prompt.systemPrompt,
878
- messages: prompt.messages,
879
- variables: prompt.variables,
880
- variablesSchema: prompt.variablesSchema,
881
- modelConfig: prompt.modelConfig,
882
- toolRefs: prompt.toolRefs,
883
- responseSchema: prompt.responseSchema,
884
- contentHash: sha256(normalized)
885
- };
886
- }
887
- function readManifest() {
888
- if (!existsSync2(MANIFEST_PATH)) {
889
- return { promptPaths: DEFAULT_PROMPT_PATHS };
890
- }
891
- const text = readFileSync(MANIFEST_PATH, "utf8");
892
- const promptPaths = [...text.matchAll(/^\s*-\s*["']?([^"'\n]+)["']?\s*$/gm)].map((match) => match[1]).filter((path) => path.includes(".prompt."));
893
- return { promptPaths: promptPaths.length ? promptPaths : DEFAULT_PROMPT_PATHS };
894
- }
895
- function readPromptLock() {
896
- return readLock();
897
- }
898
- function parsePromptFileForAb(absPath) {
899
- const parsed = parsePromptFile(absPath);
900
- return { key: parsed.key, content: parsed.content };
901
- }
902
- function readLock() {
903
- if (!existsSync2(LOCK_PATH))
904
- return null;
905
- return JSON.parse(readFileSync(LOCK_PATH, "utf8"));
906
- }
907
- function writeLock(prompts, synced) {
908
- const syncedByKey = new Map(synced.map((item) => [item.key, item]));
909
- const lock = {
910
- version: 1,
911
- generatedAt: new Date().toISOString(),
912
- prompts: Object.fromEntries(prompts.map((prompt) => {
913
- const result = syncedByKey.get(prompt.key);
914
- return [prompt.key, {
915
- key: prompt.key,
916
- sourcePath: prompt.sourcePath,
917
- contentHash: prompt.contentHash,
918
- promptId: result?.promptId,
919
- versionId: result?.versionId
920
- }];
921
- }))
922
- };
923
- mkdirSync(dirname(LOCK_PATH), { recursive: true });
924
- writeFileSync2(LOCK_PATH, `${JSON.stringify(lock, null, 2)}
925
- `);
926
- }
927
- function walk(dir) {
928
- let entries;
929
- try {
930
- entries = readdirSync(dir);
931
- } catch {
932
- return [];
933
- }
934
- const files = [];
935
- for (const entry of entries) {
936
- if (entry === "node_modules" || entry === ".git" || entry === "dist" || entry === "build")
937
- continue;
938
- const fullPath = join(dir, entry);
939
- let stat;
940
- try {
941
- stat = lstatSync(fullPath);
942
- } catch {
943
- continue;
944
- }
945
- if (stat.isSymbolicLink())
946
- continue;
947
- if (stat.isDirectory())
948
- files.push(...walk(fullPath));
949
- else if (stat.isFile())
950
- files.push(fullPath);
951
- }
952
- return files;
953
- }
954
- function parseFrontmatter(raw) {
955
- if (!raw.startsWith("---"))
956
- return { frontmatter: {}, body: raw };
957
- const end = raw.indexOf(`
958
- ---`, 3);
959
- if (end === -1)
960
- return { frontmatter: {}, body: raw };
961
- const frontmatterText = raw.slice(3, end).trim();
962
- const frontmatter = Object.fromEntries(frontmatterText.split(`
963
- `).map((line) => line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/)).filter((match) => Boolean(match)).map((match) => [match[1], match[2].replace(/^["']|["']$/g, "")]));
964
- return { frontmatter, body: raw.slice(end + 4) };
965
- }
966
- function keyFromPath(path) {
967
- return path.replace(/^prompts\//, "").replace(/\.prompt\.(md|json|ya?ml)$/i, "").replace(/[\\/]/g, ".");
968
- }
969
- function hashFile(path) {
970
- return existsSync2(path) ? sha256(readFileSync(path, "utf8")) : "";
971
- }
972
- function getGitCommit() {
973
- try {
974
- return execFileSync("git", ["rev-parse", "HEAD"], {
975
- encoding: "utf8",
976
- stdio: ["ignore", "pipe", "ignore"]
977
- }).trim();
978
- } catch {
979
- return;
980
- }
981
- }
982
- function stableJson(value) {
983
- return JSON.stringify(sortJson(value));
984
- }
985
- function sortJson(value) {
986
- if (Array.isArray(value))
987
- return value.map(sortJson);
988
- if (value && typeof value === "object") {
989
- return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson(item)]));
990
- }
991
- return value;
992
- }
993
- function sha256(input) {
994
- return createHash("sha256").update(input).digest("hex");
995
- }
996
- function printJson(value) {
997
- console.log(JSON.stringify(value, null, 2));
998
- }
999
-
1000
- // src/init/rules.ts
1001
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
1002
- import { dirname as dirname2, join as join2 } from "node:path";
1003
- import { fileURLToPath } from "node:url";
1004
- import { confirm } from "@inquirer/prompts";
1005
- var PACKAGE_ROOT = join2(dirname2(fileURLToPath(import.meta.url)), "..", "..");
1006
- var SKILLS_SOURCE_DIR = join2(PACKAGE_ROOT, "skills");
1007
- var SKILLS = [
1008
- {
1009
- id: "moda-cli",
1010
- label: "moda-cli — let coding agents query Moda analytics via the CLI"
1011
- }
1012
- ];
1013
- var ADAPTERS = {
1014
- "Claude Code": {
1015
- name: "Claude Code",
1016
- targetFor: (skillId) => ({
1017
- dir: join2(".claude", "skills", skillId),
1018
- filename: "SKILL.md"
1019
- }),
1020
- transform: (content) => content
1021
- },
1022
- Cursor: {
1023
- name: "Cursor",
1024
- targetFor: (skillId) => ({
1025
- dir: join2(".cursor", "rules"),
1026
- filename: `${skillId}.mdc`
1027
- }),
1028
- transform: claudeSkillToCursorRule
1029
- }
1030
- };
1031
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
1032
- var DESCRIPTION_RE = /^description:\s*(.+?)\s*$/m;
1033
- function claudeSkillToCursorRule(skillContent) {
1034
- const match = FRONTMATTER_RE.exec(skillContent);
1035
- if (!match) {
1036
- return skillContent;
1037
- }
1038
- const [, frontmatter, body] = match;
1039
- const descMatch = DESCRIPTION_RE.exec(frontmatter);
1040
- const description = descMatch?.[1].trim() ?? "";
1041
- const cursorFrontmatter = `---
1042
- ` + `description: ${JSON.stringify(description)}
1043
- ` + `alwaysApply: false
1044
- ` + `---
1045
- `;
1046
- return `${cursorFrontmatter}
1047
- ${body.replace(/^\s+/, "")}`;
1048
- }
1049
- function loadSkillSource(skillId) {
1050
- const path = join2(SKILLS_SOURCE_DIR, skillId, "SKILL.md");
1051
- if (!existsSync3(path)) {
1052
- throw new Error(`Skill source missing: ${path}`);
1053
- }
1054
- return readFileSync2(path, "utf8");
1055
- }
1056
- function installSkillForAgent(skill, adapter, content) {
1057
- const { dir, filename } = adapter.targetFor(skill.id);
1058
- const dirPath = join2(process.cwd(), dir);
1059
- const filePath = join2(dirPath, filename);
1060
- const relativePath = join2(dir, filename);
1061
- try {
1062
- mkdirSync2(dirPath, { recursive: true });
1063
- if (existsSync3(filePath)) {
1064
- return { agent: adapter.name, relativePath, status: "exists" };
1065
- }
1066
- writeFileSync3(filePath, adapter.transform(content));
1067
- return { agent: adapter.name, relativePath, status: "wrote" };
1068
- } catch (err) {
1069
- return {
1070
- agent: adapter.name,
1071
- relativePath,
1072
- status: "failed",
1073
- error: err instanceof Error ? err.message : String(err)
1074
- };
1075
- }
1076
- }
1077
- async function installRules(detectedAgents, options = {}) {
1078
- const adapters = detectedAgents.map((a) => ADAPTERS[a]).filter((a) => a !== undefined);
1079
- if (adapters.length === 0) {
1080
- if (!options.silent)
1081
- console.log(" No supported coding agents detected");
1082
- return;
1083
- }
1084
- const agentList = adapters.map((a) => a.name).join(", ");
1085
- const skillList = SKILLS.map((s) => `
1086
- • ${s.label}`).join("");
1087
- const shouldInstall = options.yes ? true : await confirm({
1088
- message: `Install Moda skills for ${agentList}?${skillList}
1089
- Continue?`,
1090
- default: true
1091
- });
1092
- if (!shouldInstall) {
1093
- if (!options.silent)
1094
- console.log(" Skipped");
1095
- return;
1096
- }
1097
- let wrote = 0;
1098
- let skipped = 0;
1099
- let failed = 0;
1100
- for (const skill of SKILLS) {
1101
- let content;
1102
- try {
1103
- content = loadSkillSource(skill.id);
1104
- } catch (err) {
1105
- const message = err instanceof Error ? err.message : String(err);
1106
- console.error(` Warning: ${message}`);
1107
- failed += adapters.length;
1108
- continue;
1109
- }
1110
- for (const adapter of adapters) {
1111
- const outcome = installSkillForAgent(skill, adapter, content);
1112
- switch (outcome.status) {
1113
- case "wrote":
1114
- if (!options.silent)
1115
- console.log(` ${outcome.agent}: wrote ${outcome.relativePath}`);
1116
- wrote++;
1117
- break;
1118
- case "exists":
1119
- if (!options.silent) {
1120
- console.log(` ${outcome.agent}: ${outcome.relativePath} already exists — skipping`);
1121
- }
1122
- skipped++;
1123
- break;
1124
- case "failed":
1125
- if (!options.silent) {
1126
- console.error(` ${outcome.agent}: failed to write ${outcome.relativePath} — ${outcome.error}`);
1127
- }
1128
- failed++;
1129
- break;
1130
- }
1131
- }
1132
- }
1133
- const summary = [
1134
- wrote && `${wrote} written`,
1135
- skipped && `${skipped} already present`,
1136
- failed && `${failed} failed`
1137
- ].filter(Boolean).join(", ");
1138
- if (summary) {
1139
- if (!options.silent)
1140
- console.log(` Done — ${summary}`);
1141
- }
1142
- }
1143
-
1144
- // src/init/mcp.ts
1145
- import { existsSync as existsSync4 } from "node:fs";
1146
- import { join as join3 } from "node:path";
1147
- import { execSync } from "node:child_process";
1148
- function isOnPath(cmd) {
1149
- try {
1150
- execSync(`which ${cmd}`, { stdio: "pipe" });
1151
- return true;
1152
- } catch {
1153
- return false;
1154
- }
1155
- }
1156
- function detectAgents() {
1157
- const cwd = process.cwd();
1158
- const agents = [];
1159
- if (existsSync4(join3(cwd, ".claude")) || isOnPath("claude")) {
1160
- agents.push("Claude Code");
1161
- }
1162
- if (existsSync4(join3(cwd, ".cursor"))) {
1163
- agents.push("Cursor");
1164
- }
1165
- return agents;
1166
- }
1167
-
1168
- // src/skills.ts
1169
- import { execFileSync as execFileSync2 } from "node:child_process";
1170
- import { createHash as createHash2 } from "node:crypto";
1171
- import {
1172
- existsSync as existsSync6,
1173
- lstatSync as lstatSync2,
1174
- mkdirSync as mkdirSync4,
1175
- readdirSync as readdirSync3,
1176
- readFileSync as readFileSync4,
1177
- writeFileSync as writeFileSync5
1178
- } from "node:fs";
1179
- import { basename, dirname as dirname4, join as join5, relative as relative3 } from "node:path";
1180
-
1181
- // src/skills-manifest.ts
1182
- import {
1183
- existsSync as existsSync5,
1184
- mkdirSync as mkdirSync3,
1185
- readdirSync as readdirSync2,
1186
- readFileSync as readFileSync3,
1187
- rmdirSync,
1188
- unlinkSync,
1189
- writeFileSync as writeFileSync4
1190
- } from "node:fs";
1191
- import { dirname as dirname3, isAbsolute as isAbsolute4, join as join4, normalize, relative as relative2, resolve as resolve4 } from "node:path";
1192
- var SKILLS_MANIFEST_PATH = join4(".moda", "skills.yml");
1193
- var SKILL_SOURCE_HARNESS = "moda";
1194
- var SKILL_SOURCE_INTEGRATION_BUNDLED = "integration-bundled";
1195
- function readSkillsManifest(cwd) {
1196
- const manifestPath = join4(cwd, SKILLS_MANIFEST_PATH);
1197
- if (!existsSync5(manifestPath))
1198
- return null;
1199
- let text = "";
1200
- try {
1201
- text = readFileSync3(manifestPath, "utf8");
1202
- } catch {
1203
- return null;
1204
- }
1205
- return {
1206
- tenantId: readYamlScalar(text, "tenant_id"),
1207
- generationRunId: readYamlScalar(text, "generation_run_id"),
1208
- generationVersion: Number(readYamlScalar(text, "generation_version") || 0),
1209
- releaseKey: readYamlScalar(text, "release_key"),
1210
- statusFilter: readYamlScalar(text, "status_filter"),
1211
- releasedAt: readYamlScalar(text, "released_at"),
1212
- managedPaths: readManagedPaths(text),
1213
- records: readSkillRecords(text)
1214
- };
1215
- }
1216
- function manifestHeaderFrom(manifest) {
1217
- return {
1218
- tenantId: manifest?.tenantId ?? "",
1219
- releaseKey: manifest?.releaseKey ?? "",
1220
- statusFilter: manifest?.statusFilter ?? "",
1221
- generationRunId: manifest?.generationRunId ?? "",
1222
- generationVersion: manifest?.generationVersion ?? 0,
1223
- releasedAt: manifest?.releasedAt ?? ""
1224
- };
1225
- }
1226
- function writeSkillsManifest(cwd, header, records) {
1227
- const manifestPath = join4(cwd, SKILLS_MANIFEST_PATH);
1228
- const managedPaths = [...new Set(records.flatMap((record) => record.paths))].sort();
1229
- const lines = [
1230
- "version: 1",
1231
- "source: moda",
1232
- `tenant_id: ${yamlQuote(header.tenantId)}`,
1233
- `release_key: ${yamlQuote(header.releaseKey)}`,
1234
- `status_filter: ${yamlQuote(header.statusFilter)}`,
1235
- `generation_run_id: ${yamlQuote(header.generationRunId)}`,
1236
- `generation_version: ${header.generationVersion}`,
1237
- `released_at: ${yamlQuote(header.releasedAt)}`,
1238
- `installed_at: ${yamlQuote(new Date().toISOString())}`,
1239
- "managed_paths:",
1240
- ...managedPaths.map((path) => ` - ${yamlQuote(path)}`),
1241
- "skills:",
1242
- ...records.flatMap((record) => [
1243
- ` - skill_id: ${yamlQuote(record.skillId)}`,
1244
- ` content_hash: ${yamlQuote(record.contentHash)}`,
1245
- ` source: ${yamlQuote(record.source)}`,
1246
- " paths:",
1247
- ...record.paths.map((path) => ` - ${yamlQuote(path)}`)
1248
- ]),
1249
- ""
1250
- ];
1251
- mkdirSync3(dirname3(manifestPath), { recursive: true });
1252
- writeFileSync4(manifestPath, lines.join(`
1253
- `));
1254
- }
1255
- function upsertSkillManifestRecord(cwd, record) {
1256
- const manifest = readSkillsManifest(cwd);
1257
- const records = (manifest?.records ?? []).filter((existing) => !(existing.skillId === record.skillId && existing.source === record.source));
1258
- records.push(record);
1259
- writeSkillsManifest(cwd, manifestHeaderFrom(manifest), records);
1260
- }
1261
- function pruneManagedSkillPaths(cwd, managedPaths, log) {
1262
- let pruned = 0;
1263
- for (const rawPath of [...new Set(managedPaths)]) {
1264
- const safePath = safeManagedRelativePath(rawPath);
1265
- if (!safePath)
1266
- continue;
1267
- const absolutePath = absoluteManagedPath(cwd, safePath);
1268
- if (!absolutePath || !existsSync5(absolutePath))
1269
- continue;
1270
- try {
1271
- unlinkSync(absolutePath);
1272
- pruned++;
1273
- log(` pruned ${safePath}`);
1274
- pruneEmptyClaudeSkillDir(cwd, safePath);
1275
- } catch {}
1276
- }
1277
- return pruned;
1278
- }
1279
- function pruneEmptyClaudeSkillDir(cwd, relativePath) {
1280
- if (!relativePath.startsWith(".claude/skills/") || !relativePath.endsWith("/SKILL.md"))
1281
- return;
1282
- const dir = dirname3(join4(cwd, relativePath));
1283
- try {
1284
- if (existsSync5(dir) && readdirSync2(dir).length === 0) {
1285
- rmdirSync(dir);
1286
- }
1287
- } catch {}
1288
- }
1289
- function absoluteManagedPath(cwd, relativePath) {
1290
- const abs = resolve4(cwd, relativePath);
1291
- const root = resolve4(cwd);
1292
- const rel = relative2(root, abs);
1293
- if (rel.startsWith("..") || isAbsolute4(rel))
1294
- return null;
1295
- return abs;
1296
- }
1297
- function safeManagedRelativePath(rawPath) {
1298
- if (!rawPath || rawPath.includes("\x00") || isAbsolute4(rawPath))
1299
- return null;
1300
- const normalized = normalize(rawPath).replace(/\\/g, "/");
1301
- if (normalized.startsWith("../") || normalized === ".." || normalized.includes("/../"))
1302
- return null;
1303
- const isClaudeSkill = normalized.startsWith(".claude/skills/") && normalized.endsWith("/SKILL.md");
1304
- const isCursorRule = normalized.startsWith(".cursor/rules/") && normalized.endsWith(".mdc");
1305
- return isClaudeSkill || isCursorRule ? normalized : null;
1306
- }
1307
- function readYamlScalar(text, key) {
1308
- const match = text.match(new RegExp(`^${key}:\\s*(.*?)\\s*$`, "m"));
1309
- return match ? parseYamlScalar(match[1]) : "";
1310
- }
1311
- function readSkillRecords(text) {
1312
- const records = [];
1313
- let inSkills = false;
1314
- let current = null;
1315
- let inPaths = false;
1316
- for (const line of text.split(/\r?\n/)) {
1317
- if (line.trim() === "skills:") {
1318
- inSkills = true;
1319
- continue;
1320
- }
1321
- if (!inSkills)
1322
- continue;
1323
- if (line.length > 0 && !line.startsWith(" "))
1324
- break;
1325
- const skillIdMatch = line.match(/^ {2}- skill_id:\s*(.*?)\s*$/);
1326
- if (skillIdMatch) {
1327
- if (current)
1328
- records.push(current);
1329
- current = {
1330
- skillId: parseYamlScalar(skillIdMatch[1]),
1331
- contentHash: "",
1332
- source: SKILL_SOURCE_HARNESS,
1333
- paths: []
1334
- };
1335
- inPaths = false;
1336
- continue;
1337
- }
1338
- if (!current)
1339
- continue;
1340
- const hashMatch = line.match(/^ {4}content_hash:\s*(.*?)\s*$/);
1341
- if (hashMatch) {
1342
- current.contentHash = parseYamlScalar(hashMatch[1]);
1343
- continue;
1344
- }
1345
- const sourceMatch = line.match(/^ {4}source:\s*(.*?)\s*$/);
1346
- if (sourceMatch) {
1347
- current.source = parseYamlScalar(sourceMatch[1]) || SKILL_SOURCE_HARNESS;
1348
- continue;
1349
- }
1350
- if (/^ {4}paths:\s*$/.test(line)) {
1351
- inPaths = true;
1352
- continue;
1353
- }
1354
- const pathMatch = inPaths ? line.match(/^ {6}-\s*(.*?)\s*$/) : null;
1355
- if (pathMatch) {
1356
- const value = parseYamlScalar(pathMatch[1]);
1357
- if (value)
1358
- current.paths.push(value);
1359
- }
1360
- }
1361
- if (current)
1362
- records.push(current);
1363
- return records;
1364
- }
1365
- function readManagedPaths(text) {
1366
- const paths = [];
1367
- let inManagedPaths = false;
1368
- for (const line of text.split(/\r?\n/)) {
1369
- if (line.trim() === "managed_paths:") {
1370
- inManagedPaths = true;
1371
- continue;
1372
- }
1373
- if (inManagedPaths && line.length > 0 && !line.startsWith(" "))
1374
- break;
1375
- if (!inManagedPaths)
1376
- continue;
1377
- const match = line.match(/^\s*-\s*(.*?)\s*$/);
1378
- if (match)
1379
- paths.push(parseYamlScalar(match[1]));
1380
- }
1381
- return paths.filter(Boolean);
1382
- }
1383
- function parseYamlScalar(raw) {
1384
- const value = raw.trim();
1385
- if (!value)
1386
- return "";
1387
- if (value.startsWith('"')) {
1388
- try {
1389
- return String(JSON.parse(value));
1390
- } catch {
1391
- return value.replace(/^"|"$/g, "");
1392
- }
1393
- }
1394
- return value.replace(/^'|'$/g, "");
1395
- }
1396
- function yamlQuote(value) {
1397
- return JSON.stringify(value);
1398
- }
1399
-
1400
- // src/skills.ts
1401
- var SKILLS_REQUEST_TIMEOUT_MS = 15000;
1402
- var VALID_STATUSES = new Set(["approved", "proposed", "all"]);
1403
- var SKILL_PROPOSALS_MANIFEST_PATH = join5(".moda", "skill-proposals.yml");
1404
- async function fetchSkills(status) {
1405
- const apiKey = resolveApiKey();
1406
- if (!apiKey) {
1407
- throw new CliInputError("No Moda API key found. Run `moda init` first (or set MODA_API_KEY).");
1408
- }
1409
- const ingestUrl = resolveIngestUrl().replace(/\/$/, "");
1410
- const url = `${ingestUrl}/v1/skills?status=${encodeURIComponent(status)}`;
1411
- const controller = new AbortController;
1412
- const timeout = setTimeout(() => controller.abort(), SKILLS_REQUEST_TIMEOUT_MS);
1413
- let response;
1414
- try {
1415
- response = await fetch(url, {
1416
- headers: { Authorization: `Bearer ${apiKey}` },
1417
- signal: controller.signal
1418
- });
1419
- } catch (err) {
1420
- const message = err instanceof Error ? err.message : String(err);
1421
- throw new Error(`Network error: failed to reach ${ingestUrl} (${message})`);
1422
- } finally {
1423
- clearTimeout(timeout);
1424
- }
1425
- if (!response.ok) {
1426
- const body = await response.text().catch(() => "");
1427
- throw new Error(`Failed to fetch skills (HTTP ${response.status}): ${body.slice(0, 200)}`);
1428
- }
1429
- const data = await response.json();
1430
- if (!data.success) {
1431
- throw new Error(data.message || "Failed to fetch skills");
1432
- }
1433
- const skills = Array.isArray(data.skills) ? data.skills : [];
1434
- const release = data.release && typeof data.release === "object" ? data.release : null;
1435
- return {
1436
- skills,
1437
- tenantId: data.tenant_id || resolveTenantId() || "",
1438
- release,
1439
- releaseKey: releaseKeyFor(release, skills, status),
1440
- status
1441
- };
1442
- }
1443
- async function skillRunsRequest(url) {
1444
- const apiKey = resolveApiKey();
1445
- if (!apiKey) {
1446
- throw new CliInputError("No Moda API key found. Run `moda init` first (or set MODA_API_KEY).");
1447
- }
1448
- const controller = new AbortController;
1449
- const timeout = setTimeout(() => controller.abort(), SKILLS_REQUEST_TIMEOUT_MS);
1450
- let response;
1451
- try {
1452
- response = await fetch(url, {
1453
- headers: { Authorization: `Bearer ${apiKey}` },
1454
- signal: controller.signal
1455
- });
1456
- } catch (err) {
1457
- const message = err instanceof Error ? err.message : String(err);
1458
- throw new Error(`Network error: failed to reach ${url} (${message})`);
1459
- } finally {
1460
- clearTimeout(timeout);
1461
- }
1462
- const text = await response.text().catch(() => "");
1463
- if (!response.ok && response.status !== 404) {
1464
- throw new Error(`Failed to fetch run status (HTTP ${response.status}): ${text.slice(0, 200)}`);
1465
- }
1466
- let body = {};
1467
- try {
1468
- body = text ? JSON.parse(text) : {};
1469
- } catch {
1470
- body = {};
1471
- }
1472
- return { status: response.status, body };
1473
- }
1474
- async function fetchSkillRunStatus(runId) {
1475
- const ingestUrl = resolveIngestUrl().replace(/\/$/, "");
1476
- let resolvedRunId = runId;
1477
- if (!resolvedRunId) {
1478
- const list = await skillRunsRequest(`${ingestUrl}/v1/skills/runs?limit=1`);
1479
- const listBody = list.body;
1480
- const latest = Array.isArray(listBody.runs) ? listBody.runs[0] : undefined;
1481
- if (!latest)
1482
- return { run: null, events: [] };
1483
- resolvedRunId = latest.generation_run_id;
1484
- if (!resolvedRunId)
1485
- return { run: latest, events: [] };
1486
- }
1487
- const detail = await skillRunsRequest(`${ingestUrl}/v1/skills/runs/${encodeURIComponent(resolvedRunId)}`);
1488
- if (detail.status === 404) {
1489
- if (runId) {
1490
- throw new CliInputError(`No skill generation run found with id '${runId}'`);
1491
- }
1492
- return { run: null, events: [] };
1493
- }
1494
- const detailBody = detail.body;
1495
- return {
1496
- run: detailBody.run ?? null,
1497
- events: Array.isArray(detailBody.events) ? detailBody.events : []
1498
- };
1499
- }
1500
- var STATUS_HUMAN_EVENT_LIMIT = 10;
1501
- function formatRunStatusHuman(run, events) {
1502
- const counters = run.counters ?? {};
1503
- const lines = [
1504
- run.generation_run_id && `run: ${run.generation_run_id}`,
1505
- `status: ${run.status || "unknown"}`,
1506
- run.current_stage && `stage: ${run.current_stage}`,
1507
- run.outcome && `outcome: ${run.outcome}`,
1508
- `processed: ${Number(counters.sessions_processed ?? 0)}`,
1509
- `failed: ${Number(counters.sessions_failed ?? 0)}`,
1510
- Number(counters.distilled_skills_generated ?? 0) > 0 && `distilled: ${Number(counters.distilled_skills_generated ?? 0)}`,
1511
- Number(counters.skills_indexed ?? 0) > 0 && `indexed: ${Number(counters.skills_indexed ?? 0)}`,
1512
- (run.generated_skill_ids?.length ?? 0) > 0 && `skills: ${(run.generated_skill_ids ?? []).join(", ")}`,
1513
- run.error && `error: ${run.error}`,
1514
- run.started_at && `started: ${run.started_at}`,
1515
- run.updated_at && `updated: ${run.updated_at}`
1516
- ].filter(Boolean);
1517
- if (events.length > 0) {
1518
- lines.push("recent events (newest first):");
1519
- for (const event of events.slice(0, STATUS_HUMAN_EVENT_LIMIT)) {
1520
- lines.push(` ${[event.created_at, event.level, event.stage, event.message].filter(Boolean).join(" ")}`);
1521
- }
1522
- }
1523
- return lines.join(`
1524
- `);
1525
- }
1526
- function writeIfChanged(filePath, content) {
1527
- try {
1528
- mkdirSync4(dirname4(filePath), { recursive: true });
1529
- if (existsSync6(filePath)) {
1530
- const current = readFileSync4(filePath, "utf8");
1531
- if (current === content)
1532
- return "unchanged";
1533
- writeFileSync5(filePath, content);
1534
- return "updated";
1535
- }
1536
- writeFileSync5(filePath, content);
1537
- return "wrote";
1538
- } catch {
1539
- return "failed";
1540
- }
1541
- }
1542
- async function pullSkills(options = {}) {
1543
- const status = options.status ?? "approved";
1544
- const cwd = options.cwd ?? process.cwd();
1545
- const log = (msg) => {
1546
- if (!options.silent)
1547
- console.log(msg);
1548
- };
1549
- const fetched = await fetchSkills(status);
1550
- const { skills } = fetched;
1551
- const summary = {
1552
- total: skills.length,
1553
- pruned: 0,
1554
- wrote: 0,
1555
- updated: 0,
1556
- unchanged: 0,
1557
- failed: 0
1558
- };
1559
- if (skills.length === 0) {
1560
- log(" No skills to pull yet.");
1561
- return summary;
1562
- }
1563
- const previousManifest = readSkillsManifest(cwd);
1564
- const preservedIntegrationRecords = (previousManifest?.records ?? []).filter((record) => record.source === SKILL_SOURCE_INTEGRATION_BUNDLED);
1565
- if (previousManifest && previousManifest.managedPaths.length > 0 && previousManifest.releaseKey !== fetched.releaseKey) {
1566
- const integrationPaths = new Set(preservedIntegrationRecords.flatMap((record) => record.paths));
1567
- const harnessPaths = previousManifest.managedPaths.filter((path) => !integrationPaths.has(path));
1568
- if (harnessPaths.length > 0) {
1569
- summary.pruned = pruneManagedSkillPaths(cwd, harnessPaths, log);
1570
- }
1571
- }
1572
- const agents = detectAgents();
1573
- const writeCursor = agents.includes("Cursor") || existsSync6(join5(cwd, ".cursor"));
1574
- const records = [];
1575
- for (const skill of skills) {
1576
- if (!skill.skill_id || !skill.skill_md) {
1577
- summary.failed++;
1578
- continue;
1579
- }
1580
- const safeId = basename(skill.skill_id);
1581
- if (!safeId || !/^[A-Za-z0-9_-]+$/.test(safeId)) {
1582
- summary.failed++;
1583
- log(` skipped invalid skill_id: ${JSON.stringify(skill.skill_id).slice(0, 80)}`);
1584
- continue;
1585
- }
1586
- const claudePath = join5(cwd, ".claude", "skills", safeId, "SKILL.md");
1587
- const claudeStatus = writeIfChanged(claudePath, skill.skill_md);
1588
- const claudeRelativePath = `.claude/skills/${safeId}/SKILL.md`;
1589
- recordOutcome(summary, claudeStatus, log, claudeRelativePath);
1590
- const paths = [];
1591
- if (claudeStatus !== "failed")
1592
- paths.push(claudeRelativePath);
1593
- if (writeCursor) {
1594
- const cursorPath = join5(cwd, ".cursor", "rules", `${safeId}.mdc`);
1595
- const cursorStatus = writeIfChanged(cursorPath, claudeSkillToCursorRule(skill.skill_md));
1596
- const cursorRelativePath = `.cursor/rules/${safeId}.mdc`;
1597
- recordOutcome(summary, cursorStatus, log, cursorRelativePath);
1598
- if (cursorStatus !== "failed")
1599
- paths.push(cursorRelativePath);
1600
- }
1601
- if (paths.length > 0) {
1602
- records.push({
1603
- skillId: safeId,
1604
- contentHash: sha2562(skill.skill_md),
1605
- source: SKILL_SOURCE_HARNESS,
1606
- paths
1607
- });
1608
- }
1609
- }
1610
- writeSkillsManifest(cwd, manifestHeaderFor(fetched), [...records, ...preservedIntegrationRecords]);
1611
- return summary;
1612
- }
1613
- function recordOutcome(summary, status, log, relativePath) {
1614
- switch (status) {
1615
- case "wrote":
1616
- summary.wrote++;
1617
- log(` wrote ${relativePath}`);
1618
- break;
1619
- case "updated":
1620
- summary.updated++;
1621
- log(` updated ${relativePath}`);
1622
- break;
1623
- case "unchanged":
1624
- summary.unchanged++;
1625
- break;
1626
- case "failed":
1627
- summary.failed++;
1628
- log(` failed ${relativePath}`);
1629
- break;
1630
- }
1631
- }
1632
- var SKILLS_DIR = join5(".claude", "skills");
1633
- var SKILL_FILE = "SKILL.md";
1634
- var MAX_SKILL_WALK_DEPTH = 4;
1635
- async function runSkillSync(flags = {}, profileOptions = {}, options = {}) {
1636
- const cwd = options.cwd ?? process.cwd();
1637
- const dryRun = flags["dry-run"] === "true";
1638
- const skills = discoverLocalSkills(cwd);
1639
- if (skills.length === 0) {
1640
- return { synced: [], dryRun };
1641
- }
1642
- const sourceCommit = getGitCommit2(cwd);
1643
- const response = await callControlAPI("/skills/sync", {
1644
- method: "POST",
1645
- body: JSON.stringify({
1646
- skills: skills.map((skill) => ({
1647
- key: skill.key,
1648
- name: skill.name,
1649
- description: skill.description,
1650
- skillMd: skill.skillMd,
1651
- sourcePath: skill.sourcePath,
1652
- contentHash: skill.contentHash,
1653
- sourceCommit
1654
- })),
1655
- sourceCommit,
1656
- dryRun
1657
- })
1658
- }, profileOptions);
1659
- return { synced: response.synced ?? [], dryRun };
1660
- }
1661
- function discoverLocalSkills(cwd) {
1662
- const skillsRoot = join5(cwd, SKILLS_DIR);
1663
- if (!existsSync6(skillsRoot))
1664
- return [];
1665
- const managed = new Set(managedSkillPaths(cwd));
1666
- const skills = [];
1667
- for (const absPath of walkSkillFiles(skillsRoot, 0)) {
1668
- const sourcePath = relative3(cwd, absPath).replace(/\\/g, "/");
1669
- if (managed.has(sourcePath))
1670
- continue;
1671
- const key = basename(dirname4(absPath));
1672
- if (key === "moda-cli" || !/^[A-Za-z0-9_-]+$/.test(key))
1673
- continue;
1674
- let content;
1675
- try {
1676
- content = readFileSync4(absPath, "utf8");
1677
- } catch {
1678
- continue;
1679
- }
1680
- if (!content.trim())
1681
- continue;
1682
- const { name, description } = parseSkillFrontmatter(content, key);
1683
- skills.push({
1684
- key,
1685
- name,
1686
- description,
1687
- skillMd: content,
1688
- sourcePath,
1689
- contentHash: sha2562(content)
1690
- });
1691
- }
1692
- return skills.sort((a, b) => a.key.localeCompare(b.key));
1693
- }
1694
- function managedSkillPaths(cwd) {
1695
- const manifest = readSkillsManifest(cwd);
1696
- if (!manifest)
1697
- return [];
1698
- return manifest.managedPaths.filter((path) => path.startsWith(".claude/skills/") && path.endsWith("/SKILL.md"));
1699
- }
1700
- function walkSkillFiles(dir, depth) {
1701
- if (depth > MAX_SKILL_WALK_DEPTH)
1702
- return [];
1703
- let entries;
1704
- try {
1705
- entries = readdirSync3(dir);
1706
- } catch {
1707
- return [];
1708
- }
1709
- const files = [];
1710
- for (const entry of entries) {
1711
- const fullPath = join5(dir, entry);
1712
- let stat;
1713
- try {
1714
- stat = lstatSync2(fullPath);
1715
- } catch {
1716
- continue;
1717
- }
1718
- if (stat.isSymbolicLink())
1719
- continue;
1720
- if (stat.isDirectory()) {
1721
- files.push(...walkSkillFiles(fullPath, depth + 1));
1722
- } else if (stat.isFile() && entry === SKILL_FILE) {
1723
- files.push(fullPath);
1724
- }
1725
- }
1726
- return files;
1727
- }
1728
- function parseSkillFrontmatter(raw, fallbackKey) {
1729
- let name = fallbackKey;
1730
- let description;
1731
- if (raw.startsWith("---")) {
1732
- const end = raw.indexOf(`
1733
- ---`, 3);
1734
- if (end !== -1) {
1735
- const frontmatter = raw.slice(3, end);
1736
- const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
1737
- const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
1738
- if (nameMatch)
1739
- name = stripQuotes(nameMatch[1].trim()) || fallbackKey;
1740
- if (descMatch)
1741
- description = stripQuotes(descMatch[1].trim()) || undefined;
1742
- }
1743
- }
1744
- return { name, description };
1745
- }
1746
- function stripQuotes(value) {
1747
- return value.replace(/^["']|["']$/g, "");
1748
- }
1749
- function getGitCommit2(cwd) {
1750
- try {
1751
- return execFileSync2("git", ["rev-parse", "HEAD"], {
1752
- cwd,
1753
- encoding: "utf8",
1754
- stdio: ["ignore", "pipe", "ignore"]
1755
- }).trim();
1756
- } catch {
1757
- return;
1758
- }
1759
- }
1760
- async function fetchSkillProposals(flags, profileOptions = {}) {
1761
- const params = new URLSearchParams;
1762
- if (flags["generation-run-id"])
1763
- params.set("generationRunId", flags["generation-run-id"]);
1764
- const status = flags.status ?? "ready_for_pr";
1765
- if (status !== "all")
1766
- params.set("status", status);
1767
- if (flags["tenant-id"])
1768
- params.set("tenantId", flags["tenant-id"]);
1769
- const suffix = params.toString() ? `?${params.toString()}` : "";
1770
- const response = await callControlAPI(`/skills/proposals${suffix}`, { method: "GET" }, profileOptions);
1771
- return Array.isArray(response.proposals) ? response.proposals : [];
1772
- }
1773
- async function applySkillProposal(proposalId, flags, profileOptions = {}, options = {}) {
1774
- const cwd = options.cwd ?? process.cwd();
1775
- const params = new URLSearchParams;
1776
- if (flags["tenant-id"])
1777
- params.set("tenantId", flags["tenant-id"]);
1778
- const suffix = params.toString() ? `?${params.toString()}` : "";
1779
- const proposal = await callControlAPI(`/skills/proposals/${encodeURIComponent(proposalId)}${suffix}`, { method: "GET" }, profileOptions);
1780
- if (!proposal.skillMd?.trim()) {
1781
- throw new CliInputError(`Proposal ${proposalId} does not include SKILL.md content`);
1782
- }
1783
- const safeId = safeSkillId(proposal.skillId);
1784
- const relativePath = `.claude/skills/${safeId}/SKILL.md`;
1785
- const writeStatus = writeIfChanged(join5(cwd, relativePath), proposal.skillMd);
1786
- if (writeStatus === "failed") {
1787
- throw new Error(`Failed to write ${relativePath}`);
1788
- }
1789
- writeSkillProposalManifest(cwd, proposal, relativePath);
1790
- let acknowledged = true;
1791
- let ackError;
1792
- try {
1793
- await callControlAPI(`/skills/proposals/${encodeURIComponent(proposalId)}/mark-applied${suffix}`, {
1794
- method: "POST",
1795
- body: JSON.stringify({ appliedBy: "moda-cli" })
1796
- }, profileOptions);
1797
- } catch (err) {
1798
- acknowledged = false;
1799
- ackError = err instanceof Error ? err.message : String(err);
1800
- }
1801
- return {
1802
- proposalId,
1803
- skillId: proposal.skillId,
1804
- path: relativePath,
1805
- status: writeStatus,
1806
- acknowledged,
1807
- ackError
1808
- };
1809
- }
1810
- function safeSkillId(skillId) {
1811
- const safeId = basename(skillId || "");
1812
- if (!safeId || !/^[A-Za-z0-9_-]+$/.test(safeId)) {
1813
- throw new CliInputError(`Invalid skill_id in proposal: ${JSON.stringify(skillId).slice(0, 80)}`);
1814
- }
1815
- return safeId;
1816
- }
1817
- function writeSkillProposalManifest(cwd, proposal, relativePath) {
1818
- const manifestPath = join5(cwd, SKILL_PROPOSALS_MANIFEST_PATH);
1819
- mkdirSync4(dirname4(manifestPath), { recursive: true });
1820
- const existing = existsSync6(manifestPath) ? readFileSync4(manifestPath, "utf8") : `applied_proposals:
1821
- `;
1822
- const entry = [
1823
- ` - proposal_id: "${escapeYamlString(proposal.proposalId)}"`,
1824
- ` skill_id: "${escapeYamlString(proposal.skillId)}"`,
1825
- ` skill_family_id: "${escapeYamlString(proposal.skillFamilyId)}"`,
1826
- ` skill_version: ${Number(proposal.skillVersion || 0)}`,
1827
- ` generation_run_id: "${escapeYamlString(proposal.generationRunId)}"`,
1828
- ` clustering_run_id: "${escapeYamlString(proposal.clusteringRunId)}"`,
1829
- ` topic_id: "${escapeYamlString(proposal.topicId)}"`,
1830
- ` replay_set_id: "${escapeYamlString(proposal.replaySetId)}"`,
1831
- ` delta_pass_rate: ${Number(proposal.deltaPassRate || 0)}`,
1832
- ` path: "${escapeYamlString(relativePath)}"`,
1833
- ` applied_at: "${new Date().toISOString()}"`
1834
- ].join(`
1835
- `);
1836
- const prefix = existing.endsWith(`
1837
- `) ? existing : `${existing}
1838
- `;
1839
- writeFileSync5(manifestPath, `${prefix}${entry}
1840
- `);
1841
- }
1842
- function escapeYamlString(value) {
1843
- return String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
1844
- }
1845
- function formatProposalRate(value) {
1846
- return `${(Number(value || 0) * 100).toFixed(1)}%`;
1847
- }
1848
- async function runSkillsCommand(input) {
1849
- const context = "output" in input ? input : createCommandContext({
1850
- command: input.command,
1851
- positional: input.positional,
1852
- positionals: input.positionals,
1853
- flags: input.flags
1854
- }, {
1855
- outputMode: input.flags.agent === "true" ? "agent" : input.flags.json === "true" ? "json" : "human"
1856
- });
1857
- const sub = context.positional;
1858
- const positionals = context.positionals ?? (sub ? [sub] : []);
1859
- const flags = context.flags;
1860
- if (sub === "gen") {
1861
- const result = await generateSkills(flags);
1862
- if (context.outputMode !== "human") {
1863
- context.output.writeData(result, {
1864
- status: Number(result.sessions_failed ?? 0) > 0 ? "error" : "success"
1865
- });
1866
- } else if (result.accepted) {
1867
- console.log([
1868
- "accepted: true",
1869
- result.generation_run_id && `generation_run_id: ${result.generation_run_id}`,
1870
- result.message && `message: ${result.message}`,
1871
- result.generation_run_id && `follow progress: moda skills status ${result.generation_run_id}`
1872
- ].filter(Boolean).join(`
1873
- `));
1874
- } else {
1875
- console.log([
1876
- `processed: ${result.sessions_processed ?? 0}`,
1877
- `failed: ${result.sessions_failed ?? 0}`,
1878
- `pending: ${result.sessions_pending ?? 0}`,
1879
- `pending_found: ${result.pending_sessions_found ?? 0}`,
1880
- `distilled: ${Number(result.cluster_result?.distilled_skills_generated ?? 0)}`,
1881
- `indexed: ${Number(result.cluster_result?.skills_indexed ?? 0)}`,
1882
- result.distillation_skipped && `distillation skipped: ${result.distillation_skip_reason || "unknown"}`
1883
- ].filter(Boolean).join(`
1884
- `));
1885
- }
1886
- return Number(result.sessions_failed ?? 0) > 0 ? 1 : 0;
1887
- }
1888
- if (sub === "status") {
1889
- const runId = positionals[1] || flags["run-id"] || undefined;
1890
- const { run, events } = await fetchSkillRunStatus(runId);
1891
- if (context.outputMode !== "human") {
1892
- context.output.writeData({ run, events }, { status: run?.status === "failed" ? "error" : "success" });
1893
- } else if (!run) {
1894
- console.log("No skill generation runs found yet. Start one with `moda skills gen`.");
1895
- } else {
1896
- console.log(formatRunStatusHuman(run, events));
1897
- }
1898
- return run?.status === "failed" ? 1 : 0;
1899
- }
1900
- if (sub === "proposals" && positionals[1] === "list") {
1901
- const proposals = await fetchSkillProposals(flags, { profile: context.profile });
1902
- if (flags.json === "true") {
1903
- console.log(JSON.stringify({ proposals }));
1904
- } else if (proposals.length === 0) {
1905
- console.log("No skill proposals found.");
1906
- } else {
1907
- for (const proposal of proposals) {
1908
- console.log([
1909
- proposal.proposalId,
1910
- proposal.proposalStatus,
1911
- `${proposal.skillId}@v${proposal.skillVersion}`,
1912
- `delta=${formatProposalRate(proposal.deltaPassRate)}`,
1913
- `baseline=${formatProposalRate(proposal.baselinePassRate)}`,
1914
- `candidate=${formatProposalRate(proposal.candidatePassRate)}`,
1915
- proposal.name
1916
- ].filter(Boolean).join("\t"));
1917
- }
1918
- }
1919
- return 0;
1920
- }
1921
- if (sub === "proposal" && positionals[1] === "apply") {
1922
- const proposalId = positionals[2] || flags["proposal-id"];
1923
- if (!proposalId) {
1924
- throw new CliInputError("A proposal id is required", "Usage: moda skills proposal apply <proposal-id>");
1925
- }
1926
- const result = await applySkillProposal(proposalId, flags, { profile: context.profile });
1927
- if (flags.json === "true") {
1928
- console.log(JSON.stringify(result));
1929
- } else {
1930
- console.log([
1931
- `${result.status} ${result.path}`,
1932
- result.acknowledged ? "acknowledged: true" : "acknowledged: false"
1933
- ].join(`
1934
- `));
1935
- }
1936
- if (!result.acknowledged) {
1937
- console.error(`Warning: wrote ${result.path} but failed to acknowledge proposal ${proposalId} on the server` + (result.ackError ? ` (${result.ackError})` : "") + ". The proposal is still listed as ready — re-run `moda skills proposal apply` to acknowledge it.");
1938
- return 1;
1939
- }
1940
- return 0;
1941
- }
1942
- if (sub === "sync") {
1943
- const result = await runSkillSync(flags, { profile: context.profile, env: context.env });
1944
- context.output.writeData({ synced: result.synced, dryRun: result.dryRun });
1945
- return 0;
1946
- }
1947
- if (sub !== "pull") {
1948
- throw new CliInputError(sub ? `Unknown skills subcommand '${sub}'` : "A skills subcommand is required", "Usage: moda skills gen [--source=all|sdk] [--max-sessions=N] [--start-at=ISO] [--end-at=ISO] [--replay] [--replay-set-id=ID] [--reprocess-segments] [--force-reprocess] [--wait] | moda skills status [run-id] | moda skills proposals list [--status=ready_for_pr] | moda skills proposal apply <proposal-id> | moda skills pull [--status=approved|proposed|all] | moda skills sync [--dry-run]");
1949
- }
1950
- const status = (flags.status ?? "approved").toLowerCase();
1951
- if (!VALID_STATUSES.has(status)) {
1952
- throw new CliInputError(`Invalid --status '${status}'`, `Must be one of: ${[...VALID_STATUSES].join(", ")}`);
1953
- }
1954
- const summary = await pullSkills({ status, silent: context.outputMode !== "human" });
1955
- const parts = [
1956
- summary.pruned && `${summary.pruned} pruned`,
1957
- summary.wrote && `${summary.wrote} written`,
1958
- summary.updated && `${summary.updated} updated`,
1959
- summary.unchanged && `${summary.unchanged} unchanged`,
1960
- summary.failed && `${summary.failed} failed`
1961
- ].filter(Boolean);
1962
- if (context.outputMode === "human" && summary.total > 0) {
1963
- console.log(` Done — ${parts.join(", ") || "no changes"}`);
1964
- }
1965
- if (context.outputMode !== "human") {
1966
- context.output.writeData({ status, summary });
1967
- }
1968
- return summary.failed > 0 ? 1 : 0;
1969
- }
1970
- async function generateSkills(flags) {
1971
- const apiKey = resolveApiKey();
1972
- if (!apiKey) {
1973
- throw new CliInputError("No Moda API key found. Run `moda init` first (or set MODA_API_KEY).");
1974
- }
1975
- const tenantId = flags["tenant-id"] || resolveTenantId();
1976
- const directHarnessUrl = flags["harness-url"] || process.env.MODA_SKILL_HARNESS_URL;
1977
- if (directHarnessUrl && !tenantId) {
1978
- throw new CliInputError("No Moda tenant id found. Run `moda init` first (or set MODA_TENANT_ID / --tenant-id).");
1979
- }
1980
- const endpoint = directHarnessUrl ? `${directHarnessUrl.replace(/\/+$/, "")}/skills/generate` : `${resolveIngestUrl().replace(/\/+$/, "")}/v1/skills/generate`;
1981
- const waitForCompletion = flags.wait === "true" || flags["wait-for-completion"] === "true";
1982
- const forceReprocess = flags["force-reprocess"] === "true";
1983
- const source = normalizeSkillSource(flags.source);
1984
- if (source === "coding_agent") {
1985
- console.error("Note: --source=coding-agent is deprecated for skills gen (SDK-only). The run will find no coding-agent sessions; use --source=all or --source=sdk.");
1986
- }
1987
- const body = compactObject({
1988
- tenant_id: tenantId || undefined,
1989
- max_sessions: parsePositiveInt3(flags["max-sessions"], 0),
1990
- lookback_hours: parsePositiveInt3(flags["lookback-hours"], 0),
1991
- start_at: flags["start-at"] || undefined,
1992
- end_at: flags["end-at"] || undefined,
1993
- clustering_run_id: flags["clustering-run-id"] || undefined,
1994
- replay_set_id: flags["replay-set-id"] || flags["eval-set-id"] || undefined,
1995
- run_clustering: flags["no-clustering"] !== "true",
1996
- source,
1997
- clustering_timeout_seconds: parsePositiveInt3(flags["clustering-timeout-seconds"], 7200),
1998
- clustering_poll_seconds: parsePositiveInt3(flags["clustering-poll-seconds"], 15),
1999
- max_cluster_pct: parseOptionalFloat(flags["max-cluster-pct"]),
2000
- reprocess_segments: flags["reprocess-segments"] === "true" || forceReprocess,
2001
- run_replay: flags.replay === "true" && flags["no-replay"] !== "true",
2002
- max_traces: parsePositiveInt3(flags["max-traces"], 0),
2003
- improve_candidates: flags["improve-candidates"] === "true",
2004
- improvement_rounds: parsePositiveInt3(flags["improvement-rounds"], 2),
2005
- force_reprocess: forceReprocess,
2006
- wait_for_completion: waitForCompletion
2007
- });
2008
- const headers = {
2009
- Authorization: `Bearer ${apiKey}`,
2010
- "Content-Type": "application/json"
2011
- };
2012
- if (tenantId)
2013
- headers["X-Moda-Tenant-Id"] = tenantId;
2014
- const response = await fetch(endpoint, {
2015
- method: "POST",
2016
- headers,
2017
- body: JSON.stringify(body)
2018
- });
2019
- if (!response.ok) {
2020
- const text = await response.text().catch(() => "");
2021
- throw new Error(`Failed to generate skills (HTTP ${response.status}): ${text.slice(0, 200)}`);
2022
- }
2023
- return await response.json();
2024
- }
2025
- function parsePositiveInt3(value, fallback) {
2026
- if (!value)
2027
- return fallback;
2028
- const parsed = Number.parseInt(value, 10);
2029
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
2030
- }
2031
- function parseOptionalFloat(value) {
2032
- if (!value)
2033
- return;
2034
- const parsed = Number.parseFloat(value);
2035
- return Number.isFinite(parsed) ? parsed : undefined;
2036
- }
2037
- function compactObject(input) {
2038
- return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
2039
- }
2040
- function normalizeSkillSource(value) {
2041
- const normalized = (value || "all").toLowerCase().replace(/-/g, "_");
2042
- if (["all", "sdk", "coding_agent"].includes(normalized))
2043
- return normalized;
2044
- return "all";
2045
- }
2046
- function releaseKeyFor(release, skills, status) {
2047
- if (release?.generation_run_id)
2048
- return `${status}:run:${release.generation_run_id}`;
2049
- if (Number(release?.generation_version || 0) > 0) {
2050
- return `${status}:version:${Number(release?.generation_version)}`;
2051
- }
2052
- return `${status}:legacy:${sha2562(JSON.stringify(skills.map((skill) => ({
2053
- skill_id: skill.skill_id,
2054
- hash: sha2562(skill.skill_md || "")
2055
- })).sort((a, b) => a.skill_id.localeCompare(b.skill_id))))}`;
2056
- }
2057
- function manifestHeaderFor(fetched) {
2058
- const release = fetched.release;
2059
- return {
2060
- tenantId: fetched.tenantId,
2061
- releaseKey: fetched.releaseKey,
2062
- statusFilter: fetched.status,
2063
- generationRunId: release?.generation_run_id || "",
2064
- generationVersion: Number(release?.generation_version || 0),
2065
- releasedAt: release?.released_at || ""
2066
- };
2067
- }
2068
- function sha2562(input) {
2069
- return createHash2("sha256").update(input).digest("hex");
2070
- }
2071
-
2072
126
  // src/harness-remote.ts
2073
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "node:fs";
2074
- import { join as join8 } from "node:path";
127
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
128
+ import { join as join3 } from "node:path";
2075
129
 
2076
130
  // src/markdown-terminal.ts
2077
131
  function shouldColorTerminalOutput(opts = {}) {
@@ -2213,23 +267,23 @@ function collapseBlankRuns(lines) {
2213
267
  }
2214
268
 
2215
269
  // src/harness-report.ts
2216
- import { createHash as createHash3 } from "node:crypto";
270
+ import { createHash } from "node:crypto";
2217
271
  import {
2218
- existsSync as existsSync7,
2219
- lstatSync as lstatSync3,
2220
- mkdirSync as mkdirSync5,
2221
- readFileSync as readFileSync5,
2222
- writeFileSync as writeFileSync6
272
+ existsSync,
273
+ lstatSync,
274
+ mkdirSync,
275
+ readFileSync,
276
+ writeFileSync
2223
277
  } from "node:fs";
2224
- import { basename as basename2, dirname as dirname5, join as join6, relative as relative4, resolve as resolve5 } from "node:path";
2225
- import { confirm as confirm2 } from "@inquirer/prompts";
278
+ import { basename, dirname, join, relative, resolve } from "node:path";
279
+ import { confirm } from "@inquirer/prompts";
2226
280
  var HARNESS_REPORT_SCHEMA_VERSION = "harness_report.v0.1";
2227
281
  var HARNESS_REPORT_VALIDATION_SCHEMA_VERSION = "harness_report_validation.v0.1";
2228
282
  var HARNESS_REPORT_APPROVAL_SCHEMA_VERSION = "harness_report_approval.v0.1";
2229
- var HARNESS_REPORT_PATH = join6(".moda", "harness-report.json");
2230
- var HARNESS_REPORT_MARKDOWN_PATH = join6(".moda", "HARNESS_REPORT.md");
2231
- var HARNESS_REPORT_VALIDATION_PATH = join6(".moda", "harness-report-validation.json");
2232
- var HARNESS_REPORT_APPROVAL_PATH = join6(".moda", "harness-report-approval.json");
283
+ var HARNESS_REPORT_PATH = join(".moda", "harness-report.json");
284
+ var HARNESS_REPORT_MARKDOWN_PATH = join(".moda", "HARNESS_REPORT.md");
285
+ var HARNESS_REPORT_VALIDATION_PATH = join(".moda", "harness-report-validation.json");
286
+ var HARNESS_REPORT_APPROVAL_PATH = join(".moda", "harness-report-approval.json");
2233
287
  var HARNESS_GRAPH_ARRAY_FIELDS = [
2234
288
  "agentFamilies",
2235
289
  "runtimeAgents",
@@ -2682,7 +736,7 @@ async function approveHarnessReport(options) {
2682
736
  if (!options.interactive || process.stdin.isTTY !== true) {
2683
737
  throw new CliInputError("Harness report approval requires an interactive confirmation or --yes.", "Review the report, then run `moda harness approve --yes`.");
2684
738
  }
2685
- const approved = await confirm2({
739
+ const approved = await confirm({
2686
740
  message: [
2687
741
  "Approve this harness report for Moda sync?",
2688
742
  `${report.summary.runtimeAgentCount} runtime agent(s), ${report.summary.artifactCount} artifact(s), ${report.claims.length} cited claim(s).`
@@ -2701,9 +755,9 @@ async function approveHarnessReport(options) {
2701
755
  approved: true,
2702
756
  approvedAt: new Date().toISOString(),
2703
757
  approvedBy,
2704
- reportPath: normalizePath(relative4(options.rootDir, options.reportPath)) || HARNESS_REPORT_PATH
758
+ reportPath: normalizePath(relative(options.rootDir, options.reportPath)) || HARNESS_REPORT_PATH
2705
759
  };
2706
- writeJson(join6(options.rootDir, HARNESS_REPORT_APPROVAL_PATH), approval);
760
+ writeJson(join(options.rootDir, HARNESS_REPORT_APPROVAL_PATH), approval);
2707
761
  return { report, validation, approval };
2708
762
  }
2709
763
  function loadHarnessReportForSync(rootDir, reportPath) {
@@ -2713,13 +767,13 @@ function loadHarnessReportForSync(rootDir, reportPath) {
2713
767
  throw new CliInputError("Harness report validation failed; cannot sync.", validation.errors.slice(0, 5).join(`
2714
768
  `));
2715
769
  }
2716
- const approvalPath = join6(rootDir, HARNESS_REPORT_APPROVAL_PATH);
2717
- const approval = existsSync7(approvalPath) ? JSON.parse(readFileSync5(approvalPath, "utf8")) : null;
770
+ const approvalPath = join(rootDir, HARNESS_REPORT_APPROVAL_PATH);
771
+ const approval = existsSync(approvalPath) ? JSON.parse(readFileSync(approvalPath, "utf8")) : null;
2718
772
  const approved = approval?.schemaVersion === HARNESS_REPORT_APPROVAL_SCHEMA_VERSION && approval.reportId === report.reportId && approval.reportHash === hashHarnessReport(report) && approval.approved === true;
2719
773
  return { report, validation, approval, approved };
2720
774
  }
2721
775
  function readHarnessReport(reportPath) {
2722
- return normalizeHarnessReport(JSON.parse(readFileSync5(reportPath, "utf8")));
776
+ return normalizeHarnessReport(JSON.parse(readFileSync(reportPath, "utf8")));
2723
777
  }
2724
778
  function normalizeHarnessReport(value) {
2725
779
  const report = cloneJson(value);
@@ -3306,18 +1360,18 @@ function limitReportEvidence(claims, citations) {
3306
1360
  };
3307
1361
  }
3308
1362
  function resolveHarnessReportPath(rootDir, value) {
3309
- return resolve5(rootDir, value && value !== "true" ? value : HARNESS_REPORT_PATH);
1363
+ return resolve(rootDir, value && value !== "true" ? value : HARNESS_REPORT_PATH);
3310
1364
  }
3311
1365
  function writeHarnessReportArtifacts(options) {
3312
1366
  const written = [];
3313
- writeJson(join6(options.rootDir, HARNESS_REPORT_PATH), options.report);
1367
+ writeJson(join(options.rootDir, HARNESS_REPORT_PATH), options.report);
3314
1368
  written.push(HARNESS_REPORT_PATH);
3315
- writeJson(join6(options.rootDir, HARNESS_REPORT_VALIDATION_PATH), options.validation);
1369
+ writeJson(join(options.rootDir, HARNESS_REPORT_VALIDATION_PATH), options.validation);
3316
1370
  written.push(HARNESS_REPORT_VALIDATION_PATH);
3317
1371
  if (options.markdown) {
3318
- const markdownPath = join6(options.rootDir, HARNESS_REPORT_MARKDOWN_PATH);
3319
- mkdirSync5(dirname5(markdownPath), { recursive: true });
3320
- writeFileSync6(markdownPath, renderHarnessReportMarkdown(options.report, options.validation));
1372
+ const markdownPath = join(options.rootDir, HARNESS_REPORT_MARKDOWN_PATH);
1373
+ mkdirSync(dirname(markdownPath), { recursive: true });
1374
+ writeFileSync(markdownPath, renderHarnessReportMarkdown(options.report, options.validation));
3321
1375
  written.push(HARNESS_REPORT_MARKDOWN_PATH);
3322
1376
  }
3323
1377
  return written;
@@ -3391,10 +1445,10 @@ function hashHarnessReport(report) {
3391
1445
  return hashStableJson(report);
3392
1446
  }
3393
1447
  function hashStableJson(value) {
3394
- return createHash3("sha256").update(JSON.stringify(sortJson2(value))).digest("hex");
1448
+ return createHash("sha256").update(JSON.stringify(sortJson(value))).digest("hex");
3395
1449
  }
3396
1450
  function graphForReport(graph) {
3397
- return sortJson2({
1451
+ return sortJson({
3398
1452
  ...graph,
3399
1453
  scan: {
3400
1454
  ...graph.scan,
@@ -3443,14 +1497,14 @@ function upsertCitation(citations, citationIndex, evidence, rootDir) {
3443
1497
  function citationExcerpt(rootDir, path, line) {
3444
1498
  if (isUnsafeCitationPath(path))
3445
1499
  return {};
3446
- const absPath = resolve5(rootDir, path);
3447
- if (!isInside(rootDir, absPath) || !existsSync7(absPath))
1500
+ const absPath = resolve(rootDir, path);
1501
+ if (!isInside(rootDir, absPath) || !existsSync(absPath))
3448
1502
  return {};
3449
1503
  try {
3450
- const stat = lstatSync3(absPath);
1504
+ const stat = lstatSync(absPath);
3451
1505
  if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 128 * 1024)
3452
1506
  return {};
3453
- const lines = readFileSync5(absPath, "utf8").split(/\r?\n/);
1507
+ const lines = readFileSync(absPath, "utf8").split(/\r?\n/);
3454
1508
  const selected = line ? lines[line - 1] : lines.find((candidate) => candidate.trim().length > 0);
3455
1509
  if (!selected)
3456
1510
  return {};
@@ -3588,17 +1642,17 @@ function validateCitation(citation, rootDir, errors, warnings, options) {
3588
1642
  errors.push(`Citation ${citation.id} points to an unsafe path.`);
3589
1643
  return;
3590
1644
  }
3591
- const absPath = resolve5(rootDir, citation.path);
1645
+ const absPath = resolve(rootDir, citation.path);
3592
1646
  if (!isInside(rootDir, absPath)) {
3593
1647
  errors.push(`Citation ${citation.id} points outside the harness root.`);
3594
1648
  return;
3595
1649
  }
3596
- if (!existsSync7(absPath)) {
1650
+ if (!existsSync(absPath)) {
3597
1651
  errors.push(`Citation ${citation.id} file does not exist: ${citation.path}.`);
3598
1652
  return;
3599
1653
  }
3600
1654
  try {
3601
- const stat = lstatSync3(absPath);
1655
+ const stat = lstatSync(absPath);
3602
1656
  if (stat.isSymbolicLink())
3603
1657
  errors.push(`Citation ${citation.id} points to a symlink.`);
3604
1658
  if (stat.isDirectory()) {
@@ -3607,7 +1661,7 @@ function validateCitation(citation, rootDir, errors, warnings, options) {
3607
1661
  }
3608
1662
  if (!stat.isFile())
3609
1663
  errors.push(`Citation ${citation.id} does not point to a file.`);
3610
- const lines = readFileSync5(absPath, "utf8").split(/\r?\n/);
1664
+ const lines = readFileSync(absPath, "utf8").split(/\r?\n/);
3611
1665
  if (citation.line !== undefined) {
3612
1666
  const lineCount = lines.length;
3613
1667
  if (citation.line < 1 || citation.line > lineCount) {
@@ -3651,24 +1705,24 @@ function isUnsafeCitationPath(path) {
3651
1705
  return path.startsWith("/") || path.includes("..") || path.split(/[\\/]/).some((part) => part === "..") || isSecretishPath2(path);
3652
1706
  }
3653
1707
  function isSecretishPath2(path) {
3654
- const name = basename2(path).toLowerCase();
1708
+ const name = basename(path).toLowerCase();
3655
1709
  if (name === ".env.example" || name === ".env.sample" || name === ".env.template" || name === ".env.defaults" || /^\.env\..*(example|sample|template)$/.test(name)) {
3656
1710
  return false;
3657
1711
  }
3658
1712
  return name === ".env" || name.startsWith(".env.") || name.includes("secret") || name.includes("private-key") || name.endsWith(".pem");
3659
1713
  }
3660
1714
  function isInside(rootDir, absPath) {
3661
- const rel = relative4(resolve5(rootDir), resolve5(absPath));
1715
+ const rel = relative(resolve(rootDir), resolve(absPath));
3662
1716
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
3663
1717
  }
3664
1718
  function writeJson(path, value) {
3665
- mkdirSync5(dirname5(path), { recursive: true });
3666
- writeFileSync6(path, `${JSON.stringify(value, null, 2)}
1719
+ mkdirSync(dirname(path), { recursive: true });
1720
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
3667
1721
  `);
3668
1722
  }
3669
1723
  function stableId(prefix, input) {
3670
1724
  const slug = input.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64);
3671
- const digest = createHash3("sha256").update(input).digest("hex").slice(0, 12);
1725
+ const digest = createHash("sha256").update(input).digest("hex").slice(0, 12);
3672
1726
  if (!slug)
3673
1727
  return `${prefix}_${digest}`;
3674
1728
  return slug.length >= 64 ? `${prefix}_${slug.slice(0, 51)}_${digest}` : `${prefix}_${slug}`;
@@ -3676,11 +1730,11 @@ function stableId(prefix, input) {
3676
1730
  function titleFromId(id) {
3677
1731
  return id.replace(/^[a-z]+_/, "").split(/[_\-/]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") || id;
3678
1732
  }
3679
- function sortJson2(value) {
1733
+ function sortJson(value) {
3680
1734
  if (Array.isArray(value))
3681
- return value.map((item) => sortJson2(item));
1735
+ return value.map((item) => sortJson(item));
3682
1736
  if (value && typeof value === "object") {
3683
- return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson2(item)]));
1737
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson(item)]));
3684
1738
  }
3685
1739
  return value;
3686
1740
  }
@@ -3692,19 +1746,19 @@ function normalizePath(path) {
3692
1746
  }
3693
1747
 
3694
1748
  // src/harness.ts
3695
- import { createHash as createHash4 } from "node:crypto";
3696
- import { execFileSync as execFileSync3, spawn } from "node:child_process";
1749
+ import { createHash as createHash2 } from "node:crypto";
1750
+ import { execFileSync, spawn } from "node:child_process";
3697
1751
  import { homedir } from "node:os";
3698
1752
  import {
3699
- existsSync as existsSync9,
3700
- lstatSync as lstatSync5,
3701
- mkdirSync as mkdirSync7,
3702
- readFileSync as readFileSync7,
3703
- readdirSync as readdirSync4,
3704
- writeFileSync as writeFileSync8
1753
+ existsSync as existsSync3,
1754
+ lstatSync as lstatSync3,
1755
+ mkdirSync as mkdirSync3,
1756
+ readFileSync as readFileSync3,
1757
+ readdirSync,
1758
+ writeFileSync as writeFileSync3
3705
1759
  } from "node:fs";
3706
- import { basename as basename5, delimiter, dirname as dirname8, join as join7, relative as relative6, resolve as resolve8, sep } from "node:path";
3707
- import { confirm as confirm3, select } from "@inquirer/prompts";
1760
+ import { basename as basename4, delimiter, dirname as dirname4, join as join2, relative as relative3, resolve as resolve4, sep } from "node:path";
1761
+ import { confirm as confirm2, select } from "@inquirer/prompts";
3708
1762
 
3709
1763
  // src/analyst-tui.ts
3710
1764
  class AnalystTui {
@@ -3995,8 +2049,8 @@ function stripAnsi2(value) {
3995
2049
  }
3996
2050
 
3997
2051
  // src/harness-artifact-bodies.ts
3998
- import { existsSync as existsSync8, lstatSync as lstatSync4, readFileSync as readFileSync6, realpathSync } from "node:fs";
3999
- import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute5, relative as relative5, resolve as resolve6 } from "node:path";
2052
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2, realpathSync } from "node:fs";
2053
+ import { basename as basename2, dirname as dirname2, isAbsolute, relative as relative2, resolve as resolve2 } from "node:path";
4000
2054
  var MAX_SCANNED_FILE_BYTES2 = 512 * 1024;
4001
2055
  var SNAPSHOT_BUDGET_SKIP_REASONS = new Set([
4002
2056
  "file too large for harness scan",
@@ -4016,7 +2070,7 @@ function isSecretishPath3(path) {
4016
2070
  return normalized.includes(".env") || normalized.endsWith(".pem") || normalized.endsWith(".key") || normalized.includes("/secrets/") || normalized.includes("/credentials/");
4017
2071
  }
4018
2072
  function isOutsideRoot(rel) {
4019
- return rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute5(rel);
2073
+ return rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute(rel);
4020
2074
  }
4021
2075
  function walkAncestorDirs(fromAbs, rootAbs, maxDepth = 8) {
4022
2076
  const dirs = [];
@@ -4025,7 +2079,7 @@ function walkAncestorDirs(fromAbs, rootAbs, maxDepth = 8) {
4025
2079
  dirs.push(cursor);
4026
2080
  if (normalizePath2(cursor) === normalizePath2(rootAbs))
4027
2081
  break;
4028
- const parent = dirname6(cursor);
2082
+ const parent = dirname2(cursor);
4029
2083
  if (parent === cursor)
4030
2084
  break;
4031
2085
  cursor = parent;
@@ -4033,16 +2087,16 @@ function walkAncestorDirs(fromAbs, rootAbs, maxDepth = 8) {
4033
2087
  return dirs;
4034
2088
  }
4035
2089
  function readBoundedRepoText(absPath, rootDir) {
4036
- if (!existsSync8(absPath))
2090
+ if (!existsSync2(absPath))
4037
2091
  return;
4038
- const rel = normalizePath2(relative5(rootDir, absPath));
2092
+ const rel = normalizePath2(relative2(rootDir, absPath));
4039
2093
  if (isOutsideRoot(rel))
4040
2094
  return;
4041
2095
  if (isSecretishPath3(rel))
4042
2096
  return;
4043
2097
  let stat;
4044
2098
  try {
4045
- stat = lstatSync4(absPath);
2099
+ stat = lstatSync2(absPath);
4046
2100
  } catch {
4047
2101
  return;
4048
2102
  }
@@ -4052,20 +2106,20 @@ function readBoundedRepoText(absPath, rootDir) {
4052
2106
  if (!isTextFile2(rel))
4053
2107
  return;
4054
2108
  try {
4055
- if (isOutsideRoot(normalizePath2(relative5(realpathSync(rootDir), realpathSync(absPath))))) {
2109
+ if (isOutsideRoot(normalizePath2(relative2(realpathSync(rootDir), realpathSync(absPath))))) {
4056
2110
  return;
4057
2111
  }
4058
2112
  } catch {
4059
2113
  return;
4060
2114
  }
4061
2115
  try {
4062
- return readFileSync6(absPath, "utf8");
2116
+ return readFileSync2(absPath, "utf8");
4063
2117
  } catch {
4064
2118
  return;
4065
2119
  }
4066
2120
  }
4067
2121
  function readRepoRelativeText(rootDir, relPath) {
4068
- return readBoundedRepoText(resolve6(rootDir, relPath), rootDir);
2122
+ return readBoundedRepoText(resolve2(rootDir, relPath), rootDir);
4069
2123
  }
4070
2124
  function isDedicatedToolFilePath(path) {
4071
2125
  return /(^|\/)tools?\//i.test(path) && /\.(ts|tsx|js|mjs|py)$/i.test(path);
@@ -4106,14 +2160,14 @@ function annotatedSourceCandidates(sourcePath, annotation) {
4106
2160
  const explicitSourcePath = normalizePath2(sourcePath) !== normalizePath2(annotation.evidence.path);
4107
2161
  if (!explicitSourcePath) {
4108
2162
  return [
4109
- resolve6(annotation.rootDir, sourcePath),
4110
- resolve6(dirname6(annotation.sourceAbsPath), sourcePath),
2163
+ resolve2(annotation.rootDir, sourcePath),
2164
+ resolve2(dirname2(annotation.sourceAbsPath), sourcePath),
4111
2165
  annotation.sourceAbsPath
4112
2166
  ];
4113
2167
  }
4114
- const rootAbs = resolve6(annotation.rootDir);
4115
- const candidates = walkAncestorDirs(dirname6(annotation.sourceAbsPath), rootAbs).map((dir) => resolve6(dir, sourcePath));
4116
- candidates.push(resolve6(annotation.rootDir, sourcePath));
2168
+ const rootAbs = resolve2(annotation.rootDir);
2169
+ const candidates = walkAncestorDirs(dirname2(annotation.sourceAbsPath), rootAbs).map((dir) => resolve2(dir, sourcePath));
2170
+ candidates.push(resolve2(annotation.rootDir, sourcePath));
4117
2171
  return candidates;
4118
2172
  }
4119
2173
  function readAnnotatedSourceText(sourcePath, annotation) {
@@ -4136,7 +2190,7 @@ function resolveAnnotatedSourcePath(sourcePath, annotation) {
4136
2190
  seen.add(candidate);
4137
2191
  if (readBoundedRepoText(candidate, annotation.rootDir) === undefined)
4138
2192
  continue;
4139
- const rel = normalizePath2(relative5(annotation.rootDir, candidate));
2193
+ const rel = normalizePath2(relative2(annotation.rootDir, candidate));
4140
2194
  if (!isOutsideRoot(rel))
4141
2195
  return rel;
4142
2196
  }
@@ -4150,7 +2204,7 @@ function readPromptOrToolBody(type, sourcePath, annotation) {
4150
2204
  return;
4151
2205
  if (type === "prompt" || isDedicatedToolFilePath(sourcePath))
4152
2206
  return text;
4153
- return extractToolBodyFromText(text, first(annotation.values.name) ?? basename3(sourcePath)) ?? text;
2207
+ return extractToolBodyFromText(text, first(annotation.values.name) ?? basename2(sourcePath)) ?? text;
4154
2208
  }
4155
2209
  function extractToolBody(file, tool) {
4156
2210
  const text = file.text ?? readRepoRelativeText(file.rootDir, file.path);
@@ -4194,9 +2248,9 @@ function readSourcePathRelativeToEvidence(artifact, rootDir) {
4194
2248
  const evidencePath = firstEvidencePath(artifact);
4195
2249
  if (!evidencePath)
4196
2250
  return;
4197
- const rootAbs = resolve6(rootDir);
4198
- for (const dir of walkAncestorDirs(dirname6(resolve6(rootDir, evidencePath)), rootAbs)) {
4199
- const text = readBoundedRepoText(resolve6(dir, artifact.sourcePath), rootDir);
2251
+ const rootAbs = resolve2(rootDir);
2252
+ for (const dir of walkAncestorDirs(dirname2(resolve2(rootDir, evidencePath)), rootAbs)) {
2253
+ const text = readBoundedRepoText(resolve2(dir, artifact.sourcePath), rootDir);
4200
2254
  if (text !== undefined)
4201
2255
  return text;
4202
2256
  }
@@ -4225,8 +2279,8 @@ function enrichScanCaptureMetadata(graph) {
4225
2279
  }
4226
2280
 
4227
2281
  // src/harness-candidates.ts
4228
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "node:fs";
4229
- import { basename as basename4, dirname as dirname7, extname, resolve as resolve7 } from "node:path";
2282
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
2283
+ import { basename as basename3, dirname as dirname3, extname, resolve as resolve3 } from "node:path";
4230
2284
  var EXCERPT_MAX_CHARS = 240;
4231
2285
  var MAX_CANDIDATES_PER_FILE_PER_SIGNAL = 20;
4232
2286
  var MIN_PROMPT_LITERAL_CHARS = 160;
@@ -4544,7 +2598,7 @@ function detectAdjacentStringBlocks(file, out) {
4544
2598
  }
4545
2599
  var PROMPT_MODULE_NAME_HINT = /(^|[._-])(system[._-]?)?prompts?([._-]|$)/i;
4546
2600
  function detectPromptModule(file, out) {
4547
- const base = basename4(file.path, extname(file.path));
2601
+ const base = basename3(file.path, extname(file.path));
4548
2602
  if (!PROMPT_MODULE_NAME_HINT.test(base))
4549
2603
  return;
4550
2604
  out.push({
@@ -4607,12 +2661,12 @@ function detectPromptFile(file, out) {
4607
2661
  const ext = extname(file.path).toLowerCase();
4608
2662
  if (!PROMPT_DOC_EXTENSIONS.has(ext))
4609
2663
  return;
4610
- const base = basename4(file.path).toLowerCase();
2664
+ const base = basename3(file.path).toLowerCase();
4611
2665
  if (NON_PROMPT_DOC_BASENAMES.has(base))
4612
2666
  return;
4613
2667
  if (/(^|\/)(docs?|documentation|examples?|\.github)\//i.test(file.path))
4614
2668
  return;
4615
- const dir = dirname7(file.path);
2669
+ const dir = dirname3(file.path);
4616
2670
  const nameHint = PROMPT_NAME_HINT.test(base);
4617
2671
  const dirHint = AGENTIC_DIR_HINT.test(dir);
4618
2672
  const score = instructionScore(file.text.slice(0, 20000));
@@ -4677,7 +2731,7 @@ function detectHarnessCandidatesInFiles(files) {
4677
2731
  function detectHarnessCandidates(rootDir) {
4678
2732
  const startedAt = Date.now();
4679
2733
  const skipped = [];
4680
- const root = resolve7(rootDir);
2734
+ const root = resolve3(rootDir);
4681
2735
  const inventory = inventoryFiles(root, skipped, {
4682
2736
  includeFile: (path) => PROMPT_DOC_EXTENSIONS.has(extname(path).toLowerCase())
4683
2737
  });
@@ -4703,9 +2757,9 @@ function detectHarnessCandidates(rootDir) {
4703
2757
  return redactDeep(report);
4704
2758
  }
4705
2759
  function writeHarnessCandidatesReport(report, outPath) {
4706
- const target = resolve7(outPath);
4707
- mkdirSync6(dirname7(target), { recursive: true });
4708
- writeFileSync7(target, `${JSON.stringify(report, null, 2)}
2760
+ const target = resolve3(outPath);
2761
+ mkdirSync2(dirname3(target), { recursive: true });
2762
+ writeFileSync2(target, `${JSON.stringify(report, null, 2)}
4709
2763
  `, "utf8");
4710
2764
  return target;
4711
2765
  }
@@ -5039,17 +3093,17 @@ function wrapLine(text, width, indent) {
5039
3093
  }
5040
3094
 
5041
3095
  // src/harness.ts
5042
- var MODA_DIR2 = ".moda";
5043
- var HARNESS_PATH = join7(MODA_DIR2, "harness.json");
5044
- var HARNESS_SUMMARY_PATH = join7(MODA_DIR2, "HARNESS.md");
5045
- var SYNC_STATE_PATH = join7(MODA_DIR2, "sync-state.json");
3096
+ var MODA_DIR = ".moda";
3097
+ var HARNESS_PATH = join2(MODA_DIR, "harness.json");
3098
+ var HARNESS_SUMMARY_PATH = join2(MODA_DIR, "HARNESS.md");
3099
+ var SYNC_STATE_PATH = join2(MODA_DIR, "sync-state.json");
5046
3100
  var SCHEMA_VERSION = "harness.v0.1";
5047
3101
  var REPORT_JSON_BEGIN = "MODA_HARNESS_REPORT_JSON_BEGIN";
5048
3102
  var REPORT_JSON_END = "MODA_HARNESS_REPORT_JSON_END";
5049
3103
  var EXTERNAL_ANALYST_ADAPTERS = ["claude", "codex", "cursor"];
5050
3104
  async function runHarnessCommand(context) {
5051
3105
  const subcommand = context.positionals[0] ?? "scan";
5052
- const rootDir = resolve8(context.cwd, context.flags.path ?? ".");
3106
+ const rootDir = resolve4(context.cwd, context.flags.path ?? ".");
5053
3107
  const selectedAgentId = selectedRuntimeAgentId(context.flags);
5054
3108
  const tenant = resolveHarnessTenant(context.flags, context);
5055
3109
  const scanPlan = buildCommandScanPlan(context, rootDir);
@@ -5100,7 +3154,7 @@ async function runHarnessCommand(context) {
5100
3154
  throw new CliInputError("Harness report is not approved; refusing to sync.", "Run `moda harness validate-report`, review the report, then run `moda harness approve --yes` before syncing.");
5101
3155
  }
5102
3156
  const syncGraph2 = graphForReport(report.graph);
5103
- const sourceCommit2 = report.source.sourceCommit ?? getGitCommit3(rootDir);
3157
+ const sourceCommit2 = report.source.sourceCommit ?? getGitCommit(rootDir);
5104
3158
  const localGraphHash2 = hashStableJson(syncGraph2);
5105
3159
  const plan2 = createHarnessSyncPlan({
5106
3160
  context,
@@ -5156,7 +3210,7 @@ async function runHarnessCommand(context) {
5156
3210
  report: {
5157
3211
  reportId: report.reportId,
5158
3212
  reportHash,
5159
- path: normalizePath3(relative6(rootDir, reportPath)) || ".moda/harness-report.json"
3213
+ path: normalizePath3(relative3(rootDir, reportPath)) || ".moda/harness-report.json"
5160
3214
  },
5161
3215
  validation,
5162
3216
  approval: {
@@ -5180,7 +3234,7 @@ async function runHarnessCommand(context) {
5180
3234
  pathWasExplicit: context.flags.path !== undefined
5181
3235
  });
5182
3236
  const syncGraph = graphForSync(graph);
5183
- const sourceCommit = getGitCommit3(rootDir);
3237
+ const sourceCommit = getGitCommit(rootDir);
5184
3238
  const localGraphHash = hashStableJson2(syncGraph);
5185
3239
  const plan = createHarnessSyncPlan({
5186
3240
  context,
@@ -5272,6 +3326,23 @@ async function runHarnessCommand(context) {
5272
3326
  message: "Starting Moda Analyst harness analysis.",
5273
3327
  elapsed_ms: Date.now() - context.startedAt
5274
3328
  });
3329
+ if (context.flags["github-actions"] === "true") {
3330
+ const { runGithubActionsAnalyze } = await import("./harness-github-actions-7t3cncnt.js");
3331
+ const result = await runGithubActionsAnalyze(rootDir, {
3332
+ writeReport: (report2) => {
3333
+ const normalized = normalizeHarnessReport(report2);
3334
+ const validation2 = validateHarnessReport(normalized, rootDir);
3335
+ writeHarnessReportArtifacts({
3336
+ rootDir,
3337
+ report: normalized,
3338
+ validation: validation2,
3339
+ markdown: context.outputMode === "human"
3340
+ });
3341
+ }
3342
+ });
3343
+ context.output.writeData({ schema: "harness_github_actions.v0.1", ...result });
3344
+ return { exitCode: result.status === "completed" ? 0 : 1 };
3345
+ }
5275
3346
  if (isRemoteHarnessAnalyze(context)) {
5276
3347
  return runRemoteHarnessAnalyst({ context, rootDir, scanPlan, tenant });
5277
3348
  }
@@ -5309,7 +3380,7 @@ async function runHarnessCommand(context) {
5309
3380
  const report = createHarnessReport({
5310
3381
  graph,
5311
3382
  rootDir,
5312
- sourceCommit: getGitCommit3(rootDir),
3383
+ sourceCommit: getGitCommit(rootDir),
5313
3384
  analystAdapter: "local-scan"
5314
3385
  });
5315
3386
  const validation = validateHarnessReport(report, rootDir);
@@ -5373,7 +3444,7 @@ async function runHarnessCommand(context) {
5373
3444
  report: {
5374
3445
  reportId: approval.report.reportId,
5375
3446
  reportHash: hashHarnessReport(approval.report),
5376
- path: normalizePath3(relative6(rootDir, reportPath)) || ".moda/harness-report.json"
3447
+ path: normalizePath3(relative3(rootDir, reportPath)) || ".moda/harness-report.json"
5377
3448
  },
5378
3449
  validation: approval.validation,
5379
3450
  approval: approval.approval
@@ -5393,9 +3464,9 @@ async function runHarnessCommand(context) {
5393
3464
  throw new CliInputError("Missing output path for --out.", "Usage: moda harness candidates --out=<file>");
5394
3465
  }
5395
3466
  if (outFlag && !context.dryRun) {
5396
- written.push(writeHarnessCandidatesReport(report, resolve8(context.cwd, outFlag)));
3467
+ written.push(writeHarnessCandidatesReport(report, resolve4(context.cwd, outFlag)));
5397
3468
  }
5398
- writeHumanProgress(context, `Harness candidates: ${report.stats.candidateCount} candidates across ` + `${report.stats.filesWithCandidates} files (${report.stats.filesScanned} scanned, ` + `${report.stats.filesPerSecond.toLocaleString()} files/sec).`);
3469
+ writeHumanProgress(context, `Harness candidates: ${report.stats.candidateCount} candidates across ${report.stats.filesWithCandidates} files (${report.stats.filesScanned} scanned, ${report.stats.filesPerSecond.toLocaleString()} files/sec).`);
5399
3470
  context.output.writeData({ scanPlan, ...report, written, dryRun: context.dryRun });
5400
3471
  return { exitCode: 0 };
5401
3472
  }
@@ -5441,8 +3512,8 @@ async function runHarnessCommand(context) {
5441
3512
  return { exitCode: 0 };
5442
3513
  }
5443
3514
  case "status": {
5444
- const manifestPath = join7(rootDir, HARNESS_PATH);
5445
- const graph = existsSync9(manifestPath) ? readHarnessGraph(manifestPath) : scanHarness({
3515
+ const manifestPath = join2(rootDir, HARNESS_PATH);
3516
+ const graph = existsSync3(manifestPath) ? readHarnessGraph(manifestPath) : scanHarness({
5446
3517
  rootDir,
5447
3518
  tenantId: tenant?.tenantId,
5448
3519
  tenantSource: tenant?.source
@@ -5452,7 +3523,7 @@ async function runHarnessCommand(context) {
5452
3523
  const remoteRun = readRemoteRunState(rootDir);
5453
3524
  context.output.writeData({
5454
3525
  manifest: HARNESS_PATH,
5455
- exists: existsSync9(manifestPath),
3526
+ exists: existsSync3(manifestPath),
5456
3527
  summary: summarizeHarness(graph),
5457
3528
  graphHash: currentGraphHash,
5458
3529
  remoteRun: remoteRun ? {
@@ -5587,14 +3658,14 @@ async function runExternalHarnessAnalyst(options) {
5587
3658
  reportPath: HARNESS_REPORT_PATH
5588
3659
  }));
5589
3660
  }
5590
- const timeoutMs = numericEnv("MODA_HARNESS_ANALYST_TIMEOUT_MS", 20 * 60 * 1000);
3661
+ const timeoutMs = numericEnv("MODA_HARNESS_ANALYST_TIMEOUT_MS", 1200000);
5591
3662
  try {
5592
3663
  const result = await runExternalAnalystWithRetries(invocation, {
5593
3664
  cwd: options.rootDir,
5594
3665
  env: buildExternalAnalystEnv(options.context.env),
5595
3666
  inheritedOutput,
5596
3667
  timeoutMs,
5597
- maxBuffer: 20 * 1024 * 1024,
3668
+ maxBuffer: 20971520,
5598
3669
  progress: inheritedOutput && !options.context.quiet || options.context.outputMode === "agent-stream",
5599
3670
  progressToStderr: inheritedOutput && !options.context.quiet && !analystTui,
5600
3671
  streamEvents: invocation.streamEvents,
@@ -5704,8 +3775,8 @@ function buildExternalAnalystEnv(env) {
5704
3775
  return next;
5705
3776
  }
5706
3777
  function readExternalAnalystReport(rootDir, result, adapter) {
5707
- const reportPath = resolve8(rootDir, HARNESS_REPORT_PATH);
5708
- if (existsSync9(reportPath))
3778
+ const reportPath = resolve4(rootDir, HARNESS_REPORT_PATH);
3779
+ if (existsSync3(reportPath))
5709
3780
  return repairExternalAnalystReport(readHarnessReport(reportPath), rootDir);
5710
3781
  const output = [
5711
3782
  result.stdout,
@@ -5791,12 +3862,12 @@ function repairCitationLines(report, rootDir) {
5791
3862
  if (citation.type !== "file" && citation.type !== "generated_doc" || !citation.path || !citation.excerpt)
5792
3863
  continue;
5793
3864
  const excerpt = citation.excerpt;
5794
- const candidatePath = resolve8(rootDir, citation.path);
5795
- if (!existsSync9(candidatePath))
3865
+ const candidatePath = resolve4(rootDir, citation.path);
3866
+ if (!existsSync3(candidatePath))
5796
3867
  continue;
5797
3868
  let text;
5798
3869
  try {
5799
- text = readFileSync7(candidatePath, "utf8");
3870
+ text = readFileSync3(candidatePath, "utf8");
5800
3871
  } catch {
5801
3872
  continue;
5802
3873
  }
@@ -6015,7 +4086,7 @@ async function runExternalAnalystWithRetries(invocation, options) {
6015
4086
  process.stderr.write(`${renderHarnessProgress(message)}
6016
4087
  `);
6017
4088
  if (options.backoffMs > 0)
6018
- await sleep2(options.backoffMs);
4089
+ await sleep(options.backoffMs);
6019
4090
  }
6020
4091
  lastResult = await runExternalAnalystProcess(invocation, options);
6021
4092
  if (!isRetryableExternalAnalystFailure(lastResult))
@@ -6036,7 +4107,7 @@ function externalAnalystMaxAttempts(adapter) {
6036
4107
  const defaultRetries = adapter === "claude" ? 2 : 1;
6037
4108
  return 1 + numericEnvAllowZero("MODA_HARNESS_ANALYST_RETRIES", defaultRetries);
6038
4109
  }
6039
- function sleep2(ms) {
4110
+ function sleep(ms) {
6040
4111
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
6041
4112
  }
6042
4113
  function recordExternalAgentTextChunk(text, tui) {
@@ -6336,13 +4407,13 @@ async function runCodingAgent(options) {
6336
4407
  stream,
6337
4408
  mode: options.mode
6338
4409
  });
6339
- const timeoutMs = options.timeoutMs ?? numericEnv("MODA_HARNESS_ANALYST_TIMEOUT_MS", 20 * 60 * 1000);
4410
+ const timeoutMs = options.timeoutMs ?? numericEnv("MODA_HARNESS_ANALYST_TIMEOUT_MS", 1200000);
6340
4411
  const result = await runExternalAnalystWithRetries(invocation, {
6341
4412
  cwd: options.rootDir,
6342
4413
  env: buildExternalAnalystEnv(options.env ?? process.env),
6343
4414
  inheritedOutput: false,
6344
4415
  timeoutMs,
6345
- maxBuffer: 20 * 1024 * 1024,
4416
+ maxBuffer: 20971520,
6346
4417
  progress: Boolean(options.onProgress) || Boolean(options.progressToStderr),
6347
4418
  progressToStderr: options.progressToStderr ?? false,
6348
4419
  streamEvents: stream,
@@ -6483,10 +4554,10 @@ function listAnalystPreflightPaths(rootDir) {
6483
4554
  }
6484
4555
  function readGitTrackedPaths(rootDir) {
6485
4556
  try {
6486
- return execFileSync3("git", ["ls-files", "-co", "--exclude-standard"], {
4557
+ return execFileSync("git", ["ls-files", "-co", "--exclude-standard"], {
6487
4558
  cwd: rootDir,
6488
4559
  encoding: "utf8",
6489
- maxBuffer: 4 * 1024 * 1024
4560
+ maxBuffer: 4194304
6490
4561
  }).split(/\r?\n/).filter(Boolean);
6491
4562
  } catch {
6492
4563
  return [];
@@ -6497,10 +4568,10 @@ function readDirectoryPreflightPaths(rootDir) {
6497
4568
  const stack = ["."];
6498
4569
  while (stack.length > 0 && paths.length < 2000) {
6499
4570
  const current = stack.pop() ?? ".";
6500
- const absolute = current === "." ? rootDir : join7(rootDir, current);
4571
+ const absolute = current === "." ? rootDir : join2(rootDir, current);
6501
4572
  let entries;
6502
4573
  try {
6503
- entries = readdirSync4(absolute);
4574
+ entries = readdirSync(absolute);
6504
4575
  } catch {
6505
4576
  continue;
6506
4577
  }
@@ -6508,10 +4579,10 @@ function readDirectoryPreflightPaths(rootDir) {
6508
4579
  const child = current === "." ? entry : `${current}/${entry}`;
6509
4580
  if (shouldSkipPreflightPath(child))
6510
4581
  continue;
6511
- const childAbsolute = join7(rootDir, child);
4582
+ const childAbsolute = join2(rootDir, child);
6512
4583
  let stat;
6513
4584
  try {
6514
- stat = lstatSync5(childAbsolute);
4585
+ stat = lstatSync3(childAbsolute);
6515
4586
  } catch {
6516
4587
  continue;
6517
4588
  }
@@ -6536,7 +4607,7 @@ function shouldSkipPreflightPath(path) {
6536
4607
  return isSecretishPath(normalized);
6537
4608
  }
6538
4609
  function isManifestLikePath(path) {
6539
- const name = basename5(path).toLowerCase();
4610
+ const name = basename4(path).toLowerCase();
6540
4611
  return /^(package\.json|pyproject\.toml|requirements.*\.txt|uv\.lock|poetry\.lock|pnpm-lock\.yaml|bun\.lockb?|yarn\.lock|cargo\.toml|go\.mod|deno\.json|turbo\.json|langgraph\.json|agents\.json|config\.(ts|js|json|yaml|yml|toml))$/.test(name);
6541
4612
  }
6542
4613
  function isDocLikePath(path) {
@@ -6566,15 +4637,15 @@ function resolveAnalystCommand(adapter) {
6566
4637
  return findOnPath("cursor-agent") || findOnPath("cursor-cli") || findOnPath("cursor") || findOnPath("/Applications/Cursor.app/Contents/Resources/app/bin/cursor");
6567
4638
  }
6568
4639
  function cursorSubcommandArgs(command, args) {
6569
- const name = basename5(command);
4640
+ const name = basename4(command);
6570
4641
  return name === "cursor" || name === "cursor-cli" ? ["agent", ...args] : args;
6571
4642
  }
6572
4643
  function findOnPath(command) {
6573
- if (command.includes("/") && existsSync9(command))
4644
+ if (command.includes("/") && existsSync3(command))
6574
4645
  return command;
6575
4646
  for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
6576
- const candidate = join7(dir, command);
6577
- if (existsSync9(candidate))
4647
+ const candidate = join2(dir, command);
4648
+ if (existsSync3(candidate))
6578
4649
  return candidate;
6579
4650
  }
6580
4651
  return;
@@ -6587,7 +4658,7 @@ function truncateText(value, maxLength) {
6587
4658
  return compact.length > maxLength ? `${compact.slice(0, maxLength - 1)}...` : compact;
6588
4659
  }
6589
4660
  function scanHarness(options = {}) {
6590
- const rootDir = resolve8(options.rootDir ?? process.cwd());
4661
+ const rootDir = resolve4(options.rootDir ?? process.cwd());
6591
4662
  const skipped = [];
6592
4663
  const files = inventoryFiles(rootDir, skipped);
6593
4664
  const annotations = files.flatMap((file) => parseAnnotations(file));
@@ -6595,7 +4666,7 @@ function scanHarness(options = {}) {
6595
4666
  const readPlan = buildHarnessReadPlan(rootDir, options.pathWasExplicit === true);
6596
4667
  const graph = {
6597
4668
  schemaVersion: SCHEMA_VERSION,
6598
- harnessId: stableId2("harness", repo.name || basename5(rootDir) || "repo"),
4669
+ harnessId: stableId2("harness", repo.name || basename4(rootDir) || "repo"),
6599
4670
  tenant: options.tenantId ? {
6600
4671
  tenantId: options.tenantId,
6601
4672
  source: options.tenantSource ?? "scan_option"
@@ -6650,11 +4721,11 @@ function collectSnapshotInputs(rootDir, maxFiles, maxTextBytes) {
6650
4721
  const inputs = [];
6651
4722
  let inventoryFileCount = 0;
6652
4723
  let inventoryTextBytes = 0;
6653
- function walk2(current) {
6654
- const path = normalizePath3(relative6(rootDir, current)) || ".";
4724
+ function walk(current) {
4725
+ const path = normalizePath3(relative3(rootDir, current)) || ".";
6655
4726
  let stat;
6656
4727
  try {
6657
- stat = lstatSync5(current);
4728
+ stat = lstatSync3(current);
6658
4729
  } catch {
6659
4730
  return;
6660
4731
  }
@@ -6665,8 +4736,8 @@ function collectSnapshotInputs(rootDir, maxFiles, maxTextBytes) {
6665
4736
  if (stat.isDirectory()) {
6666
4737
  if (path !== "." && (isSkippedDir(path) || isSkippedPathPrefix(path)))
6667
4738
  return;
6668
- for (const entry of readdirSync4(current).sort())
6669
- walk2(join7(current, entry));
4739
+ for (const entry of readdirSync(current).sort())
4740
+ walk(join2(current, entry));
6670
4741
  return;
6671
4742
  }
6672
4743
  if (!stat.isFile())
@@ -6689,7 +4760,7 @@ function collectSnapshotInputs(rootDir, maxFiles, maxTextBytes) {
6689
4760
  let text = "";
6690
4761
  if (isTextFile(path)) {
6691
4762
  try {
6692
- text = readFileSync7(current, "utf8");
4763
+ text = readFileSync3(current, "utf8");
6693
4764
  inventoryTextBytes += Buffer.byteLength(text, "utf8");
6694
4765
  } catch {
6695
4766
  return;
@@ -6698,21 +4769,21 @@ function collectSnapshotInputs(rootDir, maxFiles, maxTextBytes) {
6698
4769
  inputs.push({ path, text, byteSize: stat.size });
6699
4770
  inventoryFileCount += 1;
6700
4771
  }
6701
- walk2(rootDir);
4772
+ walk(rootDir);
6702
4773
  return inputs;
6703
4774
  }
6704
4775
  function buildSourceSnapshot(rootDir) {
6705
- const root = resolve8(rootDir);
4776
+ const root = resolve4(rootDir);
6706
4777
  const maxFiles = numericEnv("MODA_HARNESS_MAX_FILES", DEFAULT_MAX_INVENTORY_FILES);
6707
4778
  const maxTextBytes = numericEnv("MODA_HARNESS_MAX_TEXT_BYTES", DEFAULT_MAX_INVENTORY_TEXT_BYTES);
6708
4779
  const inputs = collectSnapshotInputs(root, maxFiles, maxTextBytes);
6709
4780
  const repo = detectRepo(root, inputs.map((input) => ({
6710
4781
  path: input.path,
6711
- absPath: join7(root, input.path),
4782
+ absPath: join2(root, input.path),
6712
4783
  rootDir: root,
6713
4784
  text: input.text
6714
4785
  })));
6715
- const gitCommit = getGitCommit3(root);
4786
+ const gitCommit = getGitCommit(root);
6716
4787
  return packSnapshot(inputs, {
6717
4788
  repoName: repo.name,
6718
4789
  ...gitCommit ? { gitCommit } : {},
@@ -6722,7 +4793,7 @@ function buildSourceSnapshot(rootDir) {
6722
4793
  }
6723
4794
  function buildCommandScanPlan(context, rootDir) {
6724
4795
  const explicitPath = context.flags.path !== undefined;
6725
- const rel = normalizePath3(relative6(context.cwd, rootDir)) || ".";
4796
+ const rel = normalizePath3(relative3(context.cwd, rootDir)) || ".";
6726
4797
  const scope = explicitPath && rel !== "." ? "narrow" : "broad";
6727
4798
  const machineReadable = context.outputMode !== "human";
6728
4799
  const approvalRequired = context.positional === "analyze" && scope === "broad" && !context.yes;
@@ -6759,7 +4830,7 @@ async function approveScanIfNeeded(scanPlan, options = {}) {
6759
4830
  willSkip: scanPlan.readPlan.willSkip
6760
4831
  }));
6761
4832
  }
6762
- const approved = await confirm3({
4833
+ const approved = await confirm2({
6763
4834
  message: options.message ?? "Scan this project to build a Moda harness manifest?",
6764
4835
  default: true
6765
4836
  });
@@ -6785,7 +4856,7 @@ async function confirmHarnessDelete(context, idOrKey) {
6785
4856
  resume_command: ["moda", "harness", "delete", idOrKey, "--yes"]
6786
4857
  }, "Re-run with `--yes` to confirm the deletion.");
6787
4858
  }
6788
- const approved = await confirm3({ message, default: false });
4859
+ const approved = await confirm2({ message, default: false });
6789
4860
  if (!approved) {
6790
4861
  throw new CliInputError("Harness delete was not approved.", "Re-run with `--yes` to skip the prompt, or leave the harness in place.");
6791
4862
  }
@@ -6807,7 +4878,7 @@ function writeHumanBlock(context, message) {
6807
4878
  function buildHarnessReadPlan(rootDir, pathWasExplicit) {
6808
4879
  return {
6809
4880
  scope: pathWasExplicit ? "narrow" : "broad",
6810
- rootPaths: [normalizePath3(relative6(rootDir, rootDir)) || "."],
4881
+ rootPaths: [normalizePath3(relative3(rootDir, rootDir)) || "."],
6811
4882
  willRead: [
6812
4883
  "package manifests",
6813
4884
  "source files under the selected path",
@@ -6925,11 +4996,11 @@ async function syncHarnessWithRemote(context, body) {
6925
4996
  }, profileOptions);
6926
4997
  }
6927
4998
  function readHarnessSyncState(rootDir) {
6928
- const path = join7(rootDir, SYNC_STATE_PATH);
6929
- if (!existsSync9(path))
4999
+ const path = join2(rootDir, SYNC_STATE_PATH);
5000
+ if (!existsSync3(path))
6930
5001
  return;
6931
5002
  try {
6932
- const parsed = JSON.parse(readFileSync7(path, "utf8"));
5003
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
6933
5004
  if (!isRecord3(parsed) || parsed.schemaVersion !== "sync_state.v0.1") {
6934
5005
  return;
6935
5006
  }
@@ -6939,9 +5010,9 @@ function readHarnessSyncState(rootDir) {
6939
5010
  }
6940
5011
  }
6941
5012
  function writeHarnessSyncState(rootDir, state) {
6942
- const path = join7(rootDir, SYNC_STATE_PATH);
6943
- mkdirSync7(dirname8(path), { recursive: true });
6944
- writeFileSync8(path, `${JSON.stringify(state, null, 2)}
5013
+ const path = join2(rootDir, SYNC_STATE_PATH);
5014
+ mkdirSync3(dirname4(path), { recursive: true });
5015
+ writeFileSync3(path, `${JSON.stringify(state, null, 2)}
6945
5016
  `);
6946
5017
  return SYNC_STATE_PATH;
6947
5018
  }
@@ -6953,11 +5024,11 @@ function selectedRuntimeAgentId(flags) {
6953
5024
  return;
6954
5025
  }
6955
5026
  function loadOrScanHarness(rootDir, tenant, context) {
6956
- const manifestPath = join7(rootDir, HARNESS_PATH);
6957
- if (existsSync9(manifestPath))
5027
+ const manifestPath = join2(rootDir, HARNESS_PATH);
5028
+ if (existsSync3(manifestPath))
6958
5029
  return readHarnessGraph(manifestPath);
6959
5030
  if (context) {
6960
- writeHarnessNotice(context, `No ${HARNESS_PATH} found; showing a live scan of ${rootDir}. ` + "This is local scan output, NOT synced or account data. " + "Run `moda harness scan` to persist it and `moda harness sync` to upload it.");
5031
+ writeHarnessNotice(context, `No ${HARNESS_PATH} found; showing a live scan of ${rootDir}. This is local scan output, NOT synced or account data. Run \`moda harness scan\` to persist it and \`moda harness sync\` to upload it.`);
6961
5032
  }
6962
5033
  return scanHarness({
6963
5034
  rootDir,
@@ -7010,43 +5081,43 @@ var FRESHER_REPORT_MAX_DIRS = 4000;
7010
5081
  function findFresherHarnessReports(rootDir) {
7011
5082
  let rootMtimeMs = 0;
7012
5083
  try {
7013
- rootMtimeMs = lstatSync5(join7(rootDir, HARNESS_REPORT_PATH)).mtimeMs;
5084
+ rootMtimeMs = lstatSync3(join2(rootDir, HARNESS_REPORT_PATH)).mtimeMs;
7014
5085
  } catch {
7015
5086
  rootMtimeMs = 0;
7016
5087
  }
7017
5088
  const fresher = [];
7018
5089
  let visited = 0;
7019
- const walk2 = (dir, depth) => {
5090
+ const walk = (dir, depth) => {
7020
5091
  if (depth > FRESHER_REPORT_MAX_DEPTH || visited >= FRESHER_REPORT_MAX_DIRS)
7021
5092
  return;
7022
5093
  visited += 1;
7023
5094
  let entries;
7024
5095
  try {
7025
- entries = readdirSync4(dir, { withFileTypes: true });
5096
+ entries = readdirSync(dir, { withFileTypes: true });
7026
5097
  } catch {
7027
5098
  return;
7028
5099
  }
7029
5100
  for (const entry of entries) {
7030
5101
  if (!entry.isDirectory() || entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name))
7031
5102
  continue;
7032
- const child = join7(dir, entry.name);
5103
+ const child = join2(dir, entry.name);
7033
5104
  try {
7034
- const candidate = lstatSync5(join7(child, HARNESS_REPORT_PATH));
5105
+ const candidate = lstatSync3(join2(child, HARNESS_REPORT_PATH));
7035
5106
  if (candidate.isFile() && candidate.mtimeMs > rootMtimeMs) {
7036
5107
  fresher.push({
7037
- path: normalizePath3(relative6(rootDir, join7(child, HARNESS_REPORT_PATH))),
5108
+ path: normalizePath3(relative3(rootDir, join2(child, HARNESS_REPORT_PATH))),
7038
5109
  mtimeMs: candidate.mtimeMs
7039
5110
  });
7040
5111
  }
7041
5112
  } catch {}
7042
- walk2(child, depth + 1);
5113
+ walk(child, depth + 1);
7043
5114
  }
7044
5115
  };
7045
- walk2(rootDir, 1);
5116
+ walk(rootDir, 1);
7046
5117
  return fresher.sort((a, b) => b.mtimeMs - a.mtimeMs);
7047
5118
  }
7048
5119
  function warnFresherSubdirHarnessReports(context, rootDir, explicitReport) {
7049
- const explicitReportPath = explicitReport && explicitReport !== "true" ? normalizePath3(relative6(rootDir, resolve8(rootDir, explicitReport))) : undefined;
5120
+ const explicitReportPath = explicitReport && explicitReport !== "true" ? normalizePath3(relative3(rootDir, resolve4(rootDir, explicitReport))) : undefined;
7050
5121
  if (explicitReportPath && explicitReportPath !== normalizePath3(HARNESS_REPORT_PATH))
7051
5122
  return;
7052
5123
  let fresher;
@@ -7058,18 +5129,18 @@ function warnFresherSubdirHarnessReports(context, rootDir, explicitReport) {
7058
5129
  if (fresher.length === 0)
7059
5130
  return;
7060
5131
  const newest = fresher[0];
7061
- const subdir = normalizePath3(dirname8(dirname8(newest.path)));
7062
- const rootState = existsSync9(join7(rootDir, HARNESS_REPORT_PATH)) ? `older ${HARNESS_REPORT_PATH}` : `no ${HARNESS_REPORT_PATH}`;
7063
- writeHarnessNotice(context, `A fresher harness report exists at ${newest.path} (this directory has ${rootState}). ` + `If you meant that report, re-run with --path=${subdir}.`);
5132
+ const subdir = normalizePath3(dirname4(dirname4(newest.path)));
5133
+ const rootState = existsSync3(join2(rootDir, HARNESS_REPORT_PATH)) ? `older ${HARNESS_REPORT_PATH}` : `no ${HARNESS_REPORT_PATH}`;
5134
+ writeHarnessNotice(context, `A fresher harness report exists at ${newest.path} (this directory has ${rootState}). If you meant that report, re-run with --path=${subdir}.`);
7064
5135
  }
7065
5136
  function readHarnessGraph(manifestPath) {
7066
- return JSON.parse(readFileSync7(manifestPath, "utf8"));
5137
+ return JSON.parse(readFileSync3(manifestPath, "utf8"));
7067
5138
  }
7068
5139
  function writeHarnessArtifacts(graph, rootDir, options = {}) {
7069
- const modaDir = join7(rootDir, MODA_DIR2);
7070
- mkdirSync7(modaDir, { recursive: true });
7071
- const manifestPath = join7(rootDir, HARNESS_PATH);
7072
- writeFileSync8(manifestPath, `${JSON.stringify(graph, null, 2)}
5140
+ const modaDir = join2(rootDir, MODA_DIR);
5141
+ mkdirSync3(modaDir, { recursive: true });
5142
+ const manifestPath = join2(rootDir, HARNESS_PATH);
5143
+ writeFileSync3(manifestPath, `${JSON.stringify(graph, null, 2)}
7073
5144
  `);
7074
5145
  const written = [HARNESS_PATH];
7075
5146
  if (options.summary) {
@@ -7078,9 +5149,9 @@ function writeHarnessArtifacts(graph, rootDir, options = {}) {
7078
5149
  return written;
7079
5150
  }
7080
5151
  function writeHarnessSummary(graph, rootDir) {
7081
- const summaryPath = join7(rootDir, HARNESS_SUMMARY_PATH);
7082
- mkdirSync7(dirname8(summaryPath), { recursive: true });
7083
- writeFileSync8(summaryPath, renderHarnessMarkdown(graph));
5152
+ const summaryPath = join2(rootDir, HARNESS_SUMMARY_PATH);
5153
+ mkdirSync3(dirname4(summaryPath), { recursive: true });
5154
+ writeFileSync3(summaryPath, renderHarnessMarkdown(graph));
7084
5155
  return HARNESS_SUMMARY_PATH;
7085
5156
  }
7086
5157
  function renderHarnessMarkdown(graph) {
@@ -7176,7 +5247,7 @@ function renderFrameworkAndProviderLines(graph) {
7176
5247
  return lines;
7177
5248
  }
7178
5249
  function graphForSync(graph) {
7179
- return sortJson3({
5250
+ return sortJson2({
7180
5251
  ...graph,
7181
5252
  scan: {
7182
5253
  ...graph.scan,
@@ -7187,19 +5258,19 @@ function graphForSync(graph) {
7187
5258
  });
7188
5259
  }
7189
5260
  function hashStableJson2(value) {
7190
- return hashText(JSON.stringify(sortJson3(value)));
5261
+ return hashText(JSON.stringify(sortJson2(value)));
7191
5262
  }
7192
- function sortJson3(value) {
5263
+ function sortJson2(value) {
7193
5264
  if (Array.isArray(value))
7194
- return value.map((item) => sortJson3(item));
5265
+ return value.map((item) => sortJson2(item));
7195
5266
  if (value && typeof value === "object") {
7196
- return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson3(item)]));
5267
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson2(item)]));
7197
5268
  }
7198
5269
  return value;
7199
5270
  }
7200
- function getGitCommit3(rootDir) {
5271
+ function getGitCommit(rootDir) {
7201
5272
  try {
7202
- return execFileSync3("git", ["rev-parse", "HEAD"], {
5273
+ return execFileSync("git", ["rev-parse", "HEAD"], {
7203
5274
  cwd: rootDir,
7204
5275
  encoding: "utf8",
7205
5276
  stdio: ["ignore", "pipe", "ignore"]
@@ -7221,15 +5292,15 @@ function explainAgent(graph, agent) {
7221
5292
  }
7222
5293
  function inventoryFiles(rootDir, skipped, options = {}) {
7223
5294
  const files = [];
7224
- const rootReal = resolve8(rootDir);
5295
+ const rootReal = resolve4(rootDir);
7225
5296
  const maxFiles = numericEnv("MODA_HARNESS_MAX_FILES", DEFAULT_MAX_INVENTORY_FILES);
7226
5297
  const maxTextBytes = numericEnv("MODA_HARNESS_MAX_TEXT_BYTES", DEFAULT_MAX_INVENTORY_TEXT_BYTES);
7227
5298
  let textBytes = 0;
7228
- function walk2(current) {
7229
- const rel = normalizePath3(relative6(rootReal, current)) || ".";
5299
+ function walk(current) {
5300
+ const rel = normalizePath3(relative3(rootReal, current)) || ".";
7230
5301
  let stat;
7231
5302
  try {
7232
- stat = lstatSync5(current);
5303
+ stat = lstatSync3(current);
7233
5304
  } catch {
7234
5305
  recordSkipped(skipped, { path: rel, reason: "stat failed" });
7235
5306
  return;
@@ -7243,8 +5314,8 @@ function inventoryFiles(rootDir, skipped, options = {}) {
7243
5314
  recordSkipped(skipped, { path: rel, reason: "ignored directory" });
7244
5315
  return;
7245
5316
  }
7246
- for (const entry of readdirSync4(current).sort()) {
7247
- walk2(join7(current, entry));
5317
+ for (const entry of readdirSync(current).sort()) {
5318
+ walk(join2(current, entry));
7248
5319
  }
7249
5320
  return;
7250
5321
  }
@@ -7275,7 +5346,7 @@ function inventoryFiles(rootDir, skipped, options = {}) {
7275
5346
  recordSkipped(skipped, { path: rel, reason: "scan text budget exceeded" });
7276
5347
  } else {
7277
5348
  try {
7278
- item.text = readFileSync7(current, "utf8");
5349
+ item.text = readFileSync3(current, "utf8");
7279
5350
  textBytes += Buffer.byteLength(item.text, "utf8");
7280
5351
  } catch {
7281
5352
  recordSkipped(skipped, { path: rel, reason: "text read failed" });
@@ -7284,7 +5355,7 @@ function inventoryFiles(rootDir, skipped, options = {}) {
7284
5355
  }
7285
5356
  files.push(item);
7286
5357
  }
7287
- walk2(rootReal);
5358
+ walk(rootReal);
7288
5359
  return files;
7289
5360
  }
7290
5361
  function shouldSkipDirectory(rel) {
@@ -7330,7 +5401,7 @@ function numericEnvAllowZero(name, fallback) {
7330
5401
  function detectRepo(rootDir, files) {
7331
5402
  const packageJson = files.find((file) => file.path === "package.json");
7332
5403
  const pyproject = files.find((file) => file.path === "pyproject.toml");
7333
- const name = packageJson?.text ? safePackageName(packageJson.text) ?? basename5(rootDir) : pyproject?.text ? safeTomlName(pyproject.text) ?? basename5(rootDir) : basename5(rootDir);
5404
+ const name = packageJson?.text ? safePackageName(packageJson.text) ?? basename4(rootDir) : pyproject?.text ? safeTomlName(pyproject.text) ?? basename4(rootDir) : basename4(rootDir);
7334
5405
  const packageManagers = [
7335
5406
  files.some((file) => file.path === "bun.lock" || file.path === "bun.lockb") ? "bun" : null,
7336
5407
  files.some((file) => file.path === "pnpm-lock.yaml") ? "pnpm" : null,
@@ -7442,7 +5513,7 @@ function runtimeAgentFromAnnotation(annotation) {
7442
5513
  kind: "runtime_agent",
7443
5514
  purpose: first2(values.purpose) ?? first2(values.description),
7444
5515
  familyId: first2(values.family),
7445
- root: normalizePath3(first2(values.root) ?? dirname8(annotation.evidence.path)),
5516
+ root: normalizePath3(first2(values.root) ?? dirname4(annotation.evidence.path)),
7446
5517
  entrypointIds: values.entrypoint ?? [stableId2("entry", annotation.evidence.path)],
7447
5518
  channelAdapterIds: values.channel ?? [],
7448
5519
  artifactIds: [...values.artifact ?? [], ...values.prompt ?? [], ...values.tool ?? []],
@@ -7582,7 +5653,7 @@ function maybeAddPromptArtifact(graph, file) {
7582
5653
  if (!isPrompt)
7583
5654
  return;
7584
5655
  const id = stableId2("prompt", file.path);
7585
- const text = file.text ?? readFileSync7(file.absPath, "utf8");
5656
+ const text = file.text ?? readFileSync3(file.absPath, "utf8");
7586
5657
  upsertArtifact(graph, {
7587
5658
  id,
7588
5659
  type: "prompt",
@@ -7604,7 +5675,7 @@ function maybeAddSkillArtifact(graph, file) {
7604
5675
  upsertArtifact(graph, {
7605
5676
  id: stableId2("skill", file.path),
7606
5677
  type: "skill",
7607
- name: titleFromId2(basename5(file.path).replace(/\.(md|mdc)$/i, "")),
5678
+ name: titleFromId2(basename4(file.path).replace(/\.(md|mdc)$/i, "")),
7608
5679
  scope: "global",
7609
5680
  sourcePath: file.path,
7610
5681
  contentHash: hashText(file.text ?? ""),
@@ -7647,7 +5718,7 @@ function inferRuntimeAgent(file, graph) {
7647
5718
  return null;
7648
5719
  if (!/\.(ts|tsx|js|mjs|py)$/i.test(file.path))
7649
5720
  return null;
7650
- if (basename5(file.path) === "__init__.py")
5721
+ if (basename4(file.path) === "__init__.py")
7651
5722
  return null;
7652
5723
  const loweredPath = file.path.toLowerCase();
7653
5724
  const loweredText = file.text.toLowerCase();
@@ -7678,7 +5749,7 @@ function inferRuntimeAgent(file, graph) {
7678
5749
  name: titleFromId2(file.path.replace(/\.(ts|tsx|js|mjs|py)$/i, "")),
7679
5750
  kind: "runtime_agent",
7680
5751
  purpose: inferAgentPurpose(file),
7681
- root: normalizePath3(dirname8(file.path)),
5752
+ root: normalizePath3(dirname4(file.path)),
7682
5753
  entrypointIds: [entryId],
7683
5754
  channelAdapterIds: channelIds,
7684
5755
  artifactIds,
@@ -7754,7 +5825,7 @@ function inferArtifactsForFile(file, graph) {
7754
5825
  return false;
7755
5826
  if (artifact.sourcePath === file.path)
7756
5827
  return true;
7757
- const base = basename5(artifact.sourcePath);
5828
+ const base = basename4(artifact.sourcePath);
7758
5829
  if (text.includes(base) || text.includes(artifact.sourcePath))
7759
5830
  return true;
7760
5831
  if (artifact.type === "tool") {
@@ -8401,7 +6472,7 @@ function upsertDeployment(graph, next) {
8401
6472
  }
8402
6473
  function contentHashForAnnotatedSource(sourcePath, annotation) {
8403
6474
  const bodyAnnotation = annotation;
8404
- const rootCandidate = resolve8(annotation.rootDir, sourcePath);
6475
+ const rootCandidate = resolve4(annotation.rootDir, sourcePath);
8405
6476
  const candidates = [
8406
6477
  rootCandidate,
8407
6478
  ...annotatedSourceCandidates(sourcePath, bodyAnnotation).filter((c) => c !== rootCandidate)
@@ -8418,14 +6489,14 @@ function contentHashForAnnotatedSource(sourcePath, annotation) {
8418
6489
  return;
8419
6490
  }
8420
6491
  function readSmallTextFileForHash(absPath, rootDir) {
8421
- if (!existsSync9(absPath))
6492
+ if (!existsSync3(absPath))
8422
6493
  return;
8423
- const rel = normalizePath3(relative6(rootDir, absPath));
6494
+ const rel = normalizePath3(relative3(rootDir, absPath));
8424
6495
  if (isSecretishPath(rel))
8425
6496
  return;
8426
6497
  let stat;
8427
6498
  try {
8428
- stat = lstatSync5(absPath);
6499
+ stat = lstatSync3(absPath);
8429
6500
  } catch {
8430
6501
  return;
8431
6502
  }
@@ -8435,7 +6506,7 @@ function readSmallTextFileForHash(absPath, rootDir) {
8435
6506
  if (!isTextFile(rel))
8436
6507
  return;
8437
6508
  try {
8438
- return readFileSync7(absPath, "utf8");
6509
+ return readFileSync3(absPath, "utf8");
8439
6510
  } catch {
8440
6511
  return;
8441
6512
  }
@@ -8519,13 +6590,13 @@ function titleFromId2(id) {
8519
6590
  return id.replace(/^(agent|family|channel|prompt|tool|skill|eval|identity|deploy|entry|env)_/, "").split(/[_/.-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ") || id;
8520
6591
  }
8521
6592
  function promptNameFromPath(path) {
8522
- return titleFromId2(basename5(path).replace(/\.(prompt\.)?(md|json|ya?ml)$/i, ""));
6593
+ return titleFromId2(basename4(path).replace(/\.(prompt\.)?(md|json|ya?ml)$/i, ""));
8523
6594
  }
8524
6595
  function normalizePath3(path) {
8525
6596
  return path.split(sep).join("/");
8526
6597
  }
8527
6598
  function hashText(text) {
8528
- return createHash4("sha256").update(text).digest("hex");
6599
+ return createHash2("sha256").update(text).digest("hex");
8529
6600
  }
8530
6601
 
8531
6602
  // src/harness-remote.ts
@@ -8536,7 +6607,7 @@ var MIN_REMOTE_POLL_DELAY_MS = 250;
8536
6607
  var REMOTE_POLL_MAX_CONSECUTIVE_FAILURES = 4;
8537
6608
  var REMOTE_UPLOAD_TIMEOUT_MS = 120000;
8538
6609
  var REMOTE_STATUS_TIMEOUT_MS = 30000;
8539
- var HARNESS_REMOTE_RUN_STATE_PATH = join8(".moda", "harness-remote-run.json");
6610
+ var HARNESS_REMOTE_RUN_STATE_PATH = join3(".moda", "harness-remote-run.json");
8540
6611
  function isRemoteHarnessAnalyze(context) {
8541
6612
  const flag = context.flags.remote;
8542
6613
  const noRemote = context.flags["no-remote"] ?? context.flags.no_remote;
@@ -8551,11 +6622,11 @@ function isRemoteHarnessAnalyze(context) {
8551
6622
  return env !== "" && env !== "0" && env !== "false";
8552
6623
  }
8553
6624
  function readRemoteRunState(rootDir) {
8554
- const path = join8(rootDir, HARNESS_REMOTE_RUN_STATE_PATH);
8555
- if (!existsSync10(path))
6625
+ const path = join3(rootDir, HARNESS_REMOTE_RUN_STATE_PATH);
6626
+ if (!existsSync4(path))
8556
6627
  return;
8557
6628
  try {
8558
- const parsed = JSON.parse(readFileSync8(path, "utf8"));
6629
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
8559
6630
  if (!parsed || typeof parsed.run_id !== "string" || parsed.run_id.length === 0)
8560
6631
  return;
8561
6632
  return {
@@ -8569,8 +6640,8 @@ function readRemoteRunState(rootDir) {
8569
6640
  }
8570
6641
  function writeRemoteRunState(rootDir, state) {
8571
6642
  try {
8572
- mkdirSync8(join8(rootDir, ".moda"), { recursive: true });
8573
- writeFileSync9(join8(rootDir, HARNESS_REMOTE_RUN_STATE_PATH), `${JSON.stringify(state, null, 2)}
6643
+ mkdirSync4(join3(rootDir, ".moda"), { recursive: true });
6644
+ writeFileSync4(join3(rootDir, HARNESS_REMOTE_RUN_STATE_PATH), `${JSON.stringify(state, null, 2)}
8574
6645
  `);
8575
6646
  } catch {}
8576
6647
  }
@@ -8666,7 +6737,7 @@ async function pollRemoteAnalyzeUntilDone(options) {
8666
6737
  message: `Status poll failed (${consecutiveFailures}/${REMOTE_POLL_MAX_CONSECUTIVE_FAILURES}); retrying.`,
8667
6738
  elapsed_ms: Date.now() - context.startedAt
8668
6739
  });
8669
- await sleep2(Math.min(delay, Math.max(deadline - Date.now(), MIN_REMOTE_POLL_DELAY_MS)));
6740
+ await sleep(Math.min(delay, Math.max(deadline - Date.now(), MIN_REMOTE_POLL_DELAY_MS)));
8670
6741
  if (signal?.aborted)
8671
6742
  return last;
8672
6743
  delay = Math.min(delay * 2, capMs);
@@ -8686,7 +6757,7 @@ async function pollRemoteAnalyzeUntilDone(options) {
8686
6757
  message: `Server-side analysis status: ${last?.status ?? "running"}.`,
8687
6758
  elapsed_ms: Date.now() - context.startedAt
8688
6759
  });
8689
- await sleep2(Math.min(delay, remaining));
6760
+ await sleep(Math.min(delay, remaining));
8690
6761
  if (signal?.aborted)
8691
6762
  return last;
8692
6763
  delay = Math.min(delay * 2, capMs);
@@ -8906,872 +6977,4 @@ async function remoteAnalyzeFetch(url, apiKey, init, timeoutMs) {
8906
6977
  return response.json();
8907
6978
  }
8908
6979
 
8909
- // src/doctor.ts
8910
- import { createHash as createHash5 } from "node:crypto";
8911
- import { existsSync as existsSync11, readFileSync as readFileSync9, statSync } from "node:fs";
8912
- import { join as join9 } from "node:path";
8913
- async function runDoctorCommand(context) {
8914
- const report = await buildDoctorReport(context, {
8915
- offline: context.flags.offline === "true"
8916
- });
8917
- if (context.outputMode === "human") {
8918
- if (!context.quiet)
8919
- console.log(renderDoctorHuman(report));
8920
- return { exitCode: 0 };
8921
- }
8922
- context.output.writeData(report, {
8923
- status: report.summary.fail > 0 ? "error" : "success",
8924
- ...doctorAgentWriteOptions(report, "doctor")
8925
- });
8926
- return { exitCode: 0 };
8927
- }
8928
- async function runStatusCommand(context) {
8929
- const report = await buildDoctorReport(context, {
8930
- offline: context.flags.offline === "true" || context.flags.online !== "true"
8931
- });
8932
- const topBroken = firstBrokenCheck(report.checks);
8933
- const statusPayload = {
8934
- schemaVersion: "status.v0.1",
8935
- generatedAt: report.generatedAt,
8936
- status: report.summary.status,
8937
- activeProfile: report.profile.active,
8938
- auth: {
8939
- apiKey: resolveApiKey() ? "configured" : "missing"
8940
- },
8941
- workspace: {
8942
- tenantId: resolveTenantId() ?? null,
8943
- baseUrl: resolveModaBaseUrl(),
8944
- ingestUrl: resolveIngestUrl()
8945
- },
8946
- dataFlow: checkStatus(report.checks, "cloud_data_flow"),
8947
- harness: checkStatus(report.checks, "harness_manifest_current"),
8948
- harnessSync: checkStatus(report.checks, "harness_sync_state_present"),
8949
- harnessReport: checkStatus(report.checks, "harness_report_approved_synced"),
8950
- shadowSpark: checkStatus(report.checks, "shadow_spark_workspace_health"),
8951
- prompts: checkStatus(report.checks, "prompts_discovered"),
8952
- skills: checkStatus(report.checks, "skills_discovered"),
8953
- topBrokenCheck: topBroken ? {
8954
- id: topBroken.id,
8955
- status: topBroken.status,
8956
- severity: topBroken.severity,
8957
- nextCommand: topBroken.nextCommand
8958
- } : null,
8959
- nextCommands: report.nextCommands.slice(0, 3)
8960
- };
8961
- if (context.outputMode === "human") {
8962
- if (!context.quiet)
8963
- console.log(renderStatusHuman(statusPayload));
8964
- return { exitCode: 0 };
8965
- }
8966
- context.output.writeData(statusPayload, {
8967
- status: report.summary.fail > 0 ? "error" : "success",
8968
- ...doctorAgentWriteOptions(report, "status")
8969
- });
8970
- return { exitCode: 0 };
8971
- }
8972
- async function buildDoctorReport(context, options) {
8973
- const checks = [];
8974
- const profileConfig = loadProfileConfig({ env: context.env });
8975
- const activeProfile = getActiveProfile({ profile: context.profile, env: context.env });
8976
- const paths = getModaPaths({ env: context.env });
8977
- const profileOptions = { profile: context.profile, env: context.env };
8978
- const apiKey = resolveApiKey(profileOptions);
8979
- const tenantId = resolveTenantId(profileOptions);
8980
- const baseUrl = resolveModaBaseUrl(profileOptions);
8981
- const ingestUrl = resolveIngestUrl(profileOptions);
8982
- checks.push({
8983
- id: "active_profile_resolves",
8984
- group: "config",
8985
- title: "Active profile resolves",
8986
- status: activeProfile ? "pass" : "fail",
8987
- severity: activeProfile ? "info" : "critical",
8988
- evidence: [
8989
- { label: "active_profile", value: profileConfig.active_profile },
8990
- { label: "environment", value: activeProfile.environment }
8991
- ],
8992
- remediation: activeProfile ? undefined : "Create or select a Moda profile.",
8993
- nextCommand: activeProfile ? undefined : "moda profiles list"
8994
- });
8995
- checks.push({
8996
- id: "api_key_resolves",
8997
- group: "config",
8998
- title: "API key resolves",
8999
- status: apiKey ? "pass" : "fail",
9000
- severity: apiKey ? "info" : "critical",
9001
- evidence: [{ label: "source", value: apiKey ? "environment/profile/config" : "none" }],
9002
- remediation: apiKey ? undefined : "Authenticate and create a tenant API key.",
9003
- nextCommand: apiKey ? undefined : "moda init"
9004
- });
9005
- checks.push({
9006
- id: "tenant_resolves",
9007
- group: "config",
9008
- title: "Tenant/workspace resolves",
9009
- status: tenantId ? "pass" : "warning",
9010
- severity: tenantId ? "info" : "warning",
9011
- evidence: [{ label: "tenant_id", value: tenantId ? "configured" : "missing" }],
9012
- remediation: tenantId ? undefined : "Select a tenant during init or set MODA_TENANT_ID.",
9013
- nextCommand: tenantId ? undefined : "moda init"
9014
- });
9015
- checks.push(authTokenCheck(context, Boolean(apiKey)));
9016
- checks.push(configPermissionCheck(paths.legacyConfigPath));
9017
- if (activeProfile.api_key_ref) {
9018
- const secretPath = secretFilePathFromRef(activeProfile.api_key_ref);
9019
- if (secretPath)
9020
- checks.push(secretPermissionCheck(secretPath));
9021
- checks.push({
9022
- id: "profile_api_key_ref_resolves",
9023
- group: "config",
9024
- title: "Profile API key reference resolves",
9025
- status: resolveSecretReference(activeProfile.api_key_ref) ? "pass" : "warning",
9026
- severity: "warning",
9027
- evidence: [{ label: "api_key_ref", value: activeProfile.api_key_ref }],
9028
- remediation: "Create or update the profile API key reference.",
9029
- nextCommand: "moda profiles create"
9030
- });
9031
- }
9032
- checks.push(urlCheck("base_url_valid", "config", "Base URL is valid", baseUrl));
9033
- checks.push(urlCheck("ingest_url_valid", "config", "Ingest URL is valid", ingestUrl));
9034
- checks.push(localBaseUrlCheck(activeProfile.environment, baseUrl));
9035
- const manifestPath = join9(context.cwd, ".moda", "harness.json");
9036
- const manifestExists = existsSync11(manifestPath);
9037
- checks.push({
9038
- id: "harness_manifest_exists",
9039
- group: "harness",
9040
- title: "Harness manifest exists",
9041
- status: manifestExists ? "pass" : "warning",
9042
- severity: manifestExists ? "info" : "warning",
9043
- evidence: [{ label: "path", value: ".moda/harness.json" }],
9044
- remediation: manifestExists ? undefined : "Run `moda harness scan --summary` to scan the project and write a harness manifest.",
9045
- nextCommand: manifestExists ? undefined : "moda harness scan --summary"
9046
- });
9047
- checks.push(harnessCurrentCheck(context.cwd, manifestPath, manifestExists, tenantId));
9048
- checks.push(harnessSyncStateCheck(context.cwd));
9049
- checks.push(harnessReportStateCheck(context.cwd));
9050
- checks.push(discoveryCheck("prompts_discovered", "harness", "Prompts are discovered", context.cwd, [
9051
- "prompts",
9052
- ".moda/prompts.yml"
9053
- ], "moda prompts init"));
9054
- checks.push(discoveryCheck("skills_discovered", "harness", "Skills are discovered", context.cwd, [
9055
- "skills",
9056
- ".claude/skills",
9057
- ".cursor/rules"
9058
- ], "moda skills pull"));
9059
- checks.push(discoveryCheck("evals_discovered", "harness", "Evals/tests are discovered", context.cwd, [
9060
- "evals",
9061
- "tests",
9062
- "__tests__"
9063
- ]));
9064
- checks.push(sdkInitializationCheck(context.cwd));
9065
- checks.push(validationEventPathCheck(ingestUrl));
9066
- if (options.offline || !apiKey) {
9067
- checks.push(...offlineCloudChecks(options.offline));
9068
- } else {
9069
- checks.push(...await cloudDataFlowChecks(ingestUrl, apiKey));
9070
- }
9071
- const nextCommands = nextCommandsForChecks(checks);
9072
- const summary = summarizeChecks(checks);
9073
- return {
9074
- schemaVersion: "doctor.v0.1",
9075
- generatedAt: new Date().toISOString(),
9076
- profile: {
9077
- active: activeProfile.name,
9078
- requested: context.profile
9079
- },
9080
- summary,
9081
- checks,
9082
- nextCommands,
9083
- fixPlan: checks.filter((check) => check.status === "fail" || check.status === "warning").map((check) => ({
9084
- checkId: check.id,
9085
- command: check.nextCommand,
9086
- action: check.remediation ?? "Review this diagnostic."
9087
- }))
9088
- };
9089
- }
9090
- function authTokenCheck(context, hasApiKey) {
9091
- const session = loadAuthSession({
9092
- profile: context.profile,
9093
- env: context.env,
9094
- includeExpired: true
9095
- });
9096
- const valid = isAuthSessionValid(session);
9097
- if (!valid && hasApiKey) {
9098
- return {
9099
- id: "auth_token_valid",
9100
- group: "config",
9101
- title: "Browser auth token is valid",
9102
- status: "skipped",
9103
- severity: "info",
9104
- evidence: [{ label: "reason", value: "api key configured" }]
9105
- };
9106
- }
9107
- return {
9108
- id: "auth_token_valid",
9109
- group: "config",
9110
- title: "Browser auth token is valid",
9111
- status: valid ? "pass" : "warning",
9112
- severity: valid ? "info" : "warning",
9113
- evidence: [
9114
- { label: "token", value: session ? "present" : "missing" },
9115
- ...session ? [{ label: "expires_at", value: new Date(session.expires_at * 1000).toISOString() }] : []
9116
- ],
9117
- remediation: valid ? undefined : "Refresh browser authentication.",
9118
- nextCommand: valid ? undefined : "moda init"
9119
- };
9120
- }
9121
- function configPermissionCheck(path) {
9122
- if (!existsSync11(path)) {
9123
- return {
9124
- id: "config_file_permissions",
9125
- group: "config",
9126
- title: "Config file permissions",
9127
- status: "skipped",
9128
- severity: "info",
9129
- evidence: [{ label: "path", value: path }]
9130
- };
9131
- }
9132
- const permission = ensureSecretFilePermissions(path);
9133
- return {
9134
- id: "config_file_permissions",
9135
- group: "config",
9136
- title: "Config file permissions",
9137
- status: permission.secure ? "pass" : "fail",
9138
- severity: permission.secure ? "info" : "critical",
9139
- evidence: [
9140
- { label: "path", value: path },
9141
- { label: "mode", value: permission.mode ?? "unknown" }
9142
- ],
9143
- remediation: permission.secure ? undefined : "Restrict config file permissions to the current user."
9144
- };
9145
- }
9146
- function secretPermissionCheck(path) {
9147
- const permission = ensureSecretFilePermissions(path);
9148
- return {
9149
- id: "secret_file_permissions",
9150
- group: "config",
9151
- title: "Secret file permissions",
9152
- status: permission.secure ? "pass" : "fail",
9153
- severity: permission.secure ? "info" : "critical",
9154
- evidence: [
9155
- { label: "path", value: path },
9156
- { label: "mode", value: permission.mode ?? "unknown" }
9157
- ],
9158
- remediation: permission.secure ? undefined : "Restrict secret file permissions to the current user."
9159
- };
9160
- }
9161
- function urlCheck(id, group, title, value) {
9162
- let valid = false;
9163
- try {
9164
- const parsed = new URL(value);
9165
- valid = parsed.protocol === "http:" || parsed.protocol === "https:";
9166
- } catch {
9167
- valid = false;
9168
- }
9169
- return {
9170
- id,
9171
- group,
9172
- title,
9173
- status: valid ? "pass" : "fail",
9174
- severity: valid ? "info" : "critical",
9175
- evidence: [{ label: "url", value }],
9176
- remediation: valid ? undefined : "Configure a valid http(s) URL.",
9177
- nextCommand: valid ? undefined : "moda profiles create"
9178
- };
9179
- }
9180
- function localBaseUrlCheck(environment, baseUrl) {
9181
- if (environment !== "local") {
9182
- return {
9183
- id: "local_base_url_frontend",
9184
- group: "config",
9185
- title: "Local base URL targets the frontend",
9186
- status: "skipped",
9187
- severity: "info",
9188
- evidence: [{ label: "environment", value: environment }]
9189
- };
9190
- }
9191
- let port = null;
9192
- let loopback = false;
9193
- try {
9194
- const parsed = new URL(baseUrl);
9195
- loopback = isLoopbackBaseUrl(baseUrl);
9196
- port = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
9197
- } catch {
9198
- return {
9199
- id: "local_base_url_frontend",
9200
- group: "config",
9201
- title: "Local base URL targets the frontend",
9202
- status: "fail",
9203
- severity: "critical",
9204
- evidence: [{ label: "base_url", value: baseUrl }],
9205
- remediation: "Set the local profile base URL to the frontend origin (http://localhost:3000).",
9206
- nextCommand: "moda profiles create local --environment local --base-url http://localhost:3000"
9207
- };
9208
- }
9209
- const looksLikeBackend = loopback && (port === "3001" || port === "3002");
9210
- if (looksLikeBackend) {
9211
- return {
9212
- id: "local_base_url_frontend",
9213
- group: "config",
9214
- title: "Local base URL targets the frontend",
9215
- status: "warning",
9216
- severity: "warning",
9217
- evidence: [
9218
- { label: "base_url", value: baseUrl },
9219
- { label: "port", value: port ?? "unknown" }
9220
- ],
9221
- remediation: "Local CLI auth and control APIs are served by the frontend. " + "Use http://localhost:3000 (or your worktree frontend port), not the backend :3001.",
9222
- nextCommand: "moda profiles create local --environment local --base-url http://localhost:3000 --use"
9223
- };
9224
- }
9225
- if (!loopback) {
9226
- return {
9227
- id: "local_base_url_frontend",
9228
- group: "config",
9229
- title: "Local base URL targets the frontend",
9230
- status: "warning",
9231
- severity: "warning",
9232
- evidence: [{ label: "base_url", value: baseUrl }],
9233
- remediation: "Local profile should use a loopback frontend URL (http://localhost:3000). " + "Activate with `moda profiles use local` or set MODA_PROFILE=local.",
9234
- nextCommand: "moda profiles use local"
9235
- };
9236
- }
9237
- return {
9238
- id: "local_base_url_frontend",
9239
- group: "config",
9240
- title: "Local base URL targets the frontend",
9241
- status: "pass",
9242
- severity: "info",
9243
- evidence: [{ label: "base_url", value: baseUrl }]
9244
- };
9245
- }
9246
- function harnessCurrentCheck(cwd, manifestPath, manifestExists, tenantId) {
9247
- if (!manifestExists) {
9248
- return {
9249
- id: "harness_manifest_current",
9250
- group: "harness",
9251
- title: "Harness manifest matches scanner output",
9252
- status: "skipped",
9253
- severity: "info",
9254
- evidence: [{ label: "reason", value: "manifest missing" }],
9255
- nextCommand: "moda harness scan --summary"
9256
- };
9257
- }
9258
- try {
9259
- const current = scanHarness({
9260
- rootDir: cwd,
9261
- scannerVersion: "doctor",
9262
- tenantId,
9263
- tenantSource: tenantId ? "env_or_config" : undefined
9264
- });
9265
- const saved = JSON.parse(readFileSync9(manifestPath, "utf8"));
9266
- const savedHash = stableGraphHash(saved);
9267
- const currentHash = stableGraphHash(current);
9268
- const matches = savedHash === currentHash;
9269
- return {
9270
- id: "harness_manifest_current",
9271
- group: "harness",
9272
- title: "Harness manifest matches scanner output",
9273
- status: matches ? "pass" : "warning",
9274
- severity: matches ? "info" : "warning",
9275
- evidence: [
9276
- { label: "saved_hash", value: savedHash.slice(0, 12) },
9277
- { label: "current_hash", value: currentHash.slice(0, 12) },
9278
- ...matches ? [] : [{ label: "diff", value: "Manifest hash differs from fresh scanner output." }]
9279
- ],
9280
- remediation: matches ? undefined : "Refresh the local harness manifest.",
9281
- nextCommand: matches ? undefined : "moda harness scan --summary"
9282
- };
9283
- } catch (err) {
9284
- return {
9285
- id: "harness_manifest_current",
9286
- group: "harness",
9287
- title: "Harness manifest matches scanner output",
9288
- status: "fail",
9289
- severity: "critical",
9290
- evidence: [{ label: "error", value: err instanceof Error ? err.message : String(err) }],
9291
- remediation: "Regenerate the harness manifest.",
9292
- nextCommand: "moda harness scan --summary"
9293
- };
9294
- }
9295
- }
9296
- function harnessSyncStateCheck(cwd) {
9297
- const state = readHarnessSyncState(cwd);
9298
- if (!state) {
9299
- return {
9300
- id: "harness_sync_state_present",
9301
- group: "harness",
9302
- title: "Harness sync state exists",
9303
- status: "warning",
9304
- severity: "warning",
9305
- evidence: [{ label: "path", value: ".moda/sync-state.json" }],
9306
- remediation: "Sync the harness graph so Moda can correlate local state with cloud state.",
9307
- nextCommand: "moda harness sync --dry-run"
9308
- };
9309
- }
9310
- return {
9311
- id: "harness_sync_state_present",
9312
- group: "harness",
9313
- title: "Harness sync state exists",
9314
- status: "pass",
9315
- severity: "info",
9316
- evidence: [
9317
- { label: "updated_at", value: state.updatedAt },
9318
- { label: "profile", value: state.profile },
9319
- { label: "harness_id", value: state.harnessId },
9320
- { label: "remote_version_id", value: state.remote.versionId || "none" }
9321
- ]
9322
- };
9323
- }
9324
- function harnessReportStateCheck(cwd) {
9325
- const reportPath = join9(cwd, HARNESS_REPORT_PATH);
9326
- if (!existsSync11(reportPath)) {
9327
- return {
9328
- id: "harness_report_approved_synced",
9329
- group: "harness",
9330
- title: "Harness analyst report is approved and synced",
9331
- status: "warning",
9332
- severity: "warning",
9333
- evidence: [{ label: "path", value: HARNESS_REPORT_PATH }],
9334
- remediation: "Run a Moda Analyst pass so the harness graph has cited evidence before cloud sync.",
9335
- nextCommand: "moda harness analyze --summary"
9336
- };
9337
- }
9338
- try {
9339
- const report = readHarnessReport(reportPath);
9340
- const validation = validateHarnessReport(report, cwd);
9341
- const reportHash = hashHarnessReport(report);
9342
- const evidence = [
9343
- { label: "path", value: HARNESS_REPORT_PATH },
9344
- { label: "report_hash", value: reportHash.slice(0, 12) },
9345
- { label: "validation", value: validation.status }
9346
- ];
9347
- if (validation.status !== "pass") {
9348
- return {
9349
- id: "harness_report_approved_synced",
9350
- group: "harness",
9351
- title: "Harness analyst report is approved and synced",
9352
- status: "fail",
9353
- severity: "critical",
9354
- evidence: [
9355
- ...evidence,
9356
- { label: "errors", value: validation.errors.slice(0, 3).join(" | ") || "validation failed" }
9357
- ],
9358
- remediation: "Regenerate or fix the harness report before approving or syncing.",
9359
- nextCommand: "moda harness analyze --summary"
9360
- };
9361
- }
9362
- const approval = readHarnessReportApproval(cwd);
9363
- const approved = approval?.schemaVersion === HARNESS_REPORT_APPROVAL_SCHEMA_VERSION && approval.reportId === report.reportId && approval.reportHash === reportHash && approval.approved === true;
9364
- evidence.push({ label: "approval", value: approved ? "approved" : approval ? "stale_or_invalid" : "missing" });
9365
- if (approval && !approved) {
9366
- return {
9367
- id: "harness_report_approved_synced",
9368
- group: "harness",
9369
- title: "Harness analyst report is approved and synced",
9370
- status: "fail",
9371
- severity: "critical",
9372
- evidence,
9373
- remediation: "Approval no longer matches the current report. Review and approve the report again.",
9374
- nextCommand: "moda harness approve --yes"
9375
- };
9376
- }
9377
- if (!approved) {
9378
- return {
9379
- id: "harness_report_approved_synced",
9380
- group: "harness",
9381
- title: "Harness analyst report is approved and synced",
9382
- status: "warning",
9383
- severity: "warning",
9384
- evidence,
9385
- remediation: "Review and approve the validated harness report before syncing.",
9386
- nextCommand: "moda harness approve --yes"
9387
- };
9388
- }
9389
- const state = readHarnessSyncState(cwd);
9390
- const synced = state?.source === "approved_report" && state.report?.reportHash === reportHash;
9391
- evidence.push({ label: "sync", value: synced ? "synced" : "not_synced_to_this_report" }, ...state?.report?.reportHash ? [{ label: "sync_report_hash", value: state.report.reportHash.slice(0, 12) }] : []);
9392
- return {
9393
- id: "harness_report_approved_synced",
9394
- group: "harness",
9395
- title: "Harness analyst report is approved and synced",
9396
- status: synced ? "pass" : "warning",
9397
- severity: synced ? "info" : "warning",
9398
- evidence,
9399
- remediation: synced ? undefined : "Sync the approved harness report so Moda cloud has the same harness graph.",
9400
- nextCommand: synced ? undefined : "moda harness sync --from-report .moda/harness-report.json"
9401
- };
9402
- } catch (err) {
9403
- return {
9404
- id: "harness_report_approved_synced",
9405
- group: "harness",
9406
- title: "Harness analyst report is approved and synced",
9407
- status: "fail",
9408
- severity: "critical",
9409
- evidence: [
9410
- { label: "path", value: HARNESS_REPORT_PATH },
9411
- { label: "error", value: err instanceof Error ? err.message : String(err) }
9412
- ],
9413
- remediation: "Regenerate the harness report.",
9414
- nextCommand: "moda harness analyze --summary"
9415
- };
9416
- }
9417
- }
9418
- function readHarnessReportApproval(cwd) {
9419
- const approvalPath = join9(cwd, HARNESS_REPORT_APPROVAL_PATH);
9420
- if (!existsSync11(approvalPath))
9421
- return null;
9422
- try {
9423
- return JSON.parse(readFileSync9(approvalPath, "utf8"));
9424
- } catch {
9425
- return {
9426
- schemaVersion: HARNESS_REPORT_APPROVAL_SCHEMA_VERSION,
9427
- reportId: "unreadable",
9428
- reportHash: "unreadable",
9429
- approved: true,
9430
- approvedAt: "",
9431
- approvedBy: "local-human",
9432
- reportPath: HARNESS_REPORT_PATH
9433
- };
9434
- }
9435
- }
9436
- function discoveryCheck(id, group, title, cwd, relativePaths, nextCommand) {
9437
- const found = relativePaths.filter((path) => existsSync11(join9(cwd, path)));
9438
- return {
9439
- id,
9440
- group,
9441
- title,
9442
- status: found.length > 0 ? "pass" : "warning",
9443
- severity: found.length > 0 ? "info" : "warning",
9444
- evidence: [{ label: "paths", value: found.length > 0 ? found.join(", ") : "none" }],
9445
- remediation: found.length > 0 ? undefined : `Expected one of: ${relativePaths.join(", ")}.`,
9446
- nextCommand: found.length > 0 ? undefined : nextCommand
9447
- };
9448
- }
9449
- function sdkInitializationCheck(cwd) {
9450
- const candidates = ["src", "app", "agent_harness"];
9451
- let detected = false;
9452
- for (const candidate of candidates) {
9453
- const path = join9(cwd, candidate);
9454
- if (!existsSync11(path))
9455
- continue;
9456
- try {
9457
- const stat = statSync(path);
9458
- detected ||= stat.isDirectory() || stat.isFile();
9459
- } catch {}
9460
- }
9461
- return {
9462
- id: "sdk_initialization_detected",
9463
- group: "harness",
9464
- title: "SDK initialization is detectable",
9465
- status: detected ? "warning" : "skipped",
9466
- severity: "warning",
9467
- evidence: [{ label: "status", value: detected ? "code present, instrumentation not verified" : "no source candidates" }],
9468
- remediation: "Use the generated integration guide to add Moda SDK instrumentation.",
9469
- nextCommand: "moda init"
9470
- };
9471
- }
9472
- function validationEventPathCheck(ingestUrl) {
9473
- return {
9474
- id: "validation_event_path_defined",
9475
- group: "cloud",
9476
- title: "Validation event path is defined",
9477
- status: "pass",
9478
- severity: "info",
9479
- evidence: [{ label: "path", value: `${ingestUrl.replace(/\/+$/, "")}/v1/otel/validation` }]
9480
- };
9481
- }
9482
- function offlineCloudChecks(isOffline) {
9483
- const status = isOffline ? "skipped" : "warning";
9484
- const evidence = [{ label: "mode", value: isOffline ? "offline" : "missing api key" }];
9485
- const remediation = isOffline ? "Rerun without --offline to check Moda Cloud." : "Configure an API key before checking cloud data flow.";
9486
- const nextCommand = isOffline ? "moda doctor --online" : "moda init";
9487
- return [
9488
- {
9489
- id: "cloud_api_reachable",
9490
- group: "cloud",
9491
- title: "Moda Cloud API is reachable",
9492
- status,
9493
- severity: "warning",
9494
- evidence,
9495
- remediation,
9496
- nextCommand
9497
- },
9498
- {
9499
- id: "ingest_endpoint_reachable",
9500
- group: "cloud",
9501
- title: "Moda ingest endpoint is reachable",
9502
- status,
9503
- severity: "warning",
9504
- evidence,
9505
- remediation,
9506
- nextCommand
9507
- },
9508
- {
9509
- id: "recent_data_flow",
9510
- group: "cloud",
9511
- title: "Recent traces/logs/runs exist",
9512
- status,
9513
- severity: "warning",
9514
- evidence,
9515
- remediation,
9516
- nextCommand
9517
- },
9518
- {
9519
- id: "cloud_data_flow",
9520
- group: "cloud",
9521
- title: "Moda Cloud data flow",
9522
- status,
9523
- severity: "warning",
9524
- evidence,
9525
- remediation,
9526
- nextCommand
9527
- }
9528
- ];
9529
- }
9530
- async function cloudDataFlowChecks(ingestUrl, apiKey) {
9531
- let overview;
9532
- let cloudApiCheck;
9533
- try {
9534
- overview = await callDataAPI("/overview?days_back=1");
9535
- cloudApiCheck = {
9536
- id: "cloud_api_reachable",
9537
- group: "cloud",
9538
- title: "Moda Cloud API is reachable",
9539
- status: "pass",
9540
- severity: "info",
9541
- evidence: [{ label: "overview", value: "reachable" }]
9542
- };
9543
- } catch (err) {
9544
- const error = err instanceof Error ? err.message : String(err);
9545
- return [
9546
- {
9547
- id: "cloud_api_reachable",
9548
- group: "cloud",
9549
- title: "Moda Cloud API is reachable",
9550
- status: "warning",
9551
- severity: "warning",
9552
- evidence: [{ label: "error", value: error }],
9553
- remediation: "Check API key and base URL.",
9554
- nextCommand: "moda doctor --offline"
9555
- },
9556
- {
9557
- id: "ingest_endpoint_reachable",
9558
- group: "cloud",
9559
- title: "Moda ingest endpoint is reachable",
9560
- status: "skipped",
9561
- severity: "warning",
9562
- evidence: [{ label: "reason", value: "cloud API unreachable" }]
9563
- },
9564
- {
9565
- id: "recent_data_flow",
9566
- group: "cloud",
9567
- title: "Recent traces/logs/runs exist",
9568
- status: "skipped",
9569
- severity: "warning",
9570
- evidence: [{ label: "reason", value: "overview unavailable" }]
9571
- },
9572
- {
9573
- id: "cloud_data_flow",
9574
- group: "cloud",
9575
- title: "Moda Cloud data flow",
9576
- status: "warning",
9577
- severity: "warning",
9578
- evidence: [{ label: "error", value: error }],
9579
- remediation: "Check API key, base URL, and whether data has reached Moda.",
9580
- nextCommand: "moda doctor --offline"
9581
- }
9582
- ];
9583
- }
9584
- const ingestCheck = await ingestReachabilityCheck(ingestUrl, apiKey);
9585
- const recentCheck = recentDataFlowCheck(overview);
9586
- const cloudStatus = cloudApiCheck.status === "pass" && ingestCheck.status === "pass" && recentCheck.status === "pass" ? "pass" : "warning";
9587
- return [
9588
- cloudApiCheck,
9589
- ingestCheck,
9590
- recentCheck,
9591
- {
9592
- id: "cloud_data_flow",
9593
- group: "cloud",
9594
- title: "Moda Cloud data flow",
9595
- status: cloudStatus,
9596
- severity: cloudStatus === "pass" ? "info" : "warning",
9597
- evidence: [
9598
- { label: "cloud_api", value: cloudApiCheck.status },
9599
- { label: "ingest", value: ingestCheck.status },
9600
- { label: "recent_data", value: recentCheck.status }
9601
- ],
9602
- remediation: cloudStatus === "pass" ? undefined : "Check API key, ingest URL, and whether data has reached Moda.",
9603
- nextCommand: cloudStatus === "pass" ? undefined : "moda doctor --offline"
9604
- }
9605
- ];
9606
- }
9607
- async function ingestReachabilityCheck(ingestUrl, apiKey) {
9608
- const url = `${ingestUrl.replace(/\/+$/, "")}/`;
9609
- try {
9610
- const response = await fetch(url, {
9611
- method: "GET",
9612
- headers: { Authorization: `Bearer ${apiKey}` },
9613
- signal: AbortSignal.timeout(1e4)
9614
- });
9615
- return {
9616
- id: "ingest_endpoint_reachable",
9617
- group: "cloud",
9618
- title: "Moda ingest endpoint is reachable",
9619
- status: "pass",
9620
- severity: "info",
9621
- evidence: [
9622
- { label: "url", value: url },
9623
- { label: "http_status", value: String(response.status) }
9624
- ]
9625
- };
9626
- } catch (err) {
9627
- return {
9628
- id: "ingest_endpoint_reachable",
9629
- group: "cloud",
9630
- title: "Moda ingest endpoint is reachable",
9631
- status: "warning",
9632
- severity: "warning",
9633
- evidence: [{ label: "error", value: err instanceof Error ? err.message : String(err) }],
9634
- remediation: "Check the configured ingest URL and network reachability.",
9635
- nextCommand: "moda profiles create"
9636
- };
9637
- }
9638
- }
9639
- function recentDataFlowCheck(overview) {
9640
- const data = isRecord4(overview) ? overview : {};
9641
- const conversations = numberAt(data, ["conversations", "total"]);
9642
- const toolFailures = numberAt(data, ["tool_failures", "total"]);
9643
- const recentActivity = Array.isArray(data.recent_activity) ? data.recent_activity : [];
9644
- const lastEventAt = lastActivityTimestamp(recentActivity);
9645
- const hasData = conversations > 0 || toolFailures > 0 || Boolean(lastEventAt);
9646
- return {
9647
- id: "recent_data_flow",
9648
- group: "cloud",
9649
- title: "Recent traces/logs/runs exist",
9650
- status: hasData ? "pass" : "warning",
9651
- severity: hasData ? "info" : "warning",
9652
- evidence: [
9653
- { label: "conversations", value: String(conversations) },
9654
- { label: "tool_failures", value: String(toolFailures) },
9655
- { label: "last_event_at", value: lastEventAt ?? "none" }
9656
- ],
9657
- remediation: hasData ? undefined : "No recent Moda data was visible in the overview window.",
9658
- nextCommand: hasData ? undefined : "moda hooks install --agent=all"
9659
- };
9660
- }
9661
- function numberAt(value, path) {
9662
- let cursor = value;
9663
- for (const key of path) {
9664
- if (!isRecord4(cursor))
9665
- return 0;
9666
- cursor = cursor[key];
9667
- }
9668
- return typeof cursor === "number" && Number.isFinite(cursor) ? cursor : 0;
9669
- }
9670
- function lastActivityTimestamp(items) {
9671
- const timestamps = items.map((item) => isRecord4(item) && typeof item.timestamp === "string" ? item.timestamp : undefined).filter((item) => Boolean(item)).sort();
9672
- return timestamps.at(-1);
9673
- }
9674
- function summarizeChecks(checks) {
9675
- const fail = checks.filter((check) => check.status === "fail").length;
9676
- const warning = checks.filter((check) => check.status === "warning").length;
9677
- const skipped = checks.filter((check) => check.status === "skipped").length;
9678
- const pass = checks.filter((check) => check.status === "pass").length;
9679
- return {
9680
- status: fail > 0 ? "fail" : warning > 0 ? "warning" : "pass",
9681
- pass,
9682
- warning,
9683
- fail,
9684
- skipped
9685
- };
9686
- }
9687
- function nextCommandsForChecks(checks) {
9688
- return [...new Set(checks.filter((check) => check.status === "fail" || check.status === "warning").map((check) => check.nextCommand).filter((command) => Boolean(command)))];
9689
- }
9690
- function firstBrokenCheck(checks) {
9691
- return checks.find((check) => check.status === "fail") ?? checks.find((check) => check.status === "warning");
9692
- }
9693
- function checkStatus(checks, id) {
9694
- return checks.find((check) => check.id === id)?.status ?? "skipped";
9695
- }
9696
- function doctorAgentWriteOptions(report, command) {
9697
- const broken = report.checks.filter((check) => check.status === "fail" || check.status === "warning");
9698
- return {
9699
- summary: `${command} completed with status ${report.summary.status}: ${report.summary.fail} failing checks and ${report.summary.warning} warnings.`,
9700
- findings: broken.slice(0, 16).map((check) => ({
9701
- id: check.id,
9702
- title: check.title,
9703
- status: check.status,
9704
- severity: check.severity,
9705
- description: check.remediation ?? `Doctor check ${check.id} is ${check.status}.`,
9706
- evidence_ref_ids: [`doctor:${check.id}`]
9707
- })),
9708
- evidenceRefs: report.checks.map((check) => ({
9709
- id: `doctor:${check.id}`,
9710
- kind: "doctor_check",
9711
- label: check.title,
9712
- status: check.status
9713
- })),
9714
- recommendedActions: report.fixPlan.slice(0, 16).map((item, index) => ({
9715
- id: `doctor_action_${index + 1}_${item.checkId}`,
9716
- title: item.action,
9717
- command: item.command,
9718
- rationale: `Doctor check ${item.checkId} is not passing.`,
9719
- mutability: item.command ? agentMutabilityForCommand(item.command) : "unknown",
9720
- requires_approval: item.command ? agentCommandRequiresApproval(item.command) : true
9721
- })),
9722
- nextCommands: report.nextCommands.slice(0, 16).map((nextCommand) => ({
9723
- command: nextCommand,
9724
- purpose: agentPurposeForCommand(nextCommand),
9725
- mutability: agentMutabilityForCommand(nextCommand),
9726
- requires_approval: agentCommandRequiresApproval(nextCommand)
9727
- })),
9728
- warnings: broken.filter((check) => check.status === "warning").slice(0, 16).map((check) => `${check.title}: ${check.remediation ?? check.status}`)
9729
- };
9730
- }
9731
- function agentMutabilityForCommand(command) {
9732
- if (/\bsync\b/.test(command))
9733
- return "sync";
9734
- if (/\b(init|install|approve|pull|gen|promote|remove)\b/.test(command))
9735
- return "write";
9736
- return "read";
9737
- }
9738
- function agentCommandRequiresApproval(command) {
9739
- return agentMutabilityForCommand(command) !== "read" && !command.includes("--dry-run");
9740
- }
9741
- function agentPurposeForCommand(command) {
9742
- if (command.includes("doctor"))
9743
- return "Re-run Moda diagnostics.";
9744
- if (command.includes("harness approve"))
9745
- return "Approve the validated harness report after review.";
9746
- if (command.includes("harness sync"))
9747
- return "Sync harness state to Moda Cloud.";
9748
- if (command.includes("harness scan"))
9749
- return "Refresh the local harness manifest.";
9750
- if (command.includes("init"))
9751
- return "Run Moda onboarding.";
9752
- return "Inspect or repair Moda setup.";
9753
- }
9754
- function stableGraphHash(graph) {
9755
- return createHash5("sha256").update(JSON.stringify(sortJson4({
9756
- ...graph,
9757
- scan: {
9758
- ...graph.scan,
9759
- createdAt: undefined,
9760
- scannerVersion: undefined,
9761
- skipped: undefined
9762
- }
9763
- }))).digest("hex");
9764
- }
9765
- function sortJson4(value) {
9766
- if (Array.isArray(value))
9767
- return value.map((item) => sortJson4(item));
9768
- if (value && typeof value === "object") {
9769
- return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson4(item)]));
9770
- }
9771
- return value;
9772
- }
9773
- function isRecord4(value) {
9774
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9775
- }
9776
-
9777
- export { ApiError, callDataAPI, callControlAPI, getApiRequestCount, resetApiRequestCount, runPromptsCommand, initPrompts, runPromptSync, claudeSkillToCursorRule, installRules, detectAgents, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, upsertSkillManifestRecord, runSkillSync, runSkillsCommand, shouldColorTerminalOutput, terminalStyles, stripAnsi, renderMarkdownForTerminal, HARNESS_REPORT_PATH, HARNESS_REPORT_APPROVAL_PATH, validateHarnessReport, readHarnessReport, hashHarnessReport, hashStableJson, startRemoteAnalyze, pollRemoteAnalyzeUntilDone, renderInitBanner, renderInitStep, renderInitNote, renderInitSetupPlan, renderInitComplete, runHarnessCommand, describeCodingAgentEvent, isDeterministicLocalAnalyst, detectAvailableCodingAgents, codingAgentDisplayName, runCodingAgent, summarizeHarness, readHarnessSyncState, describeSyncIdentity, describeSyncTenantLabel, graphForSync, runDoctorCommand, runStatusCommand, buildDoctorReport };
6980
+ export { ApiError, callDataAPI, callControlAPI, getApiRequestCount, resetApiRequestCount, shouldColorTerminalOutput, terminalStyles, stripAnsi, renderMarkdownForTerminal, HARNESS_REPORT_APPROVAL_SCHEMA_VERSION, HARNESS_REPORT_PATH, HARNESS_REPORT_APPROVAL_PATH, validateHarnessReport, readHarnessReport, hashHarnessReport, hashStableJson, startRemoteAnalyze, pollRemoteAnalyzeUntilDone, renderDoctorHuman, renderStatusHuman, renderInitBanner, renderInitStep, renderInitNote, renderInitSetupPlan, renderInitComplete, runHarnessCommand, describeCodingAgentEvent, isDeterministicLocalAnalyst, detectAvailableCodingAgents, codingAgentDisplayName, runCodingAgent, scanHarness, summarizeHarness, buildSourceSnapshot, readHarnessSyncState, describeSyncIdentity, describeSyncTenantLabel, graphForSync };