panopticon-cli 0.3.2 → 0.3.3

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
@@ -340,6 +340,37 @@ Cloister manages specialized agents that handle specific phases of the developme
340
340
  | **review-agent** | Code review before merge | After tests pass (manual trigger) |
341
341
  | **merge-agent** | Handles git merge and conflict resolution | "Approve & Merge" button |
342
342
 
343
+ #### Merge Agent Workflow
344
+
345
+ The merge-agent is a specialist that handles the entire PR merge process:
346
+
347
+ 1. **Pull latest main** - Ensures local main is up-to-date
348
+ 2. **Merge main into feature branch** - Brings in any changes from main
349
+ 3. **Resolve conflicts** - Uses AI to resolve merge conflicts intelligently
350
+ 4. **Run tests** - Verifies the merge didn't break anything
351
+ 5. **Push changes** - Pushes the resolved merge
352
+ 6. **Create/Update PR** - Creates a PR if one doesn't exist
353
+ 7. **Merge PR** - Merges the PR using `gh pr merge`
354
+
355
+ **Triggering merge-agent:**
356
+
357
+ ```bash
358
+ # Via dashboard - click "Approve & Merge" on an issue card
359
+
360
+ # Via CLI
361
+ pan specialists wake merge-agent --issue MIN-123
362
+ ```
363
+
364
+ The merge-agent uses a specialized prompt template that instructs it to:
365
+ - Never force-push
366
+ - Always run tests before merging
367
+ - Document conflict resolution decisions
368
+ - Update the issue status on success
369
+
370
+ #### Specialist Auto-Initialization
371
+
372
+ When Cloister starts, it automatically initializes specialists that don't exist yet. This ensures the test-agent, review-agent, and merge-agent are ready to receive wake signals without manual setup.
373
+
343
374
  ### Automatic Handoffs
344
375
 
345
376
  Cloister detects situations that require intervention:
@@ -349,8 +380,55 @@ Cloister detects situations that require intervention:
349
380
  | **stuck_escalation** | No activity for 30+ minutes | Escalate to more capable model |
350
381
  | **complexity_upgrade** | Task complexity exceeds model capability | Route to Opus |
351
382
  | **implementation_complete** | Agent signals work is done | Wake test-agent |
383
+ | **test_failure** | Tests fail repeatedly | Escalate model or request help |
384
+ | **planning_complete** | Planning session finishes | Transition to implementation |
352
385
  | **merge_requested** | User clicks "Approve & Merge" | Wake merge-agent |
353
386
 
387
+ ### Handoff Methods
388
+
389
+ Cloister supports two handoff methods, automatically selected based on agent type:
390
+
391
+ | Method | When Used | How It Works |
392
+ |--------|-----------|--------------|
393
+ | **Kill & Spawn** | General agents (agent-min-123, etc.) | 1. Captures full context (STATE.md, beads, git state)<br>2. Kills tmux session<br>3. Spawns new agent with handoff prompt<br>4. New agent continues work with preserved context |
394
+ | **Specialist Wake** | Permanent specialists (merge-agent, test-agent) | 1. Captures handoff context<br>2. Sends wake message to existing session<br>3. Specialist resumes with context injection |
395
+
396
+ **Kill & Spawn** is used for temporary agents that work on specific issues. It creates a clean handoff by:
397
+ - Capturing the agent's current understanding (from STATE.md)
398
+ - Preserving beads task progress and open items
399
+ - Including relevant git diff and file context
400
+ - Building a comprehensive handoff prompt for the new model
401
+
402
+ **Specialist Wake** is used for permanent specialists that persist across multiple issues. It avoids the overhead of killing/respawning by injecting context into the existing session.
403
+
404
+ ### Handoff Context Capture
405
+
406
+ When a handoff occurs, Cloister captures:
407
+
408
+ ```json
409
+ {
410
+ "agentId": "agent-min-123",
411
+ "issueId": "MIN-123",
412
+ "currentModel": "sonnet",
413
+ "targetModel": "opus",
414
+ "reason": "stuck_escalation",
415
+ "handoffCount": 1,
416
+ "state": {
417
+ "phase": "implementation",
418
+ "complexity": "complex",
419
+ "lastActivity": "2024-01-22T10:30:00-08:00"
420
+ },
421
+ "beadsTasks": [...],
422
+ "gitContext": {
423
+ "branch": "feature/min-123",
424
+ "uncommittedChanges": ["src/auth.ts", "src/tests/auth.test.ts"],
425
+ "recentCommits": [...]
426
+ }
427
+ }
428
+ ```
429
+
430
+ Handoff prompts are saved to `~/.panopticon/agents/{agent-id}/handoffs/` for debugging.
431
+
354
432
  ### Heartbeat Monitoring
355
433
 
356
434
  Agents send heartbeats via Claude Code hooks. Cloister tracks:
@@ -373,6 +451,41 @@ Heartbeat files are stored in `~/.panopticon/heartbeats/`:
373
451
  }
