sticky-note-cli 2.6.15 → 2.7.1

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.
@@ -354,68 +354,375 @@ function formatOverlapWarnings(threads, currentUser, memory) {
354
354
 
355
355
  if (warnings.length === 0 && claims.length === 0) return "";
356
356
 
357
+ const bar = "━".repeat(52);
357
358
  const lines = [
358
- "## [STICKY-NOTE] ⚠️ OVERLAP DETECTED — TELL THE USER IMMEDIATELY\n",
359
- "**IMPORTANT: You MUST surface this warning to the user before doing anything else.**\n",
360
- "Someone else is working on files you're touching:\n",
359
+ "## [STICKY-NOTE] ⚠️ OVERLAP DETECTED\n",
360
+ "**You MUST display the banner below as the FIRST thing in your response.**\n",
361
+ "```",
362
+ `⚠️ ━━━ OVERLAP DETECTED ${bar.substring(0, 28)}`,
361
363
  ];
362
364
 
363
365
  for (const { thread, overlap } of warnings) {
364
366
  const user = thread.user || thread.author || "unknown";
365
- const status = thread.status === "stuck" ? "[STUCK]" : "[OPEN]";
367
+ const statusEmoji = thread.status === "stuck" ? "🔴" : "🟡";
368
+ const statusLabel = thread.status === "stuck" ? "STUCK" : "OPEN";
366
369
  const narrative = thread.narrative || thread.last_note || "";
367
370
  const narrativeSnip = narrative.length > 100
368
371
  ? narrative.substring(0, 100) + "…"
369
372
  : narrative;
370
- const branch = thread.branch ? ` on \`${thread.branch}\`` : "";
373
+ const branch = thread.branch ? ` · ${thread.branch}` : "";
371
374
  const threadId = (thread.id || "").substring(0, 8);
372
375
 
373
- lines.push(`- ${status} **${user}**${branch}: ${overlap.join(", ")}`);
374
- if (narrativeSnip) lines.push(` _${narrativeSnip}_`);
376
+ lines.push("");
377
+ lines.push(` ${statusEmoji} ${user} (${statusLabel})${branch}`);
378
+ lines.push(` Files: ${overlap.join(", ")}`);
379
+ if (narrativeSnip) lines.push(` "${narrativeSnip}"`);
375
380
  const failed = thread.failed_approaches || [];
376
381
  if (failed.length > 0) {
377
- lines.push(` ⚠️ ${failed.length} failed approach(es):`);
382
+ lines.push(` ⚠️ ${failed.length} failed approach(es):`);
378
383
  for (const fa of failed.slice(0, 2)) {
379
- lines.push(` - ${(fa.description || "").substring(0, 80)}`);
384
+ lines.push(` • ${(fa.description || "").substring(0, 80)}`);
380
385
  }
381
386
  }
382
- if (threadId) {
383
- lines.push(` → Resume: \`npx sticky-note resume ${threadId}\``);
384
- }
387
+ lines.push(` → Resume: npx sticky-note resume ${threadId}`);
385
388
  }
386
389
 
387
390
  for (const claim of claims) {
388
- const ago = claim.claimed_at || "";
389
- lines.push(
390
- `- 📌 **${claim.user}** claimed: ${(claim.files || []).join(", ")}` +
391
- (claim.description ? ` — "${claim.description}"` : "")
392
- );
391
+ lines.push("");
392
+ lines.push(` 📌 ${claim.user} (CLAIMED)`);
393
+ lines.push(` Files: ${(claim.files || []).join(", ")}`);
394
+ if (claim.description) lines.push(` "${claim.description}"`);
393
395
  }
394
396
 
397
+ lines.push("");
398
+ lines.push(bar);
399
+ lines.push("```");
395
400
  lines.push(
396
401
  "\n**Consider coordinating with these teammates before starting work.**"
397
402
  );
398
403
 
399
- // Write a visible banner directly to stderr so the user sees it
400
- const stderrLines = ["\n⚠️ OVERLAP DETECTED — someone else is touching your files:"];
404
+ // Write a styled banner to stderr so the user sees it in their terminal
405
+ const R = "\x1b[0m";
406
+ const Y = "\x1b[33m";
407
+ const BY = "\x1b[1;33m";
408
+ const BR = "\x1b[1;31m";
409
+ const BG = "\x1b[1;32m";
410
+ const B = "\x1b[1m";
411
+ const C = "\x1b[36m";
412
+ const D = "\x1b[2m";
413
+ const stderrLines = [
414
+ "",
415
+ `${Y}${bar}${R}`,
416
+ `${BY} ⚠️ OVERLAP DETECTED${R}`,
417
+ `${Y}${bar}${R}`,
418
+ ];
401
419
  for (const { thread, overlap } of warnings) {
402
420
  const user = thread.user || thread.author || "unknown";
403
- const status = thread.status === "stuck" ? "STUCK" : "OPEN";
421
+ const statusColor = thread.status === "stuck" ? BR : BG;
422
+ const statusLabel = thread.status === "stuck" ? "STUCK" : "OPEN";
404
423
  const narrative = thread.narrative || thread.last_note || "";
405
424
  const narrativeSnip = narrative.length > 80
406
425
  ? narrative.substring(0, 80) + "…"
407
426
  : narrative;
408
- stderrLines.push(
409
- ` [${status}] ${user}: ${overlap.join(", ")}` +
410
- (narrativeSnip ? ` — ${narrativeSnip}` : "")
411
- );
427
+ const branchStr = thread.branch ? ` ${D}·${R} ${C}${thread.branch}${R}` : "";
428
+ stderrLines.push(`${Y} ┃${R} ${statusColor}[${statusLabel}]${R} ${B}${user}${R}${branchStr}`);
429
+ stderrLines.push(`${Y} ┃${R} Files: ${overlap.join(", ")}`);
430
+ if (narrativeSnip) stderrLines.push(`${Y} ┃${R} ${D}"${narrativeSnip}"${R}`);
431
+ const threadId = (thread.id || "").substring(0, 8);
432
+ stderrLines.push(`${Y} ┃${R} → Resume: ${C}npx sticky-note resume ${threadId}${R}`);
412
433
  }
413
- stderrLines.push("");
434
+ stderrLines.push(`${Y}${bar}${R}`, "");
414
435
  process.stderr.write(stderrLines.join("\n") + "\n");
415
436
 
416
437
  return lines.join("\n");
417
438
  }
