@planu/cli 1.11.0 → 1.13.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 (298) hide show
  1. package/dist/config/ai-tool-registry.json +71 -0
  2. package/dist/config/autopilot-config.json +21 -0
  3. package/dist/config/competitive-catalog.json +83 -0
  4. package/dist/config/license-plans.json +43 -2
  5. package/dist/engine/agent-registry/lifecycle-manager.d.ts +8 -0
  6. package/dist/engine/agent-registry/lifecycle-manager.d.ts.map +1 -0
  7. package/dist/engine/agent-registry/lifecycle-manager.js +81 -0
  8. package/dist/engine/agent-registry/lifecycle-manager.js.map +1 -0
  9. package/dist/engine/agent-registry/role-catalog.d.ts +17 -0
  10. package/dist/engine/agent-registry/role-catalog.d.ts.map +1 -0
  11. package/dist/engine/agent-registry/role-catalog.js +55 -0
  12. package/dist/engine/agent-registry/role-catalog.js.map +1 -0
  13. package/dist/engine/api-compat/compatibility-checker.d.ts +4 -0
  14. package/dist/engine/api-compat/compatibility-checker.d.ts.map +1 -0
  15. package/dist/engine/api-compat/compatibility-checker.js +118 -0
  16. package/dist/engine/api-compat/compatibility-checker.js.map +1 -0
  17. package/dist/engine/autopilot/action-executor.d.ts +18 -0
  18. package/dist/engine/autopilot/action-executor.d.ts.map +1 -0
  19. package/dist/engine/autopilot/action-executor.js +91 -0
  20. package/dist/engine/autopilot/action-executor.js.map +1 -0
  21. package/dist/engine/autopilot/event-bus.d.ts +8 -0
  22. package/dist/engine/autopilot/event-bus.d.ts.map +1 -0
  23. package/dist/engine/autopilot/event-bus.js +28 -0
  24. package/dist/engine/autopilot/event-bus.js.map +1 -0
  25. package/dist/engine/autopilot/trigger-rules.d.ts +3 -0
  26. package/dist/engine/autopilot/trigger-rules.d.ts.map +1 -0
  27. package/dist/engine/autopilot/trigger-rules.js +125 -0
  28. package/dist/engine/autopilot/trigger-rules.js.map +1 -0
  29. package/dist/engine/checkpoint/checkpoint-manager.d.ts +22 -0
  30. package/dist/engine/checkpoint/checkpoint-manager.d.ts.map +1 -0
  31. package/dist/engine/checkpoint/checkpoint-manager.js +76 -0
  32. package/dist/engine/checkpoint/checkpoint-manager.js.map +1 -0
  33. package/dist/engine/checkpoint/policy-engine.d.ts +10 -0
  34. package/dist/engine/checkpoint/policy-engine.d.ts.map +1 -0
  35. package/dist/engine/checkpoint/policy-engine.js +87 -0
  36. package/dist/engine/checkpoint/policy-engine.js.map +1 -0
  37. package/dist/engine/competitive/gap-analyzer.d.ts +12 -0
  38. package/dist/engine/competitive/gap-analyzer.d.ts.map +1 -0
  39. package/dist/engine/competitive/gap-analyzer.js +214 -0
  40. package/dist/engine/competitive/gap-analyzer.js.map +1 -0
  41. package/dist/engine/compliance/auto-remediator.d.ts +9 -0
  42. package/dist/engine/compliance/auto-remediator.d.ts.map +1 -0
  43. package/dist/engine/compliance/auto-remediator.js +118 -0
  44. package/dist/engine/compliance/auto-remediator.js.map +1 -0
  45. package/dist/engine/context-profile/profile-catalog.d.ts +5 -0
  46. package/dist/engine/context-profile/profile-catalog.d.ts.map +1 -0
  47. package/dist/engine/context-profile/profile-catalog.js +145 -0
  48. package/dist/engine/context-profile/profile-catalog.js.map +1 -0
  49. package/dist/engine/critical-path/path-analyzer.d.ts +3 -0
  50. package/dist/engine/critical-path/path-analyzer.d.ts.map +1 -0
  51. package/dist/engine/critical-path/path-analyzer.js +145 -0
  52. package/dist/engine/critical-path/path-analyzer.js.map +1 -0
  53. package/dist/engine/drift/violation-resolver.d.ts +9 -0
  54. package/dist/engine/drift/violation-resolver.d.ts.map +1 -0
  55. package/dist/engine/drift/violation-resolver.js +128 -0
  56. package/dist/engine/drift/violation-resolver.js.map +1 -0
  57. package/dist/engine/ears/criterion-scorer.d.ts +7 -0
  58. package/dist/engine/ears/criterion-scorer.d.ts.map +1 -0
  59. package/dist/engine/ears/criterion-scorer.js +87 -0
  60. package/dist/engine/ears/criterion-scorer.js.map +1 -0
  61. package/dist/engine/ears/pattern-matcher.d.ts +5 -0
  62. package/dist/engine/ears/pattern-matcher.d.ts.map +1 -0
  63. package/dist/engine/ears/pattern-matcher.js +48 -0
  64. package/dist/engine/ears/pattern-matcher.js.map +1 -0
  65. package/dist/engine/ears/rewriter.d.ts +7 -0
  66. package/dist/engine/ears/rewriter.d.ts.map +1 -0
  67. package/dist/engine/ears/rewriter.js +45 -0
  68. package/dist/engine/ears/rewriter.js.map +1 -0
  69. package/dist/engine/ears/spec-linter.d.ts +7 -0
  70. package/dist/engine/ears/spec-linter.d.ts.map +1 -0
  71. package/dist/engine/ears/spec-linter.js +127 -0
  72. package/dist/engine/ears/spec-linter.js.map +1 -0
  73. package/dist/engine/health/auto-fixer.d.ts +7 -0
  74. package/dist/engine/health/auto-fixer.d.ts.map +1 -0
  75. package/dist/engine/health/auto-fixer.js +130 -0
  76. package/dist/engine/health/auto-fixer.js.map +1 -0
  77. package/dist/engine/hook-generator/ai-hook-templates.d.ts +8 -0
  78. package/dist/engine/hook-generator/ai-hook-templates.d.ts.map +1 -0
  79. package/dist/engine/hook-generator/ai-hook-templates.js +43 -0
  80. package/dist/engine/hook-generator/ai-hook-templates.js.map +1 -0
  81. package/dist/engine/hook-generator/hook-merger.d.ts +13 -0
  82. package/dist/engine/hook-generator/hook-merger.d.ts.map +1 -0
  83. package/dist/engine/hook-generator/hook-merger.js +148 -0
  84. package/dist/engine/hook-generator/hook-merger.js.map +1 -0
  85. package/dist/engine/hook-generator/stack-hook-templates.d.ts +10 -0
  86. package/dist/engine/hook-generator/stack-hook-templates.d.ts.map +1 -0
  87. package/dist/engine/hook-generator/stack-hook-templates.js +105 -0
  88. package/dist/engine/hook-generator/stack-hook-templates.js.map +1 -0
  89. package/dist/engine/mcp-catalog/catalog-advisor.d.ts +3 -0
  90. package/dist/engine/mcp-catalog/catalog-advisor.d.ts.map +1 -0
  91. package/dist/engine/mcp-catalog/catalog-advisor.js +180 -0
  92. package/dist/engine/mcp-catalog/catalog-advisor.js.map +1 -0
  93. package/dist/engine/project-dna/ai-tool-detector.d.ts +12 -0
  94. package/dist/engine/project-dna/ai-tool-detector.d.ts.map +1 -0
  95. package/dist/engine/project-dna/ai-tool-detector.js +103 -0
  96. package/dist/engine/project-dna/ai-tool-detector.js.map +1 -0
  97. package/dist/engine/project-dna/rules-generator.d.ts +18 -0
  98. package/dist/engine/project-dna/rules-generator.d.ts.map +1 -0
  99. package/dist/engine/project-dna/rules-generator.js +193 -0
  100. package/dist/engine/project-dna/rules-generator.js.map +1 -0
  101. package/dist/engine/project-dna/stack-detector.d.ts +24 -0
  102. package/dist/engine/project-dna/stack-detector.d.ts.map +1 -0
  103. package/dist/engine/project-dna/stack-detector.js +309 -0
  104. package/dist/engine/project-dna/stack-detector.js.map +1 -0
  105. package/dist/engine/similar-problems/similarity-finder.d.ts +3 -0
  106. package/dist/engine/similar-problems/similarity-finder.d.ts.map +1 -0
  107. package/dist/engine/similar-problems/similarity-finder.js +144 -0
  108. package/dist/engine/similar-problems/similarity-finder.js.map +1 -0
  109. package/dist/engine/sync/asana-puller.d.ts +9 -0
  110. package/dist/engine/sync/asana-puller.d.ts.map +1 -0
  111. package/dist/engine/sync/asana-puller.js +91 -0
  112. package/dist/engine/sync/asana-puller.js.map +1 -0
  113. package/dist/engine/sync/conflict-resolver.d.ts +17 -0
  114. package/dist/engine/sync/conflict-resolver.d.ts.map +1 -0
  115. package/dist/engine/sync/conflict-resolver.js +58 -0
  116. package/dist/engine/sync/conflict-resolver.js.map +1 -0
  117. package/dist/engine/sync/monday-puller.d.ts +9 -0
  118. package/dist/engine/sync/monday-puller.d.ts.map +1 -0
  119. package/dist/engine/sync/monday-puller.js +110 -0
  120. package/dist/engine/sync/monday-puller.js.map +1 -0
  121. package/dist/engine/sync/notion-puller.d.ts +15 -0
  122. package/dist/engine/sync/notion-puller.d.ts.map +1 -0
  123. package/dist/engine/sync/notion-puller.js +101 -0
  124. package/dist/engine/sync/notion-puller.js.map +1 -0
  125. package/dist/engine/verifier/code-scanner.d.ts +8 -0
  126. package/dist/engine/verifier/code-scanner.d.ts.map +1 -0
  127. package/dist/engine/verifier/code-scanner.js +73 -0
  128. package/dist/engine/verifier/code-scanner.js.map +1 -0
  129. package/dist/engine/verifier/compliance-scorer.d.ts +17 -0
  130. package/dist/engine/verifier/compliance-scorer.d.ts.map +1 -0
  131. package/dist/engine/verifier/compliance-scorer.js +131 -0
  132. package/dist/engine/verifier/compliance-scorer.js.map +1 -0
  133. package/dist/engine/verifier/criterion-matcher.d.ts +15 -0
  134. package/dist/engine/verifier/criterion-matcher.d.ts.map +1 -0
  135. package/dist/engine/verifier/criterion-matcher.js +210 -0
  136. package/dist/engine/verifier/criterion-matcher.js.map +1 -0
  137. package/dist/index.js +24 -0
  138. package/dist/index.js.map +1 -1
  139. package/dist/storage/agent-registry-store.d.ts +11 -0
  140. package/dist/storage/agent-registry-store.d.ts.map +1 -0
  141. package/dist/storage/agent-registry-store.js +45 -0
  142. package/dist/storage/agent-registry-store.js.map +1 -0
  143. package/dist/storage/compliance-score-store.d.ts +16 -0
  144. package/dist/storage/compliance-score-store.d.ts.map +1 -0
  145. package/dist/storage/compliance-score-store.js +30 -0
  146. package/dist/storage/compliance-score-store.js.map +1 -0
  147. package/dist/storage/context-profile-store.d.ts +14 -0
  148. package/dist/storage/context-profile-store.d.ts.map +1 -0
  149. package/dist/storage/context-profile-store.js +34 -0
  150. package/dist/storage/context-profile-store.js.map +1 -0
  151. package/dist/storage/workflow-checkpoint-store.d.ts +16 -0
  152. package/dist/storage/workflow-checkpoint-store.d.ts.map +1 -0
  153. package/dist/storage/workflow-checkpoint-store.js +71 -0
  154. package/dist/storage/workflow-checkpoint-store.js.map +1 -0
  155. package/dist/tools/checkpoint/approve-checkpoint-handler.d.ts +3 -0
  156. package/dist/tools/checkpoint/approve-checkpoint-handler.d.ts.map +1 -0
  157. package/dist/tools/checkpoint/approve-checkpoint-handler.js +32 -0
  158. package/dist/tools/checkpoint/approve-checkpoint-handler.js.map +1 -0
  159. package/dist/tools/checkpoint/configure-policy-handler.d.ts +3 -0
  160. package/dist/tools/checkpoint/configure-policy-handler.d.ts.map +1 -0
  161. package/dist/tools/checkpoint/configure-policy-handler.js +60 -0
  162. package/dist/tools/checkpoint/configure-policy-handler.js.map +1 -0
  163. package/dist/tools/checkpoint/list-checkpoints-handler.d.ts +3 -0
  164. package/dist/tools/checkpoint/list-checkpoints-handler.d.ts.map +1 -0
  165. package/dist/tools/checkpoint/list-checkpoints-handler.js +25 -0
  166. package/dist/tools/checkpoint/list-checkpoints-handler.js.map +1 -0
  167. package/dist/tools/checkpoint/reject-checkpoint-handler.d.ts +3 -0
  168. package/dist/tools/checkpoint/reject-checkpoint-handler.d.ts.map +1 -0
  169. package/dist/tools/checkpoint/reject-checkpoint-handler.js +32 -0
  170. package/dist/tools/checkpoint/reject-checkpoint-handler.js.map +1 -0
  171. package/dist/tools/checkpoint/require-checkpoint-handler.d.ts +3 -0
  172. package/dist/tools/checkpoint/require-checkpoint-handler.d.ts.map +1 -0
  173. package/dist/tools/checkpoint/require-checkpoint-handler.js +44 -0
  174. package/dist/tools/checkpoint/require-checkpoint-handler.js.map +1 -0
  175. package/dist/tools/competitive-handlers.d.ts +30 -0
  176. package/dist/tools/competitive-handlers.d.ts.map +1 -0
  177. package/dist/tools/competitive-handlers.js +155 -0
  178. package/dist/tools/competitive-handlers.js.map +1 -0
  179. package/dist/tools/create-spec/post-creation.d.ts +1 -1
  180. package/dist/tools/create-spec/post-creation.d.ts.map +1 -1
  181. package/dist/tools/create-spec/post-creation.js +13 -1
  182. package/dist/tools/create-spec/post-creation.js.map +1 -1
  183. package/dist/tools/create-spec.js +1 -1
  184. package/dist/tools/create-spec.js.map +1 -1
  185. package/dist/tools/hook-generator-handler.d.ts +8 -0
  186. package/dist/tools/hook-generator-handler.d.ts.map +1 -0
  187. package/dist/tools/hook-generator-handler.js +154 -0
  188. package/dist/tools/hook-generator-handler.js.map +1 -0
  189. package/dist/tools/project-dna-handler.d.ts +34 -0
  190. package/dist/tools/project-dna-handler.d.ts.map +1 -0
  191. package/dist/tools/project-dna-handler.js +261 -0
  192. package/dist/tools/project-dna-handler.js.map +1 -0
  193. package/dist/tools/pull-sync-handler.d.ts +25 -0
  194. package/dist/tools/pull-sync-handler.d.ts.map +1 -0
  195. package/dist/tools/pull-sync-handler.js +161 -0
  196. package/dist/tools/pull-sync-handler.js.map +1 -0
  197. package/dist/tools/register-agent-registry.d.ts +5 -0
  198. package/dist/tools/register-agent-registry.d.ts.map +1 -0
  199. package/dist/tools/register-agent-registry.js +254 -0
  200. package/dist/tools/register-agent-registry.js.map +1 -0
  201. package/dist/tools/register-auto-remediation.d.ts +3 -0
  202. package/dist/tools/register-auto-remediation.d.ts.map +1 -0
  203. package/dist/tools/register-auto-remediation.js +174 -0
  204. package/dist/tools/register-auto-remediation.js.map +1 -0
  205. package/dist/tools/register-autopilot.d.ts +3 -0
  206. package/dist/tools/register-autopilot.d.ts.map +1 -0
  207. package/dist/tools/register-autopilot.js +78 -0
  208. package/dist/tools/register-autopilot.js.map +1 -0
  209. package/dist/tools/register-checkpoints.d.ts +3 -0
  210. package/dist/tools/register-checkpoints.d.ts.map +1 -0
  211. package/dist/tools/register-checkpoints.js +134 -0
  212. package/dist/tools/register-checkpoints.js.map +1 -0
  213. package/dist/tools/register-competitive.d.ts +3 -0
  214. package/dist/tools/register-competitive.d.ts.map +1 -0
  215. package/dist/tools/register-competitive.js +88 -0
  216. package/dist/tools/register-competitive.js.map +1 -0
  217. package/dist/tools/register-context-profile.d.ts +3 -0
  218. package/dist/tools/register-context-profile.d.ts.map +1 -0
  219. package/dist/tools/register-context-profile.js +106 -0
  220. package/dist/tools/register-context-profile.js.map +1 -0
  221. package/dist/tools/register-ears.d.ts +3 -0
  222. package/dist/tools/register-ears.d.ts.map +1 -0
  223. package/dist/tools/register-ears.js +148 -0
  224. package/dist/tools/register-ears.js.map +1 -0
  225. package/dist/tools/register-enterprise-compliance.js +1 -1
  226. package/dist/tools/register-enterprise-compliance.js.map +1 -1
  227. package/dist/tools/register-hook-generator.d.ts +3 -0
  228. package/dist/tools/register-hook-generator.d.ts.map +1 -0
  229. package/dist/tools/register-hook-generator.js +96 -0
  230. package/dist/tools/register-hook-generator.js.map +1 -0
  231. package/dist/tools/register-project-dna.d.ts +3 -0
  232. package/dist/tools/register-project-dna.d.ts.map +1 -0
  233. package/dist/tools/register-project-dna.js +43 -0
  234. package/dist/tools/register-project-dna.js.map +1 -0
  235. package/dist/tools/register-pull-sync.d.ts +3 -0
  236. package/dist/tools/register-pull-sync.d.ts.map +1 -0
  237. package/dist/tools/register-pull-sync.js +71 -0
  238. package/dist/tools/register-pull-sync.js.map +1 -0
  239. package/dist/tools/register-spec405-tools.d.ts +7 -0
  240. package/dist/tools/register-spec405-tools.d.ts.map +1 -0
  241. package/dist/tools/register-spec405-tools.js +194 -0
  242. package/dist/tools/register-spec405-tools.js.map +1 -0
  243. package/dist/tools/register-verifier.d.ts +3 -0
  244. package/dist/tools/register-verifier.d.ts.map +1 -0
  245. package/dist/tools/register-verifier.js +141 -0
  246. package/dist/tools/register-verifier.js.map +1 -0
  247. package/dist/tools/update-status/side-effects.d.ts.map +1 -1
  248. package/dist/tools/update-status/side-effects.js +32 -0
  249. package/dist/tools/update-status/side-effects.js.map +1 -1
  250. package/dist/types/agent-registry.d.ts +53 -0
  251. package/dist/types/agent-registry.d.ts.map +1 -0
  252. package/dist/types/agent-registry.js +2 -0
  253. package/dist/types/agent-registry.js.map +1 -0
  254. package/dist/types/analysis.d.ts +98 -0
  255. package/dist/types/analysis.d.ts.map +1 -1
  256. package/dist/types/autopilot.d.ts +36 -0
  257. package/dist/types/autopilot.d.ts.map +1 -0
  258. package/dist/types/autopilot.js +3 -0
  259. package/dist/types/autopilot.js.map +1 -0
  260. package/dist/types/competitive.d.ts +41 -0
  261. package/dist/types/competitive.d.ts.map +1 -0
  262. package/dist/types/competitive.js +3 -0
  263. package/dist/types/competitive.js.map +1 -0
  264. package/dist/types/context-profile.d.ts +22 -0
  265. package/dist/types/context-profile.d.ts.map +1 -0
  266. package/dist/types/context-profile.js +2 -0
  267. package/dist/types/context-profile.js.map +1 -0
  268. package/dist/types/ears.d.ts +34 -0
  269. package/dist/types/ears.d.ts.map +1 -0
  270. package/dist/types/ears.js +3 -0
  271. package/dist/types/ears.js.map +1 -0
  272. package/dist/types/health.d.ts +40 -0
  273. package/dist/types/health.d.ts.map +1 -0
  274. package/dist/types/health.js +3 -0
  275. package/dist/types/health.js.map +1 -0
  276. package/dist/types/hook-generator.d.ts +49 -0
  277. package/dist/types/hook-generator.d.ts.map +1 -0
  278. package/dist/types/hook-generator.js +3 -0
  279. package/dist/types/hook-generator.js.map +1 -0
  280. package/dist/types/index.d.ts +9 -0
  281. package/dist/types/index.d.ts.map +1 -1
  282. package/dist/types/index.js +9 -0
  283. package/dist/types/index.js.map +1 -1
  284. package/dist/types/notion-asana-monday.d.ts +38 -0
  285. package/dist/types/notion-asana-monday.d.ts.map +1 -1
  286. package/dist/types/project-dna.d.ts +46 -0
  287. package/dist/types/project-dna.d.ts.map +1 -0
  288. package/dist/types/project-dna.js +4 -0
  289. package/dist/types/project-dna.js.map +1 -0
  290. package/dist/types/workflow-checkpoint.d.ts +66 -0
  291. package/dist/types/workflow-checkpoint.d.ts.map +1 -0
  292. package/dist/types/workflow-checkpoint.js +4 -0
  293. package/dist/types/workflow-checkpoint.js.map +1 -0
  294. package/package.json +1 -1
  295. package/src/config/ai-tool-registry.json +71 -0
  296. package/src/config/autopilot-config.json +21 -0
  297. package/src/config/competitive-catalog.json +83 -0
  298. package/src/config/license-plans.json +43 -2
