@getpawl/setup 1.4.1 → 1.4.5

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.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Then the `pawl` command will be available globally (`pawl sync`, `pawl connect`,
18
18
  npx @getpawl/setup init
19
19
  ```
20
20
 
21
- Creates `.pawl/` directory with sync scripts, configures Claude Code hooks, and sets up git post-commit hook.
21
+ Sets up Pawl in this repo — works for both new and existing projects. Creates `.pawl/` directory with sync scripts, configures Claude Code hooks, sets up git post-commit hook, and pulls existing context if available.
22
22
 
23
23
  ### 2. Connect
24
24
 
@@ -37,6 +37,20 @@ pawl sync --pull # pull latest context from dashboard
37
37
 
38
38
  Sync happens automatically via hooks. Use these commands for manual sync or agents without lifecycle hooks (Codex, etc.).
39
39
 
40
+ ## Joining an existing Pawl project
41
+
42
+ If your team already uses Pawl, you don't need to bootstrap — just pull:
43
+
44
+ 1. Clone the repo
45
+ 2. Copy `.env.example` to `.pawl/.env` and add your API key and project ID
46
+ — get these from [app.getpawl.dev](https://app.getpawl.dev) → your project → Settings
47
+ 3. Run `npx @getpawl/setup init`
48
+ 4. Run `npx @getpawl/setup status` to confirm everything is connected
49
+ 5. Start a Claude Code session — full context is ready
50
+
51
+ > `pawl init` detects existing specs and skips bootstrap automatically.
52
+ > Pawl currently supports one project per repo. Multi-project monorepo support is planned.
53
+
40
54
  ## Legacy Setup
41
55
 
42
56
  Existing users can still use the one-step flow:
package/dist/index.js CHANGED
@@ -22,6 +22,8 @@ async function main() {
22
22
  const noConnect = rest.includes("--no-connect");
23
23
  const key = rest.find((a) => !a.startsWith("--"));
24
24
  await pawlInit(key, { noConnect });
25
+ } else if (arg === "status") {
26
+ await pawlStatus();
25
27
  } else if (arg === "migrate") {
26
28
  pawlMigrate();
27
29
  } else {
@@ -33,9 +35,10 @@ function printUsage() {
33
35
  Pawl \u2014 project visibility layer for AI-native development
34
36
 
35
37
  Usage:
36
- pawl init Set up Pawl in this repo
38
+ pawl init Set up Pawl in this repo (new or existing project)
37
39
  pawl connect Link this repo to your Pawl dashboard
38
40
  pawl sync [--pull] Push session data to dashboard (or pull context)
41
+ pawl status Check project health and connectivity
39
42
  `);
40
43
  }
