claude-autopm 3.24.2 → 3.25.2

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.
@@ -32,6 +32,7 @@ jobs:
32
32
  - plugin-pm-azure
33
33
  - plugin-pm-github
34
34
  - plugin-testing
35
+ - plugin-obsidian
35
36
  steps:
36
37
  - uses: actions/checkout@v4
37
38
  - uses: actions/setup-node@v6
@@ -266,8 +266,10 @@ async function handleInstall(manager, pluginName, argv) {
266
266
  console.log(chalk.gray(`npm package already installed: @claudeautopm/${fullPluginName}`));
267
267
  }
268
268
 
269
- // Install plugin agents
270
- console.log(chalk.gray('Installing plugin agents...'));
269
+ // Discover and install plugin resources
270
+ console.log(chalk.gray('Discovering plugins...'));
271
+ await manager.initialize();
272
+ console.log(chalk.gray('Installing plugin resources...'));
271
273
  const result = await manager.installPlugin(fullPluginName);
272
274
 
273
275
  console.log(chalk.green(`\n✓ Plugin installed successfully!`));
@@ -131,23 +131,64 @@ class PluginManager extends EventEmitter {
131
131
  }
132
132
 
133
133
  /**
134
- * Discover all installed plugins in node_modules
134
+ * Discover all installed plugins in node_modules (local, global, and bundled)
135
135
  * Based on npm workspaces pattern from Context7
136
136
  */
