github-issue-tower-defence-management 1.184.1 → 1.185.1

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/adapter/entry-points/cli/index.js +31 -29
  3. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  4. package/bin/adapter/entry-points/console/consoleOperationApi.js +26 -2
  5. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  6. package/bin/adapter/entry-points/console/consoleProjectResolver.js +5 -1
  7. package/bin/adapter/entry-points/console/consoleProjectResolver.js.map +1 -1
  8. package/bin/adapter/entry-points/console/ui-dist/assets/index-DQT4HL0G.js +86 -0
  9. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  10. package/bin/adapter/entry-points/console/webServer.js +4 -0
  11. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  12. package/bin/adapter/repositories/ProcClaudeHandoverSessionRepository.js +40 -3
  13. package/bin/adapter/repositories/ProcClaudeHandoverSessionRepository.js.map +1 -1
  14. package/bin/domain/entities/Project.js +17 -0
  15. package/bin/domain/entities/Project.js.map +1 -1
  16. package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js +5 -16
  17. package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js.map +1 -1
  18. package/bin/domain/usecases/TokenExhaustionHandoverUseCase.js +9 -1
  19. package/bin/domain/usecases/TokenExhaustionHandoverUseCase.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/adapter/entry-points/cli/index.ts +57 -52
  22. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +242 -12
  23. package/src/adapter/entry-points/console/consoleOperationApi.ts +43 -7
  24. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +30 -10
  25. package/src/adapter/entry-points/console/consoleProjectResolver.ts +13 -2
  26. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +37 -2
  27. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +20 -0
  28. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +4 -1
  29. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +127 -8
  30. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +119 -71
  31. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +26 -0
  32. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +20 -0
  33. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +12 -0
  34. package/src/adapter/entry-points/console/ui-dist/assets/index-DQT4HL0G.js +86 -0
  35. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  36. package/src/adapter/entry-points/console/webServer.test.ts +74 -0
  37. package/src/adapter/entry-points/console/webServer.ts +8 -0
  38. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.test.ts +48 -0
  39. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.ts +55 -2
  40. package/src/domain/entities/ClaudeHandoverSession.ts +1 -0
  41. package/src/domain/entities/Project.ts +23 -0
  42. package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +16 -34
  43. package/src/domain/usecases/TokenExhaustionHandoverUseCase.test.ts +59 -1
  44. package/src/domain/usecases/TokenExhaustionHandoverUseCase.ts +14 -1
  45. package/types/adapter/entry-points/cli/index.d.ts +1 -1
  46. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  47. package/types/adapter/entry-points/console/consoleOperationApi.d.ts +8 -3
  48. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  49. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +5 -1
  50. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -1
  51. package/types/adapter/entry-points/console/webServer.d.ts +3 -1
  52. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  53. package/types/adapter/repositories/ProcClaudeHandoverSessionRepository.d.ts +1 -0
  54. package/types/adapter/repositories/ProcClaudeHandoverSessionRepository.d.ts.map +1 -1
  55. package/types/domain/entities/ClaudeHandoverSession.d.ts +1 -0
  56. package/types/domain/entities/ClaudeHandoverSession.d.ts.map +1 -1
  57. package/types/domain/entities/Project.d.ts +4 -0
  58. package/types/domain/entities/Project.d.ts.map +1 -1
  59. package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts +6 -8
  60. package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts.map +1 -1
  61. package/types/domain/usecases/TokenExhaustionHandoverUseCase.d.ts +1 -0
  62. package/types/domain/usecases/TokenExhaustionHandoverUseCase.d.ts.map +1 -1
  63. package/bin/adapter/entry-points/console/ui-dist/assets/index-CMrnEYO1.js +0 -86
  64. package/src/adapter/entry-points/console/ui-dist/assets/index-CMrnEYO1.js +0 -86
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>TDPM Console</title>
7
- <script type="module" crossorigin src="/assets/index-CMrnEYO1.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-DQT4HL0G.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-8Ni5iu-C.css">
9
9
  </head>
10
10
  <body>
@@ -1107,6 +1107,80 @@ describe('webServer new routes integration', () => {
1107
1107
  fs.rmSync(tmpDir, { recursive: true, force: true });
1108
1108
  }
1109
1109
  });
