@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
@@ -27,74 +27,23 @@ import chalk from 'chalk';
27
27
  import { withSchema } from '../schema.js';
28
28
  import { findGitRoot } from '../utils/hooks.js';
29
29
  import { resolveAuthContext } from '../utils/auth.js';
30
- import { classifyHttpError, readWithRetries, SERVICE_SILENCE_LIMIT_MS, ServiceSilentError, transientReadFailure, } from '../utils/haystack-api.js';
30
+ import { classifyHttpError, readWithRetries, SERVICE_SILENCE_LIMIT_MS, ServiceSilentError, transientReadFailure, repoApiPath, } from '../utils/haystack-api.js';
31
31
  import { GATEWAY_TIMEOUT_MS, gatewayFetch } from './case-batch.js';
32
32
  import { CRAWL_BRIEF_MAX_FUNCTIONS, CRAWL_BRIEF_MAX_GROUPS, CRAWL_BRIEF_MAX_PRODUCTION_ROWS, CRAWL_BUDGET_MAX_MS, CRAWL_BUDGET_MIN_MS, CRAWL_MAX_FINDINGS, CRAWL_MAX_FINDING_STEPS, CRAWL_MAX_IDEAS, CRAWL_MAX_STAND_IN_LINES, CRAWL_MAX_STAND_IN_ROWS, CRAWL_MAX_TEXT_CHARS, CRAWL_WAIT_MAX_MS, CRAWL_POOLS, } from './crawl-contract.js';
33
33
  import { captureCheckout, crawlUnavailableText, EXPLICIT_WALL_MS, normalModeFailure, postPrecomputeCapture, } from './verify-precompute.js';
34
+ import { SPOT_WORDS, TERMINAL, VERDICT_WORDS, VERDICTS, currentStep, errorWords, headline, plural, reportedStatus, results, verifyReport, } from './crawl-report.js';
34
35
  import { formatOnboarding, onboardingExitCode, readOnboardingStatus, reportOnboardingState, reviewNotes, standInGapNotes, waitForOnboarding, } from './verify-onboarding.js';
35
36
  import { formatCapture, readPreVerifyCapture } from './capture-brief.js';
36
- const CRAWLS_PATH = '/api/agent/cloud-verifier/crawls';
37
37
  /** A held read (waitAfter): the service's hold, then the time any read gets. */
38
38
  const HELD_READ_TIMEOUT_MS = CRAWL_WAIT_MAX_MS + GATEWAY_TIMEOUT_MS;
39
39
  const RUN_ID = /^cv_[0-9a-f]{48}$/;
40
40
  const REPOSITORY_COMPONENT = /^[A-Za-z0-9_.-]{1,100}$/;
41
41
  const STATUSES = ['queued', 'preparing', 'crawling', 'draining', 'completed', 'prepared', 'incomplete', 'cancelled'];
42
- const TERMINAL = new Set(['completed', 'prepared', 'incomplete', 'cancelled']);
43
42
  const STAGES = ['reconcile', 'source', 'worlds', 'blast', 'knobs', 'prepare', 'crawl', 'publish', 'cleanup'];
44
43
  const ERROR_CODES = ['source-mismatch', 'blast-unavailable', 'world-build-failed',
45
44
  'prepare-failed', 'crawl-failed', 'no-capacity', 'stalled', 'lease-lost', 'cancelled'];
46
- const VERDICTS = ['bug', 'cant-tell', 'being-judged', 'not-a-bug'];
47
45
  const SPOT_STAGES = ['resolved', 'loaded', 'exposed', 'activated', 'pressed', 'observed', 'confirmed', 'judged'];
48
46
  const REUSE = ['hit', 'miss', null];