374
452
  ```
375
453
 
454
+ ### Heartbeat Hook Installation
455
+
456
+ The heartbeat hook is automatically synced to `~/.panopticon/bin/heartbeat-hook` via `pan sync`. It's also installed automatically when you install or upgrade Panopticon via npm.
457
+
458
+ **Manual installation:**
459
+ ```bash
460
+ pan sync # Syncs all skills, agents, AND hooks
461
+ ```
462
+
463
+ **Hook configuration in `~/.claude/settings.json`:**
464
+ ```json
465
+ {
466
+ "hooks": {
467
+ "PostToolUse": [
468
+ {
469
+ "matcher": "*",
470
+ "hooks": [
471
+ {
472
+ "type": "command",
473
+ "command": "~/.panopticon/bin/heartbeat-hook"
474
+ }
475
+ ]
476
+ }
477
+ ]
478
+ }
479
+ }
480
+ ```
481
+
482
+ **Hook resilience:** The heartbeat hook is designed to fail silently if:
483
+ - The heartbeats directory doesn't exist
484
+ - Write permissions are missing
485
+ - The hook script has errors
486
+
487
+ This prevents hook failures from interrupting agent work.
488
+
376
489
  ### Configuration
377
490
 
378
491
  Cloister configuration lives in `~/.panopticon/cloister/config.json`:
@@ -398,6 +511,77 @@ Cloister configuration lives in `~/.panopticon/cloister/config.json`:
398
511
 
399
512
  ---
400
513
 
514
+ ## Model Routing & Complexity Detection
515
+
516
+ Cloister automatically routes tasks to the appropriate model based on detected complexity, optimizing for cost while ensuring quality.
517
+
518
+ ### Complexity Levels
519
+
520
+ | Level | Model | Use Case |
521
+ |-------|-------|----------|
522
+ | **trivial** | Haiku | Typos, comments, documentation updates |
523
+ | **simple** | Haiku | Small fixes, test additions, minor changes |
524
+ | **medium** | Sonnet | Features, components, integrations |
525
+ | **complex** | Sonnet/Opus | Refactors, migrations, redesigns |
526
+ | **expert** | Opus | Architecture, security, performance optimization |
527
+
528
+ ### Complexity Detection Signals
529
+
530
+ Complexity is detected from multiple signals (in priority order):
531
+
532
+ 1. **Explicit field** - Task has a `complexity` field set (e.g., in beads)
533
+ 2. **Labels/tags** - Issue labels like `architecture`, `security`, `refactor`
534
+ 3. **Keywords** - Title/description contains keywords like "migration", "overhaul"
535
+ 4. **File count** - Number of files changed (>20 files = complex)
536
+ 5. **Time estimate** - If estimate exceeds thresholds
537
+
538
+ **Keyword patterns:**
539
+ ```javascript
540
+ {
541
+ trivial: ['typo', 'rename', 'comment', 'documentation', 'readme'],
542
+ simple: ['add comment', 'update docs', 'fix typo', 'small fix'],
543
+ medium: ['feature', 'endpoint', 'component', 'service'],
544
+ complex: ['refactor', 'migration', 'redesign', 'overhaul'],
545
+ expert: ['architecture', 'security', 'performance optimization']
546
+ }
547
+ ```
548
+
549
+ ### Configuring Model Routing
550
+
551
+ Edit `~/.panopticon/cloister/config.json`:
552
+
553
+ ```json
554
+ {
555
+ "model_selection": {
556
+ "default_model": "sonnet",
557
+ "complexity_routing": {
558
+ "trivial": "haiku",
559
+ "simple": "haiku",
560
+ "medium": "sonnet",
561
+ "complex": "sonnet",
562
+ "expert": "opus"
563
+ }
564
+ }
565
+ }
566
+ ```
567
+
568
+ ### Cost Optimization
569
+
570
+ Model routing helps optimize costs:
571
+
572
+ | Model | Relative Cost | Best For |
573
+ |-------|---------------|----------|
574
+ | Haiku | 1x (cheapest) | Simple tasks, bulk operations |
575
+ | Sonnet | 3x | Most development work |
576
+ | Opus | 15x | Complex architecture, critical fixes |
577
+
578
+ A typical agent run might:
579
+ 1. Start on Haiku for initial exploration
580
+ 2. Escalate to Sonnet for implementation
581
+ 3. Escalate to Opus only if stuck or complexity detected
582
+
583
+ ---
584
+
401
585
  ## Multi-Project Support
402
586
 
403
587
  Panopticon supports managing multiple projects with intelligent issue routing.
@@ -462,9 +646,22 @@ pan sync --dry-run # Preview what will be synced
462
646
  pan doctor # Check system health
463
647
  pan skills # List available skills
464
648
  pan status # Show running agents
649
+ pan up # Start dashboard (Docker or minimal)
650
+ pan down # Stop dashboard and services
465
651
  ```
466
652
 
467
- > **Note:** `pan sync` now automatically syncs heartbeat hooks to `~/.panopticon/bin/`. This happens automatically on `npm install/upgrade` as well.
653
+ #### What `pan sync` Does
654
+
655
+ `pan sync` synchronizes Panopticon assets to all supported AI tools:
656
+
657
+ | Asset Type | Source | Destinations |
658
+ |------------|--------|--------------|
659
+ | **Skills** | `~/.panopticon/skills/` | `~/.claude/skills/`, `~/.codex/skills/`, `~/.gemini/skills/` |
660
+ | **Agents** | `~/.panopticon/agents/*.md` | `~/.claude/agents/` |
661
+ | **Commands** | `~/.panopticon/commands/` | `~/.claude/commands/` |
662
+ | **Hooks** | `src/hooks/` (in package) | `~/.panopticon/bin/` |
663
+
664
+ **Automatic sync:** Hooks are also synced automatically when you install or upgrade Panopticon via npm (`postinstall` hook).
468
665
 
469
666
  ### Agent Management
470
667
 
@@ -1029,6 +1226,7 @@ This ensures every Panopticon-managed project has a well-defined canonical PRD t
1029
1226
  hook.json # GUPP work queue
1030
1227
  cv.json # Work history
1031
1228
  mail/ # Incoming messages
1229
+ handoffs/ # Handoff prompts (for debugging)
1032
1230
 