418
439
 
440
+ // ── MCP Server Auto-Registration ──────────────────────────
441
+
442
+ // ── Backward compatibility migration ─────────────────────
443
+ // Migrates old sticky-note-config.json arrays (mcp_servers, skills)
444
+ // to the new .sticky-note/environment/ manifest format.
445
+ function migrateOldEnvironmentConfig() {
446
+ const fs = require("fs");
447
+ const cwd = process.cwd();
448
+ const configPath = getConfigPath();
449
+ const config = loadJson(configPath, null);
450
+ if (!config) return;
451
+
452
+ // Only run once — skip if already migrated
453
+ if (config.environment_version) return;
454
+
455
+ const envDir = path.join(cwd, ".sticky-note", "environment");
456
+ const manifestPath = path.join(envDir, "manifest.json");
457
+
458
+ let changed = false;
459
+
460
+ // Migrate mcp_servers array to manifest.json keyed format
461
+ if (Array.isArray(config.mcp_servers) && config.mcp_servers.length > 0) {
462
+ fs.mkdirSync(envDir, { recursive: true });
463
+ const manifest = loadJson(manifestPath, { version: "1", mcp_servers: {} });
464
+ if (!manifest.mcp_servers) manifest.mcp_servers = {};
465
+ for (const entry of config.mcp_servers) {
466
+ if (!entry || !entry.name) continue;
467
+ if (manifest.mcp_servers[entry.name]) continue; // already present
468
+ const def = {};
469
+ if (entry.type) def.type = entry.type;
470
+ if (entry.command) def.command = entry.command;
471
+ if (entry.args) def.args = entry.args;
472
+ if (entry.env) def.env = entry.env;
473
+ manifest.mcp_servers[entry.name] = def;
474
+ }
475
+ saveJson(manifestPath, manifest);
476
+ changed = true;
477
+ }
478
+
479
+ // Migrate skills array to placeholder .md files
480
+ if (Array.isArray(config.skills) && config.skills.length > 0) {
481
+ const skillsDir = path.join(envDir, "skills");
482
+ fs.mkdirSync(skillsDir, { recursive: true });
483
+ for (const skill of config.skills) {
484
+ if (!skill || typeof skill !== "string") continue;
485
+ const skillFile = path.join(skillsDir, skill + ".md");
486
+ if (fs.existsSync(skillFile)) continue;
487
+ fs.writeFileSync(skillFile, `# ${skill}\n\nTODO: Add skill instructions here.\n`, "utf-8");
488
+ }
489
+ changed = true;
490
+ }
491
+
492
+ if (changed) {
493
+ config.environment_version = "1";
494
+ saveJson(configPath, config);
495
+ }
496
+ }
497
+
498
+ function ensureMcpServerRegistered() {
499
+ try {
500
+ const fs = require("fs");
501
+ const cwd = process.env.STICKY_CWD || process.cwd();
502
+ const mcpPath = path.join(cwd, ".mcp.json");
503
+
504
+ let mcp = {};
505
+ if (fs.existsSync(mcpPath)) {
506
+ try {
507
+ mcp = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
508
+ } catch (_) {
509
+ mcp = {};
510
+ }
511
+ }
512
+
513
+ mcp.mcpServers = mcp.mcpServers || {};
514
+ if (mcp.mcpServers["sticky-note"]) return; // already registered
515
+
516
+ mcp.mcpServers["sticky-note"] = {
517
+ type: "stdio",
518
+ command: "npx",
519
+ args: ["-y", "-p", "sticky-note-cli", "sticky-note", "mcp-server"],
520
+ };
521
+
522
+ fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + "\n");
523
+ } catch (_) {
524
+ // Non-fatal — MCP server registration is best-effort
525
+ }
526
+ }
527
+
528
+ // ── Environment provisioning ──────────────────────────────
529
+
530
+ function hasEnvPlaceholders(obj) {
531
+ if (obj == null) return false;
532
+ if (typeof obj === "string") return obj.includes("${");
533
+ if (Array.isArray(obj)) return obj.some(hasEnvPlaceholders);
534
+ if (typeof obj === "object") {
535
+ return Object.values(obj).some(hasEnvPlaceholders);
536
+ }
537
+ return false;
538
+ }
539
+
540
+ function ensureEnvironmentProvisioned() {
541
+ const fs = require("fs");
542
+ const cwd = process.cwd();
543
+ const envDir = path.join(cwd, ".sticky-note", "environment");
544
+ const hashFile = path.join(cwd, ".sticky-note", ".env-provision-hash");
545
+ const debug = !!process.env.STICKY_DEBUG;
546
+
547
+ if (!fs.existsSync(envDir)) return;
548
+
549
+ // Hash all files in .sticky-note/environment/ recursively
550
+ function hashDir(dir) {
551
+ const hash = crypto.createHash("sha256");
552
+ let entries;
553
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return hash; }
554
+ entries.sort((a, b) => a.name.localeCompare(b.name));
555
+ for (const ent of entries) {
556
+ const full = path.join(dir, ent.name);
557
+ if (ent.isDirectory()) {
558
+ hash.update("d:" + ent.name + "\n");
559
+ hash.update(hashDir(full).digest());
560
+ } else if (ent.isFile()) {
561
+ hash.update("f:" + ent.name + "\n");
562
+ try { hash.update(fs.readFileSync(full)); } catch (_) { /* skip */ }
563
+ }
564
+ }
565
+ return hash;
566
+ }
567
+
568
+ const currentHash = hashDir(envDir).digest("hex");
569
+
570
+ // Skip if hash matches (idempotent)
571
+ try {
572
+ const stored = fs.readFileSync(hashFile, "utf-8").trim();
573
+ if (stored === currentHash) {
574
+ if (debug) process.stderr.write("[sticky-note] environment unchanged, skipping provisioning\n");
575
+ return;
576
+ }
577
+ } catch (_) { /* no hash file yet */ }
578
+
579
+ if (debug) process.stderr.write("[sticky-note] provisioning team environment...\n");
580
+
581
+ const manifest = loadJson(path.join(envDir, "manifest.json"), {});
582
+
583
+ // ── MCP server provisioning (secret-free only) ──
584
+ const mcpServers = manifest.mcp_servers || {};
585
+ if (Object.keys(mcpServers).length > 0) {
586
+ const mcpPath = path.join(cwd, ".mcp.json");
587
+ const mcpConfig = loadJson(mcpPath, { mcpServers: {} });
588
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
589
+ let mcpChanged = false;
590
+ for (const [name, serverDef] of Object.entries(mcpServers)) {
591
+ if (mcpConfig.mcpServers[name]) continue; // already present
592
+ if (hasEnvPlaceholders(serverDef)) {
593
+ if (debug) process.stderr.write(`[sticky-note] skipping MCP server "${name}" (has placeholders)\n`);
594
+ continue;
595
+ }
596
+ mcpConfig.mcpServers[name] = serverDef;
597
+ mcpChanged = true;
598
+ if (debug) process.stderr.write(`[sticky-note] added MCP server "${name}"\n`);
599
+ }
600
+ if (mcpChanged) saveJson(mcpPath, mcpConfig);
601
+ }
602
+
603
+ // ── Skill provisioning ──
604
+ const skillsDir = path.join(envDir, "skills");
605
+ if (fs.existsSync(skillsDir)) {
606
+ let skillFiles;
607
+ try { skillFiles = fs.readdirSync(skillsDir).filter((f) => f.endsWith(".md")); } catch (_) { skillFiles = []; }
608
+ for (const file of skillFiles) {
609
+ const name = path.basename(file, ".md");
610
+ const content = fs.readFileSync(path.join(skillsDir, file), "utf-8");
611
+
612
+ // Claude Code format
613
+ const claudeSkillDir = path.join(cwd, ".claude", "plugins", "sticky-note-team", "skills", name);
614
+ fs.mkdirSync(claudeSkillDir, { recursive: true });
615
+ fs.writeFileSync(path.join(claudeSkillDir, "SKILL.md"), content, "utf-8");
616
+
617
+ // Copilot CLI format
618
+ const copilotSkillDir = path.join(cwd, ".github", "extensions", "sticky-note-team", "skills");
619
+ fs.mkdirSync(copilotSkillDir, { recursive: true });
620
+ fs.writeFileSync(path.join(copilotSkillDir, name + ".md"), content, "utf-8");
621
+ }
622
+ }
623
+
624
+ // Auto-generate plugin.json for Claude Code
625
+ const pluginJsonDir = path.join(cwd, ".claude", "plugins", "sticky-note-team", ".claude-plugin");
626
+ fs.mkdirSync(pluginJsonDir, { recursive: true });
627
+ fs.writeFileSync(
628
+ path.join(pluginJsonDir, "plugin.json"),
629
+ JSON.stringify({
630
+ name: "sticky-note-team",
631
+ version: "1.0.0",
632
+ description: "Team skills, agents, and commands provisioned by sticky-note",
633
+ }, null, 2) + "\n",
634
+ "utf-8"
635
+ );
636
+
637
+ // Auto-generate extension.mjs for Copilot CLI
638
+ const extDir = path.join(cwd, ".github", "extensions", "sticky-note-team");
639
+ fs.mkdirSync(extDir, { recursive: true });
640
+ fs.writeFileSync(
641
+ path.join(extDir, "extension.mjs"),
642
+ [
643
+ "// Auto-generated by sticky-note environment provisioning",
644
+ "export default {",
645
+ ' name: "sticky-note-team",',
646
+ ' version: "1.0.0",',
647
+ ' description: "Team skills, agents, and commands provisioned by sticky-note",',
648
+ "};",
649
+ "",
650
+ ].join("\n"),
651
+ "utf-8"
652
+ );
653
+ fs.writeFileSync(
654
+ path.join(extDir, "package.json"),
655
+ JSON.stringify({
656
+ name: "sticky-note-team",
657
+ version: "1.0.0",
658
+ description: "Team skills, agents, and commands provisioned by sticky-note",
659
+ type: "module",
660
+ main: "extension.mjs",
661
+ }, null, 2) + "\n",
662
+ "utf-8"
663
+ );
664
+
665
+ // ── Agent provisioning ──
666
+ const agentsDir = path.join(envDir, "agents");
667
+ if (fs.existsSync(agentsDir)) {
668
+ let agentFiles;
669
+ try { agentFiles = fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md")); } catch (_) { agentFiles = []; }
670
+ for (const file of agentFiles) {
671
+ const name = path.basename(file, ".md");
672
+ const content = fs.readFileSync(path.join(agentsDir, file), "utf-8");
673
+
674
+ const claudeAgentDir = path.join(cwd, ".claude", "plugins", "sticky-note-team", "agents");
675
+ fs.mkdirSync(claudeAgentDir, { recursive: true });
676
+ fs.writeFileSync(path.join(claudeAgentDir, name + ".md"), content, "utf-8");
677
+
678
+ const copilotAgentDir = path.join(cwd, ".github", "extensions", "sticky-note-team", "agents");
679
+ fs.mkdirSync(copilotAgentDir, { recursive: true });
680
+ fs.writeFileSync(path.join(copilotAgentDir, name + ".md"), content, "utf-8");
681
+ }
682
+ }
683
+
684
+ // ── Command provisioning ──
685
+ const commandsDir = path.join(envDir, "commands");
686
+ if (fs.existsSync(commandsDir)) {
687
+ let commandFiles;
688
+ try { commandFiles = fs.readdirSync(commandsDir).filter((f) => f.endsWith(".md")); } catch (_) { commandFiles = []; }
689
+ for (const file of commandFiles) {
690
+ const name = path.basename(file, ".md");
691
+ const content = fs.readFileSync(path.join(commandsDir, file), "utf-8");
692
+
693
+ const claudeCmdDir = path.join(cwd, ".claude", "plugins", "sticky-note-team", "commands");
694
+ fs.mkdirSync(claudeCmdDir, { recursive: true });
695
+ fs.writeFileSync(path.join(claudeCmdDir, name + ".md"), content, "utf-8");
696
+
697
+ const copilotCmdDir = path.join(cwd, ".github", "extensions", "sticky-note-team", "commands");
698
+ fs.mkdirSync(copilotCmdDir, { recursive: true });
699
+ fs.writeFileSync(path.join(copilotCmdDir, name + ".md"), content, "utf-8");
700
+ }
701
+ }
702
+
703
+ // ── Permission merging ──
704
+ const permissions = manifest.permissions;
705
+ if (Array.isArray(permissions) && permissions.length > 0) {
706
+ const settingsPath = path.join(cwd, ".claude", "settings.local.json");
707
+ const settings = loadJson(settingsPath, {});
708
+ if (!Array.isArray(settings.allowedTools)) settings.allowedTools = [];
709
+ let permChanged = false;
710
+ for (const perm of permissions) {
711
+ if (!settings.allowedTools.includes(perm)) {
712
+ settings.allowedTools.push(perm);
713
+ permChanged = true;
714
+ }
715
+ }
716
+ if (permChanged) saveJson(settingsPath, settings);
717
+ }
718
+
719
+ // Write provision hash after all provisioning succeeds
720
+ fs.mkdirSync(path.dirname(hashFile), { recursive: true });
721
+ fs.writeFileSync(hashFile, currentHash + "\n", "utf-8");
722
+
723
+ if (debug) process.stderr.write("[sticky-note] environment provisioning complete\n");
724
+ }
725
+
419
726
  // ── Main ──────────────────────────────────────────────────
