aiwg 2026.1.4 → 2026.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ # CI/CD Secrets Configuration
2
+
3
+ **Version:** 1.0
4
+ **Last Updated:** 2026-01-14
5
+ **Target Audience:** Repository maintainers and administrators
6
+
7
+ ## Overview
8
+
9
+ This document describes the secrets required for CI/CD workflows in the AIWG repository. Secrets are used for authentication with package registries and external services.
10
+
11
+ ## Required Secrets
12
+
13
+ ### NPM_TOKEN
14
+
15
+ **Purpose:** Authenticate with Gitea's npm package registry for publishing.
16
+
17
+ **Required Scopes:**
18
+ - `package:write` - Required to publish packages
19
+ - `package:read` - Required to verify published packages
20
+
21
+ **Used In:**
22
+ - `.gitea/workflows/npm-publish.yml` - Publishing to Gitea npm registry
23
+ - Creating Gitea releases via API
24
+
25
+ ### Setting Up NPM_TOKEN
26
+
27
+ #### Step 1: Create a Gitea Access Token
28
+
29
+ 1. Log in to [git.integrolabs.net](https://git.integrolabs.net)
30
+ 2. Navigate to **Settings** → **Applications** → **Access Tokens**
31
+ - Direct URL: https://git.integrolabs.net/user/settings/applications
32
+ 3. Create a new token with:
33
+ - **Token Name:** `ci-npm-publish` (or descriptive name)
34
+ - **Select Scopes:**
35
+ - ✅ `write:package` (includes read:package)
36
+ - ✅ `read:repository` (for checkout operations)
37
+ - **Expiration:** Set according to your security policy (recommend 1 year max)
38
+ 4. Click **Generate Token**
39
+ 5. **IMPORTANT:** Copy the token immediately - it won't be shown again
40
+
41
+ #### Step 2: Add Secret to Gitea Repository
42
+
43
+ 1. Navigate to the repository: https://git.integrolabs.net/roctinam/ai-writing-guide
44
+ 2. Go to **Settings** → **Actions** → **Secrets**
45
+ 3. Click **Add Secret**
46
+ 4. Configure:
47
+ - **Name:** `NPM_TOKEN`
48
+ - **Value:** Paste the token from Step 1
49
+ 5. Click **Add Secret**
50
+
51
+ #### Step 3: Verify Configuration
52
+
53
+ Trigger a manual workflow run to verify:
54
+
55
+ ```bash
56
+ # Push a test tag (can be deleted after)
57
+ git tag v9999.99.99-test
58
+ git push origin v9999.99.99-test
59
+
60
+ # Watch the workflow at:
61
+ # https://git.integrolabs.net/roctinam/ai-writing-guide/actions
62
+
63
+ # Clean up test tag
64
+ git tag -d v9999.99.99-test
65
+ git push origin :refs/tags/v9999.99.99-test
66
+ ```
67
+
68
+ Or use the workflow dispatch with dry_run enabled.
69
+
70
+ ## Troubleshooting
71
+
72
+ ### Error: 401 Unauthorized
73
+
74
+ ```
75
+ npm error code E401
76
+ npm error 401 Unauthorized - PUT https://git.integrolabs.net/api/packages/roctinam/npm/aiwg
77
+ ```
78
+
79
+ **Causes:**
80
+ 1. **Token expired** - Create a new token and update the secret
81
+ 2. **Token missing** - Verify NPM_TOKEN secret exists in repository settings
82
+ 3. **Wrong scopes** - Token must have `write:package` scope
83
+ 4. **Token revoked** - Check if token still exists in user settings
84
+
85
+ **Resolution:**
86
+ 1. Go to https://git.integrolabs.net/user/settings/applications
87
+ 2. Check if the token exists and hasn't expired
88
+ 3. If expired/missing, create a new token with `write:package` scope
89
+ 4. Update the repository secret with the new token
90
+
91
+ ### Error: 403 Forbidden
92
+
93
+ **Causes:**
94
+ 1. Token belongs to user without package write permissions
95
+ 2. Repository doesn't allow package publishing
96
+
97
+ **Resolution:**
98
+ 1. Ensure token owner has write access to the repository
99
+ 2. Check organization/repository package settings
100
+
101
+ ### Token Not Being Used
102
+
103
+ If the workflow isn't picking up the secret:
104
+
105
+ 1. Verify secret name is exactly `NPM_TOKEN` (case-sensitive)
106
+ 2. Check workflow file references `${{ secrets.NPM_TOKEN }}`
107
+ 3. Ensure workflow has appropriate permissions in `permissions:` block
108
+
109
+ ## Security Best Practices
110
+
111
+ ### Token Management
112
+
113
+ - **Rotation:** Rotate tokens annually or when team members leave
114
+ - **Scope:** Use minimum required scopes (write:package, read:repository)
115
+ - **Naming:** Use descriptive names like `ci-npm-publish-2026`
116
+ - **Audit:** Periodically review active tokens
117
+
118
+ ### Secret Storage
119
+
120
+ - Never commit tokens to the repository
121
+ - Use repository/organization secrets, not environment variables in code
122
+ - Don't echo or log token values in workflows
123
+
124
+ ### Workflow Security
125
+
126
+ ```yaml
127
+ # Good: Token passed via secrets
128
+ env:
129
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
130
+
131
+ # Bad: Token hardcoded or echoed
132
+ run: echo ${{ secrets.NPM_TOKEN }} # NEVER do this
133
+ ```
134
+
135
+ ## Workflow Architecture
136
+
137
+ ### npm-publish.yml Flow
138
+
139
+ ```
140
+ [Tag Push v*] → [Checkout] → [Configure npm] → [Build] → [Publish to Gitea] → [Verify]
141
+
142
+ Uses NPM_TOKEN for:
143
+ - .npmrc authentication
144
+ - npm publish command
145
+ - Gitea release API
146
+ ```
147
+
148
+ ### Secret Usage in Workflow
149
+
150
+ ```yaml
151
+ # .npmrc configuration (line 55-56)
152
+ //git.integrolabs.net/api/packages/roctinam/npm/:_authToken=${{ secrets.NPM_TOKEN }}
153
+
154
+ # Publish command (line 107-109)
155
+ npm publish --registry=${{ env.GITEA_NPM_REGISTRY }}
156
+ env:
157
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
158
+
159
+ # Release creation (line 137)
160
+ -H "Authorization: token ${{ secrets.NPM_TOKEN }}"
161
+ ```
162
+
163
+ ## Additional Secrets (Optional)
164
+
165
+ ### NPMJS_TOKEN (for public npm)
166
+
167
+ If publishing to public npmjs.org:
168
+
169
+ 1. Create token at https://www.npmjs.com/settings/tokens
170
+ 2. Select "Automation" token type
171
+ 3. Add as secret named `NPMJS_TOKEN`
172
+ 4. Update workflow to use separate token for public registry
173
+
174
+ ### GITHUB_TOKEN (for GitHub mirror)
175
+
176
+ For GitHub Actions (`.github/workflows/`):
177
+
178
+ - Automatically provided by GitHub Actions
179
+ - No manual configuration needed
180
+ - Used for GitHub Releases and npm publish to GitHub Packages
181
+
182
+ ## References
183
+
184
+ - [Gitea Package Registry Documentation](https://docs.gitea.com/usage/packages/npm)
185
+ - [Gitea Actions Secrets](https://docs.gitea.com/usage/actions/secrets)
186
+ - [npm Authentication](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
187
+ - @.gitea/workflows/npm-publish.yml - Main publish workflow
188
+ - @.claude/rules/token-security.md - Token security rules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiwg",
3
- "version": "2026.01.4",
3
+ "version": "2026.01.5",
4
4
  "description": "Modular agentic framework for AI-powered SDLC, marketing automation, and workflow orchestration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -568,6 +568,136 @@ export function filterAgentFiles(files, opts) {
568
568
  });
569
569
  }
570
570
 
571
+ // ============================================================================
572
+ // Addon Discovery
573
+ // ============================================================================
574
+
575
+ /**
576
+ * Discover all addons in the agentic/code/addons directory
577
+ * @param {string} srcRoot - Source root directory
578
+ * @returns {Array<{name: string, path: string, manifest: object}>} - Array of addon info
579
+ */
580
+ export function discoverAddons(srcRoot) {
581
+ const addonsDir = path.join(srcRoot, 'agentic', 'code', 'addons');
582
+ if (!fs.existsSync(addonsDir)) return [];
583
+
584
+ const addons = [];
585
+ for (const entry of fs.readdirSync(addonsDir, { withFileTypes: true })) {
586
+ if (!entry.isDirectory()) continue;
587
+
588
+ const addonPath = path.join(addonsDir, entry.name);
589
+ const manifestPath = path.join(addonPath, 'manifest.json');
590
+
591
+ let manifest = {};
592
+ if (fs.existsSync(manifestPath)) {
593
+ try {
594
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
595
+ } catch (e) {
596
+ console.warn(`Warning: Could not parse manifest for addon ${entry.name}: ${e.message}`);
597
+ }
598
+ }
599
+
600
+ addons.push({
601
+ name: entry.name,
602
+ path: addonPath,
603
+ manifest
604
+ });
605
+ }
606
+
607
+ return addons;
608
+ }
609
+
610
+ /**
611
+ * Get all agent files from all addons
612
+ * @param {string} srcRoot - Source root directory
613
+ * @param {string[]} excludeAddons - Addon names to exclude (default: none)
614
+ * @returns {string[]} - Array of agent file paths
615
+ */
616
+ export function getAddonAgentFiles(srcRoot, excludeAddons = []) {
617
+ const addons = discoverAddons(srcRoot);
618
+ const files = [];
619
+
620
+ for (const addon of addons) {
621
+ if (excludeAddons.includes(addon.name)) continue;
622
+
623
+ const agentsDir = path.join(addon.path, 'agents');
624
+ if (fs.existsSync(agentsDir)) {
625
+ files.push(...listMdFiles(agentsDir));
626
+ }
627
+ }
628
+
629
+ return files;
630
+ }
631
+
632
+ /**
633
+ * Get all command files from all addons
634
+ * @param {string} srcRoot - Source root directory
635
+ * @param {string[]} excludeAddons - Addon names to exclude (default: none)
636
+ * @returns {string[]} - Array of command file paths
637
+ */
638
+ export function getAddonCommandFiles(srcRoot, excludeAddons = []) {
639
+ const addons = discoverAddons(srcRoot);
640
+ const files = [];
641
+
642
+ for (const addon of addons) {
643
+ if (excludeAddons.includes(addon.name)) continue;
644
+
645
+ const commandsDir = path.join(addon.path, 'commands');
646
+ if (fs.existsSync(commandsDir)) {
647
+ files.push(...listMdFiles(commandsDir));
648
+ }
649
+ }
650
+
651
+ return files;
652
+ }
653
+
654
+ /**
655
+ * Get all skill directories from all addons
656
+ * @param {string} srcRoot - Source root directory
657
+ * @param {string[]} excludeAddons - Addon names to exclude (default: none)
658
+ * @returns {string[]} - Array of skill directory paths
659
+ */
660
+ export function getAddonSkillDirs(srcRoot, excludeAddons = []) {
661
+ const addons = discoverAddons(srcRoot);
662
+ const dirs = [];
663
+
664
+ for (const addon of addons) {
665
+ if (excludeAddons.includes(addon.name)) continue;
666
+
667
+ const skillsDir = path.join(addon.path, 'skills');
668
+ if (fs.existsSync(skillsDir)) {
669
+ dirs.push(...listSkillDirs(skillsDir));
670
+ }
671
+ }
672
+
673
+ return dirs;
674
+ }
675
+
676
+ /**
677
+ * Get addon files by category (agents, commands, skills)
678
+ * @param {string} srcRoot - Source root directory
679
+ * @param {object} options - Options
680
+ * @param {string[]} options.excludeAddons - Addon names to exclude
681
+ * @param {boolean} options.includeAgents - Include agent files (default: true)
682
+ * @param {boolean} options.includeCommands - Include command files (default: true)
683
+ * @param {boolean} options.includeSkills - Include skill directories (default: true)
684
+ * @returns {{agents: string[], commands: string[], skills: string[]}}
685
+ */
686
+ export function getAddonFiles(srcRoot, options = {}) {
687
+ const {
688
+ excludeAddons = [],
689
+ includeAgents = true,
690
+ includeCommands = true,
691
+ includeSkills = true
692
+ } = options;
693
+
694
+ return {
695
+ agents: includeAgents ? getAddonAgentFiles(srcRoot, excludeAddons) : [],
696
+ commands: includeCommands ? getAddonCommandFiles(srcRoot, excludeAddons) : [],
697
+ skills: includeSkills ? getAddonSkillDirs(srcRoot, excludeAddons) : []
698
+ };
699
+ }
700
+
571
701
  // ============================================================================
572
702
  // Provider Interface
573
703
  // ============================================================================
@@ -23,7 +23,10 @@ import {
23
23
  deploySkillDir,
24
24
  parseFrontmatter,
25
25
  initializeFrameworkWorkspace,
26
- filterAgentFiles
26
+ filterAgentFiles,
27
+ getAddonAgentFiles,
28
+ getAddonCommandFiles,
29
+ getAddonSkillDirs
27
30
  } from './base.mjs';
28
31
 
29
32
  // ============================================================================
@@ -281,19 +284,16 @@ export async function deploy(opts) {
281
284
  }
282
285
  }