1033
1231
  cloister/ # Cloister AI lifecycle manager
1034
1232
  config.json # Cloister settings
@@ -1038,6 +1236,9 @@ This ensures every Panopticon-managed project has a well-defined canonical PRD t
1038
1236
  heartbeats/ # Real-time agent activity
1039
1237
  agent-min-123.json # Last heartbeat from agent
1040
1238
 
1239
+ logs/ # Log files
1240
+ handoffs.jsonl # All handoff events (for analytics)
1241
+
1041
1242
  costs/ # Raw cost logs (JSONL)
1042
1243
  backups/ # Sync backups
1043
1244
  traefik/ # Traefik reverse proxy config
@@ -1045,6 +1246,36 @@ This ensures every Panopticon-managed project has a well-defined canonical PRD t
1045
1246
  certs/ # TLS certificates
1046
1247
  ```
1047
1248
 
1249
+ ### Agent State Management
1250
+
1251
+ Each agent's state is tracked in `~/.panopticon/agents/{agent-id}/state.json`:
1252
+
1253
+ ```json
1254
+ {
1255
+ "id": "agent-min-123",
1256
+ "issueId": "MIN-123",
1257
+ "workspace": "/home/user/projects/myapp/workspaces/feature-min-123",
1258
+ "branch": "feature/min-123",
1259
+ "phase": "implementation",
1260
+ "model": "sonnet",
1261
+ "complexity": "medium",
1262
+ "handoffCount": 0,
1263
+ "sessionId": "abc123",
1264
+ "createdAt": "2024-01-22T10:00:00-08:00",
1265
+ "updatedAt": "2024-01-22T10:30:00-08:00"
1266
+ }
1267
+ ```
1268
+
1269
+ | Field | Description |
1270
+ |-------|-------------|
1271
+ | `phase` | Current work phase: `planning`, `implementation`, `testing`, `review`, `merging` |
1272
+ | `model` | Current model: `haiku`, `sonnet`, `opus` |
1273
+ | `complexity` | Detected complexity: `trivial`, `simple`, `medium`, `complex`, `expert` |
1274
+ | `handoffCount` | Number of times the agent has been handed off to a different model |
1275
+ | `sessionId` | Claude Code session ID (for resuming after handoff) |
1276
+
1277
+ **State Cleanup:** When an agent is killed or aborted (`pan work kill`), Panopticon automatically cleans up its state files to prevent stale data from affecting future runs.
1278
+
1048
1279
  ## Health Monitoring (Deacon Pattern)
1049
1280
 
1050
1281
  Panopticon implements the Deacon pattern for stuck agent detection:
@@ -0,0 +1,103 @@
1
+ // src/lib/paths.ts
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { dirname } from "path";
6
+ var PANOPTICON_HOME = join(homedir(), ".panopticon");
7
+ var CONFIG_DIR = PANOPTICON_HOME;
8
+ var SKILLS_DIR = join(PANOPTICON_HOME, "skills");
9
+ var COMMANDS_DIR = join(PANOPTICON_HOME, "commands");
10
+ var AGENTS_DIR = join(PANOPTICON_HOME, "agents");
11
+ var BIN_DIR = join(PANOPTICON_HOME, "bin");
12
+ var BACKUPS_DIR = join(PANOPTICON_HOME, "backups");
13
+ var COSTS_DIR = join(PANOPTICON_HOME, "costs");
14
+ var HEARTBEATS_DIR = join(PANOPTICON_HOME, "heartbeats");
15
+ var TRAEFIK_DIR = join(PANOPTICON_HOME, "traefik");
16
+ var TRAEFIK_DYNAMIC_DIR = join(TRAEFIK_DIR, "dynamic");
17
+ var TRAEFIK_CERTS_DIR = join(TRAEFIK_DIR, "certs");
18
+ var CERTS_DIR = join(PANOPTICON_HOME, "certs");
19
+ var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
20
+ var CLAUDE_DIR = join(homedir(), ".claude");
21
+ var CODEX_DIR = join(homedir(), ".codex");
22
+ var CURSOR_DIR = join(homedir(), ".cursor");
23
+ var GEMINI_DIR = join(homedir(), ".gemini");
24
+ var SYNC_TARGETS = {
25
+ claude: {
26
+ skills: join(CLAUDE_DIR, "skills"),
27
+ commands: join(CLAUDE_DIR, "commands"),
28
+ agents: join(CLAUDE_DIR, "agents")
29
+ },
30
+ codex: {
31
+ skills: join(CODEX_DIR, "skills"),
32
+ commands: join(CODEX_DIR, "commands"),
33
+ agents: join(CODEX_DIR, "agents")
34
+ },
35
+ cursor: {
36
+ skills: join(CURSOR_DIR, "skills"),
37
+ commands: join(CURSOR_DIR, "commands"),
38
+ agents: join(CURSOR_DIR, "agents")
39
+ },
40
+ gemini: {
41
+ skills: join(GEMINI_DIR, "skills"),
42
+ commands: join(GEMINI_DIR, "commands"),
43
+ agents: join(GEMINI_DIR, "agents")
44
+ }
45
+ };
46
+ var TEMPLATES_DIR = join(PANOPTICON_HOME, "templates");
47
+ var CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, "claude-md", "sections");
48
+ var currentFile = fileURLToPath(import.meta.url);
49
+ var currentDir = dirname(currentFile);
50
+ var packageRoot;
51
+ if (currentDir.includes("/src/")) {
52
+ packageRoot = dirname(dirname(currentDir));
53
+ } else {
54
+ packageRoot = currentDir.endsWith("/lib") ? dirname(dirname(currentDir)) : dirname(currentDir);
55
+ }
56
+ var SOURCE_TEMPLATES_DIR = join(packageRoot, "templates");
57
+ var SOURCE_TRAEFIK_TEMPLATES = join(SOURCE_TEMPLATES_DIR, "traefik");
58
+ var SOURCE_SCRIPTS_DIR = join(packageRoot, "scripts");
59
+ var INIT_DIRS = [
60
+ PANOPTICON_HOME,
61
+ SKILLS_DIR,
62
+ COMMANDS_DIR,
63
+ AGENTS_DIR,
64
+ BIN_DIR,
65
+ BACKUPS_DIR,
66
+ COSTS_DIR,
67
+ HEARTBEATS_DIR,
68
+ TEMPLATES_DIR,
69
+ CLAUDE_MD_TEMPLATES,
70
+ CERTS_DIR,
71
+ TRAEFIK_DIR,
72
+ TRAEFIK_DYNAMIC_DIR,
73
+ TRAEFIK_CERTS_DIR
74
+ ];
75
+
76
+ export {
77
+ PANOPTICON_HOME,
78
+ CONFIG_DIR,
79
+ SKILLS_DIR,
80
+ COMMANDS_DIR,
81
+ AGENTS_DIR,
82
+ BIN_DIR,
83
+ BACKUPS_DIR,
84
+ COSTS_DIR,
85
+ HEARTBEATS_DIR,
86
+ TRAEFIK_DIR,
87
+ TRAEFIK_DYNAMIC_DIR,
88
+ TRAEFIK_CERTS_DIR,
89
+ CERTS_DIR,
90
+ CONFIG_FILE,
91
+ CLAUDE_DIR,
92
+ CODEX_DIR,
93
+ CURSOR_DIR,
94
+ GEMINI_DIR,
95
+ SYNC_TARGETS,
96
+ TEMPLATES_DIR,
97
+ CLAUDE_MD_TEMPLATES,
98
+ SOURCE_TEMPLATES_DIR,
99
+ SOURCE_TRAEFIK_TEMPLATES,
100
+ SOURCE_SCRIPTS_DIR,
101
+ INIT_DIRS
102
+ };
103
+ //# sourceMappingURL=chunk-3SI436SZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/paths.ts"],"sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\n\n// Panopticon home directory\nexport const PANOPTICON_HOME = join(homedir(), '.panopticon');\n\n// Subdirectories\nexport const CONFIG_DIR = PANOPTICON_HOME;\nexport const SKILLS_DIR = join(PANOPTICON_HOME, 'skills');\nexport const COMMANDS_DIR = join(PANOPTICON_HOME, 'commands');\nexport const AGENTS_DIR = join(PANOPTICON_HOME, 'agents');\nexport const BIN_DIR = join(PANOPTICON_HOME, 'bin');\nexport const BACKUPS_DIR = join(PANOPTICON_HOME, 'backups');\nexport const COSTS_DIR = join(PANOPTICON_HOME, 'costs');\nexport const HEARTBEATS_DIR = join(PANOPTICON_HOME, 'heartbeats');\n\n// Traefik directories\nexport const TRAEFIK_DIR = join(PANOPTICON_HOME, 'traefik');\nexport const TRAEFIK_DYNAMIC_DIR = join(TRAEFIK_DIR, 'dynamic');\nexport const TRAEFIK_CERTS_DIR = join(TRAEFIK_DIR, 'certs');\n\n// Legacy certs directory (for backwards compatibility)\nexport const CERTS_DIR = join(PANOPTICON_HOME, 'certs');\n\n// Config files\nexport const CONFIG_FILE = join(CONFIG_DIR, 'config.toml');\n\n// AI tool directories\nexport const CLAUDE_DIR = join(homedir(), '.claude');\nexport const CODEX_DIR = join(homedir(), '.codex');\nexport const CURSOR_DIR = join(homedir(), '.cursor');\nexport const GEMINI_DIR = join(homedir(), '.gemini');\n\n// Target sync locations\nexport const SYNC_TARGETS = {\n claude: {\n skills: join(CLAUDE_DIR, 'skills'),\n commands: join(CLAUDE_DIR, 'commands'),\n agents: join(CLAUDE_DIR, 'agents'),\n },\n codex: {\n skills: join(CODEX_DIR, 'skills'),\n commands: join(CODEX_DIR, 'commands'),\n agents: join(CODEX_DIR, 'agents'),\n },\n cursor: {\n skills: join(CURSOR_DIR, 'skills'),\n commands: join(CURSOR_DIR, 'commands'),\n agents: join(CURSOR_DIR, 'agents'),\n },\n gemini: {\n skills: join(GEMINI_DIR, 'skills'),\n commands: join(GEMINI_DIR, 'commands'),\n agents: join(GEMINI_DIR, 'agents'),\n },\n} as const;\n\nexport type Runtime = keyof typeof SYNC_TARGETS;\n\n// Templates directory (in user's ~/.panopticon)\nexport const TEMPLATES_DIR = join(PANOPTICON_HOME, 'templates');\nexport const CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, 'claude-md', 'sections');\n\n// Source templates directory (bundled with the package)\n// This is resolved at runtime from the package root\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst currentFile = fileURLToPath(import.meta.url);\nconst currentDir = dirname(currentFile);\n\n// Handle both development (src/lib/) and production (dist/) modes\n// In dev: /path/to/panopticon/src/lib/paths.ts -> /path/to/panopticon\n// In prod: /path/to/panopticon/dist/lib/paths.js -> /path/to/panopticon\nlet packageRoot: string;\nif (currentDir.includes('/src/')) {\n // Development mode - go up from src/lib to package root\n packageRoot = dirname(dirname(currentDir));\n} else {\n // Production mode - go up from dist (or dist/lib) to package root\n packageRoot = currentDir.endsWith('/lib')\n ? dirname(dirname(currentDir))\n : dirname(currentDir);\n}\n\nexport const SOURCE_TEMPLATES_DIR = join(packageRoot, 'templates');\nexport const SOURCE_TRAEFIK_TEMPLATES = join(SOURCE_TEMPLATES_DIR, 'traefik');\nexport const SOURCE_SCRIPTS_DIR = join(packageRoot, 'scripts');\n\n// All directories to create on init\nexport const INIT_DIRS = [\n PANOPTICON_HOME,\n SKILLS_DIR,\n COMMANDS_DIR,\n AGENTS_DIR,\n BIN_DIR,\n BACKUPS_DIR,\n COSTS_DIR,\n HEARTBEATS_DIR,\n TEMPLATES_DIR,\n CLAUDE_MD_TEMPLATES,\n CERTS_DIR,\n TRAEFIK_DIR,\n TRAEFIK_DYNAMIC_DIR,\n TRAEFIK_CERTS_DIR,\n];\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,YAAY;AAgErB,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AA9DjB,IAAM,kBAAkB,KAAK,QAAQ,GAAG,aAAa;AAGrD,IAAM,aAAa;AACnB,IAAM,aAAa,KAAK,iBAAiB,QAAQ;AACjD,IAAM,eAAe,KAAK,iBAAiB,UAAU;AACrD,IAAM,aAAa,KAAK,iBAAiB,QAAQ;AACjD,IAAM,UAAU,KAAK,iBAAiB,KAAK;AAC3C,IAAM,cAAc,KAAK,iBAAiB,SAAS;AACnD,IAAM,YAAY,KAAK,iBAAiB,OAAO;AAC/C,IAAM,iBAAiB,KAAK,iBAAiB,YAAY;AAGzD,IAAM,cAAc,KAAK,iBAAiB,SAAS;AACnD,IAAM,sBAAsB,KAAK,aAAa,SAAS;AACvD,IAAM,oBAAoB,KAAK,aAAa,OAAO;AAGnD,IAAM,YAAY,KAAK,iBAAiB,OAAO;AAG/C,IAAM,cAAc,KAAK,YAAY,aAAa;AAGlD,IAAM,aAAa,KAAK,QAAQ,GAAG,SAAS;AAC5C,IAAM,YAAY,KAAK,QAAQ,GAAG,QAAQ;AAC1C,IAAM,aAAa,KAAK,QAAQ,GAAG,SAAS;AAC5C,IAAM,aAAa,KAAK,QAAQ,GAAG,SAAS;AAG5C,IAAM,eAAe;AAAA,EAC1B,QAAQ;AAAA,IACN,QAAQ,KAAK,YAAY,QAAQ;AAAA,IACjC,UAAU,KAAK,YAAY,UAAU;AAAA,IACrC,QAAQ,KAAK,YAAY,QAAQ;AAAA,EACnC;AAAA,EACA,OAAO;AAAA,IACL,QAAQ,KAAK,WAAW,QAAQ;AAAA,IAChC,UAAU,KAAK,WAAW,UAAU;AAAA,IACpC,QAAQ,KAAK,WAAW,QAAQ;AAAA,EAClC;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ,KAAK,YAAY,QAAQ;AAAA,IACjC,UAAU,KAAK,YAAY,UAAU;AAAA,IACrC,QAAQ,KAAK,YAAY,QAAQ;AAAA,EACnC;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ,KAAK,YAAY,QAAQ;AAAA,IACjC,UAAU,KAAK,YAAY,UAAU;AAAA,IACrC,QAAQ,KAAK,YAAY,QAAQ;AAAA,EACnC;AACF;AAKO,IAAM,gBAAgB,KAAK,iBAAiB,WAAW;AACvD,IAAM,sBAAsB,KAAK,eAAe,aAAa,UAAU;AAO9E,IAAM,cAAc,cAAc,YAAY,GAAG;AACjD,IAAM,aAAa,QAAQ,WAAW;AAKtC,IAAI;AACJ,IAAI,WAAW,SAAS,OAAO,GAAG;AAEhC,gBAAc,QAAQ,QAAQ,UAAU,CAAC;AAC3C,OAAO;AAEL,gBAAc,WAAW,SAAS,MAAM,IACpC,QAAQ,QAAQ,UAAU,CAAC,IAC3B,QAAQ,UAAU;AACxB;AAEO,IAAM,uBAAuB,KAAK,aAAa,WAAW;AAC1D,IAAM,2BAA2B,KAAK,sBAAsB,SAAS;AACrE,IAAM,qBAAqB,KAAK,aAAa,SAAS;AAGtD,IAAM,YAAY;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
@@ -1,12 +1,8 @@
1
1
  import {
2
- PANOPTICON_HOME,
3
- init_esm_shims,
4
- init_paths
5
- } from "./chunk-SG7O6I7R.js";
2
+ PANOPTICON_HOME
3
+ } from "./chunk-3SI436SZ.js";
6
4
 
7
5
  // src/lib/projects.ts
8
- init_esm_shims();
9
- init_paths();
10
6
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
11
7
  import { join } from "path";
12
8
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
@@ -174,4 +170,4 @@ export {
174
170
  createDefaultProjectsConfig,
175
171
  initializeProjectsConfig
176
172
  };
177
- //# sourceMappingURL=chunk-PSJRCUOA.js.map
173
+ //# sourceMappingURL=chunk-IVAFJ6DS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/projects.ts"],"sourcesContent":["/**\n * Project Registry - Multi-project support for Panopticon\n *\n * Maps Linear team prefixes and labels to project paths for workspace creation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { PANOPTICON_HOME } from './paths.js';\n\nexport const PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, 'projects.yaml');\n\n/**\n * Issue routing rule - routes issues with certain labels to specific paths\n */\nexport interface IssueRoutingRule {\n labels?: string[];\n default?: boolean;\n path: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n issue_routing?: IssueRoutingRule[];\n}\n\n/**\n * Full projects configuration file\n */\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Resolved project info for workspace creation\n */\nexport interface ResolvedProject {\n projectKey: string;\n projectName: string;\n projectPath: string;\n linearTeam?: string;\n}\n\n/**\n * Load projects configuration from ~/.panopticon/projects.yaml\n */\nexport function loadProjectsConfig(): ProjectsConfig {\n if (!existsSync(PROJECTS_CONFIG_FILE)) {\n return { projects: {} };\n }\n\n try {\n const content = readFileSync(PROJECTS_CONFIG_FILE, 'utf-8');\n const config = parseYaml(content) as ProjectsConfig;\n return config || { projects: {} };\n } catch (error: any) {\n console.error(`Failed to parse projects.yaml: ${error.message}`);\n return { projects: {} };\n }\n}\n\n/**\n * Save projects configuration\n */\nexport function saveProjectsConfig(config: ProjectsConfig): void {\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const yaml = stringifyYaml(config, { indent: 2 });\n writeFileSync(PROJECTS_CONFIG_FILE, yaml, 'utf-8');\n}\n\n/**\n * Get a list of all registered projects\n */\nexport function listProjects(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects).map(([key, projectConfig]) => ({\n key,\n config: projectConfig,\n }));\n}\n\n/**\n * Add or update a project in the registry\n */\nexport function registerProject(key: string, projectConfig: ProjectConfig): void {\n const config = loadProjectsConfig();\n config.projects[key] = projectConfig;\n saveProjectsConfig(config);\n}\n\n/**\n * Remove a project from the registry\n */\nexport function unregisterProject(key: string): boolean {\n const config = loadProjectsConfig();\n if (config.projects[key]) {\n delete config.projects[key];\n saveProjectsConfig(config);\n return true;\n }\n return false;\n}\n\n/**\n * Extract Linear team prefix from an issue ID\n * E.g., \"MIN-123\" -> \"MIN\", \"PAN-456\" -> \"PAN\"\n */\nexport function extractTeamPrefix(issueId: string): string | null {\n const match = issueId.match(/^([A-Z]+)-\\d+$/i);\n return match ? match[1].toUpperCase() : null;\n}\n\n/**\n * Find project by Linear team prefix\n */\nexport function findProjectByTeam(teamPrefix: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix.toUpperCase()) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n/**\n * Resolve the correct project path for an issue based on labels\n *\n * @param project - The project config\n * @param labels - Array of label names from the Linear issue\n * @returns The resolved path (may differ from project.path based on routing rules)\n */\nexport function resolveProjectPath(project: ProjectConfig, labels: string[] = []): string {\n if (!project.issue_routing || project.issue_routing.length === 0) {\n return project.path;\n }\n\n // Normalize labels to lowercase for comparison\n const normalizedLabels = labels.map(l => l.toLowerCase());\n\n // First, check label-based routing rules\n for (const rule of project.issue_routing) {\n if (rule.labels && rule.labels.length > 0) {\n const ruleLabels = rule.labels.map(l => l.toLowerCase());\n const hasMatch = ruleLabels.some(label => normalizedLabels.includes(label));\n if (hasMatch) {\n return rule.path;\n }\n }\n }\n\n // Then, find default rule\n for (const rule of project.issue_routing) {\n if (rule.default) {\n return rule.path;\n }\n }\n\n // Fall back to project path\n return project.path;\n}\n\n/**\n * Resolve project from an issue ID (and optional labels)\n *\n * @param issueId - Linear issue ID (e.g., \"MIN-123\")\n * @param labels - Optional array of label names\n * @returns Resolved project info or null if not found\n */\nexport function resolveProjectFromIssue(\n issueId: string,\n labels: string[] = []\n): ResolvedProject | null {\n const teamPrefix = extractTeamPrefix(issueId);\n if (!teamPrefix) {\n return null;\n }\n\n const config = loadProjectsConfig();\n\n // Find project by team prefix\n for (const [key, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: projectConfig.linear_team,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Get a project by key\n */\nexport function getProject(key: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n return config.projects[key] || null;\n}\n\n/**\n * Check if projects.yaml exists and has any projects\n */\nexport function hasProjects(): boolean {\n const config = loadProjectsConfig();\n return Object.keys(config.projects).length > 0;\n}\n\n/**\n * Create a default projects.yaml with example structure\n */\nexport function createDefaultProjectsConfig(): ProjectsConfig {\n const defaultConfig: ProjectsConfig = {\n projects: {\n // Example project - commented out in actual file\n },\n };\n\n return defaultConfig;\n}\n\n/**\n * Initialize projects.yaml with example configuration\n */\nexport function initializeProjectsConfig(): void {\n if (existsSync(PROJECTS_CONFIG_FILE)) {\n console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);\n return;\n }\n\n const exampleYaml = `# Panopticon Project Registry\n# Maps Linear teams to project paths for workspace creation\n\nprojects:\n # Example: Mind Your Now project\n # myn:\n # name: \"Mind Your Now\"\n # path: /home/user/projects/myn\n # linear_team: MIN\n # issue_routing:\n # # Route docs/marketing issues to docs repo\n # - labels: [docs, marketing, seo, landing-pages]\n # path: /home/user/projects/myn/docs\n # # Default: main repo\n # - default: true\n # path: /home/user/projects/myn\n\n # Example: Panopticon itself\n # panopticon:\n # name: \"Panopticon\"\n # path: /home/user/projects/panopticon\n # linear_team: PAN\n`;\n\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, 'utf-8');\n console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lib/projects.ts"],"sourcesContent":["/**\n * Project Registry - Multi-project support for Panopticon\n *\n * Maps Linear team prefixes and labels to project paths for workspace creation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { PANOPTICON_HOME } from './paths.js';\n\nexport const PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, 'projects.yaml');\n\n/**\n * Issue routing rule - routes issues with certain labels to specific paths\n */\nexport interface IssueRoutingRule {\n labels?: string[];\n default?: boolean;\n path: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n issue_routing?: IssueRoutingRule[];\n}\n\n/**\n * Full projects configuration file\n */\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Resolved project info for workspace creation\n */\nexport interface ResolvedProject {\n projectKey: string;\n projectName: string;\n projectPath: string;\n linearTeam?: string;\n}\n\n/**\n * Load projects configuration from ~/.panopticon/projects.yaml\n */\nexport function loadProjectsConfig(): ProjectsConfig {\n if (!existsSync(PROJECTS_CONFIG_FILE)) {\n return { projects: {} };\n }\n\n try {\n const content = readFileSync(PROJECTS_CONFIG_FILE, 'utf-8');\n const config = parseYaml(content) as ProjectsConfig;\n return config || { projects: {} };\n } catch (error: any) {\n console.error(`Failed to parse projects.yaml: ${error.message}`);\n return { projects: {} };\n }\n}\n\n/**\n * Save projects configuration\n */\nexport function saveProjectsConfig(config: ProjectsConfig): void {\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const yaml = stringifyYaml(config, { indent: 2 });\n writeFileSync(PROJECTS_CONFIG_FILE, yaml, 'utf-8');\n}\n\n/**\n * Get a list of all registered projects\n */\nexport function listProjects(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects).map(([key, projectConfig]) => ({\n key,\n config: projectConfig,\n }));\n}\n\n/**\n * Add or update a project in the registry\n */\nexport function registerProject(key: string, projectConfig: ProjectConfig): void {\n const config = loadProjectsConfig();\n config.projects[key] = projectConfig;\n saveProjectsConfig(config);\n}\n\n/**\n * Remove a project from the registry\n */\nexport function unregisterProject(key: string): boolean {\n const config = loadProjectsConfig();\n if (config.projects[key]) {\n delete config.projects[key];\n saveProjectsConfig(config);\n return true;\n }\n return false;\n}\n\n/**\n * Extract Linear team prefix from an issue ID\n * E.g., \"MIN-123\" -> \"MIN\", \"PAN-456\" -> \"PAN\"\n */\nexport function extractTeamPrefix(issueId: string): string | null {\n const match = issueId.match(/^([A-Z]+)-\\d+$/i);\n return match ? match[1].toUpperCase() : null;\n}\n\n/**\n * Find project by Linear team prefix\n */\nexport function findProjectByTeam(teamPrefix: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix.toUpperCase()) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n/**\n * Resolve the correct project path for an issue based on labels\n *\n * @param project - The project config\n * @param labels - Array of label names from the Linear issue\n * @returns The resolved path (may differ from project.path based on routing rules)\n */\nexport function resolveProjectPath(project: ProjectConfig, labels: string[] = []): string {\n if (!project.issue_routing || project.issue_routing.length === 0) {\n return project.path;\n }\n\n // Normalize labels to lowercase for comparison\n const normalizedLabels = labels.map(l => l.toLowerCase());\n\n // First, check label-based routing rules\n for (const rule of project.issue_routing) {\n if (rule.labels && rule.labels.length > 0) {\n const ruleLabels = rule.labels.map(l => l.toLowerCase());\n const hasMatch = ruleLabels.some(label => normalizedLabels.includes(label));\n if (hasMatch) {\n return rule.path;\n }\n }\n }\n\n // Then, find default rule\n for (const rule of project.issue_routing) {\n if (rule.default) {\n return rule.path;\n }\n }\n\n // Fall back to project path\n return project.path;\n}\n\n/**\n * Resolve project from an issue ID (and optional labels)\n *\n * @param issueId - Linear issue ID (e.g., \"MIN-123\")\n * @param labels - Optional array of label names\n * @returns Resolved project info or null if not found\n */\nexport function resolveProjectFromIssue(\n issueId: string,\n labels: string[] = []\n): ResolvedProject | null {\n const teamPrefix = extractTeamPrefix(issueId);\n if (!teamPrefix) {\n return null;\n }\n\n const config = loadProjectsConfig();\n\n // Find project by team prefix\n for (const [key, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: projectConfig.linear_team,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Get a project by key\n */\nexport function getProject(key: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n return config.projects[key] || null;\n}\n\n/**\n * Check if projects.yaml exists and has any projects\n */\nexport function hasProjects(): boolean {\n const config = loadProjectsConfig();\n return Object.keys(config.projects).length > 0;\n}\n\n/**\n * Create a default projects.yaml with example structure\n */\nexport function createDefaultProjectsConfig(): ProjectsConfig {\n const defaultConfig: ProjectsConfig = {\n projects: {\n // Example project - commented out in actual file\n },\n };\n\n return defaultConfig;\n}\n\n/**\n * Initialize projects.yaml with example configuration\n */\nexport function initializeProjectsConfig(): void {\n if (existsSync(PROJECTS_CONFIG_FILE)) {\n console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);\n return;\n }\n\n const exampleYaml = `# Panopticon Project Registry\n# Maps Linear teams to project paths for workspace creation\n\nprojects:\n # Example: Mind Your Now project\n # myn:\n # name: \"Mind Your Now\"\n # path: /home/user/projects/myn\n # linear_team: MIN\n # issue_routing:\n # # Route docs/marketing issues to docs repo\n # - labels: [docs, marketing, seo, landing-pages]\n # path: /home/user/projects/myn/docs\n # # Default: main repo\n # - default: true\n # path: /home/user/projects/myn\n\n # Example: Panopticon itself\n # panopticon:\n # name: \"Panopticon\"\n # path: /home/user/projects/panopticon\n # linear_team: PAN\n`;\n\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, 'utf-8');\n console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);\n}\n"],"mappings":"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","names":[]}
@@ -6,14 +6,10 @@ import {
6
6
  CONFIG_FILE,
7
7
  SKILLS_DIR,
8
8
  SOURCE_SCRIPTS_DIR,
9
- SYNC_TARGETS,
10
- init_esm_shims,
11
- init_paths
12
- } from "./chunk-SG7O6I7R.js";
9
+ SYNC_TARGETS
10
+ } from "./chunk-3SI436SZ.js";
13
11
 
