neuronlayer 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.
- package/CONTRIBUTING.md +127 -0
- package/LICENSE +21 -0
- package/README.md +305 -0
- package/dist/index.js +38016 -0
- package/esbuild.config.js +26 -0
- package/package.json +63 -0
- package/src/cli/commands.ts +382 -0
- package/src/core/adr-exporter.ts +253 -0
- package/src/core/architecture/architecture-enforcement.ts +228 -0
- package/src/core/architecture/duplicate-detector.ts +288 -0
- package/src/core/architecture/index.ts +6 -0
- package/src/core/architecture/pattern-learner.ts +306 -0
- package/src/core/architecture/pattern-library.ts +403 -0
- package/src/core/architecture/pattern-validator.ts +324 -0
- package/src/core/change-intelligence/bug-correlator.ts +444 -0
- package/src/core/change-intelligence/change-intelligence.ts +221 -0
- package/src/core/change-intelligence/change-tracker.ts +334 -0
- package/src/core/change-intelligence/fix-suggester.ts +340 -0
- package/src/core/change-intelligence/index.ts +5 -0
- package/src/core/code-verifier.ts +843 -0
- package/src/core/confidence/confidence-scorer.ts +251 -0
- package/src/core/confidence/conflict-checker.ts +289 -0
- package/src/core/confidence/index.ts +5 -0
- package/src/core/confidence/source-tracker.ts +263 -0
- package/src/core/confidence/warning-detector.ts +241 -0
- package/src/core/context-rot/compaction.ts +284 -0
- package/src/core/context-rot/context-health.ts +243 -0
- package/src/core/context-rot/context-rot-prevention.ts +213 -0
- package/src/core/context-rot/critical-context.ts +221 -0
- package/src/core/context-rot/drift-detector.ts +255 -0
- package/src/core/context-rot/index.ts +7 -0
- package/src/core/context.ts +263 -0
- package/src/core/decision-extractor.ts +339 -0
- package/src/core/decisions.ts +69 -0
- package/src/core/deja-vu.ts +421 -0
- package/src/core/engine.ts +1455 -0
- package/src/core/feature-context.ts +726 -0
- package/src/core/ghost-mode.ts +412 -0
- package/src/core/learning.ts +485 -0
- package/src/core/living-docs/activity-tracker.ts +296 -0
- package/src/core/living-docs/architecture-generator.ts +428 -0
- package/src/core/living-docs/changelog-generator.ts +348 -0
- package/src/core/living-docs/component-generator.ts +230 -0
- package/src/core/living-docs/doc-engine.ts +110 -0
- package/src/core/living-docs/doc-validator.ts +282 -0
- package/src/core/living-docs/index.ts +8 -0
- package/src/core/project-manager.ts +297 -0
- package/src/core/summarizer.ts +267 -0
- package/src/core/test-awareness/change-validator.ts +499 -0
- package/src/core/test-awareness/index.ts +5 -0
- package/src/index.ts +49 -0
- package/src/indexing/ast.ts +563 -0
- package/src/indexing/embeddings.ts +85 -0
- package/src/indexing/indexer.ts +245 -0
- package/src/indexing/watcher.ts +78 -0
- package/src/server/gateways/aggregator.ts +374 -0
- package/src/server/gateways/index.ts +473 -0
- package/src/server/gateways/memory-ghost.ts +343 -0
- package/src/server/gateways/memory-query.ts +452 -0
- package/src/server/gateways/memory-record.ts +346 -0
- package/src/server/gateways/memory-review.ts +410 -0
- package/src/server/gateways/memory-status.ts +517 -0
- package/src/server/gateways/memory-verify.ts +392 -0
- package/src/server/gateways/router.ts +434 -0
- package/src/server/gateways/types.ts +610 -0
- package/src/server/mcp.ts +154 -0
- package/src/server/resources.ts +85 -0
- package/src/server/tools.ts +2261 -0
- package/src/storage/database.ts +262 -0
- package/src/storage/tier1.ts +135 -0
- package/src/storage/tier2.ts +764 -0
- package/src/storage/tier3.ts +123 -0
- package/src/types/documentation.ts +619 -0
- package/src/types/index.ts +222 -0
- package/src/utils/config.ts +193 -0
- package/src/utils/files.ts +117 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/tokens.ts +52 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import type Database from 'better-sqlite3';
|
|
5
|
+
import type { Tier2Storage } from '../../storage/tier2.js';
|
|
6
|
+
import type {
|
|
7
|
+
ActivityResult,
|
|
8
|
+
ActivityChange,
|
|
9
|
+
ActivityDecision
|
|
10
|
+
} from '../../types/documentation.js';
|
|
11
|
+
|
|
12
|
+
export class ActivityTracker {
|
|
13
|
+
private projectPath: string;
|
|
14
|
+
private db: Database.Database;
|
|
15
|
+
private tier2: Tier2Storage;
|
|
16
|
+
private isGitRepo: boolean;
|
|
17
|
+
|
|
18
|
+
constructor(projectPath: string, db: Database.Database, tier2: Tier2Storage) {
|
|
19
|
+
this.projectPath = projectPath;
|
|
20
|
+
this.db = db;
|
|
21
|
+
this.tier2 = tier2;
|
|
22
|
+
this.isGitRepo = existsSync(join(projectPath, '.git'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async whatHappened(since: string, scope?: string): Promise<ActivityResult> {
|
|
26
|
+
const sinceDate = this.parseSinceString(since);
|
|
27
|
+
const untilDate = new Date();
|
|
28
|
+
|
|
29
|
+
const gitActivity = this.getGitActivity(sinceDate, untilDate, scope);
|
|
30
|
+
const decisions = this.getDecisionActivity(sinceDate, untilDate);
|
|
31
|
+
const filesAffected = this.getAffectedFiles(gitActivity);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
timeRange: { since: sinceDate, until: untilDate },
|
|
35
|
+
scope: scope || 'all',
|
|
36
|
+
summary: this.generateSummary(gitActivity, decisions),
|
|
37
|
+
changes: gitActivity,
|
|
38
|
+
decisions,
|
|
39
|
+
filesAffected
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
logActivity(
|
|
44
|
+
activityType: string,
|
|
45
|
+
description: string,
|
|
46
|
+
filePath?: string,
|
|
47
|
+
metadata?: Record<string, unknown>,
|
|
48
|
+
commitHash?: string
|
|
49
|
+
): void {
|
|
50
|
+
try {
|
|
51
|
+
const stmt = this.db.prepare(`
|
|
52
|
+
INSERT INTO activity_log (activity_type, description, file_path, metadata, commit_hash)
|
|
53
|
+
VALUES (?, ?, ?, ?, ?)
|
|
54
|
+
`);
|
|
55
|
+
stmt.run(
|
|
56
|
+
activityType,
|
|
57
|
+
description,
|
|
58
|
+
filePath || null,
|
|
59
|
+
metadata ? JSON.stringify(metadata) : null,
|
|
60
|
+
commitHash || null
|
|
61
|
+
);
|
|
62
|
+
} catch {
|
|
63
|
+
// Ignore logging errors
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private parseSinceString(since: string): Date {
|
|
68
|
+
const now = new Date();
|
|
69
|
+
const lower = since.toLowerCase();
|
|
70
|
+
|
|
71
|
+
if (lower === 'yesterday') {
|
|
72
|
+
const yesterday = new Date(now);
|
|
73
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
74
|
+
yesterday.setHours(0, 0, 0, 0);
|
|
75
|
+
return yesterday;
|
|
76
|
+
}
|
|
77
|
+
if (lower === 'today') {
|
|
78
|
+
const today = new Date(now);
|
|
79
|
+
today.setHours(0, 0, 0, 0);
|
|
80
|
+
return today;
|
|
81
|
+
}
|
|
82
|
+
if (lower === 'this week') {
|
|
83
|
+
const dayOfWeek = now.getDay();
|
|
84
|
+
const startOfWeek = new Date(now);
|
|
85
|
+
startOfWeek.setDate(now.getDate() - dayOfWeek);
|
|
86
|
+
startOfWeek.setHours(0, 0, 0, 0);
|
|
87
|
+
return startOfWeek;
|
|
88
|
+
}
|
|
89
|
+
if (lower === 'this month') {
|
|
90
|
+
return new Date(now.getFullYear(), now.getMonth(), 1);
|
|
91
|
+
}
|
|
92
|
+
if (lower === 'last week') {
|
|
93
|
+
const dayOfWeek = now.getDay();
|
|
94
|
+
const startOfLastWeek = new Date(now);
|
|
95
|
+
startOfLastWeek.setDate(now.getDate() - dayOfWeek - 7);
|
|
96
|
+
startOfLastWeek.setHours(0, 0, 0, 0);
|
|
97
|
+
return startOfLastWeek;
|
|
98
|
+
}
|
|
99
|
+
if (lower === 'last month') {
|
|
100
|
+
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
101
|
+
return lastMonth;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Try parsing as a date string
|
|
105
|
+
const parsed = new Date(since);
|
|
106
|
+
return isNaN(parsed.getTime()) ? new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) : parsed;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getGitActivity(since: Date, until: Date, scope?: string): ActivityChange[] {
|
|
110
|
+
const changes: ActivityChange[] = [];
|
|
111
|
+
|
|
112
|
+
if (!this.isGitRepo) {
|
|
113
|
+
return changes;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const sinceStr = since.toISOString().split('T')[0];
|
|
118
|
+
const untilStr = until.toISOString().split('T')[0];
|
|
119
|
+
|
|
120
|
+
let gitCmd = `git log --since="${sinceStr}" --until="${untilStr}" --format="%H|%s|%an|%ad" --date=iso-strict`;
|
|
121
|
+
|
|
122
|
+
// If scope is provided, filter by path
|
|
123
|
+
if (scope && scope !== 'all') {
|
|
124
|
+
gitCmd += ` -- "${scope}"`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const output = execSync(gitCmd, {
|
|
128
|
+
cwd: this.projectPath,
|
|
129
|
+
encoding: 'utf-8',
|
|
130
|
+
maxBuffer: 5 * 1024 * 1024
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const lines = output.trim().split('\n').filter(Boolean);
|
|
134
|
+
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
const [hash, subject, author, dateStr] = line.split('|');
|
|
137
|
+
if (!hash || !subject) continue;
|
|
138
|
+
|
|
139
|
+
// Get files changed in this commit
|
|
140
|
+
const files = this.getCommitFiles(hash);
|
|
141
|
+
|
|
142
|
+
changes.push({
|
|
143
|
+
timestamp: new Date(dateStr || Date.now()),
|
|
144
|
+
type: 'commit',
|
|
145
|
+
description: subject,
|
|
146
|
+
details: {
|
|
147
|
+
hash: hash.slice(0, 8),
|
|
148
|
+
author,
|
|
149
|
+
files
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// Git command failed
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Also get activity from activity_log table
|
|
158
|
+
try {
|
|
159
|
+
const sinceUnix = Math.floor(since.getTime() / 1000);
|
|
160
|
+
const untilUnix = Math.floor(until.getTime() / 1000);
|
|
161
|
+
|
|
162
|
+
let query = `
|
|
163
|
+
SELECT timestamp, activity_type, description, file_path, metadata
|
|
164
|
+
FROM activity_log
|
|
165
|
+
WHERE timestamp >= ? AND timestamp <= ?
|
|
166
|
+
`;
|
|
167
|
+
const params: (number | string)[] = [sinceUnix, untilUnix];
|
|
168
|
+
|
|
169
|
+
if (scope && scope !== 'all') {
|
|
170
|
+
query += ' AND file_path LIKE ?';
|
|
171
|
+
params.push(`%${scope}%`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
query += ' ORDER BY timestamp DESC';
|
|
175
|
+
|
|
176
|
+
const stmt = this.db.prepare(query);
|
|
177
|
+
const rows = stmt.all(...params) as Array<{
|
|
178
|
+
timestamp: number;
|
|
179
|
+
activity_type: string;
|
|
180
|
+
description: string;
|
|
181
|
+
file_path: string | null;
|
|
182
|
+
metadata: string | null;
|
|
183
|
+
}>;
|
|
184
|
+
|
|
185
|
+
for (const row of rows) {
|
|
186
|
+
changes.push({
|
|
187
|
+
timestamp: new Date(row.timestamp * 1000),
|
|
188
|
+
type: 'file_change',
|
|
189
|
+
description: row.description,
|
|
190
|
+
details: {
|
|
191
|
+
activityType: row.activity_type,
|
|
192
|
+
filePath: row.file_path,
|
|
193
|
+
...(row.metadata ? JSON.parse(row.metadata) : {})
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
// Database query failed
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Sort by timestamp descending
|
|
202
|
+
changes.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
203
|
+
|
|
204
|
+
return changes;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private getCommitFiles(hash: string): string[] {
|
|
208
|
+
try {
|
|
209
|
+
const output = execSync(
|
|
210
|
+
`git show --name-only --format="" "${hash}"`,
|
|
211
|
+
{ cwd: this.projectPath, encoding: 'utf-8', maxBuffer: 1024 * 1024 }
|
|
212
|
+
);
|
|
213
|
+
return output.trim().split('\n').filter(Boolean);
|
|
214
|
+
} catch {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private getDecisionActivity(since: Date, until: Date): ActivityDecision[] {
|
|
220
|
+
const decisions: ActivityDecision[] = [];
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const sinceUnix = Math.floor(since.getTime() / 1000);
|
|
224
|
+
const untilUnix = Math.floor(until.getTime() / 1000);
|
|
225
|
+
|
|
226
|
+
const stmt = this.db.prepare(`
|
|
227
|
+
SELECT id, title, created_at
|
|
228
|
+
FROM decisions
|
|
229
|
+
WHERE created_at >= ? AND created_at <= ?
|
|
230
|
+
ORDER BY created_at DESC
|
|
231
|
+
`);
|
|
232
|
+
|
|
233
|
+
const rows = stmt.all(sinceUnix, untilUnix) as Array<{
|
|
234
|
+
id: string;
|
|
235
|
+
title: string;
|
|
236
|
+
created_at: number;
|
|
237
|
+
}>;
|
|
238
|
+
|
|
239
|
+
for (const row of rows) {
|
|
240
|
+
decisions.push({
|
|
241
|
+
id: row.id,
|
|
242
|
+
title: row.title,
|
|
243
|
+
date: new Date(row.created_at * 1000)
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// Database query failed
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return decisions;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private getAffectedFiles(changes: ActivityChange[]): string[] {
|
|
254
|
+
const filesSet = new Set<string>();
|
|
255
|
+
|
|
256
|
+
for (const change of changes) {
|
|
257
|
+
if (change.details.files && Array.isArray(change.details.files)) {
|
|
258
|
+
for (const file of change.details.files) {
|
|
259
|
+
filesSet.add(file as string);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (change.details.filePath) {
|
|
263
|
+
filesSet.add(change.details.filePath as string);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return Array.from(filesSet).sort();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private generateSummary(changes: ActivityChange[], decisions: ActivityDecision[]): string {
|
|
271
|
+
const commits = changes.filter(c => c.type === 'commit').length;
|
|
272
|
+
const fileChanges = changes.filter(c => c.type === 'file_change').length;
|
|
273
|
+
const decisionCount = decisions.length;
|
|
274
|
+
|
|
275
|
+
const parts: string[] = [];
|
|
276
|
+
|
|
277
|
+
if (commits > 0) {
|
|
278
|
+
parts.push(`${commits} commit${commits !== 1 ? 's' : ''}`);
|
|
279
|
+
}
|
|
280
|
+
if (fileChanges > 0) {
|
|
281
|
+
parts.push(`${fileChanges} file change${fileChanges !== 1 ? 's' : ''}`);
|
|
282
|
+
}
|
|
283
|
+
if (decisionCount > 0) {
|
|
284
|
+
parts.push(`${decisionCount} decision${decisionCount !== 1 ? 's' : ''}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (parts.length === 0) {
|
|
288
|
+
return 'No activity in this time period';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Get unique files affected
|
|
292
|
+
const filesAffected = this.getAffectedFiles(changes);
|
|
293
|
+
|
|
294
|
+
return `${parts.join(', ')} affecting ${filesAffected.length} file${filesAffected.length !== 1 ? 's' : ''}`;
|
|
295
|
+
}
|
|
296
|
+
}
|