283
286
 
284
- // Writing/general agents
285
- if (mode === 'general' || mode === 'writing' || mode === 'both' || mode === 'all') {
286
- const writingAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'agents');
287
- agentFiles.push(...listMdFiles(writingAgentsDir));
287
+ // All addons (dynamically discovered)
288
+ if (mode === 'general' || mode === 'writing' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
289
+ agentFiles.push(...getAddonAgentFiles(srcRoot));
288
290
 
289
291
  if (shouldDeployCommands || commandsOnly) {
290
- const writingCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'commands');
291
- commandFiles.push(...listMdFiles(writingCommandsDir));
292
+ commandFiles.push(...getAddonCommandFiles(srcRoot));
292
293
  }
293
294
 
294
295
  if (shouldDeploySkills || skillsOnly) {
295
- const writingSkillsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'skills');
296
- skillDirs.push(...listSkillDirs(writingSkillsDir));
296
+ skillDirs.push(...getAddonSkillDirs(srcRoot));
297
297
  }
298
298
  }
299
299
 
@@ -329,28 +329,6 @@ export async function deploy(opts) {
329
329
  }
330
330
  }
331
331
 
332
- // Utils addon (always deployed with sdlc or all)
333
- if (mode === 'sdlc' || mode === 'all') {
334
- const utilsAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'agents');
335
- agentFiles.push(...listMdFiles(utilsAgentsDir));
336
-
337
- if (shouldDeployCommands || commandsOnly) {
338
- const utilsCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'commands');
339
- commandFiles.push(...listMdFiles(utilsCommandsDir));
340
- }
341
-
342
- if (shouldDeploySkills || skillsOnly) {
343
- const utilsSkillsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'skills');
344
- skillDirs.push(...listSkillDirs(utilsSkillsDir));
345
- }
346
- }
347
-
348
- // Voice framework skills (always deployed)
349
- if (shouldDeploySkills || skillsOnly) {
350
- const voiceSkillsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'voice-framework', 'skills');
351
- skillDirs.push(...listSkillDirs(voiceSkillsDir));
352
- }
353
-
354
332
  // Deploy based on flags
