1
1
import * as fs from 'fs' ;
2
2
import * as path from 'path' ;
3
- import type { AnalysisResult } from "./analysis" ;
4
3
5
- // Best-effort write; failures are non-fatal and only logged to stderr.
6
- export function saveArtifact ( issueNumber : number , name : string , contents = '' ) : void {
7
- try {
8
- const artifactsDir = path . join ( process . cwd ( ) , 'artifacts' ) ;
9
- const filePath = path . join ( artifactsDir , `${ issueNumber } -${ name } ` ) ;
10
- fs . mkdirSync ( artifactsDir , { recursive : true } ) ;
11
- fs . writeFileSync ( filePath , contents , 'utf8' ) ;
12
- } catch ( err ) {
13
- const message = err instanceof Error ? err . message : String ( err ) ;
14
- console . error ( `⚠️ Failed to save artifact ${ name } for #${ issueNumber } : ${ message } ` ) ;
15
- }
4
+ export interface TriageDbEntry {
5
+ lastTriaged ?: string ; // ISO timestamp of when triage was completed
6
+ thoughts ?: string ; // Raw model "thoughts" / chain-of-thought output
7
+ summary ?: string ; // One-line summary from analysis
16
8
}
17
9
10
+ export type TriageDb = Record < string , TriageDbEntry > ;
11
+
18
12
export function loadDatabase ( dbPath ?: string ) : TriageDb {
19
13
if ( ! dbPath ) return { } ;
14
+
20
15
try {
21
16
if ( ! fs . existsSync ( dbPath ) ) return { } ;
17
+
22
18
const contents = fs . readFileSync ( dbPath , 'utf8' ) ;
23
19
const db = contents ? JSON . parse ( contents ) : { } ;
24
20
console . info ( `📊 Loaded ${ dbPath } with ${ Object . keys ( db ) . length } entries` ) ;
25
21
return db ;
26
22
} catch ( err ) {
27
- const message = err instanceof Error ? err . message : String ( err ) ;
23
+ const message = getErrorMessage ( err ) ;
28
24
console . error ( `⚠️ Failed to load ${ dbPath } : ${ message } . Starting with empty database.` ) ;
29
25
return { } ;
30
26
}
31
27
}
32
28
33
29
export function saveDatabase ( db : TriageDb , dbPath ?: string , enabled ?: boolean ) : void {
34
30
if ( ! dbPath || ! enabled ) return ;
31
+
35
32
try {
36
33
fs . mkdirSync ( path . dirname ( dbPath ) , { recursive : true } ) ;
37
34
fs . writeFileSync ( dbPath , JSON . stringify ( db , null , 2 ) ) ;
38
35
} catch ( err ) {
39
- const message = err instanceof Error ? err . message : String ( err ) ;
36
+ const message = getErrorMessage ( err ) ;
40
37
console . error ( `⚠️ Failed to save ${ dbPath } : ${ message } ` ) ;
41
38
}
42
39
}
43
40
44
- export type TriageDb = Record < string , TriageDbEntry > ;
45
-
46
- export interface TriageDbEntry {
47
- lastTriaged ?: string ;
48
- thoughts ?: string ;
49
- summary ?: string ;
50
- }
51
-
52
41
export function getDbEntry ( db : TriageDb , issueNumber : number ) : TriageDbEntry {
53
42
return db [ String ( issueNumber ) ] || { } ;
54
43
}
@@ -57,59 +46,96 @@ export function updateDbEntry(
57
46
db : TriageDb ,
58
47
issueNumber : number ,
59
48
summary : string ,
60
- thoughts : string ,
49
+ thoughts : string
61
50
) : void {
62
51
const key = String ( issueNumber ) ;
63
- const existing : TriageDbEntry | undefined = db [ key ] ;
64
- const entry : TriageDbEntry = {
65
- ... existing ,
66
- summary,
67
- thoughts,
68
- lastTriaged : new Date ( ) . toISOString ( ) ,
69
- } ;
52
+ const existing = db [ key ] || { } ;
53
+ const entry : TriageDbEntry = { ... existing } ;
54
+
55
+ entry . summary = summary ;
56
+ entry . thoughts = thoughts ;
57
+ entry . lastTriaged = new Date ( ) . toISOString ( ) ;
58
+
70
59
db [ key ] = entry ;
71
60
}
72
61
73
- export type Config = {
74
- owner : string ;
75
- repo : string ;
76
- token : string ;
77
- geminiApiKey : string ;
78
- modelTemperature : number ;
79
- enabled : boolean ;
80
- thinkingBudget : number ;
81
- issueNumber ?: number ;
82
- issueNumbers ?: number [ ] ;
83
- promptPath : string ;
84
- readmePath : string ;
85
- dbPath ?: string ;
86
- modelFast : string ;
87
- modelPro : string ;
88
- maxTimelineEvents : number ;
89
- maxTriages : number ;
90
- } ;
62
+ function getErrorMessage ( error : unknown ) : string {
63
+ return error instanceof Error ? error . message : String ( error ) ;
64
+ }
65
+
66
+ export function saveArtifact ( issueNumber : number , name : string , contents : string ) : void {
67
+ try {
68
+ const artifactsDir = path . join ( process . cwd ( ) , 'artifacts' ) ;
69
+ const fileName = `${ issueNumber } -${ name } ` ;
70
+ const filePath = path . join ( artifactsDir , fileName ) ;
71
+
72
+ fs . mkdirSync ( artifactsDir , { recursive : true } ) ;
73
+ fs . writeFileSync ( filePath , contents , 'utf8' ) ;
74
+ } catch ( err ) {
75
+ const message = getErrorMessage ( err ) ;
76
+ console . error ( `⚠️ Failed to save artifact ${ name } for #${ issueNumber } : ${ message } ` ) ;
77
+ }
78
+ }
91
79
92
80
export function loadReadme ( readmePath ?: string ) : string {
93
81
if ( ! readmePath ) return '' ;
82
+
94
83
try {
95
- const resolved = path . isAbsolute ( readmePath ) ? readmePath : path . join ( process . cwd ( ) , readmePath ) ;
84
+ const resolved = path . isAbsolute ( readmePath )
85
+ ? readmePath
86
+ : path . join ( process . cwd ( ) , readmePath ) ;
87
+
96
88
if ( ! fs . existsSync ( resolved ) ) return '' ;
97
89
return fs . readFileSync ( resolved , 'utf8' ) ;
98
90
} catch ( err ) {
99
- const message = err instanceof Error ? err . message : String ( err ) ;
100
- console . warn ( `Warning: failed to read README at '${ readmePath } ': ${ message } ` ) ;
91
+ const message = getErrorMessage ( err ) ;
92
+ console . warn ( `⚠️ Failed to read README at '${ readmePath } ': ${ message } ` ) ;
101
93
return '' ;
102
94
}
103
95
}
104
96
105
97
export function loadPrompt ( promptPath : string ) : string {
98
+ if ( ! promptPath ) {
99
+ throw new Error ( 'Prompt path is required' ) ;
100
+ }
101
+
106
102
try {
107
- // If a prompt path is provided, try to load it
108
- const resolvedPath = path . isAbsolute ( promptPath ) ? promptPath : path . join ( process . cwd ( ) , promptPath ) ;
103
+ // Try custom prompt path first
104
+ const resolvedPath = path . isAbsolute ( promptPath )
105
+ ? promptPath
106
+ : path . join ( process . cwd ( ) , promptPath ) ;
109
107
return fs . readFileSync ( resolvedPath , 'utf8' ) ;
110
108
} catch ( error ) {
111
- // If custom prompt file doesn't exist, fall through to bundled default
112
- const bundledPath = path . join ( __dirname , 'AutoTriage.prompt' ) ;
113
- return fs . readFileSync ( bundledPath , 'utf8' ) ;
109
+ // Fall back to bundled default prompt
110
+ try {
111
+ const bundledPath = path . join ( __dirname , 'AutoTriage.prompt' ) ;
112
+ return fs . readFileSync ( bundledPath , 'utf8' ) ;
113
+ } catch ( bundledError ) {
114
+ const customMessage = getErrorMessage ( error ) ;
115
+ const bundledMessage = getErrorMessage ( bundledError ) ;
116
+ throw new Error (
117
+ `Failed to load prompt. Custom path '${ promptPath } ': ${ customMessage } . ` +
118
+ `Bundled fallback: ${ bundledMessage } `
119
+ ) ;
120
+ }
114
121
}
115
122
}
123
+
124
+ export interface Config {
125
+ owner : string ;
126
+ repo : string ;
127
+ token : string ;
128
+ geminiApiKey : string ;
129
+ modelTemperature : number ;
130
+ enabled : boolean ;
131
+ thinkingBudget : number ;
132
+ issueNumber ?: number ;
133
+ issueNumbers ?: number [ ] ;
134
+ promptPath : string ;
135
+ readmePath : string ;
136
+ dbPath ?: string ;
137
+ modelFast : string ;
138
+ modelPro : string ;
139
+ maxTimelineEvents : number ;
140
+ maxTriages : number ;
141
+ }
0 commit comments