panopticon-cli 0.4.33 → 0.5.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.
Files changed (71) hide show
  1. package/README.md +96 -210
  2. package/dist/{agents-VLK4BMVA.js → agents-5OPQKM5K.js} +6 -5
  3. package/dist/{chunk-OMNXYPXC.js → chunk-2V4NF7J2.js} +14 -1
  4. package/dist/chunk-2V4NF7J2.js.map +1 -0
  5. package/dist/{chunk-XKT5MHPT.js → chunk-4YSYJ4HM.js} +2 -2
  6. package/dist/{chunk-XFR2DLMR.js → chunk-76F6DSVS.js} +49 -10
  7. package/dist/chunk-76F6DSVS.js.map +1 -0
  8. package/dist/{chunk-PI7Y3PSN.js → chunk-F5555J3A.js} +42 -6
  9. package/dist/chunk-F5555J3A.js.map +1 -0
  10. package/dist/{chunk-KJ2TRXNK.js → chunk-FTCPTHIJ.js} +47 -420
  11. package/dist/chunk-FTCPTHIJ.js.map +1 -0
  12. package/dist/chunk-HJSM6E6U.js +1038 -0
  13. package/dist/chunk-HJSM6E6U.js.map +1 -0
  14. package/dist/{chunk-RBUO57TC.js → chunk-NLQRED36.js} +3 -3
  15. package/dist/chunk-NLQRED36.js.map +1 -0
  16. package/dist/{chunk-ASY7T35E.js → chunk-OWHXCGVO.js} +245 -90
  17. package/dist/chunk-OWHXCGVO.js.map +1 -0
  18. package/dist/{chunk-BKCWRMUX.js → chunk-VHKSS7QX.js} +106 -11
  19. package/dist/chunk-VHKSS7QX.js.map +1 -0
  20. package/dist/{chunk-GFP3PIPB.js → chunk-YGJ54GW2.js} +1 -1
  21. package/dist/chunk-YGJ54GW2.js.map +1 -0
  22. package/dist/cli/index.js +1521 -935
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/dashboard/prompts/work-agent.md +2 -0
  25. package/dist/dashboard/public/assets/index-Ce6q21Fm.js +743 -0
  26. package/dist/dashboard/public/assets/{index-UjZq6ykz.css → index-NzpI0ItZ.css} +1 -1
  27. package/dist/dashboard/public/index.html +2 -2
  28. package/dist/dashboard/server.js +4274 -2320
  29. package/dist/{feedback-writer-LVZ5TFYZ.js → feedback-writer-VRMMWWTW.js} +2 -2
  30. package/dist/git-utils-I2UDKNZH.js +131 -0
  31. package/dist/git-utils-I2UDKNZH.js.map +1 -0
  32. package/dist/index.d.ts +12 -1
  33. package/dist/index.js +5 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/{projects-JEIVIYC6.js → projects-CFX3RTDL.js} +4 -2
  36. package/dist/{remote-workspace-AHVHQEES.js → remote-workspace-7FPGF2RM.js} +2 -2
  37. package/dist/{review-status-EPFG4XM7.js → review-status-TDPSOU5J.js} +2 -2
  38. package/dist/{specialist-context-T3NBMCIE.js → specialist-context-WGUUYDWY.js} +5 -5
  39. package/dist/{specialist-logs-CVKD3YJ3.js → specialist-logs-XJB5TCKJ.js} +5 -5
  40. package/dist/{specialists-TKAP6T6Z.js → specialists-5LBRHYFA.js} +5 -5
  41. package/dist/{traefik-QX4ZV4YG.js → traefik-WFMQX2LY.js} +3 -3
  42. package/dist/{workspace-manager-KLHUCIZV.js → workspace-manager-E434Z45T.js} +2 -2
  43. package/package.json +1 -1
  44. package/scripts/record-cost-event.js +5 -5
  45. package/scripts/stop-hook +7 -0
  46. package/scripts/work-agent-stop-hook +137 -0
  47. package/skills/myn-standards/SKILL.md +351 -0
  48. package/skills/pan-new-project/SKILL.md +304 -0
  49. package/skills/write-spec/SKILL.md +138 -0
  50. package/dist/chunk-7XNJJBH6.js +0 -538
  51. package/dist/chunk-7XNJJBH6.js.map +0 -1
  52. package/dist/chunk-ASY7T35E.js.map +0 -1
  53. package/dist/chunk-BKCWRMUX.js.map +0 -1
  54. package/dist/chunk-GFP3PIPB.js.map +0 -1
  55. package/dist/chunk-KJ2TRXNK.js.map +0 -1
  56. package/dist/chunk-OMNXYPXC.js.map +0 -1
  57. package/dist/chunk-PI7Y3PSN.js.map +0 -1
  58. package/dist/chunk-RBUO57TC.js.map +0 -1
  59. package/dist/chunk-XFR2DLMR.js.map +0 -1
  60. package/dist/dashboard/public/assets/index-kAJqtLDO.js +0 -708
  61. /package/dist/{agents-VLK4BMVA.js.map → agents-5OPQKM5K.js.map} +0 -0
  62. /package/dist/{chunk-XKT5MHPT.js.map → chunk-4YSYJ4HM.js.map} +0 -0
  63. /package/dist/{feedback-writer-LVZ5TFYZ.js.map → feedback-writer-VRMMWWTW.js.map} +0 -0
  64. /package/dist/{projects-JEIVIYC6.js.map → projects-CFX3RTDL.js.map} +0 -0
  65. /package/dist/{remote-workspace-AHVHQEES.js.map → remote-workspace-7FPGF2RM.js.map} +0 -0
  66. /package/dist/{review-status-EPFG4XM7.js.map → review-status-TDPSOU5J.js.map} +0 -0
  67. /package/dist/{specialist-context-T3NBMCIE.js.map → specialist-context-WGUUYDWY.js.map} +0 -0
  68. /package/dist/{specialist-logs-CVKD3YJ3.js.map → specialist-logs-XJB5TCKJ.js.map} +0 -0
  69. /package/dist/{specialists-TKAP6T6Z.js.map → specialists-5LBRHYFA.js.map} +0 -0
  70. /package/dist/{traefik-QX4ZV4YG.js.map → traefik-WFMQX2LY.js.map} +0 -0
  71. /package/dist/{workspace-manager-KLHUCIZV.js.map → workspace-manager-E434Z45T.js.map} +0 -0
