specweave 0.23.5 → 0.23.8

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
@@ -1129,6 +1129,131 @@ bash scripts/validate-plugin-directories.sh --fix
1129
1129
  - Skills index: `plugins/specweave/skills/SKILLS-INDEX.md`
1130
1130
  - Claude Code Skills docs: `~/CLAUDE.md` (Personal skills reference)
1131
1131
 
1132
+ ### 16. YAML Frontmatter Validation (CRITICAL!)
1133
+
1134
+ **Rule**: ALL spec.md files MUST have valid YAML frontmatter with required fields.
1135
+
1136
+ **Required Format**:
1137
+ ```yaml
1138
+ ---
1139
+ increment: 0001-feature-name # REQUIRED: 4-digit number + kebab-case
1140
+ title: Feature Title # OPTIONAL: Human-readable title
1141
+ feature_id: FS-001 # OPTIONAL: Living docs feature ID
1142
+ ---
1143
+ ```
1144
+
1145
+ **Common Mistakes**:
1146
+
1147
+ ```yaml
1148
+ # ❌ WRONG: Unclosed bracket
1149
+ ---
1150
+ increment: 0001-test
1151
+ data: [unclosed
1152
+ ---
1153
+
1154
+ # ❌ WRONG: Unclosed quote
1155
+ ---
1156
+ increment: 0001-test
1157
+ title: "unclosed string
1158
+ ---
1159
+
1160
+ # ❌ WRONG: Invalid YAML object
1161
+ ---
1162
+ increment: 0001-test
1163
+ config: {key: value, broken
1164
+ ---
1165
+
1166
+ # ❌ WRONG: Invalid increment ID format
1167
+ ---
1168
+ increment: 1-test # Missing leading zeros
1169
+ ---
1170
+
1171
+ # ❌ WRONG: Invalid increment ID format (uppercase)
1172
+ ---
1173
+ increment: 0001-Test-Feature # Uppercase letters not allowed
1174
+ ---
1175
+
1176
+ # ❌ WRONG: Missing required field
1177
+ ---
1178
+ title: Feature Title # Missing increment field!
1179
+ ---
1180
+
1181
+ # ✅ CORRECT: Minimal valid frontmatter
1182
+ ---
1183
+ increment: 0001-feature-name
1184
+ ---
1185
+
1186
+ # ✅ CORRECT: Full frontmatter
1187
+ ---
1188
+ increment: 0042-bug-fix
1189
+ title: Critical Bug Fix
1190
+ feature_id: FS-013
1191
+ ---
1192
+ ```
1193
+
1194
+ **Prevention Layers**:
1195
+
1196
+ 1. **Pre-commit hook** - Validates YAML before commits (blocks malformed frontmatter)
1197
+ ```bash
1198
+ # Runs automatically on git commit
1199
+ scripts/pre-commit-yaml-validation.sh
1200
+ ```
1201
+
1202
+ 2. **Spec parser** - Uses `js-yaml` library (detects errors at runtime)
1203
+ - Provides descriptive error messages
1204
+ - Shows line numbers for YAML errors
1205
+ - Suggests common fixes
1206
+
1207
+ 3. **Command validation** - `/specweave:validate` checks YAML syntax
1208
+ ```bash
1209
+ /specweave:validate 0001
1210
+ ```
1211
+
1212
+ **Manual YAML Testing**:
1213
+
1214
+ ```bash
1215
+ # Validate YAML in spec.md (manual check)
1216
+ node -e "
1217
+ const yaml = require('js-yaml');
1218
+ const fs = require('fs');
1219
+ const content = fs.readFileSync('.specweave/increments/0001-test/spec.md', 'utf-8');
1220
+ const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
1221
+ if (!frontmatter) throw new Error('No frontmatter found');
1222
+ const parsed = yaml.load(frontmatter[1]);
1223
+ console.log('✅ Valid YAML:', JSON.stringify(parsed, null, 2));
1224
+ "
1225
+ ```
1226
+
1227
+ **Error Messages**:
1228
+
1229
+ When YAML validation fails, you'll see descriptive errors:
1230
+
1231
+ ```
1232
+ ERROR: Malformed YAML syntax in frontmatter
1233
+ DETAILS: Unexpected end of stream
1234
+ LOCATION: Lines 2-5
1235
+ HINT: Common mistakes:
1236
+ - Unclosed brackets: [unclosed
1237
+ - Unclosed quotes: "unclosed
1238
+ - Invalid YAML object: {key: value, broken
1239
+ ```
1240
+
1241
+ **Why This Matters**:
1242
+ - **Silent failures**: Malformed YAML can cause missing/incorrect metadata
1243
+ - **Sync issues**: Invalid increment IDs break living docs sync
1244
+ - **GitHub sync**: Broken frontmatter prevents issue creation
1245
+ - **Validation failures**: Tasks/ACs can't be parsed without valid metadata
1246
+
1247
+ **Incident History**:
1248
+ - **Test case**: `tests/integration/commands/plan-command.integration.test.ts:626` tests malformed YAML handling
1249
+ - **Root cause**: Regex-based parsing was tolerant but unreliable
1250
+ - **Solution**: Multi-layered YAML validation with `js-yaml` library
1251
+
1252
+ **See Also**:
1253
+ - Pre-commit hook: `scripts/pre-commit-yaml-validation.sh`
1254
+ - Spec parser: `src/generators/spec/spec-parser.ts` (uses js-yaml)
1255
+ - Test suite: `tests/integration/commands/plan-command.integration.test.ts`
1256
+
1132
1257
  ---