49
- const STATUS_WORDS = {
50
- queued: 'waiting for a machine',
51
- preparing: 'getting ready',
52
- crawling: 'exploring the app',
53
- draining: 'finishing the last checks',
54
- completed: 'finished',
55
- prepared: 'built and ready to check',
56
- incomplete: 'stopped before finishing',
57
- cancelled: 'cancelled',
58
- };
59
- const STAGE_WORDS = {
60
- reconcile: 'cleaning up after an earlier try',
61
- source: 'rebuilding your change',
62
- worlds: 'building and starting the app with and without your change',
63
- blast: 'waiting for the analysis of what your change touches',
64
- knobs: "reading the app's settings",
65
- prepare: 'setting up starting points next to your change',
66
- crawl: 'exploring the app',
67
- publish: 'saving the results',
68
- cleanup: 'shutting down the machines it used',
69
- };
70
- const ERROR_WORDS = {
71
- 'source-mismatch': 'your change could not be rebuilt exactly as it was captured',
72
- 'blast-unavailable': 'the analysis of what your change touches was not available',
73
- 'world-build-failed': 'the app did not build or start, with or without your change',
74
- 'prepare-failed': 'setting up starting points next to your change failed',
75
- 'crawl-failed': 'exploring the app failed',
76
- 'no-capacity': 'there was no room on the machines for it',
77
- stalled: 'it stopped making progress',
78
- 'lease-lost': 'the machine running it stopped responding',
79
- cancelled: 'it was cancelled',
80
- };
81
- const SPOT_WORDS = {
82
- resolved: 'never reached',
83
- loaded: 'loaded but never ran',
84
- exposed: 'shown on screen',
85
- activated: 'ran',
86
- pressed: 'ran when its control was pressed',
87
- observed: 'a difference seen',
88
- confirmed: 'a difference seen and confirmed',
89
- // The stage, not the verdict: a judged difference may be judged not a bug. The findings carry the verdicts.
90
- judged: 'a difference seen, confirmed and judged',
91
- };
92
- const VERDICT_WORDS = {
93
- bug: 'Bug',
94
- 'cant-tell': "Can't tell",
95
- 'being-judged': 'Still being judged',
96
- 'not-a-bug': 'Not a bug',
97
- };
98
47
  /** A response the CLI will not print: malformed, contradictory, or another revision's. */
