@planu/cli 1.11.0 → 1.12.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 (179) hide show
  1. package/dist/config/license-plans.json +27 -2
  2. package/dist/engine/api-compat/compatibility-checker.d.ts +4 -0
  3. package/dist/engine/api-compat/compatibility-checker.d.ts.map +1 -0
  4. package/dist/engine/api-compat/compatibility-checker.js +118 -0
  5. package/dist/engine/api-compat/compatibility-checker.js.map +1 -0
  6. package/dist/engine/checkpoint/checkpoint-manager.d.ts +22 -0
  7. package/dist/engine/checkpoint/checkpoint-manager.d.ts.map +1 -0
  8. package/dist/engine/checkpoint/checkpoint-manager.js +76 -0
  9. package/dist/engine/checkpoint/checkpoint-manager.js.map +1 -0
  10. package/dist/engine/checkpoint/policy-engine.d.ts +10 -0
  11. package/dist/engine/checkpoint/policy-engine.d.ts.map +1 -0
  12. package/dist/engine/checkpoint/policy-engine.js +87 -0
  13. package/dist/engine/checkpoint/policy-engine.js.map +1 -0
  14. package/dist/engine/compliance/auto-remediator.d.ts +9 -0
  15. package/dist/engine/compliance/auto-remediator.d.ts.map +1 -0
  16. package/dist/engine/compliance/auto-remediator.js +118 -0
  17. package/dist/engine/compliance/auto-remediator.js.map +1 -0
  18. package/dist/engine/context-profile/profile-catalog.d.ts +5 -0
  19. package/dist/engine/context-profile/profile-catalog.d.ts.map +1 -0
  20. package/dist/engine/context-profile/profile-catalog.js +145 -0
  21. package/dist/engine/context-profile/profile-catalog.js.map +1 -0
  22. package/dist/engine/critical-path/path-analyzer.d.ts +3 -0
  23. package/dist/engine/critical-path/path-analyzer.d.ts.map +1 -0
  24. package/dist/engine/critical-path/path-analyzer.js +145 -0
  25. package/dist/engine/critical-path/path-analyzer.js.map +1 -0
  26. package/dist/engine/drift/violation-resolver.d.ts +9 -0
  27. package/dist/engine/drift/violation-resolver.d.ts.map +1 -0
  28. package/dist/engine/drift/violation-resolver.js +128 -0
  29. package/dist/engine/drift/violation-resolver.js.map +1 -0
  30. package/dist/engine/ears/criterion-scorer.d.ts +7 -0
  31. package/dist/engine/ears/criterion-scorer.d.ts.map +1 -0
  32. package/dist/engine/ears/criterion-scorer.js +87 -0
  33. package/dist/engine/ears/criterion-scorer.js.map +1 -0
  34. package/dist/engine/ears/pattern-matcher.d.ts +5 -0
  35. package/dist/engine/ears/pattern-matcher.d.ts.map +1 -0
  36. package/dist/engine/ears/pattern-matcher.js +48 -0
  37. package/dist/engine/ears/pattern-matcher.js.map +1 -0
  38. package/dist/engine/ears/rewriter.d.ts +7 -0
  39. package/dist/engine/ears/rewriter.d.ts.map +1 -0
  40. package/dist/engine/ears/rewriter.js +45 -0
  41. package/dist/engine/ears/rewriter.js.map +1 -0
  42. package/dist/engine/ears/spec-linter.d.ts +7 -0
  43. package/dist/engine/ears/spec-linter.d.ts.map +1 -0
  44. package/dist/engine/ears/spec-linter.js +127 -0
  45. package/dist/engine/ears/spec-linter.js.map +1 -0
  46. package/dist/engine/health/auto-fixer.d.ts +7 -0
  47. package/dist/engine/health/auto-fixer.d.ts.map +1 -0
  48. package/dist/engine/health/auto-fixer.js +130 -0
  49. package/dist/engine/health/auto-fixer.js.map +1 -0
  50. package/dist/engine/mcp-catalog/catalog-advisor.d.ts +3 -0
  51. package/dist/engine/mcp-catalog/catalog-advisor.d.ts.map +1 -0
  52. package/dist/engine/mcp-catalog/catalog-advisor.js +180 -0
  53. package/dist/engine/mcp-catalog/catalog-advisor.js.map +1 -0
  54. package/dist/engine/similar-problems/similarity-finder.d.ts +3 -0
  55. package/dist/engine/similar-problems/similarity-finder.d.ts.map +1 -0
  56. package/dist/engine/similar-problems/similarity-finder.js +144 -0
  57. package/dist/engine/similar-problems/similarity-finder.js.map +1 -0
  58. package/dist/engine/sync/asana-puller.d.ts +9 -0
  59. package/dist/engine/sync/asana-puller.d.ts.map +1 -0
  60. package/dist/engine/sync/asana-puller.js +91 -0
  61. package/dist/engine/sync/asana-puller.js.map +1 -0
  62. package/dist/engine/sync/conflict-resolver.d.ts +17 -0
  63. package/dist/engine/sync/conflict-resolver.d.ts.map +1 -0
  64. package/dist/engine/sync/conflict-resolver.js +58 -0
  65. package/dist/engine/sync/conflict-resolver.js.map +1 -0
  66. package/dist/engine/sync/monday-puller.d.ts +9 -0
  67. package/dist/engine/sync/monday-puller.d.ts.map +1 -0
  68. package/dist/engine/sync/monday-puller.js +110 -0
  69. package/dist/engine/sync/monday-puller.js.map +1 -0
  70. package/dist/engine/sync/notion-puller.d.ts +15 -0
  71. package/dist/engine/sync/notion-puller.d.ts.map +1 -0
  72. package/dist/engine/sync/notion-puller.js +101 -0
  73. package/dist/engine/sync/notion-puller.js.map +1 -0
  74. package/dist/engine/verifier/code-scanner.d.ts +8 -0
  75. package/dist/engine/verifier/code-scanner.d.ts.map +1 -0
  76. package/dist/engine/verifier/code-scanner.js +73 -0
  77. package/dist/engine/verifier/code-scanner.js.map +1 -0
  78. package/dist/engine/verifier/compliance-scorer.d.ts +17 -0
  79. package/dist/engine/verifier/compliance-scorer.d.ts.map +1 -0
  80. package/dist/engine/verifier/compliance-scorer.js +131 -0
  81. package/dist/engine/verifier/compliance-scorer.js.map +1 -0
  82. package/dist/engine/verifier/criterion-matcher.d.ts +15 -0
  83. package/dist/engine/verifier/criterion-matcher.d.ts.map +1 -0
  84. package/dist/engine/verifier/criterion-matcher.js +210 -0
  85. package/dist/engine/verifier/criterion-matcher.js.map +1 -0
  86. package/dist/index.js +14 -0
  87. package/dist/index.js.map +1 -1
  88. package/dist/storage/compliance-score-store.d.ts +16 -0
  89. package/dist/storage/compliance-score-store.d.ts.map +1 -0
  90. package/dist/storage/compliance-score-store.js +30 -0
  91. package/dist/storage/compliance-score-store.js.map +1 -0
  92. package/dist/storage/context-profile-store.d.ts +14 -0
  93. package/dist/storage/context-profile-store.d.ts.map +1 -0
  94. package/dist/storage/context-profile-store.js +34 -0
  95. package/dist/storage/context-profile-store.js.map +1 -0
  96. package/dist/storage/workflow-checkpoint-store.d.ts +16 -0
  97. package/dist/storage/workflow-checkpoint-store.d.ts.map +1 -0
  98. package/dist/storage/workflow-checkpoint-store.js +71 -0
  99. package/dist/storage/workflow-checkpoint-store.js.map +1 -0
  100. package/dist/tools/checkpoint/approve-checkpoint-handler.d.ts +3 -0
  101. package/dist/tools/checkpoint/approve-checkpoint-handler.d.ts.map +1 -0
  102. package/dist/tools/checkpoint/approve-checkpoint-handler.js +32 -0
  103. package/dist/tools/checkpoint/approve-checkpoint-handler.js.map +1 -0
  104. package/dist/tools/checkpoint/configure-policy-handler.d.ts +3 -0
  105. package/dist/tools/checkpoint/configure-policy-handler.d.ts.map +1 -0
  106. package/dist/tools/checkpoint/configure-policy-handler.js +60 -0
  107. package/dist/tools/checkpoint/configure-policy-handler.js.map +1 -0
  108. package/dist/tools/checkpoint/list-checkpoints-handler.d.ts +3 -0
  109. package/dist/tools/checkpoint/list-checkpoints-handler.d.ts.map +1 -0
  110. package/dist/tools/checkpoint/list-checkpoints-handler.js +25 -0
  111. package/dist/tools/checkpoint/list-checkpoints-handler.js.map +1 -0
  112. package/dist/tools/checkpoint/reject-checkpoint-handler.d.ts +3 -0
  113. package/dist/tools/checkpoint/reject-checkpoint-handler.d.ts.map +1 -0
  114. package/dist/tools/checkpoint/reject-checkpoint-handler.js +32 -0
  115. package/dist/tools/checkpoint/reject-checkpoint-handler.js.map +1 -0
  116. package/dist/tools/checkpoint/require-checkpoint-handler.d.ts +3 -0
  117. package/dist/tools/checkpoint/require-checkpoint-handler.d.ts.map +1 -0
  118. package/dist/tools/checkpoint/require-checkpoint-handler.js +44 -0
  119. package/dist/tools/checkpoint/require-checkpoint-handler.js.map +1 -0
  120. package/dist/tools/pull-sync-handler.d.ts +25 -0
  121. package/dist/tools/pull-sync-handler.d.ts.map +1 -0
  122. package/dist/tools/pull-sync-handler.js +161 -0
  123. package/dist/tools/pull-sync-handler.js.map +1 -0
  124. package/dist/tools/register-auto-remediation.d.ts +3 -0
  125. package/dist/tools/register-auto-remediation.d.ts.map +1 -0
  126. package/dist/tools/register-auto-remediation.js +174 -0
  127. package/dist/tools/register-auto-remediation.js.map +1 -0
  128. package/dist/tools/register-checkpoints.d.ts +3 -0
  129. package/dist/tools/register-checkpoints.d.ts.map +1 -0
  130. package/dist/tools/register-checkpoints.js +134 -0
  131. package/dist/tools/register-checkpoints.js.map +1 -0
  132. package/dist/tools/register-context-profile.d.ts +3 -0
  133. package/dist/tools/register-context-profile.d.ts.map +1 -0
  134. package/dist/tools/register-context-profile.js +106 -0
  135. package/dist/tools/register-context-profile.js.map +1 -0
  136. package/dist/tools/register-ears.d.ts +3 -0
  137. package/dist/tools/register-ears.d.ts.map +1 -0
  138. package/dist/tools/register-ears.js +148 -0
  139. package/dist/tools/register-ears.js.map +1 -0
  140. package/dist/tools/register-enterprise-compliance.js +1 -1
  141. package/dist/tools/register-enterprise-compliance.js.map +1 -1
  142. package/dist/tools/register-pull-sync.d.ts +3 -0
  143. package/dist/tools/register-pull-sync.d.ts.map +1 -0
  144. package/dist/tools/register-pull-sync.js +71 -0
  145. package/dist/tools/register-pull-sync.js.map +1 -0
  146. package/dist/tools/register-spec405-tools.d.ts +7 -0
  147. package/dist/tools/register-spec405-tools.d.ts.map +1 -0
  148. package/dist/tools/register-spec405-tools.js +194 -0
  149. package/dist/tools/register-spec405-tools.js.map +1 -0
  150. package/dist/tools/register-verifier.d.ts +3 -0
  151. package/dist/tools/register-verifier.d.ts.map +1 -0
  152. package/dist/tools/register-verifier.js +141 -0
  153. package/dist/tools/register-verifier.js.map +1 -0
  154. package/dist/types/analysis.d.ts +98 -0
  155. package/dist/types/analysis.d.ts.map +1 -1
  156. package/dist/types/context-profile.d.ts +22 -0
  157. package/dist/types/context-profile.d.ts.map +1 -0
  158. package/dist/types/context-profile.js +2 -0
  159. package/dist/types/context-profile.js.map +1 -0
  160. package/dist/types/ears.d.ts +34 -0
  161. package/dist/types/ears.d.ts.map +1 -0
  162. package/dist/types/ears.js +3 -0
  163. package/dist/types/ears.js.map +1 -0
  164. package/dist/types/health.d.ts +40 -0
  165. package/dist/types/health.d.ts.map +1 -0
  166. package/dist/types/health.js +3 -0
  167. package/dist/types/health.js.map +1 -0
  168. package/dist/types/index.d.ts +4 -0
  169. package/dist/types/index.d.ts.map +1 -1
  170. package/dist/types/index.js +4 -0
  171. package/dist/types/index.js.map +1 -1
  172. package/dist/types/notion-asana-monday.d.ts +38 -0
  173. package/dist/types/notion-asana-monday.d.ts.map +1 -1
  174. package/dist/types/workflow-checkpoint.d.ts +66 -0
  175. package/dist/types/workflow-checkpoint.d.ts.map +1 -0
  176. package/dist/types/workflow-checkpoint.js +4 -0
  177. package/dist/types/workflow-checkpoint.js.map +1 -0
  178. package/package.json +1 -1
  179. package/src/config/license-plans.json +27 -2
