@tonycasey/lisa 0.5.13
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/README.md +42 -0
- package/dist/cli.js +390 -0
- package/dist/lib/interfaces/IDockerClient.js +2 -0
- package/dist/lib/interfaces/IMcpClient.js +2 -0
- package/dist/lib/interfaces/IServices.js +2 -0
- package/dist/lib/interfaces/ITemplateCopier.js +2 -0
- package/dist/lib/mcp.js +35 -0
- package/dist/lib/services.js +57 -0
- package/dist/package.json +36 -0
- package/dist/templates/agents/.sample.env +12 -0
- package/dist/templates/agents/docs/STORAGE_SETUP.md +161 -0
- package/dist/templates/agents/skills/common/group-id.js +193 -0
- package/dist/templates/agents/skills/init-review/SKILL.md +119 -0
- package/dist/templates/agents/skills/init-review/scripts/ai-enrich.js +258 -0
- package/dist/templates/agents/skills/init-review/scripts/init-review.js +769 -0
- package/dist/templates/agents/skills/lisa/SKILL.md +92 -0
- package/dist/templates/agents/skills/lisa/cache/.gitkeep +0 -0
- package/dist/templates/agents/skills/lisa/scripts/storage.js +374 -0
- package/dist/templates/agents/skills/memory/SKILL.md +31 -0
- package/dist/templates/agents/skills/memory/scripts/memory.js +533 -0
- package/dist/templates/agents/skills/prompt/SKILL.md +19 -0
- package/dist/templates/agents/skills/prompt/scripts/prompt.js +184 -0
- package/dist/templates/agents/skills/tasks/SKILL.md +31 -0
- package/dist/templates/agents/skills/tasks/scripts/tasks.js +489 -0
- package/dist/templates/claude/config.js +40 -0
- package/dist/templates/claude/hooks/README.md +158 -0
- package/dist/templates/claude/hooks/common/complexity-rater.js +290 -0
- package/dist/templates/claude/hooks/common/context.js +263 -0
- package/dist/templates/claude/hooks/common/group-id.js +188 -0
- package/dist/templates/claude/hooks/common/mcp-client.js +131 -0
- package/dist/templates/claude/hooks/common/transcript-parser.js +256 -0
- package/dist/templates/claude/hooks/common/zep-client.js +175 -0
- package/dist/templates/claude/hooks/session-start.js +401 -0
- package/dist/templates/claude/hooks/session-stop-worker.js +341 -0
- package/dist/templates/claude/hooks/session-stop.js +122 -0
- package/dist/templates/claude/hooks/user-prompt-submit.js +256 -0
- package/dist/templates/claude/settings.json +46 -0
- package/dist/templates/docker/.env.lisa.example +17 -0
- package/dist/templates/docker/docker-compose.graphiti.yml +45 -0
- package/dist/templates/rules/shared/clean-architecture.md +333 -0
- package/dist/templates/rules/shared/code-quality-rules.md +469 -0
- package/dist/templates/rules/shared/git-rules.md +64 -0
- package/dist/templates/rules/shared/testing-principles.md +469 -0
- package/dist/templates/rules/typescript/coding-standards.md +751 -0
- package/dist/templates/rules/typescript/testing.md +629 -0
- package/dist/templates/rules/typescript/typescript-config-guide.md +465 -0
- package/package.json +64 -0
- package/scripts/postinstall.js +710 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// Scoring constants
|
|
4
|
+
const POINTS = {
|
|
5
|
+
FILE_CREATED: 3,
|
|
6
|
+
FILE_EDITED: 2,
|
|
7
|
+
COMMAND_RUN: 1.5,
|
|
8
|
+
DOC_CREATED: 2,
|
|
9
|
+
TEST_MODIFIED: 1.5,
|
|
10
|
+
CONFIG_FILE: 1,
|
|
11
|
+
VERSION_BUMP: 2,
|
|
12
|
+
STRUCTURAL_CHANGE: 2,
|
|
13
|
+
MULTI_DIRECTORY: 3,
|
|
14
|
+
CI_CD_SETUP: 3,
|
|
15
|
+
};
|
|
16
|
+
const MULTIPLIERS = {
|
|
17
|
+
TOOL_DIVERSITY_5: 1.2,
|
|
18
|
+
TOOL_DIVERSITY_8: 1.3,
|
|
19
|
+
};
|
|
20
|
+
// Score thresholds for rating normalization
|
|
21
|
+
const THRESHOLDS = {
|
|
22
|
+
RATING_2: 2,
|
|
23
|
+
RATING_3: 5,
|
|
24
|
+
RATING_4: 10,
|
|
25
|
+
RATING_5: 20,
|
|
26
|
+
};
|
|
27
|
+
// Version files to detect version bumps
|
|
28
|
+
const VERSION_FILES = ['package.json', 'pyproject.toml', 'Cargo.toml', 'setup.py', 'version.py', 'VERSION'];
|
|
29
|
+
// Config files that indicate setup/configuration work
|
|
30
|
+
const CONFIG_PATTERNS = [
|
|
31
|
+
'tsconfig.json',
|
|
32
|
+
'tsconfig',
|
|
33
|
+
'jest.config',
|
|
34
|
+
'.eslintrc',
|
|
35
|
+
'.prettierrc',
|
|
36
|
+
'webpack.config',
|
|
37
|
+
'vite.config',
|
|
38
|
+
'rollup.config',
|
|
39
|
+
'.gitignore',
|
|
40
|
+
'.dockerignore',
|
|
41
|
+
'Dockerfile',
|
|
42
|
+
'docker-compose',
|
|
43
|
+
'.github/workflows',
|
|
44
|
+
'.gitlab-ci',
|
|
45
|
+
'Makefile',
|
|
46
|
+
'CMakeLists',
|
|
47
|
+
];
|
|
48
|
+
// CI/CD indicators
|
|
49
|
+
const CI_CD_PATTERNS = ['.github/workflows', '.gitlab-ci', 'Jenkinsfile', '.circleci', '.travis'];
|
|
50
|
+
/**
|
|
51
|
+
* Rate the complexity of work based on signals
|
|
52
|
+
*/
|
|
53
|
+
function rateComplexity(work) {
|
|
54
|
+
const signals = extractSignals(work);
|
|
55
|
+
const rawScore = calculateRawScore(signals, work);
|
|
56
|
+
const rating = normalizeToRating(rawScore);
|
|
57
|
+
const signalDescriptions = describeSignals(signals, work);
|
|
58
|
+
const summary = generateSummary(rating, signals, work);
|
|
59
|
+
return {
|
|
60
|
+
rating,
|
|
61
|
+
rawScore: Math.round(rawScore * 10) / 10,
|
|
62
|
+
signals: signalDescriptions,
|
|
63
|
+
summary,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract complexity signals from work summary
|
|
68
|
+
*/
|
|
69
|
+
function extractSignals(work) {
|
|
70
|
+
const filesModified = work.filesModified.size;
|
|
71
|
+
const filesCreated = work.filesCreated.size;
|
|
72
|
+
const allFiles = [...work.filesModified, ...work.filesCreated];
|
|
73
|
+
return {
|
|
74
|
+
filesModified,
|
|
75
|
+
filesCreated,
|
|
76
|
+
versionBump: hasVersionBump(allFiles),
|
|
77
|
+
docsCreated: countDocs(work.filesCreated),
|
|
78
|
+
testsModified: countTests(allFiles),
|
|
79
|
+
configFiles: countConfigFiles(allFiles),
|
|
80
|
+
structuralChanges: countStructuralChanges(work.commandsRun),
|
|
81
|
+
toolDiversity: work.toolsUsed.size,
|
|
82
|
+
commandCount: work.commandsRun.length,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Calculate raw score from signals
|
|
87
|
+
*/
|
|
88
|
+
function calculateRawScore(signals, work) {
|
|
89
|
+
let score = 0;
|
|
90
|
+
// Base points for file operations
|
|
91
|
+
score += signals.filesCreated * POINTS.FILE_CREATED;
|
|
92
|
+
score += signals.filesModified * POINTS.FILE_EDITED;
|
|
93
|
+
// Points for commands (weighted)
|
|
94
|
+
score += signals.commandCount * POINTS.COMMAND_RUN;
|
|
95
|
+
// Bonus points for specific types
|
|
96
|
+
score += signals.docsCreated * POINTS.DOC_CREATED;
|
|
97
|
+
score += signals.testsModified * POINTS.TEST_MODIFIED;
|
|
98
|
+
score += signals.configFiles * POINTS.CONFIG_FILE;
|
|
99
|
+
// Bonus for version bump
|
|
100
|
+
if (signals.versionBump) {
|
|
101
|
+
score += POINTS.VERSION_BUMP;
|
|
102
|
+
}
|
|
103
|
+
// Bonus for structural changes
|
|
104
|
+
score += signals.structuralChanges * POINTS.STRUCTURAL_CHANGE;
|
|
105
|
+
// Bonus for multi-directory work
|
|
106
|
+
if (hasMultiDirectoryWork(work)) {
|
|
107
|
+
score += POINTS.MULTI_DIRECTORY;
|
|
108
|
+
}
|
|
109
|
+
// Bonus for CI/CD setup
|
|
110
|
+
if (hasCICDSetup(work)) {
|
|
111
|
+
score += POINTS.CI_CD_SETUP;
|
|
112
|
+
}
|
|
113
|
+
// Apply multipliers for tool diversity
|
|
114
|
+
if (signals.toolDiversity >= 8) {
|
|
115
|
+
score *= MULTIPLIERS.TOOL_DIVERSITY_8;
|
|
116
|
+
}
|
|
117
|
+
else if (signals.toolDiversity >= 5) {
|
|
118
|
+
score *= MULTIPLIERS.TOOL_DIVERSITY_5;
|
|
119
|
+
}
|
|
120
|
+
return score;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Normalize raw score to 1-5 rating
|
|
124
|
+
*/
|
|
125
|
+
function normalizeToRating(rawScore) {
|
|
126
|
+
if (rawScore >= THRESHOLDS.RATING_5)
|
|
127
|
+
return 5;
|
|
128
|
+
if (rawScore >= THRESHOLDS.RATING_4)
|
|
129
|
+
return 4;
|
|
130
|
+
if (rawScore >= THRESHOLDS.RATING_3)
|
|
131
|
+
return 3;
|
|
132
|
+
if (rawScore >= THRESHOLDS.RATING_2)
|
|
133
|
+
return 2;
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if any version files were modified
|
|
138
|
+
*/
|
|
139
|
+
function hasVersionBump(files) {
|
|
140
|
+
return files.some((filePath) => VERSION_FILES.some((vf) => filePath.endsWith(vf) || filePath.includes(vf)));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Count documentation files created
|
|
144
|
+
*/
|
|
145
|
+
function countDocs(filesCreated) {
|
|
146
|
+
return Array.from(filesCreated).filter((filePath) => filePath.endsWith('.md') || filePath.includes('/docs/') || filePath.toUpperCase().includes('README')).length;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Count test files modified or created
|
|
150
|
+
*/
|
|
151
|
+
function countTests(files) {
|
|
152
|
+
return files.filter((filePath) => /\.(test|spec)\.(ts|js|tsx|jsx|py|rs)$/.test(filePath) ||
|
|
153
|
+
filePath.includes('/tests/') ||
|
|
154
|
+
filePath.includes('/__tests__/') ||
|
|
155
|
+
filePath.includes('/test/')).length;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Count config files modified
|
|
159
|
+
*/
|
|
160
|
+
function countConfigFiles(files) {
|
|
161
|
+
return files.filter((filePath) => CONFIG_PATTERNS.some((pattern) => filePath.includes(pattern))).length;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Count structural changes (mkdir commands)
|
|
165
|
+
*/
|
|
166
|
+
function countStructuralChanges(commands) {
|
|
167
|
+
return commands.filter((cmd) => cmd.startsWith('mkdir') || cmd.includes('mkdir ')).length;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check if work spans multiple directories
|
|
171
|
+
*/
|
|
172
|
+
function hasMultiDirectoryWork(work) {
|
|
173
|
+
const allFiles = [...work.filesModified, ...work.filesCreated];
|
|
174
|
+
const directories = new Set();
|
|
175
|
+
for (const filePath of allFiles) {
|
|
176
|
+
const parts = filePath.split('/');
|
|
177
|
+
if (parts.length > 1) {
|
|
178
|
+
directories.add(parts[0]);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return directories.size >= 3;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Check if CI/CD files were created/modified
|
|
185
|
+
*/
|
|
186
|
+
function hasCICDSetup(work) {
|
|
187
|
+
const allFiles = [...work.filesModified, ...work.filesCreated];
|
|
188
|
+
return allFiles.some((filePath) => CI_CD_PATTERNS.some((pattern) => filePath.includes(pattern)));
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Generate human-readable signal descriptions
|
|
192
|
+
*/
|
|
193
|
+
function describeSignals(signals, work) {
|
|
194
|
+
const descriptions = [];
|
|
195
|
+
if (signals.filesCreated > 0) {
|
|
196
|
+
descriptions.push(`${signals.filesCreated} file${signals.filesCreated > 1 ? 's' : ''} created`);
|
|
197
|
+
}
|
|
198
|
+
if (signals.filesModified > 0) {
|
|
199
|
+
descriptions.push(`${signals.filesModified} file${signals.filesModified > 1 ? 's' : ''} modified`);
|
|
200
|
+
}
|
|
201
|
+
if (signals.testsModified > 0) {
|
|
202
|
+
descriptions.push(`${signals.testsModified} test file${signals.testsModified > 1 ? 's' : ''}`);
|
|
203
|
+
}
|
|
204
|
+
if (signals.docsCreated > 0) {
|
|
205
|
+
descriptions.push(`${signals.docsCreated} documentation file${signals.docsCreated > 1 ? 's' : ''}`);
|
|
206
|
+
}
|
|
207
|
+
if (signals.versionBump) {
|
|
208
|
+
descriptions.push('Version bump detected');
|
|
209
|
+
}
|
|
210
|
+
if (signals.configFiles > 0) {
|
|
211
|
+
descriptions.push(`${signals.configFiles} config file${signals.configFiles > 1 ? 's' : ''}`);
|
|
212
|
+
}
|
|
213
|
+
if (signals.structuralChanges > 0) {
|
|
214
|
+
descriptions.push(`${signals.structuralChanges} structural change${signals.structuralChanges > 1 ? 's' : ''}`);
|
|
215
|
+
}
|
|
216
|
+
if (hasMultiDirectoryWork(work)) {
|
|
217
|
+
descriptions.push('Multi-directory changes');
|
|
218
|
+
}
|
|
219
|
+
if (hasCICDSetup(work)) {
|
|
220
|
+
descriptions.push('CI/CD setup');
|
|
221
|
+
}
|
|
222
|
+
if (signals.toolDiversity >= 5) {
|
|
223
|
+
descriptions.push(`${signals.toolDiversity} different tools used`);
|
|
224
|
+
}
|
|
225
|
+
return descriptions;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Generate a summary description of the work
|
|
229
|
+
*/
|
|
230
|
+
function generateSummary(rating, signals, work) {
|
|
231
|
+
const ratingLabels = {
|
|
232
|
+
1: 'Trivial',
|
|
233
|
+
2: 'Simple',
|
|
234
|
+
3: 'Moderate',
|
|
235
|
+
4: 'Complex',
|
|
236
|
+
5: 'Very Complex',
|
|
237
|
+
};
|
|
238
|
+
const label = ratingLabels[rating] || 'Unknown';
|
|
239
|
+
const totalFiles = signals.filesCreated + signals.filesModified;
|
|
240
|
+
// Generate contextual summary
|
|
241
|
+
if (rating === 1) {
|
|
242
|
+
if (totalFiles === 0) {
|
|
243
|
+
return `${label}: Read-only session or minimal interaction`;
|
|
244
|
+
}
|
|
245
|
+
return `${label}: Minor change to ${totalFiles} file${totalFiles > 1 ? 's' : ''}`;
|
|
246
|
+
}
|
|
247
|
+
if (rating === 2) {
|
|
248
|
+
return `${label}: ${totalFiles} file${totalFiles > 1 ? 's' : ''} affected`;
|
|
249
|
+
}
|
|
250
|
+
if (rating === 3) {
|
|
251
|
+
const extras = [];
|
|
252
|
+
if (signals.testsModified > 0)
|
|
253
|
+
extras.push('tests');
|
|
254
|
+
if (signals.docsCreated > 0)
|
|
255
|
+
extras.push('docs');
|
|
256
|
+
const extra = extras.length > 0 ? ` with ${extras.join(' and ')}` : '';
|
|
257
|
+
return `${label}: Feature implementation${extra}`;
|
|
258
|
+
}
|
|
259
|
+
if (rating === 4) {
|
|
260
|
+
return `${label}: Multi-file refactor or significant feature`;
|
|
261
|
+
}
|
|
262
|
+
// Rating 5
|
|
263
|
+
const highlights = [];
|
|
264
|
+
if (signals.versionBump)
|
|
265
|
+
highlights.push('version bump');
|
|
266
|
+
if (hasCICDSetup(work))
|
|
267
|
+
highlights.push('CI/CD');
|
|
268
|
+
if (signals.docsCreated >= 2)
|
|
269
|
+
highlights.push('documentation');
|
|
270
|
+
const highlight = highlights.length > 0 ? `: ${highlights.join(', ')}` : '';
|
|
271
|
+
return `${label}: Major milestone${highlight}`;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Get rating label
|
|
275
|
+
*/
|
|
276
|
+
function getRatingLabel(rating) {
|
|
277
|
+
const labels = {
|
|
278
|
+
1: 'Trivial',
|
|
279
|
+
2: 'Simple',
|
|
280
|
+
3: 'Moderate',
|
|
281
|
+
4: 'Complex',
|
|
282
|
+
5: 'Very Complex',
|
|
283
|
+
};
|
|
284
|
+
return labels[rating] || 'Unknown';
|
|
285
|
+
}
|
|
286
|
+
module.exports = {
|
|
287
|
+
rateComplexity,
|
|
288
|
+
getRatingLabel,
|
|
289
|
+
THRESHOLDS,
|
|
290
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
const ENV_FILE = '.agents/.env';
|
|
7
|
+
/**
|
|
8
|
+
* Parse a .env file into key-value pairs
|
|
9
|
+
*/
|
|
10
|
+
function parseEnvFile(content) {
|
|
11
|
+
const env = {};
|
|
12
|
+
content.split(/\r?\n/).forEach((line) => {
|
|
13
|
+
if (!line || line.startsWith('#'))
|
|
14
|
+
return;
|
|
15
|
+
const idx = line.indexOf('=');
|
|
16
|
+
if (idx === -1)
|
|
17
|
+
return;
|
|
18
|
+
env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
19
|
+
});
|
|
20
|
+
return env;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read the .agents/.env file
|
|
24
|
+
*/
|
|
25
|
+
function readEnvConfig() {
|
|
26
|
+
const envPath = path.join(process.cwd(), ENV_FILE);
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(envPath)) {
|
|
29
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
30
|
+
return parseEnvFile(content);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (_err) {
|
|
34
|
+
// File doesn't exist or is invalid
|
|
35
|
+
}
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Write or update PROJECT_NAME in .agents/.env
|
|
40
|
+
* Preserves existing entries
|
|
41
|
+
*/
|
|
42
|
+
function writeProjectName(projectName, detectedFrom) {
|
|
43
|
+
const envPath = path.join(process.cwd(), ENV_FILE);
|
|
44
|
+
const agentsDir = path.join(process.cwd(), '.agents');
|
|
45
|
+
try {
|
|
46
|
+
// Ensure .agents directory exists
|
|
47
|
+
if (!fs.existsSync(agentsDir)) {
|
|
48
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
// Read existing content
|
|
51
|
+
let lines = [];
|
|
52
|
+
if (fs.existsSync(envPath)) {
|
|
53
|
+
lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/);
|
|
54
|
+
}
|
|
55
|
+
// Check if PROJECT_NAME already exists
|
|
56
|
+
const hasProjectName = lines.some(line => line.trim().startsWith('PROJECT_NAME='));
|
|
57
|
+
if (!hasProjectName) {
|
|
58
|
+
// Add PROJECT_NAME with a comment about detection source
|
|
59
|
+
const newLines = [
|
|
60
|
+
`# Project name detected from ${detectedFrom}`,
|
|
61
|
+
`PROJECT_NAME=${projectName}`,
|
|
62
|
+
'',
|
|
63
|
+
];
|
|
64
|
+
// Prepend to existing content
|
|
65
|
+
const content = [...newLines, ...lines].join('\n');
|
|
66
|
+
fs.writeFileSync(envPath, content);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (_err) {
|
|
71
|
+
// Failed to write - continue without persisting
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Detect project name from package.json
|
|
77
|
+
*/
|
|
78
|
+
function detectFromPackageJson() {
|
|
79
|
+
try {
|
|
80
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
81
|
+
if (fs.existsSync(pkgPath)) {
|
|
82
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
83
|
+
if (pkg.name && typeof pkg.name === 'string') {
|
|
84
|
+
// Strip org scope if present (e.g., @org/package -> package)
|
|
85
|
+
return pkg.name.replace(/^@[^/]+\//, '');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (_err) {
|
|
90
|
+
// Ignore parse errors
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Detect project name from pyproject.toml (Python)
|
|
96
|
+
*/
|
|
97
|
+
function detectFromPyproject() {
|
|
98
|
+
try {
|
|
99
|
+
const pyPath = path.join(process.cwd(), 'pyproject.toml');
|
|
100
|
+
if (fs.existsSync(pyPath)) {
|
|
101
|
+
const content = fs.readFileSync(pyPath, 'utf8');
|
|
102
|
+
// Simple regex to find name in [project] or [tool.poetry] section
|
|
103
|
+
const match = content.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
|
|
104
|
+
if (match) {
|
|
105
|
+
return match[1];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (_err) {
|
|
110
|
+
// Ignore errors
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Detect project name from Cargo.toml (Rust)
|
|
116
|
+
*/
|
|
117
|
+
function detectFromCargoToml() {
|
|
118
|
+
try {
|
|
119
|
+
const cargoPath = path.join(process.cwd(), 'Cargo.toml');
|
|
120
|
+
if (fs.existsSync(cargoPath)) {
|
|
121
|
+
const content = fs.readFileSync(cargoPath, 'utf8');
|
|
122
|
+
const match = content.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
|
|
123
|
+
if (match) {
|
|
124
|
+
return match[1];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (_err) {
|
|
129
|
+
// Ignore errors
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Detect project name from go.mod (Go)
|
|
135
|
+
*/
|
|
136
|
+
function detectFromGoMod() {
|
|
137
|
+
try {
|
|
138
|
+
const goModPath = path.join(process.cwd(), 'go.mod');
|
|
139
|
+
if (fs.existsSync(goModPath)) {
|
|
140
|
+
const content = fs.readFileSync(goModPath, 'utf8');
|
|
141
|
+
// Module line like: module github.com/user/project
|
|
142
|
+
const match = content.match(/^\s*module\s+(\S+)/m);
|
|
143
|
+
if (match) {
|
|
144
|
+
// Return just the last part (project name)
|
|
145
|
+
const parts = match[1].split('/');
|
|
146
|
+
return parts[parts.length - 1];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (_err) {
|
|
151
|
+
// Ignore errors
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Detect project name using the fallback chain
|
|
157
|
+
* Returns { name, source } where source indicates where it was detected from
|
|
158
|
+
*/
|
|
159
|
+
function detectProjectName() {
|
|
160
|
+
// 1. Try package.json
|
|
161
|
+
const pkgName = detectFromPackageJson();
|
|
162
|
+
if (pkgName) {
|
|
163
|
+
return { name: pkgName, source: 'package.json' };
|
|
164
|
+
}
|
|
165
|
+
// 2. Try pyproject.toml
|
|
166
|
+
const pyName = detectFromPyproject();
|
|
167
|
+
if (pyName) {
|
|
168
|
+
return { name: pyName, source: 'pyproject.toml' };
|
|
169
|
+
}
|
|
170
|
+
// 3. Try Cargo.toml
|
|
171
|
+
const cargoName = detectFromCargoToml();
|
|
172
|
+
if (cargoName) {
|
|
173
|
+
return { name: cargoName, source: 'Cargo.toml' };
|
|
174
|
+
}
|
|
175
|
+
// 4. Try go.mod
|
|
176
|
+
const goName = detectFromGoMod();
|
|
177
|
+
if (goName) {
|
|
178
|
+
return { name: goName, source: 'go.mod' };
|
|
179
|
+
}
|
|
180
|
+
// 5. Fallback to folder name
|
|
181
|
+
return { name: path.basename(process.cwd()), source: 'folder' };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get the project name, initializing .agents/.env if needed
|
|
185
|
+
*/
|
|
186
|
+
function detectRepo() {
|
|
187
|
+
// 1. Check for PROJECT_NAME in .agents/.env
|
|
188
|
+
const env = readEnvConfig();
|
|
189
|
+
if (env.PROJECT_NAME) {
|
|
190
|
+
return env.PROJECT_NAME;
|
|
191
|
+
}
|
|
192
|
+
// 2. Detect from config files and auto-initialize
|
|
193
|
+
const detected = detectProjectName();
|
|
194
|
+
writeProjectName(detected.name, detected.source);
|
|
195
|
+
return detected.name;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get project aliases for querying (includes current name and all aliases)
|
|
199
|
+
*/
|
|
200
|
+
function getProjectAliases() {
|
|
201
|
+
const env = readEnvConfig();
|
|
202
|
+
const projectName = env.PROJECT_NAME || detectRepo();
|
|
203
|
+
const folderName = path.basename(process.cwd());
|
|
204
|
+
const aliases = new Set();
|
|
205
|
+
aliases.add(projectName);
|
|
206
|
+
// Add explicit aliases from PROJECT_ALIASES
|
|
207
|
+
if (env.PROJECT_ALIASES) {
|
|
208
|
+
env.PROJECT_ALIASES.split(',').forEach(alias => {
|
|
209
|
+
const trimmed = alias.trim();
|
|
210
|
+
if (trimmed)
|
|
211
|
+
aliases.add(trimmed);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// Auto-include folder name if different from project name
|
|
215
|
+
if (folderName !== projectName) {
|
|
216
|
+
aliases.add(folderName);
|
|
217
|
+
}
|
|
218
|
+
return Array.from(aliases);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get the env file path
|
|
222
|
+
*/
|
|
223
|
+
function getEnvPath() {
|
|
224
|
+
return path.join(process.cwd(), ENV_FILE);
|
|
225
|
+
}
|
|
226
|
+
function detectBranch() {
|
|
227
|
+
try {
|
|
228
|
+
const out = execSync('git rev-parse --abbrev-ref HEAD', { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
229
|
+
return out.toString().trim();
|
|
230
|
+
}
|
|
231
|
+
catch (_err) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function repoTags({ repo, branch } = {}) {
|
|
236
|
+
const tags = [];
|
|
237
|
+
if (repo)
|
|
238
|
+
tags.push(`repo:${repo}`);
|
|
239
|
+
if (branch)
|
|
240
|
+
tags.push(`branch:${branch}`);
|
|
241
|
+
return tags;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Generate repo tags for all aliases (for querying)
|
|
245
|
+
*/
|
|
246
|
+
function repoTagsWithAliases({ branch } = {}) {
|
|
247
|
+
const aliases = getProjectAliases();
|
|
248
|
+
return aliases.map(alias => repoTags({ repo: alias, branch }));
|
|
249
|
+
}
|
|
250
|
+
function getUserName() {
|
|
251
|
+
return process.env.USER || process.env.USERNAME || 'unknown';
|
|
252
|
+
}
|
|
253
|
+
module.exports = {
|
|
254
|
+
detectRepo,
|
|
255
|
+
detectBranch,
|
|
256
|
+
repoTags,
|
|
257
|
+
repoTagsWithAliases,
|
|
258
|
+
getProjectAliases,
|
|
259
|
+
getEnvPath,
|
|
260
|
+
getUserName,
|
|
261
|
+
readEnvConfig,
|
|
262
|
+
ENV_FILE,
|
|
263
|
+
};
|