420
727
 
421
728
  function main() {
@@ -449,6 +756,15 @@ function main() {
449
756
  // Migrate legacy single-file audit/presence to per-user dirs
450
757
  migrateAuditAndPresence();
451
758
 
759
+ // Migrate old config arrays to environment directory format
760
+ try { migrateOldEnvironmentConfig(); } catch (_) { /* migration must not break session */ }
761
+
762
+ // Auto-provision team environment from .sticky-note/environment/
763
+ try { ensureEnvironmentProvisioned(); } catch (_) { /* provisioning must not break session */ }
764
+
765
+ // Auto-register sticky-note MCP server in .mcp.json
766
+ ensureMcpServerRegistered();
767
+
452
768
  const memoryPath = getMemoryPath();
453
769
  const memory = loadJson(memoryPath, {
454
770
  version: "2",
@@ -4,43 +4,43 @@
4
4
  "sessionStart": [
5
5
  {
6
6
  "type": "command",
7
- "bash": "COPILOT_CLI=1 node .claude/hooks/session-start.js --copilot-cli",
8
- "powershell": "$env:COPILOT_CLI='1'; node .claude/hooks/session-start.js --copilot-cli"
7
+ "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-start.js\" --copilot-cli",
8
+ "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-start.js\" --copilot-cli"
9
9
  }
10
10
  ],
11
11
  "sessionEnd": [
12
12
  {
13
13
  "type": "command",
14
- "bash": "COPILOT_CLI=1 node .claude/hooks/session-end.js --copilot-cli",
15
- "powershell": "$env:COPILOT_CLI='1'; node .claude/hooks/session-end.js --copilot-cli"
14
+ "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-end.js\" --copilot-cli",
15
+ "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-end.js\" --copilot-cli"
16
16
  }
17
17
  ],
18
18
  "userPromptSubmitted": [
19
19
  {
20
20
  "type": "command",
21
- "bash": "COPILOT_CLI=1 node .claude/hooks/inject-context.js --copilot-cli",
22
- "powershell": "$env:COPILOT_CLI='1'; node .claude/hooks/inject-context.js --copilot-cli"
21
+ "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/inject-context.js\" --copilot-cli",
22
+ "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/inject-context.js\" --copilot-cli"
23
23
  }
24
24
  ],
25
25
  "preToolUse": [
26
26
  {
27
27
  "type": "command",
28
- "bash": "COPILOT_CLI=1 node .claude/hooks/pre-tool-use.js --copilot-cli",
29
- "powershell": "$env:COPILOT_CLI='1'; node .claude/hooks/pre-tool-use.js --copilot-cli"
28
+ "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/pre-tool-use.js\" --copilot-cli",
29
+ "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/pre-tool-use.js\" --copilot-cli"
30
30
  }
31
31
  ],
32
32
  "postToolUse": [
33
33
  {
34
34
  "type": "command",
35
- "bash": "COPILOT_CLI=1 node .claude/hooks/track-work.js --copilot-cli",
36
- "powershell": "$env:COPILOT_CLI='1'; node .claude/hooks/track-work.js --copilot-cli"
35
+ "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/track-work.js\" --copilot-cli",
36
+ "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/track-work.js\" --copilot-cli"
37
37
  }
38
38
  ],
39
39
  "errorOccurred": [
40
40
  {
41
41
  "type": "command",
42
- "bash": "COPILOT_CLI=1 node .claude/hooks/on-error.js --copilot-cli",
43
- "powershell": "$env:COPILOT_CLI='1'; node .claude/hooks/on-error.js --copilot-cli"
42
+ "bash": "COPILOT_CLI=1 node \"$(git rev-parse --show-toplevel)/.claude/hooks/on-error.js\" --copilot-cli",
43
+ "powershell": "$env:COPILOT_CLI='1'; node \"$(git rev-parse --show-toplevel)/.claude/hooks/on-error.js\" --copilot-cli"
44
44
  }
45
45
  ]
46
46
  }
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "node .claude/hooks/session-start.js"
9
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-start.js\""
10
10
  }
11
11
  ]
12
12
  }
