@haystackeditor/cli 0.15.27 → 0.15.29

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 (55) hide show
  1. package/README.md +13 -20
  2. package/dist/assets/hooks/agent-context/detect.ts +7 -7
  3. package/dist/assets/hooks/agent-context/parsers/claude.ts +1 -1
  4. package/dist/assets/hooks/scripts/commit-msg.sh +1 -3
  5. package/dist/assets/hooks/scripts/post-commit.sh +1 -3
  6. package/dist/assets/hooks/scripts/pre-push.sh +1 -4
  7. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +1 -2
  8. package/dist/assets/telemetry/browser-runtime.js +668 -43
  9. package/dist/assets/telemetry/runtime.cjs +62 -68
  10. package/dist/commands/design-verify.d.ts +11 -3
  11. package/dist/commands/design-verify.js +147 -4
  12. package/dist/commands/hooks.d.ts +1 -5
  13. package/dist/commands/hooks.js +10 -112
  14. package/dist/commands/install-session-hooks.d.ts +2 -2
  15. package/dist/commands/install-session-hooks.js +71 -21
  16. package/dist/commands/mcp.js +1 -34
  17. package/dist/commands/pr.js +2 -7
  18. package/dist/commands/setup.d.ts +1 -2
  19. package/dist/commands/setup.js +18 -272
  20. package/dist/commands/submit.js +1 -2
  21. package/dist/commands/verify-hosted-mcp.js +12 -2
  22. package/dist/commands/verify-hosted-reproducibility.d.ts +1 -0
  23. package/dist/commands/verify-hosted-reproducibility.js +4 -1
  24. package/dist/commands/verify-hosted.d.ts +20 -0
  25. package/dist/commands/verify-hosted.js +119 -11
  26. package/dist/commands/verify-precompute.d.ts +19 -0
  27. package/dist/commands/verify-precompute.js +328 -0
  28. package/dist/index.js +45 -44
  29. package/dist/schema.d.ts +1 -2
  30. package/dist/schema.js +1 -2
  31. package/dist/triage/prompts.d.ts +0 -7
  32. package/dist/triage/prompts.js +0 -145
  33. package/dist/triage/runner.js +3 -22
  34. package/dist/triage/types.d.ts +1 -8
  35. package/dist/types.d.ts +8 -8
  36. package/dist/types.js +1 -1
  37. package/dist/utils/design-verifier-api.d.ts +71 -0
  38. package/dist/utils/design-verifier-api.js +96 -0
  39. package/dist/utils/design-verifier-result.d.ts +7 -2
  40. package/dist/utils/design-verifier-result.js +67 -21
  41. package/dist/utils/git.d.ts +5 -1
  42. package/dist/utils/git.js +19 -9
  43. package/dist/utils/github-api.js +11 -14
  44. package/dist/utils/hooks.d.ts +1 -15
  45. package/dist/utils/hooks.js +1 -127
  46. package/dist/utils/telemetry.d.ts +2 -0
  47. package/dist/utils/telemetry.js +81 -8
  48. package/package.json +2 -1
  49. package/schemas/cloud-verifier.v1.json +69 -3
  50. package/schemas/{pr.v2.json → pr.v3.json} +3 -12
  51. package/dist/commands/traces.d.ts +0 -48
  52. package/dist/commands/traces.js +0 -92
  53. package/dist/triage/traces.d.ts +0 -20
  54. package/dist/triage/traces.js +0 -311
  55. package/schemas/traces.v1.json +0 -53