package/README.md CHANGED
@@ -7,64 +7,99 @@
7
7
  [![npm version](https://img.shields.io/npm/v/panopticon-cli.svg)](https://www.npmjs.com/package/panopticon-cli)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
9
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
10
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/eltmon/panopticon/pulls)
10
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/eltmon/panopticon-cli/pulls)
11
11
 
12
12
  > *"The Panopticon had six sides, one for each of the Founders of Gallifrey..."*
13
+ >
14
+ > — Classic Doctor Who. The Panopticon was the great hall at the heart of the Time Lord Citadel, where all could be observed. We liked the metaphor.
15
+
16
+ Spawn AI agents from a dashboard. Route tasks to the right model. Review, test, and merge automatically.
13
17
 
14
- <img src="docs/dashboard-overview.png" alt="Panopticon Dashboard" width="800" />
18
+ <img src="docs/screenshot-board.png" alt="Panopticon Kanban Board" width="800" />
15
19
 
16
20
  </div>
17
21
 
18
22
  ---
19
23
 
20
- ## What is Panopticon?
24
+ ## Why Panopticon?
25
+
26
+ - **Stop babysitting agents.** Spawn them from a dashboard, monitor progress in real time, and let specialists handle code review, testing, and merging.
27
+ - **Use the right model for the job.** Opus for planning, Kimi for implementation, Haiku for quick commands — automatic routing based on task type and required capabilities.
28
+ - **Work survives across sessions.** PRDs, state files, beads, and skills persist context so agents don't start from zero every time.
29
+ - **One skill format, every tool.** Write a SKILL.md once and it works across Claude Code, Codex, Cursor, and Gemini CLI.
30
+
31
+ ---
32
+
33
+ ## How It Works
34
+
35
+ ```
36
+ Issue PRD Agent Review Test Merge
37
+ ┌──────┐ ┌──────┐ ┌──────────┐ ┌──────┐ ┌──────┐ ┌──────────┐
38
+ │ Task │ ─► │ Plan │ ─► │ Write │ ─► │ Code │ ─► │ Run │ ─► │ PR │
39
+ │ from │ │ with │ │ code in │ │ rev. │ │ test │ │ merged │
40
+ │ any │ │ Opus │ │ isolated │ │ by │ │ by │ │ by │
41
+ │track-│ │ │ │ worktree │ │ spec-│ │ spec-│ │ spec- │
42
+ │ er │ │ │ │ │ │ialist│ │ialist│ │ ialist │
43
+ └──────┘ └──────┘ └──────────┘ └──────┘ └──────┘ └──────────┘
44
+ GitHub Opus Kimi/Sonnet Opus Sonnet Sonnet
45
+ Linear (routed)
46
+ GitLab
47
+ Jira
48
+ Rally
49
+ ```
50
+
51
+ Create a workspace, and Panopticon handles the rest: planning with Opus, implementation with your configured model, automated code review, test execution, and merge — the only manual step is clicking **MERGE** when you're satisfied.
52
+
53
+ ---
54
+
55
+ ## Key Features
21
56
 
22
- | Without Panopticon | With Panopticon |
23
- |:------------------|:----------------|
24
- | Manually juggle multiple AI agents | **Automatic orchestration** - spawn, monitor, and coordinate agents from a dashboard |
25
- | Agents start fresh every session | **Persistent context** - skills, state files, and beads track work across sessions |
26
- | Simple tasks eat Opus credits | **Smart model routing** - Haiku for simple, Sonnet for medium, Opus for complex |
27
- | Stuck agents waste your time | **Automatic recovery** - detect stuck agents and hand off to specialists |
28
- | AI tools have separate configs | **Universal skills** - one SKILL.md works across Claude, Codex, Cursor, Gemini |
57
+ | Feature | Description |
58
+ |:--------|:------------|
59
+ | **Multi-Agent Orchestration** | Spawn and manage AI agents in tmux sessions via dashboard or CLI |
60
+ | **Cloister Lifecycle Manager** | Automatic model routing, stuck detection, cost tracking, and specialist handoffs |
61
+ | **Mission Control** | 11-view dashboard project tree, activity feed, kanban board, agent status, costs, metrics, and more |
62
+ | **PRD-Driven Workflow** | Opus writes a PRD before implementation starts; agents are blocked without one |
63
+ | **67+ Universal Skills** | Pre-built skills ship out of the box, synced via `pan sync` — one SKILL.md works across all AI tools |
64
+ | **Multi-Tracker Support** | GitHub Issues, Linear, GitLab, Jira, Rally — all from one dashboard |
65
+ | **Multi-Model Routing** | Anthropic, OpenAI, Google, Kimi, Zhipu — route by task type, capability, and budget |
66
+ | **Workspaces** | Git worktree-based feature branches with Docker isolation (local and remote via exe.dev) |
67
+ | **Convoys** | Run parallel agents on related issues with automatic synthesis |
68
+ | **Specialists** | Dedicated review, test, and merge agents — fully automated quality pipeline |
69
+ | **Beads** | Git-backed task tracking that survives context compaction and works offline |
70
+ | **Cost Tracking** | Per-issue, per-stage token costs with dashboard analytics |
71
+ | **Legacy Codebase Support** | AI self-monitoring skills that learn your codebase conventions over time ([details](docs/LEGACY-CODEBASE.md)) |
72
+
73
+ ---
29
74
 