41
44
  function pawlSync(flag) {
@@ -60,19 +63,21 @@ function pawlSync(flag) {
60
63
  }
61
64
  function pawlMigrate() {
62
65
  const cwd = process.cwd();
63
- const syncPath = (0, import_node_path.join)(cwd, ".pawl", "sync.sh");
64
- if (!(0, import_node_fs.existsSync)(syncPath)) {
66
+ const pawlDir = (0, import_node_path.join)(cwd, ".pawl");
67
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(pawlDir, "sync.sh")) && !(0, import_node_fs.existsSync)((0, import_node_path.join)(pawlDir, "sync.mjs"))) {
65
68
  console.log(" Nothing to migrate \u2014 run `pawl init` first.");
66
69
  process.exit(0);
67
70
  }
68
- let content = (0, import_node_fs.readFileSync)(syncPath, "utf-8");
69
- if (!content.includes("AGENTMAP_")) {
70
- console.log(" Already up to date.");
71
- process.exit(0);
71
+ const syncPath = (0, import_node_path.join)(pawlDir, "sync.sh");
72
+ if ((0, import_node_fs.existsSync)(syncPath)) {
73
+ let content = (0, import_node_fs.readFileSync)(syncPath, "utf-8");
74
+ if (content.includes("AGENTMAP_")) {
75
+ content = content.replace(/AGENTMAP_API_URL/g, "PAWL_API_URL").replace(/AGENTMAP_PROJECT_ID/g, "PAWL_PROJECT_ID").replace(/AGENTMAP_API_KEY/g, "PAWL_API_KEY");
76
+ (0, import_node_fs.writeFileSync)(syncPath, content, "utf-8");
77
+ console.log(" .pawl/sync.sh migrated to PAWL_* vars.");
78
+ }
72
79
  }
73
- content = content.replace(/AGENTMAP_API_URL/g, "PAWL_API_URL").replace(/AGENTMAP_PROJECT_ID/g, "PAWL_PROJECT_ID").replace(/AGENTMAP_API_KEY/g, "PAWL_API_KEY");
74
- (0, import_node_fs.writeFileSync)(syncPath, content, "utf-8");
75
- const envPath = (0, import_node_path.join)(cwd, ".pawl", ".env");
80
+ const envPath = (0, import_node_path.join)(pawlDir, ".env");
76
81
  if ((0, import_node_fs.existsSync)(envPath)) {
77
82
  let envContent = (0, import_node_fs.readFileSync)(envPath, "utf-8");
78
83
  if (envContent.includes("AGENTMAP_")) {
@@ -81,14 +86,21 @@ function pawlMigrate() {
81
86
  console.log(" .pawl/.env also migrated.");
82
87
  }
83
88
  }
84
- if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl", "sync.mjs"))) {
89
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(pawlDir, "sync.mjs"))) {
85
90
  writePawlSyncMjs(cwd);
86
91
  }
87
- if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl", "track-file.mjs"))) {
92
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(pawlDir, "track-file.mjs"))) {
88
93
  writeTrackFileScript(cwd);
89
94
  }
90
95
  mergeClaudeSettings(cwd, ".pawl");
91
- console.log(" Migration complete \u2014 .pawl/sync.sh updated to PAWL_* vars.");
96
+ writePawlClaudeMd(cwd);
97
+ writeAgentsMd(cwd);
98
+ const oldDecisions = (0, import_node_path.join)(pawlDir, "decisions.json");
99
+ if ((0, import_node_fs.existsSync)(oldDecisions)) {
100
+ console.log(" \u26A0 Found .pawl/decisions.json (old format). Per-decision files are now in .pawl/decisions/.");
101
+ console.log(" Safe to delete .pawl/decisions.json after confirming decisions/ is populated.");
102
+ }
103
+ console.log(" Migration complete.");
92
104
  }