@@ -1,311 +0,0 @@
1
- /**
2
- * Local Entire CLI trace discovery for intent drift analysis.
3
- *
4
- * Finds .entire/metadata/ session files whose modified files
5
- * overlap with the current diff's changed files.
6
- */
7
- import { execSync } from 'child_process';
8
- import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
9
- import { join } from 'path';
10
- /**
11
- * Get the list of files changed in the current diff vs base branch.
12
- */
13
- function getChangedFiles(gitRoot, baseBranch) {
14
- try {
15
- const output = execSync(`git diff ${baseBranch}...HEAD --name-only`, {
16
- encoding: 'utf-8',
17
- cwd: gitRoot,
18
- stdio: ['pipe', 'pipe', 'pipe'],
19
- }).trim();
20
- if (!output)
21
- return [];
22
- return output.split('\n').filter(Boolean);
23
- }
24
- catch {
25
- return [];
26
- }
27
- }
28
- /**
29
- * Extract file paths mentioned in a JSONL trace file.
30
- *
31
- * Looks for file paths in tool_use content blocks (Write, Edit, Read operations)
32
- * by scanning for common path patterns in the JSONL entries.
33
- */
34
- function extractFilesFromTrace(tracePath) {
35
- const files = new Set();
36
- try {
37
- const content = readFileSync(tracePath, 'utf-8');
38
- const lines = content.split('\n').filter(Boolean);
39
- for (const line of lines) {
40
- try {
41
- const entry = JSON.parse(line);
42
- // Look for file paths in tool use inputs
43
- // Claude Code format: { type: "tool_use", name: "Write"|"Edit"|"Read", input: { file_path: "..." } }
44
- if (entry?.type === 'tool_use' || entry?.message?.content) {
45
- const text = typeof entry === 'string' ? entry : JSON.stringify(entry);
46
- // Match file_path values
47
- const filePathMatches = text.match(/"file_path"\s*:\s*"([^"]+)"/g);
48
- if (filePathMatches) {
49
- for (const match of filePathMatches) {
50
- const pathMatch = match.match(/"file_path"\s*:\s*"([^"]+)"/);
51
- if (pathMatch?.[1]) {
52
- // Normalize: strip leading ./ and absolute path prefixes
53
- let filePath = pathMatch[1];
54
- filePath = filePath.replace(/^\.\//, '');
55
- // Extract relative path if absolute
56
- const parts = filePath.split('/');
57
- // Keep as-is for now; the overlap check handles it
58
- files.add(filePath);
59
- }
60
- }
61
- }
62
- }
63
- }
64
- catch {
65
- // Skip malformed JSONL lines
66
- }
67
- }
68
- }
69
- catch {
70
- // File read error
71
- }
72
- return Array.from(files);
73
- }
74
- /**
75
- * Check if any file from the trace overlaps with changed files.
76
- * Uses fuzzy matching: a trace file "src/foo.ts" matches changed file "src/foo.ts",
77
- * and an absolute path "/repo/src/foo.ts" matches "src/foo.ts" by suffix.
78
- */
79
- function hasOverlap(traceFiles, changedFiles) {
80
- for (const tf of traceFiles) {
81
- for (const cf of changedFiles) {
82
- if (tf === cf || tf.endsWith('/' + cf) || cf.endsWith('/' + tf)) {
83
- return true;
84
- }
85
- }
86
- }
87
- return false;
88
- }
89
- /**
90
- * Matches a complete slash-command block from <command-name> through the last
91
- * </command-*> tag. The block structure is:
92
- * <command-name>/foo</command-name>
93
- * <command-message>...</command-message>
94
- * <command-args>...</command-args>
95
- *
96
- * We match each tag group individually and make message/args optional so partial
97
- * blocks (e.g., name-only) are still stripped.
98
- */
99
- const COMMAND_BLOCK_RE = /<command-name>\s*\/[^<]*<\/command-name>(?:\s*<command-message>[\s\S]*?<\/command-message>)?(?:\s*<command-args>[\s\S]*?<\/command-args>)?\s*/g;
100
- /**
101
- * Matches a single <local-command-caveat>...</local-command-caveat> wrapper.
102
- * Non-greedy so adjacent caveats are matched individually (avoids eating
103
- * human text between two caveats).
104
- */
105
- const CAVEAT_RE = /<local-command-caveat>[\s\S]*?<\/local-command-caveat>\s*/g;
106
- /**
107
- * Anchored: matches only when the entire string (after trimming) is one or
108
- * more command blocks with no surrounding human text.
109
- */
110
- const COMMAND_ONLY_RE = /^\s*(?:<command-name>\s*\/[^<]*<\/command-name>(?:\s*<command-message>[\s\S]*?<\/command-message>)?(?:\s*<command-args>[\s\S]*?<\/command-args>)?\s*)+$/;
111
- /**
112
- * Strip machine-generated wrappers from a text block so only the
113
- * human-authored content remains. Removes:
114
- * - <local-command-caveat>...</local-command-caveat> wrappers (individually)
115
- * - <command-name>...(closing tag) slash-command blocks
116
- */
117
- function stripWrappers(text) {
118
- return text
119
- .replace(CAVEAT_RE, '')
120
- .replace(COMMAND_BLOCK_RE, '')
121
- .trim();
122
- }
123
- /**
124
- * Returns true if the text consists entirely of slash-command blocks with no
125
- * surrounding human text. Anchored to avoid false positives when a user
126
- * message merely quotes or references a command tag.
127
- */
128
- function isSlashCommand(text) {
129
- return COMMAND_ONLY_RE.test(text);
130
- }
131
- /**
132
- * Extract the human-authored text from a message content value.
133
- *
134
- * Content can be a plain string or a block array. For block arrays, we pull
135
- * only `{ type: "text" }` blocks (skipping tool_result, tool_reference, etc.)
136
- * and strip machine wrappers from each block individually.
137
- */
138
- function extractHumanText(content) {
139
- if (typeof content === 'string') {
140
- const stripped = stripWrappers(content);
141
- if (!stripped || isSlashCommand(stripped))
142
- return null;
143
- return stripped;
144
- }
145
- if (!Array.isArray(content))
146
- return null;
147
- const textParts = [];
148
- for (const block of content) {
149
- // Support both old ({type: "text", text}) and new ({id, text}) content blocks
150
- if (typeof block?.text !== 'string')
151
- continue;
152
- if (block.type && block.type !== 'text')
153
- continue;
154
- const stripped = stripWrappers(block.text);
155
- if (stripped && !isSlashCommand(stripped)) {
156
- textParts.push(stripped);
157
- }
158
- }
159
- return textParts.length > 0 ? textParts.join('\n') : null;
160
- }
161
- /**
162
- * Extract only user-role messages from a JSONL trace file.
163
- *
164
- * The raw trace contains tool results, assistant reasoning, and tool_use blocks
165
- * that can bloat a single session to 10MB+. The intent-drift checker only needs
166
- * the user's actual messages to judge whether the agent fulfilled the request.
167
- *
168
- * Filters out:
169
- * - tool_result / tool_reference blocks (machine output, not user intent)
170
- * - All slash-command messages (not just /clear)
171
- * - local-command-caveat wrappers (stripped per-block for arrays)
172
- */
173
- /**
174
- * Parse JSONL content that may contain multi-line JSON entries.
175
- *
176
- * Tries each line as a standalone JSON object first (fast path). On parse
177
- * failure, accumulates subsequent lines until a valid object is formed or
178
- * a cap is hit, so multi-line entries aren't silently dropped.
179
- */
180
- function parseJsonl(content, tracePath) {
181
- const entries = [];
182
- const lines = content.split('\n');
183
- let i = 0;
184
- while (i < lines.length) {
185
- const line = lines[i].trim();
186
- if (!line) {
187
- i++;
188
- continue;
189
- }
190
- try {
191
- entries.push(JSON.parse(line));
192
- i++;
193
- }
194
- catch {
195
- // Accumulate lines for a multi-line entry (cap at 50 lines)
196
- let accumulated = line;
197
- let found = false;
198
- const limit = Math.min(i + 50, lines.length);
199
- let j = i + 1;
200
- while (j < limit) {
201
- accumulated += '\n' + lines[j];
202
- try {
203
- entries.push(JSON.parse(accumulated));
204
- found = true;
205
- j++;
206
- break;
207
- }
208
- catch {
209
- j++;
210
- }
211
- }
212
- if (!found) {
213
- console.error(`[traces] Unparseable JSONL at line ${i + 1} in ${tracePath}`);
214
- // Only skip the single unparseable line — don't jump to j which
215
- // would skip valid entries that were probed during accumulation.
216
- i++;
217
- }
218
- else {
219
- i = j;
220
- }
221
- }
222
- }
223
- return entries;
224
- }
225
- function extractUserMessages(tracePath) {
226
- const messages = [];
227
- let content;
228
- try {
229
- content = readFileSync(tracePath, 'utf-8');
230
- }
231
- catch (err) {
232
- console.error(`[traces] Failed to read trace file ${tracePath}: ${err}`);
233
- return messages;
234
- }
235
- const entries = parseJsonl(content, tracePath);
236
- for (const entry of entries) {
237
- // Support both old format (entry.message.role === 'user') and new (entry.type === 'user')
238
- const role = entry.message?.role ?? entry?.type;
239
- if (role !== 'user')
240
- continue;
241
- // Support both old format ({message: {content}}) and new ({content} at top level)
242
- const msg = entry.message?.content ?? entry.content;
243
- if (!msg)
244
- continue;
245
- const text = extractHumanText(msg);
246
- if (text) {
247
- messages.push(text);
248
- }
249
- }
250
- return messages;
251
- }
252
- /**
253
- * Find local Entire CLI trace files relevant to the current diff.
254
- *
255
- * Scans .entire/metadata/ for session directories containing full.jsonl,
256
- * then checks if any files modified in those sessions overlap with the
257
- * files changed in the current diff.
258
- *
259
- * Returns paths to condensed files containing only user messages (not the
260
- * raw multi-MB traces). This keeps the intent-drift sub-agent focused on
261
- * user intent without burning its context window on tool outputs.
262
- *
263
- * @returns Array of absolute paths to condensed user-messages files
264
- */
265
- export function findRelevantTraces(gitRoot, baseBranch) {
266
- const metadataDir = join(gitRoot, '.entire', 'metadata');
267
- if (!existsSync(metadataDir)) {
268
- return [];
269
- }
270
- const changedFiles = getChangedFiles(gitRoot, baseBranch);
271
- if (changedFiles.length === 0) {
272
- return [];
273
- }
274
- const triageDir = join(gitRoot, '.haystack', 'triage', 'traces');
275
- mkdirSync(triageDir, { recursive: true });
276
- const relevantTraces = [];
277
- let sessions;
278
- try {
279
- sessions = readdirSync(metadataDir, { withFileTypes: true });
280
- }
281
- catch (err) {
282
- console.error(`[traces] Failed to read metadata directory ${metadataDir}: ${err}`);
283
- return relevantTraces;
284
- }
285
- let traceIndex = 0;
286
- for (const session of sessions) {
287
- if (!session.isDirectory())
288
- continue;
289
- const tracePath = join(metadataDir, session.name, 'full.jsonl');
290
- if (!existsSync(tracePath))
291
- continue;
292
- const traceFiles = extractFilesFromTrace(tracePath);
293
- if (hasOverlap(traceFiles, changedFiles)) {
294
- const userMessages = extractUserMessages(tracePath);
295
- if (userMessages.length === 0)
296
- continue;
297
- // Use a monotonic index prefix to avoid collisions if session names
298
- // are reused across metadata directories.
299
- const condensedPath = join(triageDir, `${traceIndex}-${session.name}.txt`);
300
- traceIndex++;
301
- try {
302
- writeFileSync(condensedPath, userMessages.map((m, i) => `--- User message ${i + 1} ---\n${m}`).join('\n\n'));
303
- relevantTraces.push(condensedPath);
304
- }
305
- catch (err) {
306
- console.error(`[traces] Failed to write condensed trace ${condensedPath}: ${err}`);
307
- }
308
- }
309
- }
310
- return relevantTraces;
311
- }
@@ -1,53 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://docs.haystackeditor.com/cli/schemas/traces.v1.json",
4
- "title": "haystack traces list|get --json",
5
- "type": "object",
6
- "additionalProperties": false,
7
- "required": ["schema_version", "operation", "ref", "head_sha"],
8
- "allOf": [
9
- {
10
- "if": { "properties": { "operation": { "const": "list" } } },
11
- "then": { "required": ["checkpoints"] }
12
- },
13
- {
14
- "if": { "properties": { "operation": { "const": "get" } } },
15
- "then": { "required": ["checkpoint_id", "checkpoint", "chunks", "total_chunks", "total_chars", "next_cursor"] }
16
- }
17
- ],
18
- "$defs": {
19
- "checkpoint": {
20
- "type": "object",
21
- "additionalProperties": false,
22
- "required": ["checkpoint_id", "commit_sha", "commit_message", "timestamp", "summary", "user_prompt", "files_touched", "branch"],
23
- "properties": {
24
- "checkpoint_id": { "type": "string" },
25
- "commit_sha": { "type": "string" },
26
- "commit_message": { "type": "string" },
27
- "timestamp": { "type": ["string", "null"] },
28
- "summary": { "type": "string" },
29
- "user_prompt": { "type": ["string", "null"] },
30
- "files_touched": { "type": "array", "items": { "type": "string" } },
31
- "branch": { "type": ["string", "null"] }
32
- }
33
- }
34
- },
35
- "properties": {
36
- "schema_version": { "type": "string", "const": "1.0.0" },
37
- "operation": { "type": "string", "enum": ["list", "get"] },
38
- "ref": { "type": "string" },
39
- "head_sha": { "type": "string" },
40
- "checkpoints": { "type": "array", "items": { "$ref": "#/$defs/checkpoint" } },
41
- "checkpoint_id": { "type": "string" },
42
- "checkpoint": {
43
- "anyOf": [
44
- { "$ref": "#/$defs/checkpoint" },
45
- { "type": "null" }
46
- ]
47
- },
48
- "chunks": { "type": "array", "items": { "type": "string" } },
49
- "total_chunks": { "type": "integer" },
50
- "total_chars": { "type": "integer" },
51
- "next_cursor": { "type": ["string", "null"] }
52
- }
53
- }