@moda-ai/cli 1.55.26 → 1.55.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{agent-nqmha8ck.js → agent-0w9rtdsw.js} +22 -16
  2. package/dist/cli-3n0gz4d0.js +17971 -0
  3. package/dist/{cli-b4740tnv.js → cli-3retgh64.js} +85 -1165
  4. package/dist/{cli-1mbb0ct0.js → cli-97jew729.js} +1 -1
  5. package/dist/{cli-sqsjd08w.js → cli-99p2jh28.js} +105 -893
  6. package/dist/{cli-k6q8dbpk.js → cli-9bgk5jeh.js} +8 -6
  7. package/dist/{cli-q6f6vrrr.js → cli-9bgnvth2.js} +5 -3
  8. package/dist/cli-9kfxkxmk.js +6994 -0
  9. package/dist/cli-cesds766.js +1516 -0
  10. package/dist/{cli-cdrvjva5.js → cli-dr6x0zv7.js} +28 -6
  11. package/dist/{cli-jtyvbd8d.js → cli-e23g9ywa.js} +1 -1
  12. package/dist/cli-fwpqghgc.js +569 -0
  13. package/dist/cli-k4tqwah4.js +89 -0
  14. package/dist/{cli-8tfk8qeb.js → cli-mg5zv4s6.js} +18 -15
  15. package/dist/{cli-jgfm7w1r.js → cli-n2hng3gv.js} +9 -6
  16. package/dist/cli-ns5fcnkz.js +341 -0
  17. package/dist/{cli-5g1tv80h.js → cli-ny48a8de.js} +12 -9
  18. package/dist/{cli-d577kq6h.js → cli-qpq84eee.js} +5220 -32715
  19. package/dist/cli-xq45etg1.js +1098 -0
  20. package/dist/cli-yf39avhh.js +754 -0
  21. package/dist/cli-yz8xcpyk.js +12 -0
  22. package/dist/cli.js +355 -283
  23. package/dist/doctor-k3ta6h2y.js +24 -0
  24. package/dist/{fix-crkwdkt4.js → fix-w5z02wt4.js} +47 -50
  25. package/dist/{harness-x3e7d68b.js → harness-7azmzytc.js} +15 -7
  26. package/dist/{harness-github-actions-fjhvhgpj.js → harness-github-actions-bv2qrnqb.js} +11 -5
  27. package/dist/harness-report-rd9c4e9v.js +54 -0
  28. package/dist/{index-2kf3fnnb.js → index-d1z50dsq.js} +45 -36
  29. package/dist/{production-intelligence-7n83c701.js → production-intelligence-wvsgjxth.js} +21 -18
  30. package/dist/{profile-commands-ct2t0bfr.js → profile-commands-h8evz9x0.js} +9 -5
  31. package/dist/prompt-attribution-1nytc9wj.js +148 -0
  32. package/dist/prompt-source-mcp.js +14 -7
  33. package/dist/prompt-sources-07tczyqa.js +19 -0
  34. package/dist/{prompts-92aqqb2k.js → prompts-6y9wfq5z.js} +8 -7
  35. package/dist/prompts-ab-ezrd63sk.js +70 -0
  36. package/dist/prompts-propose-2k3enm2q.js +255 -0
  37. package/dist/{provision-gn8raqpt.js → provision-y5bdbh4y.js} +9 -6
  38. package/dist/{registry-ex162j5y.js → registry-96aze5wp.js} +7 -4
  39. package/dist/{skills-2mbsj9bt.js → skills-j8865009.js} +7 -5
  40. package/dist/{sync-kghmmmk5.js → sync-ew7yvy0e.js} +16 -10
  41. package/package.json +1 -1
  42. package/skills/integration/index.json +2 -2
  43. package/dist/cli-bb7133m7.js +0 -144
  44. package/dist/doctor-w913fj4g.js +0 -22
@@ -1,6 +1,12 @@
1
1
  import {
2
- INTELLIGENCE_SCHEMA_VERSION
3
- } from "./cli-bb7133m7.js";
2
+ INTELLIGENCE_SCHEMA_VERSION,
3
+ asNumber,
4
+ asRecord,
5
+ asString,
6
+ contextTarget,
7
+ frustrationAnchorIndex,
8
+ frustrationsFromEmotions
9
+ } from "./cli-fwpqghgc.js";
4
10
  import {
5
11
  renderMarkdownForTerminal,
6
12
  shouldColorTerminalOutput,
@@ -10,26 +16,23 @@ import {
10
16
  import {
11
17
  callControlAPI,
12
18
  callDataAPI
13
- } from "./cli-jtyvbd8d.js";
19
+ } from "./cli-e23g9ywa.js";
14
20
  import {
15
- CliAuthError,
16
- CliInputError,
17
21
  EXIT_DEGRADED,
18
22
  EXIT_OK,
19
- apiErrorStatus,
20
23
  apiKeyHeaders,
21
- asNumber,
22
- asRecord,
23
- asString,
24
- contextTarget,
25
- frustrationAnchorIndex,
26
- frustrationsFromEmotions,
27
24
  recordApiRequest,
28
25
  resolveApiKey,
29
26
  resolveModaBaseUrl,
30
27
  safeWriteStderr,
31
- shellWord
32
- } from "./cli-sqsjd08w.js";
28
+ shellArg
29
+ } from "./cli-99p2jh28.js";
30
+ import {
31
+ CliAuthError,
32
+ CliInputError,
33
+ apiErrorStatus
34
+ } from "./cli-k4tqwah4.js";
35
+ import"./cli-ns5fcnkz.js";
33
36
  import"./cli-0v6na3yp.js";
