kspec 1.0.19 → 1.0.20

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 +62 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kspec",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "description": "Spec-driven development workflow for Kiro CLI",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -167,25 +167,71 @@ function formatDate(format) {
167
167
  }
168
168
 
169
169
  function slugify(text) {
170
- // Extract key words and create a meaningful short identifier
171
- const words = text.toLowerCase()
172
- .replace(/[^a-z0-9\s]/g, ' ') // Replace non-alphanumeric with spaces
173
- .split(/\s+/)
174
- .filter(word => word.length > 2) // Remove short words
175
- .filter(word => !['the', 'and', 'for', 'with', 'app', 'web', 'api'].includes(word)); // Remove common words
176
-
177
- // Take first 3-4 meaningful words and truncate to max 25 chars total
178
- let slug = words.slice(0, 4).join('-');
179
- if (slug.length > 25) {
180
- slug = words.slice(0, 3).join('-');
170
+ // Filler words to remove (action verbs, articles, prepositions, generic terms)
171
+ const fillerWords = new Set([
172
+ 'a', 'an', 'the', 'and', 'or', 'but', 'for', 'with', 'using', 'via',
173
+ 'create', 'build', 'make', 'implement', 'add', 'develop', 'write',
174
+ 'application', 'app', 'system', 'feature', 'functionality', 'module',
175
+ 'that', 'this', 'which', 'will', 'should', 'can', 'could', 'would',
176
+ 'new', 'simple', 'basic', 'full', 'complete'
177
+ ]);
178
+
179
+ // Tech terms to prioritize (frameworks, libraries, tools)
180
+ const techTerms = new Set([
181
+ 'react', 'nextjs', 'next', 'vue', 'angular', 'svelte', 'node', 'express',
182
+ 'typescript', 'javascript', 'python', 'rust', 'go', 'java',
183
+ 'tailwind', 'shadcn', 'prisma', 'drizzle', 'supabase', 'firebase',
184
+ 'postgres', 'mysql', 'mongodb', 'redis', 'graphql', 'rest', 'trpc',
185
+ 'docker', 'kubernetes', 'aws', 'vercel', 'netlify'
186
+ ]);
187
+
188
+ // Normalize text: handle "to do" -> "todo", lowercase, clean
189
+ let normalized = text.toLowerCase()
190
+ .replace(/to\s*do/g, 'todo')
191
+ .replace(/e[\s-]?commerce/g, 'ecommerce')
192
+ .replace(/[^a-z0-9\s]/g, ' ')
193
+ .replace(/\s+/g, ' ')
194
+ .trim();
195
+
196
+ const words = normalized.split(' ').filter(w => w.length > 0);
197
+
198
+ // Separate into tech terms and other meaningful words
199
+ const tech = [];
200
+ const meaningful = [];
201
+
202
+ for (const word of words) {
203
+ if (fillerWords.has(word)) continue;
204
+ if (techTerms.has(word)) {
205
+ tech.push(word);
206
+ } else if (word.length > 2) {
207
+ meaningful.push(word);
208
+ }
181
209
  }
182
- if (slug.length > 25) {
183
- slug = words.slice(0, 2).join('-');
210
+
211
+ // Prioritize: first meaningful word + tech terms (max 3-4 words total)
212
+ const selected = [];
213
+ if (meaningful.length > 0) selected.push(meaningful[0]);
214
+ selected.push(...tech.slice(0, 2));
215
+ if (selected.length < 3 && meaningful.length > 1) {
216
+ selected.push(meaningful[1]);
184
217
  }
185
- if (slug.length > 25) {
186
- slug = slug.slice(0, 25);
218
+
219
+ // Fallback if nothing meaningful
220
+ if (selected.length === 0) {
221
+ const fallback = words.filter(w => w.length > 2).slice(0, 2);
222
+ selected.push(...fallback);
187
223
  }
188
-
224
+
225
+ let slug = selected.join('-');
226
+
227
+ // Truncate if still too long
228
+ if (slug.length > 30) {
229
+ slug = selected.slice(0, 2).join('-');
230
+ }
231
+ if (slug.length > 30) {
232
+ slug = slug.slice(0, 30);
233
+ }
234
+
189
235
  return slug.replace(/^-+|-+$/g, '') || 'feature';
190
236
  }
191
237