355
333
  if (!commandsOnly && !skillsOnly) {
356
334
  // Apply filters if specified
@@ -26,7 +26,10 @@ import {
26
26
  writeFile,
27
27
  deployFiles,
28
28
  createAgentsMdFromTemplate,
29
- initializeFrameworkWorkspace
29
+ initializeFrameworkWorkspace,
30
+ getAddonAgentFiles,
31
+ getAddonCommandFiles,
32
+ getAddonSkillDirs
30
33
  } from './base.mjs';
31
34
 
32
35
  // ============================================================================
@@ -298,28 +301,20 @@ export async function deploy(opts) {
298
301
  // Collect source files based on mode
299
302
  const agentFiles = [];
300
303
 
301
- // Writing/general agents
302
- if (mode === 'general' || mode === 'writing' || mode === 'both' || mode === 'all') {
303
- const writingAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'agents');
304
- agentFiles.push(...listMdFiles(writingAgentsDir));
305
- }
306
-
307
- // SDLC framework
304
+ // Frameworks
308
305
  if (mode === 'sdlc' || mode === 'both' || mode === 'all') {
309
306
  const sdlcAgentsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'agents');
310
307
  agentFiles.push(...listMdFiles(sdlcAgentsDir));
311
308
  }
312
309
 
313
- // Marketing framework
314
310
  if (mode === 'marketing' || mode === 'all') {
315
311
  const marketingAgentsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'media-marketing-kit', 'agents');
