@haystackeditor/cli 0.25.0 → 0.26.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 (113) hide show
  1. package/README.md +39 -455
  2. package/dist/capture/adapters/django.js +7 -0
  3. package/dist/capture/adapters/index.js +10 -0
  4. package/dist/capture/adapters/next-build.js +113 -0
  5. package/dist/capture/adapters/next.js +272 -105
  6. package/dist/capture/adapters/nuxt.js +7 -0
  7. package/dist/capture/adapters/rails.js +7 -0
  8. package/dist/capture/adapters/react-router.js +7 -0
  9. package/dist/capture/adapters/sveltekit.js +7 -0
  10. package/dist/capture/adapters/vite.js +14 -0
  11. package/dist/capture/app-config.js +27 -2
  12. package/dist/capture/csp.js +7 -6
  13. package/dist/capture/url-rewrites.js +232 -0
  14. package/dist/capture-step.js +3 -0
  15. package/dist/commands/capture-brief.js +41 -32
  16. package/dist/commands/capture-manifest.js +25 -17
  17. package/dist/commands/feedback.js +66 -0
  18. package/dist/commands/init-capture.js +44 -27
  19. package/dist/commands/init-telemetry.js +77 -14
  20. package/dist/commands/init.js +19 -4
  21. package/dist/commands/lockfile-pin.js +307 -0
  22. package/dist/commands/verify-onboarding.js +16 -0
  23. package/dist/commands/verify.js +70 -15
  24. package/dist/index.js +29 -923
  25. package/dist/schema.js +4 -10
  26. package/dist/utils/haystack-api.js +0 -36
  27. package/package.json +1 -5
  28. package/schemas/feedback.v1.json +13 -0
  29. package/schemas/init.v1.json +1 -1
  30. package/schemas/pre-verify.v2.json +240 -0
  31. package/schemas/verify-raw.v1.json +1132 -0
  32. package/schemas/verify.v2.json +655 -0
  33. package/dist/assets/hooks/agent-context/detect.ts +0 -316
  34. package/dist/assets/hooks/agent-context/format.ts +0 -100
  35. package/dist/assets/hooks/agent-context/index.ts +0 -41
  36. package/dist/assets/hooks/agent-context/parsers/claude.ts +0 -262
  37. package/dist/assets/hooks/agent-context/parsers/codex.ts +0 -416
  38. package/dist/assets/hooks/agent-context/parsers/gemini.ts +0 -155
  39. package/dist/assets/hooks/agent-context/parsers/opencode.ts +0 -174
  40. package/dist/assets/hooks/agent-context/tsconfig.json +0 -14
  41. package/dist/assets/hooks/agent-context/types.ts +0 -58
  42. package/dist/assets/hooks/llm-rules-template.md +0 -59
  43. package/dist/assets/hooks/package-lock.json +0 -598
  44. package/dist/assets/hooks/package.json +0 -12
  45. package/dist/assets/hooks/scripts/commit-msg.sh +0 -5
  46. package/dist/assets/hooks/scripts/post-commit.sh +0 -5
  47. package/dist/assets/hooks/scripts/pre-commit.sh +0 -175
  48. package/dist/assets/hooks/scripts/pre-push.sh +0 -25
  49. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +0 -5
  50. package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +0 -528
  51. package/dist/assets/hooks/truncation-checker/index.ts +0 -595
  52. package/dist/assets/hooks/truncation-checker/tsconfig.json +0 -13
  53. package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +0 -2051
  54. package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +0 -4
  55. package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +0 -3411
  56. package/dist/assets/skills/map-your-system.md +0 -143
  57. package/dist/assets/skills/submit.md +0 -200
  58. package/dist/commands/ask.js +0 -20
  59. package/dist/commands/cloud-verifier-behaviors.js +0 -218
  60. package/dist/commands/cloud-verifier-data-store-census.js +0 -539
  61. package/dist/commands/cloud-verifier-data-store-drift.js +0 -158
  62. package/dist/commands/cloud-verifier-identity-census.js +0 -4060
  63. package/dist/commands/cloud-verifier-materialization.js +0 -704
  64. package/dist/commands/cloud-verifier-pascal-selector-census.js +0 -1382
  65. package/dist/commands/cloud-verifier-python-manifest-selector-census.js +0 -2015
  66. package/dist/commands/cloud-verifier-specialized-operational-census.js +0 -11432
  67. package/dist/commands/cloud-verifier-universe.js +0 -10178
  68. package/dist/commands/config.js +0 -549
  69. package/dist/commands/design-verify.js +0 -311
  70. package/dist/commands/dismiss.js +0 -159
  71. package/dist/commands/hooks.js +0 -226
  72. package/dist/commands/inbox.js +0 -137
  73. package/dist/commands/mcp.js +0 -201
  74. package/dist/commands/policy.js +0 -371
  75. package/dist/commands/pr-status.js +0 -207
  76. package/dist/commands/pr.js +0 -105
  77. package/dist/commands/prepare-universe-review.js +0 -1092
  78. package/dist/commands/production-source-deny-policy.js +0 -100
  79. package/dist/commands/request-review.js +0 -74
  80. package/dist/commands/review.js +0 -191
  81. package/dist/commands/rules.js +0 -98
  82. package/dist/commands/scaffold-provisional-universe.js +0 -806
  83. package/dist/commands/setup.js +0 -1170
  84. package/dist/commands/skills.js +0 -447
  85. package/dist/commands/submit.js +0 -745
  86. package/dist/commands/system-map.js +0 -228
  87. package/dist/commands/triage.js +0 -598
  88. package/dist/commands/webhooks.js +0 -241
  89. package/dist/states.js +0 -46
  90. package/dist/tools/detect.js +0 -832
  91. package/dist/triage/astra.js +0 -202
  92. package/dist/triage/prompts.js +0 -188
  93. package/dist/triage/runner.js +0 -200
  94. package/dist/triage/types.js +0 -7
  95. package/dist/utils/action-output.js +0 -26
  96. package/dist/utils/analysis-api.js +0 -416
  97. package/dist/utils/design-verifier-api.js +0 -294
  98. package/dist/utils/design-verifier-history.js +0 -79
  99. package/dist/utils/design-verifier-result.js +0 -424
  100. package/dist/utils/github-api.js +0 -324
  101. package/dist/utils/pending-state.js +0 -86
  102. package/dist/utils/pr-ref.js +0 -56
  103. package/dist/utils/prompter.js +0 -328
  104. package/schemas/action.v1.json +0 -22
  105. package/schemas/ask.v1.json +0 -40
  106. package/schemas/inbox.v1.json +0 -27
  107. package/schemas/pr-status.v1.json +0 -61
  108. package/schemas/pr.v1.json +0 -97
  109. package/schemas/pr.v3.json +0 -45
  110. package/schemas/setup.v1.json +0 -75
  111. package/schemas/submit.v1.json +0 -90
  112. package/schemas/triage.v1.json +0 -103
  113. 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
- }