tlc-claude-code 1.8.5 → 2.0.1

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 (76) hide show
  1. package/.claude/commands/tlc/bootstrap.md +77 -0
  2. package/.claude/commands/tlc/build.md +20 -6
  3. package/.claude/commands/tlc/recall.md +87 -0
  4. package/.claude/commands/tlc/remember.md +71 -0
  5. package/CLAUDE.md +84 -201
  6. package/dashboard-web/dist/assets/index-Uhc49PE-.css +1 -0
  7. package/dashboard-web/dist/assets/index-W36XHPC5.js +431 -0
  8. package/dashboard-web/dist/assets/index-W36XHPC5.js.map +1 -0
  9. package/dashboard-web/dist/index.html +2 -2
  10. package/package.json +1 -1
  11. package/server/index.js +13 -0
  12. package/server/lib/bug-writer.js +204 -0
  13. package/server/lib/bug-writer.test.js +279 -0
  14. package/server/lib/claude-cascade.js +247 -0
  15. package/server/lib/claude-cascade.test.js +245 -0
  16. package/server/lib/context-injection.js +121 -0
  17. package/server/lib/context-injection.test.js +340 -0
  18. package/server/lib/conversation-chunker.js +320 -0
  19. package/server/lib/conversation-chunker.test.js +573 -0
  20. package/server/lib/embedding-client.js +160 -0
  21. package/server/lib/embedding-client.test.js +243 -0
  22. package/server/lib/global-config.js +198 -0
  23. package/server/lib/global-config.test.js +288 -0
  24. package/server/lib/inherited-search.js +184 -0
  25. package/server/lib/inherited-search.test.js +343 -0
  26. package/server/lib/memory-api.js +180 -0
  27. package/server/lib/memory-api.test.js +322 -0
  28. package/server/lib/memory-hooks-capture.test.js +350 -0
  29. package/server/lib/memory-hooks.js +101 -0
  30. package/server/lib/memory-inheritance.js +179 -0
  31. package/server/lib/memory-inheritance.test.js +360 -0
  32. package/server/lib/plan-writer.js +196 -0
  33. package/server/lib/plan-writer.test.js +298 -0
  34. package/server/lib/project-scanner.js +267 -0
  35. package/server/lib/project-scanner.test.js +389 -0
  36. package/server/lib/project-status.js +302 -0
  37. package/server/lib/project-status.test.js +470 -0
  38. package/server/lib/projects-registry.js +237 -0
  39. package/server/lib/projects-registry.test.js +275 -0
  40. package/server/lib/recall-command.js +207 -0
  41. package/server/lib/recall-command.test.js +306 -0
  42. package/server/lib/remember-command.js +96 -0
  43. package/server/lib/remember-command.test.js +265 -0
  44. package/server/lib/rich-capture.js +221 -0
  45. package/server/lib/rich-capture.test.js +312 -0
  46. package/server/lib/roadmap-api.js +200 -0
  47. package/server/lib/roadmap-api.test.js +318 -0
  48. package/server/lib/semantic-recall.js +242 -0
  49. package/server/lib/semantic-recall.test.js +446 -0
  50. package/server/lib/setup-generator.js +315 -0
  51. package/server/lib/setup-generator.test.js +303 -0
  52. package/server/lib/test-inventory.js +112 -0
  53. package/server/lib/test-inventory.test.js +360 -0
  54. package/server/lib/vector-indexer.js +246 -0
  55. package/server/lib/vector-indexer.test.js +459 -0
  56. package/server/lib/vector-store.js +260 -0
  57. package/server/lib/vector-store.test.js +706 -0
  58. package/server/lib/workspace-api.js +811 -0
  59. package/server/lib/workspace-api.test.js +743 -0
  60. package/server/lib/workspace-bootstrap.js +164 -0
  61. package/server/lib/workspace-bootstrap.test.js +503 -0
  62. package/server/lib/workspace-context.js +129 -0
  63. package/server/lib/workspace-context.test.js +214 -0
  64. package/server/lib/workspace-detector.js +162 -0
  65. package/server/lib/workspace-detector.test.js +193 -0
  66. package/server/lib/workspace-init.js +307 -0
  67. package/server/lib/workspace-init.test.js +244 -0
  68. package/server/lib/workspace-snapshot.js +236 -0
  69. package/server/lib/workspace-snapshot.test.js +444 -0
  70. package/server/lib/workspace-watcher.js +162 -0
  71. package/server/lib/workspace-watcher.test.js +257 -0
  72. package/server/package-lock.json +552 -0
  73. package/server/package.json +4 -0
  74. package/dashboard-web/dist/assets/index-B1I_joSL.js +0 -393
  75. package/dashboard-web/dist/assets/index-B1I_joSL.js.map +0 -1
  76. package/dashboard-web/dist/assets/index-Trhg1C1Y.css +0 -1