316
312
  agentFiles.push(...listMdFiles(marketingAgentsDir));
317
313
  }
318
314
 
319
- // Utils addon
320
- if (mode === 'sdlc' || mode === 'all') {
321
- const utilsAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'agents');
322
- agentFiles.push(...listMdFiles(utilsAgentsDir));
315
+ // All addons (dynamically discovered)
316
+ if (mode === 'general' || mode === 'writing' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
317
+ agentFiles.push(...getAddonAgentFiles(srcRoot));
323
318
  }
324
319
 
325
320
  // Deploy based on flags
@@ -24,7 +24,9 @@ import {
24
24
  deployFiles,
25
25
  inferAgentCategory,
26
26
  toKebabCase,
27
- initializeFrameworkWorkspace
27
+ initializeFrameworkWorkspace,
28
+ getAddonAgentFiles,
29
+ getAddonCommandFiles
28
30
  } from './base.mjs';
29
31
 
30
32
  // ============================================================================
@@ -348,17 +350,16 @@ export async function deploy(opts) {
348
350
  const agentFiles = [];
349
351
  const commandFiles = [];
350
352
 
351
- // Collect files based on mode
352
- if (mode === 'general' || mode === 'writing' || mode === 'both' || mode === 'all') {
353
- const writingAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'agents');
354
- agentFiles.push(...listMdFiles(writingAgentsDir));
353
+ // All addons (dynamically discovered)
354
+ if (mode === 'general' || mode === 'writing' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
355
+ agentFiles.push(...getAddonAgentFiles(srcRoot));
355
356
 
356
357
  if (shouldDeployCommands || commandsOnly) {
357
- const writingCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'commands');
358
- commandFiles.push(...listMdFiles(writingCommandsDir));
358
+ commandFiles.push(...getAddonCommandFiles(srcRoot));
359
359
  }
360
360
  }
