@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
package/dist/schema.js CHANGED
@@ -10,22 +10,16 @@
10
10
  * uses the same envelope as the `--json` output, byte-for-byte.
11
11
  */
12
12
  export const SCHEMA_VERSIONS = {
13
- triage: '2.0.0',
14
- setup: '1.0.0',
15
- pr: '3.0.0',
16
- 'pr-status': '1.0.0',
17
- inbox: '1.0.0',
18
- ask: '1.0.0',
19
- submit: '1.0.1',
20
- action: '1.0.0',
21
13
  'cloud-verifier': '1.0.0',
22
14
  'case-batch': '1.0.1',
23
- verify: '1.0.4',
24
- 'pre-verify': '1.0.2',
15
+ verify: '2.0.0',
16
+ 'verify-raw': '1.0.0',
17
+ 'pre-verify': '2.0.0',
25
18
  'verify-answer': '1.0.1',
26
19
  'verify-onboarding': '1.0.0',
27
20
  init: '1.0.2',
28
21
  login: '1.0.0',
22
+ feedback: '1.0.0',
29
23
  error: '1.0.0',
30
24
  };
31
25
  /** Wrap a payload with the `schema_version` envelope. */
@@ -1,6 +1,14 @@
1
1
  export function haystackApiBase() {
2
2
  return process.env.HAYSTACK_API_BASE ?? 'https://haystackeditor.com';
3
3
  }
