kspec 1.0.21 → 1.0.23
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/package.json +1 -1
- package/src/index.js +124 -5
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -411,6 +411,34 @@ function getCurrentTask(folder) {
|
|
|
411
411
|
return null;
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
// Check if spec.md has been modified after spec-lite.md
|
|
415
|
+
function isSpecStale(folder) {
|
|
416
|
+
const specFile = path.join(folder, 'spec.md');
|
|
417
|
+
const specLiteFile = path.join(folder, 'spec-lite.md');
|
|
418
|
+
|
|
419
|
+
if (!fs.existsSync(specFile)) return false;
|
|
420
|
+
if (!fs.existsSync(specLiteFile)) return true; // No spec-lite means stale
|
|
421
|
+
|
|
422
|
+
const specMtime = fs.statSync(specFile).mtime;
|
|
423
|
+
const specLiteMtime = fs.statSync(specLiteFile).mtime;
|
|
424
|
+
|
|
425
|
+
return specMtime > specLiteMtime;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Check staleness and prompt user before proceeding
|
|
429
|
+
async function checkStaleness(folder) {
|
|
430
|
+
if (!isSpecStale(folder)) return true; // Not stale, proceed
|
|
431
|
+
|
|
432
|
+
console.log('\n⚠️ spec.md has been modified since spec-lite.md was generated.');
|
|
433
|
+
console.log(' This may cause outdated information to be used.\n');
|
|
434
|
+
console.log(' Options:');
|
|
435
|
+
console.log(' 1. Run `kspec refresh` to update spec-lite.md first (recommended)');
|
|
436
|
+
console.log(' 2. Continue anyway with potentially stale context\n');
|
|
437
|
+
|
|
438
|
+
const proceed = await confirm('Continue with potentially stale spec?');
|
|
439
|
+
return proceed;
|
|
440
|
+
}
|
|
441
|
+
|
|
414
442
|
function refreshContext() {
|
|
415
443
|
const contextFile = path.join(KSPEC_DIR, 'CONTEXT.md');
|
|
416
444
|
const current = getCurrentSpec();
|
|
@@ -429,11 +457,22 @@ No active spec. Run: \`kspec spec "Feature Name"\`
|
|
|
429
457
|
const specName = path.basename(current);
|
|
430
458
|
const stats = getTaskStats(current);
|
|
431
459
|
const currentTask = getCurrentTask(current);
|
|
460
|
+
const stale = isSpecStale(current);
|
|
432
461
|
|
|
433
|
-
// Read spec-lite if exists
|
|
462
|
+
// Read spec-lite if exists, or fall back to spec.md if stale
|
|
434
463
|
const specLiteFile = path.join(current, 'spec-lite.md');
|
|
464
|
+
const specFile = path.join(current, 'spec.md');
|
|
435
465
|
let specLite = '';
|
|
436
|
-
|
|
466
|
+
let usingSpecFallback = false;
|
|
467
|
+
|
|
468
|
+
if (stale && fs.existsSync(specFile)) {
|
|
469
|
+
// Use spec.md directly when stale (truncate if too long)
|
|
470
|
+
const specContent = fs.readFileSync(specFile, 'utf8');
|
|
471
|
+
specLite = specContent.length > 3000
|
|
472
|
+
? specContent.slice(0, 3000) + '\n\n... (truncated, run `kspec refresh` for full summary)'
|
|
473
|
+
: specContent;
|
|
474
|
+
usingSpecFallback = true;
|
|
475
|
+
} else if (fs.existsSync(specLiteFile)) {
|
|
437
476
|
specLite = fs.readFileSync(specLiteFile, 'utf8');
|
|
438
477
|
}
|
|
439
478
|
|
|
@@ -460,8 +499,17 @@ No active spec. Run: \`kspec spec "Feature Name"\`
|
|
|
460
499
|
## Current Spec
|
|
461
500
|
**${specName}**
|
|
462
501
|
Path: \`${current}\`
|
|
502
|
+
`;
|
|
503
|
+
|
|
504
|
+
if (stale) {
|
|
505
|
+
content += `
|
|
506
|
+
**NOTE: spec.md was modified. Using spec.md directly for context.**
|
|
507
|
+
Run \`kspec refresh\` to generate optimized spec-lite.md summary.
|
|
463
508
|
|
|
464
509
|
`;
|
|
510
|
+
} else {
|
|
511
|
+
content += '\n';
|
|
512
|
+
}
|
|
465
513
|
|
|
466
514
|
if (stats) {
|
|
467
515
|
content += `## Progress
|
|
@@ -1077,6 +1125,9 @@ Report created subtasks with their URLs.`, 'kspec-jira');
|
|
|
1077
1125
|
|
|
1078
1126
|
async tasks(args) {
|
|
1079
1127
|
const folder = getOrSelectSpec(args.join(' '));
|
|
1128
|
+
|
|
1129
|
+
if (!await checkStaleness(folder)) return;
|
|
1130
|
+
|
|
1080
1131
|
log(`Generating tasks: ${folder}`);
|
|
1081
1132
|
|
|
1082
1133
|
await chat(`Generate tasks from specification.
|
|
@@ -1093,8 +1144,11 @@ Create ${folder}/tasks.md with:
|
|
|
1093
1144
|
|
|
1094
1145
|
async 'verify-tasks'(args) {
|
|
1095
1146
|
const folder = getOrSelectSpec(args.join(' '));
|
|
1147
|
+
|
|
1148
|
+
if (!await checkStaleness(folder)) return;
|
|
1149
|
+
|
|
1096
1150
|
const stats = getTaskStats(folder);
|
|
1097
|
-
|
|
1151
|
+
|
|
1098
1152
|
log(`Verifying tasks: ${folder}`);
|
|
1099
1153
|
if (stats) log(`Progress: ${stats.done}/${stats.total} tasks completed`);
|
|
1100
1154
|
|
|
@@ -1110,6 +1164,9 @@ Report: X/Y tasks done, gaps found, coverage assessment.`, 'kspec-verify');
|
|
|
1110
1164
|
|
|
1111
1165
|
async build(args) {
|
|
1112
1166
|
const folder = getOrSelectSpec(args.join(' '));
|
|
1167
|
+
|
|
1168
|
+
if (!await checkStaleness(folder)) return;
|
|
1169
|
+
|
|
1113
1170
|
const stats = getTaskStats(folder);
|
|
1114
1171
|
|
|
1115
1172
|
log(`Building: ${folder}`);
|
|
@@ -1144,6 +1201,9 @@ NEVER delete .kiro or .kspec folders.`, 'kspec-build');
|
|
|
1144
1201
|
|
|
1145
1202
|
async verify(args) {
|
|
1146
1203
|
const folder = getOrSelectSpec(args.join(' '));
|
|
1204
|
+
|
|
1205
|
+
if (!await checkStaleness(folder)) return;
|
|
1206
|
+
|
|
1147
1207
|
const stats = getTaskStats(folder);
|
|
1148
1208
|
|
|
1149
1209
|
log(`Verifying implementation: ${folder}`);
|
|
@@ -1159,11 +1219,69 @@ NEVER delete .kiro or .kspec folders.`, 'kspec-build');
|
|
|
1159
1219
|
|
|
1160
1220
|
Report:
|
|
1161
1221
|
- Requirements: X/Y implemented
|
|
1162
|
-
- Tasks: X/Y completed
|
|
1222
|
+
- Tasks: X/Y completed
|
|
1163
1223
|
- Tests: PASS/FAIL
|
|
1164
1224
|
- Gaps: [list any]`, 'kspec-verify');
|
|
1165
1225
|
},
|
|
1166
1226
|
|
|
1227
|
+
async refresh(args) {
|
|
1228
|
+
const folder = getOrSelectSpec(args.join(' '));
|
|
1229
|
+
const specFile = path.join(folder, 'spec.md');
|
|
1230
|
+
const specLiteFile = path.join(folder, 'spec-lite.md');
|
|
1231
|
+
|
|
1232
|
+
if (!fs.existsSync(specFile)) {
|
|
1233
|
+
die(`No spec.md found in ${folder}`);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
const stale = isSpecStale(folder);
|
|
1237
|
+
if (!stale && !args.includes('--force')) {
|
|
1238
|
+
log('spec-lite.md is up to date with spec.md');
|
|
1239
|
+
log('Use --force to regenerate anyway');
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// Read the current spec.md content
|
|
1244
|
+
const specContent = fs.readFileSync(specFile, 'utf8');
|
|
1245
|
+
|
|
1246
|
+
log(`Refreshing spec-lite.md from ${folder}/spec.md...`);
|
|
1247
|
+
|
|
1248
|
+
await chat(`URGENT: Regenerate spec-lite.md NOW.
|
|
1249
|
+
|
|
1250
|
+
TARGET FILE: ${specLiteFile}
|
|
1251
|
+
|
|
1252
|
+
CURRENT spec.md CONTENT (source of truth):
|
|
1253
|
+
---
|
|
1254
|
+
${specContent}
|
|
1255
|
+
---
|
|
1256
|
+
|
|
1257
|
+
YOUR TASK:
|
|
1258
|
+
1. Write a NEW ${specLiteFile} file immediately
|
|
1259
|
+
2. Summarize the above spec.md content (under 500 words)
|
|
1260
|
+
3. MUST include:
|
|
1261
|
+
- All tech stack with EXACT versions (e.g., Next.js 16+, NOT 14+)
|
|
1262
|
+
- Key requirements and acceptance criteria
|
|
1263
|
+
- Any Jira references
|
|
1264
|
+
|
|
1265
|
+
DO THIS NOW:
|
|
1266
|
+
- Use your write tool to create ${specLiteFile}
|
|
1267
|
+
- Copy the tech stack details EXACTLY as shown above
|
|
1268
|
+
- Do not read the old spec-lite.md, replace it completely
|
|
1269
|
+
|
|
1270
|
+
After writing, show me what you wrote.`, 'kspec-spec');
|
|
1271
|
+
|
|
1272
|
+
// Verify spec-lite.md was updated
|
|
1273
|
+
if (isSpecStale(folder)) {
|
|
1274
|
+
console.log('\n⚠️ spec-lite.md may not have been updated correctly.');
|
|
1275
|
+
console.log(' Please verify the file was written with updated content.\n');
|
|
1276
|
+
} else {
|
|
1277
|
+
log('spec-lite.md updated successfully');
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Update CONTEXT.md with new spec-lite
|
|
1281
|
+
refreshContext();
|
|
1282
|
+
log('Context refreshed');
|
|
1283
|
+
},
|
|
1284
|
+
|
|
1167
1285
|
async done(args) {
|
|
1168
1286
|
const folder = getOrSelectSpec(args.join(' '));
|
|
1169
1287
|
const stats = getTaskStats(folder);
|
|
@@ -1347,6 +1465,7 @@ Jira Integration (requires Atlassian MCP):
|
|
|
1347
1465
|
Create subtasks under specific issue
|
|
1348
1466
|
|
|
1349
1467
|
Other:
|
|
1468
|
+
kspec refresh Regenerate spec-lite.md after editing spec.md
|
|
1350
1469
|
kspec context Refresh/view context file
|
|
1351
1470
|
kspec review [target] Code review
|
|
1352
1471
|
kspec list List all specs
|
|
@@ -1389,4 +1508,4 @@ async function run(args) {
|
|
|
1389
1508
|
}
|
|
1390
1509
|
}
|
|
1391
1510
|
|
|
1392
|
-
module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentSpec, getCurrentTask, checkForUpdates, compareVersions, hasAtlassianMcp, getMcpConfig, slugify, generateSlug };
|
|
1511
|
+
module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentSpec, getCurrentTask, checkForUpdates, compareVersions, hasAtlassianMcp, getMcpConfig, slugify, generateSlug, isSpecStale };
|