@@ -0,0 +1,302 @@
1
+ /**
2
+ * @file project-status.js
3
+ * @description Project Status module (Phase 75, Task 1).
4
+ *
5
+ * Factory function `createProjectStatus(deps)` accepts injected dependencies
6
+ * (fs, execSync) and returns `{ getFullStatus(projectPath) }`.
7
+ *
8
+ * Parses `.planning/ROADMAP.md` for milestones and phases, reads per-phase
9
+ * PLAN.md, TESTS.md, and VERIFIED.md files, extracts git log and project info.
10
+ */
11
+
12
+ /**
13
+ * Parse phase status marker from heading text.
14
+ * @param {string} text - Heading text like "Phase 1: Core Infrastructure [x]"
15
+ * @returns {'done'|'in_progress'|'pending'} Phase status
16
+ */
17
+ function parseStatus(text) {
18
+ if (/\[x\]/.test(text)) return 'done';
19
+ if (/\[>\]/.test(text)) return 'in_progress';
20
+ return 'pending';
21
+ }
22
+
23
+ /**
24
+ * Parse ROADMAP.md content into milestones with phases.
25
+ * @param {string} content - Raw ROADMAP.md content
26
+ * @returns {{ milestones: Array, totalPhases: number, completedPhases: number }}
27
+ */
28
+ function parseRoadmap(content) {
29
+ const lines = content.replace(/\r/g, '').split('\n');
30
+ const milestones = [];
31
+ let currentMilestone = null;
32
+ let currentPhase = null;
33
+ let inDeliverables = false;
34
+
35
+ for (const line of lines) {
36
+ // Milestone heading: ## Milestone: Name
37
+ const milestoneMatch = line.match(/^## Milestone:\s*(.+)$/);
38
+ if (milestoneMatch) {
39
+ currentMilestone = { name: milestoneMatch[1].trim(), phases: [] };
40
+ milestones.push(currentMilestone);
41
+ currentPhase = null;
42
+ inDeliverables = false;
43
+ continue;
44
+ }
45
+
46
+ // Phase heading: ### Phase N: Name [status] optional suffix
47
+ const phaseMatch = line.match(/^### Phase (\d+):\s*(.+)$/);
48
+ if (phaseMatch) {
49
+ const number = parseInt(phaseMatch[1], 10);
50
+ const rest = phaseMatch[2];
51
+ // Extract name: everything before the status marker
52
+ const nameMatch = rest.match(/^(.+?)\s*\[/);
53
+ const name = nameMatch ? nameMatch[1].trim() : rest.trim();
54
+ const status = parseStatus(rest);
55
+
56
+ currentPhase = {
57
+ number,
58
+ name,
59
+ status,
60
+ goal: '',
61
+ deliverables: [],
62
+ taskCount: 0,
63
+ completedTaskCount: 0,
64
+ hasTests: false,
65
+ testFileCount: 0,
66
+ testCount: 0,
67
+ verified: false,
68
+ };
69
+
70
+ if (currentMilestone) {
71
+ currentMilestone.phases.push(currentPhase);
72
+ }
73
+ inDeliverables = false;
74
+ continue;
75
+ }
76
+
77
+ if (!currentPhase) continue;
78
+
79
+ // Goal line: **Goal:** text
80
+ const goalMatch = line.match(/^\*\*Goal:\*\*\s*(.+)$/);
81
+ if (goalMatch) {
82
+ currentPhase.goal = goalMatch[1].trim();
83
+ continue;
84
+ }
85
+
86
+ // Deliverables header
87
+ if (line.match(/^\*\*Deliverables:\*\*/)) {
88
+ inDeliverables = true;
89
+ continue;
90
+ }
91
+
92
+ // Deliverable/checklist item: - [x] text or - [ ] text
93
+ // Match both inside **Deliverables:** sections and standalone checklist items
94
+ const deliverableMatch = line.match(/^- \[(x| )\]\s*(.+)$/);
95
+ if (deliverableMatch) {
96
+ currentPhase.deliverables.push({
97
+ text: deliverableMatch[2].trim(),
98
+ done: deliverableMatch[1] === 'x',
99
+ });
100
+ continue;
101
+ }
102
+ // End deliverables section on non-deliverable, non-empty, non-blank line
103
+ if (inDeliverables && line.trim() !== '' && !line.match(/^- \[/)) {
104
+ inDeliverables = false;
105
+ }
106
+ }
107
+
108
+ const allPhases = milestones.flatMap((m) => m.phases);
109
+ const totalPhases = allPhases.length;
110
+ const completedPhases = allPhases.filter((p) => p.status === 'done').length;
111
+
112
+ return { milestones, totalPhases, completedPhases };
113
+ }
114
+
115
+ /**
116
+ * Parse PLAN.md to count tasks and completed tasks.
117
+ * @param {string} content - Raw PLAN.md content
118
+ * @returns {{ taskCount: number, completedTaskCount: number }}
119
+ */
120
+ function parsePlan(content) {
121
+ const lines = content.replace(/\r/g, '').split('\n');
122
+ let taskCount = 0;
123
+ let completedTaskCount = 0;
124
+
125
+ for (const line of lines) {
126
+ const taskMatch = line.match(/^### Task \d+:.+\[(x| )\]/);
127
+ if (taskMatch) {
128
+ taskCount++;
129
+ if (taskMatch[1] === 'x') completedTaskCount++;
130
+ }
131
+ }
132
+
133
+ return { taskCount, completedTaskCount };
134
+ }
135
+
136
+ /**
137
+ * Parse TESTS.md to count test files and total tests.
138
+ * @param {string} content - Raw TESTS.md content
139
+ * @returns {{ testFileCount: number, testCount: number }}
140
+ */
141
+ function parseTests(content) {
142
+ const lines = content.replace(/\r/g, '').split('\n');
143
+ let testFileCount = 0;
144
+ let testCount = 0;
145
+
146
+ for (const line of lines) {
147
+ // Match table rows like: | lib/core.test.js | 10 | Passing |
148
+ // Skip header row, separator row, and total row
149
+ const rowMatch = line.match(/^\|\s*([^|]+)\s*\|\s*(\d+)\s*\|\s*([^|]+)\s*\|$/);
150
+ if (rowMatch) {
151
+ const file = rowMatch[1].trim();
152
+ const count = parseInt(rowMatch[2].trim(), 10);
153
+ // Skip header, separator, and total rows
154
+ if (file === 'File' || file.startsWith('--') || file.startsWith('**')) continue;
155
+ testFileCount++;
156
+ testCount += count;
157
+ }
158
+ }
159
+
160
+ return { testFileCount, testCount };
161
+ }
162
+
163
+ /**
164
+ * Parse git log output into structured commit objects.
165
+ * @param {string} output - Raw git log output (pipe-delimited)
166
+ * @returns {Array<{hash: string, message: string, date: string, author: string}>}
167
+ */
168
+ function parseGitLog(output) {
169
+ if (!output || !output.trim()) return [];
170
+ return output.trim().split('\n').map((line) => {
171
+ const [hash, message, date, author] = line.split('|');
172
+ return { hash, message, date, author };
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Creates a project status reader with injected dependencies.
178
+ * @param {Object} deps - Dependencies
179
+ * @param {Object} deps.fs - File system module (existsSync, readFileSync, readdirSync)
180
+ * @param {Function} deps.execSync - Child process execSync function
181
+ * @returns {{ getFullStatus: (projectPath: string) => Object }}
182
+ */
183
+ function createProjectStatus({ fs, execSync }) {
184
+ /**
185
+ * Read a file safely, returning null if it doesn't exist.
186
+ * @param {string} filePath - Absolute path
187
+ * @returns {string|null}
188
+ */
189
+ function readFile(filePath) {
190
+ if (fs.existsSync(filePath)) {
191
+ return fs.readFileSync(filePath, 'utf-8');
192
+ }
193
+ return null;
194
+ }
195
+
196
+ /**
197
+ * Get full project status for a given project path.
198
+ * @param {string} projectPath - Absolute path to the project root
199
+ * @returns {Object} Full status object
200
+ */
201
+ function getFullStatus(projectPath) {
202
+ // Parse project info
203
+ const projectInfo = {};
204
+ const pkgContent = readFile(`${projectPath}/package.json`);
205
+ if (pkgContent) {
206
+ const pkg = JSON.parse(pkgContent);
207
+ projectInfo.name = pkg.name;
208
+ projectInfo.version = pkg.version;
209
+ }
210
+
211
+ const projectMd = readFile(`${projectPath}/PROJECT.md`);
212
+ if (projectMd) {
213
+ // Extract first non-heading, non-empty paragraph
214
+ const mdLines = projectMd.split('\n');
215
+ for (const mdLine of mdLines) {
216
+ if (mdLine.trim() && !mdLine.startsWith('#')) {
217
+ projectInfo.description = mdLine.trim();
218
+ break;
219
+ }
220
+ }
221
+ }
222
+
223
+ // Parse roadmap
224
+ const roadmapContent = readFile(`${projectPath}/.planning/ROADMAP.md`);
225
+ let milestones = [];
226
+ let totalPhases = 0;
227
+ let completedPhases = 0;
228
+
229
+ if (roadmapContent) {
230
+ const roadmap = parseRoadmap(roadmapContent);
231
+ milestones = roadmap.milestones;
232
+ totalPhases = roadmap.totalPhases;
233
+ completedPhases = roadmap.completedPhases;
234
+ }
235
+
236
+ // Enrich phases with per-phase file data
237
+ let totalTestFiles = 0;
238
+ let totalTests = 0;
239
+
240
+ for (const milestone of milestones) {
241
+ for (const phase of milestone.phases) {
242
+ const n = phase.number;
243
+
244
+ // PLAN.md
245
+ const planContent = readFile(`${projectPath}/.planning/phases/${n}-PLAN.md`);
246
+ if (planContent) {
247
+ const plan = parsePlan(planContent);
248
+ phase.taskCount = plan.taskCount;
249
+ phase.completedTaskCount = plan.completedTaskCount;
250
+ }
251
+
252
+ // TESTS.md
253
+ const testsContent = readFile(`${projectPath}/.planning/phases/${n}-TESTS.md`);
254
+ if (testsContent) {
255
+ const tests = parseTests(testsContent);
256
+ phase.hasTests = true;
257
+ phase.testFileCount = tests.testFileCount;
258
+ phase.testCount = tests.testCount;
259
+ totalTestFiles += tests.testFileCount;
260
+ totalTests += tests.testCount;
261
+ }
262
+
263
+ // Fall back to deliverable counts when no PLAN.md exists
264
+ if (phase.taskCount === 0 && phase.deliverables.length > 0) {
265
+ phase.taskCount = phase.deliverables.length;
266
+ phase.completedTaskCount = phase.deliverables.filter((d) => d.done).length;
267
+ }
268
+
269
+ // VERIFIED.md
270
+ phase.verified = fs.existsSync(`${projectPath}/.planning/phases/${n}-VERIFIED.md`);
271
+ }
272
+ }
273
+
274
+ // Git log
275
+ let recentCommits = [];
276
+ try {
277
+ const gitOutput = execSync(
278
+ `git log --format="%h|%s|%ad|%an" --date=short -10`,
279
+ { cwd: projectPath, encoding: 'utf8' }
280
+ );
281
+ recentCommits = parseGitLog(gitOutput);
282
+ } catch {
283
+ // Git not available or not a git repo
284
+ }
285
+
286
+ return {
287
+ milestones,
288
+ totalPhases,
289
+ completedPhases,
290
+ testSummary: {
291
+ totalFiles: totalTestFiles,
292
+ totalTests: totalTests,
293
+ },
294
+ recentCommits,
295
+ projectInfo,
296
+ };
297
+ }
298
+
299
+ return { getFullStatus };
300
+ }
301
+
302
+ module.exports = { createProjectStatus };