tlc-claude-code 1.3.0 → 1.4.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.
- package/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +0 -16
- package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Summary Generator Module
|
|
3
|
+
* Generates "What does this repo do" one-pager summaries
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Directories to ignore when scanning
|
|
11
|
+
*/
|
|
12
|
+
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.nuxt'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Common entry point file names
|
|
16
|
+
*/
|
|
17
|
+
const ENTRY_POINT_FILES = ['index.js', 'index.ts', 'main.js', 'main.ts', 'app.js', 'app.ts'];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Service Summary Generator class
|
|
21
|
+
*/
|
|
22
|
+
class ServiceSummaryGenerator {
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} repoPath - Path to the repository
|
|
25
|
+
*/
|
|
26
|
+
constructor(repoPath) {
|
|
27
|
+
this.repoPath = repoPath;
|
|
28
|
+
this.workspaceContext = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Set workspace context for dependency information
|
|
33
|
+
* @param {Object} context - Workspace context with getDependents/getDependencies methods
|
|
34
|
+
*/
|
|
35
|
+
setWorkspaceContext(context) {
|
|
36
|
+
this.workspaceContext = context;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Read package.json if it exists
|
|
41
|
+
* @returns {Object|null} Parsed package.json or null
|
|
42
|
+
*/
|
|
43
|
+
readPackageJson() {
|
|
44
|
+
const pkgPath = path.join(this.repoPath, 'package.json');
|
|
45
|
+
try {
|
|
46
|
+
if (fs.existsSync(pkgPath)) {
|
|
47
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
// Ignore parse errors
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Read README.md if it exists
|
|
57
|
+
* @returns {string|null} README content or null
|
|
58
|
+
*/
|
|
59
|
+
readReadme() {
|
|
60
|
+
const readmePaths = ['README.md', 'readme.md', 'Readme.md', 'README.markdown'];
|
|
61
|
+
for (const name of readmePaths) {
|
|
62
|
+
const readmePath = path.join(this.repoPath, name);
|
|
63
|
+
try {
|
|
64
|
+
if (fs.existsSync(readmePath)) {
|
|
65
|
+
return fs.readFileSync(readmePath, 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// Ignore read errors
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract purpose/description from package.json or README
|
|
76
|
+
* @returns {string} Purpose description
|
|
77
|
+
*/
|
|
78
|
+
extractPurpose() {
|
|
79
|
+
const pkg = this.readPackageJson();
|
|
80
|
+
|
|
81
|
+
// First try package.json description
|
|
82
|
+
if (pkg?.description) {
|
|
83
|
+
return pkg.description;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Fall back to README first paragraph
|
|
87
|
+
const readme = this.readReadme();
|
|
88
|
+
if (readme) {
|
|
89
|
+
const firstParagraph = this.extractFirstParagraph(readme);
|
|
90
|
+
if (firstParagraph) {
|
|
91
|
+
return firstParagraph;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Extract first meaningful paragraph from markdown
|
|
100
|
+
* @param {string} markdown - Markdown content
|
|
101
|
+
* @returns {string} First paragraph text
|
|
102
|
+
*/
|
|
103
|
+
extractFirstParagraph(markdown) {
|
|
104
|
+
const lines = markdown.split('\n');
|
|
105
|
+
let paragraph = [];
|
|
106
|
+
let foundHeading = false;
|
|
107
|
+
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
const trimmed = line.trim();
|
|
110
|
+
|
|
111
|
+
// Skip initial title/heading
|
|
112
|
+
if (trimmed.startsWith('#')) {
|
|
113
|
+
foundHeading = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Skip empty lines before first paragraph
|
|
118
|
+
if (!foundHeading && !trimmed) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Skip empty lines after heading
|
|
123
|
+
if (foundHeading && !trimmed && paragraph.length === 0) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// End of paragraph
|
|
128
|
+
if (!trimmed && paragraph.length > 0) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Skip subsequent headings
|
|
133
|
+
if (trimmed.startsWith('#') && paragraph.length > 0) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Collect paragraph content
|
|
138
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
139
|
+
paragraph.push(trimmed);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return paragraph.join(' ');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Identify main entry points
|
|
148
|
+
* @returns {Array} Array of entry point objects
|
|
149
|
+
*/
|
|
150
|
+
identifyMainEntryPoints() {
|
|
151
|
+
const entryPoints = [];
|
|
152
|
+
const pkg = this.readPackageJson();
|
|
153
|
+
|
|
154
|
+
// Check package.json main field
|
|
155
|
+
if (pkg?.main) {
|
|
156
|
+
const mainPath = path.join(this.repoPath, pkg.main);
|
|
157
|
+
if (fs.existsSync(mainPath)) {
|
|
158
|
+
entryPoints.push({
|
|
159
|
+
file: pkg.main.replace(/^\.\//, ''),
|
|
160
|
+
type: 'main',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check for common entry point files (if main not specified)
|
|
166
|
+
if (!pkg?.main) {
|
|
167
|
+
for (const file of ENTRY_POINT_FILES) {
|
|
168
|
+
const filePath = path.join(this.repoPath, file);
|
|
169
|
+
if (fs.existsSync(filePath)) {
|
|
170
|
+
entryPoints.push({
|
|
171
|
+
file,
|
|
172
|
+
type: 'main',
|
|
173
|
+
});
|
|
174
|
+
break; // Only add first found
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Check in src directory
|
|
178
|
+
const srcPath = path.join(this.repoPath, 'src', file);
|
|
179
|
+
if (fs.existsSync(srcPath)) {
|
|
180
|
+
entryPoints.push({
|
|
181
|
+
file: `src/${file}`,
|
|
182
|
+
type: 'main',
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check for bin entry points
|
|
190
|
+
if (pkg?.bin) {
|
|
191
|
+
const bins = typeof pkg.bin === 'string'
|
|
192
|
+
? { [pkg.name]: pkg.bin }
|
|
193
|
+
: pkg.bin;
|
|
194
|
+
|
|
195
|
+
for (const [name, binPath] of Object.entries(bins)) {
|
|
196
|
+
const normalizedPath = binPath.replace(/^\.\//, '');
|
|
197
|
+
const fullPath = path.join(this.repoPath, normalizedPath);
|
|
198
|
+
if (fs.existsSync(fullPath)) {
|
|
199
|
+
entryPoints.push({
|
|
200
|
+
file: normalizedPath,
|
|
201
|
+
type: 'bin',
|
|
202
|
+
name,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return entryPoints;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* List exported functions/classes from main entry point
|
|
213
|
+
* @returns {Array} Array of export names
|
|
214
|
+
*/
|
|
215
|
+
listExports() {
|
|
216
|
+
const exports = [];
|
|
217
|
+
const entryPoints = this.identifyMainEntryPoints();
|
|
218
|
+
const pkg = this.readPackageJson();
|
|
219
|
+
|
|
220
|
+
// Determine which file to analyze
|
|
221
|
+
let entryFile = null;
|
|
222
|
+
|
|
223
|
+
if (pkg?.main) {
|
|
224
|
+
entryFile = path.join(this.repoPath, pkg.main);
|
|
225
|
+
} else {
|
|
226
|
+
// Try common entry points
|
|
227
|
+
for (const file of ENTRY_POINT_FILES) {
|
|
228
|
+
const filePath = path.join(this.repoPath, file);
|
|
229
|
+
if (fs.existsSync(filePath)) {
|
|
230
|
+
entryFile = filePath;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!entryFile || !fs.existsSync(entryFile)) {
|
|
237
|
+
return exports;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const content = fs.readFileSync(entryFile, 'utf-8');
|
|
242
|
+
|
|
243
|
+
// Detect CommonJS exports: module.exports = { name1, name2 }
|
|
244
|
+
const cjsMatch = content.match(/module\.exports\s*=\s*\{([^}]+)\}/);
|
|
245
|
+
if (cjsMatch) {
|
|
246
|
+
const exportList = cjsMatch[1];
|
|
247
|
+
const names = exportList.match(/\b([a-zA-Z_]\w*)\b/g);
|
|
248
|
+
if (names) {
|
|
249
|
+
exports.push(...names.filter(n => n !== 'exports' && n !== 'module'));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Detect ES module exports: export function name() {}
|
|
254
|
+
const esExportFnRegex = /export\s+(?:async\s+)?function\s+(\w+)/g;
|
|
255
|
+
let match;
|
|
256
|
+
while ((match = esExportFnRegex.exec(content)) !== null) {
|
|
257
|
+
if (!exports.includes(match[1])) {
|
|
258
|
+
exports.push(match[1]);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Detect ES module class exports: export class Name {}
|
|
263
|
+
const esExportClassRegex = /export\s+(?:default\s+)?class\s+(\w+)/g;
|
|
264
|
+
while ((match = esExportClassRegex.exec(content)) !== null) {
|
|
265
|
+
if (!exports.includes(match[1])) {
|
|
266
|
+
exports.push(match[1]);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Detect ES module const exports: export const NAME = ...
|
|
271
|
+
const esExportConstRegex = /export\s+const\s+(\w+)\s*=/g;
|
|
272
|
+
while ((match = esExportConstRegex.exec(content)) !== null) {
|
|
273
|
+
if (!exports.includes(match[1])) {
|
|
274
|
+
exports.push(match[1]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Detect default class: export default class Name {}
|
|
279
|
+
const defaultClassRegex = /export\s+default\s+class\s+(\w+)/g;
|
|
280
|
+
while ((match = defaultClassRegex.exec(content)) !== null) {
|
|
281
|
+
if (!exports.includes(match[1])) {
|
|
282
|
+
exports.push(match[1]);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
} catch (err) {
|
|
287
|
+
// Ignore read errors
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return exports;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Get repos that consume/depend on this repo
|
|
295
|
+
* @returns {Array} Array of consumer repo names
|
|
296
|
+
*/
|
|
297
|
+
getConsumerRepos() {
|
|
298
|
+
if (!this.workspaceContext?.getDependents) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const pkg = this.readPackageJson();
|
|
303
|
+
const repoName = path.basename(this.repoPath);
|
|
304
|
+
|
|
305
|
+
return this.workspaceContext.getDependents(repoName) || [];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get repos that this repo depends on
|
|
310
|
+
* @returns {Array} Array of dependency repo names
|
|
311
|
+
*/
|
|
312
|
+
getDependencyRepos() {
|
|
313
|
+
if (!this.workspaceContext?.getDependencies) {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const repoName = path.basename(this.repoPath);
|
|
318
|
+
|
|
319
|
+
return this.workspaceContext.getDependencies(repoName) || [];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Analyze file structure to infer service type
|
|
324
|
+
* @returns {Object} Analysis result with type and indicators
|
|
325
|
+
*/
|
|
326
|
+
analyzeFileStructure() {
|
|
327
|
+
const indicators = [];
|
|
328
|
+
let type = 'unknown';
|
|
329
|
+
const pkg = this.readPackageJson();
|
|
330
|
+
|
|
331
|
+
// Check for bin entry (CLI tool)
|
|
332
|
+
if (pkg?.bin) {
|
|
333
|
+
indicators.push('bin entry');
|
|
334
|
+
type = 'cli';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check for routes directory (API service)
|
|
338
|
+
const routesDirs = [
|
|
339
|
+
path.join(this.repoPath, 'routes'),
|
|
340
|
+
path.join(this.repoPath, 'src', 'routes'),
|
|
341
|
+
path.join(this.repoPath, 'api'),
|
|
342
|
+
path.join(this.repoPath, 'src', 'api'),
|
|
343
|
+
];
|
|
344
|
+
for (const routesDir of routesDirs) {
|
|
345
|
+
if (fs.existsSync(routesDir) && fs.statSync(routesDir).isDirectory()) {
|
|
346
|
+
indicators.push('routes directory');
|
|
347
|
+
type = 'api';
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Check for components directory (web app)
|
|
353
|
+
const componentsDirs = [
|
|
354
|
+
path.join(this.repoPath, 'components'),
|
|
355
|
+
path.join(this.repoPath, 'src', 'components'),
|
|
356
|
+
];
|
|
357
|
+
for (const compDir of componentsDirs) {
|
|
358
|
+
if (fs.existsSync(compDir) && fs.statSync(compDir).isDirectory()) {
|
|
359
|
+
indicators.push('components directory');
|
|
360
|
+
if (type === 'unknown') {
|
|
361
|
+
type = 'web-app';
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Check for library pattern (src with index exports)
|
|
368
|
+
if (type === 'unknown') {
|
|
369
|
+
const srcDir = path.join(this.repoPath, 'src');
|
|
370
|
+
if (fs.existsSync(srcDir) && fs.statSync(srcDir).isDirectory()) {
|
|
371
|
+
for (const file of ['index.js', 'index.ts']) {
|
|
372
|
+
if (fs.existsSync(path.join(srcDir, file))) {
|
|
373
|
+
indicators.push('src directory with index');
|
|
374
|
+
type = 'library';
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Check root index for library pattern
|
|
382
|
+
if (type === 'unknown') {
|
|
383
|
+
for (const file of ['index.js', 'index.ts']) {
|
|
384
|
+
if (fs.existsSync(path.join(this.repoPath, file))) {
|
|
385
|
+
indicators.push('root index file');
|
|
386
|
+
type = 'library';
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return { type, indicators };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Generate the full service summary
|
|
397
|
+
* @returns {string} Complete markdown summary
|
|
398
|
+
*/
|
|
399
|
+
generate() {
|
|
400
|
+
const pkg = this.readPackageJson();
|
|
401
|
+
const name = pkg?.name || path.basename(this.repoPath);
|
|
402
|
+
const purpose = this.extractPurpose();
|
|
403
|
+
const entryPoints = this.identifyMainEntryPoints();
|
|
404
|
+
const exports = this.listExports();
|
|
405
|
+
const consumers = this.getConsumerRepos();
|
|
406
|
+
const dependencies = this.getDependencyRepos();
|
|
407
|
+
const analysis = this.analyzeFileStructure();
|
|
408
|
+
|
|
409
|
+
const sections = [];
|
|
410
|
+
|
|
411
|
+
// Title
|
|
412
|
+
sections.push(`# ${name}`);
|
|
413
|
+
sections.push('');
|
|
414
|
+
|
|
415
|
+
// Overview section
|
|
416
|
+
sections.push('## Overview');
|
|
417
|
+
sections.push('');
|
|
418
|
+
|
|
419
|
+
if (purpose) {
|
|
420
|
+
sections.push(purpose);
|
|
421
|
+
sections.push('');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (analysis.type !== 'unknown') {
|
|
425
|
+
sections.push(`**Type:** ${this.formatType(analysis.type)}`);
|
|
426
|
+
sections.push('');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (pkg?.version) {
|
|
430
|
+
sections.push(`**Version:** ${pkg.version}`);
|
|
431
|
+
sections.push('');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Entry Points section
|
|
435
|
+
if (entryPoints.length > 0) {
|
|
436
|
+
sections.push('## Entry Points');
|
|
437
|
+
sections.push('');
|
|
438
|
+
for (const entry of entryPoints) {
|
|
439
|
+
if (entry.type === 'bin') {
|
|
440
|
+
sections.push(`- \`${entry.file}\` (CLI: \`${entry.name}\`)`);
|
|
441
|
+
} else {
|
|
442
|
+
sections.push(`- \`${entry.file}\``);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
sections.push('');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Exports section
|
|
449
|
+
if (exports.length > 0) {
|
|
450
|
+
sections.push('## Exports');
|
|
451
|
+
sections.push('');
|
|
452
|
+
for (const exp of exports) {
|
|
453
|
+
sections.push(`- \`${exp}\``);
|
|
454
|
+
}
|
|
455
|
+
sections.push('');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Consumers section
|
|
459
|
+
if (consumers.length > 0) {
|
|
460
|
+
sections.push('## Consumers');
|
|
461
|
+
sections.push('');
|
|
462
|
+
sections.push('The following repos depend on this service:');
|
|
463
|
+
sections.push('');
|
|
464
|
+
for (const consumer of consumers) {
|
|
465
|
+
sections.push(`- ${consumer}`);
|
|
466
|
+
}
|
|
467
|
+
sections.push('');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Dependencies section
|
|
471
|
+
if (dependencies.length > 0) {
|
|
472
|
+
sections.push('## Dependencies');
|
|
473
|
+
sections.push('');
|
|
474
|
+
sections.push('This service depends on:');
|
|
475
|
+
sections.push('');
|
|
476
|
+
for (const dep of dependencies) {
|
|
477
|
+
sections.push(`- ${dep}`);
|
|
478
|
+
}
|
|
479
|
+
sections.push('');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return sections.join('\n').trim() + '\n';
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Format type for display
|
|
487
|
+
* @param {string} type - Type identifier
|
|
488
|
+
* @returns {string} Formatted type name
|
|
489
|
+
*/
|
|
490
|
+
formatType(type) {
|
|
491
|
+
const typeNames = {
|
|
492
|
+
'api': 'API Service',
|
|
493
|
+
'cli': 'CLI Tool',
|
|
494
|
+
'library': 'Library',
|
|
495
|
+
'web-app': 'Web Application',
|
|
496
|
+
'unknown': 'Unknown',
|
|
497
|
+
};
|
|
498
|
+
return typeNames[type] || type;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Write summary to disk
|
|
503
|
+
* @param {string} outputPath - Custom output path (defaults to SERVICE-SUMMARY.md)
|
|
504
|
+
*/
|
|
505
|
+
write(outputPath) {
|
|
506
|
+
const summary = this.generate();
|
|
507
|
+
const target = outputPath || path.join(this.repoPath, 'SERVICE-SUMMARY.md');
|
|
508
|
+
fs.writeFileSync(target, summary, 'utf-8');
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Convenience function to generate service summary for a path
|
|
514
|
+
* @param {string} repoPath - Path to repository
|
|
515
|
+
* @param {Object} workspaceContext - Optional workspace context
|
|
516
|
+
* @returns {string} Generated summary content
|
|
517
|
+
*/
|
|
518
|
+
function generateServiceSummary(repoPath, workspaceContext) {
|
|
519
|
+
const generator = new ServiceSummaryGenerator(repoPath);
|
|
520
|
+
if (workspaceContext) {
|
|
521
|
+
generator.setWorkspaceContext(workspaceContext);
|
|
522
|
+
}
|
|
523
|
+
return generator.generate();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Factory function to create a service summary generator
|
|
528
|
+
* @param {string} repoPath - Path to repository
|
|
529
|
+
* @returns {Object} Generator instance with bound methods
|
|
530
|
+
*/
|
|
531
|
+
function createServiceSummaryGenerator(repoPath) {
|
|
532
|
+
const generator = new ServiceSummaryGenerator(repoPath);
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
generate: () => generator.generate(),
|
|
536
|
+
write: (outputPath) => generator.write(outputPath),
|
|
537
|
+
extractPurpose: () => generator.extractPurpose(),
|
|
538
|
+
identifyMainEntryPoints: () => generator.identifyMainEntryPoints(),
|
|
539
|
+
listExports: () => generator.listExports(),
|
|
540
|
+
getConsumerRepos: () => generator.getConsumerRepos(),
|
|
541
|
+
getDependencyRepos: () => generator.getDependencyRepos(),
|
|
542
|
+
analyzeFileStructure: () => generator.analyzeFileStructure(),
|
|
543
|
+
setWorkspaceContext: (ctx) => generator.setWorkspaceContext(ctx),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
module.exports = {
|
|
548
|
+
ServiceSummaryGenerator,
|
|
549
|
+
generateServiceSummary,
|
|
550
|
+
createServiceSummaryGenerator,
|
|
551
|
+
IGNORE_DIRS,
|
|
552
|
+
ENTRY_POINT_FILES,
|
|
553
|
+
};
|