@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 +15 -1
- package/dist/index.js +231 -16
- package/dist/parse-cc-session.js +84 -39
- package/package.json +1 -1
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,
|
|
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
|
|
64
|
-
if (!(0, import_node_fs.existsSync)(
|
|
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
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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)(
|
|
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)(
|
|
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
|
-
|
|
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
|
-
|
|
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)) {
|
package/dist/parse-cc-session.js
CHANGED
|
@@ -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
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 (
|
|
119
|
-
|
|
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"
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
if (
|
|
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;
|