aiwg 2026.1.5 → 2026.1.6

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/CLAUDE.md CHANGED
@@ -190,7 +190,9 @@ Before pushing a version tag:
190
190
 
191
191
  ### Version Format
192
192
 
193
- - **CalVer**: `YYYY.MM.PATCH` (e.g., `2026.01.3`)
193
+ - **CalVer**: `YYYY.M.PATCH` (e.g., `2026.1.5`, `2026.12.0`)
194
+ - **CRITICAL**: No leading zeros! npm semver rejects `01`, `02`, etc.
194
195
  - PATCH resets each month
195
- - Tag format: `vYYYY.MM.PATCH` (e.g., `v2026.01.3`)
196
+ - Tag format: `vYYYY.M.PATCH` (e.g., `v2026.1.5`)
197
+ - See `@docs/contributing/versioning.md` for full details
196
198
 
@@ -0,0 +1,196 @@
1
+ # Versioning Guide
2
+
3
+ **Version:** 1.0
4
+ **Last Updated:** 2026-01-14
5
+ **Target Audience:** All contributors and AI agents
6
+
7
+ ## Overview
8
+
9
+ AIWG uses **Calendar Versioning (CalVer)** with npm-compatible format. This document explains the versioning scheme and critical rules to avoid npm publishing failures.
10
+
11
+ ## Version Format
12
+
13
+ ```
14
+ YYYY.M.PATCH
15
+ ```
16
+
17
+ | Component | Description | Example |
18
+ |-----------|-------------|---------|
19
+ | `YYYY` | Four-digit year | `2026` |
20
+ | `M` | Month (1-12, **NO leading zeros**) | `1`, `12` |
21
+ | `PATCH` | Patch number within month (resets each month) | `0`, `1`, `5` |
22
+
23
+ ### Examples
24
+
25
+ | Correct | Incorrect | Why |
26
+ |---------|-----------|-----|
27
+ | `2026.1.0` | `2026.01.0` | Leading zero in month |
28
+ | `2026.1.5` | `2026.01.05` | Leading zeros in month and patch |
29
+ | `2026.12.0` | `2026.12.00` | Leading zero in patch |
30
+
31
+ ## Critical Rule: No Leading Zeros
32
+
33
+ **npm's semver parser rejects leading zeros.** This is per the [Semantic Versioning spec](https://semver.org/):
34
+
35
+ > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and **MUST NOT contain leading zeroes**.
36
+
37
+ ### What Happens With Leading Zeros
38
+
39
+ ```bash
40
+ # This FAILS
41
+ $ npm -g update aiwg
42
+ npm error Invalid Version: 2026.01.4
43
+
44
+ # This WORKS (same package, different command)
45
+ $ npm -g install aiwg
46
+ # Installs successfully but update is broken
47
+ ```
48
+
49
+ The `npm install` command is more lenient than `npm update`. Users will be able to install but not update, causing confusion and support issues.
50
+
51
+ ## Tag Format
52
+
53
+ Git tags should match the version with a `v` prefix:
54
+
55
+ ```bash
56
+ # Correct
57
+ git tag -a v2026.1.5 -m "v2026.1.5 - Feature Name"
58
+
59
+ # Incorrect
60
+ git tag -a v2026.01.5 -m "v2026.01.5 - Feature Name"
61
+ ```
62
+
63
+ ## Release Workflow
64
+
65
+ ### 1. Update package.json
66
+
67
+ ```json
68
+ {
69
+ "version": "2026.1.5"
70
+ }
71
+ ```
72
+
73
+ **Validation**: Run this to check for leading zeros:
74
+
75
+ ```bash
76
+ grep '"version"' package.json | grep -E '\.[0-9]{2}\.' && echo "ERROR: Leading zero detected!" || echo "OK: No leading zeros"
77
+ ```
78
+
79
+ ### 2. Update CHANGELOG.md
80
+
81
+ ```markdown
82
+ ## [2026.1.5] - 2026-01-14 – "Release Name"
83
+ ```
84
+
85
+ ### 3. Create and Push Tag
86
+
87
+ ```bash
88
+ # Create annotated tag
89
+ git tag -a v2026.1.5 -m "v2026.1.5 - Release Name"
90
+
91
+ # Push to both remotes
92
+ git push origin main --tags
93
+ git push github main --tags
94
+ ```
95
+
96
+ ### 4. Verify Published Version
97
+
98
+ After CI/CD completes:
99
+
100
+ ```bash
101
+ npm view aiwg version
102
+ # Should show: 2026.1.5
103
+ ```
104
+
105
+ ## Version Progression Examples
106
+
107
+ ### Within a Month
108
+
109
+ ```
110
+ 2026.1.0 → First release in January 2026
111
+ 2026.1.1 → Bug fix
112
+ 2026.1.2 → Another fix
113
+ 2026.1.3 → Feature addition
114
+ ```
115
+
116
+ ### Month Transitions
117
+
118
+ ```
119
+ 2026.1.5 → Last release in January
120
+ 2026.2.0 → First release in February (PATCH resets)
121
+ 2026.2.1 → Next release in February
122
+ ```
123
+
124
+ ### Year Transitions
125
+
126
+ ```
127
+ 2026.12.3 → December release
128
+ 2027.1.0 → January of next year
129
+ ```
130
+
131
+ ## Automated Validation
132
+
133
+ ### Pre-commit Hook (Optional)
134
+
135
+ Add to `.git/hooks/pre-commit`:
136
+
137
+ ```bash
138
+ #!/bin/bash
139
+ VERSION=$(grep '"version"' package.json | head -1)
140
+ if echo "$VERSION" | grep -qE '\.[0-9]{2}\.'; then
141
+ echo "ERROR: package.json version has leading zeros!"
142
+ echo "Found: $VERSION"
143
+ echo "Fix: Remove leading zeros (e.g., 2026.01.5 → 2026.1.5)"
144
+ exit 1
145
+ fi
146
+ ```
147
+
148
+ ### CI Validation
149
+
150
+ The npm publish workflow will fail if the version has leading zeros, but it's better to catch this before pushing.
151
+
152
+ ## Common Mistakes
153
+
154
+ ### Mistake 1: Copy-Paste from Dates
155
+
156
+ ```bash
157
+ # Today is January 5, 2026
158
+ # WRONG: Using date format
159
+ 2026.01.05
160
+
161
+ # RIGHT: Using CalVer format
162
+ 2026.1.5
163
+ ```
164
+
165
+ ### Mistake 2: Assuming Two-Digit Month
166
+
167
+ ```bash
168
+ # WRONG: Padding single-digit months
169
+ 2026.01.0, 2026.02.0, ..., 2026.09.0
170
+
171
+ # RIGHT: No padding
172
+ 2026.1.0, 2026.2.0, ..., 2026.9.0
173
+ ```
174
+
175
+ ### Mistake 3: Incrementing Without Checking Format
176
+
177
+ When bumping versions, always verify the format:
178
+
179
+ ```bash
180
+ # Before: 2026.1.4
181
+ # Bumping patch...
182
+
183
+ # WRONG (if you typed it manually)
184
+ "version": "2026.01.5"
185
+
186
+ # RIGHT
187
+ "version": "2026.1.5"
188
+ ```
189
+
190
+ ## References
191
+
192
+ - [Semantic Versioning 2.0.0](https://semver.org/)
193
+ - [Calendar Versioning](https://calver.org/)
194
+ - [npm semver](https://docs.npmjs.com/cli/v6/using-npm/semver)
195
+ - @CLAUDE.md - Release Documentation Requirements
196
+ - @docs/contributing/ci-cd-secrets.md - CI/CD configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiwg",
3
- "version": "2026.01.5",
3
+ "version": "2026.1.6",
4
4
  "description": "Modular agentic framework for AI-powered SDLC, marketing automation, and workflow orchestration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -606,17 +606,24 @@ async function main() {
606
606
  // Collect agent files based on mode
607
607
  const agentFiles = [];
608
608
 
609
- // Writing/General agents
610
- if (mode === 'general' || mode === 'writing' || mode === 'both' || mode === 'all') {
611
- const writingAddonAgentsRoot = path.join(srcRoot, 'agentic', 'code', 'addons', 'writing-quality', 'agents');
612
- const legacyAgentsRoot = path.join(srcRoot, 'agents');
613
- const agentsRoot = fs.existsSync(writingAddonAgentsRoot) ? writingAddonAgentsRoot : legacyAgentsRoot;
614
-
615
- if (fs.existsSync(agentsRoot)) {
616
- const files = listMdFiles(agentsRoot);
617
- if (files.length > 0) {
618
- console.log(`Found ${files.length} writing/general agents`);
619
- agentFiles.push(...files);
609
+ // All addons (dynamically discovered)
610
+ if (mode === 'general' || mode === 'writing' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
611
+ const addonsRoot = path.join(srcRoot, 'agentic', 'code', 'addons');
612
+ if (fs.existsSync(addonsRoot)) {
613
+ let addonAgentCount = 0;
614
+ const addonDirs = fs.readdirSync(addonsRoot, { withFileTypes: true })
615
+ .filter(e => e.isDirectory())
616
+ .map(e => path.join(addonsRoot, e.name, 'agents'));
617
+
618
+ for (const addonAgentsDir of addonDirs) {
619
+ if (fs.existsSync(addonAgentsDir)) {
620
+ const files = listMdFiles(addonAgentsDir);
621
+ agentFiles.push(...files);
622
+ addonAgentCount += files.length;
623
+ }
624
+ }
625
+ if (addonAgentCount > 0) {
626
+ console.log(`Found ${addonAgentCount} addon agents`);
620
627
  }
621
628
  }
622
629
  }
@@ -693,6 +700,20 @@ async function main() {
693
700
  // Collect skills count for .windsurfrules
694
701
  let skillCount = 0;
695
702
  if (deploySkills) {
703
+ // Addon skills (dynamically discovered)
704
+ const addonsRoot = path.join(srcRoot, 'agentic', 'code', 'addons');
705
+ if (fs.existsSync(addonsRoot)) {
706
+ const addonDirs = fs.readdirSync(addonsRoot, { withFileTypes: true })
707
+ .filter(e => e.isDirectory())
708
+ .map(e => path.join(addonsRoot, e.name, 'skills'));
709
+
710
+ for (const addonSkillsDir of addonDirs) {
711
+ if (fs.existsSync(addonSkillsDir)) {
712
+ skillCount += listSkillDirs(addonSkillsDir).length;
713
+ }
714
+ }
715
+ }
716
+
696
717
  // SDLC skills
697
718
  if (mode === 'sdlc' || mode === 'both' || mode === 'all') {
698
719
  const sdlcSkillsRoot = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'skills');
@@ -701,16 +722,9 @@ async function main() {
701
722
  }
702
723
  }
703
724
 
704
- // Utils addon skills
705
- const utilsSkillsRoot = path.join(srcRoot, 'agentic', 'code', 'addons', 'aiwg-utils', 'skills');
706
- if (fs.existsSync(utilsSkillsRoot)) {
707
- skillCount += listSkillDirs(utilsSkillsRoot).length;
708
- }
709
-
710
725
  if (skillCount > 0) {
711
726
  console.log(`\n[NOTE] Skills (${skillCount} found) are not directly deployed to Windsurf.`);
712
- console.log('Reference skill files in prompts using @-mentions:');
713
- console.log(' @~/.local/share/ai-writing-guide/agentic/code/addons/aiwg-utils/skills/<skill>/SKILL.md');
727
+ console.log('Reference skill files in prompts using @-mentions.');
714
728
  }
715
729
  }
716
730
 
@@ -201,6 +201,22 @@ function getCommandDirectories(srcRoot, mode) {
201
201
  }
202
202
  }
203
203
 
204
+ // Addon commands (dynamically discovered)
205
+ if (mode === 'general' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
206
+ const addonsRoot = path.join(srcRoot, 'agentic', 'code', 'addons');
207
+ if (fs.existsSync(addonsRoot)) {
208
+ const addonDirs = fs.readdirSync(addonsRoot, { withFileTypes: true })
209
+ .filter(e => e.isDirectory())
210
+ .map(e => path.join(addonsRoot, e.name, 'commands'));
211
+
212
+ for (const addonCommandsDir of addonDirs) {
213
+ if (fs.existsSync(addonCommandsDir)) {
214
+ dirs.push({ dir: addonCommandsDir, label: path.basename(path.dirname(addonCommandsDir)) });
215
+ }
216
+ }
217
+ }
218
+ }
219
+
204
220
  // SDLC framework commands
205
221
  if (mode === 'sdlc' || mode === 'both' || mode === 'all') {
206
222
  const sdlcCommandsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'commands');
@@ -241,6 +241,22 @@ function getCommandDirectories(srcRoot, mode) {
241
241
  }
242
242
  }
243
243
 
244
+ // Addon commands (dynamically discovered)
245
+ if (mode === 'general' || mode === 'sdlc' || mode === 'both' || mode === 'all') {
246
+ const addonsRoot = path.join(srcRoot, 'agentic', 'code', 'addons');
247
+ if (fs.existsSync(addonsRoot)) {
248
+ const addonDirs = fs.readdirSync(addonsRoot, { withFileTypes: true })
249
+ .filter(e => e.isDirectory())
250
+ .map(e => path.join(addonsRoot, e.name, 'commands'));
251
+
252
+ for (const addonCommandsDir of addonDirs) {
253
+ if (fs.existsSync(addonCommandsDir)) {
254
+ dirs.push({ dir: addonCommandsDir, label: path.basename(path.dirname(addonCommandsDir)) });
255
+ }
256
+ }
257
+ }
258
+ }
259
+
244
260
  // SDLC framework commands
245
261
  if (mode === 'sdlc' || mode === 'both' || mode === 'all') {
246
262
  const sdlcCommandsDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'commands');
@@ -279,6 +279,21 @@ function generateAIWGContent(aiwgRoot, mode) {
279
279
  agentPaths.push(...listMdFiles(generalAgents));
280
280
  }
281
281
  }
282
+
283
+ // Addon agents (dynamically discovered)
284
+ const addonsRoot = path.join(aiwgRoot, 'agentic', 'code', 'addons');
285
+ if (fs.existsSync(addonsRoot)) {
286
+ const addonDirs = fs.readdirSync(addonsRoot, { withFileTypes: true })
287
+ .filter(e => e.isDirectory())
288
+ .map(e => path.join(addonsRoot, e.name, 'agents'));
289
+
290
+ for (const addonAgentsDir of addonDirs) {
291
+ if (fs.existsSync(addonAgentsDir)) {
292
+ agentPaths.push(...listMdFiles(addonAgentsDir));
293
+ }
294
+ }
295
+ }
296
+
282
297
  if (mode === 'sdlc' || mode === 'both') {
283
298
  const sdlcAgents = path.join(aiwgRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'agents');
284
299
  if (fs.existsSync(sdlcAgents)) {
@@ -304,6 +319,20 @@ function generateAIWGContent(aiwgRoot, mode) {
304
319
  commandPaths.push(...listMdFiles(generalCommands));
305
320
  }
306
321
  }
322
+
323
+ // Addon commands (dynamically discovered)
324
+ if (fs.existsSync(addonsRoot)) {
325
+ const addonCommandDirs = fs.readdirSync(addonsRoot, { withFileTypes: true })
326
+ .filter(e => e.isDirectory())
327
+ .map(e => path.join(addonsRoot, e.name, 'commands'));
328
+
329
+ for (const addonCommandsDir of addonCommandDirs) {
330
+ if (fs.existsSync(addonCommandsDir)) {
331
+ commandPaths.push(...listMdFiles(addonCommandsDir));
332
+ }
333
+ }
334
+ }
335
+
307
336
  if (mode === 'sdlc' || mode === 'both') {
308
337
  const sdlcCommands = path.join(aiwgRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'commands');
309
338
  if (fs.existsSync(sdlcCommands)) {