neuronlayer 0.1.9 → 0.2.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.

Potentially problematic release.


This version of neuronlayer might be problematic. Click here for more details.

Files changed (81) hide show
  1. package/README.md +3 -2
  2. package/dist/index.js +172 -90
  3. package/dist/index.js.map +7 -0
  4. package/package.json +6 -1
  5. package/esbuild.config.js +0 -26
  6. package/src/cli/commands.ts +0 -573
  7. package/src/core/adr-exporter.ts +0 -253
  8. package/src/core/architecture/architecture-enforcement.ts +0 -228
  9. package/src/core/architecture/duplicate-detector.ts +0 -288
  10. package/src/core/architecture/index.ts +0 -6
  11. package/src/core/architecture/pattern-learner.ts +0 -306
  12. package/src/core/architecture/pattern-library.ts +0 -403
  13. package/src/core/architecture/pattern-validator.ts +0 -324
  14. package/src/core/change-intelligence/bug-correlator.ts +0 -544
  15. package/src/core/change-intelligence/change-intelligence.ts +0 -264
  16. package/src/core/change-intelligence/change-tracker.ts +0 -334
  17. package/src/core/change-intelligence/fix-suggester.ts +0 -340
  18. package/src/core/change-intelligence/index.ts +0 -5
  19. package/src/core/code-verifier.ts +0 -843
  20. package/src/core/confidence/confidence-scorer.ts +0 -251
  21. package/src/core/confidence/conflict-checker.ts +0 -289
  22. package/src/core/confidence/index.ts +0 -5
  23. package/src/core/confidence/source-tracker.ts +0 -263
  24. package/src/core/confidence/warning-detector.ts +0 -241
  25. package/src/core/context-rot/compaction.ts +0 -284
  26. package/src/core/context-rot/context-health.ts +0 -243
  27. package/src/core/context-rot/context-rot-prevention.ts +0 -213
  28. package/src/core/context-rot/critical-context.ts +0 -221
  29. package/src/core/context-rot/drift-detector.ts +0 -255
  30. package/src/core/context-rot/index.ts +0 -7
  31. package/src/core/context.ts +0 -263
  32. package/src/core/decision-extractor.ts +0 -339
  33. package/src/core/decisions.ts +0 -69
  34. package/src/core/deja-vu.ts +0 -421
  35. package/src/core/engine.ts +0 -1646
  36. package/src/core/feature-context.ts +0 -726
  37. package/src/core/ghost-mode.ts +0 -465
  38. package/src/core/learning.ts +0 -519
  39. package/src/core/living-docs/activity-tracker.ts +0 -296
  40. package/src/core/living-docs/architecture-generator.ts +0 -428
  41. package/src/core/living-docs/changelog-generator.ts +0 -348
  42. package/src/core/living-docs/component-generator.ts +0 -230
  43. package/src/core/living-docs/doc-engine.ts +0 -134
  44. package/src/core/living-docs/doc-validator.ts +0 -282
  45. package/src/core/living-docs/index.ts +0 -8
  46. package/src/core/project-manager.ts +0 -301
  47. package/src/core/refresh/activity-gate.ts +0 -256
  48. package/src/core/refresh/git-staleness-checker.ts +0 -108
  49. package/src/core/refresh/index.ts +0 -27
  50. package/src/core/summarizer.ts +0 -290
  51. package/src/core/test-awareness/change-validator.ts +0 -499
  52. package/src/core/test-awareness/index.ts +0 -5
  53. package/src/index.ts +0 -90
  54. package/src/indexing/ast.ts +0 -868
  55. package/src/indexing/embeddings.ts +0 -85
  56. package/src/indexing/indexer.ts +0 -270
  57. package/src/indexing/watcher.ts +0 -78
  58. package/src/server/gateways/aggregator.ts +0 -374
  59. package/src/server/gateways/index.ts +0 -473
  60. package/src/server/gateways/memory-ghost.ts +0 -343
  61. package/src/server/gateways/memory-query.ts +0 -452
  62. package/src/server/gateways/memory-record.ts +0 -346
  63. package/src/server/gateways/memory-review.ts +0 -410
  64. package/src/server/gateways/memory-status.ts +0 -517
  65. package/src/server/gateways/memory-verify.ts +0 -392
  66. package/src/server/gateways/router.ts +0 -434
  67. package/src/server/gateways/types.ts +0 -610
  68. package/src/server/http.ts +0 -228
  69. package/src/server/mcp.ts +0 -154
  70. package/src/server/resources.ts +0 -85
  71. package/src/server/tools.ts +0 -2460
  72. package/src/storage/database.ts +0 -271
  73. package/src/storage/tier1.ts +0 -135
  74. package/src/storage/tier2.ts +0 -972
  75. package/src/storage/tier3.ts +0 -123
  76. package/src/types/documentation.ts +0 -619
  77. package/src/types/index.ts +0 -222
  78. package/src/utils/config.ts +0 -194
  79. package/src/utils/files.ts +0 -117
  80. package/src/utils/time.ts +0 -37
  81. package/src/utils/tokens.ts +0 -52
