@moda-ai/cli 1.22.0 → 1.22.2

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 (115) hide show
  1. package/dist/cli-g8qs2tj9.js +9777 -0
  2. package/dist/cli-pwbvtes8.js +2312 -0
  3. package/dist/cli-qszcpda3.js +284 -0
  4. package/dist/cli.js +4775 -1159
  5. package/dist/index-6h0nc1r5.js +2765 -0
  6. package/dist/provision-j5gq8cvx.js +72 -0
  7. package/package.json +3 -2
  8. package/skills/integration/index.json +2 -2
  9. package/dist/agent-schemas.d.ts +0 -3
  10. package/dist/agent-schemas.js +0 -358
  11. package/dist/agent.d.ts +0 -5
  12. package/dist/agent.js +0 -477
  13. package/dist/analyst-tui.d.ts +0 -81
  14. package/dist/analyst-tui.js +0 -314
  15. package/dist/api-client.d.ts +0 -46
  16. package/dist/api-client.js +0 -120
  17. package/dist/ask-stream.d.ts +0 -146
  18. package/dist/ask-stream.js +0 -473
  19. package/dist/auth-storage.d.ts +0 -35
  20. package/dist/auth-storage.js +0 -159
  21. package/dist/auth.d.ts +0 -50
  22. package/dist/auth.js +0 -694
  23. package/dist/cli-session.d.ts +0 -26
  24. package/dist/cli-session.js +0 -169
  25. package/dist/cli.d.ts +0 -44
  26. package/dist/command-context.d.ts +0 -62
  27. package/dist/command-context.js +0 -57
  28. package/dist/command-registry.d.ts +0 -22
  29. package/dist/command-registry.js +0 -15
  30. package/dist/config.d.ts +0 -60
  31. package/dist/config.js +0 -110
  32. package/dist/doctor.d.ts +0 -79
  33. package/dist/doctor.js +0 -1182
  34. package/dist/errors.d.ts +0 -50
  35. package/dist/errors.js +0 -64
  36. package/dist/exit-funnel.d.ts +0 -14
  37. package/dist/exit-funnel.js +0 -53
  38. package/dist/harness-artifact-bodies.d.ts +0 -28
  39. package/dist/harness-artifact-bodies.js +0 -276
  40. package/dist/harness-candidates.d.ts +0 -77
  41. package/dist/harness-candidates.js +0 -482
  42. package/dist/harness-remote.d.ts +0 -135
  43. package/dist/harness-remote.js +0 -540
  44. package/dist/harness-report.d.ts +0 -145
  45. package/dist/harness-report.js +0 -1579
  46. package/dist/harness-sync-limits.d.ts +0 -8
  47. package/dist/harness-sync-limits.js +0 -21
  48. package/dist/harness.d.ts +0 -463
  49. package/dist/harness.js +0 -4009
  50. package/dist/init/index.d.ts +0 -78
  51. package/dist/init/index.js +0 -1442
  52. package/dist/init/mcp.d.ts +0 -2
  53. package/dist/init/mcp.js +0 -27
  54. package/dist/init/progress-board.d.ts +0 -80
  55. package/dist/init/progress-board.js +0 -225
  56. package/dist/init/rules.d.ts +0 -6
  57. package/dist/init/rules.js +0 -171
  58. package/dist/init/sdk-catalog.d.ts +0 -10
  59. package/dist/init/sdk-catalog.js +0 -80
  60. package/dist/init/sdk-guide.d.ts +0 -19
  61. package/dist/init/sdk-guide.js +0 -185
  62. package/dist/init/sdk-integration.d.ts +0 -53
  63. package/dist/init/sdk-integration.js +0 -835
  64. package/dist/init/sdk-protocol.d.ts +0 -135
  65. package/dist/init/sdk-protocol.js +0 -152
  66. package/dist/init/sdk-verification.d.ts +0 -27
  67. package/dist/init/sdk-verification.js +0 -123
  68. package/dist/init/tenant.d.ts +0 -67
  69. package/dist/init/tenant.js +0 -421
  70. package/dist/intelligence-schema.d.ts +0 -349
  71. package/dist/intelligence-schema.js +0 -174
  72. package/dist/keys.d.ts +0 -47
  73. package/dist/keys.js +0 -136
  74. package/dist/manifest.d.ts +0 -5
  75. package/dist/manifest.js +0 -114
  76. package/dist/markdown-terminal.d.ts +0 -49
  77. package/dist/markdown-terminal.js +0 -212
  78. package/dist/output.d.ts +0 -191
  79. package/dist/output.js +0 -624
  80. package/dist/production-intelligence.d.ts +0 -175
  81. package/dist/production-intelligence.js +0 -1430
  82. package/dist/profile-commands.d.ts +0 -10
  83. package/dist/profile-commands.js +0 -342
  84. package/dist/profiles.d.ts +0 -112
  85. package/dist/profiles.js +0 -573
  86. package/dist/prompts-ab.d.ts +0 -71
  87. package/dist/prompts-ab.js +0 -313
  88. package/dist/prompts-propose.d.ts +0 -15
  89. package/dist/prompts-propose.js +0 -212
  90. package/dist/prompts.d.ts +0 -58
  91. package/dist/prompts.js +0 -418
  92. package/dist/provision.d.ts +0 -28
  93. package/dist/provision.js +0 -104
  94. package/dist/redact.d.ts +0 -2
  95. package/dist/redact.js +0 -90
  96. package/dist/schemas.d.ts +0 -225
  97. package/dist/schemas.js +0 -126
  98. package/dist/skills-bundled.d.ts +0 -35
  99. package/dist/skills-bundled.js +0 -157
  100. package/dist/skills-manifest.d.ts +0 -33
  101. package/dist/skills-manifest.js +0 -244
  102. package/dist/skills.d.ts +0 -57
  103. package/dist/skills.js +0 -777
  104. package/dist/sync.d.ts +0 -5
  105. package/dist/sync.js +0 -36
  106. package/dist/telemetry.d.ts +0 -56
  107. package/dist/telemetry.js +0 -167
  108. package/dist/transforms.d.ts +0 -25
  109. package/dist/transforms.js +0 -102
  110. package/dist/tui.d.ts +0 -48
  111. package/dist/tui.js +0 -393
  112. package/dist/update-check.d.ts +0 -31
  113. package/dist/update-check.js +0 -141
  114. package/dist/workspace.d.ts +0 -12
  115. package/dist/workspace.js +0 -1294
