haiku-method 2.3.0 → 3.1.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.
@@ -7,127 +7,133 @@
7
7
  // Usage:
8
8
  // node capture-playwright-worker.js '{"url":"http://localhost:3000","routes":"/,/about",...}'
9
9
 
10
- const { chromium } = require("playwright");
11
- const path = require("path");
12
- const fs = require("fs");
10
+ const { chromium } = require("playwright")
11
+ const path = require("path")
12
+ const fs = require("fs")
13
13
 
14
14
  const BREAKPOINT_NAMES = {
15
- 375: "mobile",
16
- 768: "tablet",
17
- 1280: "desktop",
18
- };
15
+ 375: "mobile",
16
+ 768: "tablet",
17
+ 1280: "desktop",
18
+ }
19
19
 
20
20
  function breakpointName(width) {
21
- return BREAKPOINT_NAMES[width] || String(width);
21
+ return BREAKPOINT_NAMES[width] || String(width)
22
22
  }
23
23
 
24
24
  function viewName(route) {
25
- if (route === "/") return "home";
26
- // Strip leading slash, replace remaining slashes with dashes
27
- return route.replace(/^\//, "").replace(/\//g, "-");
25
+ if (route === "/") return "home"
26
+ // Strip leading slash, replace remaining slashes with dashes
27
+ return route.replace(/^\//, "").replace(/\//g, "-")
28
28
  }
29
29
 
30
30
  async function main() {
31
- const rawArgs = process.argv[2];
32
- if (!rawArgs) {
33
- console.error("ai-dlc: capture-playwright-worker: no arguments provided");
34
- process.exit(1);
35
- }
36
-
37
- let args;
38
- try {
39
- args = JSON.parse(rawArgs);
40
- } catch (e) {
41
- console.error(
42
- "ai-dlc: capture-playwright-worker: invalid JSON arguments:",
43
- e.message
44
- );
45
- process.exit(1);
46
- }
47
-
48
- const {
49
- url,
50
- static: staticPath,
51
- routes: routesStr,
52
- outputDir,
53
- breakpoints: breakpointsStr,
54
- prefix,
55
- waitFor,
56
- } = args;
57
-
58
- // In static mode (single file), routes are irrelevant — always capture one view per breakpoint.
59
- // Iterating multiple routes against a static file produces duplicate screenshots with misleading names.
60
- const allRoutes = routesStr.split(",").map((r) => r.trim());
61
- const routes = staticPath && !fs.statSync(staticPath).isDirectory() ? ["/"] : allRoutes;
62
- const breakpoints = breakpointsStr.split(",").map((b) => parseInt(b.trim(), 10));
63
- const screenshots = [];
64
-
65
- let browser;
66
- try {
67
- browser = await chromium.launch({ headless: true });
68
-
69
- for (const bp of breakpoints) {
70
- const context = await browser.newContext({
71
- viewport: { width: bp, height: 900 },
72
- });
73
- const page = await context.newPage();
74
-
75
- for (const route of routes) {
76
- let targetUrl;
77
- if (staticPath) {
78
- targetUrl = `file://${staticPath}`;
79
- } else {
80
- // Join base URL with route, avoiding double slashes
81
- const base = url.replace(/\/$/, "");
82
- targetUrl = route === "/" ? base : `${base}${route}`;
83
- }
84
-
85
- await page.goto(targetUrl, { waitUntil: "networkidle" });
86
-
87
- if (waitFor) {
88
- await page.waitForSelector(waitFor, { timeout: 10000 });
89
- }
90
-
91
- const bpName = breakpointName(bp);
92
- const view = viewName(route);
93
- const filename = prefix
94
- ? `${prefix}${bpName}-${view}.png`
95
- : `${bpName}-${view}.png`;
96
- const filepath = path.join(outputDir, filename);
97
-
98
- await page.screenshot({ path: filepath, fullPage: true });
99
-
100
- screenshots.push({
101
- breakpoint: bp,
102
- view: view,
103
- path: filename,
104
- });
105
-
106
- console.log(` captured: ${filename} (${bp}px)`);
107
- }
108
-
109
- await context.close();
110
- }
111
-
112
- // Write manifest
113
- const manifest = {
114
- provider: "playwright",
115
- captured_at: new Date().toISOString(),
116
- breakpoints: breakpoints,
117
- screenshots: screenshots,
118
- };
119
-
120
- const manifestPath = path.join(outputDir, "manifest.json");
121
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
122
- console.log(` manifest: ${manifestPath}`);
123
- } catch (err) {
124
- console.error("ai-dlc: capture-playwright-worker: capture failed:", err.message);
125
- process.exit(1);
126
- } finally {
127
- if (browser) {
128
- await browser.close();
129
- }
130
- }
31
+ const rawArgs = process.argv[2]
32
+ if (!rawArgs) {
33
+ console.error("ai-dlc: capture-playwright-worker: no arguments provided")
34
+ process.exit(1)
35
+ }
36
+
37
+ let args
38
+ try {
39
+ args = JSON.parse(rawArgs)
40
+ } catch (e) {
41
+ console.error(
42
+ "ai-dlc: capture-playwright-worker: invalid JSON arguments:",
43
+ e.message,
44
+ )
45
+ process.exit(1)
46
+ }
47
+
48
+ const {
49
+ url,
50
+ static: staticPath,
51
+ routes: routesStr,
52
+ outputDir,
53
+ breakpoints: breakpointsStr,
54
+ prefix,
55
+ waitFor,
56
+ } = args
57
+
58
+ // In static mode (single file), routes are irrelevant — always capture one view per breakpoint.
59
+ // Iterating multiple routes against a static file produces duplicate screenshots with misleading names.
60
+ const allRoutes = routesStr.split(",").map((r) => r.trim())
61
+ const routes =
62
+ staticPath && !fs.statSync(staticPath).isDirectory() ? ["/"] : allRoutes
63
+ const breakpoints = breakpointsStr
64
+ .split(",")
65
+ .map((b) => parseInt(b.trim(), 10))
66
+ const screenshots = []
67
+
68
+ let browser
69
+ try {
70
+ browser = await chromium.launch({ headless: true })
71
+
72
+ for (const bp of breakpoints) {
73
+ const context = await browser.newContext({
74
+ viewport: { width: bp, height: 900 },
75
+ })
76
+ const page = await context.newPage()
77
+
78
+ for (const route of routes) {
79
+ let targetUrl
80
+ if (staticPath) {
81
+ targetUrl = `file://${staticPath}`
82
+ } else {
83
+ // Join base URL with route, avoiding double slashes
84
+ const base = url.replace(/\/$/, "")
85
+ targetUrl = route === "/" ? base : `${base}${route}`
86
+ }
87
+
88
+ await page.goto(targetUrl, { waitUntil: "networkidle" })
89
+
90
+ if (waitFor) {
91
+ await page.waitForSelector(waitFor, { timeout: 10000 })
92
+ }
93
+
94
+ const bpName = breakpointName(bp)
95
+ const view = viewName(route)
96
+ const filename = prefix
97
+ ? `${prefix}${bpName}-${view}.png`
98
+ : `${bpName}-${view}.png`
99
+ const filepath = path.join(outputDir, filename)
100
+
101
+ await page.screenshot({ path: filepath, fullPage: true })
102
+
103
+ screenshots.push({
104
+ breakpoint: bp,
105
+ view: view,
106
+ path: filename,
107
+ })
108
+
109
+ console.log(` captured: ${filename} (${bp}px)`)
110
+ }
111
+
112
+ await context.close()
113
+ }
114
+
115
+ // Write manifest
116
+ const manifest = {
117
+ provider: "playwright",
118
+ captured_at: new Date().toISOString(),
119
+ breakpoints: breakpoints,
120
+ screenshots: screenshots,
121
+ }
122
+
123
+ const manifestPath = path.join(outputDir, "manifest.json")
124
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n")
125
+ console.log(` manifest: ${manifestPath}`)
126
+ } catch (err) {
127
+ console.error(
128
+ "ai-dlc: capture-playwright-worker: capture failed:",
129
+ err.message,
130
+ )
131
+ process.exit(1)
132
+ } finally {
133
+ if (browser) {
134
+ await browser.close()
135
+ }
136
+ }
131
137
  }
132
138
 
133
- main();
139
+ main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haiku-method",
3
- "version": "2.3.0",
3
+ "version": "3.1.0",
4
4
  "description": "H·AI·K·U methodology — universal lifecycle orchestration with hat-based workflows, completion criteria, and automatic context preservation.",
5
5
  "homepage": "https://haikumethod.ai",
6
6
  "repository": {
@@ -15,14 +15,16 @@
15
15
  "additionalProperties": {
16
16
  "type": "string"
17
17
  },
18
- "examples": [{
19
- "appointmentscheduled": "research",
20
- "qualifiedtobuy": "qualification",
21
- "presentationscheduled": "proposal",
22
- "decisionmakerboughtin": "negotiation",
23
- "closedwon": "close",
24
- "closedlost": "close"
25
- }]
18
+ "examples": [
19
+ {
20
+ "appointmentscheduled": "research",
21
+ "qualifiedtobuy": "qualification",
22
+ "presentationscheduled": "proposal",
23
+ "decisionmakerboughtin": "negotiation",
24
+ "closedwon": "close",
25
+ "closedlost": "close"
26
+ }
27
+ ]
26
28
  },
27
29
  "sync_activities": {
28
30
  "type": "boolean",
@@ -29,7 +29,9 @@
29
29
  "type": "array",
30
30
  "items": { "type": "string" }
31
31
  },
32
- "examples": [{ "backend": ["Elixir"], "frontend": ["React", "TypeScript"] }]
32
+ "examples": [
33
+ { "backend": ["Elixir"], "frontend": ["React", "TypeScript"] }
34
+ ]
33
35
  },
34
36
  "story_points": {
35
37
  "type": "string",
@@ -11,7 +11,16 @@
11
11
  },
12
12
  "default_export_target": {
13
13
  "type": "string",
14
- "enum": ["react", "vue", "svelte", "html", "flutter", "swiftui", "compose", "react-native"],
14
+ "enum": [
15
+ "react",
16
+ "vue",
17
+ "svelte",
18
+ "html",
19
+ "flutter",
20
+ "swiftui",
21
+ "compose",
22
+ "react-native"
23
+ ],
15
24
  "default": "react",
16
25
  "description": "Default framework target for code export"
17
26
  },
@@ -19,14 +19,16 @@
19
19
  "additionalProperties": {
20
20
  "type": "string"
21
21
  },
22
- "examples": [{
23
- "Prospecting": "research",
24
- "Qualification": "qualification",
25
- "Proposal/Price Quote": "proposal",
26
- "Negotiation/Review": "negotiation",
27
- "Closed Won": "close",
28
- "Closed Lost": "close"
29
- }]
22
+ "examples": [
23
+ {
24
+ "Prospecting": "research",
25
+ "Qualification": "qualification",
26
+ "Proposal/Price Quote": "proposal",
27
+ "Negotiation/Review": "negotiation",
28
+ "Closed Won": "close",
29
+ "Closed Lost": "close"
30
+ }
31
+ ]
30
32
  },
