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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +124 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kspec",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "Spec-driven development workflow for Kiro CLI",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
- if (fs.existsSync(specLiteFile)) {
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 };