99
48
  export class CrawlResponseError extends Error {
100
49
  constructor(message) {
@@ -363,16 +312,15 @@ async function responseBody(response) {
363
312
  function read(path, token, waitAfter) {
364
313
  return waitAfter === undefined
365
314
  ? gatewayFetch(path, token)
366
- : gatewayFetch(`${path}&waitAfter=${encodeURIComponent(waitAfter)}`, token, { method: 'GET', timeoutMs: HELD_READ_TIMEOUT_MS });
315
+ : gatewayFetch(`${path}${path.includes('?') ? '&' : '?'}waitAfter=${encodeURIComponent(waitAfter)}`, token, { method: 'GET', timeoutMs: HELD_READ_TIMEOUT_MS });
367
316
  }
368
317
  /** The caller's newest crawl of this capture's revision, or null. Its change
369
318
  * title may differ from the capture's (sameChange). */
370
319
  async function readCrawl(identity, token, waitAfter) {
371
- const path = `${CRAWLS_PATH}?repository=${encodeURIComponent(identity.repository)}`
372
- + `&workCommit=${encodeURIComponent(identity.workCommit)}`;
320
+ const path = repoApiPath(identity.repository, `/checks?commit=${encodeURIComponent(identity.workCommit)}`);
373
321
  const response = await read(path, token, waitAfter);
374
322
  if (!response.ok)
375
- throw await classifyHttpError(response, `Haystack API ${CRAWLS_PATH}`);
323
+ throw await classifyHttpError(response, `Haystack API ${path}`);
376
324
  const body = await responseBody(response);
377
325
  if (!isRecord(body) || !Object.hasOwn(body, 'crawl'))
378
326
  invalid('no crawl field');
@@ -382,9 +330,10 @@ async function readCrawl(identity, token, waitAfter) {
382
330
  async function readCrawlRun(runId, identity, token, waitAfter) {
383
331
  if (!RUN_ID.test(runId))
384
332
  invalid('an acknowledged run id');
385
- const response = await read(`${CRAWLS_PATH}/${runId}?repository=${encodeURIComponent(identity.repository)}`, token, waitAfter);
333
+ const path = repoApiPath(identity.repository, `/checks/${runId}`);
334
+ const response = await read(path, token, waitAfter);
386
335
  if (!response.ok)
387
- throw await classifyHttpError(response, `Haystack API ${CRAWLS_PATH}/${runId}`);
336
+ throw await classifyHttpError(response, `Haystack API ${path}`);
388
337
  const view = parseCrawlView(await responseBody(response), identity);
389
338
  if (view.runId !== runId || !sameChange(view, identity)) {
390
339
  throw new CrawlResponseError(`The crawl service returned another crawl for ${runId}; it is not shown.`);
@@ -397,52 +346,6 @@ function safe(value) {
397
346
  // eslint-disable-next-line no-control-regex -- replacing control characters is the point
398
347
  return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ');
399
348
  }
400
- function plural(count, noun) {
401
- return `${count} ${noun}${count === 1 ? '' : 's'}`;
402
- }
403
- function errorWords(error) {
404
- return error.code === 'stalled'
405
- ? `${ERROR_WORDS.stalled} while ${STAGE_WORDS[error.stage]}`
406
- : ERROR_WORDS[error.code];
407
- }
408
- /** What a running crawl is doing now: its stage, except while it waits for a
409
- * machine or finishes its last checks, which its status says better. */
410
- function currentStep(view) {
411
- return view.status === 'queued' || view.status === 'draining' ? STATUS_WORDS[view.status] : STAGE_WORDS[view.stage];
412
- }
413
- /** The crawl's results: its sealed manifest, else (amendment 9) its answer at the budget, which has the same findings and reach. */
414
- function results(view) {
415
- return view.manifest ?? (view.answer?.status === 'answered' ? view.answer : null);
416
- }
417
- /** The status the command reports: a running crawl that has answered (amendment 9: its budget is up and its work is
418
- * done; only its shutdown remains) reads as completed. */
419
- function reportedStatus(view) {
420
- return !TERMINAL.has(view.status) && view.answer?.status === 'answered' ? 'completed' : view.status;
421
- }
422
- function headline(view) {
423
- const bugs = results(view)?.bugs ?? view.totals.bugs;
424
- const found = bugs > 0 ? ` It found ${plural(bugs, 'bug')} before it stopped.` : '';
425
- switch (reportedStatus(view)) {
426
- case 'completed':
427
- return bugs > 0 ? `Found ${plural(bugs, 'bug')} in your change.` : 'No bugs found in your change.';
428
- case 'incomplete': {
429
- const error = results(view)?.error ?? view.error;
430
- return `The crawl could not finish: ${error ? errorWords(error) : 'no reason was recorded'}.${found}`;
431
- }
432
- case 'prepared':
433
- // Amendment 12: the turn-end hook built and froze this change; `haystack verify` asks for the crawl itself.
434
- return 'Your change is built and ready to check, but was not crawled yet. Run `haystack verify` to check it.';
435
- case 'cancelled':
436
- return `The crawl was cancelled.${found}`;
437
- default: {
438
- const so = bugs > 0 ? ` ${plural(bugs, 'bug')} found so far.` : '';
439
- if (view.cancelRequested) {
440
- return `Being cancelled: ${currentStep(view)}.${so}`;
441
- }
442
- return `Still running: ${currentStep(view)}.${so}`;
443
- }
444
- }
445
- }
446
349
  function spotLines(spot) {
447
350
  const how = spot.furthest === null ? 'never reached' : SPOT_WORDS[spot.furthest];
448
351
  const found = spot.findingIds.length === 0 ? ''
@@ -560,6 +463,7 @@ export function formatCrawl(view) {
560
463
  * 2 ended without finishing (incomplete, cancelled) or finished with cleanup
561
464
  * unproven (an answered crawl has not finished its cleanup, so it is not judged); bugs
562
465
  * found never change it. The command's own failures exit 1 elsewhere. */
466
+ export { verifyReport };
563
467
  export function crawlExitCode(view) {
564
468
  const status = reportedStatus(view);
565
469
  if (!TERMINAL.has(status))
@@ -791,9 +695,9 @@ function checkedLine(change) {
791
695
  }
792
696
  /** Prints the onboarding (and nothing else) as the command's answer: `--json`
793
697
  * carries OnboardingStatusV1. A block exits 3: its ask says what to do. */
794
- function finishWithOnboarding(status, json, change) {
795
- if (json)
796
- process.stdout.write(`${JSON.stringify(withSchema('verify', { change, onboarding: status, crawl: null }), null, 2)}\n`);
698
+ function finishWithOnboarding(status, options, change) {
699
+ if (options.json || options.raw)
700
+ printVerifyJson(options, change, status, null);
797
701
  else
798
702
  console.log(formatOnboarding(status));
799
703
  process.exitCode = onboardingExitCode(status);
@@ -997,7 +901,7 @@ export async function preVerifyCommand(options) {
997
901
  if (options.wait !== false && briefOf(view) === null && !TERMINAL.has(reportedStatus(view)))
998
902
  view = await waitForBrief(view, identity, auth.token);
999
903
  const brief = briefOf(view);
1000
- // CAPTURE-V1 rule 7b: the routes the change touches, with their share of captured sessions; null without capture set up.
904
+ // CAPTURE-V1 rule 7b: the routes the change touches, with their share of captured sessions, per app; null without capture set up.
1001
905
  const captured = await readPreVerifyCapture(findGitRoot(), identity.repository, auth.token, [...new Set([...(brief?.functions.map(fn => fn.file) ?? []), ...change.files])], new Date());
1002
906
  if (options.json)
1003
907
  finish(onboarding, view, captured);
@@ -1014,13 +918,22 @@ export async function preVerifyCommand(options) {
1014
918
  : view.status === 'completed' ? 'It was checked before briefs existed and is not run again: change anything in the checkout and run `haystack pre-verify` again.'
1015
919
  : 'Run `haystack pre-verify` again to prepare it again.'));
1016
920
  if (!options.json && captured !== null)
1017
- console.log(`\n${formatCapture(captured)}`);
921
+ for (const section of captured)
922
+ console.log(`\n${formatCapture(section)}`);
1018
923
  if (brief === null && TERMINAL.has(reportedStatus(view)))
1019
924
  process.exitCode = 2;
1020
925
  }
926
+ /** verify's one JSON document: the report (`--json`) or the service's whole record (`--raw`). */
927
+ function printVerifyJson(options, change, onboarding, view) {
928
+ const document = options.raw
929
+ ? withSchema('verify-raw', { change, onboarding, crawl: view })
930
+ : withSchema('verify', { change, onboarding, crawl: view === null ? null : verifyReport(view) });
931
+ process.stdout.write(`${JSON.stringify(document, null, 2)}\n`);
932
+ }
1021
933
  export async function verifyCommand(options) {
934
+ const json = options.json || options.raw;
1022
935
  // In --json mode stdout carries the one document; every other line goes to stderr.
1023
- const note = (line) => { if (options.json)
936
+ const note = (line) => { if (json)
1024
937
  console.error(line);
1025
938
  else
1026
939
  console.log(line); };
@@ -1030,8 +943,8 @@ export async function verifyCommand(options) {
1030
943
  const change = checkedChange(request.baseCommit, request.workCommit);
1031
944
  if (!request.crawl) {
1032
945
  note(`Nothing to verify: this checkout has no changes against ${request.baseCommit.slice(0, 12)}.`);
1033
- if (options.json)
1034
- process.stdout.write(`${JSON.stringify(withSchema('verify', { change, onboarding: null, crawl: null }), null, 2)}\n`);
946
+ if (json)
947
+ printVerifyJson(options, change, null, null);
1035
948
  return;
1036
949
  }
1037
950
  note(checkedLine(change));
@@ -1065,7 +978,7 @@ export async function verifyCommand(options) {
1065
978
  reportOnboardingState(onboarding);
1066
979
  }
1067
980
  if (onboarding.state !== 'ready') {
1068
- finishWithOnboarding(onboarding, options.json, change);
981
+ finishWithOnboarding(onboarding, options, change);
1069
982
  return;
1070
983
  }
1071
984
  // Ready: the service admitted the waiting crawl when the record published; this
@@ -1075,8 +988,8 @@ export async function verifyCommand(options) {
1075
988
  note(submitted.line);
1076
989
  }
1077
990
  if (submitted.runId === null) {
1078
- if (options.json)
1079
- process.stdout.write(`${JSON.stringify(withSchema('verify', { change, onboarding, crawl: null }), null, 2)}\n`);
991
+ if (json)
992
+ printVerifyJson(options, change, onboarding, null);
1080
993
  process.exitCode = 1;
1081
994
  return;
1082
995
  }
@@ -1089,8 +1002,8 @@ export async function verifyCommand(options) {
1089
1002
  if (options.wait !== false && !TERMINAL.has(reportedStatus(view))) {
1090
1003
  view = await waitForCrawl(view, identity, auth.token);
1091
1004
  }
1092
- if (options.json)
1093
- process.stdout.write(`${JSON.stringify(withSchema('verify', { change, onboarding, crawl: view }), null, 2)}\n`);
1005
+ if (json)
1006
+ printVerifyJson(options, change, onboarding, view);
1094
1007
  else {
1095
1008
  console.log(formatCrawl(view));
1096
1009
  const notes = [...(onboarding?.dataAbsences ?? []), ...(onboarding?.versionChoices ?? []), ...(onboarding?.standIns ?? []),