34
37
 
35
38
  // src/production-intelligence.ts
@@ -538,7 +541,7 @@ async function investigateProduction(options) {
538
541
  warnings.push(`No failures found for tool "${options.toolName}" in the last ${options.daysBack} day(s) — the tool may have no failures in this window, or the name may not exist in this tenant. ` + "Run `moda detections tools` to list tools with recorded failures, or widen --days-back.");
539
542
  }
540
543
  if (options.conversationId) {
541
- warnings.push(`Scoped to trace ${options.conversationId} for frustration evidence only; overview and tool failures are tenant-wide. ` + `Open the trace itself with \`moda traces spans ${options.conversationId}\`.`);
544
+ warnings.push(`Scoped to trace ${options.conversationId} for frustration evidence only; overview and tool failures are tenant-wide. ` + `Open the trace itself with \`moda traces spans ${shellArg(options.conversationId)}\`.`);
542
545
  }
543
546
  const evidenceRefs = [];
544
547
  const findings = [];
@@ -872,7 +875,7 @@ function toolFailureFindings(data, daysBack, hints, evidenceRefs, scopedTool) {
872
875
  id: `action_tool_detail_${slug(name)}`,
873
876
  title: `Inspect ${name} failure examples.`,
874
877
  rationale: "Examples include trace anchors and error subtypes.",
875
- command: `moda detections tools ${name} --days-back=${daysBack} --include-window`,
878
+ command: `moda detections tools ${shellArg(name)} --days-back=${daysBack} --include-window`,
876
879
  mutability: "read",
877
880
  requiresApproval: false
878
881
  }]
@@ -918,7 +921,7 @@ function frustrationFindings(data, daysBack, evidenceRefs) {
918
921
  id: "action_frustration_window",
919
922
  title: "Read the frustration window.",
920
923
  rationale: "The user quote and nearby turns reveal what confused or blocked the user.",
921
- command: conversationId ? `moda traces get ${shellWord(contextTarget(conversationId))}${anchorIndex !== null ? ` --msg-index=${anchorIndex}` : ""} --window=3` : `moda detections list --family=frustration --days-back=${daysBack} --include-window`,
924
+ command: conversationId ? `moda traces get ${shellArg(contextTarget(conversationId))}${anchorIndex !== null ? ` --msg-index=${anchorIndex}` : ""} --window=3` : `moda detections list --family=frustration --days-back=${daysBack} --include-window`,
922
925
  mutability: "read",
923
926
  requiresApproval: false
924
927
  }]
@@ -994,7 +997,7 @@ function problemFindings(data, daysBack, evidenceRefs) {
994
997
  id: `action_problem_${slug(problem.id)}`,
995
998
  title: `Open the Problem dossier for ${problem.name}.`,
996
999
  rationale: "The dossier has the cause, evidence, affected traces and verification state.",
997
- command: `moda problems get ${problem.id}`,
1000
+ command: `moda problems get ${shellArg(problem.id)}`,
998
1001
  mutability: "read",
999
1002
  requiresApproval: false
1000
1003
  }]
@@ -2,7 +2,7 @@ import {
2
2
  probeApiKey,
3
3
  redactApiKey,
4
4
  resolveApiKeyCredential
5
- } from "./cli-1mbb0ct0.js";
5
+ } from "./cli-97jew729.js";
6
6
  import {
7
7
  authFetch,
8
8
  clearAuthSession,
@@ -10,9 +10,8 @@ import {
10
10
  loadAuthSession,
11
11
  login,
12
12
  revokeCliSessionToken
13
- } from "./cli-q6f6vrrr.js";
13
+ } from "./cli-9bgnvth2.js";
14
14
  import {
15
- CliInputError,
16
15
  DEFAULT_INGEST_URL,
17
16
  EXIT_AUTH_REQUIRED,
18
17
  EXIT_DEGRADED,
@@ -34,8 +33,13 @@ import {
34
33
  saveCliConfig,
35
34
  secretFilePathFromRef,
36
35
  setActiveProfile,
36
+ shellArg,
37
37
  upsertProfile
38
- } from "./cli-sqsjd08w.js";
38
+ } from "./cli-99p2jh28.js";
39
+ import {
40
+ CliInputError
41
+ } from "./cli-k4tqwah4.js";
42
+ import"./cli-ns5fcnkz.js";
39
43
  import"./cli-0v6na3yp.js";
