github-issue-tower-defence-management 1.145.0 → 1.146.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 (94) hide show
  1. package/CHANGELOG.md +22 -8
  2. package/README.md +1 -1
  3. package/bin/adapter/entry-points/cli/index.js +9 -7
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/console/ensureConsoleRunning.js +6 -1
  6. package/bin/adapter/entry-points/console/ensureConsoleRunning.js.map +1 -1
  7. package/bin/adapter/entry-points/console/webServer.js +1 -3
  8. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +3 -1
  10. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  11. package/bin/adapter/repositories/GraphqlProjectRepository.js +75 -4
  12. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
  13. package/bin/adapter/repositories/utils.js +2 -4
  14. package/bin/adapter/repositories/utils.js.map +1 -1
  15. package/bin/domain/entities/ProjectFieldName.js +6 -0
  16. package/bin/domain/entities/ProjectFieldName.js.map +1 -0
  17. package/bin/domain/entities/RequiredProjectField.js +47 -0
  18. package/bin/domain/entities/RequiredProjectField.js.map +1 -0
  19. package/bin/domain/usecases/HandleScheduledEventUseCase.js +5 -1
  20. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  21. package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js +25 -0
  22. package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js.map +1 -0
  23. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +6 -1
  24. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -1
  25. package/bin/domain/usecases/dashboard/DashboardProjectCode.js +17 -12
  26. package/bin/domain/usecases/dashboard/DashboardProjectCode.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/adapter/entry-points/cli/index.test.ts +62 -2
  29. package/src/adapter/entry-points/cli/index.ts +12 -11
  30. package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +37 -39
  31. package/src/adapter/entry-points/console/consoleDoneStore.test.ts +33 -33
  32. package/src/adapter/entry-points/console/consoleItemUrlLookup.test.ts +6 -6
  33. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +66 -66
  34. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +35 -35
  35. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +28 -28
  36. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +26 -18
  37. package/src/adapter/entry-points/console/ensureConsoleRunning.test.ts +19 -2
  38. package/src/adapter/entry-points/console/ensureConsoleRunning.ts +8 -0
  39. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +1 -1
  40. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +1 -1
  41. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +2 -2
  42. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +3 -3
  43. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +19 -31
  44. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +24 -32
  45. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +6 -6
  46. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +4 -4
  47. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +8 -8
  48. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +5 -5
  49. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +1 -1
  50. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +13 -13
  51. package/src/adapter/entry-points/console/webServer.test.ts +55 -52
  52. package/src/adapter/entry-points/console/webServer.ts +0 -3
  53. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +2 -2
  54. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +4 -0
  55. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +5 -5
  56. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +5 -5
  57. package/src/adapter/repositories/GraphqlProjectRepository.ts +110 -4
  58. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +1 -1
  59. package/src/adapter/repositories/utils.ts +3 -3
  60. package/src/domain/entities/ProjectFieldName.ts +2 -0
  61. package/src/domain/entities/RequiredProjectField.ts +64 -0
  62. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -0
  63. package/src/domain/usecases/HandleScheduledEventUseCase.ts +5 -0
  64. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +3 -3
  65. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +159 -0
  66. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +32 -0
  67. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +19 -19
  68. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +24 -14
  69. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +10 -1
  70. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -0
  71. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +28 -28
  72. package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +21 -19
  73. package/src/domain/usecases/dashboard/DashboardProjectCode.ts +21 -14
  74. package/types/adapter/entry-points/console/ensureConsoleRunning.d.ts +1 -1
  75. package/types/adapter/entry-points/console/ensureConsoleRunning.d.ts.map +1 -1
  76. package/types/adapter/entry-points/console/webServer.d.ts +0 -1
  77. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  78. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  79. package/types/adapter/repositories/GraphqlProjectRepository.d.ts +4 -1
  80. package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
  81. package/types/adapter/repositories/utils.d.ts.map +1 -1
  82. package/types/domain/entities/ProjectFieldName.d.ts +2 -0
  83. package/types/domain/entities/ProjectFieldName.d.ts.map +1 -0
  84. package/types/domain/entities/RequiredProjectField.d.ts +13 -0
  85. package/types/domain/entities/RequiredProjectField.d.ts.map +1 -0
  86. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +3 -1
  87. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  88. package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts +10 -0
  89. package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts.map +1 -0
  90. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -1
  91. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +3 -0
  92. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -1
  93. package/types/domain/usecases/dashboard/DashboardProjectCode.d.ts +2 -2
  94. package/types/domain/usecases/dashboard/DashboardProjectCode.d.ts.map +1 -1