361
361
 
362
+ // Frameworks
362
363
  if (mode === 'sdlc' || mode === 'both' || mode === 'all') {
363
364
  const sdlcAgentsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'agents');
364
365
  agentFiles.push(...listMdFiles(sdlcAgentsDir));
@@ -379,16 +380,6 @@ export async function deploy(opts) {
379
380
  }
380
381
  }
381
382
 
382
- if (mode === 'sdlc' || mode === 'all') {
383
- const utilsAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'agents');
384
- agentFiles.push(...listMdFiles(utilsAgentsDir));
385
-
386
- if (shouldDeployCommands || commandsOnly) {
387
- const utilsCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'commands');
388
- commandFiles.push(...listMdFiles(utilsCommandsDir));
389
- }
390
- }
391
-
392
383
  // Deploy
393
384
  if (!commandsOnly) {
394
385
  console.log(`\nDeploying ${agentFiles.length} agents (YAML format)...`);
@@ -26,7 +26,9 @@ import {
26
26
  toKebabCase,
27
27
  stripJsonComments,
28
28
  createAgentsMdFromTemplate,
29
- initializeFrameworkWorkspace
29
+ initializeFrameworkWorkspace,
30
+ getAddonAgentFiles,
31
+ getAddonCommandFiles
30
32
  } from './base.mjs';
31
33
 
32
34
  // ============================================================================
@@ -455,14 +457,12 @@ export async function deploy(opts) {
455
457
  const agentFiles = [];
456
458
  const commandFiles = [];
457
459
 
458
- // Writing/general agents
459
- if (mode === 'general' || mode === 'writing' || mode === 'both' || mode === 'all') {
460
- const writingAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'agents');
461
- agentFiles.push(...listMdFiles(writingAgentsDir));
460
+ // All addons (dynamically discovered)
461
+ if (mode === 'general' || mode === 'writing' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
462
+ agentFiles.push(...getAddonAgentFiles(srcRoot));
462
463
 
463
464
  if (shouldDeployCommands || commandsOnly) {
464
- const writingCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'commands');
465
- commandFiles.push(...listMdFiles(writingCommandsDir));
465
+ commandFiles.push(...getAddonCommandFiles(srcRoot));
466
466
  }
467
467
  }
468
468
 
@@ -488,17 +488,6 @@ export async function deploy(opts) {
488
488
  }
489
489
  }