1110
+
1111
+ it('routes POST /api/addstory to handleStoryAdd and calls updateStoryList', async () => {
1112
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1113
+ const updateStoryList = jest.fn().mockResolvedValue([]);
1114
+ const projectWithStory: Project = {
1115
+ ...mock<Project>(),
1116
+ id: 'PVT_1',
1117
+ url: 'https://github.com/orgs/acme-labs/projects/1',
1118
+ status: {
1119
+ name: 'Status',
1120
+ fieldId: 'statusField',
1121
+ statuses: [
1122
+ {
1123
+ id: 'status_aw',
1124
+ name: 'Awaiting workspace',
1125
+ color: 'GRAY',
1126
+ description: '',
1127
+ },
1128
+ ],
1129
+ },
1130
+ story: {
1131
+ name: 'Story',
1132
+ fieldId: 'storyField',
1133
+ databaseId: 1,
1134
+ stories: [
1135
+ {
1136
+ id: 'opt_blue',
1137
+ name: 'Existing story',
1138
+ color: 'BLUE',
1139
+ description: '',
1140
+ },
1141
+ ],
1142
+ workflowManagementStory: { id: 'wms', name: 'workflow' },
1143
+ },
1144
+ };
1145
+ const issueRepository = mock<IssueRepository>();
1146
+ const server = await startWebServer({
1147
+ accessToken: testToken,
1148
+ uiDistDir: path.join(tmpDir, 'ui-dist'),
1149
+ consoleDataOutputDir: null,
1150
+ inTmuxDataDir: null,
1151
+ dashboardDir: null,
1152
+ dashboardDataDir: null,
1153
+ dashboardProjectNames: [],
1154
+ issueRepository,
1155
+ resolveProject: async (pjcode) =>
1156
+ pjcode === 'acme' ? { pjcode, project: projectWithStory } : null,
1157
+ isPjcodeConfigured: (pjcode) => pjcode === 'acme',
1158
+ resolveProjectRepository: () => ({ updateStoryList }),
1159
+ port: 0,
1160
+ });
1161
+ try {
1162
+ const response = await request(
1163
+ server,
1164
+ 'POST',
1165
+ `/api/addstory?k=${testToken}`,
1166
+ { pjcode: 'acme', storyName: 'My new story' },
1167
+ );
1168
+ expect(response.statusCode).toBe(200);
1169
+ expect(JSON.parse(response.body)).toEqual({ ok: true });
1170
+ expect(updateStoryList).toHaveBeenCalledWith(projectWithStory, [
1171
+ {
1172
+ id: 'opt_blue',
1173
+ name: 'Existing story',
1174
+ color: 'BLUE',
1175
+ description: '',
1176
+ },
1177
+ { id: null, name: 'My new story', color: 'RED', description: '' },
1178
+ ]);
1179
+ } finally {
1180
+ await closeServer(server);
1181
+ fs.rmSync(tmpDir, { recursive: true, force: true });
1182
+ }
1183
+ });
1110
1184
  });
1111
1185
 
1112
1186
  describe('resolveFlatInTmuxFilePath', () => {
@@ -24,7 +24,9 @@ import {
24
24
  ConsoleIssueRepositoryResolver,
25
25
  ConsoleOperationContext,
26
26
  ConsolePjcodeValidator,
27
+ ConsoleProjectRepositoryResolver,
27
28
  ConsoleProjectResolver,
29
+ handleStoryAdd,
28
30
  handleAttachmentUpload,
29
31
  handleComment,
30
32
  handleCreateIssue,
@@ -233,6 +235,8 @@ export type WebServerOptions = {
233
235
  resolveIssueRepository?: ConsoleIssueRepositoryResolver | null;
234
236
  resolveProject?: ConsoleProjectResolver | null;
235
237
  isPjcodeConfigured?: ConsolePjcodeValidator | null;
238
+ resolveProjectRepository?: ConsoleProjectRepositoryResolver | null;
239
+ invalidateProject?: ((pjcode: string) => void) | null;
236
240
  issueAttachmentRepository?: IssueAttachmentRepository | null;
237
241
  projectRepository?: Pick<ProjectRepository, 'updateStoryList'> | null;
238
242
  issueTitleStateCache?: IssueTitleStateCache | null;
@@ -540,6 +544,8 @@ const dispatchOperation = (
540
544
  return handleCreateIssue(context, body);
541
545
  case '/api/reorderstory':
542
546
  return handleReorderStory(context, body);
547
+ case '/api/addstory':
548
+ return handleStoryAdd(context, body);
543
549
  default:
544
550
  return null;
545
551
  }
@@ -574,6 +580,8 @@ const handleOperationApi = async (
574
580
  ? (project, stories) =>
575
581
  projectRepository.updateStoryList(project, stories)
576
582
  : null,
583
+ resolveProjectRepository: options.resolveProjectRepository ?? null,
584
+ invalidateProject: options.invalidateProject ?? null,
577
585
  };
578
586
  const dispatched = dispatchOperation(context, requestPath, body);
579
587
  if (dispatched === null) {
@@ -8,6 +8,7 @@ type FakeProcess = {
8
8
  comm: string;
9
9
  cmdline: string;
10
10
  environ: Record<string, string>;
11
+ cgroup?: string;
11
12
  };
12
13
 
13
14
  const issueUrl = 'https://github.com/HiromiShikata/example/issues/1';
@@ -41,6 +42,12 @@ describe('ProcClaudeHandoverSessionRepository', () => {
41
42
  .map(([key, value]) => `${key}=${value}\0`)
42
43
  .join('');
43
44
  fs.writeFileSync(path.join(processDirectory, 'environ'), environBuffer);
45
+ if (fakeProcess.cgroup !== undefined) {
46
+ fs.writeFileSync(
47
+ path.join(processDirectory, 'cgroup'),
48
+ `0::${fakeProcess.cgroup}\n`,
49
+ );
50
+ }
44
51
  };
45
52
 
46
53
  it('classifies an issue-URL leader from a cl --name url launch', () => {
@@ -61,6 +68,7 @@ describe('ProcClaudeHandoverSessionRepository', () => {
61
68
  sessionName: issueUrl.replace(/[.:]/g, '_'),
62
69
  name: issueUrl,
63
70
  issueUrl,
71
+ runsUnderWorkspacePreparationScript: false,
64
72
  },
65
73
  ]);
66
74
  });
@@ -83,6 +91,7 @@ describe('ProcClaudeHandoverSessionRepository', () => {
83
91
  sessionName: 'app',
84
92
  name: 'app',
85
93
  issueUrl: null,
94
+ runsUnderWorkspacePreparationScript: false,
86
95
  },
87
96
  ]);
88
97
  });
@@ -110,6 +119,7 @@ describe('ProcClaudeHandoverSessionRepository', () => {
110
119
  sessionName: null,
111
120
  name: null,
112
121
  issueUrl,
122
+ runsUnderWorkspacePreparationScript: false,
113
123
  },
114
124
  ]);
115
125
  });