1133
1258
 
1134
1259
  ## Project Structure
package/bin/specweave.js CHANGED
@@ -37,6 +37,7 @@ program
37
37
  .option('--tech-stack <language>', 'Technology stack (nodejs, python, etc.)', undefined)
38
38
  .option('-l, --language <lang>', 'Language for generated content (en, ru, es, zh, de, fr, ja, ko, pt)', 'en')
39
39
  .option('-f, --force', 'Force fresh start (non-interactive, removes existing .specweave)', false)
40
+ .option('--force-refresh', 'Force marketplace refresh (skip cache, always pull latest)', false)
40
41
  .action(async (projectName, options) => {
41
42
  const { initCommand } = await import('../dist/src/cli/commands/init.js');
42
43
  await initCommand(projectName, options);
@@ -5,6 +5,7 @@ interface InitOptions {
5
5
  techStack?: string;
6
6
  language?: string;
7
7
  force?: boolean;
8
+ forceRefresh?: boolean;
8
9
  logger?: Logger;
9
10
  }
10
11
  export declare function initCommand(projectName?: string, options?: InitOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAgB9D,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA8ID,wBAAsB,WAAW,CAC/B,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAwyCf"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAgB9D,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA8ID,wBAAsB,WAAW,CAC/B,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CA80Cf"}
@@ -914,81 +914,66 @@ export async function initCommand(projectName, options = {}) {
914
914
  else {
915
915
  // Claude CLI available → install ALL plugins from marketplace
916
916
  try {
917
- // Step 1: Remove existing marketplace to force update (REQUIRED for updates!)
918
- // This ensures users get the latest plugins when new ones are added
919
- spinner.start('Refreshing SpecWeave marketplace...');
920
- const listResult = execFileNoThrowSync('claude', [
921
- 'plugin',
922
- 'marketplace',
923
- 'list'
924
- ]);
925
- const marketplaceExists = listResult.success &&
926
- (listResult.stdout || '').toLowerCase().includes('specweave');
927
- if (marketplaceExists) {
928
- execFileNoThrowSync('claude', [
929
- 'plugin',
930
- 'marketplace',
931
- 'remove',
932
- 'specweave'
933
- ]);
934
- console.log(chalk.blue(' 🔄 Removed existing marketplace for update'));
935
- }
936
- // Step 2: Add marketplace from GitHub (always fresh)
937
- const addResult = execFileNoThrowSync('claude', [
938
- 'plugin',
939
- 'marketplace',
940
- 'add',
941
- 'anton-abyzov/specweave'
942
- ]);
943
- if (!addResult.success) {
944
- throw new Error('Failed to add marketplace from GitHub');
945
- }
946
- console.log(chalk.green(' ✔ Marketplace registered from GitHub'));
947
- // CRITICAL: Wait for marketplace cache to be FULLY ready
948
- // Problem: Fixed delays (2s, 5s) are unreliable - cache initialization time varies
949
- // Solution: Poll until marketplace.json exists and is readable in cache
950
- spinner.text = 'Waiting for marketplace cache to initialize...';
951
917
  const marketplaceCachePath = path.join(os.homedir(), '.claude/plugins/marketplaces/specweave/.claude-plugin/marketplace.json');
952
- const maxWaitTime = 30000; // 30 seconds max
953
- const pollInterval = 500; // Check every 500ms
954
- const startTime = Date.now();
955
- let cacheReady = false;
956
- while (!cacheReady && (Date.now() - startTime) < maxWaitTime) {
957
- try {
958
- // Check if marketplace.json exists and is readable
959
- if (fs.existsSync(marketplaceCachePath)) {
918
+ // ULTRAFAST: Check if cache is fresh (< 5 min old) and valid
919
+ let needsRefresh = true;
920
+ let cacheAlreadyValid = false;
921
+ // Skip cache if forceRefresh flag is set
922
+ if (!options.forceRefresh && fs.existsSync(marketplaceCachePath)) {
923
+ const cacheStats = fs.statSync(marketplaceCachePath);
924
+ const cacheAge = Date.now() - cacheStats.mtimeMs;
925
+ const fiveMinutes = 5 * 60 * 1000;
926
+ if (cacheAge < fiveMinutes) {
927
+ try {
960
928
  const cacheData = JSON.parse(fs.readFileSync(marketplaceCachePath, 'utf-8'));
961
- // CRITICAL: Verify plugins array is FULLY populated with metadata
962
- // Not just present, but each plugin has required fields (name, version, etc.)
963
- if (cacheData.plugins && cacheData.plugins.length >= 25) {
964
- // Additional validation: check if plugins have required metadata
965
- const hasMetadata = cacheData.plugins.every((p) => p.name && p.version && p.description);
966
- if (hasMetadata) {
967
- cacheReady = true;
968
- const waitedMs = Date.now() - startTime;
969
- console.log(chalk.gray(` ⏱ Cache ready in ${Math.round(waitedMs / 1000)}s`));
970
- break;
971
- }
972
- else {
973
- // Plugins exist but metadata incomplete - keep waiting
974
- spinner.text = `Waiting for plugin metadata... (${Math.round((Date.now() - startTime) / 1000)}s)`;
975
- }
929
+ const hasValidPlugins = cacheData.plugins &&
930
+ cacheData.plugins.length >= 25 &&
931
+ cacheData.plugins.every((p) => p.name && p.version && p.description);
932
+ if (hasValidPlugins) {
933
+ needsRefresh = false;
934
+ cacheAlreadyValid = true;
935
+ console.log(chalk.green(' ⚡ Using cached marketplace (fresh)'));
976
936
  }
977
937
  }
938
+ catch {
939
+ // Cache exists but invalid, needs refresh
940
+ }
978
941
  }
979
- catch (e) {
980
- // File exists but not readable yet (still being written)
981
- }
982
- // Wait before next poll
983
- await new Promise(resolve => setTimeout(resolve, pollInterval));
984
- // Update spinner to show we're still waiting
985
- const elapsed = Math.round((Date.now() - startTime) / 1000);
986
- spinner.text = `Waiting for marketplace cache... (${elapsed}s)`;
987
942
  }
988
- if (!cacheReady) {
989
- console.log(chalk.yellow(' ⚠ Cache initialization timeout, proceeding anyway...'));
943
+ if (needsRefresh) {
944
+ // Step 1: Remove existing marketplace to force update
945
+ spinner.start('Refreshing SpecWeave marketplace...');
946
+ const listResult = execFileNoThrowSync('claude', [
947
+ 'plugin',
948
+ 'marketplace',
949
+ 'list'
950
+ ]);
951
+ const marketplaceExists = listResult.success &&
952
+ (listResult.stdout || '').toLowerCase().includes('specweave');
953
+ if (marketplaceExists) {
954
+ execFileNoThrowSync('claude', [
955
+ 'plugin',
956
+ 'marketplace',
957
+ 'remove',
958
+ 'specweave'
959
+ ]);
960
+ console.log(chalk.blue(' 🔄 Removed existing marketplace for update'));
961
+ }
962
+ // Step 2: Add marketplace from GitHub (always fresh)
963
+ const addResult = execFileNoThrowSync('claude', [
964
+ 'plugin',
965
+ 'marketplace',
966
+ 'add',
967
+ 'anton-abyzov/specweave'
968
+ ]);
969
+ if (!addResult.success) {
970
+ throw new Error('Failed to add marketplace from GitHub');
971
+ }
972
+ console.log(chalk.green(' ✔ Marketplace registered from GitHub'));
973
+ // NO WAIT NEEDED: We load from source (npm package), not cache
974
+ // The cache is populated asynchronously by Claude Code and isn't used during init
975
+ spinner.succeed('SpecWeave marketplace ready');
990
976
  }
991
- spinner.succeed('SpecWeave marketplace ready');
992
977
  // Step 2: Load marketplace.json to get ALL available plugins
993
978
  spinner.start('Loading available plugins...');
994
979
  const marketplaceJsonPath = findSourceDir('.claude-plugin/marketplace.json');
@@ -1088,6 +1073,59 @@ export async function initCommand(projectName, options = {}) {
1088
1073
  autoInstallSucceeded = false;
1089
1074
  }
1090
1075
  }
1076
+ // 10.4 Repository Hosting Setup (FUNDAMENTAL!)
1077
+ // Ask about repository hosting BEFORE issue tracker
1078
+ // This determines what issue tracker options are available
1079
+ console.log('');
1080
+ console.log(chalk.cyan.bold('📦 Repository Hosting'));
1081
+ console.log('');
1082
+ // Detect existing git remote
1083
+ const gitRemoteDetection = detectGitHubRemote(targetDir);
1084
+ let repositoryHosting = 'local';
1085
+ if (!isCI) {
1086
+ const { hosting } = await inquirer.prompt([{
1087
+ type: 'list',
1088
+ name: 'hosting',
1089
+ message: 'How do you host your repository?',
1090
+ choices: [
1091
+ {
1092
+ name: `🐙 GitHub ${gitRemoteDetection ? '(detected)' : '(recommended)'}`,
1093
+ value: 'github'
1094
+ },
1095
+ {
1096
+ name: '💻 Local git only (no remote sync)',
1097
+ value: 'local'
1098
+ },
1099
+ {
1100
+ name: '🔧 Other (GitLab, Bitbucket, etc.)',
1101
+ value: 'other'
1102
+ }
1103
+ ],
1104
+ default: gitRemoteDetection ? 'github' : 'local'
1105
+ }]);
1106
+ repositoryHosting = hosting;
1107
+ // Show info for non-GitHub choices
1108
+ if (hosting === 'other') {
1109
+ console.log('');
1110
+ console.log(chalk.yellow('⚠️ Note: SpecWeave currently has best integration with GitHub'));
1111
+ console.log(chalk.gray(' • GitHub: Full sync support (issues, milestones, labels)'));
1112
+ console.log(chalk.gray(' • GitLab/Bitbucket: Limited support (manual sync)'));
1113
+ console.log(chalk.gray(' • You can still use SpecWeave locally and sync manually'));
1114
+ console.log('');
1115
+ }
1116
+ else if (hosting === 'local') {
1117
+ console.log('');
1118
+ console.log(chalk.gray('✓ Local-only mode'));
1119
+ console.log(chalk.gray(' • All work tracked locally in .specweave/'));
1120
+ console.log(chalk.gray(' • No remote sync (you can add GitHub later)'));
1121
+ console.log('');
1122
+ }
1123
+ }
1124
+ else {
1125
+ // CI mode: auto-detect
1126
+ repositoryHosting = gitRemoteDetection ? 'github' : 'local';
1127
+ console.log(chalk.gray(` → CI mode: Auto-detected ${repositoryHosting} hosting\n`));
1128
+ }
1091
1129
  // 10.5 Issue Tracker Integration (CRITICAL!)
1092
1130
  // MUST happen AFTER plugin installation is complete
1093
1131
  // Asks user: Which tracker? (GitHub/Jira/ADO/None)
@@ -1135,7 +1173,8 @@ export async function initCommand(projectName, options = {}) {
1135
1173
  projectPath: targetDir,
1136
1174
  language: language,
1137
1175
  maxRetries: 3,
1138
- isFrameworkRepo
1176
+ isFrameworkRepo,
1177
+ repositoryHosting
1139
1178
  });
1140
1179
  }
1141
1180
  }
@@ -1151,7 +1190,8 @@ export async function initCommand(projectName, options = {}) {
1151
1190
  projectPath: targetDir,
1152
1191
  language: language,
1153
1192
  maxRetries: 3,
1154
- isFrameworkRepo
1193
+ isFrameworkRepo,
1194
+ repositoryHosting
1155
1195
  });
1156
1196
  }
1157
1197
  }