490
490
 
491
- // Utils addon
492
- if (mode === 'sdlc' || mode === 'all') {
493
- const utilsAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'agents');
494
- agentFiles.push(...listMdFiles(utilsAgentsDir));
495
-
496
- if (shouldDeployCommands || commandsOnly) {
497
- const utilsCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'commands');
498
- commandFiles.push(...listMdFiles(utilsCommandsDir));
499
- }
500
- }
501
-
502
491
  // Deploy based on flags
503
492
  if (!commandsOnly) {
504
493
  console.log(`\nDeploying ${agentFiles.length} agents as droids...`);
@@ -23,7 +23,9 @@ import {
23
23
  deployFiles,
24
24
  inferAgentCategory,
25
25
  createAgentsMdFromTemplate,
26
- initializeFrameworkWorkspace
26
+ initializeFrameworkWorkspace,
27
+ getAddonAgentFiles,
28
+ getAddonCommandFiles
27
29
  } from './base.mjs';
28
30
 
29
31
  // ============================================================================
@@ -317,17 +319,16 @@ export async function deploy(opts) {
317
319
  const agentFiles = [];
318
320
  const commandFiles = [];
319
321
 
320
- // Collect files based on mode
321
- if (mode === 'general' || mode === 'writing' || mode === 'both' || mode === 'all') {
322
- const writingAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'agents');
323
- agentFiles.push(...listMdFiles(writingAgentsDir));
322
+ // All addons (dynamically discovered)
323
+ if (mode === 'general' || mode === 'writing' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
324
+ agentFiles.push(...getAddonAgentFiles(srcRoot));
324
325
 
325
326
  if (shouldDeployCommands || commandsOnly) {
326
- const writingCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'commands');
327
- commandFiles.push(...listMdFiles(writingCommandsDir));
327
+ commandFiles.push(...getAddonCommandFiles(srcRoot));
328
328
  }
329
329
  }
330
330
 
331
+ // Frameworks
331
332
  if (mode === 'sdlc' || mode === 'both' || mode === 'all') {
332
333
  const sdlcAgentsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'agents');
333
334
  agentFiles.push(...listMdFiles(sdlcAgentsDir));
@@ -348,16 +349,6 @@ export async function deploy(opts) {
348
349
  }
349
350
  }