@@ -184,6 +194,44 @@ describe('ProcClaudeHandoverSessionRepository', () => {
184
194
  });
185
195
  });
186
196
 
197
+ it('marks a session whose cgroup scope is an aw scope as launched by the workspace preparation script', () => {
198
+ writeProcess({
199
+ pid: 209,
200
+ comm: 'claude',
201
+ cmdline: argv(
202
+ 'claude',
203
+ '-p',
204
+ `Take ownership of ${issueUrl} and finish it`,
205
+ ),
206
+ environ: { CLAUDE_CODE_OAUTH_TOKEN: 'token-h' },
207
+ cgroup:
208
+ '/user.slice/user-1000.slice/user@1000.service/aiagents.slice/aw-HiromiShikata-example-1-4005530.scope',
209
+ });
210
+
211
+ const repository = new ProcClaudeHandoverSessionRepository(procDirectory);
212
+
213
+ expect(
214
+ repository.listHandoverSessions()[0].runsUnderWorkspacePreparationScript,
215
+ ).toBe(true);
216
+ });
217
+
218
+ it('does not mark a leader session whose cgroup scope is a cl scope as launched by the workspace preparation script', () => {
219
+ writeProcess({
220
+ pid: 210,
221
+ comm: 'claude',
222
+ cmdline: argv('claude', '--name', 'uminopm'),
223
+ environ: { CLAUDE_CODE_OAUTH_TOKEN: 'token-i' },
224
+ cgroup:
225
+ '/user.slice/user-1000.slice/user@1000.service/aiagents.slice/cl-uminopm.scope',
226
+ });
227
+
228
+ const repository = new ProcClaudeHandoverSessionRepository(procDirectory);
229
+
230
+ expect(
231
+ repository.listHandoverSessions()[0].runsUnderWorkspacePreparationScript,
232
+ ).toBe(false);
233
+ });
234
+
187
235
  it('returns an empty list when the proc directory cannot be read', () => {
188
236
  const repository = new ProcClaudeHandoverSessionRepository(
189
237
  path.join(procDirectory, 'does-not-exist'),
@@ -13,6 +13,7 @@ const NAME_ARGUMENT_PREFIX = '--name=';
13
13
  const PRINT_ARGUMENTS = ['-p', '--print'];
14
14
  const PRINT_ARGUMENT_PREFIXES = ['-p=', '--print='];
15
15
  const CLAUDE_COMM_PATTERN = /^claude(-agent)?$/;
16
+ const WORKSPACE_PREPARATION_SCOPE_PATTERN = /\/aw-[^/]*\.scope(\/|$)/;
16
17
  const ISSUE_OR_PR_URL_PATTERN =
17
18
  /https:\/\/github\.com\/[^\s\0]+?\/(?:issues|pull)\/\d+/;
18
19
 
@@ -69,6 +70,7 @@ const classifySession = (
69
70
  pid: number,
70
71
  commandArguments: string[],
71
72
  token: string | null,
73
+ runsUnderWorkspacePreparationScript: boolean,
72
74
  ): ClaudeHandoverSession | null => {
73
75
  if (token === null || token.length === 0) {
74
76
  return null;
@@ -85,10 +87,19 @@ const classifySession = (
85
87
  sessionName,
86
88
  name,
87
89
  issueUrl: extractIssueUrlFromText(name),
90
+ runsUnderWorkspacePreparationScript,
88
91
  };
89
92
  }
90
93
  const kind: ClaudeHandoverSessionKind = 'bareNameLeader';
91
- return { kind, pid, token, sessionName, name, issueUrl: null };
94
+ return {
95
+ kind,
96
+ pid,
97
+ token,
98
+ sessionName,
99
+ name,
100
+ issueUrl: null,
101
+ runsUnderWorkspacePreparationScript,
102
+ };
92
103
  }
93
104
  const issueUrl = parseIssueUrlFromPrint(commandArguments);
94
105
  if (issueUrl === null) {
@@ -101,6 +112,7 @@ const classifySession = (
101
112
  sessionName: null,
102
113
  name: null,
103
114
  issueUrl,
115
+ runsUnderWorkspacePreparationScript,
104
116
  };
105
117
  };
106
118
 
@@ -146,7 +158,48 @@ export class ProcClaudeHandoverSessionRepository implements ClaudeHandoverSessio
146
158
  return null;
147
159
  }
148
160
  const token = environ.get(OAUTH_TOKEN_ENVIRON_KEY) ?? null;
149
- return classifySession(Number(processIdDirectory), commandArguments, token);
161
+ return classifySession(
162
+ Number(processIdDirectory),
163
+ commandArguments,
164
+ token,
165
+ this.readRunsUnderWorkspacePreparationScript(processIdDirectory),
166
+ );
167
+ };
168
+
169
+ private readRunsUnderWorkspacePreparationScript = (
170
+ processIdDirectory: string,
171
+ ): boolean => {
172
+ const cgroupPath = path.join(
173
+ this.procDirectory,
174
+ processIdDirectory,
175
+ 'cgroup',
176
+ );
177
+ let raw: string;
178
+ try {
179
+ raw = fs.readFileSync(cgroupPath, 'utf8');
180
+ } catch (error) {
181
+ console.error(
182
+ `ProcClaudeHandoverSessionRepository: could not read ${cgroupPath}: ${
183
+ error instanceof Error ? error.message : String(error)
184
+ }`,
185
+ );
186
+ return false;
187
+ }
188
+ for (const line of raw.split('\n')) {
189
+ const firstSeparatorIndex = line.indexOf(':');
190
+ if (firstSeparatorIndex < 0) {
191
+ continue;
192
+ }
193
+ const secondSeparatorIndex = line.indexOf(':', firstSeparatorIndex + 1);
194
+ if (secondSeparatorIndex < 0) {
195
+ continue;
196
+ }
197
+ const cgroupHierarchyPath = line.slice(secondSeparatorIndex + 1);
198
+ if (WORKSPACE_PREPARATION_SCOPE_PATTERN.test(cgroupHierarchyPath)) {
199
+ return true;
200
+ }
201
+ }
202
+ return false;
150
203
  };
151
204
 
152
205
  private readComm = (processIdDirectory: string): string | null => {
@@ -8,4 +8,5 @@ export type ClaudeHandoverSession = {
8
8
  sessionName: string | null;
9
9
  name: string | null;
10
10
  issueUrl: string | null;
11
+ runsUnderWorkspacePreparationScript: boolean;
11
12
  };
@@ -6,6 +6,29 @@ export type FieldOption = {
6
6
  'GRAY' | 'BLUE' | 'GREEN' | 'YELLOW' | 'ORANGE' | 'RED' | 'PINK' | 'PURPLE';
7
7
  description: string;
8
8
  };
9
+
10
+ export type StoryListEntry = Omit<FieldOption, 'id'> & {
11
+ id: FieldOption['id'] | null;
12
+ };
13
+
14
+ export const buildStoryListWithNew = (
15
+ existingStories: StoryListEntry[],
16
+ newName: string,
17
+ ): StoryListEntry[] => {
18
+ if (existingStories.some((s) => s.name === newName)) {
19
+ return [...existingStories];
20
+ }
21
+ const newEntry: StoryListEntry = {
22
+ id: null,
23
+ name: newName,
24
+ color: 'RED',
25
+ description: '',
26
+ };
27
+ if (existingStories.length === 0) {
28
+ return [newEntry];
29
+ }
30
+ return [existingStories[0], newEntry, ...existingStories.slice(1)];
31
+ };
9
32
  export type Project = {
10
33
  id: string;
11
34
  url: string;
@@ -1,8 +1,12 @@
1
- import { IssueRepository } from './adapter-interfaces/IssueRepository';
2
- import { FieldOption, Project } from '../entities/Project';
3
- import { StoryObjectMap } from '../entities/StoryObjectMap';
4
- import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
5
- import { Issue } from '../entities/Issue';
1
+ import type { Issue } from '../entities/Issue';
2
+ import {
3
+ buildStoryListWithNew,
4
+ type Project,
5
+ type StoryListEntry,
6
+ } from '../entities/Project';
7
+ import type { StoryObjectMap } from '../entities/StoryObjectMap';
8
+ import type { IssueRepository } from './adapter-interfaces/IssueRepository';
9
+ import type { ProjectRepository } from './adapter-interfaces/ProjectRepository';
6
10
 
7
11
  const LOG_PREFIX = '[CreateNewStoryByLabel]';
8
12
 
@@ -130,35 +134,13 @@ export class CreateNewStoryByLabelUseCase {
130
134
  projectStory: NonNullable<Project['story']>,
131
135
  storyObjectMap: StoryObjectMap,
132
136
  issues: Issue[],
133
- ): (Omit<FieldOption, 'id'> & { id: FieldOption['id'] | null })[] => {
134
- const existingStoryNames = new Set(
135
- projectStory.stories.map((story) => story.name),
136
- );
137
- const newStoryIssues = this.findNewStoryIssues(
138
- storyObjectMap,
139
- issues,
140
- ).filter((issue) => !existingStoryNames.has(issue.title));
141
- const newStoryList: (Omit<FieldOption, 'id'> & {
142
- id: FieldOption['id'] | null;
143
- })[] = [];
144
- if (projectStory.stories.length > 0) {
145
- newStoryList.push(projectStory.stories[0]);
146
- }
147
- newStoryList.push(
148
- ...newStoryIssues.map(
149
- (i): Omit<FieldOption, 'id'> & { id: FieldOption['id'] | null } => ({
150
- id: null,
151
- name: i.title,
152
- color: 'RED',
153
- description: '',
154
- }),
155
- ),
156
- );
157
- if (projectStory.stories.length > 1) {
158
- newStoryList.push(
159
- ...projectStory.stories.slice(1, projectStory.stories.length),
137
+ ): StoryListEntry[] => {
138
+ const newStoryIssues = this.findNewStoryIssues(storyObjectMap, issues);
139
+ return [...newStoryIssues]
140
+ .reverse()
141
+ .reduce<StoryListEntry[]>(
142
+ (acc, issue) => buildStoryListWithNew(acc, issue.title),
143
+ [...projectStory.stories],
160
144
  );
161
- }
162
- return newStoryList;
163
145
  };
164
146
  }
@@ -55,6 +55,7 @@ const issueUrlLeaderSession = (): ClaudeHandoverSession => ({
55
55
  sessionName: ISSUE_URL_SESSION,
56
56
  name: ISSUE_URL,
57
57
  issueUrl: ISSUE_URL,
58
+ runsUnderWorkspacePreparationScript: false,
58
59
  });
59
60
 
60
61
  const bareNameLeaderSession = (): ClaudeHandoverSession => ({
@@ -64,6 +65,7 @@ const bareNameLeaderSession = (): ClaudeHandoverSession => ({
64
65
  sessionName: BARE_NAME,
65
66
  name: BARE_NAME,
66
67
  issueUrl: null,
68
+ runsUnderWorkspacePreparationScript: false,
67
69
  });
68
70
 
69
71
  const implSubagentSession = (): ClaudeHandoverSession => ({
@@ -73,6 +75,12 @@ const implSubagentSession = (): ClaudeHandoverSession => ({
73
75
  sessionName: null,
74
76
  name: null,
75
77
  issueUrl: ISSUE_URL,
78
+ runsUnderWorkspacePreparationScript: false,
79
+ });
80
+
81
+ const workspacePreparationSession = (): ClaudeHandoverSession => ({
82
+ ...implSubagentSession(),
83
+ runsUnderWorkspacePreparationScript: true,
76
84
  });
77
85
 
78
86
  const defaultInput = (
@@ -161,7 +169,7 @@ describe('TokenExhaustionHandoverUseCase', () => {
161
169
  await useCase.run(defaultInput({ enabled: false }));
162
170
 
163
171
  expect(logSpy).toHaveBeenCalledWith(
164
- 'Token exhaustion handover: cycle summary evaluated=0 enabled=false signaled=0 killed=0 terminatedPids=0 relaunched=0 leftAlive=0',
172
+ 'Token exhaustion handover: cycle summary evaluated=0 enabled=false signaled=0 killed=0 terminatedPids=0 relaunched=0 leftAlive=0 skippedWorkspacePreparation=0',
165
173
  );
166
174
  });
167
175
 
@@ -343,6 +351,56 @@ describe('TokenExhaustionHandoverUseCase', () => {
343
351
  });
344
352
  });
345
353
 
354
+ it('leaves a session launched by the workspace preparation script untouched instead of terminating it', async () => {
355
+ handoverSessionRepository.listHandoverSessions.mockReturnValue([
356
+ workspacePreparationSession(),
357
+ ]);
358
+ snapshotRepository.listSnapshots.mockReturnValue([
359
+ snapshot(TOKEN_EXHAUSTED, { fiveHourUtilization: 0.95 }),
360
+ snapshot(TOKEN_FRESH),
361
+ ]);
362
+
363
+ const result = await useCase.run(defaultInput());
364
+
365
+ expect(processSignalRepository.terminateProcess).not.toHaveBeenCalled();
366
+ expect(processSignalRepository.killProcess).not.toHaveBeenCalled();
367
+ expect(result.newlyHandoverSentSessionNames).toEqual([]);
368
+ expect(result.skippedWorkspacePreparationSessionNames).toEqual([
369
+ `pid:${IMPL_PID}`,
370
+ ]);
371
+ expect(result.state.entries).toEqual({});
372
+ });
373
+
374
+ it('does not force-kill a workspace preparation session whose grace period has elapsed', async () => {
375
+ handoverSessionRepository.listHandoverSessions.mockReturnValue([
376
+ workspacePreparationSession(),
377
+ ]);
378
+ snapshotRepository.listSnapshots.mockReturnValue([
379
+ snapshot(TOKEN_EXHAUSTED, { fiveHourUtilization: 0.95 }),
380
+ snapshot(TOKEN_FRESH),
381
+ ]);
382
+ processSignalRepository.isProcessAlive.mockReturnValue(true);
383
+
384
+ const result = await useCase.run(
385
+ defaultInput({
386
+ state: {
387
+ entries: {
388
+ [`pid:${IMPL_PID}`]: {
389
+ signaledAtEpoch:
390
+ nowEpochSeconds -
391
+ DEFAULT_TOKEN_EXHAUSTION_GRACE_PERIOD_SECONDS -
392
+ 1,
393
+ pid: IMPL_PID,
394
+ },
395
+ },
396
+ },
397
+ }),
398
+ );
399
+
400
+ expect(processSignalRepository.killProcess).not.toHaveBeenCalled();
401
+ expect(result.terminatedPids).toEqual([]);
402
+ });
403
+
346
404
  it('does not treat an impl subagent as exhausted on seven-day utilization alone', async () => {
347
405
  handoverSessionRepository.listHandoverSessions.mockReturnValue([
348
406
  implSubagentSession(),
@@ -51,6 +51,7 @@ export type TokenExhaustionHandoverResult = {
51
51
  terminatedPids: number[];
52
52
  relaunchedLeaderNames: string[];
53
53
  leftAliveSessionNames: string[];
54
+ skippedWorkspacePreparationSessionNames: string[];
54
55
  state: TokenExhaustionHandoverState;
55
56
  };
56
57
 
@@ -95,9 +96,20 @@ export class TokenExhaustionHandoverUseCase {
95
96
  const terminatedPids: number[] = [];
96
97
  const relaunchedLeaderNames: string[] = [];
97
98
  const leftAliveSessionNames: string[] = [];
99
+ const skippedWorkspacePreparationSessionNames: string[] = [];
98
100
 
99
101
  for (const session of sessions) {
100
102
  try {
103
+ if (session.runsUnderWorkspacePreparationScript) {
104
+ console.log(
105
+ `Token exhaustion handover: skipping ${this.displayName(session)} kind=${session.kind} (launched by the workspace preparation script, which owns its own lifecycle)`,
106
+ );
107
+ skippedWorkspacePreparationSessionNames.push(
108
+ this.displayName(session),
109
+ );
110
+ delete nextEntries[this.stateKeyFor(session)];
111
+ continue;
112
+ }
101
113
  const snapshot = snapshotByToken.get(session.token);
102
114
  if (snapshot === undefined) {
103
115
  continue;
@@ -200,7 +212,7 @@ export class TokenExhaustionHandoverUseCase {
200
212
  }
201
213
 
202
214
  console.log(
203
- `Token exhaustion handover: cycle summary evaluated=${sessions.length} enabled=${input.enabled} signaled=${newlyHandoverSentSessionNames.length} killed=${killedSessionNames.length} terminatedPids=${terminatedPids.length} relaunched=${relaunchedLeaderNames.length} leftAlive=${leftAliveSessionNames.length}`,
215
+ `Token exhaustion handover: cycle summary evaluated=${sessions.length} enabled=${input.enabled} signaled=${newlyHandoverSentSessionNames.length} killed=${killedSessionNames.length} terminatedPids=${terminatedPids.length} relaunched=${relaunchedLeaderNames.length} leftAlive=${leftAliveSessionNames.length} skippedWorkspacePreparation=${skippedWorkspacePreparationSessionNames.length}`,
204
216
  );
205
217
 
206
218
  return {
@@ -209,6 +221,7 @@ export class TokenExhaustionHandoverUseCase {
209
221
  terminatedPids,
210
222
  relaunchedLeaderNames,
211
223
  leftAliveSessionNames,
224
+ skippedWorkspacePreparationSessionNames,
212
225
  state: { entries: nextEntries },
213
226
  };
214
227
  };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- export { ConfigFile, loadConfigFile, parseProjectReadmeConfig, mergeConfigs, fetchProjectReadme, } from './projectConfig';
3
+ export { ConfigFile, fetchProjectReadme, loadConfigFile, mergeConfigs, parseProjectReadmeConfig, } from './projectConfig';
4
4
  export declare const program: Command;
5
5
  export declare const reportFatalErrorAndExit: (error: unknown) => void;
6
6
  export declare const runCliProgram: (argv: string[], handleFatalError: (error: unknown) => void) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,UAAU,EACV,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAkMzB,eAAO,MAAM,OAAO,SAAgB,CAAC;AAoiCrC,eAAO,MAAM,uBAAuB,GAAI,OAAO,OAAO,KAAG,IAGxD,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EAAE,EACd,kBAAkB,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KACzC,OAAO,CAAC,IAAI,CAMd,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AAmMzB,eAAO,MAAM,OAAO,SAAgB,CAAC;AAuiCrC,eAAO,MAAM,uBAAuB,GAAI,OAAO,OAAO,KAAG,IAGxD,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EAAE,EACd,kBAAkB,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KACzC,OAAO,CAAC,IAAI,CAMd,CAAC"}
@@ -1,6 +1,7 @@
1
- import { IssueRepository } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
2
- import { IssueAttachmentRepository } from '../../../domain/usecases/adapter-interfaces/IssueAttachmentRepository';
3
- import { FieldOption, Project } from '../../../domain/entities/Project';
1
+ import { type FieldOption, type Project } from '../../../domain/entities/Project';
2
+ import type { IssueAttachmentRepository } from '../../../domain/usecases/adapter-interfaces/IssueAttachmentRepository';
3
+ import type { IssueRepository } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
4
+ import type { ProjectRepository } from '../../../domain/usecases/adapter-interfaces/ProjectRepository';
4
5
  export declare const AWAITING_WORKSPACE_STATUS_NAME = "awaiting workspace";
5
6
  export declare const IN_TMUX_BY_HUMAN_STATUS_NAME = "in tmux by human";
6
7
  export declare const CHORE_LABEL_NAME = "chore";
@@ -11,6 +12,7 @@ export type ConsoleProjectBinding = {
11
12
  export type ConsoleProjectResolver = (pjcode: string) => Promise<ConsoleProjectBinding | null>;
12
13
  export type ConsolePjcodeValidator = (pjcode: string) => boolean;
13
14
  export type ConsoleIssueRepositoryResolver = (issueOrPullRequestUrl: string) => IssueRepository;
15
+ export type ConsoleProjectRepositoryResolver = (projectUrl: string) => Pick<ProjectRepository, 'updateStoryList'>;
14
16
  export type ConsoleOperationContext = {
15
17
  resolveIssueRepository: ConsoleIssueRepositoryResolver;
16
18
  resolveProject: ConsoleProjectResolver;
@@ -18,6 +20,8 @@ export type ConsoleOperationContext = {
18
20
  consoleDataOutputDir: string | null;
19
21
  issueAttachmentRepository: IssueAttachmentRepository | null;
20
22
  updateStoryList: ((project: Project, stories: FieldOption[]) => Promise<FieldOption[]>) | null;
23
+ resolveProjectRepository: ConsoleProjectRepositoryResolver | null;
24
+ invalidateProject: ((pjcode: string) => void) | null;
21
25
  };
22
26
  export type ConsoleOperationResponse = {
23
27
  statusCode: number;
@@ -31,4 +35,5 @@ export declare const handleCreateIssue: (context: ConsoleOperationContext, body:
31
35
  export declare const handleReviewComment: (context: ConsoleOperationContext, body: Record<string, unknown>) => Promise<ConsoleOperationResponse>;
32
36
  export declare const handleIntmux: (context: ConsoleOperationContext, body: Record<string, unknown>) => Promise<ConsoleOperationResponse>;
33
37
  export declare const handleReorderStory: (context: ConsoleOperationContext, body: Record<string, unknown>) => Promise<ConsoleOperationResponse>;
38
+ export declare const handleStoryAdd: (context: ConsoleOperationContext, body: Record<string, unknown>) => Promise<ConsoleOperationResponse>;
34
39
  //# sourceMappingURL=consoleOperationApi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consoleOperationApi.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/console/consoleOperationApi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEhB,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,uEAAuE,CAAC;AAClH,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAUxE,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,4BAA4B,qBAAqB,CAAC;AAC/D,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,CACnC,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;AAE3C,MAAM,MAAM,sBAAsB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;AAEjE,MAAM,MAAM,8BAA8B,GAAG,CAC3C,qBAAqB,EAAE,MAAM,KAC1B,eAAe,CAAC;AAErB,MAAM,MAAM,uBAAuB,GAAG;IACpC,sBAAsB,EAAE,8BAA8B,CAAC;IACvD,cAAc,EAAE,sBAAsB,CAAC;IACvC,kBAAkB,EAAE,sBAAsB,CAAC;IAC3C,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,yBAAyB,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC5D,eAAe,EACX,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,GACtE,IAAI,CAAC;CACV,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AA8MF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAyJlC,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CA6FlC,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CA8BlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CA2ClC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CA0DlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CA6BlC,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAiClC,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAuClC,CAAC"}
1
+ {"version":3,"file":"consoleOperationApi.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/console/consoleOperationApi.ts"],"names":[],"mappings":"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"}
@@ -8,5 +8,9 @@ export type ConsoleProjectIdAndProjectReader = {
8
8
  export declare const createConsoleProjectLoader: (resolveProjectRepository: (projectUrl: string) => ConsoleProjectIdAndProjectReader, getCachedProject: (projectId: string) => Promise<Project | null>, reportLoadFailure: (message: string) => void) => ConsoleProjectLoader;
9
9
  export declare const buildPjcodeToProjectUrl: (defaultPjcode: string, defaultProjectUrl: string, consoleProjects: Record<string, string> | null) => Record<string, string>;
10
10
  export declare const createPjcodeConfigChecker: (pjcodeToProjectUrl: Record<string, string>) => ConsolePjcodeValidator;
11
- export declare const createConsoleProjectResolver: (pjcodeToProjectUrl: Record<string, string>, loadProject: ConsoleProjectLoader) => ConsoleProjectResolver;
11
+ export type ConsoleProjectResolverBundle = {
12
+ resolve: ConsoleProjectResolver;
13
+ invalidate: (pjcode: string) => void;
14
+ };
15
+ export declare const createConsoleProjectResolver: (pjcodeToProjectUrl: Record<string, string>, loadProject: ConsoleProjectLoader) => ConsoleProjectResolverBundle;
12
16
  //# sourceMappingURL=consoleProjectResolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consoleProjectResolver.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/console/consoleProjectResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAC3D,OAAO,EACL,sBAAsB,EAEtB,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,oBAAoB,GAAG,CACjC,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AAE7B,MAAM,MAAM,gCAAgC,GAAG;IAC7C,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACnE,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC5D,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,0BAA0B,CACxB,UAAU,EAAE,MAAM,KACf,gCAAgC,EACrC,kBAAkB,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,EAChE,mBAAmB,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,KAC3C,oBAmBF,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,eAAe,MAAM,EACrB,mBAAmB,MAAM,EACzB,iBAAiB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAC7C,MAAM,CAAC,MAAM,EAAE,MAAM,CAWvB,CAAC;AAKF,eAAO,MAAM,yBAAyB,GACpC,oBAAoB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACzC,sBAGF,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,oBAAoB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1C,aAAa,oBAAoB,KAChC,sBAmBF,CAAC"}
1
+ {"version":3,"file":"consoleProjectResolver.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/console/consoleProjectResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAC3D,OAAO,EACL,sBAAsB,EAEtB,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,oBAAoB,GAAG,CACjC,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AAE7B,MAAM,MAAM,gCAAgC,GAAG;IAC7C,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACnE,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC5D,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,0BAA0B,CACxB,UAAU,EAAE,MAAM,KACf,gCAAgC,EACrC,kBAAkB,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,EAChE,mBAAmB,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,KAC3C,oBAmBF,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,eAAe,MAAM,EACrB,mBAAmB,MAAM,EACzB,iBAAiB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAC7C,MAAM,CAAC,MAAM,EAAE,MAAM,CAWvB,CAAC;AAKF,eAAO,MAAM,yBAAyB,GACpC,oBAAoB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACzC,sBAGF,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,EAAE,sBAAsB,CAAC;IAChC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,oBAAoB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1C,aAAa,oBAAoB,KAChC,4BAyBF,CAAC"}
@@ -3,7 +3,7 @@ import { IssueAttachmentRepository } from '../../../domain/usecases/adapter-inte
3
3
  import { IssueRepository } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
4
4
  import { ProjectRepository } from '../../../domain/usecases/adapter-interfaces/ProjectRepository';
5
5
  import { IssueTitleStateCache, PullRequestStatusCache } from './consoleReadApi';
6
- import { ConsoleIssueRepositoryResolver, ConsolePjcodeValidator, ConsoleProjectResolver } from './consoleOperationApi';
6
+ import { ConsoleIssueRepositoryResolver, ConsolePjcodeValidator, ConsoleProjectRepositoryResolver, ConsoleProjectResolver } from './consoleOperationApi';
7
7
  import { ImageFetcher } from './consoleImageProxy';
8
8
  import { ConsoleGithubTokenResolver } from './consoleGithubTokenResolver';
9
9
  export declare const DEFAULT_WEB_PORT = 9980;
@@ -32,6 +32,8 @@ export type WebServerOptions = {
32
32
  resolveIssueRepository?: ConsoleIssueRepositoryResolver | null;
33
33
  resolveProject?: ConsoleProjectResolver | null;
34
34
  isPjcodeConfigured?: ConsolePjcodeValidator | null;
35
+ resolveProjectRepository?: ConsoleProjectRepositoryResolver | null;
36
+ invalidateProject?: ((pjcode: string) => void) | null;
35
37
  issueAttachmentRepository?: IssueAttachmentRepository | null;
36
38
  projectRepository?: Pick<ProjectRepository, 'updateStoryList'> | null;
37
39
  issueTitleStateCache?: IssueTitleStateCache | null;