31
33
  "sync_activities": {
32
34
  "type": "boolean",
@@ -7,7 +7,13 @@
7
7
  "properties": {
8
8
  "provider": {
9
9
  "type": "string",
10
- "enum": ["pagerduty", "opsgenie", "datadog", "prometheus-alertmanager", "none"],
10
+ "enum": [
11
+ "pagerduty",
12
+ "opsgenie",
13
+ "datadog",
14
+ "prometheus-alertmanager",
15
+ "none"
16
+ ],
11
17
  "description": "Alerting/on-call provider"
12
18
  },
13
19
  "routing": {
@@ -7,7 +7,14 @@
7
7
  "properties": {
8
8
  "provider": {
9
9
  "type": "string",
10
- "enum": ["prometheus", "datadog", "cloudwatch", "newrelic", "otel", "none"],
10
+ "enum": [
11
+ "prometheus",
12
+ "datadog",
13
+ "cloudwatch",
14
+ "newrelic",
15
+ "otel",
16
+ "none"
17
+ ],
11
18
  "description": "Monitoring provider"
12
19
  },
13
20
  "dashboards": {
@@ -192,15 +192,27 @@
192
192
  "allOf": [
193
193
  {
194
194
  "if": { "properties": { "type": { "const": "notion" } } },
195
- "then": { "properties": { "config": { "$ref": "providers/notion.schema.json" } } }
195
+ "then": {
196
+ "properties": {
197
+ "config": { "$ref": "providers/notion.schema.json" }
198
+ }
199
+ }
196
200
  },
197
201
  {
198
202
  "if": { "properties": { "type": { "const": "confluence" } } },
199
- "then": { "properties": { "config": { "$ref": "providers/confluence.schema.json" } } }
203
+ "then": {
204
+ "properties": {
205
+ "config": { "$ref": "providers/confluence.schema.json" }
206
+ }
207
+ }
200
208
  },
201
209
  {
202
210
  "if": { "properties": { "type": { "const": "google-docs" } } },
203
- "then": { "properties": { "config": { "$ref": "providers/google-docs.schema.json" } } }
211
+ "then": {
212
+ "properties": {
213
+ "config": { "$ref": "providers/google-docs.schema.json" }
214
+ }
215
+ }
204
216
  }
205
217
  ]
206
218
  },
@@ -227,19 +239,33 @@
227
239
  "allOf": [
228
240
  {
229
241
  "if": { "properties": { "type": { "const": "jira" } } },
230
- "then": { "properties": { "config": { "$ref": "providers/jira.schema.json" } } }
242
+ "then": {
243
+ "properties": { "config": { "$ref": "providers/jira.schema.json" } }
244
+ }
231
245
  },
232
246
  {
233
247
  "if": { "properties": { "type": { "const": "linear" } } },
234
- "then": { "properties": { "config": { "$ref": "providers/linear.schema.json" } } }
248
+ "then": {
249
+ "properties": {
250
+ "config": { "$ref": "providers/linear.schema.json" }
251
+ }
252
+ }
235
253
  },
236
254
  {
237
255
  "if": { "properties": { "type": { "const": "github-issues" } } },
238
- "then": { "properties": { "config": { "$ref": "providers/github-issues.schema.json" } } }
256
+ "then": {
257
+ "properties": {
258
+ "config": { "$ref": "providers/github-issues.schema.json" }
259
+ }
260
+ }
239
261
  },
240
262
  {
241
263
  "if": { "properties": { "type": { "const": "gitlab-issues" } } },
242
- "then": { "properties": { "config": { "$ref": "providers/gitlab-issues.schema.json" } } }
264
+ "then": {
265
+ "properties": {
266
+ "config": { "$ref": "providers/gitlab-issues.schema.json" }
267
+ }
268
+ }
243
269
  }
244
270
  ]
245
271
  },
@@ -249,7 +275,15 @@
249
275
  "properties": {
250
276
  "type": {
251
277
  "type": "string",
252
- "enum": ["canva", "openpencil", "pencil", "penpot", "excalidraw", "figma", "auto"],
278
+ "enum": [
279
+ "canva",
280
+ "openpencil",
281
+ "pencil",
282
+ "penpot",
283
+ "excalidraw",
284
+ "figma",
285
+ "auto"
286
+ ],
253
287
  "description": "Design provider type"
254
288
  },
255
289
  "config": {
@@ -266,27 +300,51 @@
266
300
  "allOf": [
267
301
  {
268
302
  "if": { "properties": { "type": { "const": "canva" } } },
269
- "then": { "properties": { "config": { "$ref": "providers/canva.schema.json" } } }
303
+ "then": {
304
+ "properties": {
305
+ "config": { "$ref": "providers/canva.schema.json" }
306
+ }
307
+ }
270
308
  },
271
309
  {
272
310
  "if": { "properties": { "type": { "const": "openpencil" } } },
273
- "then": { "properties": { "config": { "$ref": "providers/openpencil.schema.json" } } }
311
+ "then": {
312
+ "properties": {
313
+ "config": { "$ref": "providers/openpencil.schema.json" }
314
+ }
315
+ }
274
316
  },
275
317
  {
276
318
  "if": { "properties": { "type": { "const": "pencil" } } },
277
- "then": { "properties": { "config": { "$ref": "providers/pencil.schema.json" } } }
319
+ "then": {
320
+ "properties": {
321
+ "config": { "$ref": "providers/pencil.schema.json" }
322
+ }
323
+ }
278
324
  },
279
325
  {
280
326
  "if": { "properties": { "type": { "const": "penpot" } } },
281
- "then": { "properties": { "config": { "$ref": "providers/penpot.schema.json" } } }
327
+ "then": {
328
+ "properties": {
329
+ "config": { "$ref": "providers/penpot.schema.json" }
330
+ }
331
+ }
282
332
  },
283
333
  {
284
334
  "if": { "properties": { "type": { "const": "excalidraw" } } },
285
- "then": { "properties": { "config": { "$ref": "providers/excalidraw.schema.json" } } }
335
+ "then": {
336
+ "properties": {
337
+ "config": { "$ref": "providers/excalidraw.schema.json" }
338
+ }
339
+ }
286
340
  },
287
341
  {
288
342
  "if": { "properties": { "type": { "const": "figma" } } },
289
- "then": { "properties": { "config": { "$ref": "providers/figma.schema.json" } } }
343
+ "then": {
344
+ "properties": {
345
+ "config": { "$ref": "providers/figma.schema.json" }
346
+ }
347
+ }
290
348
  }
291
349
  ]
292
350
  },
@@ -313,15 +371,27 @@
313
371
  "allOf": [
314
372
  {
315
373
  "if": { "properties": { "type": { "const": "slack" } } },
316
- "then": { "properties": { "config": { "$ref": "providers/slack.schema.json" } } }
374
+ "then": {
375
+ "properties": {
376
+ "config": { "$ref": "providers/slack.schema.json" }
377
+ }
378
+ }
317
379
  },
318
380
  {
319
381
  "if": { "properties": { "type": { "const": "teams" } } },
320
- "then": { "properties": { "config": { "$ref": "providers/teams.schema.json" } } }
382
+ "then": {
383
+ "properties": {
384
+ "config": { "$ref": "providers/teams.schema.json" }
385
+ }
386
+ }
321
387
  },
322
388
  {
323
389
  "if": { "properties": { "type": { "const": "discord" } } },
324
- "then": { "properties": { "config": { "$ref": "providers/discord.schema.json" } } }
390
+ "then": {
391
+ "properties": {
392
+ "config": { "$ref": "providers/discord.schema.json" }
393
+ }
394
+ }
325
395
  }
326
396
  ]
327
397
  },
@@ -348,11 +418,19 @@
348
418
  "allOf": [
349
419
  {
350
420
  "if": { "properties": { "type": { "const": "salesforce" } } },
351
- "then": { "properties": { "config": { "$ref": "providers/salesforce.schema.json" } } }
421
+ "then": {
422
+ "properties": {
423
+ "config": { "$ref": "providers/salesforce.schema.json" }
424
+ }
425
+ }
352
426
  },
353
427
  {
354
428
  "if": { "properties": { "type": { "const": "hubspot" } } },
355
- "then": { "properties": { "config": { "$ref": "providers/hubspot.schema.json" } } }
429
+ "then": {
430
+ "properties": {
431
+ "config": { "$ref": "providers/hubspot.schema.json" }
432
+ }
433
+ }
356
434
  }
357
435
  ]
358
436
  },
@@ -379,15 +457,27 @@
379
457
  "allOf": [
380
458
  {
381
459
  "if": { "properties": { "type": { "const": "notion" } } },
382
- "then": { "properties": { "config": { "$ref": "providers/notion.schema.json" } } }
460
+ "then": {
461
+ "properties": {
462
+ "config": { "$ref": "providers/notion.schema.json" }
463
+ }
464
+ }
383
465
  },
384
466
  {
385
467
  "if": { "properties": { "type": { "const": "confluence" } } },
386
- "then": { "properties": { "config": { "$ref": "providers/confluence.schema.json" } } }
468
+ "then": {
469
+ "properties": {
470
+ "config": { "$ref": "providers/confluence.schema.json" }
471
+ }
472
+ }
387
473
  },
388
474
  {
389
475
  "if": { "properties": { "type": { "const": "google-docs" } } },
390
- "then": { "properties": { "config": { "$ref": "providers/google-docs.schema.json" } } }
476
+ "then": {
477
+ "properties": {
478
+ "config": { "$ref": "providers/google-docs.schema.json" }
479
+ }
480
+ }
391
481
  }
392
482
  ]
393
483
  },
@@ -7,6 +7,23 @@ description: Migrate legacy AI-DLC intents to H·AI·K·U format
7
7
 
8
8
  Convert legacy `.ai-dlc/` intents to `.haiku/` format.
9
9
 
10
- 1. Scan `.ai-dlc/` for intents. If a specific slug is given, migrate that one. Otherwise list and ask.
11
- 2. Run `haiku migrate <slug>` to perform the migration.
12
- 3. After migration, suggest `/haiku:pickup <slug>` to continue execution.
10
+ **Never apply a migration without showing a dry-run first.** Bare `haiku migrate` is rejected by the binary, but the rule still holds when you pass `--all` or a specific slug: dry-run, get the user's OK, then apply.
11
+
12
+ ## Steps
13
+
14
+ 1. List candidates: `ls .ai-dlc/`. If the user named a slug, use that. Otherwise show the list and ask which one(s).
15
+ 2. **Dry-run**: `haiku migrate <slug>` (dry-run is the default). Show the user the output — what would be written, where, how many files.
16
+ 3. **Get explicit approval** from the user before applying. Don't infer consent from prior context.
17
+ 4. **Apply**: `haiku migrate <slug> --apply`. One slug at a time unless the user explicitly approved `--all`.
18
+ 5. After migration, suggest `/haiku:pickup <slug>` to continue execution.
19
+
20
+ ## Flags
21
+
22
+ - `--apply` — actually write. Default is dry-run.
23
+ - `--all` — migrate every intent in `.ai-dlc/`. Pair with `--apply` to commit.
24
+ - `--force` — re-migrate intents that already exist under `.haiku/`. Use sparingly.
25
+ - `--allow-dirty` — skip the git-clean precheck. Don't pass this without user approval; a dirty tree means the migration output gets tangled with unrelated in-progress work.
26
+
27
+ ## Why these rules exist
28
+
29
+ Bare `haiku migrate --apply` rewrites every intent in `.ai-dlc/` at once. In a monorepo, one such commit shows up in every open MR. The dry-run-then-confirm flow exists so this can't happen by accident.