40
44
 
41
45
  // src/profile-commands.ts
@@ -326,7 +330,7 @@ async function runProfilesCommand(context) {
326
330
  default_profile: config.active_profile,
327
331
  profiles: Object.values(config.profiles).map((profile) => serializeProfile(profile, effective)),
328
332
  migrations: config.migrations
329
- }, missing ? { warnings: [`The active profile '${effective}' (from ${source}) does not exist. Create it with \`moda profiles create ${effective}\` or select an existing profile.`] } : undefined);
333
+ }, missing ? { warnings: [`The active profile '${effective}' (from ${source}) does not exist. Create it with \`moda profiles create ${shellArg(effective)}\` or select an existing profile.`] } : undefined);
330
334
  return { exitCode: 0 };
331
335
  }
332
336
  case "use": {
@@ -0,0 +1,148 @@
1
+ import {
2
+ inventoryFiles,
3
+ safePromptEvidencePath
4
+ } from "./cli-3n0gz4d0.js";
5
+ import"./cli-xq45etg1.js";
6
+ import"./cli-97fnztpy.js";
7
+ import"./cli-cesds766.js";
8
+ import {
9
+ require_typescript
10
+ } from "./cli-qpq84eee.js";
11
+ import"./cli-dr6x0zv7.js";
12
+ import"./cli-n2hng3gv.js";
13
+ import"./cli-e23g9ywa.js";
14
+ import"./cli-99p2jh28.js";
15
+ import"./cli-k4tqwah4.js";
16
+ import"./cli-9kfxkxmk.js";
17
+ import"./cli-ns5fcnkz.js";
18
+ import {
19
+ __toESM
20
+ } from "./cli-0v6na3yp.js";
21
+
22
+ // src/prompt-attribution.ts
23
+ var import_typescript = __toESM(require_typescript(), 1);
24
+ import { dirname, extname, resolve } from "node:path";
25
+ function checkPromptAttribution(rootDir, keys) {
26
+ const result = { references: [], coverage: "partial", issues: [] };
27
+ const known = new Set(keys), lock = resolve(rootDir, ".moda/prompts.lock.json");
28
+ let files;
29
+ try {
30
+ files = inventoryFiles(rootDir, []);
31
+ } catch (error) {
32
+ result.issues.push({ path: ".", reason: `Attribution scan incomplete: ${error instanceof Error ? error.message : String(error)}` });
33
+ return result;
34
+ }
35
+ for (const file of files) {
36
+ if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(extname(file.path)) || !file.text?.includes("prompts.lock.json"))
37
+ continue;
38
+ if (/(^|\/)(?:tests?|__tests__|fixtures?)(\/|$)|\.(?:test|spec)\.[^.]+$/.test(file.path))
39
+ continue;
40
+ try {
41
+ const absolute = safePromptEvidencePath(rootDir, file.path);
42
+ const program = import_typescript.default.createProgram([absolute], { noResolve: true, noLib: true, types: [], allowJs: true });
43
+ const sf = program.getSourceFile(absolute);
44
+ if (!sf || program.getSyntacticDiagnostics(sf).length) {
45
+ result.issues.push({ path: file.path, reason: "Attribution source could not be parsed" });
46
+ continue;
47
+ }
48
+ const checker = program.getTypeChecker();
49
+ const declaration = (node) => {
50
+ const all = checker.getSymbolAtLocation(node)?.declarations;
51
+ return all?.length === 1 ? all[0] : undefined;
52
+ };
53
+ const imported = (node) => {
54
+ if (import_typescript.default.isImportSpecifier(node) && node.isTypeOnly)
55
+ return false;
56
+ let parent = node;
57
+ while (parent && !import_typescript.default.isImportDeclaration(parent))
58
+ parent = parent.parent;
59
+ return !!parent && import_typescript.default.isImportDeclaration(parent) && !parent.importClause?.isTypeOnly && import_typescript.default.isStringLiteral(parent.moduleSpecifier) && parent.moduleSpecifier.text.startsWith(".") && resolve(dirname(absolute), parent.moduleSpecifier.text) === lock;
60
+ };
61
+ const initializer = (node) => {
62
+ const decl = declaration(node);
63
+ return decl && import_typescript.default.isVariableDeclaration(decl) && import_typescript.default.isVariableDeclarationList(decl.parent) && !!(decl.parent.flags & import_typescript.default.NodeFlags.Const) ? decl.initializer : undefined;
64
+ };
65
+ const unwrap = (node) => {
66
+ while (import_typescript.default.isParenthesizedExpression(node) || import_typescript.default.isAsExpression(node) || import_typescript.default.isTypeAssertionExpression(node) || import_typescript.default.isNonNullExpression(node))
67
+ node = node.expression;
68
+ return node;
69
+ };
70
+ const literal = (expression, seen = new Set) => {
71
+ const node = unwrap(expression);
72
+ if (seen.has(node))
73
+ return;
74
+ seen.add(node);
75
+ if (import_typescript.default.isStringLiteral(node) || import_typescript.default.isNoSubstitutionTemplateLiteral(node))
76
+ return node.text;
77
+ if (import_typescript.default.isIdentifier(node)) {
78
+ const init = initializer(node);
79
+ return init ? literal(init, seen) : undefined;
80
+ }
81
+ if (import_typescript.default.isBinaryExpression(node) && node.operatorToken.kind === import_typescript.default.SyntaxKind.PlusToken) {
82
+ const a = literal(node.left, new Set(seen)), b = literal(node.right, new Set(seen));
83
+ return a !== undefined && b !== undefined ? a + b : undefined;
84
+ }
85
+ };
86
+ const lockPath = (node) => import_typescript.default.isStringLiteral(node) && node.text.startsWith(".") && resolve(dirname(absolute), node.text) === lock;
87
+ const isConstBinding = (node) => import_typescript.default.isVariableDeclarationList(node.parent) && !!(node.parent.flags & import_typescript.default.NodeFlags.Const);
88
+ const isLock = (expression, seen = new Set) => {
89
+ const node = unwrap(expression);
90
+ if (seen.has(node))
91
+ return false;
92
+ seen.add(node);
93
+ if (import_typescript.default.isCallExpression(node) && import_typescript.default.isIdentifier(node.expression) && node.expression.text === "require" && !declaration(node.expression) && node.arguments.length === 1)
94
+ return lockPath(node.arguments[0]);
95
+ if (!import_typescript.default.isIdentifier(node))
96
+ return false;
97
+ const decl = declaration(node);
98
+ if (decl && (import_typescript.default.isImportClause(decl) || import_typescript.default.isNamespaceImport(decl)))
99
+ return imported(decl);
100
+ const init = initializer(node);
101
+ return !!init && isLock(init, seen);
102
+ };
103
+ const isTable = (expression, seen = new Set) => {
104
+ const node = unwrap(expression);
105
+ if (seen.has(node))
106
+ return false;
107
+ seen.add(node);
108
+ if (import_typescript.default.isPropertyAccessExpression(node))
109
+ return node.name.text === "prompts" && isLock(node.expression);
110
+ if (import_typescript.default.isElementAccessExpression(node))
111
+ return literal(node.argumentExpression) === "prompts" && isLock(node.expression);
112
+ if (import_typescript.default.isIdentifier(node)) {
113
+ const decl = declaration(node);
114
+ if (decl && import_typescript.default.isBindingElement(decl) && import_typescript.default.isObjectBindingPattern(decl.parent) && import_typescript.default.isVariableDeclaration(decl.parent.parent)) {
115
+ const owner = decl.parent.parent;
116
+ const name = decl.propertyName ?? decl.name;
117
+ if (!decl.dotDotDotToken && !decl.initializer && (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name)) && name.text === "prompts" && isConstBinding(owner) && owner.initializer)
118
+ return isLock(owner.initializer);
119
+ }
120
+ if (decl && import_typescript.default.isImportSpecifier(decl))
121
+ return (decl.propertyName?.text ?? decl.name.text) === "prompts" && imported(decl);
122
+ const init = initializer(node);
123
+ return !!init && isTable(init, seen);
124
+ }
125
+ return false;
126
+ };
127
+ const visit = (node) => {
128
+ if ((import_typescript.default.isElementAccessExpression(node) || import_typescript.default.isPropertyAccessExpression(node)) && isTable(node.expression)) {
129
+ const key = import_typescript.default.isElementAccessExpression(node) ? literal(node.argumentExpression) : node.name.text;
130
+ result.references.push({
131
+ path: file.path,
132
+ line: sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1,
133
+ key,
134
+ status: key === undefined ? "unresolved" : known.has(key) ? "mapped" : "missing"
135
+ });
136
+ }
137
+ import_typescript.default.forEachChild(node, visit);
138
+ };
139
+ visit(sf);
140
+ } catch (error) {
141
+ result.issues.push({ path: file.path, reason: error instanceof Error ? error.message : String(error) });
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+ export {
147
+ checkPromptAttribution
148
+ };
@@ -2,15 +2,22 @@
2
2
  import {
3
3
  checkPromptMapping,
4
4
  discoverHarnessPromptSources,
5
- inspectCodeSelectors,
6
- inspectPythonSelectors,
7
5
  safePromptEvidencePath
8
- } from "./cli-d577kq6h.js";
6
+ } from "./cli-3n0gz4d0.js";
7
+ import"./cli-xq45etg1.js";
9
8
  import"./cli-97fnztpy.js";
10
- import"./cli-cdrvjva5.js";
11
- import"./cli-jgfm7w1r.js";
12
- import"./cli-jtyvbd8d.js";
13
- import"./cli-sqsjd08w.js";
9
+ import"./cli-cesds766.js";
10
+ import {
11
+ inspectCodeSelectors,
12
+ inspectPythonSelectors
13
+ } from "./cli-qpq84eee.js";
14
+ import"./cli-dr6x0zv7.js";
15
+ import"./cli-n2hng3gv.js";
16
+ import"./cli-e23g9ywa.js";
17
+ import"./cli-99p2jh28.js";
18
+ import"./cli-k4tqwah4.js";
19
+ import"./cli-9kfxkxmk.js";
20
+ import"./cli-ns5fcnkz.js";
14
21
  import {
15
22
  __commonJS,
16
23
  __toESM
@@ -0,0 +1,19 @@
1
+ import {
2
+ backfillPromptSource,
3
+ discoverCodePromptCandidates,
4
+ discoverCodePromptSources,
5
+ discoverStructuredPromptSources,
6
+ resolvePromptSource,
7
+ validatePromptSource
8
+ } from "./cli-qpq84eee.js";
9
+ import"./cli-9kfxkxmk.js";
10
+ import"./cli-ns5fcnkz.js";
11
+ import"./cli-0v6na3yp.js";
12
+ export {
13
+ validatePromptSource,
14
+ resolvePromptSource,
15
+ discoverStructuredPromptSources,
16
+ discoverCodePromptSources,
17
+ discoverCodePromptCandidates,
18
+ backfillPromptSource
19
+ };
@@ -11,16 +11,17 @@ import {
11
11
  runPromptSync,
12
12
  runPromptsCommand,
13
13
  writePromptSyncProgress
14
- } from "./cli-b4740tnv.js";
14
+ } from "./cli-3retgh64.js";
15
15
  import {
16
16
  promptSourceHashPayload
17
17
  } from "./cli-mm48br00.js";
18
- import"./cli-d577kq6h.js";
19
- import"./cli-97fnztpy.js";
20
- import"./cli-cdrvjva5.js";
21
- import"./cli-jgfm7w1r.js";
22
- import"./cli-jtyvbd8d.js";
23
- import"./cli-sqsjd08w.js";
18
+ import"./cli-dr6x0zv7.js";
19
+ import"./cli-n2hng3gv.js";
20
+ import"./cli-e23g9ywa.js";
21
+ import"./cli-99p2jh28.js";
22
+ import"./cli-k4tqwah4.js";
23
+ import"./cli-9kfxkxmk.js";
24
+ import"./cli-ns5fcnkz.js";
24
25
  import"./cli-0v6na3yp.js";
25
26
  export {
26
27
  writePromptSyncProgress,
@@ -0,0 +1,70 @@
1
+ import {
2
+ DEFAULT_PROBLEM_MAX_CASES,
3
+ MAX_AUTO_GENERATE_CASES,
4
+ MAX_REPLAY_TRACES,
5
+ PLAYOUT_CONFIRM_THRESHOLD,
6
+ REPLAY_MODEL_ALLOWLIST,
7
+ TERMINAL_RUN_STATUSES,
8
+ assertAllowedModel,
9
+ assertArmSourceShape,
10
+ assertPlayoutBudget,
11
+ candidateOutcome,
12
+ describePlayouts,
13
+ describeProblemCases,
14
+ enqueueAndPollComparison,
15
+ fetchProblemCases,
16
+ fetchReplaySetCases,
17
+ formatVerdict,
18
+ parseTraceRef,
19
+ planReplaySet,
20
+ replayCasePin,
21
+ rerunWithYes,
22
+ resolvePromptArmSpec,
23
+ runPromptAb,
24
+ selectProblemCases,
25
+ waitLabel
26
+ } from "./cli-yf39avhh.js";
27
+ import {
28
+ DEFAULT_POLL_INTERVAL_MS,
29
+ DEFAULT_WAIT_TIMEOUT_MS,
30
+ fetchLatestRun
31
+ } from "./cli-yz8xcpyk.js";
32
+ import"./cli-3retgh64.js";
33
+ import"./cli-mm48br00.js";
34
+ import"./cli-dr6x0zv7.js";
35
+ import"./cli-n2hng3gv.js";
36
+ import"./cli-e23g9ywa.js";
37
+ import"./cli-99p2jh28.js";
38
+ import"./cli-k4tqwah4.js";
39
+ import"./cli-9kfxkxmk.js";
40
+ import"./cli-ns5fcnkz.js";
41
+ import"./cli-0v6na3yp.js";
42
+ export {
43
+ waitLabel,
44
+ selectProblemCases,
45
+ runPromptAb,
46
+ resolvePromptArmSpec,
47
+ rerunWithYes,
48
+ replayCasePin,
49
+ planReplaySet,
50
+ parseTraceRef,
51
+ formatVerdict,
52
+ fetchReplaySetCases,
53
+ fetchProblemCases,
54
+ fetchLatestRun,
55
+ enqueueAndPollComparison,
56
+ describeProblemCases,
57
+ describePlayouts,
58
+ candidateOutcome,
59
+ assertPlayoutBudget,
60
+ assertArmSourceShape,
61
+ assertAllowedModel,
62
+ TERMINAL_RUN_STATUSES,
63
+ REPLAY_MODEL_ALLOWLIST,
64
+ PLAYOUT_CONFIRM_THRESHOLD,
65
+ MAX_REPLAY_TRACES,
66
+ MAX_AUTO_GENERATE_CASES,
67
+ DEFAULT_WAIT_TIMEOUT_MS,
68
+ DEFAULT_PROBLEM_MAX_CASES,
69
+ DEFAULT_POLL_INTERVAL_MS
70
+ };
@@ -0,0 +1,255 @@
1
+ import {
2
+ assertAllowedModel,
3
+ assertPlayoutBudget,
4
+ candidateOutcome,
5
+ describePlayouts,
6
+ enqueueAndPollComparison,
7
+ formatVerdict,
8
+ rerunWithYes,
9
+ resolvePromptArmSpec
10
+ } from "./cli-yf39avhh.js";
11
+ import"./cli-yz8xcpyk.js";
12
+ import"./cli-3retgh64.js";
13
+ import"./cli-mm48br00.js";
14
+ import"./cli-dr6x0zv7.js";
15
+ import"./cli-n2hng3gv.js";
16
+ import {
17
+ callControlAPI
18
+ } from "./cli-e23g9ywa.js";
19
+ import {
20
+ resolveApiTenantId,
21
+ shellArg,
22
+ validateConfig
23
+ } from "./cli-99p2jh28.js";
24
+ import {
25
+ CliInputError
26
+ } from "./cli-k4tqwah4.js";
27
+ import"./cli-9kfxkxmk.js";
28
+ import"./cli-ns5fcnkz.js";
29
+ import"./cli-0v6na3yp.js";
30
+
31
+ // src/prompts-propose.ts
32
+ import { writeFileSync } from "node:fs";
33
+ import { isAbsolute, resolve } from "node:path";
34
+ var PROPOSE_TIMEOUT_MS = 300000;
35
+ async function runPromptPropose(positionals, flags, profileOptions, context) {
36
+ validateConfig();
37
+ const promptKey = flags["prompt-key"] || positionals[1];
38
+ const fromRunId = flags["from-run"] || flags["from-run-id"] || flags["run-id"];
39
+ const replaySetId = flags["set-id"] || flags["replay-set-id"];
40
+ if (!promptKey || !fromRunId || !replaySetId) {
41
+ 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 [--seeds=1-10] [--assistant-model=<slug>] [--yes]] [--promote-on-win] [--allow-any-model]");
42
+ }
43
+ const maxDossiers = parsePositiveInt(flags["max-dossiers"], 8, 16, "--max-dossiers");
44
+ parsePositiveInt(flags["gate-timeout"] ?? flags.timeout, 2 * 60 * 60 * 1000, 24 * 60 * 60 * 1000, flags["gate-timeout"] !== undefined ? "--gate-timeout" : "--timeout");
45
+ parsePositiveInt(flags["poll-interval"], 15000, 120000, "--poll-interval");
46
+ const model = assertAllowedModel(flags.model ?? flags["revise-model"], flags, "--model");
47
+ const wantGate = flags.gate === "true";
48
+ let gatePlan;
49
+ if (wantGate) {
50
+ const tenantId = flags["tenant-id"] || resolveApiTenantId(profileOptions);
51
+ if (!tenantId) {
52
+ throw new Error("Missing tenant id for --gate. Run `moda init` or pass --tenant-id=<id>.");
53
+ }
54
+ const seedsPerCase = parsePositiveInt(flags.seeds ?? flags["seeds-per-case"], 3, 10, flags.seeds !== undefined ? "--seeds" : "--seeds-per-case");
55
+ const assistantModel = assertAllowedModel(flags["assistant-model"], flags, "--assistant-model");
56
+ const snapshot = await fetchGateSetCases(tenantId, replaySetId, profileOptions);
57
+ const cases = snapshot?.ids.length ?? null;
58
+ const plannedPlayouts = cases === null ? null : cases * seedsPerCase * 2;
59
+ assertPlayoutBudget(plannedPlayouts, { seedsPerCase, cases }, context.yes, rerunWithYes("prompts propose", positionals.slice(1), flags));
60
+ if (context.outputMode === "human") {
61
+ process.stderr.write(`Gate: ${describePlayouts(plannedPlayouts, cases, seedsPerCase)}
62
+ `);
63
+ }
64
+ gatePlan = { tenantId, seedsPerCase, assistantModel, plannedPlayouts, cases, caseIds: snapshot?.ids };
65
+ }
66
+ const result = await callControlAPI(`/prompts/${encodeURIComponent(promptKey)}/propose`, {
67
+ method: "POST",
68
+ body: JSON.stringify({ fromRunId, replaySetId, maxDossiers, model: model || undefined })
69
+ }, profileOptions, { timeoutMs: PROPOSE_TIMEOUT_MS, retries: 0 });
70
+ let outPath;
71
+ if (result.status === "proposed" && result.content && flags.out) {
72
+ outPath = isAbsolute(flags.out) ? flags.out : resolve(process.cwd(), flags.out);
73
+ writeFileSync(outPath, result.content, "utf8");
74
+ }
75
+ const promoteOnWin = flags["promote-on-win"] === "true";
76
+ let gate;
77
+ if (gatePlan) {
78
+ if (result.status !== "proposed" || !result.content) {
79
+ gate = {
80
+ ran: false,
81
+ scope: "full-set",
82
+ scopeNote: `no candidate to gate (${result.status})`,
83
+ verdict: "skipped",
84
+ outcome: "no-run",
85
+ runId: null,
86
+ promoted: false,
87
+ plannedPlayouts: gatePlan.plannedPlayouts
88
+ };
89
+ } else {
90
+ gate = await runGate({
91
+ result,
92
+ promptKey,
93
+ replaySetId,
94
+ flags,
95
+ plan: gatePlan,
96
+ promoteOnWin,
97
+ profileOptions,
98
+ context
99
+ });
100
+ }
101
+ }
102
+ if (context.outputMode === "human") {
103
+ printHuman(result, outPath, gate);
104
+ }
105
+ context.output.writeData({
106
+ ...result,
107
+ ...outPath ? { outPath } : {},
108
+ ...gate ? { gate } : {}
109
+ });
110
+ }
111
+ async function fetchGateSetCases(tenantId, replaySetId, profileOptions) {
112
+ let set;
113
+ try {
114
+ set = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(replaySetId)}`, { method: "GET" }, profileOptions);
115
+ } catch (error) {
116
+ if (error?.statusCode === 404) {
117
+ throw new CliInputError(`Replay set '${replaySetId}' not found for tenant ${tenantId}, so nothing was sent.`, "Check --set-id: use the replay set the --from-run run was scored on (see `moda prompts replay-runs <key>`).");
118
+ }
119
+ return null;
120
+ }
121
+ if (!Array.isArray(set?.cases))
122
+ return null;
123
+ return {
124
+ ids: set.cases.filter((c) => String(c?.scenario ?? "").trim()).map((c, index) => String(c?.id ?? `case_${index}`))
125
+ };
126
+ }
127
+ async function runGate(args) {
128
+ const { result, promptKey, replaySetId, flags, plan, promoteOnWin, profileOptions, context } = args;
129
+ const { tenantId, seedsPerCase, assistantModel } = plan;
130
+ const prodArm = await resolvePromptArmSpec(promptKey, "baseline", { preferKey: true });
131
+ const proposedArm = {
132
+ content: result.content ?? "",
133
+ prompt_version_id: result.versionId ?? "",
134
+ prompt_id: "",
135
+ prompt_label: "candidate"
136
+ };
137
+ const timeoutMs = parsePositiveInt(flags["gate-timeout"] ?? flags.timeout, 2 * 60 * 60 * 1000, 24 * 60 * 60 * 1000, flags["gate-timeout"] !== undefined ? "--gate-timeout" : "--timeout");
138
+ const pollIntervalMs = parsePositiveInt(flags["poll-interval"], 15000, 120000, "--poll-interval");
139
+ 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";
140
+ if (context.outputMode === "human") {
141
+ process.stderr.write(`
142
+ Gating candidate for '${promptKey}' — ${scopeNote}...
143
+ `);
144
+ }
145
+ const payload = await enqueueAndPollComparison({
146
+ tenantId,
147
+ replaySetId,
148
+ promptArms: { prod: prodArm, proposed: proposedArm },
149
+ seedsPerCase,
150
+ assistantModel: assistantModel || undefined,
151
+ promotePrimary: false,
152
+ caseIds: plan.caseIds,
153
+ timeoutMs,
154
+ pollIntervalMs,
155
+ onWaiting: context.outputMode === "human" ? (status) => process.stderr.write(`Waiting for gate run (${status})...
156
+ `) : undefined
157
+ }, profileOptions);
158
+ const outcome = candidateOutcome(payload);
159
+ const gate = {
160
+ ran: true,
161
+ scope: "full-set",
162
+ scopeNote,
163
+ verdict: formatVerdict(payload),
164
+ outcome,
165
+ runId: payload.run?.runId ?? null,
166
+ promoted: false,
167
+ plannedPlayouts: plan.plannedPlayouts
168
+ };
169
+ if (promoteOnWin) {
170
+ if (outcome === "candidate" && result.versionId) {
171
+ await callControlAPI(`/prompts/${encodeURIComponent(promptKey)}/labels`, { method: "POST", body: JSON.stringify({ label: "prod", versionId: result.versionId }) }, profileOptions);
172
+ gate.promoted = true;
173
+ } else {
174
+ gate.promoteSkippedReason = outcome === "candidate" ? "no candidate versionId to promote" : `candidate did not strictly win (${outcome})`;
175
+ }
176
+ }
177
+ return gate;
178
+ }
179
+ function printHuman(result, outPath, gate) {
180
+ const w = (s) => process.stderr.write(s + `
181
+ `);
182
+ if (result.status !== "proposed") {
183
+ w(`No candidate produced (${result.status}): ${result.note}`);
184
+ return;
185
+ }
186
+ w(`Proposed candidate for '${result.promptKey}'`);
187
+ w(` new version: ${result.versionId} (unlabeled — not promoted)`);
188
+ w(` base version: ${result.baseVersionId ?? "(none)"}`);
189
+ w(` from run: built from ${result.dossierCount} failure dossier(s)`);
190
+ if (result.changelog)
191
+ w(` changelog: ${result.changelog}`);
192
+ if (result.risks.length)
193
+ w(` risks: ${result.risks.map((r) => `- ${r}`).join(`
194
+ `)}`);
195
+ if (outPath)
196
+ w(` written to: ${outPath}`);
197
+ if (result.lowN) {
198
+ w("");
199
+ w(` ⚠ low sample: ${result.lowN}`);
200
+ }
201
+ w("");
202
+ if (gate?.ran) {
203
+ w("Gate (automatic A/B of candidate vs baseline):");
204
+ w(` scope: ${gate.scopeNote}`);
205
+ w(` verdict: ${gate.verdict}`);
206
+ if (gate.plannedPlayouts !== undefined)
207
+ w(` playouts: ${gate.plannedPlayouts ?? "unknown"} planned`);
208
+ if (gate.runId)
209
+ w(` run id: ${gate.runId}`);
210
+ if (gate.promoted) {
211
+ w(` promoted: candidate won — promoted version ${result.versionId} to label 'prod'`);
212
+ } else if (gate.promoteSkippedReason) {
213
+ w(` promote: skipped (${gate.promoteSkippedReason})`);
214
+ }
215
+ w("");
216
+ if (!gate.promoted) {
217
+ w(` moda prompts promote ${shellArg(result.promptKey)} --label=prod --version=${result.versionId ? shellArg(result.versionId) : "<version_id>"}`);
218
+ }
219
+ return;
220
+ }
221
+ if (gate && !gate.ran) {
222
+ w(`Gate not run: ${gate.scopeNote}`);
223
+ w("");
224
+ }
225
+ w("Next — gate on holdout cases the revision never saw, then promote if it wins:");
226
+ if (result.holdoutCaseIds.length) {
227
+ w(` holdout cases (${result.holdoutCaseIds.length}): ${result.holdoutCaseIds.join(", ")}`);
228
+ } else {
229
+ w(" (too few failures to reserve a holdout — gate on a fresh --auto-generate set)");
230
+ }
231
+ if (outPath) {
232
+ w(` moda prompts ab --baseline-key=${shellArg(result.promptKey)} --candidate=${shellArg(outPath)} --auto-generate`);
233
+ } else {
234
+ w(` # re-run with --out=<path.prompt.md> to materialize the candidate, then:`);
235
+ w(` moda prompts ab --baseline-key=${shellArg(result.promptKey)} --candidate=<path.prompt.md> --auto-generate`);
236
+ }
237
+ w(` # or: moda prompts propose ... --gate --promote-on-win (auto-A/B, promote on a strict win)`);
238
+ w(` moda prompts promote ${shellArg(result.promptKey)} --label=prod --version=${result.versionId ? shellArg(result.versionId) : "<version_id>"}`);
239
+ }
240
+ function parsePositiveInt(raw, fallback, max, flagName) {
241
+ if (raw === undefined)
242
+ return fallback;
243
+ const text = String(raw).trim();
244
+ const parsed = Number(text);
245
+ if (!/^\d+$/.test(text) || !Number.isSafeInteger(parsed) || parsed < 1) {
246
+ throw new CliInputError(`${flagName}=${raw} must be a whole number of at least 1, so nothing was sent.`, `Use ${flagName}=N with 1 <= N <= ${max}.`);
247
+ }
248
+ if (parsed > max) {
249
+ throw new CliInputError(`${flagName}=${parsed} is above the maximum of ${max}, so nothing was sent.`, `Use ${flagName}=${max} or less.`);
250
+ }
251
+ return parsed;
252
+ }
253
+ export {
254
+ runPromptPropose
255
+ };