350
351
 
351
- if (mode === 'sdlc' || mode === 'all') {
352
- const utilsAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'agents');
353
- agentFiles.push(...listMdFiles(utilsAgentsDir));
354
-
355
- if (shouldDeployCommands || commandsOnly) {
356
- const utilsCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'commands');
357
- commandFiles.push(...listMdFiles(utilsCommandsDir));
358
- }
359
- }
360
-
361
352
  // Deploy
362
353
  if (!commandsOnly) {
363
354
  console.log(`\nDeploying ${agentFiles.length} agents...`);
@@ -22,7 +22,9 @@ import {
22
22
  ensureDir,
23
23
  listMdFiles,
24
24
  listMdFilesRecursive,
25
- initializeFrameworkWorkspace
25
+ initializeFrameworkWorkspace,
26
+ getAddonAgentFiles,
27
+ getAddonCommandFiles
26
28
  } from './base.mjs';
27
29
 
28
30
  // ============================================================================
@@ -400,30 +402,22 @@ export async function deploy(opts) {
400
402
  // Collect all agent files based on mode
401
403
  const allAgentFiles = [];
402
404
 
403
- // Writing agents
404
- if (mode === 'general' || mode === 'writing' || mode === 'both' || mode === 'all') {
405
- const writingAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'agents');
406
- allAgentFiles.push(...listMdFiles(writingAgentsDir));
405
+ // All addons (dynamically discovered)
406
+ if (mode === 'general' || mode === 'writing' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
407
+ allAgentFiles.push(...getAddonAgentFiles(srcRoot));
407
408
  }
408
409
 
409
- // SDLC agents
410
+ // Frameworks
410
411
  if (mode === 'sdlc' || mode === 'both' || mode === 'all') {
411
412
  const sdlcAgentsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'agents');
412
413
  allAgentFiles.push(...listMdFiles(sdlcAgentsDir));
413
414
  }
414
415
 
415
- // Marketing agents
416
416
  if (mode === 'marketing' || mode === 'all') {
417
417
  const marketingAgentsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'media-marketing-kit', 'agents');
418
418
  allAgentFiles.push(...listMdFiles(marketingAgentsDir));
419
419
  }
420
420
 
421
- // Utils addon agents
422
- if (mode === 'sdlc' || mode === 'all') {
423
- const utilsAgentsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'agents');
424
- allAgentFiles.push(...listMdFiles(utilsAgentsDir));
425
- }
426
-
427
421
  // Generate aggregated AGENTS.md
428
422
  if (allAgentFiles.length > 0 && !commandsOnly && !skillsOnly) {
429
423
  const agentsMdPath = path.join(target, 'AGENTS.md');
@@ -442,11 +436,12 @@ export async function deploy(opts) {
442
436
  // Collect command files based on mode
443
437
  const commandFiles = [];
444
438
 
445
- if (mode === 'general' || mode === 'both' || mode === 'all') {
446
- const writingCommandsDir = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'commands');
447
- commandFiles.push(...listMdFiles(writingCommandsDir));
439
+ // All addons (dynamically discovered)
440
+ if (mode === 'general' || mode === 'writing' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
441
+ commandFiles.push(...getAddonCommandFiles(srcRoot));
448
442
  }
449
443
 
444
+ // Frameworks
450
445
  if (mode === 'sdlc' || mode === 'both' || mode === 'all') {
451
446
  const sdlcCommandsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'commands');
452
447
  commandFiles.push(...listMdFilesRecursive(sdlcCommandsDir));