@haystackeditor/cli 0.25.1 → 0.27.0

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 (109) hide show
  1. package/README.md +39 -463
  2. package/dist/capture/app-config.js +30 -6
  3. package/dist/commands/capture-brief.js +45 -35
  4. package/dist/commands/capture-contract.js +4 -4
  5. package/dist/commands/crawl-report.js +196 -0
  6. package/dist/commands/db-profile-upload.js +3 -3
  7. package/dist/commands/feedback.js +66 -0
  8. package/dist/commands/init-telemetry.js +55 -9
  9. package/dist/commands/init.js +8 -3
  10. package/dist/commands/lockfile-pin.js +307 -0
  11. package/dist/commands/telemetry-token.js +3 -3
  12. package/dist/commands/tokens.js +4 -4
  13. package/dist/commands/verify-explore.js +3 -3
  14. package/dist/commands/verify-history.js +2 -2
  15. package/dist/commands/verify-onboarding.js +13 -16
  16. package/dist/commands/verify-precompute.js +3 -4
  17. package/dist/commands/verify.js +31 -118
  18. package/dist/index.js +55 -968
  19. package/dist/schema.js +4 -10
  20. package/dist/utils/haystack-api.js +8 -36
  21. package/package.json +1 -5
  22. package/schemas/feedback.v1.json +13 -0
  23. package/schemas/pre-verify.v2.json +240 -0
  24. package/schemas/verify-raw.v1.json +1132 -0
  25. package/schemas/verify.v2.json +655 -0
  26. package/dist/assets/hooks/agent-context/detect.ts +0 -316
  27. package/dist/assets/hooks/agent-context/format.ts +0 -100
  28. package/dist/assets/hooks/agent-context/index.ts +0 -41
  29. package/dist/assets/hooks/agent-context/parsers/claude.ts +0 -262
  30. package/dist/assets/hooks/agent-context/parsers/codex.ts +0 -416
  31. package/dist/assets/hooks/agent-context/parsers/gemini.ts +0 -155
  32. package/dist/assets/hooks/agent-context/parsers/opencode.ts +0 -174
  33. package/dist/assets/hooks/agent-context/tsconfig.json +0 -14
  34. package/dist/assets/hooks/agent-context/types.ts +0 -58
  35. package/dist/assets/hooks/llm-rules-template.md +0 -59
  36. package/dist/assets/hooks/package-lock.json +0 -598
  37. package/dist/assets/hooks/package.json +0 -12
  38. package/dist/assets/hooks/scripts/commit-msg.sh +0 -5
  39. package/dist/assets/hooks/scripts/post-commit.sh +0 -5
  40. package/dist/assets/hooks/scripts/pre-commit.sh +0 -175
  41. package/dist/assets/hooks/scripts/pre-push.sh +0 -25
  42. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +0 -5
  43. package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +0 -528
  44. package/dist/assets/hooks/truncation-checker/index.ts +0 -595
  45. package/dist/assets/hooks/truncation-checker/tsconfig.json +0 -13
  46. package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +0 -2051
  47. package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +0 -4
  48. package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +0 -3411
  49. package/dist/assets/skills/map-your-system.md +0 -143
  50. package/dist/assets/skills/submit.md +0 -200
  51. package/dist/commands/ask.js +0 -20
  52. package/dist/commands/cloud-verifier-behaviors.js +0 -218
  53. package/dist/commands/cloud-verifier-data-store-census.js +0 -539
  54. package/dist/commands/cloud-verifier-data-store-drift.js +0 -158
  55. package/dist/commands/cloud-verifier-identity-census.js +0 -4060
  56. package/dist/commands/cloud-verifier-materialization.js +0 -704
  57. package/dist/commands/cloud-verifier-pascal-selector-census.js +0 -1382
  58. package/dist/commands/cloud-verifier-python-manifest-selector-census.js +0 -2015
  59. package/dist/commands/cloud-verifier-specialized-operational-census.js +0 -11432
  60. package/dist/commands/cloud-verifier-universe.js +0 -10178
  61. package/dist/commands/config.js +0 -549
  62. package/dist/commands/design-verify.js +0 -311
  63. package/dist/commands/dismiss.js +0 -159
  64. package/dist/commands/hooks.js +0 -226
  65. package/dist/commands/inbox.js +0 -137
  66. package/dist/commands/mcp.js +0 -201
  67. package/dist/commands/policy.js +0 -371
  68. package/dist/commands/pr-status.js +0 -207
  69. package/dist/commands/pr.js +0 -105
  70. package/dist/commands/prepare-universe-review.js +0 -1092
  71. package/dist/commands/production-source-deny-policy.js +0 -100
  72. package/dist/commands/request-review.js +0 -74
  73. package/dist/commands/review.js +0 -191
  74. package/dist/commands/rules.js +0 -98
  75. package/dist/commands/scaffold-provisional-universe.js +0 -806
  76. package/dist/commands/setup.js +0 -1170
  77. package/dist/commands/skills.js +0 -447
  78. package/dist/commands/status.js +0 -35
  79. package/dist/commands/submit.js +0 -745
  80. package/dist/commands/system-map.js +0 -228
  81. package/dist/commands/triage.js +0 -598
  82. package/dist/commands/webhooks.js +0 -241
  83. package/dist/states.js +0 -46
  84. package/dist/tools/detect.js +0 -832
  85. package/dist/triage/astra.js +0 -202
  86. package/dist/triage/prompts.js +0 -188
  87. package/dist/triage/runner.js +0 -200
  88. package/dist/triage/types.js +0 -7
  89. package/dist/types.js +0 -326
  90. package/dist/utils/action-output.js +0 -26
  91. package/dist/utils/analysis-api.js +0 -416
  92. package/dist/utils/config.js +0 -54
  93. package/dist/utils/design-verifier-api.js +0 -294
  94. package/dist/utils/design-verifier-history.js +0 -79
  95. package/dist/utils/design-verifier-result.js +0 -424
  96. package/dist/utils/github-api.js +0 -324
  97. package/dist/utils/pending-state.js +0 -86
  98. package/dist/utils/pr-ref.js +0 -56
  99. package/dist/utils/prompter.js +0 -328
  100. package/schemas/action.v1.json +0 -22
  101. package/schemas/ask.v1.json +0 -40
  102. package/schemas/inbox.v1.json +0 -27
  103. package/schemas/pr-status.v1.json +0 -61
  104. package/schemas/pr.v1.json +0 -97
  105. package/schemas/pr.v3.json +0 -45
  106. package/schemas/setup.v1.json +0 -75
  107. package/schemas/submit.v1.json +0 -90
  108. package/schemas/triage.v1.json +0 -103
  109. package/schemas/triage.v2.json +0 -64