@@ -69,25 +69,25 @@ describe('webServer pure helpers', () => {
69
69
 
70
70
  describe('isConsoleAppRoute', () => {
71
71
  it('matches a per-project root route', () => {
72
- expect(isConsoleAppRoute('/projects/umino')).toBe(true);
73
- expect(isConsoleAppRoute('/projects/umino/')).toBe(true);
72
+ expect(isConsoleAppRoute('/projects/acme')).toBe(true);
73
+ expect(isConsoleAppRoute('/projects/acme/')).toBe(true);
74
74
  });
75
75
 
76
76
  it('matches a per-project tab route for every list tab', () => {
77
- expect(isConsoleAppRoute('/projects/umino/workflow-blocker')).toBe(true);
78
- expect(isConsoleAppRoute('/projects/umino/prs')).toBe(true);
79
- expect(isConsoleAppRoute('/projects/xmile/triage')).toBe(true);
80
- expect(isConsoleAppRoute('/projects/xcare/unread')).toBe(true);
81
- expect(isConsoleAppRoute('/projects/utage3/failed-preparation')).toBe(
77
+ expect(isConsoleAppRoute('/projects/acme/workflow-blocker')).toBe(true);
78
+ expect(isConsoleAppRoute('/projects/acme/prs')).toBe(true);
79
+ expect(isConsoleAppRoute('/projects/globex/triage')).toBe(true);
80
+ expect(isConsoleAppRoute('/projects/initech/unread')).toBe(true);
81
+ expect(isConsoleAppRoute('/projects/umbrella/failed-preparation')).toBe(
82
82
  true,
83
83
  );
84
- expect(isConsoleAppRoute('/projects/utage3/todo-by-human')).toBe(true);
85
- expect(isConsoleAppRoute('/projects/utage3/todo-by-agent')).toBe(true);
84
+ expect(isConsoleAppRoute('/projects/umbrella/todo-by-human')).toBe(true);
85
+ expect(isConsoleAppRoute('/projects/umbrella/todo-by-agent')).toBe(true);
86
86
  });
87
87
 
88
88
  it('does not match data, api, or unknown tab routes', () => {
89
- expect(isConsoleAppRoute('/projects/umino/prs/list.json')).toBe(false);
90
- expect(isConsoleAppRoute('/projects/umino/unknown')).toBe(false);
89
+ expect(isConsoleAppRoute('/projects/acme/prs/list.json')).toBe(false);
90
+ expect(isConsoleAppRoute('/projects/acme/unknown')).toBe(false);
91
91
  expect(isConsoleAppRoute('/projects')).toBe(false);
92
92
  expect(isConsoleAppRoute('/api/review')).toBe(false);
93
93
  expect(isConsoleAppRoute('/')).toBe(false);
@@ -339,17 +339,20 @@ describe('webServer integration', () => {
339
339
  port: 0,
340
340
  });
341
341
  try {
342
- const projectRoot = await requestServer(server, '/projects/umino');
342
+ const projectRoot = await requestServer(server, '/projects/acme');
343
343
  expect(projectRoot.statusCode).toBe(200);
344
344
  expect(projectRoot.body).toContain('spa');
345
345
  expect(projectRoot.contentType).toContain('text/html');
346
346
  expect(projectRoot.cacheControl).toBe('no-store');
347
347
 
348
- const projectTab = await requestServer(server, '/projects/xmile/prs');
348
+ const projectTab = await requestServer(server, '/projects/globex/prs');
349
349
  expect(projectTab.statusCode).toBe(200);
350
350
  expect(projectTab.body).toContain('spa');
351
351
 
352
- const unknownTab = await requestServer(server, '/projects/xmile/unknown');
352
+ const unknownTab = await requestServer(
353
+ server,
354
+ '/projects/globex/unknown',
355
+ );
353
356
  expect(unknownTab.statusCode).toBe(404);
354
357
  } finally {
355
358
  await closeServer(server);
@@ -370,7 +373,7 @@ describe('webServer integration', () => {
370
373
  port: 0,
371
374
  });
372
375
  try {
373
- const projectRoot = await requestServer(server, '/projects/umino/triage');
376
+ const projectRoot = await requestServer(server, '/projects/acme/triage');
374
377
  expect(projectRoot.statusCode).toBe(200);
375
378
  expect(projectRoot.body).toContain('TDPM Console');
376
379
  } finally {
@@ -539,12 +542,12 @@ describe('webServer new routes integration', () => {
539
542
  it('serves a data list file with the done exclusion through the token gate', async () => {
540
543
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
541
544
  const dataDir = path.join(tmpDir, 'data');
542
- const listDir = path.join(dataDir, 'umino', 'prs');
545
+ const listDir = path.join(dataDir, 'acme', 'prs');
543
546
  fs.mkdirSync(listDir, { recursive: true });
544
547
  fs.writeFileSync(
545
548
  path.join(listDir, 'list.json'),
546
549
  JSON.stringify({
547
- pjcode: 'umino',
550
+ pjcode: 'acme',
548
551
  items: [{ projectItemId: 'PVTI_keep' }, { projectItemId: 'PVTI_drop' }],
549
552
  }),
550
553
  );
@@ -566,19 +569,19 @@ describe('webServer new routes integration', () => {
566
569
  const unauthorized = await request(
567
570
  server,
568
571
  'GET',
569
- '/projects/umino/prs/list.json',
572
+ '/projects/acme/prs/list.json',
570
573
  );
571
574
  expect(unauthorized.statusCode).toBe(401);
572
575
 
573
576
  const authorized = await request(
574
577
  server,
575
578
  'GET',
576
- `/projects/umino/prs/list.json?k=${testToken}`,
579
+ `/projects/acme/prs/list.json?k=${testToken}`,
577
580
  );
578
581
  expect(authorized.statusCode).toBe(200);
579
582
  const parsed: unknown = JSON.parse(authorized.body);
580
583
  expect(parsed).toEqual({
581
- pjcode: 'umino',
584
+ pjcode: 'acme',
582
585
  items: [{ projectItemId: 'PVTI_keep' }],
583
586
  });
584
587
  } finally {
@@ -690,8 +693,8 @@ describe('webServer new routes integration', () => {
690
693
  dashboardProjectNames: [],
691
694
  issueRepository,
692
695
  resolveProject: async (pjcode) =>
693
- pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
694
- isPjcodeConfigured: (pjcode) => pjcode === 'umino',
696
+ pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
697
+ isPjcodeConfigured: (pjcode) => pjcode === 'acme',
695
698
  port: 0,
696
699
  });
697
700
  try {
@@ -700,7 +703,7 @@ describe('webServer new routes integration', () => {
700
703
  'POST',
701
704
  `/api/comment?k=${testToken}`,
702
705
  {
703
- pjcode: 'umino',
706
+ pjcode: 'acme',
704
707
  url: 'https://github.com/o/r/issues/1',
705
708
  body: 'Thanks, this resolves the parity gap.',
706
709
  },
@@ -742,8 +745,8 @@ describe('webServer new routes integration', () => {
742
745
  dashboardProjectNames: [],
743
746
  issueRepository,
744
747
  resolveProject: async (pjcode) =>
745
- pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
746
- isPjcodeConfigured: (pjcode) => pjcode === 'umino',
748
+ pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
749
+ isPjcodeConfigured: (pjcode) => pjcode === 'acme',
747
750
  port: 0,
748
751
  });
749
752
  try {
@@ -752,7 +755,7 @@ describe('webServer new routes integration', () => {
752
755
  'POST',
753
756
  `/api/review?k=${testToken}`,
754
757
  {
755
- pjcode: 'umino',
758
+ pjcode: 'acme',
756
759
  action: 'approve',
757
760
  prUrl: 'https://github.com/o/r/pull/1',
758
761
  projectItemId: 'PVTI_op',
@@ -762,7 +765,7 @@ describe('webServer new routes integration', () => {
762
765
  expect(issueRepository.approvePullRequest).toHaveBeenCalledWith(
763
766
  'https://github.com/o/r/pull/1',
764
767
  );
765
- expect(readDoneProjectItemIds(dataDir, 'umino', 'prs')).toContain(
768
+ expect(readDoneProjectItemIds(dataDir, 'acme', 'prs')).toContain(
766
769
  'PVTI_op',
767
770
  );
768
771
  } finally {
@@ -791,8 +794,8 @@ describe('webServer new routes integration', () => {
791
794
  dashboardProjectNames: [],
792
795
  issueRepository,
793
796
  resolveProject: async (pjcode) =>
794
- pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
795
- isPjcodeConfigured: (pjcode) => pjcode === 'umino',
797
+ pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
798
+ isPjcodeConfigured: (pjcode) => pjcode === 'acme',
796
799
  port: 0,
797
800
  });
798
801
  try {
@@ -801,7 +804,7 @@ describe('webServer new routes integration', () => {
801
804
  'POST',
802
805
  `/api/review?k=${testToken}`,
803
806
  {
804
- pjcode: 'umino',
807
+ pjcode: 'acme',
805
808
  action: 'approve',
806
809
  prUrl: 'https://github.com/o/r/pull/1',
807
810
  projectItemId: 'PVTI_op',
@@ -830,8 +833,8 @@ describe('webServer new routes integration', () => {
830
833
  dashboardProjectNames: [],
831
834
  issueRepository,
832
835
  resolveProject: async (pjcode) =>
833
- pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
834
- isPjcodeConfigured: (pjcode) => pjcode === 'umino',
836
+ pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
837
+ isPjcodeConfigured: (pjcode) => pjcode === 'acme',
835
838
  port: 0,
836
839
  });
837
840
  try {
@@ -912,7 +915,7 @@ describe('resolveFlatInTmuxFilePath', () => {
912
915
  it('returns null for paths outside the flat in-tmux prefix', () => {
913
916
  expect(resolveFlatInTmuxFilePath(baseDir, '/index.v4.json')).toBeNull();
914
917
  expect(
915
- resolveFlatInTmuxFilePath(baseDir, '/projects/umino/in-tmux-by-human/x'),
918
+ resolveFlatInTmuxFilePath(baseDir, '/projects/acme/in-tmux-by-human/x'),
916
919
  ).toBeNull();
917
920
  });
918
921
 
@@ -1008,8 +1011,8 @@ describe('webServer flat in-tmux-by-human route integration', () => {
1008
1011
  });
1009
1012
 
1010
1013
  const indexV4Raw =
1011
- '{"version":4,"projects":[{"name":"umino","path":"/in-tmux-by-human/umino.v4.json?k=secret"}]}\n';
1012
- const indexV3Raw = '{"version":3,"projects":["umino"]}\n';
1014
+ '{"version":4,"projects":[{"name":"acme","path":"/in-tmux-by-human/acme.v4.json?k=secret"}]}\n';
1015
+ const indexV3Raw = '{"version":3,"projects":["acme"]}\n';
1013
1016
 
1014
1017
  const startWithFixture = async (): Promise<{
1015
1018
  server: http.Server;
@@ -1218,9 +1221,9 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
1218
1221
  const writeDataFiles = (dataDir: string): void => {
1219
1222
  fs.mkdirSync(path.join(dataDir, 'projects'), { recursive: true });
1220
1223
  fs.writeFileSync(
1221
- path.join(dataDir, 'projects', 'umino.json'),
1224
+ path.join(dataDir, 'projects', 'acme.json'),
1222
1225
  JSON.stringify({
1223
- pjcode: 'umino',
1226
+ pjcode: 'acme',
1224
1227
  capturedAt: '2026-06-26T00:00:00.000Z',
1225
1228
  unread: 3,
1226
1229
  todo: 1,
@@ -1233,9 +1236,9 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
1233
1236
  }),
1234
1237
  );
1235
1238
  fs.writeFileSync(
1236
- path.join(dataDir, 'projects', 'xcare.json'),
1239
+ path.join(dataDir, 'projects', 'initech.json'),
1237
1240
  JSON.stringify({
1238
- pjcode: 'xcare',
1241
+ pjcode: 'initech',
1239
1242
  capturedAt: '2026-06-26T00:00:00.000Z',
1240
1243
  unread: 0,
1241
1244
  todo: 0,
@@ -1282,8 +1285,8 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
1282
1285
  '<tt>M55%&nbsp;C62%&nbsp;D89%&nbsp;cy14</tt><br>\n' +
1283
1286
  '<tt>LA&nbsp;16&nbsp;23&nbsp;40</tt><br>\n' +
1284
1287
  '<tt>pj&nbsp;&nbsp;&nbsp;unr&nbsp;tdo&nbsp;aqc&nbsp;fal&nbsp;prp&nbsp;aws&nbsp;dep</tt><br>\n' +
1285
- '<tt>🟢um&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;1</tt><br>\n' +
1286
- '<tt>🟢xc&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0</tt><br>\n' +
1288
+ '<tt>🟢ac&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;1</tt><br>\n' +
1289
+ '<tt>🟢in&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0</tt><br>\n' +
1287
1290
  '<tt></tt><br>\n' +
1288
1291
  '<tt>🟢alice&nbsp;&nbsp;10%&nbsp;0d01h00&nbsp;&nbsp;12%&nbsp;5d00h00&nbsp;2&nbsp;1</tt><br>\n';
1289
1292
 
@@ -1306,7 +1309,7 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
1306
1309
  inTmuxDataDir: null,
1307
1310
  dashboardDir: overrides.dashboardDir,
1308
1311
  dashboardDataDir: overrides.dashboardDataDir,
1309
- dashboardProjectNames: ['umino', 'xcare'],
1312
+ dashboardProjectNames: ['acme', 'initech'],
1310
1313
  port: 0,
1311
1314
  });
1312
1315
  return { server, tmpDir };
@@ -1748,10 +1751,10 @@ describe('webServer token cookie redirect', () => {
1748
1751
  try {
1749
1752
  const response = await requestServer(
1750
1753
  server,
1751
- `/projects/umino?k=${testToken}`,
1754
+ `/projects/acme?k=${testToken}`,
1752
1755
  );
1753
1756
  expect(response.statusCode).toBe(302);
1754
- expect(response.location).toBe('/projects/umino');
1757
+ expect(response.location).toBe('/projects/acme');
1755
1758
  expect(response.body).not.toContain(testToken);
1756
1759
  const cookie = firstSetCookie(response.setCookie);
1757
1760
  expect(cookie).toContain(`${CONSOLE_TOKEN_COOKIE}=${testToken}`);
@@ -1770,10 +1773,10 @@ describe('webServer token cookie redirect', () => {
1770
1773
  try {
1771
1774
  const response = await requestServer(
1772
1775
  server,
1773
- `/projects/xmile/prs?k=${testToken}&foo=bar`,
1776
+ `/projects/globex/prs?k=${testToken}&foo=bar`,
1774
1777
  );
1775
1778
  expect(response.statusCode).toBe(302);
1776
- expect(response.location).toBe('/projects/xmile/prs?foo=bar');
1779
+ expect(response.location).toBe('/projects/globex/prs?foo=bar');
1777
1780
  const cookie = firstSetCookie(response.setCookie);
1778
1781
  expect(cookie).toContain(`${CONSOLE_TOKEN_COOKIE}=${testToken}`);
1779
1782
  } finally {
@@ -1807,7 +1810,7 @@ describe('webServer token cookie redirect', () => {
1807
1810
  it('serves the app route without a token and never leaks a token in the URL', async () => {
1808
1811
  const { server, tmpDir } = await startWithUiDist();
1809
1812
  try {
1810
- const response = await requestServer(server, '/projects/umino');
1813
+ const response = await requestServer(server, '/projects/acme');
1811
1814
  expect(response.statusCode).toBe(200);
1812
1815
  expect(response.body).toContain('spa');
1813
1816
  expect(response.setCookie).toBeUndefined();
@@ -1825,7 +1828,7 @@ describe('webServer token cookie redirect', () => {
1825
1828
  expect(root.statusCode).toBe(200);
1826
1829
  expect(root.referrerPolicy).toBe('no-referrer');
1827
1830
 
1828
- const projectTab = await requestServer(server, '/projects/xmile/prs');
1831
+ const projectTab = await requestServer(server, '/projects/globex/prs');
1829
1832
  expect(projectTab.statusCode).toBe(200);
1830
1833
  expect(projectTab.referrerPolicy).toBe('no-referrer');
1831
1834
  } finally {
@@ -1837,11 +1840,11 @@ describe('webServer token cookie redirect', () => {
1837
1840
  it('authenticates a data list request through the cookie without a token in the URL', async () => {
1838
1841
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1839
1842
  const dataDir = path.join(tmpDir, 'data');
1840
- const listDir = path.join(dataDir, 'umino', 'prs');
1843
+ const listDir = path.join(dataDir, 'acme', 'prs');
1841
1844
  fs.mkdirSync(listDir, { recursive: true });
1842
1845
  fs.writeFileSync(
1843
1846
  path.join(listDir, 'list.json'),
1844
- JSON.stringify({ pjcode: 'umino', items: [] }),
1847
+ JSON.stringify({ pjcode: 'acme', items: [] }),
1845
1848
  );
1846
1849
  const server = await startWebServer({
1847
1850
  accessToken: testToken,
@@ -1856,12 +1859,12 @@ describe('webServer token cookie redirect', () => {
1856
1859
  try {
1857
1860
  const withCookie = await requestServer(
1858
1861
  server,
1859
- '/projects/umino/prs/list.json',
1862
+ '/projects/acme/prs/list.json',
1860
1863
  { Cookie: `${CONSOLE_TOKEN_COOKIE}=${testToken}` },
1861
1864
  );
1862
1865
  expect(withCookie.statusCode).toBe(200);
1863
1866
  expect(JSON.parse(withCookie.body)).toEqual({
1864
- pjcode: 'umino',
1867
+ pjcode: 'acme',
1865
1868
  items: [],
1866
1869
  });
1867
1870
  } finally {
@@ -35,12 +35,9 @@ import {
35
35
  composeDashboardText,
36
36
  dashboardComposeFilesPresent,
37
37
  } from './dashboardComposeService';
38
- import { DASHBOARD_PROJECT_NAMES } from '../../../domain/usecases/dashboard/DashboardProjectCode';
39
38
 
40
39
  export const DEFAULT_WEB_PORT = 9980;
41
40
 
42
- export const DEFAULT_DASHBOARD_PROJECT_NAMES = DASHBOARD_PROJECT_NAMES;
43
-
44
41
  export const CONSOLE_TOKEN_HEADER = 'x-pv-token';
45
42
 
46
43
  export const CONSOLE_TOKEN_COOKIE = 'pv_token';
@@ -394,7 +394,7 @@ allowedIssueAuthors: 'user1, user2, user3'
394
394
  mockFetchReturningReadme(null);
395
395
  const configWithCommaSeparatedAuthors = {
396
396
  ...validConfig,
397
- allowedIssueAuthors: 'owner, umino-bot ,dependabot[bot]',
397
+ allowedIssueAuthors: 'owner, acme-bot ,dependabot[bot]',
398
398
  };
399
399
  jest
400
400
  .mocked(fs.readFileSync)
@@ -404,7 +404,7 @@ allowedIssueAuthors: 'user1, user2, user3'
404
404
  await handler.handle('config.yml', false);
405
405
 
406
406
  expect(capturedRunInputs[0][0]).toMatchObject({
407
- allowedIssueAuthors: ['owner', 'umino-bot', 'dependabot[bot]'],
407
+ allowedIssueAuthors: ['owner', 'acme-bot', 'dependabot[bot]'],
408
408
  });
409
409
  });
410
410
 
@@ -62,6 +62,7 @@ import { UpdateRateLimitCacheUseCase } from '../../../domain/usecases/UpdateRate
62
62
  import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
63
63
  import { RevertNotReadyReviewQueueIssueUseCase } from '../../../domain/usecases/RevertNotReadyReviewQueueIssueUseCase';
64
64
  import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueCommentRepository';
65
+ import { ProjectRequiredFieldCreateUseCase } from '../../../domain/usecases/ProjectRequiredFieldCreateUseCase';
65
66
  import { SetupTowerDefenceProjectUseCase } from '../../../domain/usecases/SetupTowerDefenceProjectUseCase';
66
67
  import { DailySecurityScanUseCase } from '../../../domain/usecases/DailySecurityScanUseCase';
67
68
  import { KyHttpRepository } from '../../repositories/KyHttpRepository';
@@ -334,6 +335,8 @@ export class HandleScheduledEventUseCaseHandler {
334
335
  systemDateRepository,
335
336
  ...githubRepositoryParams,
336
337
  );
338
+ const projectRequiredFieldCreateUseCase =
339
+ new ProjectRequiredFieldCreateUseCase(projectRepository);
337
340
  const setupTowerDefenceProjectUseCase = new SetupTowerDefenceProjectUseCase(
338
341
  projectRepository,
339
342
  issueRepository,
@@ -425,6 +428,7 @@ export class HandleScheduledEventUseCaseHandler {
425
428
  : null;
426
429
 
427
430
  const handleScheduledEventUseCase = new HandleScheduledEventUseCase(
431
+ projectRequiredFieldCreateUseCase,
428
432
  setupTowerDefenceProjectUseCase,
429
433
  actionAnnouncement,
430
434
  setWorkflowManagementIssueToStoryUseCase,
@@ -56,7 +56,7 @@ describe('writeDashboardRow', () => {
56
56
  it('writes a per-project row file with counts, pjcode and capturedAt', () => {
57
57
  writeDashboardRow({
58
58
  dashboardDataDir: dir,
59
- pjcode: 'um',
59
+ pjcode: 'ac',
60
60
  assigneeLogin: ASSIGNEE,
61
61
  issues: [
62
62
  makeIssue({ status: 'Unread' }),
@@ -65,9 +65,9 @@ describe('writeDashboardRow', () => {
65
65
  generatedAt: '2026-06-26T12:00:00.000Z',
66
66
  });
67
67
 
68
- const written = readJson(path.join(dir, 'projects', 'um.json'));
68
+ const written = readJson(path.join(dir, 'projects', 'ac.json'));
69
69
  const expected: DashboardRowFile = {
70
- pjcode: 'um',
70
+ pjcode: 'ac',
71
71
  capturedAt: '2026-06-26T12:00:00.000Z',
72
72
  unread: 1,
73
73
  todo: 0,
@@ -84,7 +84,7 @@ describe('writeDashboardRow', () => {
84
84
  it('is a no-op when dashboardDataDir is unset', () => {
85
85
  writeDashboardRow({
86
86
  dashboardDataDir: null,
87
- pjcode: 'um',
87
+ pjcode: 'ac',
88
88
  assigneeLogin: ASSIGNEE,
89
89
  issues: [makeIssue({ status: 'Unread' })],
90
90
  });
@@ -101,7 +101,7 @@ describe('writeDashboardRow', () => {
101
101
  });
102
102
  writeDashboardRow({
103
103
  dashboardDataDir: dir,
104
- pjcode: 'um',
104
+ pjcode: 'ac',
105
105
  assigneeLogin: null,
106
106
  issues: [makeIssue({ status: 'Unread' })],
107
107
  });
@@ -195,21 +195,21 @@ describe('writeInTmuxByHumanData', () => {
195
195
  });
196
196
 
197
197
  it('writes index files listing configured projects whose per-project file exists', () => {
198
- fs.writeFileSync(file('xmile.json'), '[]\n');
198
+ fs.writeFileSync(file('globex.json'), '[]\n');
199
199
  writeInTmuxByHumanData({
200
200
  ...baseParams(outDir),
201
- inTmuxProjectOrder: ['demo', 'xmile', 'xcare'],
201
+ inTmuxProjectOrder: ['demo', 'globex', 'initech'],
202
202
  });
203
203
  expect(readJson(file('index.json'))).toEqual({
204
- projects: ['demo', 'xmile'],
204
+ projects: ['demo', 'globex'],
205
205
  });
206
206
  expect(readJson(file('index.v2.json'))).toEqual({
207
207
  version: 2,
208
- projects: ['demo', 'xmile'],
208
+ projects: ['demo', 'globex'],
209
209
  });
210
210
  expect(readJson(file('index.v3.json'))).toEqual({
211
211
  version: 3,
212
- projects: ['demo', 'xmile'],
212
+ projects: ['demo', 'globex'],
213
213
  });
214
214
  });
215
215
 
@@ -4,6 +4,13 @@ import { LocalStorageCacheRepository } from './LocalStorageCacheRepository';
4
4
  import { LocalStorageRepository } from './LocalStorageRepository';
5
5
  import { ProjectRepository } from '../../domain/usecases/adapter-interfaces/ProjectRepository';
6
6
  import { FieldOption, Project } from '../../domain/entities/Project';
7
+ import {
8
+ DEPENDED_ISSUE_URL_FIELD_NAME,
9
+ NEXT_ACTION_DATE_FIELD_NAME,
10
+ NEXT_ACTION_HOUR_FIELD_NAME,
11
+ RequiredProjectFieldDefinition,
12
+ STORY_FIELD_NAME,
13
+ } from '../../domain/entities/RequiredProjectField';
7
14
  import { normalizeFieldName } from './utils';
8
15
 
9
16
  const ONE_HOUR_MS = 60 * 60 * 1000;
@@ -38,6 +45,8 @@ export class GraphqlProjectRepository
38
45
  | 'getByUrl'
39
46
  | 'updateStoryList'
40
47
  | 'updateStatusList'
48
+ | 'listFieldNames'
49
+ | 'createField'
41
50
  >
42
51
  {
43
52
  private readonly projectIdCache = new Map<string, string>();
@@ -339,10 +348,14 @@ export class GraphqlProjectRepository
339
348
  return null;
340
349
  }
341
350
  const nextActionDate = project.fields.nodes.find(
342
- (field) => normalizeFieldName(field.name) === 'nextactiondate',
351
+ (field) =>
352
+ normalizeFieldName(field.name) ===
353
+ normalizeFieldName(NEXT_ACTION_DATE_FIELD_NAME),
343
354
  );
344
355
  const nextActionHour = project.fields.nodes.find(
345
- (field) => normalizeFieldName(field.name) === 'nextactionhour',
356
+ (field) =>
357
+ normalizeFieldName(field.name) ===
358
+ normalizeFieldName(NEXT_ACTION_HOUR_FIELD_NAME),
346
359
  );
347
360
  const status = project.fields.nodes.find(
348
361
  (field) => normalizeFieldName(field.name) === 'status',
@@ -351,7 +364,8 @@ export class GraphqlProjectRepository
351
364
  throw new Error('status field is not found');
352
365
  }
353
366
  const story = project.fields.nodes.find(
354
- (field) => normalizeFieldName(field.name) === 'story',
367
+ (field) =>
368
+ normalizeFieldName(field.name) === normalizeFieldName(STORY_FIELD_NAME),
355
369
  );
356
370
  const workflowManagementStory = story?.options.find((option) =>
357
371
  normalizeFieldName(option.name).includes('workflowmanagement'),
@@ -363,7 +377,7 @@ export class GraphqlProjectRepository
363
377
  const dependedIssueUrlSeparatedByComma = project.fields.nodes.find(
364
378
  (field) =>
365
379
  normalizeFieldName(field.name).startsWith(
366
- 'dependedissueurlseparatedbycomma',
380
+ normalizeFieldName(DEPENDED_ISSUE_URL_FIELD_NAME),
367
381
  ),
368
382
  );
369
383
  const completionDate50PercentConfidence = project.fields.nodes.find(
@@ -442,6 +456,98 @@ export class GraphqlProjectRepository
442
456
  }
443
457
  return project;
444
458
  };
459
+ listFieldNames = async (project: Project): Promise<string[]> => {
460
+ const query = `query ListProjectV2FieldNames($projectId: ID!) {
461
+ node(id: $projectId) {
462
+ ... on ProjectV2 {
463
+ fields(first: 100) {
464
+ nodes {
465
+ ... on ProjectV2FieldCommon {
466
+ name
467
+ }
468
+ }
469
+ }
470
+ }
471
+ }
472
+ }`;
473
+ const response = await postGithubGraphqlJson<{
474
+ data?: {
475
+ node: {
476
+ fields: {
477
+ nodes: { name?: string }[];
478
+ };
479
+ } | null;
480
+ };
481
+ errors?: { message: string }[];
482
+ }>({
483
+ ghToken: this.ghToken,
484
+ query,
485
+ variables: { projectId: project.id },
486
+ });
487
+ if (!response.data || !response.data.node) {
488
+ const errorMessages = response.errors
489
+ ? response.errors.map((e) => e.message).join('; ')
490
+ : 'no data field in response';
491
+ throw new Error(
492
+ `GitHub GraphQL API returned no data for listFieldNames: ${errorMessages}`,
493
+ );
494
+ }
495
+ return response.data.node.fields.nodes
496
+ .map((field) => field.name)
497
+ .filter((name): name is string => typeof name === 'string');
498
+ };
499
+ createField = async (
500
+ project: Project,
501
+ field: RequiredProjectFieldDefinition,
502
+ ): Promise<void> => {
503
+ const mutation = `mutation CreateProjectV2Field($projectId: ID!, $dataType: ProjectV2CustomFieldType!, $name: String!, $singleSelectOptions: [ProjectV2SingleSelectFieldOptionInput!]) {
504
+ createProjectV2Field(input: {
505
+ projectId: $projectId
506
+ dataType: $dataType
507
+ name: $name
508
+ singleSelectOptions: $singleSelectOptions
509
+ }) {
510
+ projectV2Field {
511
+ ... on ProjectV2FieldCommon {
512
+ id
513
+ name
514
+ }
515
+ }
516
+ }
517
+ }`;
518
+ const response = await postGithubGraphqlJson<{
519
+ data?: {
520
+ createProjectV2Field: {
521
+ projectV2Field: { id: string; name: string };
522
+ };
523
+ };
524
+ errors?: { message: string }[];
525
+ }>({
526
+ ghToken: this.ghToken,
527
+ query: mutation,
528
+ variables: {
529
+ projectId: project.id,
530
+ dataType: field.dataType,
531
+ name: field.name,
532
+ singleSelectOptions:
533
+ field.dataType === 'SINGLE_SELECT'
534
+ ? field.options.map(({ name, color, description }) => ({
535
+ name,
536
+ color,
537
+ description,
538
+ }))
539
+ : null,
540
+ },
541
+ });
542
+ if (!response.data) {
543
+ const errorMessages = response.errors
544
+ ? response.errors.map((e) => e.message).join('; ')
545
+ : 'no data field in response';
546
+ throw new Error(
547
+ `GitHub GraphQL API returned no data for createField ${field.name}: ${errorMessages}`,
548
+ );
549
+ }
550
+ };
445
551
  updateStoryList = async (
446
552
  project: Project,
447
553
  newStoryList: (Omit<FieldOption, 'id'> & {
@@ -3092,7 +3092,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
3092
3092
 
3093
3093
  const { repository } = createApiV3CheerioRestIssueRepository();
3094
3094
  const result = await repository.findRelatedOpenPRs(
3095
- 'https://github.com/HiromiShikata/umino-corporait-operation/issues/30106',
3095
+ 'https://github.com/HiromiShikata/acme-corporait-operation/issues/30106',
3096
3096
  );
3097
3097
 
3098
3098
  expect(result).toHaveLength(1);
@@ -1,3 +1,5 @@
1
+ import { normalizeProjectFieldName } from '../../domain/entities/ProjectFieldName';
2
+
1
3
  // 0:50 , 1:10 = 0:20
2
4
  // 23:50 , 0:00 = 0:10
3
5
  // 0:20, 0:10 = 23:50
@@ -40,6 +42,4 @@ export const totalDuration = (
40
42
  return `${String(totalHour).padStart(2, '0')}:${String(totalMinute).padStart(2, '0')}`;
41
43
  };
42
44
 
43
- export const normalizeFieldName = (fieldName: string) => {
44
- return fieldName.toLowerCase().replace(/[\s-()]/g, '');
45
- };
45
+ export const normalizeFieldName = normalizeProjectFieldName;
@@ -0,0 +1,2 @@
1
+ export const normalizeProjectFieldName = (fieldName: string): string =>
2
+ fieldName.toLowerCase().replace(/[\s-()]/g, '');