30
75
  ## Screenshots
31
76
 
32
77
  <div align="center">
33
78
  <table>
34
79
  <tr>
35
- <td><img src="docs/planning-session-dialog.png" alt="Planning Dialog" width="300" /></td>
36
- <td><img src="docs/planning-session-discovery.png" alt="Discovery Phase" width="300" /></td>
37
- <td><img src="docs/planning-session-active.png" alt="Active Session" width="300" /></td>
80
+ <td><img src="docs/dashboard-overview.png" alt="Mission Control" width="400" /></td>
81
+ <td><img src="docs/screenshot-agents.png" alt="Agent Management" width="400" /></td>
38
82
  </tr>
39
83
  <tr>
40
- <td align="center"><em>Start planning</em></td>
41
- <td align="center"><em>Discovery phase</em></td>
42
- <td align="center"><em>Active session</em></td>
84
+ <td align="center"><em>Mission Control — project tree, activity timeline, specialist pipeline</em></td>
85
+ <td align="center"><em>Cloister Deacon, specialist agents, and issue agent management</em></td>
86
+ </tr>
87
+ <tr>
88
+ <td colspan="2"><img src="docs/screenshot-settings.png" alt="Model Routing Settings" width="800" /></td>
89
+ </tr>
90
+ <tr>
91
+ <td colspan="2" align="center"><em>Tracker integration and capability-based model routing</em></td>
43
92
  </tr>
44
93
  </table>
45
94
  </div>
46
95
 
47
- ## Key Features
48
-
49
- | Feature | Description |
50
- |:--------|:------------|
51
- | **Multi-Agent Orchestration** | Spawn and manage AI agents in tmux sessions via dashboard or CLI |
52
- | **Cloister Lifecycle Manager** | Automatic model routing, stuck detection, and specialist handoffs |
53
- | **Universal Skills** | One SKILL.md format works across all supported AI tools |
54
- | **Workspaces** | Git worktree-based feature branches with Docker isolation |
55
- | **Convoys** | Run parallel agents on related issues with auto-synthesis |
56
- | **Specialists** | Dedicated review, test, and merge agents for quality control |
57
- | **Heartbeat Monitoring** | Real-time agent activity tracking via Claude Code hooks |
58
- | **Mission Control** | Unified monitoring view — see all active features, agent activity, and planning artifacts at a glance |
59
- | **Shadow Engineering** | Monitor existing workflows before transitioning to AI-driven development |
60
- | **Real-Time Dashboard** | Socket.io push with multi-layer caching (in-memory + SQLite) for instant loads |
61
- | **Legacy Codebase Support** | AI self-monitoring skills that learn from your codebase |
96
+ ---
62
97
 
63
98
  ## Supported Tools
64
99
 
65
100
  | Tool | Support |
66
101
  |:-----|:--------|
67
- | **Claude Code** | Full support |
102
+ | **Claude Code** | Full support — agent runtime, hooks, skills |
68
103
  | **Codex** | Skills sync |
69
104
  | **Cursor** | Skills sync |
70
105
  | **Gemini CLI** | Skills sync |
@@ -72,133 +107,7 @@
72
107
 
73
108
  ---
74
109
 
