npm-cli-gh-issue-preparator 1.32.0 → 1.33.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/.github/CODEOWNERS +6 -2
- package/.github/copilot-instructions.md +6 -0
- package/.github/dependabot.yml +17 -0
- package/.github/instructions/code-review.instructions.md +7 -0
- package/.github/workflows/api-created_issue_pr.yml +16 -1
- package/.github/workflows/close-manual-prs.yml +46 -0
- package/.github/workflows/commit-lint.yml +16 -3
- package/.github/workflows/configs/commitlint.config.js +5 -0
- package/.github/workflows/create-pr.yml +30 -21
- package/.github/workflows/secret-scan.yml +51 -0
- package/.github/workflows/umino-project.yml +111 -31
- package/.gitleaks.toml +9 -0
- package/.prettierignore +1 -0
- package/CHANGELOG.md +15 -0
- package/bin/adapter/entry-points/cli/index.js +10 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/repositories/TowerDefenceIssueRepository.js +0 -5
- package/bin/adapter/repositories/TowerDefenceIssueRepository.js.map +1 -1
- package/bin/domain/usecases/StaleTmuxSessionKillUseCase.js +80 -0
- package/bin/domain/usecases/StaleTmuxSessionKillUseCase.js.map +1 -0
- package/bin/domain/usecases/StartPreparationUseCase.js +0 -3
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +2 -2
- package/renovate.json +5 -0
- package/src/adapter/entry-points/cli/index.test.ts +58 -0
- package/src/adapter/entry-points/cli/index.ts +21 -0
- package/src/adapter/repositories/TowerDefenceIssueRepository.test.ts +1 -0
- package/src/adapter/repositories/TowerDefenceIssueRepository.ts +0 -7
- package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +262 -0
- package/src/domain/usecases/StaleTmuxSessionKillUseCase.ts +123 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +1 -0
- package/src/domain/usecases/StartPreparationUseCase.ts +0 -4
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/repositories/TowerDefenceIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/StaleTmuxSessionKillUseCase.d.ts +25 -0
- package/types/domain/usecases/StaleTmuxSessionKillUseCase.d.ts.map +1 -0
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StaleTmuxSessionKillUseCase,
|
|
3
|
+
DEFAULT_EXCLUDED_STATUS,
|
|
4
|
+
DEFAULT_IDLE_THRESHOLD_SECONDS,
|
|
5
|
+
} from './StaleTmuxSessionKillUseCase';
|
|
6
|
+
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
7
|
+
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
8
|
+
import { LocalCommandRunner } from './adapter-interfaces/LocalCommandRunner';
|
|
9
|
+
import { Issue } from '../entities/Issue';
|
|
10
|
+
import { Project } from '../entities/Project';
|
|
11
|
+
|
|
12
|
+
type Mocked<T> = jest.Mocked<T> & jest.MockedObject<T>;
|
|
13
|
+
|
|
14
|
+
const createMockProject = (): Project => ({
|
|
15
|
+
id: 'project-1',
|
|
16
|
+
url: 'https://github.com/users/user/projects/1',
|
|
17
|
+
databaseId: 1,
|
|
18
|
+
name: 'Test Project',
|
|
19
|
+
readme: null,
|
|
20
|
+
status: {
|
|
21
|
+
name: 'Status',
|
|
22
|
+
fieldId: 'status-field-id',
|
|
23
|
+
statuses: [],
|
|
24
|
+
},
|
|
25
|
+
nextActionDate: null,
|
|
26
|
+
nextActionHour: null,
|
|
27
|
+
story: null,
|
|
28
|
+
remainingEstimationMinutes: null,
|
|
29
|
+
dependedIssueUrlSeparatedByComma: null,
|
|
30
|
+
completionDate50PercentConfidence: null,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const createMockIssue = (overrides: Partial<Issue> = {}): Issue => ({
|
|
34
|
+
nameWithOwner: 'user/repo',
|
|
35
|
+
number: 1,
|
|
36
|
+
title: 'Test Issue',
|
|
37
|
+
state: 'OPEN',
|
|
38
|
+
status: DEFAULT_EXCLUDED_STATUS,
|
|
39
|
+
story: null,
|
|
40
|
+
nextActionDate: null,
|
|
41
|
+
nextActionHour: null,
|
|
42
|
+
estimationMinutes: null,
|
|
43
|
+
dependedIssueUrls: [],
|
|
44
|
+
completionDate50PercentConfidence: null,
|
|
45
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
46
|
+
assignees: [],
|
|
47
|
+
labels: [],
|
|
48
|
+
org: 'user',
|
|
49
|
+
repo: 'repo',
|
|
50
|
+
body: '',
|
|
51
|
+
itemId: 'item-1',
|
|
52
|
+
isPr: false,
|
|
53
|
+
isInProgress: false,
|
|
54
|
+
isClosed: false,
|
|
55
|
+
createdAt: new Date(),
|
|
56
|
+
author: 'testuser',
|
|
57
|
+
...overrides,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const sessionNameOf = (issueUrl: string): string =>
|
|
61
|
+
issueUrl.replace(/[.:]/g, '_');
|
|
62
|
+
|
|
63
|
+
describe('StaleTmuxSessionKillUseCase', () => {
|
|
64
|
+
let useCase: StaleTmuxSessionKillUseCase;
|
|
65
|
+
let mockProjectRepository: Mocked<Pick<ProjectRepository, 'getByUrl'>>;
|
|
66
|
+
let mockIssueRepository: Mocked<Pick<IssueRepository, 'getAllOpened'>>;
|
|
67
|
+
let mockLocalCommandRunner: Mocked<Pick<LocalCommandRunner, 'runCommand'>>;
|
|
68
|
+
let mockProject: Project;
|
|
69
|
+
const now = new Date('2026-06-26T00:00:00Z');
|
|
70
|
+
const nowEpochSeconds = Math.floor(now.getTime() / 1000);
|
|
71
|
+
|
|
72
|
+
const runParams = {
|
|
73
|
+
projectUrl: 'https://github.com/user/repo',
|
|
74
|
+
excludedStatus: DEFAULT_EXCLUDED_STATUS,
|
|
75
|
+
idleThresholdSeconds: DEFAULT_IDLE_THRESHOLD_SECONDS,
|
|
76
|
+
now,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const setLiveSessions = (lines: string[]): void => {
|
|
80
|
+
mockLocalCommandRunner.runCommand.mockResolvedValueOnce({
|
|
81
|
+
stdout: lines.join('\n'),
|
|
82
|
+
stderr: '',
|
|
83
|
+
exitCode: 0,
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
jest.resetAllMocks();
|
|
89
|
+
jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
90
|
+
mockProject = createMockProject();
|
|
91
|
+
mockProjectRepository = {
|
|
92
|
+
getByUrl: jest.fn().mockResolvedValue(mockProject),
|
|
93
|
+
};
|
|
94
|
+
mockIssueRepository = {
|
|
95
|
+
getAllOpened: jest.fn().mockResolvedValue([]),
|
|
96
|
+
};
|
|
97
|
+
mockLocalCommandRunner = {
|
|
98
|
+
runCommand: jest.fn().mockResolvedValue({
|
|
99
|
+
stdout: '',
|
|
100
|
+
stderr: '',
|
|
101
|
+
exitCode: 0,
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
useCase = new StaleTmuxSessionKillUseCase(
|
|
105
|
+
mockProjectRepository,
|
|
106
|
+
mockIssueRepository,
|
|
107
|
+
mockLocalCommandRunner,
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('exposes the excluded status and idle threshold as named constants', () => {
|
|
112
|
+
expect(DEFAULT_EXCLUDED_STATUS).toBe('In Tmux by human');
|
|
113
|
+
expect(DEFAULT_IDLE_THRESHOLD_SECONDS).toBe(86400);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('lists live sessions via the local command runner', async () => {
|
|
117
|
+
setLiveSessions([]);
|
|
118
|
+
await useCase.run(runParams);
|
|
119
|
+
expect(mockLocalCommandRunner.runCommand).toHaveBeenCalledWith('tmux', [
|
|
120
|
+
'list-sessions',
|
|
121
|
+
'-F',
|
|
122
|
+
'#{session_name} #{session_activity}',
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('kills a session mapping to an open issue whose status is not the excluded status', async () => {
|
|
127
|
+
const issueUrl = 'https://github.com/user/repo/issues/10';
|
|
128
|
+
const sessionName = sessionNameOf(issueUrl);
|
|
129
|
+
setLiveSessions([`${sessionName} ${nowEpochSeconds}`]);
|
|
130
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([
|
|
131
|
+
createMockIssue({ url: issueUrl, status: 'In Progress' }),
|
|
132
|
+
]);
|
|
133
|
+
await useCase.run(runParams);
|
|
134
|
+
expect(mockLocalCommandRunner.runCommand).toHaveBeenCalledWith('tmux', [
|
|
135
|
+
'kill-session',
|
|
136
|
+
'-t',
|
|
137
|
+
sessionName,
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('kills a session mapping to an open issue whose status is null', async () => {
|
|
142
|
+
const issueUrl = 'https://github.com/user/repo/issues/11';
|
|
143
|
+
const sessionName = sessionNameOf(issueUrl);
|
|
144
|
+
setLiveSessions([`${sessionName} ${nowEpochSeconds}`]);
|
|
145
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([
|
|
146
|
+
createMockIssue({ url: issueUrl, status: null }),
|
|
147
|
+
]);
|
|
148
|
+
await useCase.run(runParams);
|
|
149
|
+
expect(mockLocalCommandRunner.runCommand).toHaveBeenCalledWith('tmux', [
|
|
150
|
+
'kill-session',
|
|
151
|
+
'-t',
|
|
152
|
+
sessionName,
|
|
153
|
+
]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('kills an excluded-status session that has a next action date set', async () => {
|
|
157
|
+
const issueUrl = 'https://github.com/user/repo/issues/12';
|
|
158
|
+
const sessionName = sessionNameOf(issueUrl);
|
|
159
|
+
setLiveSessions([`${sessionName} ${nowEpochSeconds}`]);
|
|
160
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([
|
|
161
|
+
createMockIssue({
|
|
162
|
+
url: issueUrl,
|
|
163
|
+
status: DEFAULT_EXCLUDED_STATUS,
|
|
164
|
+
nextActionDate: new Date('2026-06-27T00:00:00Z'),
|
|
165
|
+
}),
|
|
166
|
+
]);
|
|
167
|
+
await useCase.run(runParams);
|
|
168
|
+
expect(mockLocalCommandRunner.runCommand).toHaveBeenCalledWith('tmux', [
|
|
169
|
+
'kill-session',
|
|
170
|
+
'-t',
|
|
171
|
+
sessionName,
|
|
172
|
+
]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('kills an excluded-status session that has a next action hour set', async () => {
|
|
176
|
+
const issueUrl = 'https://github.com/user/repo/issues/13';
|
|
177
|
+
const sessionName = sessionNameOf(issueUrl);
|
|
178
|
+
setLiveSessions([`${sessionName} ${nowEpochSeconds}`]);
|
|
179
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([
|
|
180
|
+
createMockIssue({
|
|
181
|
+
url: issueUrl,
|
|
182
|
+
status: DEFAULT_EXCLUDED_STATUS,
|
|
183
|
+
nextActionHour: 9,
|
|
184
|
+
}),
|
|
185
|
+
]);
|
|
186
|
+
await useCase.run(runParams);
|
|
187
|
+
expect(mockLocalCommandRunner.runCommand).toHaveBeenCalledWith('tmux', [
|
|
188
|
+
'kill-session',
|
|
189
|
+
'-t',
|
|
190
|
+
sessionName,
|
|
191
|
+
]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('never kills an excluded-status session that has no reactivation trigger', async () => {
|
|
195
|
+
const issueUrl = 'https://github.com/user/repo/issues/14';
|
|
196
|
+
const sessionName = sessionNameOf(issueUrl);
|
|
197
|
+
setLiveSessions([`${sessionName} ${nowEpochSeconds}`]);
|
|
198
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([
|
|
199
|
+
createMockIssue({
|
|
200
|
+
url: issueUrl,
|
|
201
|
+
status: DEFAULT_EXCLUDED_STATUS,
|
|
202
|
+
nextActionDate: null,
|
|
203
|
+
nextActionHour: null,
|
|
204
|
+
}),
|
|
205
|
+
]);
|
|
206
|
+
await useCase.run(runParams);
|
|
207
|
+
expect(mockLocalCommandRunner.runCommand).not.toHaveBeenCalledWith('tmux', [
|
|
208
|
+
'kill-session',
|
|
209
|
+
'-t',
|
|
210
|
+
sessionName,
|
|
211
|
+
]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('kills a no-task session that has been idle at least the idle threshold', async () => {
|
|
215
|
+
const sessionName = 'no_task_session';
|
|
216
|
+
const idleActivity = nowEpochSeconds - DEFAULT_IDLE_THRESHOLD_SECONDS;
|
|
217
|
+
setLiveSessions([`${sessionName} ${idleActivity}`]);
|
|
218
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([]);
|
|
219
|
+
await useCase.run(runParams);
|
|
220
|
+
expect(mockLocalCommandRunner.runCommand).toHaveBeenCalledWith('tmux', [
|
|
221
|
+
'kill-session',
|
|
222
|
+
'-t',
|
|
223
|
+
sessionName,
|
|
224
|
+
]);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('never kills a no-task session that was active within the idle threshold', async () => {
|
|
228
|
+
const sessionName = 'no_task_session';
|
|
229
|
+
const recentActivity = nowEpochSeconds - DEFAULT_IDLE_THRESHOLD_SECONDS + 1;
|
|
230
|
+
setLiveSessions([`${sessionName} ${recentActivity}`]);
|
|
231
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([]);
|
|
232
|
+
await useCase.run(runParams);
|
|
233
|
+
expect(mockLocalCommandRunner.runCommand).not.toHaveBeenCalledWith('tmux', [
|
|
234
|
+
'kill-session',
|
|
235
|
+
'-t',
|
|
236
|
+
sessionName,
|
|
237
|
+
]);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('does nothing when there are no live sessions', async () => {
|
|
241
|
+
setLiveSessions(['', ' ']);
|
|
242
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([]);
|
|
243
|
+
await useCase.run(runParams);
|
|
244
|
+
expect(mockLocalCommandRunner.runCommand).toHaveBeenCalledTimes(1);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('maps a session back to its issue using the dot-and-colon-to-underscore convention', async () => {
|
|
248
|
+
const issueUrl = 'https://github.com/owner/repo/issues/9';
|
|
249
|
+
const sessionName = 'https_//github_com/owner/repo/issues/9';
|
|
250
|
+
expect(sessionNameOf(issueUrl)).toBe(sessionName);
|
|
251
|
+
setLiveSessions([`${sessionName} ${nowEpochSeconds}`]);
|
|
252
|
+
mockIssueRepository.getAllOpened.mockResolvedValue([
|
|
253
|
+
createMockIssue({ url: issueUrl, status: 'In Progress' }),
|
|
254
|
+
]);
|
|
255
|
+
await useCase.run(runParams);
|
|
256
|
+
expect(mockLocalCommandRunner.runCommand).toHaveBeenCalledWith('tmux', [
|
|
257
|
+
'kill-session',
|
|
258
|
+
'-t',
|
|
259
|
+
sessionName,
|
|
260
|
+
]);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
2
|
+
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
3
|
+
import { LocalCommandRunner } from './adapter-interfaces/LocalCommandRunner';
|
|
4
|
+
import { Issue } from '../entities/Issue';
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_EXCLUDED_STATUS = 'In Tmux by human';
|
|
7
|
+
export const DEFAULT_IDLE_THRESHOLD_SECONDS = 24 * 60 * 60;
|
|
8
|
+
|
|
9
|
+
type LiveTmuxSession = {
|
|
10
|
+
sessionName: string;
|
|
11
|
+
activityEpochSeconds: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type KillCandidate = {
|
|
15
|
+
sessionName: string;
|
|
16
|
+
reason: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class StaleTmuxSessionKillUseCase {
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly projectRepository: Pick<ProjectRepository, 'getByUrl'>,
|
|
22
|
+
private readonly issueRepository: Pick<IssueRepository, 'getAllOpened'>,
|
|
23
|
+
private readonly localCommandRunner: Pick<LocalCommandRunner, 'runCommand'>,
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
run = async (params: {
|
|
27
|
+
projectUrl: string;
|
|
28
|
+
excludedStatus: string;
|
|
29
|
+
idleThresholdSeconds: number;
|
|
30
|
+
now: Date;
|
|
31
|
+
}): Promise<void> => {
|
|
32
|
+
const liveSessions = await this.listLiveSessions();
|
|
33
|
+
const project = await this.projectRepository.getByUrl(params.projectUrl);
|
|
34
|
+
const openIssues = await this.issueRepository.getAllOpened(project);
|
|
35
|
+
const issueBySessionName = new Map<string, Issue>();
|
|
36
|
+
for (const issue of openIssues) {
|
|
37
|
+
issueBySessionName.set(this.deriveSessionName(issue.url), issue);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const nowEpochSeconds = Math.floor(params.now.getTime() / 1000);
|
|
41
|
+
const killCandidates: KillCandidate[] = [];
|
|
42
|
+
for (const session of liveSessions) {
|
|
43
|
+
const reason = this.evaluateKillReason(
|
|
44
|
+
session,
|
|
45
|
+
issueBySessionName.get(session.sessionName) ?? null,
|
|
46
|
+
nowEpochSeconds,
|
|
47
|
+
params.excludedStatus,
|
|
48
|
+
params.idleThresholdSeconds,
|
|
49
|
+
);
|
|
50
|
+
if (reason !== null) {
|
|
51
|
+
killCandidates.push({ sessionName: session.sessionName, reason });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(
|
|
56
|
+
`Stale tmux session cleanup: ${killCandidates.length} kill candidate(s) of ${liveSessions.length} live session(s).`,
|
|
57
|
+
);
|
|
58
|
+
for (const candidate of killCandidates) {
|
|
59
|
+
console.log(
|
|
60
|
+
`Kill candidate: ${candidate.sessionName} (${candidate.reason})`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const candidate of killCandidates) {
|
|
65
|
+
await this.localCommandRunner.runCommand('tmux', [
|
|
66
|
+
'kill-session',
|
|
67
|
+
'-t',
|
|
68
|
+
candidate.sessionName,
|
|
69
|
+
]);
|
|
70
|
+
console.log(
|
|
71
|
+
`Killed tmux session: ${candidate.sessionName} (${candidate.reason})`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
private listLiveSessions = async (): Promise<LiveTmuxSession[]> => {
|
|
77
|
+
const result = await this.localCommandRunner.runCommand('tmux', [
|
|
78
|
+
'list-sessions',
|
|
79
|
+
'-F',
|
|
80
|
+
'#{session_name} #{session_activity}',
|
|
81
|
+
]);
|
|
82
|
+
return result.stdout
|
|
83
|
+
.split('\n')
|
|
84
|
+
.map((line) => line.trim())
|
|
85
|
+
.filter((line) => line.length > 0)
|
|
86
|
+
.map((line) => {
|
|
87
|
+
const separatorIndex = line.lastIndexOf(' ');
|
|
88
|
+
const sessionName = line.slice(0, separatorIndex);
|
|
89
|
+
const activityEpochSeconds = Number(line.slice(separatorIndex + 1));
|
|
90
|
+
return { sessionName, activityEpochSeconds };
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
private deriveSessionName = (issueUrl: string): string =>
|
|
95
|
+
issueUrl.replace(/[.:]/g, '_');
|
|
96
|
+
|
|
97
|
+
private evaluateKillReason = (
|
|
98
|
+
session: LiveTmuxSession,
|
|
99
|
+
issue: Issue | null,
|
|
100
|
+
nowEpochSeconds: number,
|
|
101
|
+
excludedStatus: string,
|
|
102
|
+
idleThresholdSeconds: number,
|
|
103
|
+
): string | null => {
|
|
104
|
+
if (issue !== null) {
|
|
105
|
+
if (issue.status !== excludedStatus) {
|
|
106
|
+
return `mapped to open issue ${issue.url} with status "${issue.status ?? 'null'}" which is not the excluded status "${excludedStatus}"`;
|
|
107
|
+
}
|
|
108
|
+
if (issue.nextActionDate !== null) {
|
|
109
|
+
return `mapped to open issue ${issue.url} which has a next action date set`;
|
|
110
|
+
}
|
|
111
|
+
if (issue.nextActionHour !== null) {
|
|
112
|
+
return `mapped to open issue ${issue.url} which has a next action hour set`;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const idleSeconds = nowEpochSeconds - session.activityEpochSeconds;
|
|
118
|
+
if (idleSeconds >= idleThresholdSeconds) {
|
|
119
|
+
return `maps to no open issue and has been idle for ${idleSeconds} seconds (threshold ${idleThresholdSeconds} seconds)`;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -100,10 +100,6 @@ export class StartPreparationUseCase {
|
|
|
100
100
|
.filter((issue) => issue.status === params.awaitingWorkspaceStatus)
|
|
101
101
|
.map((issue) => ({
|
|
102
102
|
...issue,
|
|
103
|
-
author:
|
|
104
|
-
'author' in issue && typeof issue.author === 'string'
|
|
105
|
-
? issue.author
|
|
106
|
-
: '',
|
|
107
103
|
}));
|
|
108
104
|
const currentPreparationIssueCount = allIssues.filter(
|
|
109
105
|
(issue) => issue.status === params.preparationStatus,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC,KAAK,UAAU,GAAG;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iCAAiC,CAAC,EAAE,MAAM,CAAC;CAC5C,CAAC;AA6CF,QAAA,MAAM,cAAc,GAAI,gBAAgB,MAAM,KAAG,UA6ChD,CAAC;AAEF,QAAA,MAAM,wBAAwB,GAAI,QAAQ,MAAM,KAAG,UAiDlD,CAAC;AAEF,QAAA,MAAM,YAAY,GAChB,YAAY,UAAU,EACtB,cAAc,UAAU,EACxB,iBAAiB,UAAU,KAC1B,UAmDD,CAAC;AAeH,QAAA,MAAM,OAAO,SAAgB,CAAC;AAyY9B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TowerDefenceIssueRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/TowerDefenceIssueRepository.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAGtE,qBAAa,2BAA4B,YAAW,IAAI,CACtD,eAAe,EACf,cAAc,GAAG,mBAAmB,CACrC;IAQG,OAAO,CAAC,QAAQ,CAAC,cAAc;IAPjC,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,cAAc,CAA2C;IACjE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAW;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;gBAGnC,cAAc,EAAE,MAAM,EACvC,MAAM,EAAE,MAAM,EACd,aAAa,GAAE,MAAM,EAAyB,EAC9C,KAAK,GAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAgB;YAMvC,QAAQ;IAuDtB,OAAO,CAAC,yBAAyB;IAgB3B,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAMtD,iBAAiB,GAAU,SAAS,OAAO,KAAG,OAAO,CAAC,cAAc,CAAC,CAInE;IAEF,OAAO,CAAC,UAAU;
|
|
1
|
+
{"version":3,"file":"TowerDefenceIssueRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/TowerDefenceIssueRepository.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAGtE,qBAAa,2BAA4B,YAAW,IAAI,CACtD,eAAe,EACf,cAAc,GAAG,mBAAmB,CACrC;IAQG,OAAO,CAAC,QAAQ,CAAC,cAAc;IAPjC,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,cAAc,CAA2C;IACjE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAW;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;gBAGnC,cAAc,EAAE,MAAM,EACvC,MAAM,EAAE,MAAM,EACd,aAAa,GAAE,MAAM,EAAyB,EAC9C,KAAK,GAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAgB;YAMvC,QAAQ;IAuDtB,OAAO,CAAC,yBAAyB;IAgB3B,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAMtD,iBAAiB,GAAU,SAAS,OAAO,KAAG,OAAO,CAAC,cAAc,CAAC,CAInE;IAEF,OAAO,CAAC,UAAU;CAKnB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
2
|
+
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
3
|
+
import { LocalCommandRunner } from './adapter-interfaces/LocalCommandRunner';
|
|
4
|
+
export declare const DEFAULT_EXCLUDED_STATUS = 'In Tmux by human';
|
|
5
|
+
export declare const DEFAULT_IDLE_THRESHOLD_SECONDS: number;
|
|
6
|
+
export declare class StaleTmuxSessionKillUseCase {
|
|
7
|
+
private readonly projectRepository;
|
|
8
|
+
private readonly issueRepository;
|
|
9
|
+
private readonly localCommandRunner;
|
|
10
|
+
constructor(
|
|
11
|
+
projectRepository: Pick<ProjectRepository, 'getByUrl'>,
|
|
12
|
+
issueRepository: Pick<IssueRepository, 'getAllOpened'>,
|
|
13
|
+
localCommandRunner: Pick<LocalCommandRunner, 'runCommand'>,
|
|
14
|
+
);
|
|
15
|
+
run: (params: {
|
|
16
|
+
projectUrl: string;
|
|
17
|
+
excludedStatus: string;
|
|
18
|
+
idleThresholdSeconds: number;
|
|
19
|
+
now: Date;
|
|
20
|
+
}) => Promise<void>;
|
|
21
|
+
private listLiveSessions;
|
|
22
|
+
private deriveSessionName;
|
|
23
|
+
private evaluateKillReason;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=StaleTmuxSessionKillUseCase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StaleTmuxSessionKillUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StaleTmuxSessionKillUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAG7E,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAC1D,eAAO,MAAM,8BAA8B,QAAe,CAAC;AAY3D,qBAAa,2BAA2B;IAEpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAFlB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC,EACtD,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,EACtD,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,CAAC;IAG7E,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,GAAG,EAAE,IAAI,CAAC;KACX,KAAG,OAAO,CAAC,IAAI,CAAC,CA2Cf;IAEF,OAAO,CAAC,gBAAgB,CAgBtB;IAEF,OAAO,CAAC,iBAAiB,CACQ;IAEjC,OAAO,CAAC,kBAAkB,CAyBxB;CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAQhC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAblB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,eAAe,CAC7B,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,mBAAmB,GACnB,QAAQ,GACR,oBAAoB,GACpB,oBAAoB,CACvB,EACgB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EACpD,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,cAAc,EAAE,MAAM,CAAC;QACvB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3C,8BAA8B,EAAE,MAAM,CAAC;QACvC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACtC,KAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAQhC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAblB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,eAAe,CAC7B,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,mBAAmB,GACnB,QAAQ,GACR,oBAAoB,GACpB,oBAAoB,CACvB,EACgB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EACpD,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,cAAc,EAAE,MAAM,CAAC;QACvB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3C,8BAA8B,EAAE,MAAM,CAAC;QACvC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACtC,KAAG,OAAO,CAAC,IAAI,CAAC,CA0Lf;CACH"}
|