@@ -1,544 +0,0 @@
1
- import { randomUUID } from 'crypto';
2
- import type Database from 'better-sqlite3';
3
- import type { Tier2Storage } from '../../storage/tier2.js';
4
- import type { EmbeddingGenerator } from '../../indexing/embeddings.js';
5
- import type { Change, Bug, PastBug, Diagnosis } from '../../types/documentation.js';
6
- import type { ChangeTracker } from './change-tracker.js';
7
-
8
- // Common error patterns and their likely causes
9
- const ERROR_PATTERNS = [
10
- {
11
- pattern: /cannot read (?:property |properties of )['"]?(\w+)['"]? of (?:undefined|null)/i,
12
- keywords: ['null check', 'undefined', 'optional chaining', '?.'],
13
- likelyCause: 'Missing null/undefined check'
14
- },
15
- {
16
- pattern: /(\w+) is not defined/i,
17
- keywords: ['import', 'require', 'declaration'],
18
- likelyCause: 'Missing import or variable declaration'
19
- },
20
- {
21
- pattern: /(\w+) is not a function/i,
22
- keywords: ['function', 'method', 'call', '()'],
23
- likelyCause: 'Function doesn\'t exist or wrong type'
24
- },
25
- {
26
- pattern: /unexpected token/i,
27
- keywords: ['syntax', 'parse', 'JSON'],
28
- likelyCause: 'Syntax error or malformed data'
29
- },
30
- {
31
- pattern: /timeout|timed out/i,
32
- keywords: ['timeout', 'async', 'await', 'connection'],
33
- likelyCause: 'Slow operation or connection issue'
34
- },
35
- {
36
- pattern: /ECONNREFUSED|connection refused/i,
37
- keywords: ['connection', 'port', 'server', 'database'],
38
- likelyCause: 'Service not running or wrong port'
39
- },
40
- {
41
- pattern: /out of memory|heap/i,
42
- keywords: ['memory', 'leak', 'buffer', 'array'],
43
- likelyCause: 'Memory leak or large data processing'
44
- },
45
- {
46
- pattern: /permission denied|EACCES/i,
47
- keywords: ['permission', 'access', 'chmod', 'sudo'],
48
- likelyCause: 'Missing permissions'
49
- },
50
- {
51
- pattern: /module not found|cannot find module/i,
52
- keywords: ['import', 'require', 'package', 'node_modules'],
53
- likelyCause: 'Missing dependency or wrong import path'
54
- },
55
- {
56
- pattern: /type.*is not assignable/i,
57
- keywords: ['type', 'interface', 'TypeScript'],
58
- likelyCause: 'Type mismatch'
59
- }
60
- ];
61
-
62
- export class BugCorrelator {
63
- private db: Database.Database;
64
- private changeTracker: ChangeTracker;
65
- private tier2: Tier2Storage;
66
- private embeddingGenerator: EmbeddingGenerator;
67
-
68
- constructor(
69
- db: Database.Database,
70
- changeTracker: ChangeTracker,
71
- tier2: Tier2Storage,
72
- embeddingGenerator: EmbeddingGenerator
73
- ) {
74
- this.db = db;
75
- this.changeTracker = changeTracker;
76
- this.tier2 = tier2;
77
- this.embeddingGenerator = embeddingGenerator;
78
- this.ensureTable();
79
- }
80
-
81
- private ensureTable(): void {
82
- this.db.exec(`
83
- CREATE TABLE IF NOT EXISTS bug_history (
84
- id TEXT PRIMARY KEY,
85
- error TEXT NOT NULL,
86
- stack_trace TEXT,
87
- file TEXT,
88
- line INTEGER,
89
- timestamp INTEGER NOT NULL,
90
- status TEXT DEFAULT 'open',
91
- related_changes TEXT,
92
- fixed_by TEXT,
93
- fixed_at INTEGER,
94
- fix_diff TEXT,
95
- cause TEXT
96
- );
97
- CREATE INDEX IF NOT EXISTS idx_bug_timestamp ON bug_history(timestamp);
98
- CREATE INDEX IF NOT EXISTS idx_bug_status ON bug_history(status);
99
-
100
- CREATE VIRTUAL TABLE IF NOT EXISTS bug_fts USING fts5(
101
- error,
102
- stack_trace,
103
- cause,
104
- content='bug_history',
105
- content_rowid='rowid'
106
- );
107
- `);
108
- }
109
-
110
- // Record a bug
111
- recordBug(error: string, options?: {
112
- stackTrace?: string;
113
- file?: string;
114
- line?: number;
115
- relatedChanges?: string[];
116
- }): Bug {
117
- const id = randomUUID();
118
- const timestamp = Math.floor(Date.now() / 1000);
119
-
120
- this.db.prepare(`
121
- INSERT INTO bug_history (id, error, stack_trace, file, line, timestamp, status, related_changes)
122
- VALUES (?, ?, ?, ?, ?, ?, 'open', ?)
123
- `).run(
124
- id,
125
- error,
126
- options?.stackTrace || null,
127
- options?.file || null,
128
- options?.line || null,
129
- timestamp,
130
- JSON.stringify(options?.relatedChanges || [])
131
- );
132
-
133
- return {
134
- id,
135
- error,
136
- stackTrace: options?.stackTrace,
137
- file: options?.file,
138
- line: options?.line,
139
- timestamp: new Date(timestamp * 1000),
140
- status: 'open',
141
- relatedChanges: options?.relatedChanges || []
142
- };
143
- }
144
-
145
- // Record a fix for a bug
146
- recordFix(bugId: string, fixDiff: string, cause?: string): boolean {
147
- const result = this.db.prepare(`
148
- UPDATE bug_history
149
- SET status = 'fixed', fixed_at = ?, fix_diff = ?, cause = ?
150
- WHERE id = ?
151
- `).run(
152
- Math.floor(Date.now() / 1000),
153
- fixDiff,
154
- cause || null,
155
- bugId
156
- );
157
-
158
- return result.changes > 0;
159
- }
160
-
161
- // Diagnose a bug
162
- diagnoseBug(error: string, options?: { file?: string; line?: number }): Diagnosis {
163
- // 1. Extract keywords from error
164
- const keywords = this.extractKeywords(error);
165
-
166
- // 2. Find recent changes that might be related
167
- const recentChanges = this.changeTracker.getRecentChanges(48); // Last 48 hours
168
- const relevantChanges = this.findRelevantChanges(recentChanges, keywords, options?.file);
169
-
170
- // 3. Score and rank changes
171
- const scoredChanges = this.scoreChanges(relevantChanges, error, keywords, options);
172
-
173
- // 4. Find similar past bugs
174
- const pastBugs = this.findSimilarBugs(error);
175
-
176
- // 5. Generate diagnosis
177
- const likelyCause = scoredChanges.length > 0 ? scoredChanges[0].change : null;
178
- const confidence = scoredChanges.length > 0 ? scoredChanges[0].score : 0;
179
-
180
- // 6. Generate reasoning
181
- const reasoning = this.generateReasoning(error, likelyCause, pastBugs, keywords);
182
-
183
- // 7. Suggest fix if we have past fixes
184
- const suggestedFix = this.getSuggestedFix(pastBugs, error);
185
-
186
- return {
187
- likelyCause,
188
- confidence: Math.round(confidence),
189
- relatedChanges: scoredChanges.slice(1, 5).map(s => s.change),
190
- pastSimilarBugs: pastBugs.slice(0, 5),
191
- suggestedFix,
192
- reasoning
193
- };
194
- }
195
-
196
- private extractKeywords(error: string): string[] {
197
- const keywords: string[] = [];
198
-
199
- // Extract from error patterns
200
- for (const { pattern, keywords: patternKeywords } of ERROR_PATTERNS) {
201
- if (pattern.test(error)) {
202
- keywords.push(...patternKeywords);
203
- }
204
- }
205
-
206
- // Extract identifiers (variable/function names)
207
- const identifiers = error.match(/['"`](\w+)['"`]/g);
208
- if (identifiers) {
209
- keywords.push(...identifiers.map(i => i.replace(/['"`]/g, '')));
210
- }
211
-
212
- // Extract file paths
213
- const paths = error.match(/[\w\-./]+\.(ts|js|tsx|jsx)/g);
214
- if (paths) {
215
- keywords.push(...paths);
216
- }
217
-
218
- // Extract line numbers
219
- const lineNums = error.match(/line\s*(\d+)/gi);
220
- if (lineNums) {
221
- keywords.push(...lineNums);
222
- }
223
-
224
- // Add common error-related words
225
- const words = error.toLowerCase().split(/\s+/);
226
- const significantWords = words.filter(w =>
227
- w.length > 3 &&
228
- !['error', 'the', 'and', 'for', 'with', 'from'].includes(w)
229
- );
230
- keywords.push(...significantWords.slice(0, 10));
231
-
232
- return [...new Set(keywords)];
233
- }
234
-
235
- private findRelevantChanges(changes: Change[], keywords: string[], file?: string): Change[] {
236
- return changes.filter(change => {
237
- // Priority 1: File match
238
- if (file && change.file.includes(file)) {
239
- return true;
240
- }
241
-
242
- // Priority 2: Keyword in diff or file
243
- const changeText = `${change.file} ${change.diff} ${change.commitMessage}`.toLowerCase();
244
- return keywords.some(k => changeText.includes(k.toLowerCase()));
245
- });
246
- }
247
-
248
- private scoreChanges(
249
- changes: Change[],
250
- error: string,
251
- keywords: string[],
252
- options?: { file?: string; line?: number }
253
- ): Array<{ change: Change; score: number }> {
254
- return changes.map(change => {
255
- let score = 0;
256
-
257
- // File match (high weight)
258
- if (options?.file && change.file.includes(options.file)) {
259
- score += 40;
260
- }
261
-
262
- // Recency (more recent = higher score)
263
- const hoursAgo = (Date.now() - change.timestamp.getTime()) / (1000 * 60 * 60);
264
- if (hoursAgo < 2) score += 30;
265
- else if (hoursAgo < 6) score += 20;
266
- else if (hoursAgo < 24) score += 10;
267
-
268
- // Keyword matches in diff
269
- const diffLower = change.diff.toLowerCase();
270
- let keywordMatches = 0;
271
- for (const keyword of keywords) {
272
- if (diffLower.includes(keyword.toLowerCase())) {
273
- keywordMatches++;
274
- }
275
- }
276
- score += Math.min(keywordMatches * 5, 20);
277
-
278
- // Error pattern match
279
- for (const { pattern, likelyCause } of ERROR_PATTERNS) {
280
- if (pattern.test(error)) {
281
- // Check if diff contains related changes
282
- if (diffLower.includes('null') || diffLower.includes('undefined') ||
283
- diffLower.includes('?.') || diffLower.includes('if (')) {
284
- score += 15;
285
- }
286
- }
287
- }
288
-
289
- // Deletion score (deletions often cause bugs)
290
- if (change.linesRemoved > change.linesAdded) {
291
- score += 10;
292
- }
293
-
294
- return { change, score };
295
- }).sort((a, b) => b.score - a.score);
296
- }
297
-
298
- // Find similar bugs from history
299
- findSimilarBugs(error: string, limit: number = 5): PastBug[] {
300
- try {
301
- // Simple text search
302
- const keywords = error.split(/\s+/)
303
- .filter(w => w.length > 3)
304
- .slice(0, 5)
305
- .join(' OR ');
306
-
307
- if (!keywords) return [];
308
-
309
- const rows = this.db.prepare(`
310
- SELECT id, error, cause, fix_diff, file, timestamp, status
311
- FROM bug_history
312
- WHERE status = 'fixed' AND (
313
- error LIKE ? OR
314
- error LIKE ? OR
315
- error LIKE ?
316
- )
317
- ORDER BY timestamp DESC
318
- LIMIT ?
319
- `).all(
320
- `%${error.slice(0, 30)}%`,
321
- `%${error.split(' ')[0]}%`,
322
- `%${error.split(':')[0]}%`,
323
- limit
324
- ) as Array<{
325
- id: string;
326
- error: string;
327
- cause: string | null;
328
- fix_diff: string | null;
329
- file: string | null;
330
- timestamp: number;
331
- status: string;
332
- }>;
333
-
334
- return rows.map(row => ({
335
- id: row.id,
336
- error: row.error,
337
- cause: row.cause || undefined,
338
- fix: row.cause || undefined,
339
- fixDiff: row.fix_diff || undefined,
340
- file: row.file || undefined,
341
- date: new Date(row.timestamp * 1000),
342
- similarity: this.calculateSimilarity(error, row.error)
343
- })).filter(bug => bug.similarity > 30);
344
- } catch {
345
- return [];
346
- }
347
- }
348
-
349
- private calculateSimilarity(error1: string, error2: string): number {
350
- const words1 = new Set(error1.toLowerCase().split(/\s+/));
351
- const words2 = new Set(error2.toLowerCase().split(/\s+/));
352
-
353
- let matches = 0;
354
- for (const word of words1) {
355
- if (words2.has(word)) matches++;
356
- }
357
-
358
- const total = Math.max(words1.size, words2.size);
359
- return total > 0 ? Math.round((matches / total) * 100) : 0;
360
- }
361
-
362
- private generateReasoning(
363
- error: string,
364
- likelyCause: Change | null,
365
- pastBugs: PastBug[],
366
- keywords: string[]
367
- ): string {
368
- const parts: string[] = [];
369
-
370
- if (likelyCause) {
371
- const hoursAgo = Math.round((Date.now() - likelyCause.timestamp.getTime()) / (1000 * 60 * 60));
372
- parts.push(`Found likely cause in ${likelyCause.file} (changed ${hoursAgo}h ago)`);
373
-
374
- // Check for specific patterns
375
- const diff = likelyCause.diff.toLowerCase();
376
- if (diff.includes('-') && (diff.includes('if') || diff.includes('null') || diff.includes('?.'))) {
377
- parts.push('A null/undefined check may have been removed');
378
- }
379
- if (likelyCause.linesRemoved > likelyCause.linesAdded) {
380
- parts.push('Code was removed which might have broken functionality');
381
- }
382
- } else {
383
- parts.push('Could not identify a specific recent change as the cause');
384
- }
385
-
386
- if (pastBugs.length > 0) {
387
- parts.push(`Found ${pastBugs.length} similar bug(s) in history`);
388
- if (pastBugs[0].fix) {
389
- parts.push(`Previous fix: ${pastBugs[0].fix}`);
390
- }
391
- }
392
-
393
- // Add error pattern insight
394
- for (const { pattern, likelyCause: cause } of ERROR_PATTERNS) {
395
- if (pattern.test(error)) {
396
- parts.push(`Error type suggests: ${cause}`);
397
- break;
398
- }
399
- }
400
-
401
- return parts.join('. ');
402
- }
403
-
404
- private getSuggestedFix(pastBugs: PastBug[], _error: string): string | null {
405
- if (pastBugs.length === 0) return null;
406
-
407
- const bestMatch = pastBugs.find(bug => bug.fix || bug.fixDiff);
408
- if (bestMatch) {
409
- if (bestMatch.fix) return bestMatch.fix;
410
- if (bestMatch.fixDiff) return `Apply similar fix:\n${bestMatch.fixDiff.slice(0, 200)}`;
411
- }
412
-
413
- return null;
414
- }
415
-
416
- /**
417
- * Scan git history for fix commits and auto-record as bugs
418
- * Looks for commits with "fix:", "bugfix:", "hotfix:" prefixes or "fixes #" references
419
- */
420
- scanForBugFixes(): number {
421
- try {
422
- // Get commits from change_history that look like bug fixes
423
- const fixCommits = this.db.prepare(`
424
- SELECT DISTINCT commit_hash, commit_message, file, diff, timestamp
425
- FROM change_history
426
- WHERE (
427
- LOWER(commit_message) LIKE 'fix:%' OR
428
- LOWER(commit_message) LIKE 'fix(%' OR
429
- LOWER(commit_message) LIKE 'bugfix:%' OR
430
- LOWER(commit_message) LIKE 'hotfix:%' OR
431
- LOWER(commit_message) LIKE '%fixes #%' OR
432
- LOWER(commit_message) LIKE '%fixed #%' OR
433
- LOWER(commit_message) LIKE '%closes #%'
434
- )
435
- ORDER BY timestamp DESC
436
- LIMIT 50
437
- `).all() as Array<{
438
- commit_hash: string;
439
- commit_message: string;
440
- file: string;
441
- diff: string | null;
442
- timestamp: number;
443
- }>;
444
-
445
- let recorded = 0;
446
-
447
- for (const commit of fixCommits) {
448
- // Check if we already have this bug recorded (by commit hash in related_changes)
449
- const existing = this.db.prepare(`
450
- SELECT id FROM bug_history
451
- WHERE fixed_by = ? OR related_changes LIKE ?
452
- `).get(commit.commit_hash, `%${commit.commit_hash}%`);
453
-
454
- if (existing) continue;
455
-
456
- // Extract the bug description from commit message
457
- const errorDescription = this.extractErrorFromCommitMessage(commit.commit_message);
458
-
459
- // Record the bug as already fixed
460
- const id = randomUUID();
461
-
462
- this.db.prepare(`
463
- INSERT INTO bug_history (id, error, file, timestamp, status, fixed_by, fixed_at, fix_diff, cause)
464
- VALUES (?, ?, ?, ?, 'fixed', ?, ?, ?, ?)
465
- `).run(
466
- id,
467
- errorDescription,
468
- commit.file,
469
- commit.timestamp,
470
- commit.commit_hash,
471
- commit.timestamp,
472
- commit.diff?.slice(0, 2000) || null,
473
- commit.commit_message
474
- );
475
-
476
- recorded++;
477
- }
478
-
479
- return recorded;
480
- } catch {
481
- return 0;
482
- }
483
- }
484
-
485
- /**
486
- * Extract a bug/error description from a fix commit message
487
- */
488
- private extractErrorFromCommitMessage(message: string): string {
489
- // Remove common prefixes
490
- let cleaned = message
491
- .replace(/^fix\s*[:\(]/i, '')
492
- .replace(/^bugfix\s*[:\(]/i, '')
493
- .replace(/^hotfix\s*[:\(]/i, '')
494
- .replace(/\):\s*/, ': ')
495
- .trim();
496
-
497
- // Extract issue references
498
- const issueMatch = cleaned.match(/(?:fixes|fixed|closes)\s*#(\d+)/i);
499
- if (issueMatch) {
500
- cleaned = cleaned.replace(/(?:fixes|fixed|closes)\s*#\d+/gi, '').trim();
501
- if (cleaned) {
502
- cleaned = `Issue #${issueMatch[1]}: ${cleaned}`;
503
- } else {
504
- cleaned = `Issue #${issueMatch[1]}`;
505
- }
506
- }
507
-
508
- // Capitalize first letter
509
- if (cleaned.length > 0) {
510
- cleaned = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
511
- }
512
-
513
- return cleaned || message;
514
- }
515
-
516
- // Get bug statistics
517
- getBugStats(): {
518
- total: number;
519
- open: number;
520
- fixed: number;
521
- avgTimeToFix: number;
522
- } {
523
- const stats = this.db.prepare(`
524
- SELECT
525
- COUNT(*) as total,
526
- SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open,
527
- SUM(CASE WHEN status = 'fixed' THEN 1 ELSE 0 END) as fixed,
528
- AVG(CASE WHEN status = 'fixed' THEN fixed_at - timestamp ELSE NULL END) as avg_fix_time
529
- FROM bug_history
530
- `).get() as {
531
- total: number;
532
- open: number;
533
- fixed: number;
534
- avg_fix_time: number | null;
535
- };
536
-
537
- return {
538
- total: stats.total,
539
- open: stats.open,
540
- fixed: stats.fixed,
541
- avgTimeToFix: stats.avg_fix_time ? Math.round(stats.avg_fix_time / 3600) : 0 // hours
542
- };
543
- }
544
- }