75
- ## Legacy Codebase Support
76
-
77
- > **"AI works great on greenfield projects, but it's hopeless on our legacy code."**
78
- >
79
- > Sound familiar? Your developers aren't wrong. But they're not stuck, either.
80
-
81
- ### The Problem Every Enterprise Faces
82
-
83
- AI coding assistants are trained on modern, well-documented open-source code. When they encounter your 15-year-old monolith with:
84
-
85
- - Mixed naming conventions (some `snake_case`, some `camelCase`, some `SCREAMING_CASE`)
86
- - Undocumented tribal knowledge ("we never touch the `processUser()` function directly")
87
- - Schemas that don't match the ORM ("the `accounts` table is actually users")
88
- - Three different async patterns in the same codebase
89
- - Build systems that require arcane incantations
90
-
91
- ...they stumble. Repeatedly. Every session starts from zero.
92
-
93
- ### Panopticon's Unique Solution: Adaptive Learning
94
-
95
- Panopticon includes two AI self-monitoring skills that **no other orchestration framework provides**:
96
-
97
- | Skill | What It Does | Business Impact |
98
- |-------|--------------|-----------------|
99
- | **Knowledge Capture** | Detects when AI makes mistakes or gets corrected, prompts to document the learning | AI gets smarter about YOUR codebase over time |
100
- | **Refactor Radar** | Identifies systemic code issues causing repeated AI confusion, creates actionable proposals | Surfaces technical debt that's costing you AI productivity |
101
-
102
- #### How It Works
103
-
104
- ```
105
- Session 1: AI queries users.created_at → Error (column is "createdAt")
106
- → Knowledge Capture prompts: "Document this convention?"
107
- → User: "Yes, create skill"
108
- → Creates project-specific skill documenting naming conventions
109
-
110
- Session 2: AI knows to use camelCase for this project
111
- No more mistakes on column names
112
-
113
- Session 5: Refactor Radar detects: "Same entity called 'user', 'account', 'member'
114
- across layers - this is causing repeated confusion"
115
- → Offers to create issue with refactoring proposal
116
- → Tech lead reviews and schedules cleanup sprint
117
- ```
118
-
119
- #### The Compound Effect
120
-
121
- | Week | Without Panopticon | With Panopticon |
122
- |------|-------------------|-----------------|
123
- | 1 | AI makes 20 mistakes/day on conventions | AI makes 20 mistakes, captures 8 learnings |
124
- | 2 | AI makes 20 mistakes/day (no memory) | AI makes 12 mistakes, captures 5 more |
125
- | 4 | AI makes 20 mistakes/day (still no memory) | AI makes 3 mistakes, codebase improving |
126
- | 8 | Developers give up on AI for legacy code | AI is productive, tech debt proposals in backlog |
127
-
128
- #### Shared Team Knowledge
129
-
130
- **When one developer learns, everyone benefits.**
131
-
132
- Captured skills live in your project's `.claude/skills/` directory - they're version-controlled alongside your code. When Sarah documents that "we use camelCase columns" after hitting that error, every developer on the team - and every AI session from that point forward - inherits that knowledge automatically.
133
-
134
- ```
135
- myproject/
136
- ├── .claude/skills/
137
- │ └── project-knowledge/ # ← Git-tracked, shared by entire team
138
- │ └── SKILL.md # "Database uses camelCase, not snake_case"
139
- ├── src/
140
- └── ...
141
- ```
142
-
143
- No more repeating the same corrections to AI across 10 different developers. No more tribal knowledge locked in one person's head. The team's collective understanding of your codebase becomes permanent, searchable, and automatically applied.
144
-
145
- **New hire onboarding?** The AI already knows your conventions from day one.
146
-
147
- #### For Technical Leaders
148
-
149
- **What gets measured gets managed.** Panopticon's Refactor Radar surfaces the specific patterns that are costing you AI productivity:
150
-
151
- - "Here are the 5 naming inconsistencies causing 40% of AI errors"
152
- - "These 3 missing FK constraints led to 12 incorrect deletions last month"
153
- - "Mixed async patterns in payments module caused 8 rollbacks"
154
-
155
- Each proposal includes:
156
- - **Evidence**: Specific file paths and examples
157
- - **Impact**: How this affects AI (and new developers)
158
- - **Migration path**: Incremental fix that won't break production
159
-
160
- #### For Executives
161
-
162
- **ROI is simple:**
163
-
164
- - $200K/year senior developer spends 2 hours/day correcting AI on legacy code
165
- - That's $50K/year in wasted productivity per developer
166
- - Team of 10 = **$500K/year** in AI friction
167
-
168
- Panopticon's learning system:
169
- - Captures corrections once, applies them forever
170
- - Identifies root causes (not just symptoms)
171
- - Creates actionable improvement proposals
172
- - Works across your entire AI toolchain (Claude, Codex, Cursor, Gemini)
173
-
174
- **This isn't "AI for greenfield only." This is AI that learns your business.**
175
-
176
- #### Configurable Per Team and Per Developer
177
-
178
- Different teams have different ownership boundaries. Individual developers have different preferences. Panopticon respects both:
179
-
180
- ```markdown
181
- # In ~/.claude/CLAUDE.md (developer's personal config)
182
-
183
- ## AI Suggestion Preferences
184
-
185
- ### refactor-radar
186
- skip: database-migrations, infrastructure # DBA/Platform team handles these
187
- welcome: naming, code-organization # Always happy for these
188
-
189
- ### knowledge-capture
190
- skip: authentication # Security team owns this
191
- ```
192
-
193
- - **"Skip database migrations"** - Your DBA has a change management process
194
- - **"Skip infrastructure"** - Platform team owns that
195
- - **"Welcome naming fixes"** - Low risk, high value, always appreciated
196
-
197
- The AI adapts to your org structure, not the other way around.
198
-
199
- ---
200
-
201
- ## 🚀 Quick Start
110
+ ## Quick Start
202
111
 