@@ -0,0 +1,45 @@
1
+ // engine/ears/rewriter.ts — Template-driven EARS rewrite suggestions (SPEC-410)
2
+ // Simple regex-based extraction helpers
3
+ function extractNounPhrase(text) {
4
+ // Strip leading action words and extract a subject noun phrase
5
+ const stripped = text.replace(/^(the system shall|when|while|if|then|where)\s+/i, '').trim();
6
+ // Take the first clause up to a comma, period, or conjunction
7
+ const match = /^([^,.(]+?)(?:\s+(?:should|must|shall|will|can|is|are|has|have)|[,.(]|$)/i.exec(stripped);
8
+ const raw = match?.[1]?.trim() ?? stripped.slice(0, 60).trim();
9
+ return raw.length > 0 ? raw : 'the user performs an action';
10
+ }
11
+ function extractVerbPhrase(text) {
12
+ // Look for a verb phrase after common trigger words
13
+ const patterns = [
14
+ /the system shall\s+(.+?)(?:\s+within\s+\d+|\s*$)/i,
15
+ /(?:should|must|will)\s+(?:be able to\s+)?(.+?)(?:\s+within\s+\d+|\s*$)/i,
16
+ /(?:displays?|shows?|returns?|navigates?\s+to|validates?|saves?|loads?|creates?|updates?|deletes?)\s+.+/i,
17
+ ];
18
+ for (const pattern of patterns) {
19
+ const match = pattern.exec(text);
20
+ if (match?.[1] !== undefined) {
21
+ return match[1].trim().slice(0, 80);
22
+ }
23
+ if (match?.[0] !== undefined && pattern === patterns[2]) {
24
+ return match[0].trim().slice(0, 80);
25
+ }
26
+ }
27
+ // Fallback: strip leading qualifiers
28
+ const stripped = text
29
+ .replace(/^(the system shall|when|while|if|then|where|the user|user)\s+/i, '')
30
+ .replace(/\s+(should|must|shall|will)\s+/, ' ')
31
+ .trim();
32
+ return stripped.slice(0, 80) || 'complete the requested operation';
33
+ }
34
+ /**
35
+ * Generate 2 EARS-format rewrite suggestions using templates.
36
+ * Template-driven only — no LLM calls.
37
+ */
38
+ export function generateRewrites(criterion, _pattern) {
39
+ const nounPhrase = extractNounPhrase(criterion);
40
+ const verbPhrase = extractVerbPhrase(criterion);
41
+ const suggestion1 = `WHEN ${nounPhrase}, THE SYSTEM SHALL ${verbPhrase}`;
42
+ const suggestion2 = `WHEN ${nounPhrase}, THE SYSTEM SHALL ${verbPhrase} within 2 seconds`;
43
+ return [suggestion1, suggestion2];
44
+ }
45
+ //# sourceMappingURL=rewriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewriter.js","sourceRoot":"","sources":["../../../src/engine/ears/rewriter.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAIhF,wCAAwC;AACxC,SAAS,iBAAiB,CAAC,IAAY;IACrC,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,kDAAkD,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAE7F,8DAA8D;IAC9D,MAAM,KAAK,GAAG,2EAA2E,CAAC,IAAI,CAC5F,QAAQ,CACT,CAAC;IACF,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAE/D,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,6BAA6B,CAAC;AAC9D,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY;IACrC,oDAAoD;IACpD,MAAM,QAAQ,GAAG;QACf,mDAAmD;QACnD,yEAAyE;QACzE,yGAAyG;KAC1G,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,qCAAqC;IACrC,MAAM,QAAQ,GAAG,IAAI;SAClB,OAAO,CAAC,gEAAgE,EAAE,EAAE,CAAC;SAC7E,OAAO,CAAC,gCAAgC,EAAE,GAAG,CAAC;SAC9C,IAAI,EAAE,CAAC;IAEV,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,kCAAkC,CAAC;AACrE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,QAAqB;IACvE,MAAM,UAAU,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAEhD,MAAM,WAAW,GAAG,QAAQ,UAAU,sBAAsB,UAAU,EAAE,CAAC;IACzE,MAAM,WAAW,GAAG,QAAQ,UAAU,sBAAsB,UAAU,mBAAmB,CAAC;IAE1F,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { EarsSpecReport, ProjectEarsReport } from '../../types/ears.js';
2
+ /**
3
+ * Lint a single spec by ID — reads spec.md from the planu/specs directory.
4
+ */
5
+ export declare function lintSpec(specId: string, projectPath: string): Promise<EarsSpecReport>;
6
+ export declare function lintAllSpecs(projectPath: string): Promise<ProjectEarsReport>;
7
+ //# sourceMappingURL=spec-linter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-linter.d.ts","sourceRoot":"","sources":["../../../src/engine/ears/spec-linter.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAoB,MAAM,qBAAqB,CAAC;AAuD/F;;GAEG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAyB3F;AAGD,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkDlF"}
@@ -0,0 +1,127 @@
1
+ // engine/ears/spec-linter.ts — EARS-based spec linting (SPEC-410)
2
+ import { readFile } from 'node:fs/promises';
3
+ import { existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { glob } from 'glob';
6
+ import { scoreCriterion, gradeSpec } from './criterion-scorer.js';
7
+ // Regex to extract acceptance criteria lines (checkbox format)
8
+ const AC_LINE_REGEX = /^\s*-\s+\[[ xX]\]\s+(.+)$/;
9
+ /**
10
+ * Extract acceptance criteria texts from spec.md content.
11
+ * Recognizes lines formatted as "- [ ] criterion" or "- [x] criterion".
12
+ */
13
+ function extractCriteria(content) {
14
+ return content
15
+ .split('\n')
16
+ .map((line) => AC_LINE_REGEX.exec(line))
17
+ .filter((m) => m !== null)
18
+ .map((m) => {
19
+ const text = m[1];
20
+ // This is safe: AC_LINE_REGEX always captures group 1 when match succeeds
21
+ return (text ?? '').trim();
22
+ })
23
+ .filter((t) => t.length > 0);
24
+ }
25
+ function buildSpecReport(specId, criteria) {
26
+ if (criteria.length === 0) {
27
+ return {
28
+ specId,
29
+ criteriaCount: 0,
30
+ overallScore: 0,
31
+ compliantCount: 0,
32
+ partialCount: 0,
33
+ nonCompliantCount: 0,
34
+ criteria: [],
35
+ grade: 'F',
36
+ };
37
+ }
38
+ const overallScore = Math.round((criteria.reduce((sum, c) => sum + c.overallScore, 0) / criteria.length) * 10) / 10;
39
+ const compliantCount = criteria.filter((c) => c.earsCompliant).length;
40
+ const nonCompliantCount = criteria.filter((c) => !c.earsCompliant).length;
41
+ const partialCount = 0; // reserved for future partial-match detection
42
+ return {
43
+ specId,
44
+ criteriaCount: criteria.length,
45
+ overallScore,
46
+ compliantCount,
47
+ partialCount,
48
+ nonCompliantCount,
49
+ criteria,
50
+ grade: gradeSpec(overallScore),
51
+ };
52
+ }
53
+ /**
54
+ * Lint a single spec by ID — reads spec.md from the planu/specs directory.
55
+ */
56
+ export async function lintSpec(specId, projectPath) {
57
+ const specDir = join(projectPath, 'planu', 'specs');
58
+ const pattern = `${specId}-*/spec.md`;
59
+ const matches = await glob(pattern, { cwd: specDir, absolute: false });
60
+ if (matches.length === 0) {
61
+ // Try exact match without trailing glob
62
+ const directPath = join(specDir, specId, 'spec.md');
63
+ if (existsSync(directPath)) {
64
+ const content = await readFile(directPath, 'utf-8');
65
+ const extracted = extractCriteria(content);
66
+ return buildSpecReport(specId, extracted.map(scoreCriterion));
67
+ }
68
+ return buildSpecReport(specId, []);
69
+ }
70
+ const specPath = join(specDir, matches[0] ?? '');
71
+ try {
72
+ const content = await readFile(specPath, 'utf-8');
73
+ const extracted = extractCriteria(content);
74
+ return buildSpecReport(specId, extracted.map(scoreCriterion));
75
+ }
76
+ catch {
77
+ // Unreadable — return empty report
78
+ return buildSpecReport(specId, []);
79
+ }
80
+ }
81
+ // Lint all specs in the project — reads all planu/specs/SPEC-NNN/spec.md files.
82
+ export async function lintAllSpecs(projectPath) {
83
+ const specDir = join(projectPath, 'planu', 'specs');
84
+ const specFiles = await glob('*/spec.md', { cwd: specDir, absolute: false });
85
+ const specReports = [];
86
+ for (const relPath of specFiles) {
87
+ const specDirName = relPath.split('/')[0] ?? '';
88
+ // Extract SPEC-XXX from directory name like "SPEC-042-some-slug"
89
+ const specIdMatch = /^(SPEC-\d+)/i.exec(specDirName);
90
+ const specId = specIdMatch?.[1] ?? specDirName;
91
+ const fullPath = join(specDir, relPath);
92
+ let extracted;
93
+ try {
94
+ const content = await readFile(fullPath, 'utf-8');
95
+ extracted = extractCriteria(content);
96
+ }
97
+ catch {
98
+ continue;
99
+ }
100
+ if (extracted.length > 0) {
101
+ const report = buildSpecReport(specId, extracted.map(scoreCriterion));
102
+ specReports.push(report);
103
+ }
104
+ }
105
+ const totalSpecs = specReports.length;
106
+ const averageScore = totalSpecs > 0
107
+ ? Math.round((specReports.reduce((sum, r) => sum + r.overallScore, 0) / totalSpecs) * 10) / 10
108
+ : 0;
109
+ // Collect all criteria across specs and find the 10 worst by overallScore
110
+ const allCriteria = [];
111
+ for (const report of specReports) {
112
+ for (const c of report.criteria) {
113
+ allCriteria.push({ criterion: c.text, specId: report.specId, score: c.overallScore });
114
+ }
115
+ }
116
+ allCriteria.sort((a, b) => a.score - b.score);
117
+ const topWorstCriteria = allCriteria.slice(0, 10);
118
+ return {
119
+ projectPath,
120
+ totalSpecs,
121
+ averageScore,
122
+ topWorstCriteria,
123
+ specReports,
124
+ generatedAt: new Date().toISOString(),
125
+ };
126
+ }
127
+ //# sourceMappingURL=spec-linter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-linter.js","sourceRoot":"","sources":["../../../src/engine/ears/spec-linter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { AutoFixResult } from '../../types/health.js';
2
+ /**
3
+ * Reads health check output and applies deterministic fixes where possible.
4
+ * When dryRun=true, reports what would be fixed without writing any files.
5
+ */
6
+ export declare function autoFixHealth(projectPath: string, projectId: string, dryRun?: boolean): Promise<AutoFixResult>;
7
+ //# sourceMappingURL=auto-fixer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-fixer.d.ts","sourceRoot":"","sources":["../../../src/engine/health/auto-fixer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,uBAAuB,CAAC;AAqHlF;;;GAGG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,UAAQ,GACb,OAAO,CAAC,aAAa,CAAC,CAiCxB"}
@@ -0,0 +1,130 @@
1
+ // engine/health/auto-fixer.ts — Auto-fix for health check issues (SPEC-408)
2
+ import { mkdir, writeFile, access } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { specStore } from '../../storage/index.js';
5
+ import { computeSpecHealth } from '../validation-loop.js';
6
+ async function collectHealthIssues(projectPath, projectId) {
7
+ const specs = await specStore.listSpecs(projectId);
8
+ const issues = [];
9
+ const scores = await Promise.all(specs.map(async (spec) => {
10
+ const score = await computeSpecHealth(projectPath, spec);
11
+ return { spec, score };
12
+ }));
13
+ for (const { spec, score } of scores) {
14
+ if (score.breakdown.specComplete < 20) {
15
+ issues.push({
16
+ type: 'incomplete-spec',
17
+ specId: spec.id,
18
+ specPath: spec.specPath,
19
+ projectPath,
20
+ });
21
+ }
22
+ if (score.breakdown.testsExist < 20) {
23
+ issues.push({ type: 'no-tests', specId: spec.id, projectPath });
24
+ }
25
+ if (score.breakdown.noDrift < 20) {
26
+ issues.push({ type: 'drift-detected', specId: spec.id, projectPath });
27
+ }
28
+ }
29
+ return issues;
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // Fix handlers
33
+ // ---------------------------------------------------------------------------
34
+ async function fixMissingProgress(issue, dryRun) {
35
+ const specId = issue.specId;
36
+ const progressPath = join(issue.projectPath, 'planu', 'specs', specId, 'progress.md');
37
+ try {
38
+ await access(progressPath);
39
+ return null; // Already exists
40
+ }
41
+ catch {
42
+ // Does not exist — create it
43
+ if (!dryRun) {
44
+ await mkdir(join(issue.projectPath, 'planu', 'specs', specId), { recursive: true });
45
+ const content = `# ${specId} — Progress\n\nstatus: draft\n\n## Tasks\n\n- [ ] Define acceptance criteria\n`;
46
+ await writeFile(progressPath, content, 'utf-8');
47
+ }
48
+ return {
49
+ issue: 'missing-progress.md',
50
+ action: dryRun
51
+ ? 'would create progress.md from template'
52
+ : 'created progress.md from template',
53
+ affectedItem: specId,
54
+ success: true,
55
+ };
56
+ }
57
+ }
58
+ function buildSkipForIncompleteSpec(specId) {
59
+ return {
60
+ issue: `incomplete-spec: ${specId}`,
61
+ reason: 'Missing required spec fields cannot be auto-fixed — human authoring required',
62
+ manualSteps: [
63
+ `Open planu/specs/${specId}/spec.md`,
64
+ 'Add title, description, and at least one acceptance criterion',
65
+ 'Re-run health_check_all to verify',
66
+ ],
67
+ };
68
+ }
69
+ function buildSkipForNoTests(specId) {
70
+ return {
71
+ issue: `no-tests: ${specId}`,
72
+ reason: 'Test files must be authored by a developer — cannot auto-generate without implementation context',
73
+ manualSteps: [
74
+ `Create tests/ file referencing ${specId}`,
75
+ 'Implement at least one test case covering a key acceptance criterion',
76
+ 'Run pnpm test to confirm',
77
+ ],
78
+ };
79
+ }
80
+ function buildSkipForDrift(specId) {
81
+ return {
82
+ issue: `drift-detected: ${specId}`,
83
+ reason: 'Drift requires manual reconciliation between spec and implementation',
84
+ manualSteps: [
85
+ `Run detect_drift with specId: "${specId}" for a detailed report`,
86
+ 'Review which criteria are no longer implemented',
87
+ 'Either update the spec or fix the code to match',
88
+ ],
89
+ };
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Public API
93
+ // ---------------------------------------------------------------------------
94
+ /**
95
+ * Reads health check output and applies deterministic fixes where possible.
96
+ * When dryRun=true, reports what would be fixed without writing any files.
97
+ */
98
+ export async function autoFixHealth(projectPath, projectId, dryRun = false) {
99
+ const issues = await collectHealthIssues(projectPath, projectId);
100
+ const fixes = [];
101
+ const skipped = [];
102
+ for (const issue of issues) {
103
+ switch (issue.type) {
104
+ case 'missing-progress': {
105
+ const fix = await fixMissingProgress(issue, dryRun);
106
+ if (fix) {
107
+ fixes.push(fix);
108
+ }
109
+ break;
110
+ }
111
+ case 'incomplete-spec':
112
+ skipped.push(buildSkipForIncompleteSpec(issue.specId));
113
+ break;
114
+ case 'no-tests':
115
+ skipped.push(buildSkipForNoTests(issue.specId));
116
+ break;
117
+ case 'drift-detected':
118
+ skipped.push(buildSkipForDrift(issue.specId));
119
+ break;
120
+ }
121
+ }
122
+ return {
123
+ fixedCount: fixes.filter((f) => f.success).length,
124
+ skippedCount: skipped.length,
125
+ fixes,
126
+ skipped,
127
+ executedAt: new Date().toISOString(),
128
+ };
129
+ }
130
+ //# sourceMappingURL=auto-fixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-fixer.js","sourceRoot":"","sources":["../../../src/engine/health/auto-fixer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpSuggestionResult } from '../../types/index.js';
2
+ export declare function suggestMcpServers(description: string, stack?: string): McpSuggestionResult;
3
+ //# sourceMappingURL=catalog-advisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-advisor.d.ts","sourceRoot":"","sources":["../../../src/engine/mcp-catalog/catalog-advisor.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,mBAAmB,EAEpB,MAAM,sBAAsB,CAAC;AA6J9B,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAwB1F"}
@@ -0,0 +1,180 @@
1
+ // engine/mcp-catalog/catalog-advisor.ts — SPEC-405
2
+ // Match user descriptions against a hardcoded catalog of popular MCP servers.
3
+ //
4
+ // AUTO-TRIGGER INTEGRATION NOTE:
5
+ // This module is designed to be called from create-spec.ts when certain
6
+ // keywords are detected in the spec description (e.g., 'database', 'payment',
7
+ // 'github', 'email'). The integration point is after spec creation, before
8
+ // returning the result. Add a comment "// TODO: suggest_mcp_server trigger"
9
+ // in the create-spec handler when implementing this.
10
+ // ---------------------------------------------------------------------------
11
+ // Catalog
12
+ // ---------------------------------------------------------------------------
13
+ const MCP_CATALOG = [
14
+ {
15
+ name: 'github',
16
+ description: 'GitHub repos, issues, PRs',
17
+ keywords: ['git', 'pr', 'issue', 'repository', 'code', 'github', 'pull', 'branch', 'commit'],
18
+ install: 'npx -y @modelcontextprotocol/server-github',
19
+ useCases: ['version control', 'code review'],
20
+ },
21
+ {
22
+ name: 'supabase',
23
+ description: 'Supabase database + auth',
24
+ keywords: ['database', 'auth', 'postgres', 'sql', 'realtime', 'supabase', 'authentication'],
25
+ install: 'npx -y @supabase/mcp-server-supabase',
26
+ useCases: ['data storage', 'authentication'],
27
+ },
28
+ {
29
+ name: 'stripe',
30
+ description: 'Stripe payments',
31
+ keywords: ['payment', 'billing', 'subscription', 'checkout', 'invoice', 'stripe', 'charge'],
32
+ install: 'npx -y @stripe/agent-toolkit',
33
+ useCases: ['payments', 'billing'],
34
+ },
35
+ {
36
+ name: 'slack',
37
+ description: 'Slack messaging',
38
+ keywords: ['slack', 'message', 'notification', 'channel', 'team', 'chat', 'alert'],
39
+ install: 'npx -y @modelcontextprotocol/server-slack',
40
+ useCases: ['notifications', 'team communication'],
41
+ },
42
+ {
43
+ name: 'postgres',
44
+ description: 'PostgreSQL database',
45
+ keywords: ['postgres', 'sql', 'database', 'query', 'postgresql', 'psql'],
46
+ install: 'npx -y @modelcontextprotocol/server-postgres',
47
+ useCases: ['data storage', 'queries'],
48
+ },
49
+ {
50
+ name: 'filesystem',
51
+ description: 'Local file system access',
52
+ keywords: ['file', 'directory', 'read', 'write', 'local', 'filesystem', 'folder', 'path'],
53
+ install: 'npx -y @modelcontextprotocol/server-filesystem',
54
+ useCases: ['file management'],
55
+ },
56
+ {
57
+ name: 'fetch',
58
+ description: 'HTTP fetch and web scraping',
59
+ keywords: ['http', 'api', 'fetch', 'web', 'scrape', 'url', 'request', 'rest'],
60
+ install: 'npx -y @modelcontextprotocol/server-fetch',
61
+ useCases: ['API calls', 'web scraping'],
62
+ },
63
+ {
64
+ name: 'puppeteer',
65
+ description: 'Browser automation',
66
+ keywords: [
67
+ 'browser',
68
+ 'e2e',
69
+ 'test',
70
+ 'screenshot',
71
+ 'automation',
72
+ 'selenium',
73
+ 'playwright',
74
+ 'headless',
75
+ ],
76
+ install: 'npx -y @modelcontextprotocol/server-puppeteer',
77
+ useCases: ['E2E testing', 'scraping'],
78
+ },
79
+ {
80
+ name: 'brave-search',
81
+ description: 'Web search via Brave',
82
+ keywords: ['search', 'web', 'query', 'research', 'internet', 'find', 'brave'],
83
+ install: 'npx -y @modelcontextprotocol/server-brave-search',
84
+ useCases: ['research', 'web search'],
85
+ },
86
+ {
87
+ name: 'cloudinary',
88
+ description: 'Image/video storage and CDN',
89
+ keywords: ['image', 'video', 'upload', 'media', 'cdn', 'storage', 'photo', 'cloudinary'],
90
+ install: 'npx -y @cloudinary/mcp-server',
91
+ useCases: ['media storage', 'image processing'],
92
+ },
93
+ {
94
+ name: 'linear',
95
+ description: 'Linear project management',
96
+ keywords: ['linear', 'issue', 'project', 'sprint', 'task', 'roadmap', 'backlog'],
97
+ install: 'npx -y @linear/mcp-server',
98
+ useCases: ['project management'],
99
+ },
100
+ {
101
+ name: 'notion',
102
+ description: 'Notion workspace',
103
+ keywords: ['notion', 'document', 'wiki', 'page', 'knowledge', 'notes', 'workspace'],
104
+ install: 'npx -y @modelcontextprotocol/server-notion',
105
+ useCases: ['documentation', 'knowledge base'],
106
+ },
107
+ {
108
+ name: 'redis',
109
+ description: 'Redis cache',
110
+ keywords: ['redis', 'cache', 'session', 'queue', 'pubsub', 'caching', 'key-value'],
111
+ install: 'npx -y @modelcontextprotocol/server-redis',
112
+ useCases: ['caching', 'sessions'],
113
+ },
114
+ {
115
+ name: 'aws-s3',
116
+ description: 'AWS S3 storage',
117
+ keywords: ['s3', 'aws', 'storage', 'bucket', 'object', 'upload', 'amazon', 'cloud'],
118
+ install: 'npx -y @aws-mcp/s3',
119
+ useCases: ['file storage', 'CDN'],
120
+ },
121
+ {
122
+ name: 'sendgrid',
123
+ description: 'Email via SendGrid',
124
+ keywords: ['email', 'sendgrid', 'smtp', 'mail', 'newsletter', 'transactional', 'send'],
125
+ install: 'npx -y @sendgrid/mcp',
126
+ useCases: ['email', 'transactional mail'],
127
+ },
128
+ ];
129
+ // ---------------------------------------------------------------------------
130
+ // Keyword matching helpers
131
+ // ---------------------------------------------------------------------------
132
+ function tokenize(text) {
133
+ return text
134
+ .toLowerCase()
135
+ .replace(/[^a-z0-9\s]/g, ' ')
136
+ .split(/\s+/)
137
+ .filter((w) => w.length > 1);
138
+ }
139
+ function matchScore(queryTokens, entry) {
140
+ let score = 0;
141
+ for (const token of queryTokens) {
142
+ if (entry.keywords.includes(token)) {
143
+ score++;
144
+ }
145
+ }
146
+ return score;
147
+ }
148
+ function buildRationale(queryTokens, entry) {
149
+ const matched = queryTokens.filter((t) => entry.keywords.includes(t));
150
+ if (matched.length === 0) {
151
+ return `${entry.description} matches your use case.`;
152
+ }
153
+ return `Matches keywords: ${matched.join(', ')}. ${entry.description}.`;
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // Public function
157
+ // ---------------------------------------------------------------------------
158
+ export function suggestMcpServers(description, stack) {
159
+ const combined = stack ? `${description} ${stack}` : description;
160
+ const queryTokens = tokenize(combined);
161
+ const scored = MCP_CATALOG.map((entry) => ({
162
+ entry,
163
+ score: matchScore(queryTokens, entry),
164
+ }))
165
+ .filter(({ score }) => score > 0)
166
+ .sort((a, b) => b.score - a.score)
167
+ .slice(0, 5);
168
+ const suggestions = scored.map(({ entry }) => ({
169
+ name: entry.name,
170
+ description: entry.description,
171
+ installCommand: entry.install,
172
+ rationale: buildRationale(queryTokens, entry),
173
+ useCases: entry.useCases,
174
+ }));
175
+ return {
176
+ suggestions,
177
+ query: description,
178
+ };
179
+ }
180
+ //# sourceMappingURL=catalog-advisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-advisor.js","sourceRoot":"","sources":["../../../src/engine/mcp-catalog/catalog-advisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SimilarProblemsResult } from '../../types/index.js';
2
+ export declare function findSimilarProblems(query: string, projectPath: string, specId?: string): Promise<SimilarProblemsResult>;
3
+ //# sourceMappingURL=similarity-finder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarity-finder.d.ts","sourceRoot":"","sources":["../../../src/engine/similar-problems/similarity-finder.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAEV,qBAAqB,EAEtB,MAAM,sBAAsB,CAAC;AAyG9B,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,CAAC,CAiDhC"}