93
105
  function generateCode() {
94
106
  const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -340,23 +352,190 @@ async function pawlInit(key, opts) {
340
352
  writeAgentsMd(cwd);
341
353
  updateGitignore(cwd);
342
354
  printSummary(detected, !!key, !!opts?.noConnect);
355
+ if (opts?.noConnect && !(0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl", ".env"))) {
356
+ console.log("\n No API key found. Add PAWL_API_KEY to .pawl/.env (see .env.example) then run pawl init again.");
357
+ return;
358
+ }
343
359
  let config = null;
344
360
  if (!key && !opts?.noConnect) {
345
361
  console.log("\nConnecting to Pawl dashboard...\n");
346
362
  config = await pawlConnect();
347
363
  }
348
- if (config) {
364
+ const specsDir = (0, import_node_path.join)(cwd, ".pawl", "specs");
365
+ const hasExistingSpecs = (0, import_node_fs.existsSync)(specsDir) && (0, import_node_fs.readdirSync)(specsDir).some((f) => f.endsWith(".md"));
366
+ if (hasExistingSpecs) {
367
+ console.log(" Existing specs found \u2014 skipping bootstrap");
368
+ } else if (config) {
349
369
  const repoUrl = getRepoUrl(cwd);
350
370
  if (repoUrl) {
351
371
  console.log("Scanning your repo to generate specs...");
352
372
  const result = await autoBootstrap(config.projectId, config.apiKey, repoUrl, config.apiUrl);
353
373
  if (result && result.specsGenerated > 0) {
354
374
  console.log(` Generated ${result.specsGenerated} draft specs`);
355
- console.log(` Review and confirm at: ${hyperlink(result.previewUrl, result.previewUrl)}
356
- `);
375
+ console.log(` Review and confirm at: ${hyperlink(result.previewUrl, result.previewUrl)}`);
376
+ openBrowser(result.previewUrl);
377
+ console.log(" After confirming, run:\n pawl sync --pull\n");
357
378
  }
358
379
  }
359
380
  }
381
+ const envPath = (0, import_node_path.join)(cwd, ".pawl", ".env");
382
+ if ((0, import_node_fs.existsSync)(envPath)) {
383
+ console.log(" Pulling latest context from API...");
384
+ try {
385
+ pawlSync("--pull");
386
+ console.log(" \u2713 Context ready");
387
+ } catch {
388
+ console.warn(" \u26A0 Could not pull context \u2014 run `pawl sync --pull` manually");
389
+ }
390
+ try {
391
+ const sha = (0, import_node_child_process.execSync)("git rev-parse HEAD", { cwd, encoding: "utf-8" }).trim();
392
+ (0, import_node_fs.writeFileSync)((0, import_node_path.join)(cwd, ".pawl", ".last-sync-sha"), sha, "utf-8");
393
+ } catch {
394
+ }
395
+ }
396
+ try {
397
+ const pawlEnv = readPawlEnvFromFile(cwd);
398
+ if (pawlEnv.apiKey && pawlEnv.projectId) {
399
+ const baseUrl = `${pawlEnv.apiUrl}/api/projects/${pawlEnv.projectId}`;
400
+ const res = await fetch(`${baseUrl}/decisions/export`, {
401
+ headers: { Authorization: `Bearer ${pawlEnv.apiKey}`, Accept: "application/json" }
402
+ });
403
+ if (res.ok) {
404
+ const data = await res.json();
405
+ if (data.decisions?.length) {
406
+ const decisionsDir = (0, import_node_path.join)(cwd, ".pawl", "decisions");
407
+ (0, import_node_fs.mkdirSync)(decisionsDir, { recursive: true });
408
+ let written = 0;
409
+ for (const d of data.decisions) {
410
+ const fp = (0, import_node_path.join)(decisionsDir, `${d.id}.json`);
411
+ if ((0, import_node_fs.existsSync)(fp)) continue;
412
+ (0, import_node_fs.writeFileSync)(fp, JSON.stringify(d, null, 2) + "\n", "utf-8");
413
+ written++;
414
+ }
415
+ if (written > 0) console.log(` \u2713 Seeded ${written} decision${written > 1 ? "s" : ""}`);
416
+ }
417
+ }
418
+ }
419
+ } catch {
420
+ }
421
+ }
422
+ function readPawlEnvFromFile(cwd) {
423
+ const envPath = (0, import_node_path.join)(cwd, ".pawl", ".env");
424
+ if (!(0, import_node_fs.existsSync)(envPath)) return {};
425
+ const vars = {};
426
+ for (const line of (0, import_node_fs.readFileSync)(envPath, "utf-8").split("\n")) {
427
+ const trimmed = line.trim();
428
+ if (!trimmed || trimmed.startsWith("#")) continue;
429
+ const eq = trimmed.indexOf("=");
430
+ if (eq === -1) continue;
431
+ vars[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
432
+ }
433
+ return {
434
+ apiKey: vars.PAWL_API_KEY || "",
435
+ projectId: vars.PAWL_PROJECT_ID || "",
436
+ apiUrl: vars.PAWL_API_URL || DEFAULT_API_URL
437
+ };
438
+ }
439
+ async function pawlStatus() {
440
+ const cwd = process.cwd();
441
+ const pawlEnv = readPawlEnvFromFile(cwd);
442
+ const apiKey = pawlEnv.apiKey || "";
443
+ const projectId = pawlEnv.projectId || "";
444
+ const apiUrl = pawlEnv.apiUrl || DEFAULT_API_URL;
445
+ console.log("\n pawl status\n");
446
+ if (!apiKey) {
447
+ console.log(" \u2717 PAWL_API_KEY not set");
448
+ console.log(" Fix: pawl connect\n");
449
+ process.exit(1);
450
+ }
451
+ console.log(" \u2713 PAWL_API_KEY set");
452
+ if (!projectId) {
453
+ console.log(" \u2717 PAWL_PROJECT_ID not set");
454
+ console.log(" Fix: pawl connect\n");
455
+ process.exit(1);
456
+ }
457
+ console.log(" \u2713 PAWL_PROJECT_ID set");
458
+ try {
459
+ const res = await fetch(`${apiUrl}/api/projects/${projectId}/context`, {
460
+ headers: { Authorization: `Bearer ${apiKey}` }
461
+ });
462
+ if (res.ok) {
463
+ console.log(` \u2713 API connected (${apiUrl})`);
464
+ } else {
465
+ console.log(` \u2717 API returned ${res.status}`);
466
+ process.exit(1);
467
+ }
468
+ } catch {
469
+ console.log(" \u2717 API unreachable");
470
+ process.exit(1);
471
+ }
472
+ const lastSyncPath = (0, import_node_path.join)(cwd, ".pawl", ".last-sync-sha");
473
+ if (!(0, import_node_fs.existsSync)(lastSyncPath)) {
474
+ console.log(" \u25CB Never synced");
475
+ console.log(" Fix: pawl sync --pull");
476
+ } else {
477
+ const stat = (0, import_node_fs.statSync)(lastSyncPath);
478
+ const ageMs = Date.now() - stat.mtimeMs;
479
+ const ageHrs = Math.floor(ageMs / 36e5);
480
+ const sha = (0, import_node_fs.readFileSync)(lastSyncPath, "utf-8").trim().slice(0, 7);
481
+ if (ageHrs > 24) {
482
+ console.log(` \u26A0 Last sync ${ageHrs}h ago (commit ${sha})`);
483
+ console.log(" Fix: pawl sync --pull");
484
+ } else {
485
+ const ago = ageHrs > 0 ? `${ageHrs}h ago` : `${Math.floor(ageMs / 6e4)}m ago`;
486
+ console.log(` \u2713 Last sync ${ago} (commit ${sha})`);
487
+ }
488
+ }
489
+ console.log("");
490
+ const specsDir = (0, import_node_path.join)(cwd, ".pawl", "specs");
491
+ if ((0, import_node_fs.existsSync)(specsDir)) {
492
+ const specFiles = (0, import_node_fs.readdirSync)(specsDir).filter((f) => f.endsWith(".md"));
493
+ console.log(` ${specFiles.length > 0 ? "\u2713" : "\u26A0"} specs/ ${specFiles.length} files`);
494
+ } else {
495
+ console.log(" \u26A0 specs/ missing");
496
+ console.log(" Fix: pawl sync --pull");
497
+ }
498
+ const decisionsDir = (0, import_node_path.join)(cwd, ".pawl", "decisions");
499
+ if ((0, import_node_fs.existsSync)(decisionsDir)) {
500
+ const decFiles = (0, import_node_fs.readdirSync)(decisionsDir).filter((f) => f.endsWith(".json"));
501
+ console.log(` ${decFiles.length > 0 ? "\u2713" : "\u26A0"} decisions/ ${decFiles.length} files`);
502
+ } else {
503
+ console.log(" \u26A0 decisions/ missing");
504
+ console.log(" Fix: pawl sync --pull");
505
+ }
506
+ console.log("");
507
+ const ccSettingsPath = (0, import_node_path.join)(cwd, ".claude", "settings.json");
508
+ if ((0, import_node_fs.existsSync)(ccSettingsPath)) {
509
+ const ccSettings = (0, import_node_fs.readFileSync)(ccSettingsPath, "utf-8");
510
+ const hasStopHook = ccSettings.includes("sync.mjs") && ccSettings.includes("Stop");
511
+ console.log(` ${hasStopHook ? "\u2713" : "\u2717"} CC stop hook ${hasStopHook ? "installed" : "not installed"}`);
512
+ if (!hasStopHook) console.log(" Fix: pawl init");
513
+ } else {
514
+ console.log(" \u2717 CC stop hook not installed");
515
+ console.log(" Fix: pawl init");
516
+ }
517
+ const hookPath = (0, import_node_path.join)(cwd, ".git", "hooks", "post-commit");
518
+ const hasGitHook = (0, import_node_fs.existsSync)(hookPath);
519
+ console.log(` ${hasGitHook ? "\u2713" : "\u2717"} git post-commit ${hasGitHook ? "installed" : "not installed"}`);
520
+ if (!hasGitHook) console.log(" Fix: pawl init");
521
+ const hasSyncMjs = (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl", "sync.mjs"));
522
+ console.log(` ${hasSyncMjs ? "\u2713" : "\u2717"} sync.mjs ${hasSyncMjs ? "present" : "missing"}`);
523
+ if (!hasSyncMjs) console.log(" Fix: pawl init");
524
+ console.log("");
525
+ try {
526
+ const pkgPath = (0, import_node_path.join)(__dirname, "..", "package.json");
527
+ const localVer = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8")).version;
528
+ let latestVer = localVer;
529
+ try {
530
+ latestVer = (0, import_node_child_process.execSync)("npm view @getpawl/setup version", { encoding: "utf-8" }).trim();
531
+ } catch {
532
+ }
533
+ const upToDate = localVer === latestVer;
534
+ console.log(` Package @getpawl/setup v${localVer} (latest: v${latestVer} ${upToDate ? "\u2713" : "\u26A0"})`);
535
+ if (!upToDate) console.log(" Fix: npm install -g @getpawl/setup@latest");
536
+ } catch {
537
+ }
538
+ console.log("");
360
539
  }
361
540
  function detectAgents(cwd) {
362
541
  return {
@@ -389,6 +568,11 @@ function migrateIfNeeded(cwd, hasAgentMapDir) {
389
568
  (0, import_node_fs.writeFileSync)(envPath, envContent, "utf-8");
390
569
  }
391
570
  console.log(" .agentmap/ preserved \u2014 safe to delete manually when ready.\n");
571
+ const oldDecisions = (0, import_node_path.join)(cwd, ".pawl", "decisions.json");
572
+ if ((0, import_node_fs.existsSync)(oldDecisions)) {
573
+ console.log(" \u26A0 Found .pawl/decisions.json (old format). Per-decision files are now in .pawl/decisions/.");
574
+ console.log(" Safe to delete .pawl/decisions.json after confirming decisions/ is populated.");
575
+ }
392
576
  }
393
577
  function writePawlEnvFile(cwd, config) {
394
578
  const content = [
@@ -635,6 +819,7 @@ async function pull() {
635
819
  for (const file of data.files) {
636
820
  const filePath = join(SCRIPT_DIR, file.path);
637
821
  mkdirSync(dirname(filePath), { recursive: true });
822
+ if (file.path.startsWith('decisions/') && existsSync(filePath)) continue;
638
823
  writeFileSync(filePath, file.content, 'utf-8');
639
824
  }
640
825
  const ctxPath = join(SCRIPT_DIR, 'context.md');
@@ -871,6 +1056,21 @@ function writePawlClaudeMd(cwd) {
871
1056
  "- `.pawl/decisions.json` \u2014 architectural decisions log (read before starting work)",
872
1057
  "",
873
1058
  "Read `.pawl/context.md` at session start, then load relevant specs from `.pawl/specs/` based on the task.",
1059
+ "",
1060
+ "## Decision Capture",
1061
+ "",
1062
+ "When you make a non-obvious architectural choice \u2014 picking one approach over alternatives,",
1063
+ "deciding on a data model, choosing a library, or changing direction mid-task \u2014 output this",
1064
+ "block before writing code:",
1065
+ "",
1066
+ "PAWL_INTENT:",
1067
+ "context: <what situation triggered this decision>",
1068
+ "approach: <what was chosen and why>",
1069
+ "alternatives_rejected: <what else was considered>",
1070
+ "constraints: <what limited the options>",
1071
+ "END_PAWL_INTENT",
1072
+ "",
1073
+ "Only use this for significant decisions. Skip for routine implementation, bug fixes, and style changes.",
874
1074
  PAWL_END
875
1075
  ].join("\n");
876
1076
  if (!(0, import_node_fs.existsSync)(claudeMdPath)) {
@@ -911,6 +1111,21 @@ function writeAgentsMd(cwd) {
911
1111
  "",
912
1112
  "**Start here**: Read `.pawl/context.md` first, then load relevant",
913
1113
  "spec files from `.pawl/specs/` based on the task at hand.",
1114
+ "",
1115
+ "## Decision Capture",
1116
+ "",
1117
+ "When you make a non-obvious architectural choice \u2014 picking one approach over alternatives,",
1118
+ "deciding on a data model, choosing a library, or changing direction mid-task \u2014 output this",
1119
+ "block before writing code:",
1120
+ "",
1121
+ "PAWL_INTENT:",
1122
+ "context: <what situation triggered this decision>",
1123
+ "approach: <what was chosen and why>",
1124
+ "alternatives_rejected: <what else was considered>",
1125
+ "constraints: <what limited the options>",
1126
+ "END_PAWL_INTENT",
1127
+ "",
1128
+ "Only use this for significant decisions. Skip for routine implementation, bug fixes, and style changes.",
914
1129
  END_MARKER
915
1130
  ].join("\n");
916
1131
  if (!(0, import_node_fs.existsSync)(agentsMdPath)) {
@@ -61,7 +61,10 @@ async function parseSessionFile(filePath) {
61
61
  let totalOutputTokens = 0;
62
62
  let cacheReadTokens = 0;
63
63
  let cacheWriteTokens = 0;
64
+ const reasoningDecisions = [];
65
+ let currentMessageIndex = 0;
64
66
  for await (const line of rl) {
67
+ currentMessageIndex++;
65
68
  let parsed;
66
69
  try {
67
70
  parsed = JSON.parse(line);
@@ -88,52 +91,93 @@ async function parseSessionFile(filePath) {
88
91
  const content = msg.content;
89
92
  if (Array.isArray(content)) {
90
93
  for (const block of content) {
91
- if (block.type !== "tool_use") continue;
92
- const fp = block.input?.file_path;
93
- if (fp) {
94
- if (block.name === "Read") {
95
- filesRead.add(fp);
96
- } else if (block.name === "Write" || block.name === "Edit") {
97
- filesWritten.add(fp);
94
+ if (block.type === "tool_use") {
95
+ const fp = block.input?.file_path;
96
+ if (fp) {
97
+ if (block.name === "Read") {
98
+ filesRead.add(fp);
99
+ } else if (block.name === "Write" || block.name === "Edit") {
100
+ filesWritten.add(fp);
101
+ }
98
102
  }
99
- }
100
- if (block.name === "TaskCreate") {
101
- pendingCreates.set(block.id, {
102
- subject: block.input?.subject || "",
103
- description: block.input?.description || ""
104
- });
105
- }
106
- if (block.name === "TaskUpdate") {
107
- const taskId = block.input?.taskId;
108
- const task = taskId ? tasks.get(taskId) : void 0;
109
- if (task && block.input?.status) {
110
- task.status = block.input.status;
111
- if (block.input.status === "in_progress")
112
- currentTaskId = taskId;
113
- if (block.input.status === "completed" || block.input.status === "deleted") {
114
- if (currentTaskId === taskId) currentTaskId = null;
103
+ if (block.name === "TaskCreate") {
104
+ pendingCreates.set(block.id, {
105
+ subject: block.input?.subject || "",
106
+ description: block.input?.description || ""
107
+ });
108
+ }
109
+ if (block.name === "TaskUpdate") {
110
+ const taskId = block.input?.taskId;
111
+ const task = taskId ? tasks.get(taskId) : void 0;
112
+ if (task && block.input?.status) {
113
+ task.status = block.input.status;
114
+ if (block.input.status === "in_progress")
115
+ currentTaskId = taskId;
116
+ if (block.input.status === "completed" || block.input.status === "deleted") {
117
+ if (currentTaskId === taskId) currentTaskId = null;
118
+ }
115
119
  }
116
120
  }
121
+ if (currentTaskId && (block.name === "Write" || block.name === "Edit") && fp) {
122
+ tasks.get(currentTaskId)?.filesAssociated.add(fp);
123
+ }
117
124
  }
118
- if (currentTaskId && (block.name === "Write" || block.name === "Edit") && fp) {
119
- tasks.get(currentTaskId)?.filesAssociated.add(fp);
125
+ if (block.type === "text" && typeof block.text === "string") {
126
+ const text = block.text;
127
+ const intentStart = text.indexOf("PAWL_INTENT:");
128
+ const intentEnd = text.indexOf("END_PAWL_INTENT");
129
+ if (intentStart !== -1 && intentEnd !== -1 && intentEnd > intentStart) {
130
+ const rawBlock = text.slice(intentStart, intentEnd + "END_PAWL_INTENT".length);
131
+ const lines = rawBlock.split("\n");
132
+ const fields = {};
133
+ for (const l of lines) {
134
+ const match = l.match(/^(\w+):\s*(.+)/);
135
+ if (match && match[1] !== "PAWL_INTENT") {
136
+ fields[match[1]] = match[2].trim();
137
+ }
138
+ }
139
+ reasoningDecisions.push({
140
+ type: "pawl_intent",
141
+ rawBlock,
142
+ context: fields.context,
143
+ approach: fields.approach,
144
+ alternativesRejected: fields.alternatives_rejected,
145
+ constraints: fields.constraints,
146
+ messageIndex: currentMessageIndex
147
+ });
148
+ } else if (intentStart !== -1 && intentEnd === -1) {
149
+ process.stderr.write("Warning: PAWL_INTENT block without END marker \u2014 skipped\n");
150
+ }
120
151
  }
121
152
  }
122
153
  }
123
154
  }
124
- if (parsed.type === "user" && parsed.toolUseResult?.task?.id) {
125
- const tr = parsed.toolUseResult;
126
- const content = parsed.message?.content;
127
- const toolUseId = Array.isArray(content) ? content.find((b) => b.type === "tool_result")?.tool_use_id : void 0;
128
- const pending = toolUseId ? pendingCreates.get(toolUseId) : void 0;
129
- tasks.set(tr.task.id, {
130
- id: tr.task.id,
131
- subject: tr.task.subject || pending?.subject || "",
132
- description: pending?.description || "",
133
- status: "pending",
134
- filesAssociated: /* @__PURE__ */ new Set()
135
- });
136
- if (toolUseId) pendingCreates.delete(toolUseId);
155
+ if (parsed.type === "user") {
156
+ const userContent = parsed.message?.content;
157
+ const messageText = typeof userContent === "string" ? userContent : Array.isArray(userContent) ? userContent.filter((b) => b.type === "text").map((b) => b.text).join("\n") : "";
158
+ const DECIDE_PREFIX = /^#decide\s+/i;
159
+ if (DECIDE_PREFIX.test(messageText)) {
160
+ reasoningDecisions.push({
161
+ type: "decide_tag",
162
+ rawBlock: messageText,
163
+ text: messageText.replace(DECIDE_PREFIX, "").trim(),
164
+ messageIndex: currentMessageIndex
165
+ });
166
+ }
167
+ if (parsed.toolUseResult?.task?.id) {
168
+ const tr = parsed.toolUseResult;
169
+ const content = parsed.message?.content;
170
+ const toolUseId = Array.isArray(content) ? content.find((b) => b.type === "tool_result")?.tool_use_id : void 0;
171
+ const pending = toolUseId ? pendingCreates.get(toolUseId) : void 0;
172
+ tasks.set(tr.task.id, {
173
+ id: tr.task.id,
174
+ subject: tr.task.subject || pending?.subject || "",
175
+ description: pending?.description || "",
176
+ status: "pending",
177
+ filesAssociated: /* @__PURE__ */ new Set()
178
+ });
179
+ if (toolUseId) pendingCreates.delete(toolUseId);
180
+ }
137
181
  }
138
182
  }
139
183
  if (totalInputTokens === 0 && totalOutputTokens === 0) return null;
@@ -168,7 +212,8 @@ async function parseSessionFile(filePath) {
168
212
  description: t.description,
169
213
  status: t.status,
170
214
  filesAssociated: [...t.filesAssociated]
171
- }))
215
+ })),
216
+ reasoningDecisions
172
217
  };
173
218
  } catch {
174
219
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpawl/setup",
3
- "version": "1.4.1",
3
+ "version": "1.4.5",
4
4
  "type": "commonjs",
5
5
  "description": "One-shot setup for Pawl + Claude Code hooks",
6
6
  "bin": {