203
112
  ```bash
204
113
  npm install -g panopticon-cli && pan install && pan sync && pan up
@@ -206,11 +115,9 @@ npm install -g panopticon-cli && pan install && pan sync && pan up
206
115
 
207
116
  **That's it!** Dashboard runs at https://pan.localhost (or http://localhost:3010 if you skip HTTPS setup).
208
117
 
209
- 📖 **[Full documentation →](docs/INDEX.md)**
210
-
211
118
  ---
212
119
 
213
- ## 📋 Requirements
120
+ ## Requirements
214
121
 
215
122
  ### Required
216
123
  - Node.js 18+
@@ -222,14 +129,12 @@ npm install -g panopticon-cli && pan install && pan sync && pan up
222
129
 
223
130
  ### Optional
224
131
  - **mkcert** - For HTTPS certificates (recommended)
225
- - **Linear API key** - For issue tracking
132
+ - **Linear API key** - For Linear issue tracking
226
133
  - **Beads CLI** - Auto-installed by `pan install`
227
134
 
228
- 📖 **[Platform support and detailed requirements →](docs/USAGE.md#requirements)**
229
-
230
135
  ---
231
136
 
232
- ## 🔧 Configuration
137
+ ## Configuration
233
138
 
234
139
  ```bash
235
140
  # Create config file
@@ -246,49 +151,27 @@ Register your projects:
246
151
  pan project add /path/to/your/project --name myproject
247
152
  ```
248
153
 
249
- 📖 **[Complete configuration guide →](docs/CONFIGURATION.md)**
250
- 📖 **[Work types and model routing →](docs/WORK-TYPES.md)**
251
- 📖 **[Detailed usage examples →](docs/USAGE.md)**
252
-
253
154
  ---
254
155
 
255
- ## 🎯 Key Concepts
156
+ ## Key Concepts
256
157
 
257
- ### Mission Control
258
- The default landing view. A two-panel layout with a resizable sidebar showing your project tree (grouped by project, filtered to active features) and a main area displaying agent activity, planning artifacts (PRD, STATE.md, transcripts, discussions), and status reviews.
158
+ **Mission Control** — The default view. Project tree on the left, agent activity on the right. Click a feature to see its full pipeline: planning, work, review, test results. Badge bar gives quick access to PRDs, state files, discussions, and transcripts.
259
159
 
260
- - **Project Tree**: Features grouped by project with live state labels (In Progress, Suspended, In Review, Done, Planning, Idle)
261
- - **Activity View**: Chronological agent sessions with tail-anchored scrolling — click a feature and see what the agent is doing right now
262
- - **Badge Bar**: Quick access to PRD, STATE.md, discussions, transcripts, status reviews, and file uploads
263
- - **Status Reviews**: On-demand AI-generated progress reports comparing code changes against the PRD
160
+ **Cloister** The lifecycle manager. Routes tasks to models based on capabilities, detects stuck agents, triggers specialist handoffs, and tracks costs.
264
161
 
265
- ### Shadow Engineering
266
- A mode for teams adopting AI incrementally. Register existing projects as "shadow" workspaces to monitor ongoing development without AI agents making changes.
162
+ **Workspaces** Git worktree-based feature branches with optional Docker isolation. Each issue gets its own isolated environment. Supports both local and remote (exe.dev) execution.
267
163
 
268
- - Create shadow features with `pan workspace create --shadow PAN-XXX`
269
- - Upload meeting transcripts and notes via the Badge Bar
270
- - Sync issue tracker discussions automatically
271
- - Generate inference documents (INFERENCE.md) analyzing how AI would approach the work
272
- - Transition from monitoring to AI-driven development when ready
164
+ **Specialists** Dedicated agents for code review, testing, and merging. Triggered automatically by Cloister when an agent signals completion. The pipeline is fully automated — code review to merge with zero human intervention (except the final merge click).
273
165
 
274
- ### Multi-Agent Orchestration
275
- Spawn and manage AI agents in tmux sessions, monitored by the Cloister lifecycle manager.
166
+ **Convoys** — Run parallel agents on related issues. Useful for security audits, performance reviews, or breaking an epic into concurrent work streams. Results are auto-synthesized.
276
167
 
277
- ### Workspaces
278
- Git worktree-based feature branches with optional Docker isolation. Supports both local and remote (exe.dev) execution.
168
+ **Skills** — Universal SKILL.md format works across Claude Code, Codex, Cursor, and Gemini. 67+ skills ship out of the box covering development workflows, code review, incident response, and more.
279
169
 
280
- ### Specialists
281
- Dedicated agents for code review, testing, and merging. Automatically triggered by the Cloister manager.
282
-
283
- ### Skills
284
- Universal SKILL.md format works across Claude Code, Codex, Cursor, and Gemini. Distributed via `pan sync`.
285
-
286
- 📖 **[Architecture overview →](AGENTS.md)**
287
- 📖 **[Specialist workflow →](docs/SPECIALIST_WORKFLOW.md)**
170
+ **Shadow Engineering** — Monitor existing workflows before transitioning to AI-driven development. Upload transcripts, sync discussions, generate inference documents.
288
171
 
289
172
  ---
290
173
 
291
- ## 🛠️ Common Commands
174
+ ## Common Commands
292
175
 
293
176
  ```bash
294
177
  # Start dashboard
@@ -307,11 +190,21 @@ pan logs agent-pan-123
307
190
  pan down