@@ -0,0 +1,145 @@
1
+ // engine/critical-path/path-analyzer.ts — SPEC-405
2
+ // Build a dependency graph from spec frontmatter and compute the critical path.
3
+ import { readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { glob } from 'glob';
6
+ import { parseFrontmatter } from '../frontmatter-parser.js';
7
+ // ---------------------------------------------------------------------------
8
+ // File reading helpers
9
+ // ---------------------------------------------------------------------------
10
+ async function readAllSpecMeta(projectPath) {
11
+ const pattern = join(projectPath, 'planu', 'specs', '*', 'spec.md');
12
+ const files = await glob(pattern, { nodir: true });
13
+ const results = [];
14
+ await Promise.all(files.map(async (filePath) => {
15
+ try {
16
+ const content = await readFile(filePath, 'utf-8');
17
+ const { metadata } = parseFrontmatter(content);
18
+ const id = typeof metadata.id === 'string' ? metadata.id : '';
19
+ if (!id) {
20
+ return;
21
+ }
22
+ const estimatedHours = typeof metadata.estimatedHours === 'number' ? metadata.estimatedHours : 0;
23
+ const rawDeps = metadata.dependencies;
24
+ const dependencies = Array.isArray(rawDeps)
25
+ ? rawDeps.filter((d) => typeof d === 'string')
26
+ : [];
27
+ const status = typeof metadata.status === 'string' ? metadata.status : 'pending';
28
+ results.push({ id, estimatedHours, dependencies, status });
29
+ }
30
+ catch {
31
+ // skip unreadable files
32
+ }
33
+ }));
34
+ return results;
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // DAG critical path (longest path in hours)
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Compute the longest-duration path through the spec dependency DAG.
41
+ * Uses memoized DFS. Returns the spec IDs in topological order.
42
+ */
43
+ function longestPath(specId, adjacency, hours, memo) {
44
+ const cached = memo.get(specId);
45
+ if (cached) {
46
+ return cached;
47
+ }
48
+ const children = adjacency.get(specId) ?? [];
49
+ let best = {
50
+ length: hours.get(specId) ?? 0,
51
+ path: [specId],
52
+ };
53
+ for (const child of children) {
54
+ const sub = longestPath(child, adjacency, hours, memo);
55
+ const total = (hours.get(specId) ?? 0) + sub.length;
56
+ if (total > best.length) {
57
+ best = { length: total, path: [specId, ...sub.path] };
58
+ }
59
+ }
60
+ memo.set(specId, best);
61
+ return best;
62
+ }
63
+ /**
64
+ * Count how many specs depend on each spec (in-degree of reverse graph).
65
+ */
66
+ function countDependents(specs) {
67
+ const count = new Map();
68
+ for (const spec of specs) {
69
+ if (!count.has(spec.id)) {
70
+ count.set(spec.id, 0);
71
+ }
72
+ for (const dep of spec.dependencies) {
73
+ count.set(dep, (count.get(dep) ?? 0) + 1);
74
+ }
75
+ }
76
+ return count;
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // Public function
80
+ // ---------------------------------------------------------------------------
81
+ export async function analyzeCriticalPath(projectPath) {
82
+ const specs = await readAllSpecMeta(projectPath);
83
+ if (specs.length === 0) {
84
+ return { criticalPath: [], totalHours: 0, parallelizableSpecs: [], bottlenecks: [] };
85
+ }
86
+ // Build adjacency list (spec → its dependents, i.e. specs that depend on it)
87
+ const adjacency = new Map();
88
+ const hours = new Map();
89
+ const allIds = new Set(specs.map((s) => s.id));
90
+ for (const spec of specs) {
91
+ hours.set(spec.id, spec.estimatedHours);
92
+ if (!adjacency.has(spec.id)) {
93
+ adjacency.set(spec.id, []);
94
+ }
95
+ for (const dep of spec.dependencies) {
96
+ if (allIds.has(dep)) {
97
+ const list = adjacency.get(dep) ?? [];
98
+ list.push(spec.id);
99
+ adjacency.set(dep, list);
100
+ }
101
+ }
102
+ }
103
+ // Find root nodes (no dependencies pointing to them from known specs)
104
+ const hasIncoming = new Set();
105
+ for (const spec of specs) {
106
+ for (const dep of spec.dependencies) {
107
+ if (allIds.has(dep)) {
108
+ hasIncoming.add(spec.id);
109
+ }
110
+ }
111
+ }
112
+ const roots = specs.filter((s) => !hasIncoming.has(s.id));
113
+ // Compute longest path from each root
114
+ const memo = new Map();
115
+ let bestPath = [];
116
+ let bestLength = 0;
117
+ for (const root of roots) {
118
+ const result = longestPath(root.id, adjacency, hours, memo);
119
+ if (result.length > bestLength) {
120
+ bestLength = result.length;
121
+ bestPath = result.path;
122
+ }
123
+ }
124
+ const totalHours = specs.reduce((acc, s) => acc + s.estimatedHours, 0);
125
+ // Parallelizable: specs NOT on the critical path
126
+ const criticalSet = new Set(bestPath);
127
+ const parallelizableSpecs = specs
128
+ .filter((s) => !criticalSet.has(s.id))
129
+ .map((s) => s.id)
130
+ .slice(0, 20);
131
+ // Bottlenecks: top 3 specs with most dependents
132
+ const dependentCount = countDependents(specs);
133
+ const bottlenecks = [...dependentCount.entries()]
134
+ .filter(([, count]) => count > 0)
135
+ .sort((a, b) => b[1] - a[1])
136
+ .slice(0, 3)
137
+ .map(([id]) => id);
138
+ return {
139
+ criticalPath: bestPath,
140
+ totalHours,
141
+ parallelizableSpecs,
142
+ bottlenecks,
143
+ };
144
+ }
145
+ //# sourceMappingURL=path-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-analyzer.js","sourceRoot":"","sources":["../../../src/engine/critical-path/path-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { DriftResolutionResult } from '../../types/health.js';
2
+ /**
3
+ * Resolves drift violations semi-automatically by inspecting cached drift
4
+ * entries and taking action based on the spec's current status.
5
+ *
6
+ * When dryRun=true, returns the planned resolutions without writing any files.
7
+ */
8
+ export declare function resolveDriftViolations(projectPath: string, projectId: string, dryRun?: boolean): Promise<DriftResolutionResult>;
9
+ //# sourceMappingURL=violation-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"violation-resolver.d.ts","sourceRoot":"","sources":["../../../src/engine/drift/violation-resolver.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAiB,MAAM,uBAAuB,CAAC;AAwFlF;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,UAAQ,GACb,OAAO,CAAC,qBAAqB,CAAC,CA+DhC"}
@@ -0,0 +1,128 @@
1
+ // engine/drift/violation-resolver.ts — Semi-automatic drift violation resolution (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 { DriftCacheStore } from '../../storage/drift-cache-store.js';
6
+ // ---------------------------------------------------------------------------
7
+ // Helpers
8
+ // ---------------------------------------------------------------------------
9
+ async function specDirExists(specDir) {
10
+ try {
11
+ await access(specDir);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ async function createFollowUpSpec(projectPath, parentSpecId, reason, dryRun) {
19
+ const timestamp = Date.now();
20
+ const specId = `SPEC-followup-${parentSpecId.toLowerCase()}-${timestamp}`;
21
+ if (!dryRun) {
22
+ const specDir = join(projectPath, 'planu', 'specs', specId);
23
+ await mkdir(specDir, { recursive: true });
24
+ const now = new Date().toISOString();
25
+ const specContent = [
26
+ '---',
27
+ `id: ${specId}`,
28
+ `title: "Follow-up: ${parentSpecId} drift resolution"`,
29
+ `status: draft`,
30
+ `tags: [drift, follow-up, auto-generated]`,
31
+ `createdAt: ${now}`,
32
+ '---',
33
+ '',
34
+ `# Follow-up for ${parentSpecId}`,
35
+ '',
36
+ reason,
37
+ '',
38
+ '## Acceptance Criteria',
39
+ '',
40
+ `- [ ] Review all criteria from ${parentSpecId} against current implementation`,
41
+ '- [ ] Update spec or implementation to re-align',
42
+ '- [ ] Run detect_drift to confirm score returns to 0',
43
+ '',
44
+ ].join('\n');
45
+ await writeFile(join(specDir, 'spec.md'), specContent, 'utf-8');
46
+ await writeFile(join(specDir, 'progress.md'), `# ${specId} — Progress\n\nstatus: draft\n`, 'utf-8');
47
+ }
48
+ return specId;
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Violation handlers per status
52
+ // ---------------------------------------------------------------------------
53
+ function buildDoneButDivergedResolution(specId) {
54
+ return {
55
+ specId,
56
+ action: 'follow-up-spec-queued',
57
+ description: 'Spec is done but code has diverged — follow-up spec created to track delta',
58
+ };
59
+ }
60
+ function buildInProgressAheadResolution(specId) {
61
+ return {
62
+ specId,
63
+ action: 'spec-note-added',
64
+ description: 'Spec is in-progress and code is ahead — criteria note added to flag untracked work',
65
+ };
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // Public API
69
+ // ---------------------------------------------------------------------------
70
+ /**
71
+ * Resolves drift violations semi-automatically by inspecting cached drift
72
+ * entries and taking action based on the spec's current status.
73
+ *
74
+ * When dryRun=true, returns the planned resolutions without writing any files.
75
+ */
76
+ export async function resolveDriftViolations(projectPath, projectId, dryRun = false) {
77
+ const cacheStore = new DriftCacheStore(projectId);
78
+ const cacheData = await cacheStore.load();
79
+ const cachedEntries = Object.values(cacheData.entries);
80
+ const specs = await specStore.listSpecs(projectId);
81
+ const specMap = new Map(specs.map((s) => [s.id, s]));
82
+ const resolved = [];
83
+ const followUpSpecsCreated = [];
84
+ const manualReview = [];
85
+ for (const entry of cachedEntries) {
86
+ const spec = specMap.get(entry.specId);
87
+ if (!spec) {
88
+ manualReview.push(`${entry.specId}: spec no longer exists — remove orphaned drift cache`);
89
+ continue;
90
+ }
91
+ if (spec.status === 'done') {
92
+ // Code diverged from a completed spec — create follow-up
93
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
94
+ const reason = `Drift detected (score: ${entry.driftScore.toFixed(2)}) in files: ${(entry.driftedFiles ?? []).join(', ') || 'unknown'}`;
95
+ const followUpId = await createFollowUpSpec(projectPath, spec.id, reason, dryRun);
96
+ followUpSpecsCreated.push(followUpId);
97
+ resolved.push(buildDoneButDivergedResolution(spec.id));
98
+ }
99
+ else if (spec.status === 'implementing') {
100
+ // In-progress spec with code ahead — annotate but don't block
101
+ resolved.push(buildInProgressAheadResolution(spec.id));
102
+ }
103
+ else if (spec.status === 'draft') {
104
+ // Draft spec but code already exists — flag for human review
105
+ manualReview.push(`${spec.id}: spec is in draft status but drift detected — review if implementation started`);
106
+ }
107
+ else {
108
+ manualReview.push(`${spec.id}: drift detected (status: ${spec.status}) — manual review needed`);
109
+ }
110
+ }
111
+ if (cachedEntries.length === 0) {
112
+ // Check all implementing/done specs without cached drift entries
113
+ const activeSpecs = specs.filter((s) => s.status === 'implementing' || s.status === 'done' || s.status === 'draft');
114
+ for (const spec of activeSpecs) {
115
+ const specDir = join(projectPath, 'planu', 'specs', spec.id);
116
+ const hasProgress = await specDirExists(join(specDir, 'progress.md'));
117
+ if (!hasProgress && spec.status === 'draft') {
118
+ manualReview.push(`${spec.id}: draft spec with no progress tracking — run auto_fix_health`);
119
+ }
120
+ }
121
+ }
122
+ return {
123
+ resolved,
124
+ followUpSpecsCreated,
125
+ manualReview,
126
+ };
127
+ }
128
+ //# sourceMappingURL=violation-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"violation-resolver.js","sourceRoot":"","sources":["../../../src/engine/drift/violation-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { CriterionQuality } from '../../types/ears.js';
2
+ /**
3
+ * Score a single acceptance criterion using EARS and testability heuristics.
4
+ */
5
+ export declare function scoreCriterion(text: string): CriterionQuality;
6
+ export declare function gradeSpec(avgScore: number): 'A' | 'B' | 'C' | 'D' | 'F';
7
+ //# sourceMappingURL=criterion-scorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"criterion-scorer.d.ts","sourceRoot":"","sources":["../../../src/engine/ears/criterion-scorer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AA8B5D;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAyD7D;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAcvE"}
@@ -0,0 +1,87 @@
1
+ // engine/ears/criterion-scorer.ts — EARS criterion scoring (SPEC-410)
2
+ import { detectEarsPattern, detectVaguenessFlags, isEarsCompliant } from './pattern-matcher.js';
3
+ import { generateRewrites } from './rewriter.js';
4
+ // Patterns that indicate a measurable numeric threshold
5
+ const NUMERIC_VALUE_REGEX = /\d+\s*(?:ms|milliseconds?|seconds?|minutes?|hours?|items?|%|percent|kb|mb|gb|px|requests?|users?|calls?|retries?)/i;
6
+ // Patterns for specific API routes/endpoints
7
+ const ENDPOINT_REGEX = /(?:\/api\/|GET\s+|POST\s+|PUT\s+|DELETE\s+|PATCH\s+)/i;
8
+ // Patterns for clear success conditions
9
+ const SUCCESS_CONDITION_REGEX = /\b(?:returns?|displays?|shows?|navigates?\s+to|renders?|saves?|stores?|loads?|creates?|sends?|emits?)\b/i;
10
+ // Patterns for a measurable threshold (numeric value with unit or comparison)
11
+ const MEASURABLE_THRESHOLD_REGEX = /\b(?:at (?:most|least)|no more than|within|fewer than|greater than|more than|\d+\s*(?:ms|seconds?|items?|%|kb|mb))\b/i;
12
+ // Patterns for named components/features
13
+ const COMPONENT_REGEX = /\b(?:[A-Z][a-zA-Z]+(?:Component|Page|Modal|Service|Store|API|Endpoint|Handler|Manager|Controller|View|Form|Button|Panel|Screen)|\w+[-_]\w+)\b/;
14
+ // Patterns for trigger conditions
15
+ const TRIGGER_REGEX = /\b(?:when|if|while|where|after|before|on(?:ce)?)\b/i;
16
+ // Patterns for actors
17
+ const ACTOR_REGEX = /\b(?:user|admin|system|operator|manager|client|server|guest|subscriber|owner)\b/i;
18
+ /**
19
+ * Score a single acceptance criterion using EARS and testability heuristics.
20
+ */
21
+ export function scoreCriterion(text) {
22
+ const earsPattern = detectEarsPattern(text);
23
+ const earsCompliant = isEarsCompliant(text);
24
+ const vaguenessFlagsFound = detectVaguenessFlags(text);
25
+ const vaguenessDeduction = Math.min(vaguenessFlagsFound.length * 2, 10);
26
+ // --- Testability Score (0-10) ---
27
+ let testabilityScore = 0;
28
+ if (NUMERIC_VALUE_REGEX.test(text)) {
29
+ testabilityScore += 3;
30
+ }
31
+ if (earsCompliant) {
32
+ testabilityScore += 3;
33
+ }
34
+ if (ENDPOINT_REGEX.test(text)) {
35
+ testabilityScore += 2;
36
+ }
37
+ if (SUCCESS_CONDITION_REGEX.test(text)) {
38
+ testabilityScore += 2;
39
+ }
40
+ testabilityScore = Math.max(0, testabilityScore - vaguenessDeduction);
41
+ testabilityScore = Math.min(10, testabilityScore);
42
+ // --- Specificity Score (0-10) ---
43
+ let specificityScore = 0;
44
+ if (MEASURABLE_THRESHOLD_REGEX.test(text) || NUMERIC_VALUE_REGEX.test(text)) {
45
+ specificityScore += 4;
46
+ }
47
+ if (COMPONENT_REGEX.test(text)) {
48
+ specificityScore += 3;
49
+ }
50
+ if (TRIGGER_REGEX.test(text)) {
51
+ specificityScore += 2;
52
+ }
53
+ if (ACTOR_REGEX.test(text)) {
54
+ specificityScore += 1;
55
+ }
56
+ specificityScore = Math.max(0, specificityScore - vaguenessDeduction);
57
+ specificityScore = Math.min(10, specificityScore);
58
+ const overallScore = Math.round(((testabilityScore + specificityScore) / 2) * 10) / 10;
59
+ // Generate rewrite suggestions only when score is below threshold
60
+ const rewriteSuggestions = overallScore < 7 ? generateRewrites(text, earsPattern) : [];
61
+ return {
62
+ text,
63
+ testabilityScore,
64
+ specificityScore,
65
+ earsPattern,
66
+ earsCompliant,
67
+ vaguenessFlagsFound,
68
+ overallScore,
69
+ rewriteSuggestions,
70
+ };
71
+ }
72
+ export function gradeSpec(avgScore) {
73
+ if (avgScore >= 9) {
74
+ return 'A';
75
+ }
76
+ if (avgScore >= 7) {
77
+ return 'B';
78
+ }
79
+ if (avgScore >= 5) {
80
+ return 'C';
81
+ }
82
+ if (avgScore >= 3) {
83
+ return 'D';
84
+ }
85
+ return 'F';
86
+ }
87
+ //# sourceMappingURL=criterion-scorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"criterion-scorer.js","sourceRoot":"","sources":["../../../src/engine/ears/criterion-scorer.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAGtE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,wDAAwD;AACxD,MAAM,mBAAmB,GACvB,oHAAoH,CAAC;AAEvH,6CAA6C;AAC7C,MAAM,cAAc,GAAG,uDAAuD,CAAC;AAE/E,wCAAwC;AACxC,MAAM,uBAAuB,GAC3B,0GAA0G,CAAC;AAE7G,8EAA8E;AAC9E,MAAM,0BAA0B,GAC9B,uHAAuH,CAAC;AAE1H,yCAAyC;AACzC,MAAM,eAAe,GACnB,+IAA+I,CAAC;AAElJ,kCAAkC;AAClC,MAAM,aAAa,GAAG,qDAAqD,CAAC;AAE5E,sBAAsB;AACtB,MAAM,WAAW,GACf,kFAAkF,CAAC;AAErF;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,aAAa,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,mBAAmB,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAExE,mCAAmC;IACnC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,gBAAgB,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,aAAa,EAAE,CAAC;QAClB,gBAAgB,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,gBAAgB,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,gBAAgB,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,GAAG,kBAAkB,CAAC,CAAC;IACtE,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAElD,mCAAmC;IACnC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB,IAAI,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5E,gBAAgB,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,gBAAgB,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,gBAAgB,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,gBAAgB,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,GAAG,kBAAkB,CAAC,CAAC;IACtE,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAElD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;IAEvF,kEAAkE;IAClE,MAAM,kBAAkB,GAAG,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvF,OAAO;QACL,IAAI;QACJ,gBAAgB;QAChB,gBAAgB;QAChB,WAAW;QACX,aAAa;QACb,mBAAmB;QACnB,YAAY;QACZ,kBAAkB;KACnB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { EarsPattern } from '../../types/ears.js';
2
+ export declare function detectEarsPattern(criterion: string): EarsPattern;
3
+ export declare function detectVaguenessFlags(criterion: string): string[];
4
+ export declare function isEarsCompliant(criterion: string): boolean;
5
+ //# sourceMappingURL=pattern-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-matcher.d.ts","sourceRoot":"","sources":["../../../src/engine/ears/pattern-matcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA+BvD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAOhE;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAOhE;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE1D"}
@@ -0,0 +1,48 @@
1
+ // engine/ears/pattern-matcher.ts — EARS pattern detection (SPEC-410)
2
+ // EARS pattern regexes (case-insensitive).
3
+ // Order matters: more specific patterns are tested before generic ones.
4
+ const EARS_PATTERN_CHECKS = [
5
+ { pattern: 'conditional', regex: /\bif\b.+\bthen\b.+\bthe system shall\b/i },
6
+ { pattern: 'event-driven', regex: /\bwhen\b.+\bthe system shall\b/i },
7
+ { pattern: 'state-driven', regex: /\bwhile\b.+\bthe system shall\b/i },
8
+ { pattern: 'optional', regex: /\bwhere\b.+\bthe system shall\b/i },
9
+ { pattern: 'ubiquitous', regex: /\bthe system shall\b/i },
10
+ ];
11
+ // Vagueness words that make criteria hard to test
12
+ const VAGUENESS_FLAGS = [
13
+ 'should',
14
+ 'easy',
15
+ 'fast',
16
+ 'user-friendly',
17
+ 'intuitive',
18
+ 'simple',
19
+ 'quickly',
20
+ 'efficiently',
21
+ 'nice',
22
+ 'good',
23
+ 'better',
24
+ 'appropriate',
25
+ 'suitable',
26
+ 'seamlessly',
27
+ 'ideally',
28
+ ];
29
+ export function detectEarsPattern(criterion) {
30
+ for (const { pattern, regex } of EARS_PATTERN_CHECKS) {
31
+ if (regex.test(criterion)) {
32
+ return pattern;
33
+ }
34
+ }
35
+ return 'none';
36
+ }
37
+ export function detectVaguenessFlags(criterion) {
38
+ const lower = criterion.toLowerCase();
39
+ return VAGUENESS_FLAGS.filter((flag) => {
40
+ // Match whole word to avoid false positives (e.g., "easily" vs "easy")
41
+ const wordBoundary = new RegExp(`\\b${flag.replace('-', '[- ]')}\\b`, 'i');
42
+ return wordBoundary.test(lower);
43
+ });
44
+ }
45
+ export function isEarsCompliant(criterion) {
46
+ return detectEarsPattern(criterion) !== 'none';
47
+ }
48
+ //# sourceMappingURL=pattern-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-matcher.js","sourceRoot":"","sources":["../../../src/engine/ears/pattern-matcher.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAIrE,2CAA2C;AAC3C,wEAAwE;AACxE,MAAM,mBAAmB,GAA8C;IACrE,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,yCAAyC,EAAE;IAC5E,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,iCAAiC,EAAE;IACrE,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,kCAAkC,EAAE;IACtE,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,kCAAkC,EAAE;IAClE,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,uBAAuB,EAAE;CAC1D,CAAC;AAEF,kDAAkD;AAClD,MAAM,eAAe,GAAsB;IACzC,QAAQ;IACR,MAAM;IACN,MAAM;IACN,eAAe;IACf,WAAW;IACX,QAAQ;IACR,SAAS;IACT,aAAa;IACb,MAAM;IACN,MAAM;IACN,QAAQ;IACR,aAAa;IACb,UAAU;IACV,YAAY;IACZ,SAAS;CACV,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,mBAAmB,EAAE,CAAC;QACrD,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACrC,uEAAuE;QACvE,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3E,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,OAAO,iBAAiB,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC;AACjD,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { EarsPattern } from '../../types/ears.js';
2
+ /**
3
+ * Generate 2 EARS-format rewrite suggestions using templates.
4
+ * Template-driven only — no LLM calls.
5
+ */
6
+ export declare function generateRewrites(criterion: string, _pattern: EarsPattern): string[];
7
+ //# sourceMappingURL=rewriter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewriter.d.ts","sourceRoot":"","sources":["../../../src/engine/ears/rewriter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA2CvD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,GAAG,MAAM,EAAE,CAQnF"}
@@ -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"}