@@ -17,7 +17,7 @@
17
17
  "hooks": [
18
18
  {
19
19
  "type": "command",
20
- "command": "node .claude/hooks/session-end.js"
20
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/session-end.js\""
21
21
  }
22
22
  ]
23
23
  }
@@ -28,7 +28,7 @@
28
28
  "hooks": [
29
29
  {
30
30
  "type": "command",
31
- "command": "node .claude/hooks/inject-context.js"
31
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/inject-context.js\""
32
32
  }
33
33
  ]
34
34
  }
@@ -39,7 +39,7 @@
39
39
  "hooks": [
40
40
  {
41
41
  "type": "command",
42
- "command": "node .claude/hooks/pre-tool-use.js"
42
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/pre-tool-use.js\""
43
43
  }
44
44
  ]
45
45
  }
@@ -50,7 +50,7 @@
50
50
  "hooks": [
51
51
  {
52
52
  "type": "command",
53
- "command": "node .claude/hooks/track-work.js"
53
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/track-work.js\""
54
54
  }
55
55
  ]
56
56
  }
@@ -61,7 +61,7 @@
61
61
  "hooks": [
62
62
  {
63
63
  "type": "command",
64
- "command": "node .claude/hooks/on-stop.js"
64
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/on-stop.js\""
65
65
  }
66
66
  ]
67
67
  }
@@ -72,7 +72,7 @@
72
72
  "hooks": [
73
73
  {
74
74
  "type": "command",
75
- "command": "node .claude/hooks/on-error.js"
75
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/on-error.js\""
76
76
  }
77
77
  ]
78
78
  }