308
191
  ```
309
192
 
310
- 📖 **[Complete command reference →](docs/USAGE.md#commands)**
193
+ ---
194
+
195
+ ## Maturity
196
+
197
+ Panopticon is actively used in production to develop itself and multiple other projects.
198
+
199
+ - **62 PRDs** written (16 active, 46 completed)
200
+ - **67+ skills** shipped and synced across tools
201
+ - **5 tracker integrations** (GitHub, Linear, GitLab, Jira, Rally)
202
+ - **6 AI providers** with capability-based model routing
203
+ - **v0.4.33** — hundreds of issues completed through the full pipeline
311
204
 
312
205
  ---
313
206
 
314
- ## 📚 Documentation
207
+ ## Documentation
315
208
 
316
209
  | Document | Description |
317
210
  |----------|-------------|
@@ -319,32 +212,25 @@ pan down
319
212
  | [docs/USAGE.md](docs/USAGE.md) | Detailed usage guide, examples, troubleshooting |
320
213
  | [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | Model routing, API setup, presets |
321
214
  | [AGENTS.md](AGENTS.md) | Agent architecture |
322
- | [docs/ARCHITECTURE-CACHING.md](docs/ARCHITECTURE-CACHING.md) | Dashboard caching and real-time push |
215
+ | [docs/SPECIALIST_WORKFLOW.md](docs/SPECIALIST_WORKFLOW.md) | Review, test, merge pipeline |
216
+ | [docs/LEGACY-CODEBASE.md](docs/LEGACY-CODEBASE.md) | AI adaptive learning for legacy codebases |
323
217
  | [CONTRIBUTING.md](CONTRIBUTING.md) | Contribution guidelines |
324
218
  | [CLAUDE.md](CLAUDE.md) | Agent development guidance |
325
- | [docs/MISSION-CONTROL.md](docs/MISSION-CONTROL.md) | Mission Control and Shadow Engineering guide |
326
219
 
327
220
  ---
328
221
 
329
- ## 🤝 Contributing
222
+ ## Contributing
330
223
 
331
224
  Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
332
225
 
333
226
  ---
334
227
 
335
- ## ⭐ Star History
336
-
337
- [![Star History Chart](https://api.star-history.com/svg?repos=eltmon/panopticon-cli&type=Date)](https://star-history.com/#eltmon/panopticon-cli&Date)
338
-
339
- ---
340
-
341
- ## ⚖️ License
228
+ ## License
342
229
 
343
230
  MIT License - see [LICENSE](LICENSE) for details.
344
231
 
345
232
  ---
346
233
 
347
234
  <div align="center">
348
- <p><strong>Made with ❤️ by the Panopticon team</strong></p>
349
235
  <p><a href="https://github.com/eltmon/panopticon-cli">GitHub</a> · <a href="https://www.npmjs.com/package/panopticon-cli">npm</a> · <a href="docs/INDEX.md">Documentation</a></p>
350
236
  </div>
@@ -18,11 +18,12 @@ import {
18
18
  saveSessionId,
19
19
  spawnAgent,
20
20
  stopAgent
21
- } from "./chunk-BKCWRMUX.js";
22
- import "./chunk-KJ2TRXNK.js";
23
- import "./chunk-XFR2DLMR.js";
24
- import "./chunk-7XNJJBH6.js";
21
+ } from "./chunk-VHKSS7QX.js";
22
+ import "./chunk-FTCPTHIJ.js";
23
+ import "./chunk-76F6DSVS.js";
24
+ import "./chunk-HJSM6E6U.js";
25
25
  import "./chunk-FQ66DECN.js";
26
+ import "./chunk-2V4NF7J2.js";
26
27
  import "./chunk-CFCUOV3Q.js";
27
28
  import "./chunk-ZTFNYOC7.js";
28
29
  import "./chunk-ZHC57RCV.js";
@@ -47,4 +48,4 @@ export {
47
48
  spawnAgent,
48
49
  stopAgent
49
50
  };
50
- //# sourceMappingURL=agents-VLK4BMVA.js.map
51
+ //# sourceMappingURL=agents-5OPQKM5K.js.map
@@ -14,6 +14,7 @@ __export(projects_exports, {
14
14
  PROJECTS_CONFIG_FILE: () => PROJECTS_CONFIG_FILE,
15
15
  createDefaultProjectsConfig: () => createDefaultProjectsConfig,
16
16
  extractTeamPrefix: () => extractTeamPrefix,
17
+ findProjectByPath: () => findProjectByPath,
17
18
  findProjectByTeam: () => findProjectByTeam,
18
19
  findProjectsByRallyProject: () => findProjectsByRallyProject,
19
20
  getProject: () => getProject,
@@ -88,6 +89,17 @@ function findProjectByTeam(teamPrefix) {
88
89
  }
89
90
  return null;
90
91
  }
92
+ function findProjectByPath(workspacePath) {
93
+ const config = loadProjectsConfig();
94
+ const normalizedTarget = resolve(workspacePath);
95
+ for (const [, projectConfig] of Object.entries(config.projects)) {
96
+ const normalizedProject = resolve(projectConfig.path);
97
+ if (normalizedTarget === normalizedProject || normalizedTarget.startsWith(normalizedProject + "/")) {
98
+ return projectConfig;
99
+ }
100
+ }
101
+ return null;
102
+ }
91
103
  function resolveProjectPath(project, labels = []) {
92
104
  if (!project.issue_routing || project.issue_routing.length === 0) {
93
105
  return project.path;
@@ -244,6 +256,7 @@ export {
244
256
  unregisterProject,
245
257
  extractTeamPrefix,
246
258
  findProjectByTeam,
259
+ findProjectByPath,
247
260
  resolveProjectPath,
248
261
  resolveProjectFromIssue,
249
262
  getProject,
@@ -257,4 +270,4 @@ export {
257
270
  projects_exports,
258
271
  init_projects
259
272
  };
260
- //# sourceMappingURL=chunk-OMNXYPXC.js.map
273
+ //# sourceMappingURL=chunk-2V4NF7J2.js.map
@@ -0,0 +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 * Workspace configuration (imported from workspace-config.ts for full details)\n */\nexport interface WorkspaceConfig {\n type?: 'polyrepo' | 'monorepo';\n workspaces_dir?: string;\n repos?: Array<{ name: string; path: string; branch_prefix?: string }>;\n dns?: { domain: string; entries: string[]; sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' };\n ports?: Record<string, { range: [number, number] }>;\n docker?: { traefik?: string; compose_template?: string };\n database?: { seed_file?: string; container_name?: string; [key: string]: any };\n agent?: { template_dir: string; templates?: Array<{ source: string; target: string }>; copy_dirs?: string[]; symlinks?: string[] };\n env?: { template?: string; secrets_file?: string };\n services?: Array<{ name: string; path: string; start_command: string; docker_command?: string; health_url?: string; port?: number }>;\n}\n\n/**\n * Test configuration\n */\nexport interface TestConfig {\n type: string;\n path: string;\n command: string;\n container?: boolean;\n container_name?: string;\n env?: Record<string, string>;\n}\n\n/**\n * Specialist configuration for per-project specialists\n */\nexport interface SpecialistConfig {\n /** Number of recent runs to include in context digest (default: 5) */\n context_runs?: number;\n /** Model to use for generating context digests (null = same as specialist) */\n digest_model?: string | null;\n /** Log retention policy */\n retention?: {\n /** Maximum days to keep logs */\n max_days: number;\n /** Maximum number of runs to keep (whichever is more permissive) */\n max_runs: number;\n };\n /** Per-specialist prompt overrides */\n prompts?: {\n 'review-agent'?: string;\n 'test-agent'?: string;\n 'merge-agent'?: string;\n };\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n github_repo?: string; // e.g. \"owner/repo\"\n gitlab_repo?: string; // e.g. \"group/repo\"\n issue_routing?: IssueRoutingRule[];\n /** Workspace configuration */\n workspace?: WorkspaceConfig;\n /** Test configuration by name */\n tests?: Record<string, TestConfig>;\n /** Custom command to create workspaces (e.g., infra/new-feature for MYN) */\n workspace_command?: string;\n /** Custom command to remove workspaces */\n workspace_remove_command?: string;\n /** Rally project OID (e.g., \"/project/822404704163\") for per-project Rally scoping */\n rally_project?: string;\n /** Specialist agent configuration */\n specialists?: SpecialistConfig;\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 * Find project by workspace path.\n * Matches any project whose root path is an ancestor of the given path.\n * Used to resolve the tracker (GitHub/GitLab) from a workspace directory.\n */\nexport function findProjectByPath(workspacePath: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n const normalizedTarget = resolve(workspacePath);\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n const normalizedProject = resolve(projectConfig.path);\n if (normalizedTarget === normalizedProject || normalizedTarget.startsWith(normalizedProject + '/')) {\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 # specialists:\n # context_runs: 5\n # digest_model: null # Use same model as specialist\n # retention:\n # max_days: 30\n # max_runs: 50\n # prompts:\n # review-agent: |\n # Pay special attention to:\n # - Database migration safety\n # - API backward compatibility\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\n/**\n * Default specialist configuration values\n */\nconst DEFAULT_SPECIALIST_CONFIG: Required<SpecialistConfig> = {\n context_runs: 5,\n digest_model: null,\n retention: {\n max_days: 30,\n max_runs: 50,\n },\n prompts: {},\n};\n\n/**\n * Get specialist configuration for a project with defaults\n *\n * @param projectKey - Project key\n * @returns Specialist config with defaults applied\n */\nexport function getSpecialistConfig(projectKey: string): Required<SpecialistConfig> {\n const project = getProject(projectKey);\n\n if (!project || !project.specialists) {\n return DEFAULT_SPECIALIST_CONFIG;\n }\n\n return {\n context_runs: project.specialists.context_runs ?? DEFAULT_SPECIALIST_CONFIG.context_runs,\n digest_model: project.specialists.digest_model ?? DEFAULT_SPECIALIST_CONFIG.digest_model,\n retention: {\n max_days: project.specialists.retention?.max_days ?? DEFAULT_SPECIALIST_CONFIG.retention.max_days,\n max_runs: project.specialists.retention?.max_runs ?? DEFAULT_SPECIALIST_CONFIG.retention.max_runs,\n },\n prompts: project.specialists.prompts ?? DEFAULT_SPECIALIST_CONFIG.prompts,\n };\n}\n\n/**\n * Get retention policy for a project's specialists\n *\n * @param projectKey - Project key\n * @returns Retention policy\n */\nexport function getSpecialistRetention(projectKey: string): { max_days: number; max_runs: number } {\n const config = getSpecialistConfig(projectKey);\n return config.retention;\n}\n\n/**\n * Find all projects that have a rally_project configured.\n * Returns array of { key, config } for projects with Rally project OIDs.\n */\nexport function findProjectsByRallyProject(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects)\n .filter(([, projectConfig]) => !!projectConfig.rally_project)\n .map(([key, projectConfig]) => ({ key, config: projectConfig }));\n}\n\n/**\n * Get custom prompt override for a specialist (if configured)\n *\n * @param projectKey - Project key\n * @param specialistType - Specialist type\n * @returns Custom prompt or null if not configured\n */\nexport function getSpecialistPromptOverride(\n projectKey: string,\n specialistType: 'review-agent' | 'test-agent' | 'merge-agent'\n): string | null {\n const config = getSpecialistConfig(projectKey);\n return config.prompts[specialistType] || null;\n}\n"],"mappings":"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","names":[]}
@@ -13,7 +13,7 @@ import {
13
13
  init_github,
14
14
  init_gitlab,
15
15
  init_linear
16
- } from "./chunk-XFR2DLMR.js";
16
+ } from "./chunk-76F6DSVS.js";
17
17
  import {
18
18
  getDevrootPath,
19
19
  init_config
@@ -674,4 +674,4 @@ export {
674
674
  LinkManager,
675
675
  getLinkManager
676
676
  };
677
- //# sourceMappingURL=chunk-XKT5MHPT.js.map
677
+ //# sourceMappingURL=chunk-4YSYJ4HM.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  init_config_yaml,
3
3
  loadConfig
4
- } from "./chunk-7XNJJBH6.js";
4
+ } from "./chunk-HJSM6E6U.js";
5
5
  import {
6
6
  IssueNotFoundError,
7
7
  NotImplementedError,
@@ -370,7 +370,43 @@ var init_github = __esm({
370
370
  };
371
371
  }
372
372
  async transitionIssue(id, state) {
373
- await this.updateIssue(id, { state });
373
+ const issueNumber = parseInt(id.replace(/^#/, ""), 10);
374
+ if (state === "in_progress") {
375
+ await this.ensureLabelExists("in-progress", "In progress", "0075ca");
376
+ await this.octokit.issues.addLabels({
377
+ owner: this.owner,
378
+ repo: this.repo,
379
+ issue_number: issueNumber,
380
+ labels: ["in-progress"]
381
+ });
382
+ } else {
383
+ const issue = await this.getIssue(id);
384
+ if (issue.labels?.includes("in-progress")) {
385
+ await this.octokit.issues.removeLabel({
386
+ owner: this.owner,
387
+ repo: this.repo,
388
+ issue_number: issueNumber,
389
+ name: "in-progress"
390
+ }).catch(() => {
391
+ });
392
+ }
393
+ await this.updateIssue(id, { state });
394
+ }
395
+ }
396
+ /** Ensure a label exists in the repo, creating it if needed. */
397
+ async ensureLabelExists(name, description, color) {
398
+ try {
399
+ await this.octokit.issues.getLabel({ owner: this.owner, repo: this.repo, name });
400
+ } catch {
401
+ await this.octokit.issues.createLabel({
402
+ owner: this.owner,
403
+ repo: this.repo,
404
+ name,
405
+ description,
406
+ color
407
+ }).catch(() => {
408
+ });
409
+ }
374
410
  }
375
411
  async linkPR(issueId, prUrl) {
376
412
  await this.addComment(
@@ -379,15 +415,16 @@ var init_github = __esm({
379
415
  );
380
416
  }
381
417
  normalizeIssue(ghIssue) {
418
+ const labels = ghIssue.labels.map(
419
+ (l) => typeof l === "string" ? l : l.name
420
+ );
382
421
  return {
383
422
  id: String(ghIssue.id),
384
423
  ref: `#${ghIssue.number}`,
