mcp-git-issue-priority 1.0.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/LICENSE +21 -0
- package/README.md +412 -0
- package/dist/config/index.d.ts +20 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +76 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/models/audit-log.d.ts +57 -0
- package/dist/models/audit-log.d.ts.map +1 -0
- package/dist/models/audit-log.js +50 -0
- package/dist/models/audit-log.js.map +1 -0
- package/dist/models/batch-state.d.ts +81 -0
- package/dist/models/batch-state.d.ts.map +1 -0
- package/dist/models/batch-state.js +38 -0
- package/dist/models/batch-state.js.map +1 -0
- package/dist/models/index.d.ts +9 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +9 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/issue.d.ts +193 -0
- package/dist/models/issue.d.ts.map +1 -0
- package/dist/models/issue.js +86 -0
- package/dist/models/issue.js.map +1 -0
- package/dist/models/lock.d.ts +35 -0
- package/dist/models/lock.d.ts.map +1 -0
- package/dist/models/lock.js +48 -0
- package/dist/models/lock.js.map +1 -0
- package/dist/models/pr-status.d.ts +152 -0
- package/dist/models/pr-status.d.ts.map +1 -0
- package/dist/models/pr-status.js +36 -0
- package/dist/models/pr-status.js.map +1 -0
- package/dist/models/priority-score.d.ts +41 -0
- package/dist/models/priority-score.d.ts.map +1 -0
- package/dist/models/priority-score.js +64 -0
- package/dist/models/priority-score.js.map +1 -0
- package/dist/models/selection-filter.d.ts +16 -0
- package/dist/models/selection-filter.d.ts.map +1 -0
- package/dist/models/selection-filter.js +40 -0
- package/dist/models/selection-filter.js.map +1 -0
- package/dist/models/workflow-state.d.ts +126 -0
- package/dist/models/workflow-state.d.ts.map +1 -0
- package/dist/models/workflow-state.js +106 -0
- package/dist/models/workflow-state.js.map +1 -0
- package/dist/services/batch.d.ts +23 -0
- package/dist/services/batch.d.ts.map +1 -0
- package/dist/services/batch.js +126 -0
- package/dist/services/batch.js.map +1 -0
- package/dist/services/github.d.ts +48 -0
- package/dist/services/github.d.ts.map +1 -0
- package/dist/services/github.js +315 -0
- package/dist/services/github.js.map +1 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +7 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/locking.d.ts +42 -0
- package/dist/services/locking.d.ts.map +1 -0
- package/dist/services/locking.js +195 -0
- package/dist/services/locking.js.map +1 -0
- package/dist/services/logging.d.ts +40 -0
- package/dist/services/logging.d.ts.map +1 -0
- package/dist/services/logging.js +80 -0
- package/dist/services/logging.js.map +1 -0
- package/dist/services/priority.d.ts +18 -0
- package/dist/services/priority.d.ts.map +1 -0
- package/dist/services/priority.js +33 -0
- package/dist/services/priority.js.map +1 -0
- package/dist/services/workflow.d.ts +39 -0
- package/dist/services/workflow.d.ts.map +1 -0
- package/dist/services/workflow.js +201 -0
- package/dist/services/workflow.js.map +1 -0
- package/dist/tools/advance-workflow.d.ts +3 -0
- package/dist/tools/advance-workflow.d.ts.map +1 -0
- package/dist/tools/advance-workflow.js +120 -0
- package/dist/tools/advance-workflow.js.map +1 -0
- package/dist/tools/batch-continue.d.ts +3 -0
- package/dist/tools/batch-continue.d.ts.map +1 -0
- package/dist/tools/batch-continue.js +133 -0
- package/dist/tools/batch-continue.js.map +1 -0
- package/dist/tools/bulk-update-issues.d.ts +3 -0
- package/dist/tools/bulk-update-issues.d.ts.map +1 -0
- package/dist/tools/bulk-update-issues.js +87 -0
- package/dist/tools/bulk-update-issues.js.map +1 -0
- package/dist/tools/create-issue.d.ts +41 -0
- package/dist/tools/create-issue.d.ts.map +1 -0
- package/dist/tools/create-issue.js +186 -0
- package/dist/tools/create-issue.js.map +1 -0
- package/dist/tools/force-claim.d.ts +3 -0
- package/dist/tools/force-claim.d.ts.map +1 -0
- package/dist/tools/force-claim.js +99 -0
- package/dist/tools/force-claim.js.map +1 -0
- package/dist/tools/get-pr-status.d.ts +3 -0
- package/dist/tools/get-pr-status.d.ts.map +1 -0
- package/dist/tools/get-pr-status.js +51 -0
- package/dist/tools/get-pr-status.js.map +1 -0
- package/dist/tools/get-workflow-analytics.d.ts +3 -0
- package/dist/tools/get-workflow-analytics.d.ts.map +1 -0
- package/dist/tools/get-workflow-analytics.js +264 -0
- package/dist/tools/get-workflow-analytics.js.map +1 -0
- package/dist/tools/get-workflow-status.d.ts +3 -0
- package/dist/tools/get-workflow-status.d.ts.map +1 -0
- package/dist/tools/get-workflow-status.js +103 -0
- package/dist/tools/get-workflow-status.js.map +1 -0
- package/dist/tools/implement-batch.d.ts +3 -0
- package/dist/tools/implement-batch.d.ts.map +1 -0
- package/dist/tools/implement-batch.js +124 -0
- package/dist/tools/implement-batch.js.map +1 -0
- package/dist/tools/list-backlog.d.ts +3 -0
- package/dist/tools/list-backlog.d.ts.map +1 -0
- package/dist/tools/list-backlog.js +146 -0
- package/dist/tools/list-backlog.js.map +1 -0
- package/dist/tools/release-lock.d.ts +3 -0
- package/dist/tools/release-lock.d.ts.map +1 -0
- package/dist/tools/release-lock.js +95 -0
- package/dist/tools/release-lock.js.map +1 -0
- package/dist/tools/select-next-issue.d.ts +3 -0
- package/dist/tools/select-next-issue.d.ts.map +1 -0
- package/dist/tools/select-next-issue.js +192 -0
- package/dist/tools/select-next-issue.js.map +1 -0
- package/dist/tools/sync-backlog-labels.d.ts +3 -0
- package/dist/tools/sync-backlog-labels.d.ts.map +1 -0
- package/dist/tools/sync-backlog-labels.js +164 -0
- package/dist/tools/sync-backlog-labels.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { readFile, writeFile, unlink, readdir, mkdir } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { getLocksDir, getConfig } from '../config/index.js';
|
|
4
|
+
import { createLock, getLockFileName, parseLockFileName, validateLock, isProcessAlive, LOCK_STALE_TIMEOUT_MS, } from '../models/index.js';
|
|
5
|
+
export class LockingService {
|
|
6
|
+
locksDir;
|
|
7
|
+
sessionId;
|
|
8
|
+
constructor(sessionId, locksDir) {
|
|
9
|
+
this.sessionId = sessionId;
|
|
10
|
+
this.locksDir = locksDir ?? getLocksDir();
|
|
11
|
+
}
|
|
12
|
+
getLockFilePath(owner, repo, issueNumber) {
|
|
13
|
+
return join(this.locksDir, getLockFileName(owner, repo, issueNumber));
|
|
14
|
+
}
|
|
15
|
+
async ensureLocksDir() {
|
|
16
|
+
await mkdir(this.locksDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
async acquireLock(owner, repo, issueNumber) {
|
|
19
|
+
await this.ensureLocksDir();
|
|
20
|
+
const lockFilePath = this.getLockFilePath(owner, repo, issueNumber);
|
|
21
|
+
const existingLock = await this.readLockFile(owner, repo, issueNumber);
|
|
22
|
+
if (existingLock) {
|
|
23
|
+
const isStale = await this.isLockStale(existingLock);
|
|
24
|
+
if (!isStale) {
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
error: `Issue #${issueNumber} is locked by another session`,
|
|
28
|
+
code: 'LOCK_HELD',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
await this.deleteLockFile(owner, repo, issueNumber);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const lockData = createLock(issueNumber, `${owner}/${repo}`, this.sessionId);
|
|
35
|
+
// Try to create file atomically using exclusive flag
|
|
36
|
+
await writeFile(lockFilePath, JSON.stringify(lockData, null, 2), { flag: 'wx' });
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
lock: lockData,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const err = error;
|
|
44
|
+
if (err.code === 'EEXIST') {
|
|
45
|
+
// File was created by another process between our check and write
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: `Issue #${issueNumber} is locked by another session`,
|
|
49
|
+
code: 'LOCK_HELD',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: `Failed to create lock: ${err.message}`,
|
|
55
|
+
code: 'LOCK_CREATION_FAILED',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async releaseLock(owner, repo, issueNumber) {
|
|
60
|
+
try {
|
|
61
|
+
const existingLock = await this.readLockFile(owner, repo, issueNumber);
|
|
62
|
+
if (!existingLock) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (existingLock.sessionId !== this.sessionId) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
await this.deleteLockFile(owner, repo, issueNumber);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async forceClaim(owner, repo, issueNumber) {
|
|
76
|
+
await this.ensureLocksDir();
|
|
77
|
+
const existingLock = await this.readLockFile(owner, repo, issueNumber);
|
|
78
|
+
const previousHolder = existingLock ? { ...existingLock } : null;
|
|
79
|
+
if (existingLock) {
|
|
80
|
+
await this.deleteLockFile(owner, repo, issueNumber);
|
|
81
|
+
}
|
|
82
|
+
// Small delay to ensure file system sync
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
84
|
+
const lockFilePath = this.getLockFilePath(owner, repo, issueNumber);
|
|
85
|
+
const lockData = createLock(issueNumber, `${owner}/${repo}`, this.sessionId);
|
|
86
|
+
try {
|
|
87
|
+
await writeFile(lockFilePath, JSON.stringify(lockData, null, 2));
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
previousHolder,
|
|
91
|
+
lock: lockData,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
previousHolder,
|
|
98
|
+
error: `Failed to acquire lock: ${error.message}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async isLockStale(lockData) {
|
|
103
|
+
const lockAge = Date.now() - new Date(lockData.acquiredAt).getTime();
|
|
104
|
+
if (lockAge > LOCK_STALE_TIMEOUT_MS) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const processAlive = await isProcessAlive(lockData.pid);
|
|
108
|
+
if (!processAlive) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
async readLockFile(owner, repo, issueNumber) {
|
|
114
|
+
const lockFilePath = this.getLockFilePath(owner, repo, issueNumber);
|
|
115
|
+
try {
|
|
116
|
+
const content = await readFile(lockFilePath, 'utf-8');
|
|
117
|
+
const data = JSON.parse(content);
|
|
118
|
+
return validateLock(data);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async deleteLockFile(owner, repo, issueNumber) {
|
|
125
|
+
const lockFilePath = this.getLockFilePath(owner, repo, issueNumber);
|
|
126
|
+
try {
|
|
127
|
+
await unlink(lockFilePath);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// File already deleted or doesn't exist - ignore
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async listLocks() {
|
|
134
|
+
await this.ensureLocksDir();
|
|
135
|
+
const locks = [];
|
|
136
|
+
try {
|
|
137
|
+
const files = await readdir(this.locksDir);
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
if (!file.endsWith('.lockdata'))
|
|
140
|
+
continue;
|
|
141
|
+
const parsed = parseLockFileName(file);
|
|
142
|
+
if (!parsed)
|
|
143
|
+
continue;
|
|
144
|
+
const lockData = await this.readLockFile(parsed.owner, parsed.repo, parsed.issueNumber);
|
|
145
|
+
if (!lockData)
|
|
146
|
+
continue;
|
|
147
|
+
const isStale = await this.isLockStale(lockData);
|
|
148
|
+
locks.push({
|
|
149
|
+
issueNumber: parsed.issueNumber,
|
|
150
|
+
repoFullName: `${parsed.owner}/${parsed.repo}`,
|
|
151
|
+
lock: lockData,
|
|
152
|
+
isStale,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Directory may not exist yet or permission issue - return empty array
|
|
158
|
+
}
|
|
159
|
+
return locks;
|
|
160
|
+
}
|
|
161
|
+
async getLocksForSession() {
|
|
162
|
+
const allLocks = await this.listLocks();
|
|
163
|
+
return allLocks.filter((l) => l.lock.sessionId === this.sessionId);
|
|
164
|
+
}
|
|
165
|
+
async isIssueLocked(owner, repo, issueNumber) {
|
|
166
|
+
const lockData = await this.readLockFile(owner, repo, issueNumber);
|
|
167
|
+
if (!lockData)
|
|
168
|
+
return false;
|
|
169
|
+
const isStale = await this.isLockStale(lockData);
|
|
170
|
+
return !isStale;
|
|
171
|
+
}
|
|
172
|
+
async getLockHolder(owner, repo, issueNumber) {
|
|
173
|
+
const lockData = await this.readLockFile(owner, repo, issueNumber);
|
|
174
|
+
if (!lockData)
|
|
175
|
+
return null;
|
|
176
|
+
const isStale = await this.isLockStale(lockData);
|
|
177
|
+
return { sessionId: lockData.sessionId, isStale };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
let globalLockingService = null;
|
|
181
|
+
export function getLockingService() {
|
|
182
|
+
if (!globalLockingService) {
|
|
183
|
+
const config = getConfig();
|
|
184
|
+
globalLockingService = new LockingService(config.sessionId);
|
|
185
|
+
}
|
|
186
|
+
return globalLockingService;
|
|
187
|
+
}
|
|
188
|
+
export function initializeLockingService(sessionId, locksDir) {
|
|
189
|
+
globalLockingService = new LockingService(sessionId, locksDir);
|
|
190
|
+
return globalLockingService;
|
|
191
|
+
}
|
|
192
|
+
export function resetLockingService() {
|
|
193
|
+
globalLockingService = null;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=locking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locking.js","sourceRoot":"","sources":["../../src/services/locking.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type AuditLogEntry, type AuditLogLevel, type AuditLogOutcome, type WorkflowPhase } from '../models/index.js';
|
|
2
|
+
export declare class AuditLogger {
|
|
3
|
+
private logsDir;
|
|
4
|
+
private sessionId;
|
|
5
|
+
constructor(sessionId: string, logsDir?: string);
|
|
6
|
+
log(entry: AuditLogEntry): Promise<void>;
|
|
7
|
+
logToolCall(tool: string, outcome: AuditLogOutcome, options?: {
|
|
8
|
+
level?: AuditLogLevel;
|
|
9
|
+
repoFullName?: string;
|
|
10
|
+
issueNumber?: number;
|
|
11
|
+
phase?: WorkflowPhase;
|
|
12
|
+
duration?: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
info(tool: string, options?: {
|
|
17
|
+
repoFullName?: string;
|
|
18
|
+
issueNumber?: number;
|
|
19
|
+
phase?: WorkflowPhase;
|
|
20
|
+
duration?: number;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
warn(tool: string, message: string, options?: {
|
|
24
|
+
repoFullName?: string;
|
|
25
|
+
issueNumber?: number;
|
|
26
|
+
phase?: WorkflowPhase;
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
error(tool: string, error: string | Error, options?: {
|
|
30
|
+
repoFullName?: string;
|
|
31
|
+
issueNumber?: number;
|
|
32
|
+
phase?: WorkflowPhase;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
cleanupOldLogs(retentionDays?: number): Promise<number>;
|
|
36
|
+
}
|
|
37
|
+
export declare function getLogger(sessionId?: string): AuditLogger;
|
|
38
|
+
export declare function initializeLogger(sessionId: string, logsDir?: string): AuditLogger;
|
|
39
|
+
export declare function resetLogger(): void;
|
|
40
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/services/logging.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAInB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;IAKzC,GAAG,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxC,WAAW,CACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,aAAa,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GACA,OAAO,CAAC,IAAI,CAAC;IAKV,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GACA,OAAO,CAAC,IAAI,CAAC;IAIV,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GACA,OAAO,CAAC,IAAI,CAAC;IAQV,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,KAAK,EACrB,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GACA,OAAO,CAAC,IAAI,CAAC;IASV,cAAc,CAAC,aAAa,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC;CAwBlE;AAID,wBAAgB,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAQzD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAGjF;AAED,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { appendFile, readdir, unlink } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { getLogsDir, ensureDirectories } from '../config/index.js';
|
|
4
|
+
import { createAuditLogEntry, serializeLogEntry, getLogFileName, } from '../models/index.js';
|
|
5
|
+
export class AuditLogger {
|
|
6
|
+
logsDir;
|
|
7
|
+
sessionId;
|
|
8
|
+
constructor(sessionId, logsDir) {
|
|
9
|
+
this.sessionId = sessionId;
|
|
10
|
+
this.logsDir = logsDir ?? getLogsDir();
|
|
11
|
+
}
|
|
12
|
+
async log(entry) {
|
|
13
|
+
await ensureDirectories();
|
|
14
|
+
const fileName = getLogFileName(new Date(entry.timestamp));
|
|
15
|
+
const filePath = join(this.logsDir, fileName);
|
|
16
|
+
const line = serializeLogEntry(entry) + '\n';
|
|
17
|
+
await appendFile(filePath, line);
|
|
18
|
+
}
|
|
19
|
+
async logToolCall(tool, outcome, options) {
|
|
20
|
+
const entry = createAuditLogEntry(tool, this.sessionId, outcome, options);
|
|
21
|
+
await this.log(entry);
|
|
22
|
+
}
|
|
23
|
+
async info(tool, options) {
|
|
24
|
+
await this.logToolCall(tool, 'success', { level: 'info', ...options });
|
|
25
|
+
}
|
|
26
|
+
async warn(tool, message, options) {
|
|
27
|
+
await this.logToolCall(tool, 'skipped', {
|
|
28
|
+
level: 'warn',
|
|
29
|
+
error: message,
|
|
30
|
+
...options,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async error(tool, error, options) {
|
|
34
|
+
const errorMessage = error instanceof Error ? error.message : error;
|
|
35
|
+
await this.logToolCall(tool, 'failure', {
|
|
36
|
+
level: 'error',
|
|
37
|
+
error: errorMessage,
|
|
38
|
+
...options,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async cleanupOldLogs(retentionDays = 30) {
|
|
42
|
+
await ensureDirectories();
|
|
43
|
+
const files = await readdir(this.logsDir);
|
|
44
|
+
const cutoffDate = new Date();
|
|
45
|
+
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
|
46
|
+
let deletedCount = 0;
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
if (!file.startsWith('audit-') || !file.endsWith('.jsonl'))
|
|
49
|
+
continue;
|
|
50
|
+
const dateMatch = file.match(/^audit-(\d{4}-\d{2}-\d{2})\.jsonl$/);
|
|
51
|
+
if (!dateMatch)
|
|
52
|
+
continue;
|
|
53
|
+
const fileDate = new Date(dateMatch[1]);
|
|
54
|
+
if (fileDate < cutoffDate) {
|
|
55
|
+
const filePath = join(this.logsDir, file);
|
|
56
|
+
await unlink(filePath);
|
|
57
|
+
deletedCount++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return deletedCount;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let globalLogger = null;
|
|
64
|
+
export function getLogger(sessionId) {
|
|
65
|
+
if (!globalLogger && !sessionId) {
|
|
66
|
+
throw new Error('Logger not initialized. Call initializeLogger first.');
|
|
67
|
+
}
|
|
68
|
+
if (!globalLogger && sessionId) {
|
|
69
|
+
globalLogger = new AuditLogger(sessionId);
|
|
70
|
+
}
|
|
71
|
+
return globalLogger;
|
|
72
|
+
}
|
|
73
|
+
export function initializeLogger(sessionId, logsDir) {
|
|
74
|
+
globalLogger = new AuditLogger(sessionId, logsDir);
|
|
75
|
+
return globalLogger;
|
|
76
|
+
}
|
|
77
|
+
export function resetLogger() {
|
|
78
|
+
globalLogger = null;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../../src/services/logging.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Issue } from '../models/index.js';
|
|
2
|
+
import { type PriorityScore, type SelectionFilter, calculatePriorityScore, comparePriorityScores, sortByPriority, applyFilters, calculateAgeInDays } from '../models/index.js';
|
|
3
|
+
export interface ScoredIssue {
|
|
4
|
+
issue: Issue;
|
|
5
|
+
score: PriorityScore;
|
|
6
|
+
ageInDays: number;
|
|
7
|
+
blockedByIssue?: number | null;
|
|
8
|
+
}
|
|
9
|
+
export interface DependencyInfo {
|
|
10
|
+
issueNumber: number;
|
|
11
|
+
blockedByIssue: number | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function scoreIssues(issues: Issue[]): ScoredIssue[];
|
|
14
|
+
export declare function scoreIssuesWithDependencies(issues: Issue[], dependencies: Map<number, number | null>): ScoredIssue[];
|
|
15
|
+
export declare function filterAndScoreIssues(issues: Issue[], filter: SelectionFilter): ScoredIssue[];
|
|
16
|
+
export declare function getNextPriorityIssue(issues: Issue[], filter: SelectionFilter, excludeIssueNumbers?: number[]): ScoredIssue | null;
|
|
17
|
+
export { calculatePriorityScore, comparePriorityScores, sortByPriority, applyFilters, calculateAgeInDays, };
|
|
18
|
+
//# sourceMappingURL=priority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"priority.d.ts","sourceRoot":"","sources":["../../src/services/priority.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW,EAAE,CAM1D;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,KAAK,EAAE,EACf,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GACvC,WAAW,EAAE,CAUf;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,eAAe,GAAG,WAAW,EAAE,CAK5F;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,KAAK,EAAE,EACf,MAAM,EAAE,eAAe,EACvB,mBAAmB,GAAE,MAAM,EAAO,GACjC,WAAW,GAAG,IAAI,CAKpB;AAED,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,kBAAkB,GACnB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { calculatePriorityScore, comparePriorityScores, sortByPriority, applyFilters, calculateAgeInDays, } from '../models/index.js';
|
|
2
|
+
export function scoreIssues(issues) {
|
|
3
|
+
return issues.map((issue) => ({
|
|
4
|
+
issue,
|
|
5
|
+
score: calculatePriorityScore(issue),
|
|
6
|
+
ageInDays: calculateAgeInDays(issue.created_at),
|
|
7
|
+
}));
|
|
8
|
+
}
|
|
9
|
+
export function scoreIssuesWithDependencies(issues, dependencies) {
|
|
10
|
+
return issues.map((issue) => {
|
|
11
|
+
const blockedByIssue = dependencies.get(issue.number) ?? null;
|
|
12
|
+
return {
|
|
13
|
+
issue,
|
|
14
|
+
score: calculatePriorityScore(issue, { blockedByIssue }),
|
|
15
|
+
ageInDays: calculateAgeInDays(issue.created_at),
|
|
16
|
+
blockedByIssue,
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function filterAndScoreIssues(issues, filter) {
|
|
21
|
+
const filtered = applyFilters(issues, filter);
|
|
22
|
+
const scored = scoreIssues(filtered);
|
|
23
|
+
scored.sort((a, b) => comparePriorityScores(a.score, b.score));
|
|
24
|
+
return scored;
|
|
25
|
+
}
|
|
26
|
+
export function getNextPriorityIssue(issues, filter, excludeIssueNumbers = []) {
|
|
27
|
+
const excludeSet = new Set(excludeIssueNumbers);
|
|
28
|
+
const scored = filterAndScoreIssues(issues, filter);
|
|
29
|
+
const available = scored.filter((s) => !excludeSet.has(s.issue.number));
|
|
30
|
+
return available.length > 0 ? available[0] : null;
|
|
31
|
+
}
|
|
32
|
+
export { calculatePriorityScore, comparePriorityScores, sortByPriority, applyFilters, calculateAgeInDays, };
|
|
33
|
+
//# sourceMappingURL=priority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"priority.js","sourceRoot":"","sources":["../../src/services/priority.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAc5B,MAAM,UAAU,WAAW,CAAC,MAAe;IACzC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,KAAK;QACL,KAAK,EAAE,sBAAsB,CAAC,KAAK,CAAC;QACpC,SAAS,EAAE,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;KAChD,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,MAAe,EACf,YAAwC;IAExC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC1B,MAAM,cAAc,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;QAC9D,OAAO;YACL,KAAK;YACL,KAAK,EAAE,sBAAsB,CAAC,KAAK,EAAE,EAAE,cAAc,EAAE,CAAC;YACxD,SAAS,EAAE,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;YAC/C,cAAc;SACf,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAe,EAAE,MAAuB;IAC3E,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,MAAe,EACf,MAAuB,EACvB,sBAAgC,EAAE;IAElC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxE,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpD,CAAC;AAED,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,kBAAkB,GACnB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type WorkflowState, type WorkflowPhase } from '../models/index.js';
|
|
2
|
+
export interface TransitionResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
previousPhase?: WorkflowPhase;
|
|
5
|
+
currentPhase?: WorkflowPhase;
|
|
6
|
+
error?: string;
|
|
7
|
+
code?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class WorkflowService {
|
|
10
|
+
private workflowDir;
|
|
11
|
+
private sessionId;
|
|
12
|
+
constructor(sessionId: string, workflowDir?: string);
|
|
13
|
+
private getWorkflowFilePath;
|
|
14
|
+
createWorkflowState(owner: string, repo: string, issueNumber: number, triggeredBy: string): Promise<WorkflowState>;
|
|
15
|
+
getWorkflowState(owner: string, repo: string, issueNumber: number): Promise<WorkflowState | null>;
|
|
16
|
+
saveWorkflowState(owner: string, repo: string, issueNumber: number, state: WorkflowState): Promise<void>;
|
|
17
|
+
deleteWorkflowState(owner: string, repo: string, issueNumber: number): Promise<void>;
|
|
18
|
+
validatePhaseTransition(currentPhase: WorkflowPhase, targetPhase: WorkflowPhase, options?: {
|
|
19
|
+
testsPassed?: boolean;
|
|
20
|
+
skipJustification?: string;
|
|
21
|
+
}): {
|
|
22
|
+
valid: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
code?: string;
|
|
25
|
+
requiresSkipJustification?: boolean;
|
|
26
|
+
};
|
|
27
|
+
recordPhaseTransition(owner: string, repo: string, issueNumber: number, targetPhase: WorkflowPhase, triggeredBy: string, options?: {
|
|
28
|
+
testsPassed?: boolean;
|
|
29
|
+
skipJustification?: string;
|
|
30
|
+
}): Promise<TransitionResult>;
|
|
31
|
+
updateBranchName(owner: string, repo: string, issueNumber: number, branchName: string): Promise<void>;
|
|
32
|
+
updatePrNumber(owner: string, repo: string, issueNumber: number, prNumber: number): Promise<void>;
|
|
33
|
+
generateBranchName(issueNumber: number, title: string): string;
|
|
34
|
+
listWorkflowStates(): Promise<WorkflowState[]>;
|
|
35
|
+
}
|
|
36
|
+
export declare function getWorkflowService(): WorkflowService;
|
|
37
|
+
export declare function initializeWorkflowService(sessionId: string, workflowDir?: string): WorkflowService;
|
|
38
|
+
export declare function resetWorkflowService(): void;
|
|
39
|
+
//# sourceMappingURL=workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/services/workflow.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAUnB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM;IAKnD,OAAO,CAAC,mBAAmB;IAIrB,mBAAmB,CACvB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,CAAC;IAOnB,gBAAgB,CACpB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAW1B,iBAAiB,CACrB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,IAAI,CAAC;IAMV,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1F,uBAAuB,CACrB,YAAY,EAAE,aAAa,EAC3B,WAAW,EAAE,aAAa,EAC1B,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9D;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,yBAAyB,CAAC,EAAE,OAAO,CAAA;KAAE;IA6CnF,qBAAqB,CACzB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,aAAa,EAC1B,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9D,OAAO,CAAC,gBAAgB,CAAC;IAyDtB,gBAAgB,CACpB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAQV,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAQhB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAYxD,kBAAkB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;CA2BrD;AAID,wBAAgB,kBAAkB,IAAI,eAAe,CAMpD;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,eAAe,CAGlG;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { readFile, writeFile, unlink, readdir } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { getWorkflowDir, getConfig, ensureDirectories } from '../config/index.js';
|
|
4
|
+
import { createWorkflowState, getWorkflowFileName, validateWorkflowState, isValidTransition, canSkipTo, getSkippedPhases, requiresTestsForTransition, } from '../models/index.js';
|
|
5
|
+
export class WorkflowService {
|
|
6
|
+
workflowDir;
|
|
7
|
+
sessionId;
|
|
8
|
+
constructor(sessionId, workflowDir) {
|
|
9
|
+
this.sessionId = sessionId;
|
|
10
|
+
this.workflowDir = workflowDir ?? getWorkflowDir();
|
|
11
|
+
}
|
|
12
|
+
getWorkflowFilePath(owner, repo, issueNumber) {
|
|
13
|
+
return join(this.workflowDir, getWorkflowFileName(owner, repo, issueNumber));
|
|
14
|
+
}
|
|
15
|
+
async createWorkflowState(owner, repo, issueNumber, triggeredBy) {
|
|
16
|
+
await ensureDirectories();
|
|
17
|
+
const state = createWorkflowState(issueNumber, `${owner}/${repo}`, triggeredBy);
|
|
18
|
+
await this.saveWorkflowState(owner, repo, issueNumber, state);
|
|
19
|
+
return state;
|
|
20
|
+
}
|
|
21
|
+
async getWorkflowState(owner, repo, issueNumber) {
|
|
22
|
+
const filePath = this.getWorkflowFilePath(owner, repo, issueNumber);
|
|
23
|
+
try {
|
|
24
|
+
const content = await readFile(filePath, 'utf-8');
|
|
25
|
+
const data = JSON.parse(content);
|
|
26
|
+
return validateWorkflowState(data);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async saveWorkflowState(owner, repo, issueNumber, state) {
|
|
33
|
+
await ensureDirectories();
|
|
34
|
+
const filePath = this.getWorkflowFilePath(owner, repo, issueNumber);
|
|
35
|
+
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
36
|
+
}
|
|
37
|
+
async deleteWorkflowState(owner, repo, issueNumber) {
|
|
38
|
+
const filePath = this.getWorkflowFilePath(owner, repo, issueNumber);
|
|
39
|
+
try {
|
|
40
|
+
await unlink(filePath);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// File doesn't exist or already deleted - ignore
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
validatePhaseTransition(currentPhase, targetPhase, options) {
|
|
47
|
+
if (isValidTransition(currentPhase, targetPhase)) {
|
|
48
|
+
if (requiresTestsForTransition(targetPhase)) {
|
|
49
|
+
if (!options?.testsPassed && !options?.skipJustification) {
|
|
50
|
+
return {
|
|
51
|
+
valid: false,
|
|
52
|
+
error: 'Tests must pass before creating PR',
|
|
53
|
+
code: 'TESTS_REQUIRED',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { valid: true };
|
|
58
|
+
}
|
|
59
|
+
if (canSkipTo(currentPhase, targetPhase)) {
|
|
60
|
+
const skippedPhases = getSkippedPhases(currentPhase, targetPhase);
|
|
61
|
+
if (skippedPhases.length > 0 && !options?.skipJustification) {
|
|
62
|
+
return {
|
|
63
|
+
valid: false,
|
|
64
|
+
error: `Skipping phases ${skippedPhases.join(', ')} requires justification`,
|
|
65
|
+
code: 'SKIP_JUSTIFICATION_REQUIRED',
|
|
66
|
+
requiresSkipJustification: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (requiresTestsForTransition(targetPhase)) {
|
|
70
|
+
if (!options?.testsPassed && !options?.skipJustification) {
|
|
71
|
+
return {
|
|
72
|
+
valid: false,
|
|
73
|
+
error: 'Tests must pass before creating PR',
|
|
74
|
+
code: 'TESTS_REQUIRED',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { valid: true };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
error: `Cannot advance from ${currentPhase} to ${targetPhase}`,
|
|
83
|
+
code: 'INVALID_PHASE_TRANSITION',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async recordPhaseTransition(owner, repo, issueNumber, targetPhase, triggeredBy, options) {
|
|
87
|
+
const state = await this.getWorkflowState(owner, repo, issueNumber);
|
|
88
|
+
if (!state) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
error: 'Workflow state not found',
|
|
92
|
+
code: 'WORKFLOW_NOT_FOUND',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const validation = this.validatePhaseTransition(state.currentPhase, targetPhase, options);
|
|
96
|
+
if (!validation.valid) {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: validation.error,
|
|
100
|
+
code: validation.code,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const previousPhase = state.currentPhase;
|
|
104
|
+
if (options?.skipJustification) {
|
|
105
|
+
const skippedPhases = getSkippedPhases(state.currentPhase, targetPhase);
|
|
106
|
+
for (const skippedPhase of skippedPhases) {
|
|
107
|
+
const justification = {
|
|
108
|
+
skippedPhase,
|
|
109
|
+
justification: options.skipJustification,
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
sessionId: this.sessionId,
|
|
112
|
+
};
|
|
113
|
+
state.skipJustifications.push(justification);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const transition = {
|
|
117
|
+
from: state.currentPhase,
|
|
118
|
+
to: targetPhase,
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
triggeredBy,
|
|
121
|
+
};
|
|
122
|
+
state.phaseHistory.push(transition);
|
|
123
|
+
state.currentPhase = targetPhase;
|
|
124
|
+
if (options?.testsPassed !== undefined) {
|
|
125
|
+
state.testsPassed = options.testsPassed;
|
|
126
|
+
}
|
|
127
|
+
await this.saveWorkflowState(owner, repo, issueNumber, state);
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
previousPhase,
|
|
131
|
+
currentPhase: targetPhase,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async updateBranchName(owner, repo, issueNumber, branchName) {
|
|
135
|
+
const state = await this.getWorkflowState(owner, repo, issueNumber);
|
|
136
|
+
if (state) {
|
|
137
|
+
state.branchName = branchName;
|
|
138
|
+
await this.saveWorkflowState(owner, repo, issueNumber, state);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async updatePrNumber(owner, repo, issueNumber, prNumber) {
|
|
142
|
+
const state = await this.getWorkflowState(owner, repo, issueNumber);
|
|
143
|
+
if (state) {
|
|
144
|
+
state.prNumber = prNumber;
|
|
145
|
+
await this.saveWorkflowState(owner, repo, issueNumber, state);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
generateBranchName(issueNumber, title) {
|
|
149
|
+
const kebabTitle = title
|
|
150
|
+
.toLowerCase()
|
|
151
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
152
|
+
.replace(/\s+/g, '-')
|
|
153
|
+
.replace(/-+/g, '-')
|
|
154
|
+
.substring(0, 50)
|
|
155
|
+
.replace(/-$/, '');
|
|
156
|
+
return `${issueNumber}-${kebabTitle}`;
|
|
157
|
+
}
|
|
158
|
+
async listWorkflowStates() {
|
|
159
|
+
await ensureDirectories();
|
|
160
|
+
const states = [];
|
|
161
|
+
try {
|
|
162
|
+
const files = await readdir(this.workflowDir);
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
if (!file.endsWith('.json'))
|
|
165
|
+
continue;
|
|
166
|
+
const filePath = join(this.workflowDir, file);
|
|
167
|
+
try {
|
|
168
|
+
const content = await readFile(filePath, 'utf-8');
|
|
169
|
+
const data = JSON.parse(content);
|
|
170
|
+
const state = validateWorkflowState(data);
|
|
171
|
+
if (state) {
|
|
172
|
+
states.push(state);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Invalid or corrupted workflow file - skip
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Directory doesn't exist or permission error - return empty array
|
|
182
|
+
}
|
|
183
|
+
return states;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
let globalWorkflowService = null;
|
|
187
|
+
export function getWorkflowService() {
|
|
188
|
+
if (!globalWorkflowService) {
|
|
189
|
+
const config = getConfig();
|
|
190
|
+
globalWorkflowService = new WorkflowService(config.sessionId);
|
|
191
|
+
}
|
|
192
|
+
return globalWorkflowService;
|
|
193
|
+
}
|
|
194
|
+
export function initializeWorkflowService(sessionId, workflowDir) {
|
|
195
|
+
globalWorkflowService = new WorkflowService(sessionId, workflowDir);
|
|
196
|
+
return globalWorkflowService;
|
|
197
|
+
}
|
|
198
|
+
export function resetWorkflowService() {
|
|
199
|
+
globalWorkflowService = null;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=workflow.js.map
|