vibe-fabric 0.1.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 (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/dist/cli/commands/claude.d.ts +19 -0
  4. package/dist/cli/commands/claude.d.ts.map +1 -0
  5. package/dist/cli/commands/claude.js +107 -0
  6. package/dist/cli/commands/claude.js.map +1 -0
  7. package/dist/cli/commands/coverage.d.ts +37 -0
  8. package/dist/cli/commands/coverage.d.ts.map +1 -0
  9. package/dist/cli/commands/coverage.js +374 -0
  10. package/dist/cli/commands/coverage.js.map +1 -0
  11. package/dist/cli/commands/doctor.d.ts +30 -0
  12. package/dist/cli/commands/doctor.d.ts.map +1 -0
  13. package/dist/cli/commands/doctor.js +187 -0
  14. package/dist/cli/commands/doctor.js.map +1 -0
  15. package/dist/cli/commands/gaps.d.ts +52 -0
  16. package/dist/cli/commands/gaps.d.ts.map +1 -0
  17. package/dist/cli/commands/gaps.js +487 -0
  18. package/dist/cli/commands/gaps.js.map +1 -0
  19. package/dist/cli/commands/help.d.ts +7 -0
  20. package/dist/cli/commands/help.d.ts.map +1 -0
  21. package/dist/cli/commands/help.js +51 -0
  22. package/dist/cli/commands/help.js.map +1 -0
  23. package/dist/cli/commands/init.d.ts +39 -0
  24. package/dist/cli/commands/init.d.ts.map +1 -0
  25. package/dist/cli/commands/init.js +246 -0
  26. package/dist/cli/commands/init.js.map +1 -0
  27. package/dist/cli/commands/prd.d.ts +30 -0
  28. package/dist/cli/commands/prd.d.ts.map +1 -0
  29. package/dist/cli/commands/prd.js +179 -0
  30. package/dist/cli/commands/prd.js.map +1 -0
  31. package/dist/cli/commands/repo/add.d.ts +36 -0
  32. package/dist/cli/commands/repo/add.d.ts.map +1 -0
  33. package/dist/cli/commands/repo/add.js +303 -0
  34. package/dist/cli/commands/repo/add.js.map +1 -0
  35. package/dist/cli/commands/scope.d.ts +36 -0
  36. package/dist/cli/commands/scope.d.ts.map +1 -0
  37. package/dist/cli/commands/scope.js +312 -0
  38. package/dist/cli/commands/scope.js.map +1 -0
  39. package/dist/cli/commands/send.d.ts +43 -0
  40. package/dist/cli/commands/send.d.ts.map +1 -0
  41. package/dist/cli/commands/send.js +469 -0
  42. package/dist/cli/commands/send.js.map +1 -0
  43. package/dist/cli/commands/status.d.ts +32 -0
  44. package/dist/cli/commands/status.d.ts.map +1 -0
  45. package/dist/cli/commands/status.js +422 -0
  46. package/dist/cli/commands/status.js.map +1 -0
  47. package/dist/cli/commands/sync.d.ts +37 -0
  48. package/dist/cli/commands/sync.d.ts.map +1 -0
  49. package/dist/cli/commands/sync.js +299 -0
  50. package/dist/cli/commands/sync.js.map +1 -0
  51. package/dist/cli/commands/version.d.ts +7 -0
  52. package/dist/cli/commands/version.d.ts.map +1 -0
  53. package/dist/cli/commands/version.js +45 -0
  54. package/dist/cli/commands/version.js.map +1 -0
  55. package/dist/cli/index.d.ts +3 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +65 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/cli/ui/components/ActiveScopes.d.ts +13 -0
  60. package/dist/cli/ui/components/ActiveScopes.d.ts.map +1 -0
  61. package/dist/cli/ui/components/ActiveScopes.js +25 -0
  62. package/dist/cli/ui/components/ActiveScopes.js.map +1 -0
  63. package/dist/cli/ui/components/EmptyState.d.ts +24 -0
  64. package/dist/cli/ui/components/EmptyState.d.ts.map +1 -0
  65. package/dist/cli/ui/components/EmptyState.js +13 -0
  66. package/dist/cli/ui/components/EmptyState.js.map +1 -0
  67. package/dist/cli/ui/components/Header.d.ts +11 -0
  68. package/dist/cli/ui/components/Header.d.ts.map +1 -0
  69. package/dist/cli/ui/components/Header.js +32 -0
  70. package/dist/cli/ui/components/Header.js.map +1 -0
  71. package/dist/cli/ui/components/PrdCoverage.d.ts +12 -0
  72. package/dist/cli/ui/components/PrdCoverage.d.ts.map +1 -0
  73. package/dist/cli/ui/components/PrdCoverage.js +15 -0
  74. package/dist/cli/ui/components/PrdCoverage.js.map +1 -0
  75. package/dist/cli/ui/components/RecentActivity.d.ts +12 -0
  76. package/dist/cli/ui/components/RecentActivity.d.ts.map +1 -0
  77. package/dist/cli/ui/components/RecentActivity.js +40 -0
  78. package/dist/cli/ui/components/RecentActivity.js.map +1 -0
  79. package/dist/cli/ui/components/RepoList.d.ts +12 -0
  80. package/dist/cli/ui/components/RepoList.d.ts.map +1 -0
  81. package/dist/cli/ui/components/RepoList.js +24 -0
  82. package/dist/cli/ui/components/RepoList.js.map +1 -0
  83. package/dist/cli/ui/components/Shortcuts.d.ts +7 -0
  84. package/dist/cli/ui/components/Shortcuts.d.ts.map +1 -0
  85. package/dist/cli/ui/components/Shortcuts.js +7 -0
  86. package/dist/cli/ui/components/Shortcuts.js.map +1 -0
  87. package/dist/cli/ui/components/index.d.ts +12 -0
  88. package/dist/cli/ui/components/index.d.ts.map +1 -0
  89. package/dist/cli/ui/components/index.js +12 -0
  90. package/dist/cli/ui/components/index.js.map +1 -0
  91. package/dist/cli/ui/dashboard.d.ts +9 -0
  92. package/dist/cli/ui/dashboard.d.ts.map +1 -0
  93. package/dist/cli/ui/dashboard.js +102 -0
  94. package/dist/cli/ui/dashboard.js.map +1 -0
  95. package/dist/core/commands.d.ts +32 -0
  96. package/dist/core/commands.d.ts.map +1 -0
  97. package/dist/core/commands.js +361 -0
  98. package/dist/core/commands.js.map +1 -0
  99. package/dist/core/config.d.ts +18 -0
  100. package/dist/core/config.d.ts.map +1 -0
  101. package/dist/core/config.js +78 -0
  102. package/dist/core/config.js.map +1 -0
  103. package/dist/core/coverage.d.ts +32 -0
  104. package/dist/core/coverage.d.ts.map +1 -0
  105. package/dist/core/coverage.js +286 -0
  106. package/dist/core/coverage.js.map +1 -0
  107. package/dist/core/dashboard/data.d.ts +73 -0
  108. package/dist/core/dashboard/data.d.ts.map +1 -0
  109. package/dist/core/dashboard/data.js +250 -0
  110. package/dist/core/dashboard/data.js.map +1 -0
  111. package/dist/core/dependencies.d.ts +39 -0
  112. package/dist/core/dependencies.d.ts.map +1 -0
  113. package/dist/core/dependencies.js +160 -0
  114. package/dist/core/dependencies.js.map +1 -0
  115. package/dist/core/doctor/auth.d.ts +22 -0
  116. package/dist/core/doctor/auth.d.ts.map +1 -0
  117. package/dist/core/doctor/auth.js +147 -0
  118. package/dist/core/doctor/auth.js.map +1 -0
  119. package/dist/core/doctor/config.d.ts +26 -0
  120. package/dist/core/doctor/config.d.ts.map +1 -0
  121. package/dist/core/doctor/config.js +172 -0
  122. package/dist/core/doctor/config.js.map +1 -0
  123. package/dist/core/doctor/environment.d.ts +26 -0
  124. package/dist/core/doctor/environment.d.ts.map +1 -0
  125. package/dist/core/doctor/environment.js +145 -0
  126. package/dist/core/doctor/environment.js.map +1 -0
  127. package/dist/core/doctor/index.d.ts +44 -0
  128. package/dist/core/doctor/index.d.ts.map +1 -0
  129. package/dist/core/doctor/index.js +134 -0
  130. package/dist/core/doctor/index.js.map +1 -0
  131. package/dist/core/doctor/repos.d.ts +22 -0
  132. package/dist/core/doctor/repos.d.ts.map +1 -0
  133. package/dist/core/doctor/repos.js +262 -0
  134. package/dist/core/doctor/repos.js.map +1 -0
  135. package/dist/core/doctor/sync.d.ts +18 -0
  136. package/dist/core/doctor/sync.d.ts.map +1 -0
  137. package/dist/core/doctor/sync.js +146 -0
  138. package/dist/core/doctor/sync.js.map +1 -0
  139. package/dist/core/gaps.d.ts +70 -0
  140. package/dist/core/gaps.d.ts.map +1 -0
  141. package/dist/core/gaps.js +448 -0
  142. package/dist/core/gaps.js.map +1 -0
  143. package/dist/core/github.d.ts +38 -0
  144. package/dist/core/github.d.ts.map +1 -0
  145. package/dist/core/github.js +102 -0
  146. package/dist/core/github.js.map +1 -0
  147. package/dist/core/prd/analyzer.d.ts +44 -0
  148. package/dist/core/prd/analyzer.d.ts.map +1 -0
  149. package/dist/core/prd/analyzer.js +259 -0
  150. package/dist/core/prd/analyzer.js.map +1 -0
  151. package/dist/core/prd/check.d.ts +17 -0
  152. package/dist/core/prd/check.d.ts.map +1 -0
  153. package/dist/core/prd/check.js +154 -0
  154. package/dist/core/prd/check.js.map +1 -0
  155. package/dist/core/prd/index.d.ts +6 -0
  156. package/dist/core/prd/index.d.ts.map +1 -0
  157. package/dist/core/prd/index.js +6 -0
  158. package/dist/core/prd/index.js.map +1 -0
  159. package/dist/core/project.d.ts +13 -0
  160. package/dist/core/project.d.ts.map +1 -0
  161. package/dist/core/project.js +810 -0
  162. package/dist/core/project.js.map +1 -0
  163. package/dist/core/prompts.d.ts +52 -0
  164. package/dist/core/prompts.d.ts.map +1 -0
  165. package/dist/core/prompts.js +266 -0
  166. package/dist/core/prompts.js.map +1 -0
  167. package/dist/core/repo/framework.d.ts +38 -0
  168. package/dist/core/repo/framework.d.ts.map +1 -0
  169. package/dist/core/repo/framework.js +142 -0
  170. package/dist/core/repo/framework.js.map +1 -0
  171. package/dist/core/repo/index.d.ts +6 -0
  172. package/dist/core/repo/index.d.ts.map +1 -0
  173. package/dist/core/repo/index.js +6 -0
  174. package/dist/core/repo/index.js.map +1 -0
  175. package/dist/core/repo/templates/claude-agents.d.ts +6 -0
  176. package/dist/core/repo/templates/claude-agents.d.ts.map +1 -0
  177. package/dist/core/repo/templates/claude-agents.js +173 -0
  178. package/dist/core/repo/templates/claude-agents.js.map +1 -0
  179. package/dist/core/repo/templates/claude-commands.d.ts +6 -0
  180. package/dist/core/repo/templates/claude-commands.d.ts.map +1 -0
  181. package/dist/core/repo/templates/claude-commands.js +278 -0
  182. package/dist/core/repo/templates/claude-commands.js.map +1 -0
  183. package/dist/core/repo/templates/claude-prompts.d.ts +6 -0
  184. package/dist/core/repo/templates/claude-prompts.d.ts.map +1 -0
  185. package/dist/core/repo/templates/claude-prompts.js +258 -0
  186. package/dist/core/repo/templates/claude-prompts.js.map +1 -0
  187. package/dist/core/repo/templates/claude-scripts.d.ts +6 -0
  188. package/dist/core/repo/templates/claude-scripts.d.ts.map +1 -0
  189. package/dist/core/repo/templates/claude-scripts.js +212 -0
  190. package/dist/core/repo/templates/claude-scripts.js.map +1 -0
  191. package/dist/core/repo/templates/index.d.ts +22 -0
  192. package/dist/core/repo/templates/index.d.ts.map +1 -0
  193. package/dist/core/repo/templates/index.js +121 -0
  194. package/dist/core/repo/templates/index.js.map +1 -0
  195. package/dist/core/repo/templates/vibe-readme.d.ts +6 -0
  196. package/dist/core/repo/templates/vibe-readme.d.ts.map +1 -0
  197. package/dist/core/repo/templates/vibe-readme.js +204 -0
  198. package/dist/core/repo/templates/vibe-readme.js.map +1 -0
  199. package/dist/core/repo/templates/vibe-scripts.d.ts +6 -0
  200. package/dist/core/repo/templates/vibe-scripts.d.ts.map +1 -0
  201. package/dist/core/repo/templates/vibe-scripts.js +308 -0
  202. package/dist/core/repo/templates/vibe-scripts.js.map +1 -0
  203. package/dist/core/repo/validation.d.ts +46 -0
  204. package/dist/core/repo/validation.d.ts.map +1 -0
  205. package/dist/core/repo/validation.js +154 -0
  206. package/dist/core/repo/validation.js.map +1 -0
  207. package/dist/core/runner.d.ts +38 -0
  208. package/dist/core/runner.d.ts.map +1 -0
  209. package/dist/core/runner.js +124 -0
  210. package/dist/core/runner.js.map +1 -0
  211. package/dist/core/send.d.ts +83 -0
  212. package/dist/core/send.d.ts.map +1 -0
  213. package/dist/core/send.js +565 -0
  214. package/dist/core/send.js.map +1 -0
  215. package/dist/core/status.d.ts +76 -0
  216. package/dist/core/status.d.ts.map +1 -0
  217. package/dist/core/status.js +430 -0
  218. package/dist/core/status.js.map +1 -0
  219. package/dist/core/sync/aggregator.d.ts +22 -0
  220. package/dist/core/sync/aggregator.d.ts.map +1 -0
  221. package/dist/core/sync/aggregator.js +278 -0
  222. package/dist/core/sync/aggregator.js.map +1 -0
  223. package/dist/core/sync/completion.d.ts +37 -0
  224. package/dist/core/sync/completion.d.ts.map +1 -0
  225. package/dist/core/sync/completion.js +264 -0
  226. package/dist/core/sync/completion.js.map +1 -0
  227. package/dist/core/sync/index.d.ts +51 -0
  228. package/dist/core/sync/index.d.ts.map +1 -0
  229. package/dist/core/sync/index.js +200 -0
  230. package/dist/core/sync/index.js.map +1 -0
  231. package/dist/core/sync/scanner.d.ts +39 -0
  232. package/dist/core/sync/scanner.d.ts.map +1 -0
  233. package/dist/core/sync/scanner.js +364 -0
  234. package/dist/core/sync/scanner.js.map +1 -0
  235. package/dist/types/config.d.ts +157 -0
  236. package/dist/types/config.d.ts.map +1 -0
  237. package/dist/types/config.js +58 -0
  238. package/dist/types/config.js.map +1 -0
  239. package/dist/types/coverage.d.ts +100 -0
  240. package/dist/types/coverage.d.ts.map +1 -0
  241. package/dist/types/coverage.js +8 -0
  242. package/dist/types/coverage.js.map +1 -0
  243. package/dist/types/doctor.d.ts +68 -0
  244. package/dist/types/doctor.d.ts.map +1 -0
  245. package/dist/types/doctor.js +5 -0
  246. package/dist/types/doctor.js.map +1 -0
  247. package/dist/types/gaps.d.ts +129 -0
  248. package/dist/types/gaps.d.ts.map +1 -0
  249. package/dist/types/gaps.js +8 -0
  250. package/dist/types/gaps.js.map +1 -0
  251. package/dist/types/prompts.d.ts +99 -0
  252. package/dist/types/prompts.d.ts.map +1 -0
  253. package/dist/types/prompts.js +5 -0
  254. package/dist/types/prompts.js.map +1 -0
  255. package/dist/types/runner.d.ts +156 -0
  256. package/dist/types/runner.d.ts.map +1 -0
  257. package/dist/types/runner.js +41 -0
  258. package/dist/types/runner.js.map +1 -0
  259. package/dist/types/send.d.ts +157 -0
  260. package/dist/types/send.d.ts.map +1 -0
  261. package/dist/types/send.js +18 -0
  262. package/dist/types/send.js.map +1 -0
  263. package/dist/types/status.d.ts +150 -0
  264. package/dist/types/status.d.ts.map +1 -0
  265. package/dist/types/status.js +15 -0
  266. package/dist/types/status.js.map +1 -0
  267. package/dist/types/sync.d.ts +259 -0
  268. package/dist/types/sync.d.ts.map +1 -0
  269. package/dist/types/sync.js +38 -0
  270. package/dist/types/sync.js.map +1 -0
  271. package/package.json +72 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Sync status health checks
3
+ * Validates sync data freshness
4
+ */
5
+ import { existsSync, statSync } from 'fs';
6
+ import { readdir } from 'fs/promises';
7
+ import path from 'path';
8
+ import { findProjectRoot } from '../project.js';
9
+ /**
10
+ * Default stale threshold in minutes
11
+ */
12
+ const STALE_THRESHOLD_MINUTES = 30;
13
+ /**
14
+ * Get last sync time from sync-cache directory
15
+ */
16
+ async function getLastSyncTime(projectRoot) {
17
+ const syncCachePath = path.join(projectRoot, 'docs/sync-cache');
18
+ if (!existsSync(syncCachePath)) {
19
+ return null;
20
+ }
21
+ try {
22
+ const entries = await readdir(syncCachePath, { withFileTypes: true });
23
+ let latestTime = null;
24
+ for (const entry of entries) {
25
+ const entryPath = path.join(syncCachePath, entry.name);
26
+ const stats = statSync(entryPath);
27
+ const mtime = stats.mtime;
28
+ if (!latestTime || mtime > latestTime) {
29
+ latestTime = mtime;
30
+ }
31
+ }
32
+ return latestTime;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ /**
39
+ * Format time difference as human-readable string
40
+ */
41
+ function formatTimeDiff(minutes) {
42
+ if (minutes < 1) {
43
+ return 'just now';
44
+ }
45
+ if (minutes < 60) {
46
+ return `${Math.floor(minutes)} minute${Math.floor(minutes) === 1 ? '' : 's'} ago`;
47
+ }
48
+ const hours = Math.floor(minutes / 60);
49
+ if (hours < 24) {
50
+ return `${hours} hour${hours === 1 ? '' : 's'} ago`;
51
+ }
52
+ const days = Math.floor(hours / 24);
53
+ return `${days} day${days === 1 ? '' : 's'} ago`;
54
+ }
55
+ /**
56
+ * Sync cache exists check
57
+ */
58
+ export const syncCacheExistsCheck = {
59
+ name: 'Sync cache exists',
60
+ category: 'sync',
61
+ critical: false,
62
+ run: async () => {
63
+ const cwd = process.cwd();
64
+ const projectRoot = findProjectRoot(cwd);
65
+ if (!projectRoot) {
66
+ return {
67
+ name: 'Sync cache exists',
68
+ category: 'sync',
69
+ status: 'warning',
70
+ details: 'No project found',
71
+ };
72
+ }
73
+ const syncCachePath = path.join(projectRoot, 'docs/sync-cache');
74
+ if (existsSync(syncCachePath)) {
75
+ return {
76
+ name: 'Sync cache exists',
77
+ category: 'sync',
78
+ status: 'passed',
79
+ details: 'docs/sync-cache directory exists',
80
+ };
81
+ }
82
+ return {
83
+ name: 'Sync cache exists',
84
+ category: 'sync',
85
+ status: 'warning',
86
+ details: 'docs/sync-cache not found',
87
+ fix: 'Run `vibe sync` to create sync cache',
88
+ };
89
+ },
90
+ };
91
+ /**
92
+ * Sync data freshness check
93
+ */
94
+ export const syncDataFreshCheck = {
95
+ name: 'Sync data fresh',
96
+ category: 'sync',
97
+ critical: false,
98
+ run: async () => {
99
+ const cwd = process.cwd();
100
+ const projectRoot = findProjectRoot(cwd);
101
+ if (!projectRoot) {
102
+ return {
103
+ name: 'Sync data fresh',
104
+ category: 'sync',
105
+ status: 'warning',
106
+ details: 'No project found',
107
+ };
108
+ }
109
+ const lastSync = await getLastSyncTime(projectRoot);
110
+ if (!lastSync) {
111
+ return {
112
+ name: 'Sync data fresh',
113
+ category: 'sync',
114
+ status: 'warning',
115
+ details: 'No sync data found',
116
+ fix: 'Run `vibe sync` to sync data from repositories',
117
+ fixable: true,
118
+ };
119
+ }
120
+ const ageMinutes = (Date.now() - lastSync.getTime()) / 1000 / 60;
121
+ if (ageMinutes < STALE_THRESHOLD_MINUTES) {
122
+ return {
123
+ name: 'Sync data fresh',
124
+ category: 'sync',
125
+ status: 'passed',
126
+ details: `Last synced ${formatTimeDiff(ageMinutes)}`,
127
+ };
128
+ }
129
+ return {
130
+ name: 'Sync data fresh',
131
+ category: 'sync',
132
+ status: 'warning',
133
+ details: `Data is ${formatTimeDiff(ageMinutes)} old (stale after ${STALE_THRESHOLD_MINUTES}min)`,
134
+ fix: 'Run `vibe sync` to refresh data',
135
+ fixable: true,
136
+ };
137
+ },
138
+ };
139
+ /**
140
+ * All sync checks
141
+ */
142
+ export const syncChecks = [
143
+ syncCacheExistsCheck,
144
+ syncDataFreshCheck,
145
+ ];
146
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../src/core/doctor/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Core gaps module - gap analysis between PRD and implementation
3
+ *
4
+ * Identifies missing implementations, partial implementations,
5
+ * and orphaned implementations using cached data only.
6
+ */
7
+ import { Gap, GapPriority, MissingGap, PartialGap, OrphanedGap, GapSummary, SuggestedScope, GapAnalysisData, GapsJsonOutput, GapFilterOptions } from '../types/gaps.js';
8
+ /**
9
+ * Parsed requirement from PRD
10
+ */
11
+ interface ParsedRequirement {
12
+ id: string;
13
+ title: string;
14
+ module: string;
15
+ priority: string;
16
+ tags: string[];
17
+ description: string;
18
+ }
19
+ /**
20
+ * Parse requirement files from PRD modules
21
+ */
22
+ export declare function parseRequirements(projectPath: string): Promise<ParsedRequirement[]>;
23
+ /**
24
+ * Assign gap priority based on tags and conditions
25
+ */
26
+ export declare function assignGapPriority(tags: string[], hasBlocker: boolean, originalPriority: string): GapPriority;
27
+ /**
28
+ * Find requirements without linked scopes (missing implementations)
29
+ */
30
+ export declare function findMissingImplementations(projectPath: string): Promise<MissingGap[]>;
31
+ /**
32
+ * Find scopes that are in progress but not completed (partial implementations)
33
+ */
34
+ export declare function findPartialImplementations(projectPath: string): Promise<PartialGap[]>;
35
+ /**
36
+ * Find orphaned implementations from sync cache
37
+ * Note: This is a simplified implementation that looks for
38
+ * implementation plans not linked to PRD requirements
39
+ */
40
+ export declare function findOrphanedImplementations(projectPath: string): Promise<OrphanedGap[]>;
41
+ /**
42
+ * Calculate gap summary from gap lists
43
+ */
44
+ export declare function calculateSummary(missing: MissingGap[], partial: PartialGap[], orphaned: OrphanedGap[]): GapSummary;
45
+ /**
46
+ * Generate scope suggestions from missing gaps
47
+ */
48
+ export declare function generateSuggestions(gaps: MissingGap[]): SuggestedScope[];
49
+ /**
50
+ * Apply filters to gaps
51
+ */
52
+ export declare function filterGaps<T extends Gap>(gaps: T[], options: GapFilterOptions): T[];
53
+ /**
54
+ * Perform complete gap analysis
55
+ */
56
+ export declare function analyzeGaps(projectPath: string, options?: GapFilterOptions): Promise<GapAnalysisData>;
57
+ /**
58
+ * Get list of available module names
59
+ */
60
+ export declare function getModuleNames(projectPath: string): Promise<string[]>;
61
+ /**
62
+ * Convert gap analysis data to JSON output format
63
+ */
64
+ export declare function toGapsJsonOutput(data: GapAnalysisData): GapsJsonOutput;
65
+ /**
66
+ * Check if there are any P1 gaps (for CI mode)
67
+ */
68
+ export declare function hasP1Gaps(data: GapAnalysisData): boolean;
69
+ export {};
70
+ //# sourceMappingURL=gaps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gaps.d.ts","sourceRoot":"","sources":["../../src/core/gaps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EACL,GAAG,EACH,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,UAAU,EAGV,cAAc,EACd,eAAe,EACf,cAAc,EACd,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAc1B;;GAEG;AACH,UAAU,iBAAiB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAmEzF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,UAAU,EAAE,OAAO,EACnB,gBAAgB,EAAE,MAAM,GACvB,WAAW,CAkBb;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB;AAoDD;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,EAAE,CAAC,CAwExB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,UAAU,EAAE,EACrB,QAAQ,EAAE,WAAW,EAAE,GACtB,UAAU,CAuBZ;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAqExE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,GAAG,EACtC,IAAI,EAAE,CAAC,EAAE,EACT,OAAO,EAAE,gBAAgB,GACxB,CAAC,EAAE,CAqBL;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAkC1B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAa3E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,cAAc,CAmBtE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAExD"}
@@ -0,0 +1,448 @@
1
+ /**
2
+ * Core gaps module - gap analysis between PRD and implementation
3
+ *
4
+ * Identifies missing implementations, partial implementations,
5
+ * and orphaned implementations using cached data only.
6
+ */
7
+ import { readFile, readdir } from 'fs/promises';
8
+ import { existsSync } from 'fs';
9
+ import path from 'path';
10
+ import { loadScopes } from './coverage.js';
11
+ /**
12
+ * Priority tags that indicate P1 (security/compliance)
13
+ */
14
+ const P1_TAGS = ['security', 'compliance', 'critical', 'blocking'];
15
+ /**
16
+ * Priority tags that indicate P2 (core functionality)
17
+ */
18
+ const P2_TAGS = ['core', 'essential', 'important'];
19
+ /**
20
+ * Parse requirement files from PRD modules
21
+ */
22
+ export async function parseRequirements(projectPath) {
23
+ const requirements = [];
24
+ const modulesDir = path.join(projectPath, 'docs', 'prd', 'modules');
25
+ try {
26
+ if (!existsSync(modulesDir)) {
27
+ return requirements;
28
+ }
29
+ const moduleDirs = await readdir(modulesDir);
30
+ for (const moduleDir of moduleDirs) {
31
+ const modulePath = path.join(modulesDir, moduleDir);
32
+ try {
33
+ const files = await readdir(modulePath);
34
+ for (const file of files) {
35
+ if (!file.endsWith('.md') || file.startsWith('_'))
36
+ continue;
37
+ const filePath = path.join(modulePath, file);
38
+ const content = await readFile(filePath, 'utf-8');
39
+ // Check if this is a requirement file
40
+ const idMatch = content.match(/\*\*ID\*\*\s*\|\s*`?(REQ-[A-Z]+-\d+)`?/);
41
+ if (!idMatch || !idMatch[1])
42
+ continue;
43
+ const id = idMatch[1];
44
+ // Extract title
45
+ const titleMatch = content.match(/^#\s+(?:REQ:\s*)?(.+?)(?:\n|$)/m);
46
+ const title = titleMatch?.[1] ?? id;
47
+ // Extract priority
48
+ const priorityMatch = content.match(/\*\*Priority\*\*\s*\|\s*`?(P[1234])`?/);
49
+ const priority = priorityMatch?.[1] ?? 'P3';
50
+ // Extract tags
51
+ const tagsMatch = content.match(/\*\*Tags\*\*\s*\|\s*([^|\n]+)/);
52
+ const tagsStr = tagsMatch?.[1]?.trim() ?? '';
53
+ const tags = tagsStr
54
+ .split(',')
55
+ .map((t) => t.trim().toLowerCase())
56
+ .filter(Boolean);
57
+ // Extract description
58
+ const descMatch = content.match(/## Description\s*\n\n([^\n]+)/);
59
+ const description = descMatch?.[1] ?? '';
60
+ requirements.push({
61
+ id,
62
+ title,
63
+ module: moduleDir,
64
+ priority,
65
+ tags,
66
+ description,
67
+ });
68
+ }
69
+ }
70
+ catch {
71
+ // Can't read module directory
72
+ }
73
+ }
74
+ }
75
+ catch {
76
+ // Can't read modules directory
77
+ }
78
+ return requirements;
79
+ }
80
+ /**
81
+ * Assign gap priority based on tags and conditions
82
+ */
83
+ export function assignGapPriority(tags, hasBlocker, originalPriority) {
84
+ // P1: Security/compliance tags or blocking status
85
+ if (tags.some((tag) => P1_TAGS.includes(tag)) || hasBlocker) {
86
+ return 'P1';
87
+ }
88
+ // P2: Core functionality tags or elevated from blocker
89
+ if (tags.some((tag) => P2_TAGS.includes(tag))) {
90
+ return 'P2';
91
+ }
92
+ // Use original priority from requirement if available
93
+ if (originalPriority === 'P1')
94
+ return 'P1';
95
+ if (originalPriority === 'P2')
96
+ return 'P2';
97
+ if (originalPriority === 'P3')
98
+ return 'P3';
99
+ // Default for orphaned
100
+ return 'P4';
101
+ }
102
+ /**
103
+ * Find requirements without linked scopes (missing implementations)
104
+ */
105
+ export async function findMissingImplementations(projectPath) {
106
+ const requirements = await parseRequirements(projectPath);
107
+ const scopes = await loadScopes(projectPath);
108
+ const missing = [];
109
+ for (const req of requirements) {
110
+ // Check if any scope links to this requirement
111
+ const linkedScope = scopes.find((scope) => scope.requirements.includes(req.id));
112
+ if (!linkedScope) {
113
+ const priority = assignGapPriority(req.tags, false, req.priority);
114
+ missing.push({
115
+ type: 'missing',
116
+ priority,
117
+ requirementId: req.id,
118
+ title: req.title,
119
+ module: req.module,
120
+ impact: req.description.slice(0, 100) || 'No description available',
121
+ tags: req.tags,
122
+ suggestion: `Create a scope to implement ${req.id}`,
123
+ });
124
+ }
125
+ }
126
+ return missing;
127
+ }
128
+ /**
129
+ * Parse scope file for additional details (progress, blockers)
130
+ */
131
+ async function parseScopeDetails(projectPath, scope) {
132
+ // Determine the directory based on status
133
+ const statusToDir = {
134
+ draft: 'drafts',
135
+ ready: 'ready',
136
+ sent: 'sent',
137
+ completed: 'completed',
138
+ };
139
+ const dir = statusToDir[scope.status] ?? 'drafts';
140
+ const scopesDir = path.join(projectPath, 'docs', 'scopes', dir);
141
+ // Find the scope file
142
+ try {
143
+ const files = await readdir(scopesDir);
144
+ const scopeFile = files.find((f) => f.startsWith(`${scope.id}-`) || f === `${scope.id}.md`);
145
+ if (!scopeFile) {
146
+ return { progress: 0, blocked: false };
147
+ }
148
+ const filePath = path.join(scopesDir, scopeFile);
149
+ const content = await readFile(filePath, 'utf-8');
150
+ // Extract progress (if available)
151
+ const progressMatch = content.match(/\*\*Progress\*\*\s*\|\s*`?(\d+)%?`?/);
152
+ const progress = progressMatch && progressMatch[1] ? parseInt(progressMatch[1], 10) : 0;
153
+ // Check for blockers
154
+ const blockerMatch = content.match(/\*\*Blocked\*\*\s*\|\s*`?([^|\n`]+)`?/);
155
+ const blockerValue = blockerMatch?.[1]?.trim();
156
+ const blocked = blockerValue !== undefined &&
157
+ blockerValue.toLowerCase() !== 'no' &&
158
+ blockerValue !== '-';
159
+ const blockerReason = blocked ? blockerValue : undefined;
160
+ return { progress, blocked, blockerReason };
161
+ }
162
+ catch {
163
+ return { progress: 0, blocked: false };
164
+ }
165
+ }
166
+ /**
167
+ * Find scopes that are in progress but not completed (partial implementations)
168
+ */
169
+ export async function findPartialImplementations(projectPath) {
170
+ const scopes = await loadScopes(projectPath);
171
+ const partial = [];
172
+ for (const scope of scopes) {
173
+ // Only include sent or ready scopes (in progress but not completed)
174
+ if (scope.status !== 'sent' && scope.status !== 'ready') {
175
+ continue;
176
+ }
177
+ const details = await parseScopeDetails(projectPath, scope);
178
+ // A partial gap is a scope that's in progress with < 100% completion
179
+ const priority = assignGapPriority([], details.blocked, 'P3');
180
+ partial.push({
181
+ type: 'partial',
182
+ priority: details.blocked ? 'P2' : priority,
183
+ scopeId: scope.id,
184
+ title: scope.title,
185
+ status: scope.status,
186
+ progress: details.progress,
187
+ blocked: details.blocked,
188
+ blockerReason: details.blockerReason,
189
+ requirements: scope.requirements,
190
+ });
191
+ }
192
+ return partial;
193
+ }
194
+ /**
195
+ * Find orphaned implementations from sync cache
196
+ * Note: This is a simplified implementation that looks for
197
+ * implementation plans not linked to PRD requirements
198
+ */
199
+ export async function findOrphanedImplementations(projectPath) {
200
+ const orphaned = [];
201
+ const syncCacheDir = path.join(projectPath, 'docs', 'sync-cache');
202
+ try {
203
+ if (!existsSync(syncCacheDir)) {
204
+ return orphaned;
205
+ }
206
+ // Load last sync data to get repository information
207
+ const lastSyncPath = path.join(syncCacheDir, 'last-sync.json');
208
+ if (!existsSync(lastSyncPath)) {
209
+ return orphaned;
210
+ }
211
+ const lastSyncContent = await readFile(lastSyncPath, 'utf-8');
212
+ const lastSync = JSON.parse(lastSyncContent);
213
+ // Load all requirements for checking links
214
+ const requirements = await parseRequirements(projectPath);
215
+ const reqIds = new Set(requirements.map((r) => r.id));
216
+ // Check each repository's impl-plans for orphaned implementations
217
+ for (const [repoAlias, _repoData] of Object.entries(lastSync.repos ?? {})) {
218
+ const repoDir = path.join(syncCacheDir, 'repos', repoAlias);
219
+ if (!existsSync(repoDir))
220
+ continue;
221
+ const implPlansDir = path.join(repoDir, 'impl-plans');
222
+ if (!existsSync(implPlansDir))
223
+ continue;
224
+ try {
225
+ const planFiles = await readdir(implPlansDir);
226
+ for (const planFile of planFiles) {
227
+ if (!planFile.endsWith('.md'))
228
+ continue;
229
+ const planPath = path.join(implPlansDir, planFile);
230
+ const content = await readFile(planPath, 'utf-8');
231
+ // Extract requirement references from the plan
232
+ const reqMatches = content.matchAll(/REQ-[A-Z]+-\d+/g);
233
+ const linkedReqs = new Set([...reqMatches].map((m) => m[0]));
234
+ // Check if all linked requirements exist
235
+ const orphanedReqs = [...linkedReqs].filter((r) => !reqIds.has(r));
236
+ if (orphanedReqs.length > 0 || linkedReqs.size === 0) {
237
+ const titleMatch = content.match(/^#\s+(.+?)(?:\n|$)/m);
238
+ const identifier = titleMatch?.[1] ?? planFile.replace('.md', '');
239
+ orphaned.push({
240
+ type: 'orphaned',
241
+ priority: 'P4',
242
+ identifier,
243
+ repository: repoAlias,
244
+ files: [planFile],
245
+ suggestedAction: linkedReqs.size === 0
246
+ ? 'Link to PRD requirements or remove if obsolete'
247
+ : `Update to use valid requirements (${orphanedReqs.join(', ')} not found)`,
248
+ });
249
+ }
250
+ }
251
+ }
252
+ catch {
253
+ // Can't read impl-plans directory
254
+ }
255
+ }
256
+ }
257
+ catch {
258
+ // Can't read sync cache
259
+ }
260
+ return orphaned;
261
+ }
262
+ /**
263
+ * Calculate gap summary from gap lists
264
+ */
265
+ export function calculateSummary(missing, partial, orphaned) {
266
+ const allGaps = [...missing, ...partial, ...orphaned];
267
+ const byPriority = {
268
+ P1: 0,
269
+ P2: 0,
270
+ P3: 0,
271
+ P4: 0,
272
+ total: allGaps.length,
273
+ };
274
+ const byType = {
275
+ missing: missing.length,
276
+ partial: partial.length,
277
+ orphaned: orphaned.length,
278
+ total: allGaps.length,
279
+ };
280
+ for (const gap of allGaps) {
281
+ byPriority[gap.priority]++;
282
+ }
283
+ return { byPriority, byType };
284
+ }
285
+ /**
286
+ * Generate scope suggestions from missing gaps
287
+ */
288
+ export function generateSuggestions(gaps) {
289
+ // Group gaps by module
290
+ const byModule = new Map();
291
+ for (const gap of gaps) {
292
+ const existing = byModule.get(gap.module) ?? [];
293
+ existing.push(gap);
294
+ byModule.set(gap.module, existing);
295
+ }
296
+ const suggestions = [];
297
+ for (const [module, moduleGaps] of byModule) {
298
+ // Further group by priority within module
299
+ const p1Gaps = moduleGaps.filter((g) => g.priority === 'P1');
300
+ const p2Gaps = moduleGaps.filter((g) => g.priority === 'P2');
301
+ const otherGaps = moduleGaps.filter((g) => g.priority !== 'P1' && g.priority !== 'P2');
302
+ // Create suggestions for P1 gaps (one per gap due to urgency)
303
+ for (const gap of p1Gaps) {
304
+ suggestions.push({
305
+ title: `Implement ${gap.requirementId}: ${gap.title}`,
306
+ priority: 'P1',
307
+ requirements: [gap.requirementId],
308
+ estimatedEffort: 'M',
309
+ module,
310
+ });
311
+ }
312
+ // Bundle P2 gaps by module (max 3 per scope)
313
+ if (p2Gaps.length > 0) {
314
+ for (let i = 0; i < p2Gaps.length; i += 3) {
315
+ const batch = p2Gaps.slice(i, i + 3);
316
+ const effort = batch.length >= 3 ? 'L' : batch.length >= 2 ? 'M' : 'S';
317
+ suggestions.push({
318
+ title: `${module}: ${batch.map((g) => g.title).join(', ')}`,
319
+ priority: 'P2',
320
+ requirements: batch.map((g) => g.requirementId),
321
+ estimatedEffort: effort,
322
+ module,
323
+ });
324
+ }
325
+ }
326
+ // Bundle other gaps by module (max 5 per scope)
327
+ if (otherGaps.length > 0) {
328
+ for (let i = 0; i < otherGaps.length; i += 5) {
329
+ const batch = otherGaps.slice(i, i + 5);
330
+ const effort = batch.length >= 4 ? 'L' : batch.length >= 2 ? 'M' : 'S';
331
+ suggestions.push({
332
+ title: `${module}: ${batch.map((g) => g.title).join(', ')}`,
333
+ priority: 'P3',
334
+ requirements: batch.map((g) => g.requirementId),
335
+ estimatedEffort: effort,
336
+ module,
337
+ });
338
+ }
339
+ }
340
+ }
341
+ // Sort by priority
342
+ suggestions.sort((a, b) => {
343
+ const order = { P1: 0, P2: 1, P3: 2, P4: 3 };
344
+ return order[a.priority] - order[b.priority];
345
+ });
346
+ return suggestions;
347
+ }
348
+ /**
349
+ * Apply filters to gaps
350
+ */
351
+ export function filterGaps(gaps, options) {
352
+ let filtered = [...gaps];
353
+ if (options.priority) {
354
+ filtered = filtered.filter((g) => g.priority === options.priority);
355
+ }
356
+ if (options.type) {
357
+ filtered = filtered.filter((g) => g.type === options.type);
358
+ }
359
+ if (options.module) {
360
+ filtered = filtered.filter((g) => {
361
+ if (g.type === 'missing') {
362
+ return g.module.toLowerCase() === options.module.toLowerCase();
363
+ }
364
+ return true; // Other types don't have module field
365
+ });
366
+ }
367
+ return filtered;
368
+ }
369
+ /**
370
+ * Perform complete gap analysis
371
+ */
372
+ export async function analyzeGaps(projectPath, options = {}) {
373
+ // Find all gaps
374
+ let missing = await findMissingImplementations(projectPath);
375
+ let partial = await findPartialImplementations(projectPath);
376
+ let orphaned = await findOrphanedImplementations(projectPath);
377
+ // Apply filters
378
+ if (options.priority || options.module) {
379
+ missing = filterGaps(missing, options);
380
+ partial = filterGaps(partial, options);
381
+ orphaned = filterGaps(orphaned, options);
382
+ }
383
+ if (options.type) {
384
+ if (options.type !== 'missing')
385
+ missing = [];
386
+ if (options.type !== 'partial')
387
+ partial = [];
388
+ if (options.type !== 'orphaned')
389
+ orphaned = [];
390
+ }
391
+ // Calculate summary
392
+ const summary = calculateSummary(missing, partial, orphaned);
393
+ // Generate suggestions from missing gaps
394
+ const suggestions = generateSuggestions(missing);
395
+ return {
396
+ summary,
397
+ gaps: {
398
+ missing,
399
+ partial,
400
+ orphaned,
401
+ },
402
+ suggestions,
403
+ };
404
+ }
405
+ /**
406
+ * Get list of available module names
407
+ */
408
+ export async function getModuleNames(projectPath) {
409
+ const modulesDir = path.join(projectPath, 'docs', 'prd', 'modules');
410
+ try {
411
+ if (!existsSync(modulesDir)) {
412
+ return [];
413
+ }
414
+ const entries = await readdir(modulesDir, { withFileTypes: true });
415
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
416
+ }
417
+ catch {
418
+ return [];
419
+ }
420
+ }
421
+ /**
422
+ * Convert gap analysis data to JSON output format
423
+ */
424
+ export function toGapsJsonOutput(data) {
425
+ const allGaps = [
426
+ ...data.gaps.missing,
427
+ ...data.gaps.partial,
428
+ ...data.gaps.orphaned,
429
+ ];
430
+ // Sort by priority
431
+ allGaps.sort((a, b) => {
432
+ const order = { P1: 0, P2: 1, P3: 2, P4: 3 };
433
+ return order[a.priority] - order[b.priority];
434
+ });
435
+ return {
436
+ timestamp: new Date().toISOString(),
437
+ summary: data.summary,
438
+ gaps: allGaps,
439
+ suggestions: data.suggestions,
440
+ };
441
+ }
442
+ /**
443
+ * Check if there are any P1 gaps (for CI mode)
444
+ */
445
+ export function hasP1Gaps(data) {
446
+ return data.summary.byPriority.P1 > 0;
447
+ }
448
+ //# sourceMappingURL=gaps.js.map