385
424
  title: ghIssue.title,
386
425
  description: ghIssue.body ?? "",
387
- state: this.mapStateFromGitHub(ghIssue.state),
388
- labels: ghIssue.labels.map(
389
- (l) => typeof l === "string" ? l : l.name
390
- ),
426
+ state: this.mapStateFromGitHub(ghIssue.state, labels),
427
+ labels,
391
428
  assignee: ghIssue.assignee?.login,
392
429
  url: ghIssue.html_url,
393
430
  tracker: "github",
@@ -399,8 +436,10 @@ var init_github = __esm({
399
436
  updatedAt: ghIssue.updated_at
400
437
  };
401
438
  }
402
- mapStateFromGitHub(ghState) {
403
- return ghState === "closed" ? "closed" : "open";
439
+ mapStateFromGitHub(ghState, labels = []) {
440
+ if (ghState === "closed") return "closed";
441
+ if (labels.includes("in-progress")) return "in_progress";
442
+ return "open";
404
443
  }
405
444
  mapStateToGitHub(state) {
406
445
  if (!state) return "open";
@@ -471,7 +510,7 @@ var init_gitlab = __esm({
471
510
  // src/lib/tracker/factory.ts
472
511
  function getTrackerKeyFromConfig(trackerType) {
473
512
  try {
474
- const yamlConfig = loadConfig();
513
+ const { config: yamlConfig } = loadConfig();
475
514
  return yamlConfig.trackerKeys[trackerType];
476
515
  } catch {
477
516
  return void 0;
@@ -597,4 +636,4 @@ export {
597
636
  getAllTrackers,
598
637
  init_factory
599
638
  };
600
- //# sourceMappingURL=chunk-XFR2DLMR.js.map
639
+ //# sourceMappingURL=chunk-76F6DSVS.js.map