@@ -1,482 +0,0 @@
1
- /**
2
- * Static harness-candidate pre-pass (Phase 2 prototype).
3
- *
4
- * A deterministic, LLM-free detector that walks a repo and extracts
5
- * *candidate* harness surfaces — LLM SDK call sites, agent-framework
6
- * constructs, tool definitions/schemas, prompt files and prompt-like string
7
- * literals, and model config assignments — each with a file:line citation,
8
- * a confidence heuristic, and a short excerpt.
9
- *
10
- * Purpose: today `harness analyze` (local or hosted) has a single agent read
11
- * everything, which caps out around ~450 files / ~20 minutes. This pre-pass is
12
- * pure regex/heuristics (no LLM calls, no network), runs in milliseconds per
13
- * thousand files, and produces a prioritized read plan so the LLM
14
- * classification stage only needs to open the files that plausibly matter.
15
- *
16
- * Output contract: `harness_candidates.v0.1` — see {@link HarnessCandidateReport}.
17
- * Candidates are recall-oriented: false positives are expected and cheap (the
18
- * LLM stage filters them); false negatives are the expensive failure mode.
19
- */
20
- import { mkdirSync, writeFileSync } from 'node:fs';
21
- import { basename, dirname, extname, resolve } from 'node:path';
22
- import { inventoryFiles, } from './harness.js';
23
- import { redactDeep } from './redact.js';
24
- const EXCERPT_MAX_CHARS = 240;
25
- const MAX_CANDIDATES_PER_FILE_PER_SIGNAL = 20;
26
- const MIN_PROMPT_LITERAL_CHARS = 160;
27
- const CONFIDENCE_WEIGHT = {
28
- high: 3,
29
- medium: 2,
30
- low: 1,
31
- };
32
- const PY = ['.py'];
33
- const JS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
34
- /**
35
- * Line-anchored detector table. Every regex here must be safe to run on every
36
- * line of every text file (no catastrophic backtracking), and every entry maps
37
- * to exactly one candidate signal so results stay explainable.
38
- */
39
- const LINE_PATTERNS = [
40
- // ---- LLM SDK call sites -------------------------------------------------
41
- {
42
- kind: 'llm_call_site', signal: 'openai.import', confidence: 'high',
43
- pattern: /(^\s*(import|from)\s+openai\b)|(\bfrom\s+['"]openai['"])|(\brequire\(\s*['"]openai['"]\s*\))/,
44
- },
45
- {
46
- kind: 'llm_call_site', signal: 'openai.client_construct', confidence: 'high',
47
- pattern: /\b(new\s+)?(Async)?(OpenAI|AzureOpenAI)\s*\(/,
48
- },
49
- {
50
- kind: 'llm_call_site', signal: 'openai.chat_completions_create', confidence: 'high',
51
- pattern: /\.\s*(chat\s*\.\s*)?completions\s*\.\s*(create|parse|stream)\s*\(|\.\s*responses\s*\.\s*(create|parse|stream)\s*\(/,
52
- },
53
- {
54
- kind: 'llm_call_site', signal: 'anthropic.import', confidence: 'high',
55
- pattern: /(^\s*(import|from)\s+anthropic\b)|(@anthropic-ai\/sdk)|(\bfrom\s+['"]@anthropic-ai\/)/,
56
- },
57
- {
58
- kind: 'llm_call_site', signal: 'anthropic.client_or_messages', confidence: 'high',
59
- pattern: /\b(new\s+)?(Async)?Anthropic(Bedrock|Vertex)?\s*\(|\.\s*messages\s*\.\s*(create|stream)\s*\(/,
60
- },
61
- {
62
- kind: 'llm_call_site', signal: 'openrouter.reference', confidence: 'medium',
63
- pattern: /openrouter\.ai|OPENROUTER_API_KEY|\bopen_?router\b/i,
64
- },
65
- {
66
- kind: 'llm_call_site', signal: 'vercel_ai_sdk.generate', confidence: 'high',
67
- pattern: /\b(generateText|streamText|generateObject|streamObject)\s*\(/,
68
- extensions: JS,
69
- },
70
- {
71
- kind: 'llm_call_site', signal: 'litellm.reference', confidence: 'high',
72
- pattern: /\blitellm\b/,
73
- extensions: PY,
74
- },
75
- {
76
- kind: 'llm_call_site', signal: 'google_genai.reference', confidence: 'high',
77
- pattern: /google\.generativeai|@google\/generative-ai|@google\/genai|\bGenerativeModel\s*\(/,
78
- },
79
- {
80
- kind: 'llm_call_site', signal: 'bedrock.invoke_model', confidence: 'medium',
81
- pattern: /\binvoke_model\s*\(|InvokeModelCommand|bedrock-runtime/,
82
- },
83
- // ---- Agent framework constructs ----------------------------------------
84
- {
85
- kind: 'framework_construct', signal: 'langchain.import', confidence: 'high',
86
- pattern: /(^\s*(import|from)\s+langchain\w*\b)|(\bfrom\s+['"]@?langchain\b)|(@langchain\/)/,
87
- },
88
- {
89
- kind: 'framework_construct', signal: 'langchain.construct', confidence: 'high',
90
- pattern: /\b(ChatOpenAI|ChatAnthropic|ChatVertexAI|ChatBedrock|AgentExecutor|LLMChain|ChatPromptTemplate|PromptTemplate)\s*(\(|\.)/,
91
- },
92
- {
93
- kind: 'framework_construct', signal: 'langgraph.import_or_graph', confidence: 'high',
94
- pattern: /(^\s*(import|from)\s+langgraph\b)|(@langchain\/langgraph)|\bStateGraph\s*\(|\bcreate_react_agent\s*\(/,
95
- },
96
- {
97
- kind: 'framework_construct', signal: 'crewai.import_or_crew', confidence: 'high',
98
- pattern: /(^\s*(import|from)\s+crewai\b)|\bcrewai\b/,
99
- },
100
- {
101
- kind: 'framework_construct', signal: 'openai_agents.import_or_runner', confidence: 'high',
102
- pattern: /(^\s*from\s+agents\s+import\b)|\bopenai-agents\b|\bRunner\s*\.\s*run(_sync|_streamed)?\s*\(/,
103
- },
104
- {
105
- kind: 'framework_construct', signal: 'claude_agent_sdk.import', confidence: 'high',
106
- pattern: /@anthropic-ai\/claude-agent-sdk|\bclaude_agent_sdk\b|\bclaude-agent-sdk\b/,
107
- },
108
- {
109
- kind: 'framework_construct', signal: 'other_framework.import', confidence: 'medium',
110
- pattern: /(^\s*(import|from)\s+(autogen|pydantic_ai|dspy|semantic_kernel|smolagents|llama_index)\b)|\bfrom\s+['"]llamaindex['"]/,
111
- },
112
- // ---- Tool definitions ---------------------------------------------------
113
- {
114
- kind: 'tool_definition', signal: 'python.tool_decorator', confidence: 'high',
115
- pattern: /^\s*@(tool|function_tool|mcp\.tool|app\.tool)\b/,
116
- extensions: PY,
117
- },
118
- {
119
- kind: 'tool_definition', signal: 'python.base_tool_subclass', confidence: 'high',
120
- pattern: /class\s+\w+\s*\(\s*(BaseTool|StructuredTool)\b/,
121
- extensions: PY,
122
- },
123
- {
124
- kind: 'tool_definition', signal: 'js.ai_sdk_tool', confidence: 'medium',
125
- pattern: /\btool\s*\(\s*\{|\bcreateTool\s*\(|\bDynamicStructuredTool\s*\(/,
126
- extensions: JS,
127
- },
128
- {
129
- kind: 'tool_definition', signal: 'tool_registry.registration', confidence: 'medium',
130
- pattern: /\b(register_tool|registerTool|add_tool|ToolRegistry)\s*\(/,
131
- },
132
- // ---- Tool schema dicts --------------------------------------------------
133
- {
134
- kind: 'tool_schema', signal: 'openai.function_schema', confidence: 'high',
135
- pattern: /["']type["']\s*:\s*["']function["']/,
136
- },
137
- {
138
- kind: 'tool_schema', signal: 'anthropic.input_schema', confidence: 'high',
139
- pattern: /\binput_schema\b|\binputSchema\s*[:=]/,
140
- },
141
- {
142
- kind: 'tool_schema', signal: 'json_schema.parameters_block', confidence: 'medium',
143
- pattern: /["']parameters["']\s*:\s*\{/,
144
- },
145
- // ---- Message construction (weak corroborating signal) -------------------
146
- {
147
- kind: 'message_construction', signal: 'chat.role_content_dict', confidence: 'low',
148
- pattern: /["']role["']\s*:\s*["'](system|user|assistant|tool)["']|\brole\s*=\s*["'](system|user|assistant)["']/,
149
- },
150
- // ---- Model config assignments -------------------------------------------
151
- {
152
- kind: 'model_config', signal: 'model.assignment', confidence: 'high',
153
- pattern: /\b(model|model_name|model_id|modelName|modelId|MODEL[A-Z_]*)\s*[:=]\s*["'`][\w.:/-]*(gpt-|o[134](-mini)?\b|claude|gemini|llama|mistral|mixtral|deepseek|qwen|grok|sonnet|opus|haiku|command-r|glm|gemma)[\w.:/-]*["'`]/i,
154
- },
155
- {
156
- kind: 'model_config', signal: 'model.env_default', confidence: 'medium',
157
- pattern: /\b(OPENAI_MODEL|ANTHROPIC_MODEL|MODEL_NAME|LLM_MODEL|DEFAULT_MODEL)\b/,
158
- },
159
- ];
160
- const CODE_EXTENSIONS = new Set([...JS, ...PY]);
161
- const PROMPT_DOC_EXTENSIONS = new Set(['.md', '.mdc', '.txt', '.prompt', '.jinja', '.jinja2', '.j2', '.hbs', '.tpl']);
162
- const NON_PROMPT_DOC_BASENAMES = new Set([
163
- 'readme.md', 'agents.md', 'claude.md', 'changelog.md', 'contributing.md',
164
- 'license.md', 'license.txt', 'code_of_conduct.md', 'security.md', 'requirements.txt',
165
- ]);
166
- const PROMPT_NAME_HINT = /(prompt|system[-_]?message|instruction|persona|sop)/i;
167
- const AGENTIC_DIR_HINT = /(^|\/)(agents?|prompts?|llm|ai|assistants?|chains?|crew|graph)s?(\/|$)/i;
168
- const INSTRUCTION_MARKERS = [
169
- /\byou are\b/i,
170
- /\byou're\s+(a|an|the)\b/i,
171
- /\byour (task|role|job|goal|objective)\b/i,
172
- /\b(do not|don't|never|always|must|should)\b/i,
173
- /\brespond (with|only|in|using)\b/i,
174
- /\bstep[- ]by[- ]step\b/i,
175
- /\binstructions?\b/i,
176
- /\bassistant\b/i,
177
- /\bthe user\b/i,
178
- /\bif the user\b/i,
179
- ];
180
- function instructionScore(text) {
181
- let score = 0;
182
- for (const marker of INSTRUCTION_MARKERS) {
183
- if (marker.test(text))
184
- score += 1;
185
- }
186
- return score;
187
- }
188
- function normalizeExcerpt(text) {
189
- const collapsed = text.replace(/\s+/g, ' ').trim();
190
- return collapsed.length > EXCERPT_MAX_CHARS
191
- ? `${collapsed.slice(0, EXCERPT_MAX_CHARS - 1)}…`
192
- : collapsed;
193
- }
194
- /** Map a character index into 1-based line numbers using precomputed offsets. */
195
- function buildLineIndex(text) {
196
- const offsets = [0];
197
- for (let i = 0; i < text.length; i += 1) {
198
- if (text.charCodeAt(i) === 10)
199
- offsets.push(i + 1);
200
- }
201
- return offsets;
202
- }
203
- function lineAtIndex(offsets, index) {
204
- let lo = 0;
205
- let hi = offsets.length - 1;
206
- while (lo < hi) {
207
- const mid = (lo + hi + 1) >> 1;
208
- if (offsets[mid] <= index)
209
- lo = mid;
210
- else
211
- hi = mid - 1;
212
- }
213
- return lo + 1;
214
- }
215
- function detectLinePatterns(file, out) {
216
- const ext = extname(file.path).toLowerCase();
217
- const lines = file.text.split('\n');
218
- const perSignal = new Map();
219
- for (const pattern of LINE_PATTERNS) {
220
- if (pattern.extensions && !pattern.extensions.includes(ext))
221
- continue;
222
- for (let i = 0; i < lines.length; i += 1) {
223
- const line = lines[i];
224
- if (line.length > 4000)
225
- continue; // minified/bundled line — skip, not agent source
226
- if (!pattern.pattern.test(line))
227
- continue;
228
- const seen = perSignal.get(pattern.signal) ?? 0;
229
- if (seen >= MAX_CANDIDATES_PER_FILE_PER_SIGNAL)
230
- break;
231
- perSignal.set(pattern.signal, seen + 1);
232
- out.push({
233
- kind: pattern.kind,
234
- path: file.path,
235
- startLine: i + 1,
236
- endLine: i + 1,
237
- confidence: pattern.confidence,
238
- signal: pattern.signal,
239
- excerpt: normalizeExcerpt(line),
240
- });
241
- }
242
- }
243
- }
244
- /** A line that is (only) a string literal, optionally with prefix/trailing punctuation. */
245
- const STRING_ONLY_LINE = /^\s*[frbu]{0,3}("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*[,)\]]*;?\s*$/i;
246
- const MIN_ADJACENT_STRING_LINES = 4;
247
- /**
248
- * Prompts assembled from runs of adjacent single-line string literals
249
- * (Python implicit concatenation, JS `[...].join('\n')` arrays). The
250
- * triple-quote/template-literal scan cannot see these.
251
- */
252
- function detectAdjacentStringBlocks(file, out) {
253
- const lines = file.text.split('\n');
254
- let blockStart = -1;
255
- let parts = [];
256
- let found = 0;
257
- const flush = (endLineIndex) => {
258
- const start = blockStart;
259
- blockStart = -1;
260
- const content = parts.join('\n');
261
- parts = [];
262
- if (start === -1 || endLineIndex - start + 1 < MIN_ADJACENT_STRING_LINES)
263
- return;
264
- if (content.length < MIN_PROMPT_LITERAL_CHARS)
265
- return;
266
- const score = instructionScore(content);
267
- if (score < 2)
268
- return;
269
- if (found >= MAX_CANDIDATES_PER_FILE_PER_SIGNAL)
270
- return;
271
- found += 1;
272
- out.push({
273
- kind: 'prompt_literal',
274
- path: file.path,
275
- startLine: start + 1,
276
- endLine: endLineIndex + 1,
277
- confidence: score >= 4 || content.length >= 600 ? 'high' : 'medium',
278
- signal: 'code.adjacent_string_block',
279
- excerpt: normalizeExcerpt(content),
280
- });
281
- };
282
- for (let i = 0; i < lines.length; i += 1) {
283
- const match = lines[i].length <= 4000 ? STRING_ONLY_LINE.exec(lines[i]) : null;
284
- if (match) {
285
- if (blockStart === -1)
286
- blockStart = i;
287
- parts.push(match[1].slice(1, -1));
288
- }
289
- else {
290
- flush(i - 1);
291
- }
292
- }
293
- flush(lines.length - 1);
294
- }
295
- const PROMPT_MODULE_NAME_HINT = /(^|[._-])(system[._-]?)?prompts?([._-]|$)/i;
296
- /** Code modules named like prompt builders (system_prompt.py, prompts.ts, ...). */
297
- function detectPromptModule(file, out) {
298
- const base = basename(file.path, extname(file.path));
299
- if (!PROMPT_MODULE_NAME_HINT.test(base))
300
- return;
301
- out.push({
302
- kind: 'prompt_file',
303
- path: file.path,
304
- startLine: 1,
305
- endLine: Math.max(1, file.text.split('\n').length),
306
- confidence: 'medium',
307
- signal: 'prompt_module.name_hint',
308
- excerpt: normalizeExcerpt(file.text.slice(0, EXCERPT_MAX_CHARS * 2)),
309
- });
310
- }
311
- /**
312
- * Multi-line string literal scan: Python triple-quoted strings and JS/TS
313
- * template literals whose contents are long and instruction-like.
314
- */
315
- function detectPromptLiterals(file, out) {
316
- const ext = extname(file.path).toLowerCase();
317
- const offsets = buildLineIndex(file.text);
318
- let found = 0;
319
- const record = (startIndex, endIndex, content, signal) => {
320
- if (content.length < MIN_PROMPT_LITERAL_CHARS)
321
- return;
322
- const score = instructionScore(content);
323
- if (score < 2)
324
- return;
325
- if (found >= MAX_CANDIDATES_PER_FILE_PER_SIGNAL)
326
- return;
327
- found += 1;
328
- out.push({
329
- kind: 'prompt_literal',
330
- path: file.path,
331
- startLine: lineAtIndex(offsets, startIndex),
332
- endLine: lineAtIndex(offsets, endIndex),
333
- confidence: score >= 4 || content.length >= 600 ? 'high' : 'medium',
334
- signal,
335
- excerpt: normalizeExcerpt(content),
336
- });
337
- };
338
- if (ext === '.py') {
339
- const tripleQuoted = /("""|''')([\s\S]*?)\1/g;
340
- let match;
341
- while ((match = tripleQuoted.exec(file.text)) !== null) {
342
- record(match.index, match.index + match[0].length - 1, match[2], 'python.triple_quoted_string');
343
- }
344
- return;
345
- }
346
- if (CODE_EXTENSIONS.has(ext)) {
347
- // Approximate template-literal spans: toggle on unescaped backticks. Nested
348
- // ${`...`} interpolation can split a literal in two — acceptable for a
349
- // recall-oriented heuristic (both halves are still instruction-scored).
350
- let start = -1;
351
- for (let i = 0; i < file.text.length; i += 1) {
352
- if (file.text[i] !== '`' || (i > 0 && file.text[i - 1] === '\\'))
353
- continue;
354
- if (start === -1) {
355
- start = i;
356
- }
357
- else {
358
- record(start, i, file.text.slice(start + 1, i), 'js.template_literal');
359
- start = -1;
360
- }
361
- }
362
- }
363
- }
364
- function detectPromptFile(file, out) {
365
- const ext = extname(file.path).toLowerCase();
366
- if (!PROMPT_DOC_EXTENSIONS.has(ext))
367
- return;
368
- const base = basename(file.path).toLowerCase();
369
- if (NON_PROMPT_DOC_BASENAMES.has(base))
370
- return;
371
- if (/(^|\/)(docs?|documentation|examples?|\.github)\//i.test(file.path))
372
- return;
373
- const dir = dirname(file.path);
374
- const nameHint = PROMPT_NAME_HINT.test(base);
375
- const dirHint = AGENTIC_DIR_HINT.test(dir);
376
- const score = instructionScore(file.text.slice(0, 20_000));
377
- let confidence;
378
- let signal;
379
- if (nameHint) {
380
- confidence = 'high';
381
- signal = 'prompt_file.name_hint';
382
- }
383
- else if (dirHint && score >= 3) {
384
- confidence = 'medium';
385
- signal = 'prompt_file.agent_dir_instruction_text';
386
- }
387
- else if (score >= 5) {
388
- confidence = 'low';
389
- signal = 'prompt_file.instruction_text';
390
- }
391
- if (!confidence || !signal)
392
- return;
393
- out.push({
394
- kind: 'prompt_file',
395
- path: file.path,
396
- startLine: 1,
397
- endLine: Math.max(1, file.text.split('\n').length),
398
- confidence,
399
- signal,
400
- excerpt: normalizeExcerpt(file.text.slice(0, EXCERPT_MAX_CHARS * 2)),
401
- });
402
- }
403
- /**
404
- * Pure detector core: run every heuristic over an already-materialized file
405
- * inventory. No filesystem access — unit tests and the walker wrapper both
406
- * call this.
407
- */
408
- export function detectHarnessCandidatesInFiles(files) {
409
- const candidates = [];
410
- for (const file of files) {
411
- if (typeof file.text !== 'string' || file.text.length === 0)
412
- continue;
413
- const scoped = { path: file.path, text: file.text };
414
- const ext = extname(file.path).toLowerCase();
415
- if (CODE_EXTENSIONS.has(ext) || ext === '.json' || ext === '.yaml' || ext === '.yml' || ext === '.toml') {
416
- detectLinePatterns(scoped, candidates);
417
- }
418
- if (CODE_EXTENSIONS.has(ext)) {
419
- detectAdjacentStringBlocks(scoped, candidates);
420
- detectPromptModule(scoped, candidates);
421
- }
422
- detectPromptLiterals(scoped, candidates);
423
- detectPromptFile(scoped, candidates);
424
- }
425
- candidates.sort((a, b) => a.path === b.path ? a.startLine - b.startLine || a.signal.localeCompare(b.signal) : a.path.localeCompare(b.path));
426
- const byFile = new Map();
427
- for (const candidate of candidates) {
428
- let summary = byFile.get(candidate.path);
429
- if (!summary) {
430
- summary = { path: candidate.path, score: 0, kinds: [], candidateCount: 0 };
431
- byFile.set(candidate.path, summary);
432
- }
433
- summary.score += CONFIDENCE_WEIGHT[candidate.confidence];
434
- summary.candidateCount += 1;
435
- if (!summary.kinds.includes(candidate.kind))
436
- summary.kinds.push(candidate.kind);
437
- }
438
- const fileSummaries = [...byFile.values()].sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
439
- return { candidates, files: fileSummaries };
440
- }
441
- /**
442
- * Walk `rootDir` with the harness inventory walker (same skip/secret/symlink
443
- * budget rules as `harness scan`, widened to include all prompt-doc files)
444
- * and run the static detector over it.
445
- */
446
- export function detectHarnessCandidates(rootDir) {
447
- const startedAt = Date.now();
448
- const skipped = [];
449
- const root = resolve(rootDir);
450
- const inventory = inventoryFiles(root, skipped, {
451
- includeFile: (path) => PROMPT_DOC_EXTENSIONS.has(extname(path).toLowerCase()),
452
- });
453
- const { candidates, files } = detectHarnessCandidatesInFiles(inventory);
454
- const durationMs = Math.max(1, Date.now() - startedAt);
455
- const textBytesScanned = inventory.reduce((sum, file) => sum + (file.text ? Buffer.byteLength(file.text, 'utf8') : 0), 0);
456
- const report = {
457
- schemaVersion: 'harness_candidates.v0.1',
458
- generatedAt: new Date().toISOString(),
459
- rootDir: root,
460
- stats: {
461
- filesScanned: inventory.length,
462
- filesWithCandidates: files.length,
463
- candidateCount: candidates.length,
464
- textBytesScanned,
465
- durationMs,
466
- filesPerSecond: Math.round((inventory.length / durationMs) * 1000),
467
- },
468
- candidates,
469
- files,
470
- skipped,
471
- };
472
- // Excerpts quote user source verbatim; strip anything token-shaped before
473
- // the report leaves the process.
474
- return redactDeep(report);
475
- }
476
- export function writeHarnessCandidatesReport(report, outPath) {
477
- const target = resolve(outPath);
478
- mkdirSync(dirname(target), { recursive: true });
479
- writeFileSync(target, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
480
- return target;
481
- }
482
- //# sourceMappingURL=harness-candidates.js.map
@@ -1,135 +0,0 @@
1
- /**
2
- * Server-side ("Moda-hosted") harness analyze client.
3
- *
4
- * `moda harness analyze --remote` builds a safe source snapshot with
5
- * `buildSourceSnapshot` (harness.ts), uploads it to the ingest worker
6
- * (`POST /v1/harness/analyze`), polls `GET /v1/harness/analyze/:id` for the
7
- * validated report, then writes the same local artifacts and emits the same
8
- * lifecycle events as the local analyze paths.
9
- *
10
- * The flow is split into composable pieces so callers can overlap or detach it:
11
- * `startRemoteAnalyze` (snapshot + upload + POST), `pollRemoteAnalyzeUntilDone`
12
- * (status loop with an `onProgress` hook), and `finalizeRemoteAnalyzeResult`
13
- * (repair + local validation + artifact writes). `runRemoteHarnessAnalyst`
14
- * composes all three for `moda harness analyze --remote`, and `moda harness
15
- * pull` (runHarnessPull) resumes a detached run from the
16
- * `.moda/harness-remote-run.json` state file.
17
- */
18
- import type { CommandContext } from './command-context.js';
19
- import { type HarnessCommandScanPlan, type HarnessTenantContext } from './harness.js';
20
- /** Per-repo pointer to the most recent server-side analysis run. */
21
- export declare const HARNESS_REMOTE_RUN_STATE_PATH: string;
22
- /**
23
- * Small state file written on start and on terminal states so `moda harness
24
- * pull` and `moda harness status` can find a run after the starting process
25
- * exited. Snake_case keys match the wire contract.
26
- */
27
- export interface HarnessRemoteRunState {
28
- run_id: string;
29
- started_at_iso: string;
30
- status: 'running' | 'completed' | 'failed';
31
- }
32
- /**
33
- * Optional intermediate progress attached by newer hosted runners to
34
- * `accepted`/`running` status responses. Older servers omit it.
35
- */
36
- export interface RemoteAnalyzeProgress {
37
- phase?: string;
38
- message?: string;
39
- tool_calls?: number;
40
- updated_ms?: number;
41
- }
42
- export interface RemoteAnalyzeStatus {
43
- success: boolean;
44
- status: 'accepted' | 'running' | 'completed' | 'failed';
45
- report?: unknown;
46
- validation?: unknown;
47
- error?: string;
48
- progress?: RemoteAnalyzeProgress;
49
- }
50
- /**
51
- * True when `moda harness analyze` should run server-side (Moda-hosted) instead
52
- * of spawning a local coding agent: `--remote` (flags.remote === 'true') or the
53
- * `MODA_HARNESS_REMOTE` env opt-in.
54
- */
55
- export declare function isRemoteHarnessAnalyze(context: CommandContext): boolean;
56
- /** Read `.moda/harness-remote-run.json`; undefined when absent or unparseable. */
57
- export declare function readRemoteRunState(rootDir: string): HarnessRemoteRunState | undefined;
58
- /**
59
- * Snapshot + upload + POST. Emits the inventory/upload lifecycle events,
60
- * persists `.moda/harness-remote-run.json` with status `running`, and returns
61
- * the accepted run id without waiting for the analysis.
62
- */
63
- export declare function startRemoteAnalyze(options: {
64
- context: CommandContext;
65
- rootDir: string;
66
- }): Promise<{
67
- runId: string;
68
- snapshotTruncated: boolean;
69
- }>;
70
- /**
71
- * Poll `GET /v1/harness/analyze/:id` until the run is terminal, the deadline
72
- * passes, or `signal` aborts. `onProgress` fires after every poll with the
73
- * latest status and any server-attached progress, so callers can render a live
74
- * board; the poll cadence (3s doubling to 15s) is unchanged.
75
- */
76
- export declare function pollRemoteAnalyzeUntilDone(options: {
77
- context: CommandContext;
78
- runId: string;
79
- onProgress?: (update: {
80
- status: RemoteAnalyzeStatus['status'];
81
- progress?: RemoteAnalyzeProgress;
82
- }) => void;
83
- timeoutMs?: number;
84
- signal?: AbortSignal;
85
- }): Promise<RemoteAnalyzeStatus>;
86
- /**
87
- * Turn a terminal (or timed-out) status payload into the local result: repair
88
- * the report from local source, re-validate, write `.moda` artifacts, emit the
89
- * artifact/validation events, and update the run state file. Shared by
90
- * `moda harness analyze --remote` and `moda harness pull`. Throws for runs the
91
- * client cannot recover; a timed-out non-terminal run keeps the state file at
92
- * `running` so `moda harness pull` can resume it later.
93
- */
94
- export declare function finalizeRemoteAnalyzeResult(options: {
95
- context: CommandContext;
96
- rootDir: string;
97
- scanPlan: HarnessCommandScanPlan;
98
- runId: string;
99
- final: RemoteAnalyzeStatus;
100
- snapshotTruncated?: boolean;
101
- }): {
102
- exitCode: number;
103
- };
104
- /**
105
- * Server-side ("Moda-hosted") harness analysis. Builds a safe source snapshot,
106
- * uploads it to the ingest worker, polls for the validated report, then writes
107
- * the same local artifacts the local analyze path writes. Emits the same
108
- * lifecycle events so `--agent --stream` behaves consistently with the local
109
- * path.
110
- */
111
- export declare function runRemoteHarnessAnalyst(options: {
112
- context: CommandContext;
113
- rootDir: string;
114
- scanPlan: HarnessCommandScanPlan;
115
- tenant?: HarnessTenantContext;
116
- }): Promise<{
117
- exitCode: number;
118
- }>;
119
- /**
120
- * `moda harness pull [--run-id=<id>] [--sync]`: fetch the status of a
121
- * server-side analysis run once (default run id from
122
- * `.moda/harness-remote-run.json`). Still running: print status + progress and
123
- * exit 0. Completed: finalize exactly like `harness analyze --remote` (repair,
124
- * validate, write `.moda` artifacts); with `--sync` also approve + `harness
125
- * sync --from-report` best-effort, the way `moda init` does. Failed: error,
126
- * exit 1.
127
- */
128
- export declare function runHarnessPull(options: {
129
- context: CommandContext;
130
- rootDir: string;
131
- scanPlan: HarnessCommandScanPlan;
132
- }): Promise<{
133
- exitCode: number;
134
- }>;
135
- //# sourceMappingURL=harness-remote.d.ts.map