4
+ /** A public API path for `repository` (owner/repo): /v1/repos/{owner}/{repo}{rest}. */
5
+ export function repoApiPath(repository, rest) {
6
+ const parts = repository.split('/');
7
+ if (parts.length !== 2 || parts.some(part => !/^[A-Za-z0-9_.-]{1,100}$/.test(part))) {
8
+ throw new Error(`Invalid repository ${repository}: it must be owner/repo.`);
9
+ }
10
+ return `/v1/repos/${parts[0]}/${parts[1]}${rest}`;
11
+ }
4
12
  export function haystackApiUrl(path) {
5
13
  if (!path.startsWith('/'))
6
14
  throw new Error('Haystack API path must be absolute');
@@ -202,39 +210,3 @@ export async function haystackFetch(path, token, init = {}, acceptedStatuses = [
202
210
  }
203
211
  return response;
204
212
  }
205
- export async function fetchCliIdentity(token) {
206
- return haystackJson('/api/cli/me', token);
207
- }
208
- export async function fetchHaystackInstallations(token) {
209
- const data = await haystackJson('/api/github/user-installations', token);
210
- return data.installations ?? [];
211
- }
212
- export async function fetchGitHubJson(path, token, init = {}) {
213
- return haystackJson(`/api/github${path}`, token, init);
214
- }
215
- export async function fetchAnalysisArtifact(owner, repo, prNumber, file, token) {
216
- const ref = encodeURIComponent(`${owner}/${repo}#${prNumber}`);
217
- const response = await fetch(haystackApiUrl(`/api/analysis/${ref}/${file}`), {
218
- headers: headers(token, { 'Cache-Control': 'no-cache' }),
219
- });
220
- if (response.status === 404)
221
- return null;
222
- if (!response.ok) {
223
- throw await classifyHttpError(response, `Analysis artifact ${file}`);
224
- }
225
- const data = await response.json();
226
- if (!data || typeof data !== 'object' || !('download_url' in data) || !data.download_url) {
227
- return data;
228
- }
229
- const download = await fetch(data.download_url, { headers: { 'User-Agent': 'Haystack-CLI' } });
230
- if (!download.ok)
231
- throw new Error(`Artifact download failed: HTTP ${download.status}`);
232
- return download.json();
233
- }
234
- export async function postAskHaystack(owner, repo, prNumber, question, sessionId, token) {
235
- return haystackJson(`/api/review-chat/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${prNumber}/ask`, token, {
236
- method: 'POST',
237
- headers: { 'Content-Type': 'application/json' },
238
- body: JSON.stringify({ question, session_id: sessionId }),
239
- });
240
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.25.1",
3
+ "version": "0.27.0",
4
4
  "description": "haystack verify: run your app with and without a change, and see what the change broke",
5
5
  "type": "module",
6
6
  "bin": {
@@ -48,9 +48,7 @@
48
48
  "url": "https://github.com/haystackeditor/haystack-review/issues"
49
49
  },
50
50
  "dependencies": {
51
- "@aws-sdk/client-ssm": "3.987.0",
52
51
  "@babel/core": "8.0.6",
53
- "@inquirer/prompts": "8.7.2",
54
52
  "@modelcontextprotocol/sdk": "1.26.0",
55
53
  "chalk": "6.0.0",
56
54
  "commander": "15.0.0",
@@ -58,7 +56,6 @@
58
56
  "parse5-sax-parser": "8.0.0",
59
57
  "pg": "8.23.0",
60
58
  "pg-connection-string": "2.14.0",
61
- "ws": "8.21.3",
62
59
  "yaml": "2.9.1",
63
60
  "zod": "4.6.5"
64
61
  },
@@ -66,7 +63,6 @@
66
63
  "@eslint/js": "8.57.1",
67
64
  "@types/node": "22.19.8",
68
65
  "@types/pg": "8.23.1",
69
- "@types/ws": "8.18.1",
70
66
  "@typescript-eslint/eslint-plugin": "7.18.0",
71
67
  "@typescript-eslint/parser": "7.18.0",
72
68
  "eslint": "8.57.1",
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://docs.haystackeditor.com/cli/schemas/feedback.v1.json",
4
+ "title": "Haystack feedback: a message sent to the Haystack team",
5
+ "description": "Machine-readable output of `haystack feedback --json`. status is sent once the team's channel accepted the message; a failure is the error envelope (`haystack schema error`).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema_version", "status"],
9
+ "properties": {
10
+ "schema_version": { "const": "1.0.0" },
11
+ "status": { "const": "sent" }
12
+ }
13
+ }
@@ -0,0 +1,240 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://docs.haystackeditor.com/cli/schemas/pre-verify.v2.json",
4
+ "title": "haystack pre-verify --json",
5
+ "description": "CRAWL-V1 amendment 18: the brief of the current change before any crawl: the functions it touches (the front half's blast radius, nearest first) and, when incident planning ran, Haystack's own ideas of what could break. `run` is the run of this capture that holds or will hold the brief; `brief` is null until it does (or when the run ended without one).",
6
+ "type": "object",
7
+ "required": ["schema_version", "change", "onboarding", "run", "brief", "capture"],
8
+ "properties": {
9
+ "schema_version": { "const": "2.0.0" },
10
+ "change": {
11
+ "type": "object",
12
+ "required": ["baseCommit", "workCommit", "files"],
13
+ "properties": {
14
+ "baseCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
15
+ "workCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
16
+ "files": { "type": "array", "items": { "type": "string" } }
17
+ }
18
+ },
19
+ "onboarding": { "description": "The app's onboarding for this base when this command met it (see `haystack schema verify-onboarding`), else null.", "oneOf": [{ "type": "null" }, { "type": "object" }] },
20
+ "run": {
21
+ "oneOf": [
22
+ { "type": "null" },
23
+ {
24
+ "type": "object",
25
+ "required": ["runId", "status"],
26
+ "properties": {
27
+ "runId": { "type": "string", "pattern": "^cv_[0-9a-f]{48}$" },
28
+ "status": { "enum": ["queued", "preparing", "crawling", "draining", "completed", "prepared", "incomplete", "cancelled"] }
29
+ }
30
+ }
31
+ ]
32
+ },
33
+ "brief": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/brief" }] },
34
+ "capture": {
35
+ "description": "CAPTURE-V1 rule 7b (2.0.0): what consenting real users do on the routes this change touches, from Haystack's capture tag, one section per app whose .haystack/capture.json records it (`haystack init` writes it in the app's directory, such as apps/web in a monorepo; `app` is that directory, '.' for the root); null when the checkout has none (capture not set up). The change's files (the brief's function files and the changed files) are joined to routes through each app's route manifest source entries; shares are of distinct captured sessions over the window; sessions on pages no route names are `unmapped`, never counted as zero. A read that fails is `unavailable` and never fails the command.",
36
+ "oneOf": [
37
+ { "type": "null" },
38
+ { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/captureSection" } }
39
+ ]
40
+ }
41
+ },
42
+ "$defs": {
43
+ "captureSection": {
44
+ "oneOf": [
45
+ {
46
+ "type": "object", "additionalProperties": false, "required": ["app", "applicationId", "state", "window"],
47
+ "properties": { "app": { "type": "string" }, "applicationId": { "type": "string" }, "state": { "const": "ready" }, "window": { "$ref": "#/$defs/captureWindow" } }
48
+ },
49
+ {
50
+ "type": "object", "additionalProperties": false, "required": ["app", "applicationId", "state"],
51
+ "properties": { "app": { "type": "string" }, "applicationId": { "type": "string" }, "state": { "const": "no-data" } }
52
+ },
53
+ {
54
+ "type": "object", "additionalProperties": false, "required": ["app", "applicationId", "state", "reason"],
55
+ "properties": { "app": { "type": "string" }, "applicationId": { "oneOf": [{ "type": "null" }, { "type": "string" }] }, "state": { "const": "unavailable" }, "reason": { "type": "string" } }
56
+ }
57
+ ]
58
+ },
59
+ "captureWindow": {
60
+ "type": "object",
61
+ "required": ["applicationId", "days", "newestHour", "ageHours", "stale", "sessions", "routes", "union", "unmapped", "releasesWithoutManifest", "dropped"],
62
+ "properties": {
63
+ "applicationId": { "type": "string" },
64
+ "days": { "description": "The UTC days the window covers, oldest first.", "type": "array", "items": { "type": "string" } },
65
+ "newestHour": { "description": "The newest receipt hour holding events, yyyy-mm-ddThh (UTC).", "oneOf": [{ "type": "null" }, { "type": "string" }] },
66
+ "ageHours": { "oneOf": [{ "type": "null" }, { "type": "number", "minimum": 0 }] },
67
+ "stale": { "description": "Older than 24 hours, or no data.", "type": "boolean" },
68
+ "sessions": { "description": "Distinct captured sessions in the window.", "type": "integer", "minimum": 0 },
69
+ "routes": {
70
+ "description": "The changed routes, most sessions first.",
71
+ "type": "array",
72
+ "items": {
73
+ "type": "object",
74
+ "required": ["route", "match", "source", "sessions", "share"],
75
+ "properties": {
76
+ "route": { "description": "The route id from the app's route manifest.", "type": "string" },
77
+ "match": { "description": "Its match rule, in the framework adapter's syntax.", "type": "string" },
78
+ "source": { "description": "The repository file that defines it.", "type": "string" },
79
+ "sessions": { "type": "integer", "minimum": 0 },
80
+ "share": { "oneOf": [{ "type": "null" }, { "type": "number", "minimum": 0, "maximum": 1 }] }
81
+ }
82
+ }
83
+ },
84
+ "union": {
85
+ "description": "Sessions that visited at least one changed route (exact, not a sum).",
86
+ "type": "object", "required": ["sessions", "share"],
87
+ "properties": { "sessions": { "type": "integer", "minimum": 0 }, "share": { "oneOf": [{ "type": "null" }, { "type": "number", "minimum": 0, "maximum": 1 }] } }
88
+ },
89
+ "unmapped": {
90
+ "description": "Sessions on pages no route names: unknown whether they reach the change.",
91
+ "oneOf": [{ "type": "null" }, { "type": "object", "required": ["sessions", "share"],
92
+ "properties": { "sessions": { "type": "integer", "minimum": 0 }, "share": { "oneOf": [{ "type": "null" }, { "type": "number", "minimum": 0, "maximum": 1 }] } } }]
93
+ },
94
+ "releasesWithoutManifest": { "description": "Releases whose route manifest Haystack does not hold; their pages are unmapped.", "type": "array", "items": { "type": "string" } },
95
+ "dropped": { "description": "Events dropped at ingest in the window, per reason.", "type": "object", "additionalProperties": { "type": "integer", "minimum": 0 } }
96
+ }
97
+ },
98
+ "brief": {
99
+ "type": "object",
100
+ "required": ["functions", "leftOut", "planning"],
101
+ "properties": {
102
+ "functions": {
103
+ "description": "Every affected function the front half listed (at most 2000), nearest first. distance 0 is edited by the change; 1 is one engine edge away.",
104
+ "type": "array",
105
+ "maxItems": 2000,
106
+ "items": {
107
+ "type": "object",
108
+ "required": ["file", "name", "lines", "distance", "direction"],
109
+ "properties": {
110
+ "file": { "type": "string" },
111
+ "name": { "type": "string" },
112
+ "lines": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "integer", "minimum": 0 } },
113
+ "distance": { "type": "integer", "minimum": 0 },
114
+ "direction": { "enum": ["changed", "upstream", "downstream"] }
115
+ }
116
+ }
117
+ },
118
+ "leftOut": {
119
+ "description": "Functions the blast radius found but did not list, counted with why.",
120
+ "type": "array",
121
+ "items": { "type": "object", "required": ["reason", "count"], "properties": { "reason": { "type": "string" }, "count": { "type": "integer", "minimum": 0 } } }
122
+ },
123
+ "production": {
124
+ "description": "CRAWL-V1 amendment 19, only when the repository's own Haystack telemetry holds data in the last 7 days: how the brief's functions ran in production, by their index in `functions` (production runs the code before the change; lines are moved through the change's diff). A function with no row is unknown, not unused.",
125
+ "type": "object",
126
+ "required": ["windowDays", "payloads", "newestAt", "partial", "functions"],
127
+ "properties": {
128
+ "windowDays": { "type": "integer", "minimum": 1 },
129
+ "payloads": { "type": "integer", "minimum": 1 },
130
+ "newestAt": { "type": "string" },
131
+ "partial": { "type": "boolean", "description": "The read stopped at its byte budget: every count is a lower bound." },
132
+ "functions": {
133
+ "type": "array",
134
+ "items": {
135
+ "type": "object",
136
+ "required": ["index", "branches", "branchesOmitted", "values", "valuesOmitted"],
137
+ "properties": {
138
+ "index": { "type": "integer", "minimum": 0 },
139
+ "branches": {
140
+ "type": "array",
141
+ "maxItems": 100,
142
+ "items": {
143
+ "type": "object",
144
+ "required": ["line", "whenTrue", "whenFalse", "capped"],
145
+ "properties": {
146
+ "line": { "type": "integer", "minimum": 1 },
147
+ "whenTrue": { "type": "integer", "minimum": 0 },
148
+ "whenFalse": { "type": "integer", "minimum": 0 },
149
+ "capped": { "type": "boolean", "description": "A counter hit its cap: the counts are lower bounds." }
150
+ }
151
+ }
152
+ },
153
+ "branchesOmitted": { "type": "integer", "minimum": 0 },
154
+ "values": {
155
+ "type": "array",
156
+ "maxItems": 100,
157
+ "items": {
158
+ "type": "object",
159
+ "required": ["line", "what", "shapes"],
160
+ "properties": {
161
+ "line": { "type": "integer", "minimum": 1 },
162
+ "what": { "type": "string", "description": "The value observed, e.g. parameter:email or binding:options." },
163
+ "shapes": {
164
+ "type": "array",
165
+ "items": {
166
+ "type": "object",
167
+ "required": ["shape", "count"],
168
+ "properties": {
169
+ "shape": { "type": "string", "description": "A value-free category, e.g. undefined, string:empty:empty, array:few." },
170
+ "count": { "type": "integer", "minimum": 0 }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ },
177
+ "valuesOmitted": { "type": "integer", "minimum": 0 }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ },
183
+ "planning": {
184
+ "description": "Incident planning; null when it did not run for this change.",
185
+ "oneOf": [
186
+ { "type": "null" },
187
+ {
188
+ "type": "object",
189
+ "required": ["groups", "unresolved", "ideas"],
190
+ "properties": {
191
+ "groups": {
192
+ "description": "Behavior groups of the change, each with the failure patterns selected for it.",
193
+ "type": "array",
194
+ "items": {
195
+ "type": "object",
196
+ "required": ["id", "changeKeys", "patterns"],
197
+ "properties": {
198
+ "id": { "type": "string" },
199
+ "changeKeys": { "type": "array", "items": { "type": "string" } },
200
+ "patterns": {
201
+ "type": "array",
202
+ "items": { "type": "object", "required": ["entryId", "mechanism", "symptom"],
203
+ "properties": { "entryId": { "type": "string" }, "mechanism": { "type": "string" }, "symptom": { "type": "string" } } }
204
+ }
205
+ }
206
+ }
207
+ },
208
+ "unresolved": {
209
+ "description": "Changes no behavior group holds, and why.",
210
+ "type": "array",
211
+ "items": { "type": "object", "required": ["changeKey", "reason"], "properties": { "changeKey": { "type": "string" }, "reason": { "type": "string" } } }
212
+ },
213
+ "ideas": {
214
+ "description": "Haystack's own ideas: the selected situations, in plan order (at most 64). Every crawl explores them; pass the ones you want first, and your own, to `haystack verify --idea`.",
215
+ "type": "array",
216
+ "maxItems": 64,
217
+ "items": {
218
+ "type": "object",
219
+ "required": ["id", "groupId", "pattern", "why", "failure", "setup", "exercise", "watch", "start"],
220
+ "properties": {
221
+ "id": { "type": "string" },
222
+ "groupId": { "type": "string" },
223
+ "pattern": { "type": "string", "description": "The failure pattern's library id." },
224
+ "why": { "type": "string", "description": "How that pattern could arise in this code." },
225
+ "failure": { "type": "string", "description": "What would go wrong." },
226
+ "setup": { "type": "array", "items": { "type": "string" }, "description": "What must be set up or true first." },
227
+ "exercise": { "type": "string", "description": "What to do." },
228
+ "watch": { "type": "string", "description": "What to watch." },
229
+ "start": { "type": "string", "description": "Where it starts: the start screen, or a prepared start." }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
235
+ ]
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }