@planu/cli 0.97.2 → 0.98.0

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 (97) hide show
  1. package/dist/config/license-plans.json +6 -2
  2. package/dist/config/mobile-ci-templates/android-github-actions.yml +149 -0
  3. package/dist/config/mobile-ci-templates/ios-github-actions.yml +120 -0
  4. package/dist/engine/api-validation/graphql-schema-validator.d.ts +3 -0
  5. package/dist/engine/api-validation/graphql-schema-validator.d.ts.map +1 -0
  6. package/dist/engine/api-validation/graphql-schema-validator.js +134 -0
  7. package/dist/engine/api-validation/graphql-schema-validator.js.map +1 -0
  8. package/dist/engine/api-validation/index.d.ts +3 -0
  9. package/dist/engine/api-validation/index.d.ts.map +1 -0
  10. package/dist/engine/api-validation/index.js +4 -0
  11. package/dist/engine/api-validation/index.js.map +1 -0
  12. package/dist/engine/api-validation/openapi-impl-validator.d.ts +3 -0
  13. package/dist/engine/api-validation/openapi-impl-validator.d.ts.map +1 -0
  14. package/dist/engine/api-validation/openapi-impl-validator.js +205 -0
  15. package/dist/engine/api-validation/openapi-impl-validator.js.map +1 -0
  16. package/dist/engine/ci-generator/android-jobs.d.ts +17 -0
  17. package/dist/engine/ci-generator/android-jobs.d.ts.map +1 -0
  18. package/dist/engine/ci-generator/android-jobs.js +168 -0
  19. package/dist/engine/ci-generator/android-jobs.js.map +1 -0
  20. package/dist/engine/ci-generator/ios-jobs.d.ts +17 -0
  21. package/dist/engine/ci-generator/ios-jobs.d.ts.map +1 -0
  22. package/dist/engine/ci-generator/ios-jobs.js +151 -0
  23. package/dist/engine/ci-generator/ios-jobs.js.map +1 -0
  24. package/dist/engine/ci-generator/yaml-builder.d.ts +10 -1
  25. package/dist/engine/ci-generator/yaml-builder.d.ts.map +1 -1
  26. package/dist/engine/ci-generator/yaml-builder.js +46 -1
  27. package/dist/engine/ci-generator/yaml-builder.js.map +1 -1
  28. package/dist/engine/coverage-gap-analyzer.d.ts +6 -0
  29. package/dist/engine/coverage-gap-analyzer.d.ts.map +1 -0
  30. package/dist/engine/coverage-gap-analyzer.js +177 -0
  31. package/dist/engine/coverage-gap-analyzer.js.map +1 -0
  32. package/dist/engine/dashboard/templates-kanban.d.ts.map +1 -1
  33. package/dist/engine/dashboard/templates-kanban.js +22 -0
  34. package/dist/engine/dashboard/templates-kanban.js.map +1 -1
  35. package/dist/engine/dashboard/templates-layout.d.ts.map +1 -1
  36. package/dist/engine/dashboard/templates-layout.js +59 -7
  37. package/dist/engine/dashboard/templates-layout.js.map +1 -1
  38. package/dist/engine/mutation-config-generator.d.ts +6 -0
  39. package/dist/engine/mutation-config-generator.d.ts.map +1 -0
  40. package/dist/engine/mutation-config-generator.js +111 -0
  41. package/dist/engine/mutation-config-generator.js.map +1 -0
  42. package/dist/engine/tdd-scaffold-generator.d.ts +7 -0
  43. package/dist/engine/tdd-scaffold-generator.d.ts.map +1 -0
  44. package/dist/engine/tdd-scaffold-generator.js +252 -0
  45. package/dist/engine/tdd-scaffold-generator.js.map +1 -0
  46. package/dist/engine/version-resolver.d.ts +10 -0
  47. package/dist/engine/version-resolver.d.ts.map +1 -0
  48. package/dist/engine/version-resolver.js +144 -0
  49. package/dist/engine/version-resolver.js.map +1 -0
  50. package/dist/engine/web-fetcher/stack-advisor.d.ts.map +1 -1
  51. package/dist/engine/web-fetcher/stack-advisor.js +32 -4
  52. package/dist/engine/web-fetcher/stack-advisor.js.map +1 -1
  53. package/dist/index.js +7 -11
  54. package/dist/index.js.map +1 -1
  55. package/dist/tools/checkpoint-handler.d.ts.map +1 -1
  56. package/dist/tools/checkpoint-handler.js +24 -1
  57. package/dist/tools/checkpoint-handler.js.map +1 -1
  58. package/dist/tools/dashboard.d.ts.map +1 -1
  59. package/dist/tools/dashboard.js +2 -0
  60. package/dist/tools/dashboard.js.map +1 -1
  61. package/dist/tools/register-spec-322-tools.d.ts +3 -0
  62. package/dist/tools/register-spec-322-tools.d.ts.map +1 -0
  63. package/dist/tools/register-spec-322-tools.js +41 -0
  64. package/dist/tools/register-spec-322-tools.js.map +1 -0
  65. package/dist/tools/register-spec-323-tools.d.ts +3 -0
  66. package/dist/tools/register-spec-323-tools.d.ts.map +1 -0
  67. package/dist/tools/register-spec-323-tools.js +57 -0
  68. package/dist/tools/register-spec-323-tools.js.map +1 -0
  69. package/dist/tools/tdd-scaffold-handler.d.ts +5 -0
  70. package/dist/tools/tdd-scaffold-handler.d.ts.map +1 -0
  71. package/dist/tools/tdd-scaffold-handler.js +92 -0
  72. package/dist/tools/tdd-scaffold-handler.js.map +1 -0
  73. package/dist/tools/validate-api-contract-handler.d.ts +3 -0
  74. package/dist/tools/validate-api-contract-handler.d.ts.map +1 -0
  75. package/dist/tools/validate-api-contract-handler.js +74 -0
  76. package/dist/tools/validate-api-contract-handler.js.map +1 -0
  77. package/dist/tools/validate.d.ts.map +1 -1
  78. package/dist/tools/validate.js +22 -2
  79. package/dist/tools/validate.js.map +1 -1
  80. package/dist/types/api-contract.d.ts +58 -0
  81. package/dist/types/api-contract.d.ts.map +1 -1
  82. package/dist/types/ci.d.ts +8 -0
  83. package/dist/types/ci.d.ts.map +1 -1
  84. package/dist/types/index.d.ts +1 -0
  85. package/dist/types/index.d.ts.map +1 -1
  86. package/dist/types/index.js +1 -0
  87. package/dist/types/index.js.map +1 -1
  88. package/dist/types/testing.d.ts +60 -0
  89. package/dist/types/testing.d.ts.map +1 -1
  90. package/dist/types/version-resolution.d.ts +23 -0
  91. package/dist/types/version-resolution.d.ts.map +1 -0
  92. package/dist/types/version-resolution.js +3 -0
  93. package/dist/types/version-resolution.js.map +1 -0
  94. package/package.json +1 -1
  95. package/src/config/license-plans.json +6 -2
  96. package/src/config/mobile-ci-templates/android-github-actions.yml +149 -0
  97. package/src/config/mobile-ci-templates/ios-github-actions.yml +120 -0
@@ -0,0 +1,177 @@
1
+ // engine/coverage-gap-analyzer.ts — SPEC-323
2
+ // Maps spec acceptance criteria to existing test files and surfaces untested ACs.
3
+ import { readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { glob } from 'glob';
6
+ // ---------------------------------------------------------------------------
7
+ // Risk domain classification
8
+ // ---------------------------------------------------------------------------
9
+ /** Keywords that signal high/medium/low risk domains. */
10
+ const RISK_DOMAIN_MAP = [
11
+ {
12
+ keywords: ['auth', 'login', 'logout', 'password', 'token', 'session', 'permission', 'role'],
13
+ domain: 'auth',
14
+ level: 'high',
15
+ },
16
+ {
17
+ keywords: ['payment', 'billing', 'charge', 'invoice', 'stripe', 'refund', 'subscription'],
18
+ domain: 'payment',
19
+ level: 'high',
20
+ },
21
+ {
22
+ keywords: ['data', 'persist', 'database', 'integrity', 'transaction', 'migration', 'schema'],
23
+ domain: 'data-integrity',
24
+ level: 'high',
25
+ },
26
+ {
27
+ keywords: ['security', 'encrypt', 'decrypt', 'hash', 'sanitize', 'injection', 'xss', 'csrf'],
28
+ domain: 'security',
29
+ level: 'high',
30
+ },
31
+ {
32
+ keywords: ['api', 'endpoint', 'request', 'response', 'http', 'webhook', 'validation'],
33
+ domain: 'api',
34
+ level: 'medium',
35
+ },
36
+ {
37
+ keywords: ['email', 'notification', 'alert', 'message', 'sms'],
38
+ domain: 'notification',
39
+ level: 'medium',
40
+ },
41
+ {
42
+ keywords: ['ui', 'button', 'label', 'color', 'style', 'layout', 'cosmetic', 'visual'],
43
+ domain: 'ui',
44
+ level: 'low',
45
+ },
46
+ {
47
+ keywords: ['log', 'monitor', 'metric', 'trace', 'analytics'],
48
+ domain: 'observability',
49
+ level: 'low',
50
+ },
51
+ ];
52
+ function classifyRisk(criterion) {
53
+ const lower = criterion.toLowerCase();
54
+ for (const entry of RISK_DOMAIN_MAP) {
55
+ if (entry.keywords.some((kw) => lower.includes(kw))) {
56
+ return { domain: entry.domain, level: entry.level };
57
+ }
58
+ }
59
+ return { domain: 'general', level: 'medium' };
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // AC extraction (includes both checked [x] and unchecked [ ] criteria)
63
+ // ---------------------------------------------------------------------------
64
+ function extractAcceptanceCriteria(specContent) {
65
+ const lines = specContent.split('\n');
66
+ const criteria = [];
67
+ for (const line of lines) {
68
+ const match = /^\s*-\s*\[\s*[xX ]?\]\s+(.+)$/.exec(line);
69
+ if (match?.[1]) {
70
+ criteria.push(match[1].trim());
71
+ }
72
+ }
73
+ return criteria;
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // Test file discovery
77
+ // ---------------------------------------------------------------------------
78
+ async function discoverTestFiles(projectPath) {
79
+ const patterns = [
80
+ 'tests/**/*.test.ts',
81
+ 'tests/**/*.spec.ts',
82
+ 'test/**/*.test.ts',
83
+ 'test/**/*.spec.ts',
84
+ '__tests__/**/*.ts',
85
+ '**/*.test.py',
86
+ '**/*Test.java',
87
+ '**/*_test.go',
88
+ ];
89
+ const results = await Promise.all(patterns.map((p) => glob(p, { cwd: projectPath, absolute: true }).catch(() => [])));
90
+ return [...new Set(results.flat())];
91
+ }
92
+ /** Read all discovered test files and concatenate content for substring matching. */
93
+ async function readTestCorpus(testFiles) {
94
+ const contents = await Promise.all(testFiles.map((f) => readFile(f, 'utf8').catch(() => '')));
95
+ return contents.join('\n').toLowerCase();
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Matching
99
+ // ---------------------------------------------------------------------------
100
+ /** Extract keyword tokens from a criterion for matching against test corpus. */
101
+ function extractKeywords(criterion) {
102
+ return criterion
103
+ .toLowerCase()
104
+ .replace(/[^\w\s]/g, ' ')
105
+ .split(/\s+/)
106
+ .filter((w) => w.length >= 3)
107
+ .slice(0, 5);
108
+ }
109
+ function isCovered(criterion, corpus) {
110
+ const keywords = extractKeywords(criterion);
111
+ if (keywords.length === 0) {
112
+ return false;
113
+ }
114
+ const matchCount = keywords.filter((kw) => corpus.includes(kw)).length;
115
+ return matchCount >= Math.ceil(keywords.length / 2);
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Recommendation builder
119
+ // ---------------------------------------------------------------------------
120
+ function buildRecommendation(criterion, domain, level) {
121
+ const prefix = level === 'high'
122
+ ? 'CRITICAL: Add test immediately.'
123
+ : level === 'medium'
124
+ ? 'Add test in next sprint.'
125
+ : 'Low priority — consider adding a smoke test.';
126
+ let hint;
127
+ if (domain === 'auth') {
128
+ hint = 'Cover both valid and invalid authentication scenarios.';
129
+ }
130
+ else if (domain === 'payment') {
131
+ hint = 'Test success, failure, and refund flows with mocked payment gateway.';
132
+ }
133
+ else if (domain === 'data-integrity') {
134
+ hint = 'Test transactional rollback and constraint violations.';
135
+ }
136
+ else if (domain === 'security') {
137
+ hint = 'Include penetration test cases for injection and XSS vectors.';
138
+ }
139
+ else {
140
+ hint = `Write a test for: "${criterion}"`;
141
+ }
142
+ return `${prefix} ${hint}`;
143
+ }
144
+ // ---------------------------------------------------------------------------
145
+ // Public API
146
+ // ---------------------------------------------------------------------------
147
+ /**
148
+ * Analyze coverage gaps between spec acceptance criteria and existing test files.
149
+ */
150
+ export async function analyzeCoverageGaps(specPath, projectPath) {
151
+ const specContent = await readFile(join(specPath, 'spec.md'), 'utf8').catch(() => readFile(specPath, 'utf8'));
152
+ const criteria = extractAcceptanceCriteria(specContent);
153
+ const testFiles = await discoverTestFiles(projectPath);
154
+ const corpus = await readTestCorpus(testFiles);
155
+ const gaps = [];
156
+ let coveredCount = 0;
157
+ for (const criterion of criteria) {
158
+ if (isCovered(criterion, corpus)) {
159
+ coveredCount++;
160
+ }
161
+ else {
162
+ const { domain, level } = classifyRisk(criterion);
163
+ const recommendation = buildRecommendation(criterion, domain, level);
164
+ gaps.push({ criterion, riskDomain: domain, riskLevel: level, recommendation });
165
+ }
166
+ }
167
+ const total = criteria.length;
168
+ const coverageScore = total === 0 ? 100 : Math.round((coveredCount / total) * 100);
169
+ return {
170
+ gaps,
171
+ coverageScore,
172
+ totalCriteria: total,
173
+ coveredCriteria: coveredCount,
174
+ testFilesScanned: testFiles.length,
175
+ };
176
+ }
177
+ //# sourceMappingURL=coverage-gap-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-gap-analyzer.js","sourceRoot":"","sources":["../../src/engine/coverage-gap-analyzer.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"templates-kanban.d.ts","sourceRoot":"","sources":["../../../src/engine/dashboard/templates-kanban.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AA0B9B,sEAAsE;AACtE,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAClD,SAAS,CAAC,EAAE,eAAe,GAC1B,MAAM,CAqBR;AAaD,iDAAiD;AACjD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,gBAAgB,EACzB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CA+CR"}
1
+ {"version":3,"file":"templates-kanban.d.ts","sourceRoot":"","sources":["../../../src/engine/dashboard/templates-kanban.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAqD9B,sEAAsE;AACtE,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAClD,SAAS,CAAC,EAAE,eAAe,GAC1B,MAAM,CAqBR;AAaD,iDAAiD;AACjD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,gBAAgB,EACzB,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CA+CR"}
@@ -13,12 +13,34 @@ function statusLabel(status) {
13
13
  };
14
14
  return labels[status] ?? status;
15
15
  }
16
+ /** Render the "Move to..." dropdown for mobile Kanban cards */
17
+ function renderMoveDropdown(specId, currentStatus) {
18
+ const statuses = ['draft', 'review', 'approved', 'implementing', 'done'];
19
+ const labels = {
20
+ draft: 'Draft',
21
+ review: 'Review',
22
+ approved: 'Approved',
23
+ implementing: 'Implementing',
24
+ done: 'Done',
25
+ };
26
+ const options = statuses
27
+ .filter((s) => s !== currentStatus)
28
+ .map((s) => `<option value="${escapeHtml(s)}">${escapeHtml(labels[s] ?? s)}</option>`)
29
+ .join('');
30
+ return `<select class="kanban-card-move-select" aria-label="Move to status"
31
+ onchange="(function(sel){var pid=window.location.pathname.match(/\\/project\\/([^\\/]+)/);var projectId=pid?decodeURIComponent(pid[1]):null;if(!projectId||!sel.value)return;fetch('/api/spec/'+encodeURIComponent(projectId)+'/'+encodeURIComponent('${escapeHtml(specId)}'),{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({status:sel.value})}).then(function(){window.location.reload();}).catch(function(){});sel.value='';})(this)">
32
+ <option value="">Move to...</option>
33
+ ${options}
34
+ </select>`;
35
+ }
16
36
  /** Render a single spec card inside a kanban column */
17
37
  function renderKanbanCard(spec) {
38
+ const moveDropdown = renderMoveDropdown(spec.id, spec.status);
18
39
  return `<div class="kanban-card" draggable="true" data-spec-id="${escapeHtml(spec.id)}" data-status="${escapeHtml(spec.status)}">
19
40
  <div class="kanban-card-id">${escapeHtml(spec.id)}</div>
20
41
  <div class="kanban-card-title">${escapeHtml(spec.title)}</div>
21
42
  <div class="kanban-card-progress">${spec.progressPercent}%</div>
43
+ ${moveDropdown}
22
44
  </div>`;
23
45
  }
24
46
  /** Render the 5-column kanban board with HTML5 Drag API attributes */
@@ -1 +1 @@
1
- {"version":3,"file":"templates-kanban.js","sourceRoot":"","sources":["../../../src/engine/dashboard/templates-kanban.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"templates-kanban.js","sourceRoot":"","sources":["../../../src/engine/dashboard/templates-kanban.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"templates-layout.d.ts","sourceRoot":"","sources":["../../../src/engine/dashboard/templates-layout.ts"],"names":[],"mappings":"AAGA,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CA0FjD;AA6ID,0EAA0E;AAC1E,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AA8ID,4FAA4F;AAC5F,wBAAgB,eAAe,IAAI,MAAM,CAsBxC;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAoCR"}
1
+ {"version":3,"file":"templates-layout.d.ts","sourceRoot":"","sources":["../../../src/engine/dashboard/templates-layout.ts"],"names":[],"mappings":"AAGA,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CA0FjD;AAqJD,0EAA0E;AAC1E,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AA2KD,4FAA4F;AAC5F,wBAAgB,eAAe,IAAI,MAAM,CAmCxC;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAsCR"}
@@ -208,13 +208,15 @@ function getPageStyles() {
208
208
  font-size: 0.85rem; color: var(--text-muted); }
209
209
  .shortcut-key { background: var(--surface2); padding: 0.15rem 0.5rem; border-radius: 4px;
210
210
  font-family: monospace; font-size: 0.8rem; color: var(--text); }
211
+ .kanban-card-move-select { display: none; }
211
212
  @media (max-width: 768px) {
212
213
  .grid-2 { grid-template-columns: 1fr; }
213
214
  .spec-grid { grid-template-columns: 1fr; }
214
215
  .project-card { flex-direction: column; align-items: flex-start; gap: 0.75rem; }
215
216
  .project-stats { text-align: left; }
216
- .kanban-board { flex-direction: column; }
217
- .kanban-column { min-width: unset; }
217
+ .kanban-board { display: block; }
218
+ .kanban-column { width: 100%; min-width: unset; margin-bottom: 16px; }
219
+ .card { min-height: 44px; }
218
220
  .kanban-accordion { border: 1px solid var(--border); border-radius: var(--radius);
219
221
  margin-bottom: 0.5rem; }
220
222
  .kanban-accordion summary { padding: 0.75rem; cursor: pointer; font-weight: 600;
@@ -223,6 +225,12 @@ function getPageStyles() {
223
225
  .kanban-accordion summary::before { content: '\\25B6'; margin-right: 0.5rem; font-size: 0.7rem; }
224
226
  .kanban-accordion[open] summary::before { content: '\\25BC'; }
225
227
  .kanban-accordion .kanban-column { border: none; }
228
+ .kanban-card-move-select { display: block; margin-top: 0.5rem; width: 100%;
229
+ background: var(--surface2); border: 1px solid var(--border); border-radius: 4px;
230
+ color: var(--text); font-size: 0.8rem; padding: 0.3rem; cursor: pointer; }
231
+ }
232
+ @media (min-width: 769px) {
233
+ .kanban-card-move-select { display: none; }
226
234
  }`;
227
235
  }
228
236
  /** Base CSS for the dark theme dashboard (assembled from subfunctions) */
@@ -300,13 +308,20 @@ function getPollingLogic() {
300
308
  var projectId = getCurrentProjectId();
301
309
  if (!projectId) return;
302
310
  setStatus('Actualizando...', 'updating');
311
+ var since = lastPollTime;
312
+ lastPollTime = new Date().toISOString();
303
313
  Promise.all([
304
- fetch('/api/project/' + encodeURIComponent(projectId)).then(function(r) { return r.json(); }),
314
+ fetch('/api/events/' + encodeURIComponent(projectId) + '?since=' + encodeURIComponent(since)).then(function(r) { return r.json(); }),
305
315
  fetch('/api/project/' + encodeURIComponent(projectId) + '/health').then(function(r) { return r.json(); }),
306
316
  fetch('/api/project/' + encodeURIComponent(projectId) + '/metrics').then(function(r) { return r.json(); })
307
317
  ]).then(function(results) {
308
318
  handlePollSuccess();
309
- if (results[0] && results[0].specs) { updateSpecGrid(results[0].specs); updateKanbanCards(results[0].specs); }
319
+ var eventsData = results[0];
320
+ if (eventsData && Array.isArray(eventsData.events) && eventsData.events.length > 0) {
321
+ var changedSpecs = eventsData.events.map(function(ev) { return { id: ev.specId, status: ev.data && ev.data.status ? ev.data.status : undefined, progressPercent: 0 }; });
322
+ updateSpecGrid(changedSpecs);
323
+ updateKanbanCards(changedSpecs);
324
+ }
310
325
  updateHealthScore(results[1]);
311
326
  updateMetrics(results[2]);
312
327
  pollTimer = setTimeout(pollProject, getInterval());
@@ -364,7 +379,29 @@ function getEventListeners() {
364
379
  }
365
380
  }
366
381
  });
367
- document.querySelectorAll('.kanban-card').forEach(function(card) { card.setAttribute('tabindex', '0'); });`;
382
+ document.querySelectorAll('.kanban-card').forEach(function(card) { card.setAttribute('tabindex', '0'); });
383
+ document.addEventListener('keydown', function(e) {
384
+ var focused = document.activeElement;
385
+ if (!focused || !focused.classList.contains('kanban-card')) return;
386
+ var allColumns = Array.from(document.querySelectorAll('.kanban-column'));
387
+ var currentCol = focused.closest('.kanban-column');
388
+ var colIdx = currentCol ? allColumns.indexOf(currentCol) : -1;
389
+ var cardsInCol = currentCol ? Array.from(currentCol.querySelectorAll('.kanban-card')) : [];
390
+ var cardIdx = cardsInCol.indexOf(focused);
391
+ if (e.key === 'ArrowDown') {
392
+ var next = cardsInCol[cardIdx + 1];
393
+ if (next) { next.focus(); e.preventDefault(); }
394
+ } else if (e.key === 'ArrowUp') {
395
+ var prev = cardsInCol[cardIdx - 1];
396
+ if (prev) { prev.focus(); e.preventDefault(); }
397
+ } else if (e.key === 'ArrowRight' && colIdx >= 0) {
398
+ var nextCol = allColumns[colIdx + 1];
399
+ if (nextCol) { var firstCard = nextCol.querySelector('.kanban-card'); if (firstCard) { firstCard.focus(); e.preventDefault(); } }
400
+ } else if (e.key === 'ArrowLeft' && colIdx > 0) {
401
+ var prevCol = allColumns[colIdx - 1];
402
+ if (prevCol) { var lastCards = prevCol.querySelectorAll('.kanban-card'); var lastCard = lastCards[lastCards.length - 1]; if (lastCard) { lastCard.focus(); e.preventDefault(); } }
403
+ }
404
+ });`;
368
405
  }
369
406
  /** Client-side JS for auto-refresh polling, retry logic, keyboard nav, and tab switching */
370
407
  export function getClientScript() {
@@ -375,12 +412,25 @@ export function getClientScript() {
375
412
  var MAX_FAILURES = 3;
376
413
  var pollTimer = null;
377
414
  var failureCount = 0;
415
+ var lastPollTime = new Date(0).toISOString();
378
416
  var indicator = document.getElementById('status-indicator');
379
417
  function setStatus(msg, cls) { if (!indicator) return; indicator.textContent = msg; indicator.className = 'status-indicator' + (cls ? ' ' + cls : ''); }
380
418
  function getCurrentProjectId() { var m = window.location.pathname.match(/^\\/project\\/([^\\/]+)/); return m ? decodeURIComponent(m[1]) : null; }
381
419
  function isHomePage() { return window.location.pathname === '/' || window.location.pathname === ''; }
382
- function handlePollSuccess() { failureCount = 0; setStatus('En linea', ''); }
383
- function handlePollError() { failureCount++; setStatus(failureCount >= MAX_FAILURES ? 'Reconectando...' : 'Desconectado', 'disconnected'); }
420
+ function handlePollSuccess() {
421
+ var wasDisconnected = failureCount >= MAX_FAILURES;
422
+ failureCount = 0;
423
+ if (wasDisconnected) {
424
+ setStatus('Connected', '');
425
+ setTimeout(function() { setStatus('En linea', ''); }, 2000);
426
+ } else {
427
+ setStatus('En linea', '');
428
+ }
429
+ }
430
+ function handlePollError() {
431
+ failureCount++;
432
+ setStatus(failureCount >= MAX_FAILURES ? 'Reconectando...' : 'Desconectado', 'disconnected');
433
+ }
384
434
  function getInterval() { return failureCount >= MAX_FAILURES ? RETRY_INTERVAL : POLL_INTERVAL; }
385
435
  ${getUpdateHelpers()}
386
436
  ${getPollingLogic()}
@@ -400,6 +450,8 @@ export function renderLayout(title, body, extraStyles, extraScripts) {
400
450
  <div class="shortcut-row"><span>Show/hide shortcuts</span><span class="shortcut-key">?</span></div>
401
451
  <div class="shortcut-row"><span>Close overlay/modal</span><span class="shortcut-key">Esc</span></div>
402
452
  <div class="shortcut-row"><span>Navigate between cards</span><span class="shortcut-key">Tab</span></div>
453
+ <div class="shortcut-row"><span>Move up/down in column</span><span class="shortcut-key">↑ ↓</span></div>
454
+ <div class="shortcut-row"><span>Move between columns</span><span class="shortcut-key">← →</span></div>
403
455
  <div class="shortcut-row"><span>Open spec detail</span><span class="shortcut-key">Enter</span></div>
404
456
  </div>
405
457
  </div>`;
@@ -1 +1 @@
1
- {"version":3,"file":"templates-layout.js","sourceRoot":"","sources":["../../../src/engine/dashboard/templates-layout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"templates-layout.js","sourceRoot":"","sources":["../../../src/engine/dashboard/templates-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { MutationConfigResult } from '../types/index.js';
2
+ /**
3
+ * Detect project stack and generate the appropriate mutation testing config.
4
+ */
5
+ export declare function generateMutationConfig(projectPath: string): Promise<MutationConfigResult>;
6
+ //# sourceMappingURL=mutation-config-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-config-generator.d.ts","sourceRoot":"","sources":["../../src/engine/mutation-config-generator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAoB,MAAM,mBAAmB,CAAC;AAgGhF;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAe/F"}
@@ -0,0 +1,111 @@
1
+ // engine/mutation-config-generator.ts — SPEC-323
2
+ // Detects project stack and generates the appropriate mutation testing config file.
3
+ import { readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { getMutationTool, generateMutationConfig as buildMutationConfig, } from './advanced-testing/mutation-advisor.js';
6
+ // ---------------------------------------------------------------------------
7
+ // Stack detection
8
+ // ---------------------------------------------------------------------------
9
+ async function detectStack(projectPath) {
10
+ const pkgPath = join(projectPath, 'package.json');
11
+ try {
12
+ const raw = await readFile(pkgPath, 'utf8');
13
+ const pkg = JSON.parse(raw);
14
+ const deps = {
15
+ ...(pkg.dependencies ?? {}),
16
+ ...(pkg.devDependencies ?? {}),
17
+ };
18
+ const language = 'typescript' in deps ? 'typescript' : 'javascript';
19
+ let testFramework = 'unknown';
20
+ if ('vitest' in deps) {
21
+ testFramework = 'vitest';
22
+ }
23
+ else if ('jest' in deps) {
24
+ testFramework = 'jest';
25
+ }
26
+ else if ('mocha' in deps) {
27
+ testFramework = 'mocha';
28
+ }
29
+ return { language, testFramework };
30
+ }
31
+ catch {
32
+ const hasPython = (await readFile(join(projectPath, 'setup.py'), 'utf8').catch(() => null)) !== null ||
33
+ (await readFile(join(projectPath, 'pyproject.toml'), 'utf8').catch(() => null)) !== null;
34
+ if (hasPython) {
35
+ return { language: 'python', testFramework: 'pytest' };
36
+ }
37
+ const hasMaven = (await readFile(join(projectPath, 'pom.xml'), 'utf8').catch(() => null)) !== null;
38
+ if (hasMaven) {
39
+ return { language: 'java', testFramework: 'junit' };
40
+ }
41
+ return { language: 'typescript', testFramework: 'unknown' };
42
+ }
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // Config file name resolution
46
+ // ---------------------------------------------------------------------------
47
+ function resolveConfigFileName(tool) {
48
+ switch (tool) {
49
+ case 'stryker':
50
+ return 'stryker.config.mjs';
51
+ case 'mutmut':
52
+ case 'cosmic-ray':
53
+ return 'setup.cfg';
54
+ case 'pitest':
55
+ return 'pom.xml (add mutation plugin section)';
56
+ case 'cargo-mutants':
57
+ return '.cargo-mutants.toml';
58
+ case 'gremlins':
59
+ return '.gremlins.yaml';
60
+ case 'stryker4s':
61
+ return 'stryker4s.conf';
62
+ case 'stryker-dotnet':
63
+ return 'stryker-config.json';
64
+ default:
65
+ return 'stryker.config.mjs';
66
+ }
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // CI step generation
70
+ // ---------------------------------------------------------------------------
71
+ function buildCiStep(tool) {
72
+ switch (tool) {
73
+ case 'stryker':
74
+ return 'npx stryker run';
75
+ case 'mutmut':
76
+ return 'python -m mutmut run && python -m mutmut results';
77
+ case 'pitest':
78
+ return 'mvn test-compile org.pitest:pitest-maven:mutationCoverage';
79
+ case 'cargo-mutants':
80
+ return 'cargo mutants';
81
+ case 'gremlins':
82
+ return 'gremlins unleash ./...';
83
+ case 'stryker4s':
84
+ return 'sbt stryker';
85
+ case 'stryker-dotnet':
86
+ return 'dotnet stryker';
87
+ default:
88
+ return 'npx stryker run';
89
+ }
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Public API
93
+ // ---------------------------------------------------------------------------
94
+ /**
95
+ * Detect project stack and generate the appropriate mutation testing config.
96
+ */
97
+ export async function generateMutationConfig(projectPath) {
98
+ const stack = await detectStack(projectPath);
99
+ const tool = getMutationTool(stack.language);
100
+ const mutationConfig = buildMutationConfig(tool, stack.language);
101
+ const configFileName = resolveConfigFileName(tool);
102
+ const ciStep = buildCiStep(tool);
103
+ return {
104
+ configFileName,
105
+ configContent: mutationConfig.configFile,
106
+ ciStep,
107
+ framework: stack.testFramework,
108
+ tool,
109
+ };
110
+ }
111
+ //# sourceMappingURL=mutation-config-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-config-generator.js","sourceRoot":"","sources":["../../src/engine/mutation-config-generator.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,oFAAoF;AAEpF,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,eAAe,EACf,sBAAsB,IAAI,mBAAmB,GAC9C,MAAM,wCAAwC,CAAC;AAGhD,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,KAAK,UAAU,WAAW,CAAC,WAAmB;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QACvD,MAAM,IAAI,GAA4B;YACpC,GAAG,CAAE,GAAG,CAAC,YAAoD,IAAI,EAAE,CAAC;YACpE,GAAG,CAAE,GAAG,CAAC,eAAuD,IAAI,EAAE,CAAC;SACxE,CAAC;QACF,MAAM,QAAQ,GAAG,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;QACpE,IAAI,aAAa,GAAsC,SAAS,CAAC;QACjE,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,aAAa,GAAG,QAAQ,CAAC;QAC3B,CAAC;aAAM,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YAC1B,aAAa,GAAG,MAAM,CAAC;QACzB,CAAC;aAAM,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YAC3B,aAAa,GAAG,OAAO,CAAC;QAC1B,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,SAAS,GACb,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI;YAClF,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC;QAC3F,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;QACzD,CAAC;QACD,MAAM,QAAQ,GACZ,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC;QACpF,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC;QACtD,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;IAC9D,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,8BAA8B;AAC9B,8EAA8E;AAE9E,SAAS,qBAAqB,CAAC,IAAY;IACzC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,oBAAoB,CAAC;QAC9B,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY;YACf,OAAO,WAAW,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,uCAAuC,CAAC;QACjD,KAAK,eAAe;YAClB,OAAO,qBAAqB,CAAC;QAC/B,KAAK,UAAU;YACb,OAAO,gBAAgB,CAAC;QAC1B,KAAK,WAAW;YACd,OAAO,gBAAgB,CAAC;QAC1B,KAAK,gBAAgB;YACnB,OAAO,qBAAqB,CAAC;QAC/B;YACE,OAAO,oBAAoB,CAAC;IAChC,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,SAAS,WAAW,CAAC,IAAY;IAC/B,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,iBAAiB,CAAC;QAC3B,KAAK,QAAQ;YACX,OAAO,kDAAkD,CAAC;QAC5D,KAAK,QAAQ;YACX,OAAO,2DAA2D,CAAC;QACrE,KAAK,eAAe;YAClB,OAAO,eAAe,CAAC;QACzB,KAAK,UAAU;YACb,OAAO,wBAAwB,CAAC;QAClC,KAAK,WAAW;YACd,OAAO,aAAa,CAAC;QACvB,KAAK,gBAAgB;YACnB,OAAO,gBAAgB,CAAC;QAC1B;YACE,OAAO,iBAAiB,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,WAAmB;IAC9D,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,cAAc,GAAG,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAEjE,MAAM,cAAc,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAEjC,OAAO;QACL,cAAc;QACd,aAAa,EAAE,cAAc,CAAC,UAAU;QACxC,MAAM;QACN,SAAS,EAAE,KAAK,CAAC,aAAa;QAC9B,IAAI;KACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { TddScaffoldResult } from '../types/index.js';
2
+ /**
3
+ * Generate a TDD scaffold from a spec's acceptance criteria.
4
+ * Returns paths and content for a failing test file and an impl skeleton.
5
+ */
6
+ export declare function generateTddScaffold(specPath: string, projectPath: string): Promise<TddScaffoldResult>;
7
+ //# sourceMappingURL=tdd-scaffold-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tdd-scaffold-generator.d.ts","sourceRoot":"","sources":["../../src/engine/tdd-scaffold-generator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAiC,MAAM,mBAAmB,CAAC;AAiO1F;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,CAAC,CA4C5B"}