kspec 1.0.21 → 1.0.22

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 +97 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kspec",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
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,6 +457,7 @@ 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
462
  // Read spec-lite if exists
434
463
  const specLiteFile = path.join(current, 'spec-lite.md');
@@ -460,8 +489,17 @@ No active spec. Run: \`kspec spec "Feature Name"\`
460
489
  ## Current Spec
461
490
  **${specName}**
462
491
  Path: \`${current}\`
492
+ `;
493
+
494
+ if (stale) {
495
+ content += `
496
+ **WARNING: spec.md has been modified since spec-lite.md was generated.**
497
+ Run \`kspec refresh\` to update spec-lite.md with latest changes.
463
498
 
464
499
  `;
500
+ } else {
501
+ content += '\n';
502
+ }
465
503
 
466
504
  if (stats) {
467
505
  content += `## Progress
@@ -1077,6 +1115,9 @@ Report created subtasks with their URLs.`, 'kspec-jira');
1077
1115
 
1078
1116
  async tasks(args) {
1079
1117
  const folder = getOrSelectSpec(args.join(' '));
1118
+
1119
+ if (!await checkStaleness(folder)) return;
1120
+
1080
1121
  log(`Generating tasks: ${folder}`);
1081
1122
 
1082
1123
  await chat(`Generate tasks from specification.
@@ -1093,8 +1134,11 @@ Create ${folder}/tasks.md with:
1093
1134
 
1094
1135
  async 'verify-tasks'(args) {
1095
1136
  const folder = getOrSelectSpec(args.join(' '));
1137
+
1138
+ if (!await checkStaleness(folder)) return;
1139
+
1096
1140
  const stats = getTaskStats(folder);
1097
-
1141
+
1098
1142
  log(`Verifying tasks: ${folder}`);
1099
1143
  if (stats) log(`Progress: ${stats.done}/${stats.total} tasks completed`);
1100
1144
 
@@ -1110,6 +1154,9 @@ Report: X/Y tasks done, gaps found, coverage assessment.`, 'kspec-verify');
1110
1154
 
1111
1155
  async build(args) {
1112
1156
  const folder = getOrSelectSpec(args.join(' '));
1157
+
1158
+ if (!await checkStaleness(folder)) return;
1159
+
1113
1160
  const stats = getTaskStats(folder);
1114
1161
 
1115
1162
  log(`Building: ${folder}`);
@@ -1144,6 +1191,9 @@ NEVER delete .kiro or .kspec folders.`, 'kspec-build');
1144
1191
 
1145
1192
  async verify(args) {
1146
1193
  const folder = getOrSelectSpec(args.join(' '));
1194
+
1195
+ if (!await checkStaleness(folder)) return;
1196
+
1147
1197
  const stats = getTaskStats(folder);
1148
1198
 
1149
1199
  log(`Verifying implementation: ${folder}`);
@@ -1159,11 +1209,54 @@ NEVER delete .kiro or .kspec folders.`, 'kspec-build');
1159
1209
 
1160
1210
  Report:
1161
1211
  - Requirements: X/Y implemented
1162
- - Tasks: X/Y completed
1212
+ - Tasks: X/Y completed
1163
1213
  - Tests: PASS/FAIL
1164
1214
  - Gaps: [list any]`, 'kspec-verify');
1165
1215
  },
1166
1216
 
1217
+ async refresh(args) {
1218
+ const folder = getOrSelectSpec(args.join(' '));
1219
+ const specFile = path.join(folder, 'spec.md');
1220
+ const specLiteFile = path.join(folder, 'spec-lite.md');
1221
+
1222
+ if (!fs.existsSync(specFile)) {
1223
+ die(`No spec.md found in ${folder}`);
1224
+ }
1225
+
1226
+ const stale = isSpecStale(folder);
1227
+ if (!stale && !args.includes('--force')) {
1228
+ log('spec-lite.md is up to date with spec.md');
1229
+ log('Use --force to regenerate anyway');
1230
+ return;
1231
+ }
1232
+
1233
+ log(`Refreshing spec-lite.md from ${folder}/spec.md...`);
1234
+
1235
+ await chat(`Regenerate spec-lite.md from the updated spec.md.
1236
+
1237
+ Spec folder: ${folder}
1238
+
1239
+ WORKFLOW:
1240
+ 1. Read ${specFile} carefully - this is the source of truth
1241
+ 2. Create a NEW ${specLiteFile} that:
1242
+ - Summarizes ALL key requirements (under 500 words)
1243
+ - Captures the current tech stack and versions
1244
+ - Includes critical constraints and acceptance criteria
1245
+ - Preserves any Jira issue references
1246
+ 3. This spec-lite.md will be used for context restoration after AI compression
1247
+
1248
+ IMPORTANT:
1249
+ - Read the FULL spec.md before generating spec-lite.md
1250
+ - Ensure ALL tech stack details are captured (versions matter!)
1251
+ - This replaces the old spec-lite.md completely
1252
+
1253
+ After updating, confirm what changed.`, 'kspec-spec');
1254
+
1255
+ // Update CONTEXT.md with new spec-lite
1256
+ refreshContext();
1257
+ log('Context refreshed with updated spec');
1258
+ },
1259
+
1167
1260
  async done(args) {
1168
1261
  const folder = getOrSelectSpec(args.join(' '));
1169
1262
  const stats = getTaskStats(folder);
@@ -1347,6 +1440,7 @@ Jira Integration (requires Atlassian MCP):
1347
1440
  Create subtasks under specific issue
1348
1441
 
1349
1442
  Other:
1443
+ kspec refresh Regenerate spec-lite.md after editing spec.md
1350
1444
  kspec context Refresh/view context file
1351
1445
  kspec review [target] Code review
1352
1446
  kspec list List all specs
@@ -1389,4 +1483,4 @@ async function run(args) {
1389
1483
  }
1390
1484
  }
1391
1485
 
1392
- module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentSpec, getCurrentTask, checkForUpdates, compareVersions, hasAtlassianMcp, getMcpConfig, slugify, generateSlug };
1486
+ module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentSpec, getCurrentTask, checkForUpdates, compareVersions, hasAtlassianMcp, getMcpConfig, slugify, generateSlug, isSpecStale };