@@ -1,528 +0,0 @@
1
- /**
2
- * Tree-sitter based truncation detection.
3
- *
4
- * Detects semantic truncation patterns that regex can't catch:
5
- * - Truncation in function returns
6
- * - LLM context variable truncation
7
- * - Loop-based truncation
8
- * - Chained truncations
9
- */
10
-
11
- import Parser from "tree-sitter";
12
- import TypeScript from "tree-sitter-typescript";
13
- import { readFileSync, existsSync } from "fs";
14
- import { extname } from "path";
15
-
16
- // Initialize parsers
17
- const tsxParser = new Parser();
18
- tsxParser.setLanguage(TypeScript.tsx as unknown as Parser.Language);
19
-
20
- const tsParser = new Parser();
21
- tsParser.setLanguage(TypeScript.typescript as unknown as Parser.Language);
22
-
23
- // ============================================================================
24
- // TYPES
25
- // ============================================================================
26
-
27
- export interface ASTViolation {
28
- file: string;
29
- line: number;
30
- column: number;
31
- type: string;
32
- description: string;
33
- code: string;
34
- severity: "error" | "warning";
35
- }
36
-
37
- // Variables that likely contain LLM context
38
- const LLM_CONTEXT_VARS = new Set([
39
- "context",
40
- "prompt",
41
- "messages",
42
- "output",
43
- "result",
44
- "response",
45
- "content",
46
- "text",
47
- "data",
48
- "input",
49
- "toolOutput",
50
- "toolResult",
51
- "searchResults",
52
- "fileContent",
53
- "fileContents",
54
- "codeContent",
55
- "sourceCode",
56
- ]);
57
-
58
- // Truncation method names
59
- const TRUNCATION_METHODS = new Set([
60
- "slice",
61
- "substring",
62
- "substr",
63
- "splice",
64
- "truncate",
65
- "take",
66
- "head",
67
- "limit",
68
- ]);
69
-
70
- // ============================================================================
71
- // HELPERS
72
- // ============================================================================
73
-
74
- function getParser(filePath: string): Parser | null {
75
- const ext = extname(filePath);
76
- if (ext === ".tsx" || ext === ".jsx") return tsxParser;
77
- if (ext === ".ts" || ext === ".js") return tsParser;
78
- return null;
79
- }
80
-
81
- function nodeText(node: Parser.SyntaxNode, source: string): string {
82
- return source.slice(node.startIndex, node.endIndex);
83
- }
84
-
85
- function findNodes(
86
- node: Parser.SyntaxNode,
87
- types: string[],
88
- results: Parser.SyntaxNode[] = []
89
- ): Parser.SyntaxNode[] {
90
- if (types.includes(node.type)) {
91
- results.push(node);
92
- }
93
- for (const child of node.children) {
94
- findNodes(child, types, results);
95
- }
96
- return results;
97
- }
98
-
99
- function findParent(
100
- node: Parser.SyntaxNode,
101
- types: string[]
102
- ): Parser.SyntaxNode | null {
103
- let current = node.parent;
104
- while (current) {
105
- if (types.includes(current.type)) return current;
106
- current = current.parent;
107
- }
108
- return null;
109
- }
110
-
111
- function getLineContent(source: string, line: number): string {
112
- const lines = source.split("\n");
113
- return lines[line - 1] || "";
114
- }
115
-
116
- // ============================================================================
117
- // DETECTION: Return statement truncation
118
- // ============================================================================
119
-
120
- function detectReturnTruncation(
121
- tree: Parser.Tree,
122
- source: string,
123
- filePath: string
124
- ): ASTViolation[] {
125
- const violations: ASTViolation[] = [];
126
-
127
- // Find all return statements
128
- const returnStatements = findNodes(tree.rootNode, ["return_statement"]);
129
-
130
- for (const ret of returnStatements) {
131
- const retText = nodeText(ret, source);
132
-
133
- // Check if return contains truncation
134
- const callExpressions = findNodes(ret, ["call_expression"]);
135
- for (const call of callExpressions) {
136
- const funcNode = call.childForFieldName("function");
137
- if (!funcNode) continue;
138
-
139
- // Check for method calls like .slice(), .substring()
140
- if (funcNode.type === "member_expression") {
141
- const property = funcNode.childForFieldName("property");
142
- if (property && TRUNCATION_METHODS.has(nodeText(property, source))) {
143
- // Check if it has numeric arguments (indicates truncation)
144
- const args = call.childForFieldName("arguments");
145
- if (args) {
146
- const hasNumericArg = findNodes(args, ["number"]).length > 0;
147
- const hasLimitVar = /max|limit|MAX|LIMIT/i.test(nodeText(args, source));
148
-
149
- if (hasNumericArg || hasLimitVar) {
150
- violations.push({
151
- file: filePath,
152
- line: ret.startPosition.row + 1,
153
- column: ret.startPosition.column + 1,
154
- type: "return-truncation",
155
- description: `Function returns truncated data via .${nodeText(property, source)}()`,
156
- code: retText.slice(0, 100),
157
- severity: "error",
158
- });
159
- }
160
- }
161
- }
162
- }
163
- }
164
- }
165
-
166
- return violations;
167
- }
168
-
169
- // ============================================================================
170
- // DETECTION: LLM context variable truncation
171
- // ============================================================================
172
-
173
- function detectContextVarTruncation(
174
- tree: Parser.Tree,
175
- source: string,
176
- filePath: string
177
- ): ASTViolation[] {
178
- const violations: ASTViolation[] = [];
179
-
180
- // Find all call expressions
181
- const callExpressions = findNodes(tree.rootNode, ["call_expression"]);
182
-
183
- for (const call of callExpressions) {
184
- const funcNode = call.childForFieldName("function");
185
- if (!funcNode || funcNode.type !== "member_expression") continue;
186
-
187
- const object = funcNode.childForFieldName("object");
188
- const property = funcNode.childForFieldName("property");
189
- if (!object || !property) continue;
190
-
191
- const methodName = nodeText(property, source);
192
- if (!TRUNCATION_METHODS.has(methodName)) continue;
193
-
194
- // Check if the object is an LLM context variable
195
- const objectName = nodeText(object, source);
196
-
197
- // Direct variable: context.slice()
198
- if (LLM_CONTEXT_VARS.has(objectName)) {
199
- violations.push({
200
- file: filePath,
201
- line: call.startPosition.row + 1,
202
- column: call.startPosition.column + 1,
203
- type: "context-var-truncation",
204
- description: `LLM context variable '${objectName}' is truncated via .${methodName}()`,
205
- code: nodeText(call, source).slice(0, 100),
206
- severity: "error",
207
- });
208
- continue;
209
- }
210
-
211
- // Property access: result.content.slice()
212
- if (object.type === "member_expression") {
213
- const innerProp = object.childForFieldName("property");
214
- if (innerProp && LLM_CONTEXT_VARS.has(nodeText(innerProp, source))) {
215
- violations.push({
216
- file: filePath,
217
- line: call.startPosition.row + 1,
218
- column: call.startPosition.column + 1,
219
- type: "context-var-truncation",
220
- description: `LLM context property '${nodeText(innerProp, source)}' is truncated via .${methodName}()`,
221
- code: nodeText(call, source).slice(0, 100),
222
- severity: "error",
223
- });
224
- }
225
- }
226
- }
227
-
228
- return violations;
229
- }
230
-
231
- // ============================================================================
232
- // DETECTION: Loop-based truncation (Math.min pattern)
233
- // ============================================================================
234
-
235
- function detectLoopTruncation(
236
- tree: Parser.Tree,
237
- source: string,
238
- filePath: string
239
- ): ASTViolation[] {
240
- const violations: ASTViolation[] = [];
241
-
242
- // Find for loops
243
- const forLoops = findNodes(tree.rootNode, ["for_statement"]);
244
-
245
- for (const loop of forLoops) {
246
- const loopText = nodeText(loop, source);
247
-
248
- // Check for Math.min pattern in condition
249
- if (/Math\.min\s*\([^)]*\.length/.test(loopText)) {
250
- violations.push({
251
- file: filePath,
252
- line: loop.startPosition.row + 1,
253
- column: loop.startPosition.column + 1,
254
- type: "loop-truncation",
255
- description: "Loop uses Math.min() to limit iterations (may truncate data)",
256
- code: loopText.split("\n")[0].slice(0, 100),
257
- severity: "warning",
258
- });
259
- }
260
-
261
- // Check for hardcoded limit comparison: i < 10
262
- const condition = loop.children.find((c) => c.type === "binary_expression");
263
- if (condition) {
264
- const condText = nodeText(condition, source);
265
- // Pattern: i < NUMBER where NUMBER is small
266
- const limitMatch = condText.match(/[a-z_]\w*\s*<\s*(\d+)/i);
267
- if (limitMatch && parseInt(limitMatch[1]) <= 100) {
268
- // Check if it's iterating over an array
269
- if (/\.length|\.size/.test(loopText)) {
270
- violations.push({
271
- file: filePath,
272
- line: loop.startPosition.row + 1,
273
- column: loop.startPosition.column + 1,
274
- type: "loop-truncation",
275
- description: `Loop hardcodes limit of ${limitMatch[1]} iterations`,
276
- code: loopText.split("\n")[0].slice(0, 100),
277
- severity: "warning",
278
- });
279
- }
280
- }
281
- }
282
- }
283
-
284
- return violations;
285
- }
286
-
287
- // ============================================================================
288
- // DETECTION: Filter/map that drops items
289
- // ============================================================================
290
-
291
- function detectFilterTruncation(
292
- tree: Parser.Tree,
293
- source: string,
294
- filePath: string
295
- ): ASTViolation[] {
296
- const violations: ASTViolation[] = [];
297
-
298
- const callExpressions = findNodes(tree.rootNode, ["call_expression"]);
299
-
300
- for (const call of callExpressions) {
301
- const funcNode = call.childForFieldName("function");
302
- if (!funcNode || funcNode.type !== "member_expression") continue;
303
-
304
- const property = funcNode.childForFieldName("property");
305
- if (!property) continue;
306
-
307
- const methodName = nodeText(property, source);
308
-
309
- // Check .filter((_, i) => i < N) pattern
310
- if (methodName === "filter") {
311
- const args = call.childForFieldName("arguments");
312
- if (args) {
313
- const argsText = nodeText(args, source);
314
- // Pattern: filter with index comparison
315
- if (/\(\s*_?\s*,\s*\w+\s*\)\s*=>\s*\w+\s*<\s*\d+/.test(argsText)) {
316
- violations.push({
317
- file: filePath,
318
- line: call.startPosition.row + 1,
319
- column: call.startPosition.column + 1,
320
- type: "filter-truncation",
321
- description: ".filter() uses index to limit results (truncates data)",
322
- code: nodeText(call, source).slice(0, 100),
323
- severity: "error",
324
- });
325
- }
326
- }
327
- }
328
-
329
- // Check for chained .slice() after .map()/.filter()
330
- if (methodName === "slice") {
331
- const object = funcNode.childForFieldName("object");
332
- if (object && object.type === "call_expression") {
333
- const innerFunc = object.childForFieldName("function");
334
- if (innerFunc && innerFunc.type === "member_expression") {
335
- const innerMethod = innerFunc.childForFieldName("property");
336
- if (innerMethod) {
337
- const innerMethodName = nodeText(innerMethod, source);
338
- if (["map", "filter", "flatMap", "reduce"].includes(innerMethodName)) {
339
- violations.push({
340
- file: filePath,
341
- line: call.startPosition.row + 1,
342
- column: call.startPosition.column + 1,
343
- type: "chained-truncation",
344
- description: `.${innerMethodName}() results are truncated with .slice()`,
345
- code: nodeText(call, source).slice(0, 100),
346
- severity: "error",
347
- });
348
- }
349
- }
350
- }
351
- }
352
- }
353
- }
354
-
355
- return violations;
356
- }
357
-
358
- // ============================================================================
359
- // DETECTION: Conditional early return with truncation
360
- // ============================================================================
361
-
362
- function detectConditionalTruncation(
363
- tree: Parser.Tree,
364
- source: string,
365
- filePath: string
366
- ): ASTViolation[] {
367
- const violations: ASTViolation[] = [];
368
-
369
- // Find if statements
370
- const ifStatements = findNodes(tree.rootNode, ["if_statement"]);
371
-
372
- for (const ifStmt of ifStatements) {
373
- const condition = ifStmt.childForFieldName("condition");
374
- const consequence = ifStmt.childForFieldName("consequence");
375
-
376
- if (!condition || !consequence) continue;
377
-
378
- const condText = nodeText(condition, source);
379
-
380
- // Check for length comparison in condition
381
- if (/\.length\s*>\s*\d+/.test(condText)) {
382
- // Check if consequence contains return with slice
383
- const returns = findNodes(consequence, ["return_statement"]);
384
- for (const ret of returns) {
385
- const retText = nodeText(ret, source);
386
- if (/\.slice\s*\(/.test(retText)) {
387
- violations.push({
388
- file: filePath,
389
- line: ifStmt.startPosition.row + 1,
390
- column: ifStmt.startPosition.column + 1,
391
- type: "conditional-return-truncation",
392
- description: "Conditional early return truncates data when length exceeds limit",
393
- code: `if ${condText} { ${retText.slice(0, 50)}... }`,
394
- severity: "error",
395
- });
396
- }
397
- }
398
- }
399
- }
400
-
401
- return violations;
402
- }
403
-
404
- // ============================================================================
405
- // DETECTION: Assignment truncation (result = x.slice())
406
- // ============================================================================
407
-
408
- function detectAssignmentTruncation(
409
- tree: Parser.Tree,
410
- source: string,
411
- filePath: string
412
- ): ASTViolation[] {
413
- const violations: ASTViolation[] = [];
414
-
415
- // Find assignments and variable declarations
416
- const assignments = [
417
- ...findNodes(tree.rootNode, ["assignment_expression"]),
418
- ...findNodes(tree.rootNode, ["variable_declarator"]),
419
- ];
420
-
421
- for (const assign of assignments) {
422
- // Get the left side (variable name)
423
- const left =
424
- assign.childForFieldName("left") || assign.childForFieldName("name");
425
- if (!left) continue;
426
-
427
- const varName = nodeText(left, source);
428
-
429
- // Check if it's an LLM context variable being assigned
430
- if (!LLM_CONTEXT_VARS.has(varName)) continue;
431
-
432
- // Get the right side (value)
433
- const right =
434
- assign.childForFieldName("right") || assign.childForFieldName("value");
435
- if (!right) continue;
436
-
437
- // Check if right side contains truncation
438
- const calls = findNodes(right, ["call_expression"]);
439
- for (const call of calls) {
440
- const funcNode = call.childForFieldName("function");
441
- if (funcNode && funcNode.type === "member_expression") {
442
- const property = funcNode.childForFieldName("property");
443
- if (property && TRUNCATION_METHODS.has(nodeText(property, source))) {
444
- violations.push({
445
- file: filePath,
446
- line: assign.startPosition.row + 1,
447
- column: assign.startPosition.column + 1,
448
- type: "assignment-truncation",
449
- description: `LLM context variable '${varName}' assigned truncated value`,
450
- code: nodeText(assign, source).slice(0, 100),
451
- severity: "error",
452
- });
453
- }
454
- }
455
- }
456
- }
457
-
458
- return violations;
459
- }
460
-
461
- // ============================================================================
462
- // MAIN ANALYZER
463
- // ============================================================================
464
-
465
- export function analyzeFile(filePath: string): ASTViolation[] {
466
- if (!existsSync(filePath)) return [];
467
-
468
- const parser = getParser(filePath);
469
- if (!parser) return [];
470
-
471
- const source = readFileSync(filePath, "utf-8");
472
- const tree = parser.parse(source);
473
-
474
- const violations: ASTViolation[] = [
475
- ...detectReturnTruncation(tree, source, filePath),
476
- ...detectContextVarTruncation(tree, source, filePath),
477
- ...detectLoopTruncation(tree, source, filePath),
478
- ...detectFilterTruncation(tree, source, filePath),
479
- ...detectConditionalTruncation(tree, source, filePath),
480
- ...detectAssignmentTruncation(tree, source, filePath),
481
- ];
482
-
483
- return violations;
484
- }
485
-
486
- export function analyzeFiles(filePaths: string[]): ASTViolation[] {
487
- const allViolations: ASTViolation[] = [];
488
-
489
- for (const filePath of filePaths) {
490
- const violations = analyzeFile(filePath);
491
- allViolations.push(...violations);
492
- }
493
-
494
- return allViolations;
495
- }
496
-
497
- export function formatASTViolations(violations: ASTViolation[]): string {
498
- if (violations.length === 0) return "";
499
-
500
- const lines: string[] = [];
501
- lines.push("");
502
- lines.push("========================================");
503
- lines.push(" AST-BASED TRUNCATION VIOLATIONS");
504
- lines.push("========================================");
505
- lines.push("");
506
-
507
- // Group by file
508
- const byFile = new Map<string, ASTViolation[]>();
509
- for (const v of violations) {
510
- const existing = byFile.get(v.file) || [];
511
- existing.push(v);
512
- byFile.set(v.file, existing);
513
- }
514
-
515
- for (const [file, fileViolations] of byFile) {
516
- lines.push(`📄 ${file}`);
517
- for (const v of fileViolations) {
518
- const severity = v.severity === "error" ? "❌" : "⚠️";
519
- lines.push(` ${severity} Line ${v.line}: ${v.type}`);
520
- lines.push(` ${v.description}`);
521
- lines.push(` Code: ${v.code}`);
522
- lines.push("");
523
- }
524
- }
525
-
526
- lines.push("========================================");
527
- return lines.join("\n");
528
- }