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 +125 -0
- package/bin/specweave.js +1 -0
- package/dist/src/cli/commands/init.d.ts +1 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +110 -70
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +46 -20
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.d.ts +5 -0
- package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +143 -48
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/generators/spec/spec-parser.d.ts.map +1 -1
- package/dist/src/generators/spec/spec-parser.js +86 -12
- package/dist/src/generators/spec/spec-parser.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +36 -0
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);
|
|
@@ -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,
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
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 (
|
|
989
|
-
|
|
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
|
}
|