14
12
  // src/lib/config.ts
15
- init_esm_shims();
16
- init_paths();
17
13
  import { readFileSync, writeFileSync, existsSync } from "fs";
18
14
  import { parse, stringify } from "@iarna/toml";
19
15
  var DEFAULT_CONFIG = {
@@ -74,7 +70,6 @@ function getDefaultConfig() {
74
70
  }
75
71
 
76
72
  // src/lib/shell.ts
77
- init_esm_shims();
78
73
  import { existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync } from "fs";
79
74
  import { homedir } from "os";
80
75
  import { join } from "path";
@@ -126,8 +121,6 @@ function getAliasInstructions(shell) {
126
121
  }
127
122
 
128
123
  // src/lib/backup.ts
129
- init_esm_shims();
130
- init_paths();
131
124
  import { existsSync as existsSync3, mkdirSync, readdirSync, cpSync, rmSync } from "fs";
132
125
  import { join as join2, basename } from "path";
133
126
  function createBackupTimestamp() {
@@ -194,8 +187,6 @@ function cleanOldBackups(keepCount = 10) {
194
187
  }
195
188
 
196
189
  // src/lib/sync.ts
197
- init_esm_shims();
198
- init_paths();
199
190
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync, lstatSync, readlinkSync, rmSync as rmSync2, copyFileSync, chmodSync } from "fs";
200
191
  import { join as join3 } from "path";
201
192
  function removeTarget(targetPath) {
@@ -379,7 +370,6 @@ function syncHooks() {
379
370
  }
380
371
 
381
372
  // src/lib/tracker/interface.ts
382
- init_esm_shims();
383
373
  var NotImplementedError = class extends Error {
384
374
  constructor(feature) {
385
375
  super(`Not implemented: ${feature}`);
@@ -400,7 +390,6 @@ var TrackerAuthError = class extends Error {
400
390
  };
401
391
 
402
392
  // src/lib/tracker/linear.ts
403
- init_esm_shims();
404
393
  import { LinearClient } from "@linear/sdk";
405
394
  var STATE_MAP = {
406
395
  backlog: "open",
@@ -610,7 +599,6 @@ var LinearTracker = class {
610
599
  };
611
600
 
612
601
  // src/lib/tracker/github.ts
613
- init_esm_shims();
614
602
  import { Octokit } from "@octokit/rest";
615
603
  var GitHubTracker = class {
616
604
  name = "github";
@@ -771,7 +759,6 @@ var GitHubTracker = class {
771
759
  };
772
760
 
773
761
  // src/lib/tracker/gitlab.ts
774
- init_esm_shims();
775
762
  var GitLabTracker = class {
776
763
  constructor(token, projectId) {
777
764
  this.token = token;
@@ -820,11 +807,7 @@ var GitLabTracker = class {
820
807
  }
821
808
  };
822
809
 
823
- // src/lib/tracker/factory.ts
824
- init_esm_shims();
825
-
826
810
  // src/lib/tracker/rally.ts
827
- init_esm_shims();
828
811
  import rally from "rally";
829
812
  var STATE_MAP2 = {
830
813
  Defined: "open",
@@ -1287,7 +1270,6 @@ function getAllTrackers(trackersConfig) {
1287
1270
  }
1288
1271
 
1289
1272
  // src/lib/tracker/linking.ts
1290
- init_esm_shims();
1291
1273
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1292
1274
  import { join as join4 } from "path";
1293
1275
  import { homedir as homedir2 } from "os";
@@ -1428,9 +1410,6 @@ function getLinkManager() {
1428
1410
  return _linkManager;
1429
1411
  }
1430
1412
 
1431
- // src/lib/tracker/index.ts
1432
- init_esm_shims();
1433
-
1434
1413
  export {
1435
1414
  loadConfig,
1436
1415
  saveConfig,
@@ -1466,4 +1445,4 @@ export {
1466
1445
  LinkManager,
1467
1446
  getLinkManager
1468
1447
  };
1469
- //# sourceMappingURL=chunk-B2JBBOJN.js.map
1448
+ //# sourceMappingURL=chunk-ZT55DPAC.js.map