tlc-claude-code 1.8.5 → 2.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.
- package/.claude/commands/tlc/bootstrap.md +77 -0
- package/.claude/commands/tlc/build.md +20 -6
- package/.claude/commands/tlc/deploy.md +194 -2
- package/.claude/commands/tlc/e2e-verify.md +214 -0
- package/.claude/commands/tlc/guard.md +191 -0
- package/.claude/commands/tlc/help.md +32 -0
- package/.claude/commands/tlc/init.md +73 -37
- package/.claude/commands/tlc/llm.md +19 -4
- package/.claude/commands/tlc/preflight.md +134 -0
- package/.claude/commands/tlc/recall.md +87 -0
- package/.claude/commands/tlc/remember.md +71 -0
- package/.claude/commands/tlc/review.md +17 -4
- package/.claude/commands/tlc/watchci.md +159 -0
- package/.claude/hooks/tlc-block-tools.sh +41 -0
- package/.claude/hooks/tlc-capture-exchange.sh +50 -0
- package/.claude/hooks/tlc-post-build.sh +38 -0
- package/.claude/hooks/tlc-post-push.sh +22 -0
- package/.claude/hooks/tlc-prompt-guard.sh +69 -0
- package/.claude/hooks/tlc-session-init.sh +123 -0
- package/CLAUDE.md +96 -201
- package/bin/install.js +171 -2
- package/bin/postinstall.js +45 -26
- package/dashboard-web/dist/assets/index-CdS5CHqu.css +1 -0
- package/dashboard-web/dist/assets/index-CwNPPVpg.js +483 -0
- package/dashboard-web/dist/assets/index-CwNPPVpg.js.map +1 -0
- package/dashboard-web/dist/index.html +2 -2
- package/docker-compose.dev.yml +18 -12
- package/package.json +3 -1
- package/server/index.js +240 -1
- package/server/lib/bug-writer.js +204 -0
- package/server/lib/bug-writer.test.js +279 -0
- package/server/lib/capture-bridge.js +242 -0
- package/server/lib/capture-bridge.test.js +363 -0
- package/server/lib/capture-guard.js +140 -0
- package/server/lib/capture-guard.test.js +182 -0
- package/server/lib/claude-cascade.js +247 -0
- package/server/lib/claude-cascade.test.js +245 -0
- package/server/lib/command-runner.js +159 -0
- package/server/lib/command-runner.test.js +92 -0
- package/server/lib/context-injection.js +121 -0
- package/server/lib/context-injection.test.js +340 -0
- package/server/lib/conversation-chunker.js +320 -0
- package/server/lib/conversation-chunker.test.js +573 -0
- package/server/lib/deploy/runners/dependency-runner.js +106 -0
- package/server/lib/deploy/runners/dependency-runner.test.js +148 -0
- package/server/lib/deploy/runners/secrets-runner.js +174 -0
- package/server/lib/deploy/runners/secrets-runner.test.js +127 -0
- package/server/lib/deploy/security-gates.js +11 -24
- package/server/lib/deploy/security-gates.test.js +9 -2
- package/server/lib/deploy-engine.js +182 -0
- package/server/lib/deploy-engine.test.js +147 -0
- package/server/lib/docker-api.js +137 -0
- package/server/lib/docker-api.test.js +202 -0
- package/server/lib/docker-client.js +297 -0
- package/server/lib/docker-client.test.js +308 -0
- package/server/lib/embedding-client.js +160 -0
- package/server/lib/embedding-client.test.js +243 -0
- package/server/lib/global-config.js +198 -0
- package/server/lib/global-config.test.js +288 -0
- package/server/lib/inherited-search.js +184 -0
- package/server/lib/inherited-search.test.js +343 -0
- package/server/lib/input-sanitizer.js +86 -0
- package/server/lib/input-sanitizer.test.js +117 -0
- package/server/lib/launchd-agent.js +225 -0
- package/server/lib/launchd-agent.test.js +185 -0
- package/server/lib/memory-api.js +182 -0
- package/server/lib/memory-api.test.js +320 -0
- package/server/lib/memory-bridge-e2e.test.js +160 -0
- package/server/lib/memory-committer.js +18 -4
- package/server/lib/memory-committer.test.js +21 -0
- package/server/lib/memory-hooks-capture.test.js +415 -0
- package/server/lib/memory-hooks-integration.test.js +98 -0
- package/server/lib/memory-hooks.js +139 -0
- package/server/lib/memory-inheritance.js +179 -0
- package/server/lib/memory-inheritance.test.js +360 -0
- package/server/lib/memory-store-adapter.js +105 -0
- package/server/lib/memory-store-adapter.test.js +141 -0
- package/server/lib/memory-wiring-e2e.test.js +93 -0
- package/server/lib/nginx-config.js +114 -0
- package/server/lib/nginx-config.test.js +82 -0
- package/server/lib/ollama-health.js +91 -0
- package/server/lib/ollama-health.test.js +74 -0
- package/server/lib/plan-writer.js +196 -0
- package/server/lib/plan-writer.test.js +298 -0
- package/server/lib/port-guard.js +44 -0
- package/server/lib/port-guard.test.js +65 -0
- package/server/lib/project-scanner.js +302 -0
- package/server/lib/project-scanner.test.js +541 -0
- package/server/lib/project-status.js +302 -0
- package/server/lib/project-status.test.js +470 -0
- package/server/lib/projects-registry.js +237 -0
- package/server/lib/projects-registry.test.js +275 -0
- package/server/lib/recall-command.js +207 -0
- package/server/lib/recall-command.test.js +306 -0
- package/server/lib/remember-command.js +98 -0
- package/server/lib/remember-command.test.js +288 -0
- package/server/lib/rich-capture.js +221 -0
- package/server/lib/rich-capture.test.js +312 -0
- package/server/lib/roadmap-api.js +200 -0
- package/server/lib/roadmap-api.test.js +318 -0
- package/server/lib/security/crypto-utils.test.js +2 -2
- package/server/lib/semantic-recall.js +242 -0
- package/server/lib/semantic-recall.test.js +463 -0
- package/server/lib/setup-generator.js +315 -0
- package/server/lib/setup-generator.test.js +303 -0
- package/server/lib/ssh-client.js +184 -0
- package/server/lib/ssh-client.test.js +127 -0
- package/server/lib/test-inventory.js +112 -0
- package/server/lib/test-inventory.test.js +360 -0
- package/server/lib/vector-indexer.js +246 -0
- package/server/lib/vector-indexer.test.js +459 -0
- package/server/lib/vector-store.js +260 -0
- package/server/lib/vector-store.test.js +706 -0
- package/server/lib/vps-api.js +184 -0
- package/server/lib/vps-api.test.js +208 -0
- package/server/lib/vps-bootstrap.js +124 -0
- package/server/lib/vps-bootstrap.test.js +79 -0
- package/server/lib/vps-monitor.js +126 -0
- package/server/lib/vps-monitor.test.js +98 -0
- package/server/lib/workspace-api.js +992 -0
- package/server/lib/workspace-api.test.js +1217 -0
- package/server/lib/workspace-bootstrap.js +164 -0
- package/server/lib/workspace-bootstrap.test.js +503 -0
- package/server/lib/workspace-context.js +129 -0
- package/server/lib/workspace-context.test.js +214 -0
- package/server/lib/workspace-detector.js +162 -0
- package/server/lib/workspace-detector.test.js +193 -0
- package/server/lib/workspace-init.js +307 -0
- package/server/lib/workspace-init.test.js +244 -0
- package/server/lib/workspace-snapshot.js +236 -0
- package/server/lib/workspace-snapshot.test.js +444 -0
- package/server/lib/workspace-watcher.js +162 -0
- package/server/lib/workspace-watcher.test.js +257 -0
- package/server/package-lock.json +1306 -17
- package/server/package.json +7 -0
- package/dashboard-web/dist/assets/index-B1I_joSL.js +0 -393
- package/dashboard-web/dist/assets/index-B1I_joSL.js.map +0 -1
- package/dashboard-web/dist/assets/index-Trhg1C1Y.css +0 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Scanner - Recursively discovers TLC projects within configured root paths
|
|
3
|
+
*
|
|
4
|
+
* Scans directory trees looking for:
|
|
5
|
+
* - TLC projects (.tlc.json present)
|
|
6
|
+
* - Planning-only projects (.planning/ directory present)
|
|
7
|
+
* - Candidate projects (package.json + .git/ present, not yet initialized with TLC)
|
|
8
|
+
*
|
|
9
|
+
* Returns structured project metadata including phase info from ROADMAP.md.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Directories to skip during recursive scanning
|
|
17
|
+
*/
|
|
18
|
+
const IGNORED_DIRS = new Set([
|
|
19
|
+
'node_modules',
|
|
20
|
+
'.git',
|
|
21
|
+
'dist',
|
|
22
|
+
'build',
|
|
23
|
+
'coverage',
|
|
24
|
+
'vendor',
|
|
25
|
+
'.next',
|
|
26
|
+
'.nuxt',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse phase information from a ROADMAP.md file
|
|
31
|
+
* @param {string} roadmapPath - Absolute path to ROADMAP.md
|
|
32
|
+
* @returns {{ phase: number|null, phaseName: string|null, totalPhases: number, completedPhases: number }}
|
|
33
|
+
*/
|
|
34
|
+
function parseRoadmap(roadmapPath) {
|
|
35
|
+
const result = {
|
|
36
|
+
phase: null,
|
|
37
|
+
phaseName: null,
|
|
38
|
+
totalPhases: 0,
|
|
39
|
+
completedPhases: 0,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let content;
|
|
43
|
+
try {
|
|
44
|
+
content = fs.readFileSync(roadmapPath, 'utf-8');
|
|
45
|
+
} catch {
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Format 1: Heading format — ### Phase N: Name [x] / [ ] / [>]
|
|
50
|
+
const headingRegex = /###\s+Phase\s+(\d+)(?:\.\d+)?[:\s]+(.+?)\s*\[([x >])\]\s*$/gm;
|
|
51
|
+
let headingMatch;
|
|
52
|
+
let foundHeadings = false;
|
|
53
|
+
let firstIncomplete = null;
|
|
54
|
+
|
|
55
|
+
while ((headingMatch = headingRegex.exec(content)) !== null) {
|
|
56
|
+
foundHeadings = true;
|
|
57
|
+
result.totalPhases++;
|
|
58
|
+
const phaseNum = parseInt(headingMatch[1], 10);
|
|
59
|
+
const phaseName = headingMatch[2].trim();
|
|
60
|
+
const marker = headingMatch[3];
|
|
61
|
+
|
|
62
|
+
if (marker === 'x') {
|
|
63
|
+
result.completedPhases++;
|
|
64
|
+
} else if (!firstIncomplete) {
|
|
65
|
+
firstIncomplete = { phase: phaseNum, phaseName };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (foundHeadings) {
|
|
70
|
+
if (firstIncomplete) {
|
|
71
|
+
result.phase = firstIncomplete.phase;
|
|
72
|
+
result.phaseName = firstIncomplete.phaseName;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Format 2: Table format — | N | [Name](link) | status |
|
|
78
|
+
const tableRegex = /\|\s*(\d+)\s*\|\s*\[([^\]]+)\][^|]*\|\s*(\w+)\s*\|/g;
|
|
79
|
+
let tableMatch;
|
|
80
|
+
|
|
81
|
+
while ((tableMatch = tableRegex.exec(content)) !== null) {
|
|
82
|
+
result.totalPhases++;
|
|
83
|
+
const phaseNum = parseInt(tableMatch[1], 10);
|
|
84
|
+
const phaseName = tableMatch[2].trim();
|
|
85
|
+
const status = tableMatch[3].trim().toLowerCase();
|
|
86
|
+
const completed = status === 'complete' || status === 'done' || status === 'verified';
|
|
87
|
+
|
|
88
|
+
if (completed) {
|
|
89
|
+
result.completedPhases++;
|
|
90
|
+
} else if (!firstIncomplete) {
|
|
91
|
+
firstIncomplete = { phase: phaseNum, phaseName };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (firstIncomplete) {
|
|
96
|
+
result.phase = firstIncomplete.phase;
|
|
97
|
+
result.phaseName = firstIncomplete.phaseName;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Read project metadata from a project directory
|
|
105
|
+
* @param {string} projectDir - Absolute path to the project directory
|
|
106
|
+
* @returns {object} Project metadata
|
|
107
|
+
*/
|
|
108
|
+
function readProjectMetadata(projectDir) {
|
|
109
|
+
const hasTlc = fs.existsSync(path.join(projectDir, '.tlc.json'));
|
|
110
|
+
const hasPlanning = fs.existsSync(path.join(projectDir, '.planning'));
|
|
111
|
+
|
|
112
|
+
// Read name, version, and workspaces from package.json if present
|
|
113
|
+
let name = path.basename(projectDir);
|
|
114
|
+
let version = null;
|
|
115
|
+
let isMonorepo = false;
|
|
116
|
+
let workspaces = [];
|
|
117
|
+
|
|
118
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
119
|
+
if (fs.existsSync(pkgPath)) {
|
|
120
|
+
try {
|
|
121
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
122
|
+
if (pkg.name) {
|
|
123
|
+
name = pkg.name;
|
|
124
|
+
}
|
|
125
|
+
if (pkg.version) {
|
|
126
|
+
version = pkg.version;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Detect monorepo workspaces (npm array or yarn object format)
|
|
130
|
+
let workspacePatterns = null;
|
|
131
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
132
|
+
workspacePatterns = pkg.workspaces;
|
|
133
|
+
} else if (pkg.workspaces && Array.isArray(pkg.workspaces.packages)) {
|
|
134
|
+
workspacePatterns = pkg.workspaces.packages;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (workspacePatterns) {
|
|
138
|
+
isMonorepo = true;
|
|
139
|
+
// Resolve glob patterns to actual directories
|
|
140
|
+
for (const pattern of workspacePatterns) {
|
|
141
|
+
try {
|
|
142
|
+
const globDir = path.join(projectDir, path.dirname(pattern));
|
|
143
|
+
if (fs.existsSync(globDir)) {
|
|
144
|
+
const entries = fs.readdirSync(globDir, { withFileTypes: true });
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
if (entry.isDirectory()) {
|
|
147
|
+
workspaces.push(path.join(path.dirname(pattern), entry.name));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// Ignore glob resolution errors
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Ignore malformed package.json
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Parse phase info from ROADMAP.md if .planning exists
|
|
162
|
+
let phaseInfo = { phase: null, phaseName: null, totalPhases: 0, completedPhases: 0 };
|
|
163
|
+
if (hasPlanning) {
|
|
164
|
+
const roadmapPath = path.join(projectDir, '.planning', 'ROADMAP.md');
|
|
165
|
+
if (fs.existsSync(roadmapPath)) {
|
|
166
|
+
phaseInfo = parseRoadmap(roadmapPath);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
name,
|
|
172
|
+
path: projectDir,
|
|
173
|
+
hasTlc,
|
|
174
|
+
hasPlanning,
|
|
175
|
+
version,
|
|
176
|
+
phase: phaseInfo.phase,
|
|
177
|
+
phaseName: phaseInfo.phaseName,
|
|
178
|
+
totalPhases: phaseInfo.totalPhases,
|
|
179
|
+
completedPhases: phaseInfo.completedPhases,
|
|
180
|
+
isMonorepo,
|
|
181
|
+
workspaces,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* ProjectScanner - Recursively discovers TLC projects within configured root paths
|
|
187
|
+
*/
|
|
188
|
+
class ProjectScanner {
|
|
189
|
+
/**
|
|
190
|
+
* @param {object} [options]
|
|
191
|
+
* @param {number} [options.scanDepth=5] - Maximum recursion depth
|
|
192
|
+
* @param {number} [options.cacheTTL=60000] - Cache time-to-live in milliseconds
|
|
193
|
+
*/
|
|
194
|
+
constructor(options = {}) {
|
|
195
|
+
this.scanDepth = options.scanDepth || 5;
|
|
196
|
+
this.cacheTTL = options.cacheTTL || 60000;
|
|
197
|
+
this._cache = null;
|
|
198
|
+
this._cacheTime = 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Scan root directories for TLC projects
|
|
203
|
+
* @param {string[]} roots - Array of root directory paths to scan
|
|
204
|
+
* @param {object} [options]
|
|
205
|
+
* @param {boolean} [options.force=false] - Force re-scan bypassing cache
|
|
206
|
+
* @param {function} [options.onProgress] - Progress callback receiving discovered count
|
|
207
|
+
* @returns {object[]} Array of project metadata objects, sorted by name
|
|
208
|
+
*/
|
|
209
|
+
scan(roots, options = {}) {
|
|
210
|
+
const { force = false, onProgress } = options;
|
|
211
|
+
|
|
212
|
+
// Check cache
|
|
213
|
+
if (!force && this._cache !== null) {
|
|
214
|
+
const age = Date.now() - this._cacheTime;
|
|
215
|
+
if (age < this.cacheTTL) {
|
|
216
|
+
return this._cache;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const projectsByPath = new Map();
|
|
221
|
+
|
|
222
|
+
for (const root of roots) {
|
|
223
|
+
// Check that root exists
|
|
224
|
+
if (!fs.existsSync(root)) {
|
|
225
|
+
console.warn(`ProjectScanner: root path does not exist: ${root}`);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this._scanDir(root, 0, projectsByPath, onProgress);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const projects = Array.from(projectsByPath.values());
|
|
233
|
+
projects.sort((a, b) => a.name.localeCompare(b.name));
|
|
234
|
+
|
|
235
|
+
// Update cache
|
|
236
|
+
this._cache = projects;
|
|
237
|
+
this._cacheTime = Date.now();
|
|
238
|
+
|
|
239
|
+
return projects;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Recursively scan a directory for projects
|
|
244
|
+
* @param {string} dir - Directory to scan
|
|
245
|
+
* @param {number} depth - Current recursion depth
|
|
246
|
+
* @param {Map} projectsByPath - Accumulated projects (keyed by absolute path for dedup)
|
|
247
|
+
* @param {function} [onProgress] - Progress callback
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
_scanDir(dir, depth, projectsByPath, onProgress) {
|
|
251
|
+
if (depth > this.scanDepth) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check if this directory IS a project
|
|
256
|
+
const hasTlc = fs.existsSync(path.join(dir, '.tlc.json'));
|
|
257
|
+
const hasPlanning = fs.existsSync(path.join(dir, '.planning'));
|
|
258
|
+
const hasPackageJson = fs.existsSync(path.join(dir, 'package.json'));
|
|
259
|
+
const hasGit = fs.existsSync(path.join(dir, '.git'));
|
|
260
|
+
|
|
261
|
+
const isProject = hasTlc || hasPlanning || (hasPackageJson && hasGit);
|
|
262
|
+
|
|
263
|
+
if (isProject && !projectsByPath.has(dir)) {
|
|
264
|
+
const metadata = readProjectMetadata(dir);
|
|
265
|
+
projectsByPath.set(dir, metadata);
|
|
266
|
+
|
|
267
|
+
if (typeof onProgress === 'function') {
|
|
268
|
+
onProgress(projectsByPath.size);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Stop recursion: a project's children are not separate projects
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Recurse into subdirectories (only for non-project directories)
|
|
276
|
+
let entries;
|
|
277
|
+
try {
|
|
278
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
279
|
+
} catch (err) {
|
|
280
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
281
|
+
console.warn(`ProjectScanner: permission denied reading directory: ${dir}`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (const entry of entries) {
|
|
288
|
+
if (!entry.isDirectory()) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (IGNORED_DIRS.has(entry.name)) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const childPath = path.join(dir, entry.name);
|
|
297
|
+
this._scanDir(childPath, depth + 1, projectsByPath, onProgress);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = { ProjectScanner };
|