137
137
  async discoverPlugins() {
138
138
  const pluginPattern = `${this.options.scopePrefix}/plugin-`;
139
- const nodeModulesPath = this.options.pluginDir;
140
139
 
141
- try {
142
- // Check if scoped directory exists
143
- const scopePath = path.join(nodeModulesPath, this.options.scopePrefix);
140
+ // Check multiple locations: local node_modules, global npm prefix, bundled packages/
141
+ const searchPaths = [this.options.pluginDir];
144
142
 
145
- if (!fs.existsSync(scopePath)) {
146
- this.emit('discover:no-plugins', { scopePath });
147
- return;
143
+ // Add global npm path
144
+ try {
145
+ const { execSync } = require('child_process');
146
+ const globalPath = execSync('npm root -g', { encoding: 'utf8' }).trim();
147
+ if (globalPath && !searchPaths.includes(globalPath)) {
148
+ searchPaths.push(globalPath);
148
149
  }
150
+ } catch {
151
+ // npm not available or error — skip global
152
+ }
153
+
154
+ // Add bundled packages/ directory (for development / autopm source repo)
155
+ const bundledPath = path.join(this.options.projectRoot, 'packages');
156
+ if (fs.existsSync(bundledPath)) {
157
+ // Bundled plugins are at packages/plugin-*/plugin.json (not under @claudeautopm scope)
158
+ try {
159
+ const entries = fs.readdirSync(bundledPath);
160
+ for (const entry of entries) {
161
+ if (!entry.startsWith('plugin-')) continue;
162
+ const pluginJsonPath = path.join(bundledPath, entry, 'plugin.json');
163
+ if (!fs.existsSync(pluginJsonPath)) continue;
164
+
165
+ try {
166
+ const metadata = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf-8'));
167
+ const fullName = `${this.options.scopePrefix}/${entry}`;
168
+ if (!this.plugins.has(fullName)) {
169
+ this.plugins.set(fullName, {
170
+ name: fullName,
171
+ path: path.join(bundledPath, entry),
172
+ metadata,
173
+ loaded: false
174
+ });
175
+ this.emit('discover:found', { name: fullName, source: 'bundled' });
176
+ }
177
+ } catch { /* skip invalid */ }
178
+ }
179
+ } catch { /* skip unreadable */ }
180
+ }
149
181
 
150
- const scopedPackages = fs.readdirSync(scopePath);
182
+ for (const nodeModulesPath of searchPaths) {
183
+ try {
184
+ // Check if scoped directory exists
185
+ const scopePath = path.join(nodeModulesPath, this.options.scopePrefix);
186
+
187
+ if (!fs.existsSync(scopePath)) {
188
+ continue;
189
+ }
190
+
191
+ const scopedPackages = fs.readdirSync(scopePath);
151
192
 
152
193
  for (const packageName of scopedPackages) {
153
194
  if (!packageName.startsWith('plugin-')) {
@@ -186,8 +227,9 @@ class PluginManager extends EventEmitter {
186
227
  }
187
228
  } catch (error) {
188
229
  this.emit('discover:error', { error: error.message });
189
- throw new Error(`Plugin discovery failed: ${error.message}`);
230
+ // Continue to next search path instead of failing
190
231
  }
232
+ } // end for searchPaths
191
233
  }
192
234
 
193
235
  /**
@@ -223,7 +265,10 @@ class PluginManager extends EventEmitter {
223
265
  * Implements factory pattern from unplugin Context7 research
224
266
  */
225
267
  async loadPlugin(pluginName) {
226
- const plugin = this.plugins.get(pluginName);
268
+ let plugin = this.plugins.get(pluginName);
269
+ if (!plugin && !pluginName.includes('/')) {
270
+ plugin = this.plugins.get(`${this.options.scopePrefix}/${pluginName}`);
271
+ }
227
272
 
228
273
  if (!plugin) {
229
274
  throw new Error(`Plugin not found: ${pluginName}`);
@@ -252,7 +297,7 @@ class PluginManager extends EventEmitter {
252
297
 
253
298
  this.emit('load:complete', {
254
299
  name: pluginName,
255
- agentCount: plugin.metadata.agents.length
300
+ agentCount: (plugin.metadata.agents || []).length
256
301
  });
257
302
 
258
303
  return plugin;
@@ -268,7 +313,7 @@ class PluginManager extends EventEmitter {
268
313
  async registerAgents(plugin) {
269
314
  const { metadata, path: pluginPath } = plugin;
270
315
 
271
- for (const agentMeta of metadata.agents) {
316
+ for (const agentMeta of (metadata.agents || [])) {
272
317
  const agentId = `${plugin.name}:${agentMeta.name}`;
273
318
  const agentFilePath = path.join(pluginPath, agentMeta.file);
274
319
 
@@ -302,7 +347,11 @@ class PluginManager extends EventEmitter {
302
347
  * Supports: agents, commands, rules, hooks, scripts (Schema v2.0)
303
348
  */
304
349
  async installPlugin(pluginName) {
305
- const plugin = this.plugins.get(pluginName);
350
+ // Normalize name: accept both "plugin-obsidian" and "@claudeautopm/plugin-obsidian"
351
+ let plugin = this.plugins.get(pluginName);
352
+ if (!plugin && !pluginName.includes('/')) {
353
+ plugin = this.plugins.get(`${this.options.scopePrefix}/${pluginName}`);
354
+ }
306
355
 
307
356
  if (!plugin) {
308
357
  throw new Error(`Plugin not found: ${pluginName}`);
@@ -451,6 +500,26 @@ class PluginManager extends EventEmitter {
451
500
  const installed = [];
452
501
 
453
502
  for (const command of metadata.commands) {
503
+ // Handle auto-discovery from subdirectory
504
+ if (command.subdirectory && command.discovery === 'auto') {
505
+ const commandsSourceDir = path.join(pluginPath, command.subdirectory);
506
+ if (fs.existsSync(commandsSourceDir)) {
507
+ const files = fs.readdirSync(commandsSourceDir).filter(f => f.endsWith('.md'));
508
+ for (const file of files) {
509
+ const sourcePath = path.join(commandsSourceDir, file);
510
+ const targetPath = path.join(targetDir, file);
511
+ if (!fs.existsSync(targetPath)) {
512
+ fs.copyFileSync(sourcePath, targetPath);
513
+ installed.push({ name: file.replace('.md', ''), file: targetPath });
514
+ this.emit('install:command', { command: file, path: targetPath });
515
+ }
516
+ }
517
+ }
518
+ continue;
519
+ }
520
+
521
+ // Handle individual command files
522
+ if (!command.file) continue;
454
523
  const sourcePath = path.join(pluginPath, command.file);
455
524
  const targetPath = path.join(targetDir, path.basename(command.file));
456
525
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-autopm",
3
- "version": "3.24.2",
3
+ "version": "3.25.2",
4
4
  "description": "Autonomous Project Management Framework for Claude Code - Advanced AI-powered development automation",
5
5
  "main": "bin/autopm.js",
6
6
  "workspaces": [
@@ -75,6 +75,15 @@ autopm obsidian sync --check # Dry-run (show what would sync)
75
75
  autopm obsidian sync --safe-mode # Don't delete vault files
76
76
  ```
77
77
 
78
+ ### `/obsidian:init` (Claude Code)
79
+
80
+ Generate project-aware vault files by reading your actual project structure. Unlike generic templates, this reads your real issues, epics, agents, and code to create tailored MOC, Dashboard, diagrams, and templates.
81
+
82
+ ```
83
+ /obsidian:init # Generate vault files
84
+ /obsidian:init --force # Overwrite existing files
85
+ ```
86
+
78
87
  ### `autopm obsidian doctor`
79
88
 
80
89
  Diagnose common integration problems:
@@ -0,0 +1,400 @@
1
+ ---
2
+ allowed-tools: Bash, Read, Write, Glob, Grep, LS
3
+ ---
4
+
5
+ # Obsidian Init
6
+
7
+ Generate project-aware Obsidian vault files by reading the actual project structure.
8
+
9
+ Unlike generic templates, this command reads your project's real content — issues, epics, PRDs, agents, rules, code structure — and generates MOC, Dashboard, templates, and diagrams tailored to what actually exists.
10
+
11
+ ## Usage
12
+
13
+ ```
14
+ /obsidian:init [--force]
15
+ ```
16
+
17
+ Options:
18
+ - `--force` — Overwrite existing vault files (default: skip if they exist)
19
+
20
+ ## Prerequisites
21
+
22
+ Run `autopm obsidian setup --vault-path "<path>"` first. This command reads the vault config from `.claude/config.json`.
23
+
24
+ ## Instructions
25
+
26
+ ### 1. Read Configuration
27
+
28
+ ```bash
29
+ # Get vault config
30
+ node -e "
31
+ const c = JSON.parse(require('fs').readFileSync('.claude/config.json','utf8'));
32
+ if (!c.obsidian || !c.obsidian.vault_path) { console.error('ERROR: Run autopm obsidian setup first'); process.exit(1); }
33
+ console.log(JSON.stringify(c.obsidian));
34
+ "
35
+ ```
36
+
37
+ Extract `vault_path` and `vault_prefix` from the output. The target directory for all generated files is `{vault_path}/{vault_prefix}/`.
38
+
39
+ ### 2. Discover Project Structure
40
+
41
+ Read the project to understand what exists. Gather:
42
+
43
+ **Issues/Tasks:**
44
+ - Glob for `issues/**/*.md` and `.claude/epics/**/*.md`
45
+ - Read frontmatter of each: extract `name`, `status`, `type`, `tags`, `github`
46
+ - Count by status: open, in-progress, closed
47
+
48
+ **PRDs:**
49
+ - Glob for `.claude/prds/**/*.md` or `prds/**/*.md`
50
+ - Read frontmatter: `name`, `status`
51
+
52
+ **Epics:**
53
+ - Glob for `.claude/epics/*/epic.md`
54
+ - Read frontmatter: `name`, `status`, `progress`
55
+
56
+ **Agents:**
57
+ - Glob for `.claude/agents/**/*.md` (exclude README, REGISTRY)
58
+ - Extract agent names and categories from directory structure
59
+
60
+ **Rules:**
61
+ - Glob for `.claude/rules/*.md`
62
+ - Extract rule names
63
+
64
+ **Commands:**
65
+ - Glob for `.claude/commands/*.md`
66
+ - Extract command names (the `pm:*`, `obsidian:*` prefixes)
67
+
68
+ **Code structure:**
69
+ - Check for common project indicators: `package.json`, `requirements.txt`, `go.mod`, `Cargo.toml`, `pom.xml`
70
+ - Read `package.json` if exists: extract `name`, `description`, `scripts`, main dependencies
71
+ - Check for `src/`, `lib/`, `app/`, `api/` directories
72
+ - Check for test directories: `tests/`, `test/`, `__tests__/`, `spec/`
73
+
74
+ ### 3. Generate MOC.md
75
+
76
+ Write `{vault_path}/{vault_prefix}/MOC.md` with content based on what was discovered.
77
+
78
+ The MOC (Map of Content) should be a navigation hub. Structure it as:
79
+
80
+ ```markdown
81
+ ---
82
+ type: moc
83
+ title: "{project_name} — Map of Content"
84
+ created: "{current_iso_date}"
85
+ updated: "{current_iso_date}"
86
+ tags: [moc, navigation]
87
+ ---
88
+
89
+ # {project_name} — Map of Content
90
+
91
+ > Auto-generated from project structure. Regenerate with `/obsidian:init`.
92
+
93
+ ## Project Overview
94
+
95
+ {One paragraph summary based on package.json description or README first paragraph}
96
+
97
+ Tech stack: {detected languages/frameworks}
98
+
99
+ ## Epics
100
+
101
+ {If epics exist, create a Dataview TABLE query. If no epics, write "No epics yet."}
102
+
103
+ ```dataview
104
+ TABLE status, progress
105
+ FROM "{prefix}/epics"
106
+ WHERE type = "epic"
107
+ SORT status ASC
108
+ ```
109
+
110
+ ### Active Epics
111
+ {List each discovered epic with a direct link: `- [[{prefix}/epics/{name}/epic|{title}]] — {status}`}
112
+
113
+ ## Issues & Tasks
114
+
115
+ {If issues exist, create Dataview queries grouped by status}
116
+
117
+ ```dataview
118
+ TABLE status, tags
119
+ FROM "{prefix}/issues"
120
+ WHERE status = "open" OR status = "in-progress"
121
+ SORT status ASC, file.mtime DESC
122
+ ```
123
+
124
+ **Summary:** {X} open, {Y} in-progress, {Z} closed
125
+
126
+ ## PRDs
127
+
128
+ {If PRDs exist, list them with links. Otherwise "No PRDs yet."}
129
+
130
+ ```dataview
131
+ TABLE status, created
132
+ FROM "{prefix}/prds"
133
+ SORT created DESC
134
+ ```
135
+
136
+ ## Agents ({count} available)
137
+
138
+ {Group agents by category with links}
139
+
140
+ ### Core
141
+ {List core agents}
142
+
143
+ ### Languages
144
+ {List language agents}
145
+
146
+ ### {Other categories}
147
+ {List other agents}
148
+
149
+ ## Rules ({count} active)
150
+
151
+ {List rules as a simple bullet list with links}
152
+
153
+ ## Commands ({count} available)
154
+
155
+ {Group commands by prefix: pm:*, obsidian:*, etc.}
156
+
157
+ ## Recent Changes
158
+
159
+ ```dataview
160
+ TABLE file.mtime as "Modified"
161
+ FROM "{prefix}"
162
+ SORT file.mtime DESC
163
+ LIMIT 15
164
+ ```
165
+ ```
166
+
167
+ ### 4. Generate DASHBOARD.md
168
+
169
+ Write `{vault_path}/{vault_prefix}/DASHBOARD.md` with project status overview:
170
+
171
+ ```markdown
172
+ ---
173
+ type: dashboard
174
+ title: "{project_name} Dashboard"
175
+ created: "{current_iso_date}"
176
+ updated: "{current_iso_date}"
177
+ tags: [dashboard, overview]
178
+ ---
179
+
180
+ # {project_name} Dashboard
181
+
182
+ > Auto-generated. Regenerate with `/obsidian:init`.
183
+
184
+ ## Status at a Glance
185
+
186
+ | Metric | Count |
187
+ |--------|-------|
188
+ | Open Issues | {actual_count} |
189
+ | In Progress | {actual_count} |
190
+ | Closed | {actual_count} |
191
+ | Epics | {actual_count} |
192
+ | PRDs | {actual_count} |
193
+ | Agents | {actual_count} |
194
+ | Rules | {actual_count} |
195
+
196
+ ## Open Work
197
+
198
+ ```dataview
199
+ TABLE status, tags, file.mtime as "Updated"
200
+ FROM "{prefix}/issues" OR "{prefix}/epics"
201
+ WHERE status = "open" OR status = "in-progress"
202
+ SORT status ASC, file.mtime DESC
203
+ ```
204
+
205
+ ## In Progress
206
+
207
+ ```dataview
208
+ LIST
209
+ FROM "{prefix}"
210
+ WHERE status = "in-progress"
211
+ SORT file.mtime DESC
212
+ ```
213
+
214
+ ## Recently Completed
215
+
216
+ ```dataview
217
+ TABLE status, file.mtime as "Completed"
218
+ FROM "{prefix}"
219
+ WHERE status = "closed" OR status = "completed"
220
+ SORT file.mtime DESC
221
+ LIMIT 10
222
+ ```
223
+
224
+ ## By Type
225
+
226
+ ```dataview
227
+ TABLE WITHOUT ID
228
+ type as "Type",
229
+ length(rows) as "Count"
230
+ FROM "{prefix}"
231
+ WHERE type
232
+ GROUP BY type
233
+ SORT length(rows) DESC
234
+ ```
235
+
236
+ ## Quick Links
237
+
238
+ - [[{prefix}/MOC|Map of Content]]
239
+ - [[{prefix}/agents|Agents]]
240
+ - [[{prefix}/rules|Rules]]
241
+ ```
242
+
243
+ ### 5. Generate _templates/
244
+
245
+ Create Obsidian Templater-compatible templates based on the project's actual frontmatter conventions.
246
+
247
+ Read 2-3 existing issue files to understand what fields are used. Then generate templates that match.
248
+
249
+ Write to `{vault_path}/{vault_prefix}/_templates/`:
250
+
251
+ **issue.md** — based on actual issue frontmatter fields found:
252
+ ```markdown
253
+ ---
254
+ type: issue
255
+ title: "<% tp.file.title %>"
256
+ status: open
257
+ created: "<% tp.date.now('YYYY-MM-DDTHH:mm:ss') %>Z"
258
+ updated: "<% tp.date.now('YYYY-MM-DDTHH:mm:ss') %>Z"
259
+ tags: [issue]
260
+ {any_other_fields_found_in_existing_issues}
261
+ ---
262
+
263
+ # <% tp.file.title %>
264
+
265
+ ## Description
266
+
267
+ ## Acceptance Criteria
268
+
269
+ - [ ]
270
+
271
+ ## Technical Details
272
+ ```
273
+
274
+ **prd.md** and **epic.md** — same approach, based on actual frontmatter conventions found.
275
+
276
+ ### 6. Generate Architecture Diagram
277
+
278
+ Write `{vault_path}/{vault_prefix}/diagrams/01-architecture.md` with a Mermaid diagram reflecting the actual project structure:
279
+
280
+ ```markdown
281
+ ---
282
+ type: diagram
283
+ title: "{project_name} Architecture"
284
+ tags: [diagram, mermaid, architecture]
285
+ ---
286
+
287
+ # {project_name} Architecture
288
+
289
+ > Auto-generated from project structure. Edit to match your actual architecture.
290
+
291
+ ```mermaid
292
+ graph TB
293
+ subgraph "Project: {project_name}"
294
+ {Generate nodes based on discovered directories}
295
+ {e.g., if src/api/ exists: API[API Layer]}
296
+ {e.g., if src/models/ exists: Models[Data Models]}
297
+ {Connect related components}
298
+ end
299
+
300
+ subgraph "ClaudeAutoPM"
301
+ PM[PM System]
302
+ Agents["{agent_count} Agents"]
303
+ Rules["{rule_count} Rules"]
304
+ end
305
+
306
+ subgraph "Obsidian Vault"
307
+ MOC[MOC.md]
308
+ Dashboard[DASHBOARD.md]
309
+ Issues["{issue_count} Issues"]
310
+ end
311
+
312
+ PM -->|sync| Issues
313
+ Agents -->|documented in| MOC
314
+ ```
315
+ ```
316
+
317
+ If the project is simple (no src/ structure), generate a simpler diagram showing just the ClaudeAutoPM + Obsidian relationship.
318
+
319
+ ### 7. Generate Excalidraw Whiteboard
320
+
321
+ Write `{vault_path}/{vault_prefix}/diagrams/pizarra.excalidraw.md`:
322
+
323
+ ```markdown
324
+ ---
325
+ type: diagram
326
+ title: Whiteboard
327
+ tags: [diagram, excalidraw, whiteboard]
328
+ excalidraw-plugin: parsed
329
+ ---
330
+
331
+ # Whiteboard
332
+
333
+ Open this file in Excalidraw to start drawing.
334
+
335
+ %%
336
+ # Excalidraw Data
337
+
338
+ ## Drawing
339
+ ```json
340
+ {
341
+ "type": "excalidraw",
342
+ "version": 2,
343
+ "source": "https://github.com/zsviczian/obsidian-excalidraw-plugin",
344
+ "elements": [],
345
+ "appState": {
346
+ "gridSize": null,
347
+ "viewBackgroundColor": "#ffffff"
348
+ },
349
+ "files": {}
350
+ }
351
+ ```
352
+ %%
353
+ ```
354
+
355
+ ### 8. Run Sync
356
+
357
+ After generating all files, run a sync to push everything to the vault:
358
+
359
+ ```bash
360
+ # Find and run sync script
361
+ if [ -f ".claude/scripts/obsidian/sync-to-obsidian.sh" ]; then
362
+ bash .claude/scripts/obsidian/sync-to-obsidian.sh
363
+ elif [ -f "packages/plugin-obsidian/scripts/obsidian/sync-to-obsidian.sh" ]; then
364
+ bash packages/plugin-obsidian/scripts/obsidian/sync-to-obsidian.sh
365
+ fi
366
+ ```
367
+
368
+ ### 9. Output Summary
369
+
370
+ ```
371
+ Obsidian vault initialized for {project_name}
372
+
373
+ Generated:
374
+ MOC.md — {X} sections, {Y} linked items
375
+ DASHBOARD.md — {open} open, {in_progress} in progress, {closed} closed
376
+ _templates/ — {N} templates based on project conventions
377
+ diagrams/ — architecture + whiteboard
378
+
379
+ Vault: {vault_path}/{prefix}/
380
+
381
+ Discovered:
382
+ Issues: {count} ({open} open, {in_progress} in progress, {closed} closed)
383
+ Epics: {count}
384
+ PRDs: {count}
385
+ Agents: {count} across {categories} categories
386
+ Rules: {count}
387
+ Commands: {count}
388
+
389
+ Next: Open the vault in Obsidian. Install Dataview plugin for live queries.
390
+ Resync: autopm obsidian sync
391
+ Regenerate: /obsidian:init --force
392
+ ```
393
+
394
+ ## Important Notes
395
+
396
+ - All Dataview queries use the vault prefix from `.claude/config.json`
397
+ - Generated files go directly to the vault path, not the project
398
+ - Sync runs after generation to ensure vault has latest project content
399
+ - Use `--force` to regenerate if project structure changes significantly
400
+ - The command is idempotent — safe to run multiple times